美文网首页
React开发实践--3对象引用的误区

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

作者: 继续海阔天空 | 来源:发表于2018-06-03 13:54 被阅读48次

    一个电商应用,有他的一个基本的结构。为了简化我们的代码,我们每一个页面组件外面都会再套上一层<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>
                            )}
                        />
                    );
                }
            }`

    相关文章

      网友评论

          本文标题:React开发实践--3对象引用的误区

          本文链接:https://www.haomeiwen.com/subject/amsjsftx.html