美文网首页
[react]10、react性能优化

[react]10、react性能优化

作者: 史记_d5da | 来源:发表于2021-11-07 15:24 被阅读0次

    1、列表&key

    一、React更新流程

    React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI。

    • 同层节点之间相互比较,不会垮节点比较;
    • 不同类型的节点,产生不同的树结构;
    • 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

    1、对比不同类型的元素
    当节点为不同的元素,React会拆卸原有的树,并且建立起新的树

    <div>
      <Child />
    </div>
    ~~~~~~~~~~~
    <span>
      <Child />
    </span>
    

    2、对比同一类型的元素
    当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性
    1)、React 知道只需要修改 DOM 元素上的 className 属性

    <div classname="before" title="stuff" />
    <div classname="after" title="stuff" />
    

    2)、当更新 style 属性时,React 仅更新有所更变的属性

    <div style={{ color: 'red', fontWeight: 'bold' }} />
    <div style={{ color: 'green', fontWeight: 'bold' }} />
    

    3)、同类型的组件元素
    组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps() 和 componentWillUpdate() 方

    3、对子节点进行递归
    在默认条件下,当递归 DOM 节点的子元素时,React 会同 时遍历两个子元素的列表;当产生差异时,生成一个 mutation。
    1)、在最后插入一条数据的情况,前面两个比较是完全相同的,所以不会产生mutation,最后一个比较,产生一个mutation,将其插入到新的 DOM树中即可;

    <ul>
      <li>first</li>
      <li>second</li>
    </ul>
    ~~~~~~~~~
    <ul>
      <li>first</li>
      <li>second</li>
      <li>third</li>
    </ul>
    

    最后一次插入third,只会产生一个mutation
    2)、如果我们是在中间插入一条数据
    React会对每一个子元素产生一个mutation,这种低效的比较方式会带来一定的性能问题

    <ul>
      <li>first</li>
      <li>second</li>
    </ul>
    ~~~~~~~~~
    <ul>
      <li>zero</li>
      <li>first</li>
      <li>second</li>
    </ul>
    

    在第一次插入zero,后面first、second都会产生mutation。

    二、keys的优化

    我们在前面遍历列表时,总是会提示一个警告,让我们加入一个key属性

    • 在最后位置插入数据,这种情况,有无key意义并不大
    • 在前面插入数据,这种做法,在没有key的情况下,所有的li都需要进行修改
    • 当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素
      key的注意事项:
      1)、key应该是唯一的;
      2)、key不要使用随机数(随机数在下一次render时,会重新生成一个数字);
      3)、使用index作为key,对性能是没有优化的;

    2、 shouldComponentUpdate

    在render调用之前会调用shouldComponentUpdate,不建议在 shouldComponentUpdate() 中进行深层比较或使用 JSON.stringify()。这样非常影响效率,且会损害性能。
    该方法有两个参数:

    • nextProps 修改之后,最新的props属性
    • nextState 修改之后,最新的state属性

    该方法返回值是一个boolean类型

    • 返回值为true(默认返回true),那么就需要调用render方法
    • 返回值为false,那么就不需要调用render方法
    shouldComponentUpdate(nextProps, nextState) {
       if (this.state.counter !== nextState.counter) {
            return true;
       }
       return false;
    }
    

    源码分析

    // react-reconciler/src/forks/ReactFiberClassComponent.new.js Line 291
    function checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext,
    ) {
      const instance = workInProgress.stateNode;
      if (typeof instance.shouldComponentUpdate === 'function') { // 判断instance有无shouldComponentUpdate方法
        const shouldUpdate = instance.shouldComponentUpdate(
          newProps,
          newState,
          nextContext,
        );
        return shouldUpdate;
      }
      
      // 如果isPureReactComponent=true就会进行浅层比较
      if (ctor.prototype && ctor.prototype.isPureReactComponent) {
        return (
          !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
        );
      }
      return true;
    }
    

    3、PureComponent

    React.PureComponentReact.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。

    import React, { Component, PureComponent } from 'react'
    // header
    class Header extends PureComponent {
        render() {
            console.log('App Header被调用')
            return (
                <h2>我是Header组件</h2>
            )
        }
    }
    
    export class App extends PureComponent {
        constructor(props) {
            super(props);
            this.state = {counter: 0}
        }
        render() {
            console.log('App 被调用')
            return (
                <div>
                    <h2>{"当前计数:" + this.state.counter}</h2>
                    <button onClick={e => this.increment()}>+1</button>
                    <Header/>
                </div>
            )
        }
        increment () {
            this.setState({
                counter: this.state.counter + 1
            })
        }
    }
    export default App
    

    源码分析

    PureComponent源码

    // react/src/ReactBaseClasses.js Line 129
    /**
     * Convenience component with default shallow equality check for sCU.
     */
    function PureComponent(props, context, updater) {
      this.props = props;
      this.context = context;
      // If a component has string refs, we will assign a different object later.
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
    pureComponentPrototype.constructor = PureComponent;
    // Avoid an extra prototype jump for these methods.
    Object.assign(pureComponentPrototype, Component.prototype);
    // isPureReactComponent标记为true
    pureComponentPrototype.isPureReactComponent = true; 
    
    export {Component, PureComponent};
    

    shallowEqual源码

    // shared/shallowEqual
    /**
     * Performs equality by iterating through keys on an object and returning false
     * when any key has values which are not strictly equal between the arguments.
     * Returns true when the values of all keys are strictly equal.
     */
    function shallowEqual(objA: mixed, objB: mixed): boolean {
      if (is(objA, objB)) { // 如果是同一个对象,直接返回true
        return true;
      }
    
      if (
        typeof objA !== 'object' ||
        objA === null ||
        typeof objB !== 'object' ||
        objB === null
      ) {
        return false;
      }
    
      const keysA = Object.keys(objA);
      const keysB = Object.keys(objB);
    
      if (keysA.length !== keysB.length) {
        return false;
      }
    
      // Test for A's keys different from B.
      for (let i = 0; i < keysA.length; i++) {
        if (
          !hasOwnProperty.call(objB, keysA[i]) ||
          !is(objA[keysA[i]], objB[keysA[i]])
        ) {
          return false;
        }
      }
    
      return true;
    }
    

    4、memo

    React.memo 为高阶组件。
    如果组件在相同 props 的情况下渲染相同的结果,那么可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
    React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
    默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现

    function MyComponent(props) {
      /* 使用 props 渲染 */
    }
    function areEqual(prevProps, nextProps) {
      /*
      如果把 nextProps 传入 render 方法的返回结果与
      将 prevProps 传入 render 方法的返回结果一致则返回 true,
      否则返回 false
      */
    }
    export default React.memo(MyComponent, areEqual);
    

    源码分析

    memo函数入口

    // react/src/ReactMemo.js Line 12
    export function memo<Props>(
      type: React$ElementType,
      compare?: (oldProps: Props, newProps: Props) => boolean,
    ) {
      // 调用compare方法
      const elementType = {
        $$typeof: REACT_MEMO_TYPE, // memo函数的标志
        type,
        compare: compare === undefined ? null : compare,
      };
      return elementType;
    }
    

    updateMemoComponent:使用memo函数实现的类会调用此函数

    // react-reconciler/src/forks/ReactFiberBeginWork.new.js Line 384
    function updateMemoComponent(
      current: Fiber | null,
      workInProgress: Fiber,
      Component: any,
      nextProps: any,
      updateLanes: Lanes,
      renderLanes: Lanes,
    ): null | Fiber {
      if (current === null) {
        const type = Component.type;
        if (
          isSimpleFunctionComponent(type) &&
          Component.compare === null &&
          // SimpleMemoComponent codepath doesn't resolve outer props either.
          Component.defaultProps === undefined
        ) {
          let resolvedType = type;
          // If this is a plain function component without default props,
          // and with only the default shallow comparison, we upgrade it
          // to a SimpleMemoComponent to allow fast path updates.
          workInProgress.tag = SimpleMemoComponent;
          workInProgress.type = resolvedType;
          return updateSimpleMemoComponent(
            current,
            workInProgress,
            resolvedType,
            nextProps,
            updateLanes,
            renderLanes,
          );
        }
        const child = createFiberFromTypeAndProps(
          Component.type,
          null,
          nextProps,
          workInProgress,
          workInProgress.mode,
          renderLanes,
        );
        child.ref = workInProgress.ref;
        child.return = workInProgress;
        workInProgress.child = child;
        return child;
      }
      const currentChild = ((current.child: any): Fiber); // This is always exactly one child
      if (!includesSomeLane(updateLanes, renderLanes)) {
        // This will be the props with resolved defaultProps,
        // unlike current.memoizedProps which will be the unresolved ones.
        const prevProps = currentChild.memoizedProps;
        // Default to shallow comparison
        // 判断compare是否存在,决定使用compare还是shallowEqual
        let compare = Component.compare;
        compare = compare !== null ? compare : shallowEqual;
        if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
          return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
        }
      }
      // React DevTools reads this flag.
      workInProgress.flags |= PerformedWork;
      const newChild = createWorkInProgress(currentChild, nextProps);
      newChild.ref = workInProgress.ref;
      newChild.return = workInProgress;
      workInProgress.child = newChild;
      return newChild;
    }
    

    相关文章

      网友评论

          本文标题:[react]10、react性能优化

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