美文网首页
React性能调优实践

React性能调优实践

作者: 追风的云月 | 来源:发表于2019-01-16 17:10 被阅读0次

    在介绍过前端优化方法后,我试图总结一下代码角度,自己在React项目实践的优化方法。下面从几个方面总结一下React代码优化:
    以下大部分方法是基于React的渲染角度去做优化,React的刷新过程中会导致大量不必要的diff计算,即使很多时候state没有发生变化,此时我们不需要更新UI,也就不需要diff计算,而我们正是需要找到办法来减少这一部分的性能消耗。

    1. 使用纯函数组件
    const FunctionalComponent = (props) => (
        <div>纯函数组件</div>
    )
    export default FunctionalComponent
    
    • 语法更简洁,比起有状态组件,少了很多代码,不用声明类,没有constructor等函数代码
    • 占内存更小(class 有 props context _context 等诸多属性),首次 render 的性能更好
    • 可以写成无副作用的纯函数,输入输出都是可控可预知的,并且便于测试
    • 可拓展性更强(函数的 compose,currying 等组合方式,比 class 的 extend/inherit 更灵活)
    2. 使用pureComponent组件或者在组件中实现shouldComponentUpdate方法

    首先我们要明确一点,React中只要父组件的render了,那么默认情况下就会触发子组件的render过程,子组件的render过程又会触发它的子组件的render过程,一直到React元素(即jsx中的<div>这样的元素)。当render过程到了叶子节点,即React元素的时候,diff过程便开始了,这时候diff算法会决定是否需要更新DOM元素。

    为了避免子组件无意义的render,React提供了shouldComponentUpdate方法,让我们自己选择要不要render组件,我们可以把props做一个比较,如果props没有变化,我们就不重渲染组件

    export default class ShouldUpdateComponent extends React.Component{
        constructor(props) {
            super(props);
        }
        shouldComponentUpdate(nextProps, nextState) {
            if (nextProps.custom !== this.props.custom) {
                return true
            }
            return false
        }
        render() {
            console.log('render')
            return <div>使用shouldComponentUpdate做了一个浅比较</div>
        }
    }
    

    或者使用PureComponent

    import React, { PureComponent } from 'react';
    export default class Foo extends PureComponent {
      //...
    }
    

    但是我们要注意一点,以上两种方法都是对props做了一个浅比较,无法判断生层次的属性改变,会导致组件不正常更新,关于这点,我们可以使用lodash的cloneDeep或者immutable库来实现不变的数据,这里之后详细探讨下。然而我们可以做的是,尽量扁平化props,避免过深的对象

    3. key

    对于循环或者迭代生成的元素,一定要有key值,这个想必大家都知道,因为在没有赋key的时候,React会在控制台输出警告信息。
    这里强调一下,不要用循环时的index做key,这是非常低效的做法,这个key是每次用来做Virtual DOM diff的,用index的话相当于用了随机键,每次更新都会重新渲染,要使用循环中唯一ID,一般来说是后台返回的该条数据的ID
    从diff算法的角度来看key的必须性,React diff中有一个策略,就是相同层级的一组子节点,它们可以通过唯一ID进行区分,如果ID没变,React认为这个节点可以复用,无需经历非常消耗性能的删除和构建操作,为了做到能复用节点,我们必须要给到子节点唯一ID,以便React进行判断。

    Ref详解-这篇文章讲解key可谓非常翔实

    4. React中的动态加载

    我一直在项目中使用react-loadable这个库来实现异步加载组件,大部分时候用来结合路由,实现路由级组件的按需加载。

    // 引入组件
    const TodoApp = Loadable({
        loader: () => import('../pages/todoapp'),
        delay: 300, // 避免loading组件的闪烁
        timedOut: 5000, // 超时处理
        loading: MyLoadingComponent // loading组件
    });
    // 放置于路由组件中 可做到不同路由的按需加载
    <Route exact path="/" component={TodoApp}/>
    
    // 自己定义的Loading组件
    import React from 'react';
    const MyLoadingComponent = ({isLoading, error, pastDelay, retry, timeOut}) => {
        // 处理延时展示loading情况
        if (pastDelay) {
            return <div>Loading...</div>;
        }
        // 处理组件发生错误的情况
        else if (error) {
            return <div>Error! <button onClick={ retry }>Retry</button></div>;
        }
        // 处理组件加载超时的组件
        else if (timeOut) {
            return <div>Timeout! <button onClick={ retry }>Retry</button></div>;
        }
        else {
            return null;
        }
    };
    export default MyLoadingComponent;
    
    5. 有关事件处理函数的优化

    我们知道最好不要在render中使用箭头函数或者bind进行this绑定,因为每次组件渲染都会触发render方法,那么会在每次组件渲染时创建一个新的函数,会引起一些性能消耗。所以最好的方法是render外定义事件函数并进行this绑定,然后传入组件。

    class Foo extends Component {
      // Note: this syntax is experimental and not standardized yet.
      handleClick = () => {
        console.log('Click happened');
      }
      render() {
        return <button onClick={this.handleClick}>Click Me</button>;
      }
    }
    

    在需要优化大量元素或使用依赖于React.PureComponent相等性检查的渲染树的场景下,使用箭头函数或者bind的方式为回调函数传递参数必然不是最优方案,此时我们可以通过data-属性传递参数

    handleClick(e) {
        this.setState({
          justClicked: e.target.dataset.letter
        });
      }
    
      render() {
        return (
            <ul>
              {this.state.letters.map(letter =>
                <li key={letter} data-letter={letter} onClick={this.handleClick}>
                  {letter}
                </li>
              )}
            </ul>
        )
      }
    
    6. 事件处理函数的节流和防抖
    7. 在ReactV16版本中可以使用的性能优化API
    • React.memo
      使用该api可以创建自动进行props浅比较的函数式组件,之前因为函数式组件没有自己的生命周期,也就无法应用shouldComponentUpdate做文章了,但是有了这个api一切都将不同,pure functional component诞生了!
    import React from 'react';
    const MemoComponent = React.memo(props => {
        return (
            <div>
                <div>使用react16新特性 memo 来做函数式组件的浅比较</div>
                {props.data}
            </div>
        )
    });
    export default MemoComponent;
    
    • lazy 和 Suspense
      作为React官方推出的异步加载方案,还是很值得一试的,实现的功能其实与react-loadable这个库类似,但是其中有不少异同的细节,之后希望专门写一篇文章讨论一下这个差异。
    import React, { Fragment, lazy, Suspense } from 'react';
    const ShouldUpdateComponent = lazy(() => import('../components/performance/shouldUpdateComponent'));
    <Suspense fallback={<div>Loading...</div>}>
        <ShouldUpdateComponent custom={this.state.render}/>
    </Suspense>
    

    使用该方法后,ShouldUpdateComponent这个组件以chunk的形式异步加载,起到了减少主文件代码体积的作用

    高性能 React:3 个新工具加速你的应用
    本文介绍了如何借助工具检测到react项目中不必要的组件渲染,值得借鉴

    相关文章

      网友评论

          本文标题:React性能调优实践

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