React开发实践--3对象引用的误区

一个电商应用,有他的一个基本的结构。为了简化我们的代码,我们每一个页面组件外面都会再套上一层<BaseLayout/> ,通过配置<BaseLayout/>,我们可以控制每一个页面是否需要使用公共页头(header),公共的底部导航(footer)。这当然也是一些很基础的功能了,在此不再赘述。

基于上面的需要,我们最初写了下面的代码

        const RouteWithLayout = ({loader = null, exportName = null, hideFooter = true, hideReturnTop = false, ...rest}) => {
                const loadableOpts = {
                    loader,
                    loading: LoadingComponent
                };

                if (exportName) {
                    loadableOpts.render = (loaded, props) => { // eslint-disable-line  react/display-name
                        const Component = loaded[exportName];
                        return <Component {...props} />;
                    };
                }
                const LoadableComponent = Loadable(loadableOpts);
                return (
                    <Route {...rest} render={matchProps => (
                        <BaseLayout hideFooter={hideFooter} hideReturnTop={hideReturnTop} {...matchProps}>
                            <LoadableComponent {...matchProps} />
                        </BaseLayout>
                        )}
                    />
                );
        };

看了前面 part-1的同学可能知道,我们在项目中引入了React-loadable这个第三方库,上面的代码也是基于它的实现。乍一看,他似乎没有问题。接下来,我们只需要写这样的配置就ok了。

        <RouteWithLayout exact path="/shopping-cart" hideFooter={false} hideReturnTop={true} loader={() => import('./ShoppingCart')} />

但是,在2-实现一个类似客户端的商品轮播图阅览交互 里面,我也已经提到过,我在此处遇到了一个坑。这个坑,也正是与上面我展示的代码有关。

再来简单回顾一下,当路由从/products/:productId 到/products/:productId/showpic,事实上,动态引入的组件还是那个ProductDetail组件。按照我们的期待,如果前后两次引入的是同一个组件,这个组件需要update,而不是重新mount。但是就如同我在《React开发实践--2》里面介绍的那样,当路由发生改变的时候,</ProductDetail />竟然进入了下一个生命周期。

究其原因,由于路由发生了改变,如果RouteWithLayout没有写其他的生命周期方法的话,那它必然会得到更新。那么我们来看看RouteWithLayout组件更新之后的影响。这时候我们发现LoadableComponent这个变量的引用实际上发生了变化。因此造成当render<LoadableComponent {...matchProps} />时,需要到下一个生命周期重新render。

可是对象引用发生了变化这种事情,我们应该怎么理解呢?说起来,这其实考察的还是JavaScript的基础。让我们暂时忘掉React这个框架,只提JavaScript。来看看下面这几行代码

let arr = []
for(let i=0;i<2;i++) {
    const foo={a:1};
    arr.push(foo)
}

console.log(arr[0]===arr[1]);  //false

大概学过半年JavaScript的同学,也都能明白这里为什么arr[0]和arr[1]并不相等了。这里还是谈谈我对这件事情的理解:

对于for循环内部的每次执行,都是一个block,每一个 block之间都相互独立。所以,我们也可以将上面的代码进行拆解。

let arr = []
 {
    const foo={a:1};
    arr.push(foo)
}
 {
    const foo={a:1};
    arr.push(foo)
}

console.log(arr[0]===arr[1]);  //false

这个时候,就更好理解了。对于每一个块里面的i变量来说,他只在当前块内有效,当前块执行完毕之后,他就会被垃圾回收,销毁掉了。因此当我们比较的时候,arr[0]和arr[1]之间就不相等了。

有同学可能会问了,你用的是ES6 的语法。ES5是这样的吗?of course!

var arr = []
for(var i=0;i<2;i++) {
    var foo={a:1};
    arr.push(foo)
}

console.log(arr[0]===arr[1]);  //false
console.log(i);

关于我的这个示例,ES5 和 ES6的结果并没有区别。如果硬要说有什么区别的话,那就是i这个变量是否在块级(block)之外继续有效。那相对于我本次讨论,实际上是另外一个话题了。

既然说到了这里,我们再想一想。怎么样才能让arr[0]与arr[1]相等呢?在JavaScript,只能是在比较的两者为同一引用的情况下,才能实现严格相等。所以按照这个思路,我们可以对上面的代码进行个改写。

const foo={a:1};
let arr = []
for(let i=0;i<2;i++) {
    arr.push(foo)
}

console.log(arr[0]===arr[1]);  //true

没错,就是把foo的生命提到for循环之外。这样的话,每次推入到arr这个数组中的值的引用就一定是相同的了。

这时候,让我们重新回到刚才遇到问题的React代码。RouteWithLayout当前是一个无状态组件,只要它的父组件发生了更新,它也会相应地发生更新。然后执行相应的代码。这其实就类似于我们上面提到的for循环结构。每一次RouteWithLayout执行的时候,都像是for循环在执行其中一个block,不难理解,每一次LoadableComponent的引用发生了变化。

所以,还是上面的思路,我们把LoadableComponent的声明放到render()方法之外,怎么做到呢?改写无状态组件为有状态组件。将这个对象声明以状态的方式存在于组件当中。这样就可以实现我们想要的效果,即如果动态加载的是同一个组件,则组件只会发生更新,而不会进入下一个生命周期。相应的代码在《React开发实践--2》中已经提到过了,再粘贴一遍。

        `
        import React from 'react';
        import ReactDOM from 'react-dom';
        import PropTypes from 'prop-types';
        import {BrowserRouter, Route, Switch, Redirect} from 'react-router-dom';
        import BaseLayout from './BaseLayout';
        import Loading from 'react-loading';
        import Loadable from 'react-loadable';


        const LoadingComponent = props => {
        // something 
        };


        class RouteWithLayout extends React.Component {
            state = {
                loader: () => {},
                exportName: null,
                LoadableComponent: null
            }

            static getDerivedStateFromProps(nextProps, prevState) {
            
                if (nextProps.loader.toString() === prevState.loader.toString() && nextProps.exportName === prevState.exportName) return null;
                const { loader, exportName } = nextProps;
                const loadableOpts = {
                    loader,
                    loading: LoadingComponent
                };

                if (exportName) {
                    loadableOpts.render = (loaded, props) => {
                        const Component = loaded[exportName];
                        return <Component {...props} />;
                    };
                }
                return {
                    loader,
                    exportName,
                    LoadableComponent: Loadable(loadableOpts)
                };
            }

            render() {
                const { hideFooter, hideReturnTop, ...rest} = this.props;
                return (
                    <Route {...rest} render={matchProps => (
                        <BaseLayout hideFooter={hideFooter} hideReturnTop={hideReturnTop} {...matchProps}>
                            <this.state.LoadableComponent {...matchProps} />
                        </BaseLayout>
                        )}
                    />
                );
            }
        }`
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,059评论 0 13
  • 前端开发面试题 <a name='preface'>前言</a> 只看问题点这里 看全部问题和答案点这里 本文由我...
    自you是敏感词阅读 732评论 0 3
  • 连续98天灵修经文 【诗31:14】耶和华啊,我仍旧倚靠你。我说:“你是我的神。” 《感动》感恩宣告耶和华你是我的...
    报佳音阅读 153评论 0 0
  • c59ffede9db6阅读 153评论 0 0
  • 没有人是完美的、永远不犯错该的,下次改正就好。另一方面,自己有事没做好,别人指出来也很正常,都是对自己的帮助,不要...
    陈健_6f12阅读 175评论 0 0