Component/PureComponent/React.me

2019-12-03  本文已影响0人  晓风残月1994

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/
上一篇下一篇

猜你喜欢

热点阅读