美文网首页
Component/PureComponent/React.me

Component/PureComponent/React.me

作者: 晓风残月1994 | 来源:发表于2019-12-03 13:16 被阅读0次

    1. Component 和 PureComponent

    react 包暴露出来的其中两个基础的 class(源码中是以构造函数的形式):ComponentPureComponent

    App extends React.Component {...} 用的炉火纯青,但后者很少用。

    React 生命周期函数中有一个 shouldComponentUpdate(nextProps, nextState) , 此方法缺省时总是返回 true,即只要当前组件调用 this.setState(),或者父组件产生更新,那么当前组件总是会更新。

    这可能有些性能浪费,所以你可以主动配置该生命周期函数,判断当 propsstate 真的发生了变化,才返回 true。

    或者使用 PureComponent,其源码中有句注释:

    Convenience component with default shallow equality check for sCU.

    它实现了默认的浅比较方法,即:shouldComponentUpdate(nextProps, nextState) 方法。

    另外可以是使用 this.forceUpdate() 实现无论如何,强制更新。

    [图片上传失败...(image-d331d4-1575350157935)]

    简单看一下源码:

    /**
     * Base class helpers for the updating state of a component.
     */
    function Component(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;
      // We initialize the default updater but the real one gets injected by the
      // renderer.
      this.updater = updater || ReactNoopUpdateQueue;
    }
    
    /*
     * @param {object|function} partialState Next partial state or function to
     *        produce next partial state to be merged with current state.
     * @param {?function} callback Called after state is updated.
     * @final
     * @protected
     */
    Component.prototype.setState = function(partialState, callback) {
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    
    /*
     * @param {?function} callback Called after update is complete.
     * @final
     * @protected
     */
    Component.prototype.forceUpdate = function(callback) {
      this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
    };
    
    
    function ComponentDummy() {}
    ComponentDummy.prototype = Component.prototype;
    
    /**
     * 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);
    pureComponentPrototype.isPureReactComponent = true;
    

    源码中可以看出两个“类” 本身定义时没多大区别,PureComponent 只是继承了 Component,并多设置了一个属性 pureComponentPrototype.isPureReactComponent = truePureComponent 这个标记就很有用了,会在别处被用来指导是否进行浅比较,从而决定是否更新,props 或者 state 发生变化后,就应该更新:

    if (type.prototype && type.prototype.isPureReactComponent) {
      shouldUpdate =
        !shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
    }
    

    3. React.memo

    3.1 基本用法

    React.memo() 是 React v16.6 中引入的新功能,和 React.PureComponent 类似,可以用于函数组件的渲染,第一个参数是函数组件,第二个可选参数是自定义的判断函数:

    React.memo(Component, [areEqual(prevProps, nextProps)]);
    
    export function Movie({ title, releaseDate }) {
      return (
        <div>
          <div>Movie title: {title}</div>
          <div>Release date: {releaseDate}</div>
        </div>
      );
    }
    
    export const MemoizedMovie = React.memo(Movie);
    
    // 第二个可选参数
    function moviePropsAreEqual(prevMovie, nextMovie) {
      return prevMovie.title === nextMovie.title
        && prevMovie.releaseDate === nextMovie.releaseDate;
    }
    
    const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
    

    props 和 state 变化后,react 默认总是执行 render,当然,即使得到了新的虚拟 DOM,也会和旧的 DOM 做 diff 对比,只有产生了实质变化,才更新真实 DOM。而使用本文介绍的 React.PureComponentReact.memo(),则是为了在一开始就试图跳过组件的重新渲染,自然也更不会再对比 虚拟 DOM 了。

    3.2 使用场景

    React.memo 使用场景是组件频繁 re-render,且每次接收的 props 经常是相同的值。

    3.3 注意事项

    避免被 callback 破坏初衷

    使用 React.memo 另外要注意如果 props 中有父组件传来的 callback,则应保证传入的 callback 每次是相同的实例,反面教材:

    function MyApp({ store, cookies }) {
      return (
        <div className="main">
          <header>
            <MemoizedLogout
              username={store.username}
              onLogout={() => cookies.clear('session')}
            />
          </header>
          {store.content}
        </div>
      );
    }
    
    function Logout({ username, onLogout }) {
      return (
        <div onClick={onLogout}>
          Logout {username}
        </div>
      );
    }
    
    const MemoizedLogout = React.memo(Logout);
    

    可以在父组件中使用 useCallback() 来保存 callback,这样即使多次 render 也是最初的 callback 实例:

    function MyApp({ store, cookies }) {
      const onLogout = useCallback(
        () => cookies.clear('session'), 
        [cookies]
      );
      return (
        <div className="main">
          <header>
            <MemoizedLogout
              username={store.username}
              onLogout={onLogout}
            />
          </header>
          {store.content}
        </div>
      );
    }
    

    useState()

    使用 useState() 时若 state 改变,react 总是会保证 re-render 组件,即使该组件使用了 React.memo()

    3.4 尾记

    对 react 的这些优化究竟真的变快了,还是没什么区别,亦或者强行优化反而“反模式”,这就需要一定的衡量手法和指标:profiling


    参考:

    1. https://dmitripavlutin.com/use-react-memo-wisely/

    相关文章

      网友评论

          本文标题:Component/PureComponent/React.me

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