Skip to main content

渲染控制

React 渲染

render 阶段的作用

是根据一次更新中产生的新状态值,通过 React.createElement ,替换成新的状态,得到新的 React element 对象,新的 element 对象上,保存了最新状态值。 createElement 会产生一个全新的 props。到此 render 函数使命完成了。

接下来,React 会调和由 render 函数产生 chidlren,将子代 element 变成 fiber(这个过程如果存在 alternate,会复用 alternate 进行克隆,如果没有 alternate ,那么将创建一个),将 props 变成 pendingProps ,至此当前组件更新完毕。然后如果 children 是组件,会继续重复上一步,直到全部 fiber 调和完毕。完成 render 阶段。

React 几种控制 render 方法

两种方式:

  • 第一种就是从父组件直接隔断子组件的渲染,经典的就是 memo,缓存 element 对象。
  • 第二种就是组件从自身来控制是否 render ,比如:PureComponent ,shouldComponentUpdate 。

useMemo

PureComponent

规则就是浅比较 state 和 props 是否相等。(state 和 props 中的每一个属性是否===)

shouldComponentUpdate 的优先级高于 PureComponent

PureComponent 注意事项

父组件给是 PureComponent 的子组件绑定事件要格外小心:

  • 避免使用箭头函数

  • PureComponent 的父组件是函数组件的情况,绑定函数要用 useCallback 或者 useMemo 处理。

shouldComponentUpdate

shouldComponentUpdate 可以根据传入的新的 props 和 state ,或者 newContext 来确定是否更新组件

React.memo

React.memo(Component, compare);
  • React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。和 shouldComponentUpdate 相反,shouldComponentUpdate : 返回 true 组件渲染 , 返回 false 组件不渲染。

  • memo 当二个参数 compare 不存在时,会用浅比较原则处理 props ,相当于仅比较 props 版本的 pureComponent 。

  • memo 同样适合类组件和函数组件。

被 memo 包裹的组件,element 会被打成 REACT_MEMO_TYPE 类型的 element 标签,在 element 变成 fiber 的时候,fiber 会被标记成 MemoComponent 的类型。

react/src/ReactMemo.js
function memo(type, compare) {
const elementType = {
$$typeof: REACT_MEMO_TYPE,
type, // 我们的组件
compare: compare === undefined ? null : compare //第二个参数,一个函数用于判断prop,控制更新方向。
};
return elementType;
}

那么对于 MemoComponent React 内部又是如何处理的呢?首先 React 对 MemoComponent 类型的 fiber 有单独的更新处理逻辑 updateMemoComponent 。首先一起看一下主要逻辑:

react-reconciler/src/ReactFiberBeginWork.js
function updateMemoComponent() {
if (updateExpirationTime < renderExpirationTime) {
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual; //如果 memo 有第二个参数,则用二个参数判定,没有则浅比较props是否相等。
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime
); //已经完成工作停止向下调和节点。
}
}
// 返回将要更新组件,memo包装的组件对应的fiber,继续向下调和更新。
}

主要逻辑:

  • 通过 memo 第二个参数,判断是否执行更新,如果没有那么第二个参数,那么以浅比较 props 为 diff 规则。如果相等,当前 fiber 完成工作,停止向下调和节点,所以被包裹的组件即将不更新。
  • memo 可以理解为包了一层的高阶组件,它的阻断更新机制,是通过控制下一级 children ,也就是 memo 包装的组件,是否继续调和渲染,来达到目的的。
注意事项:
// 尽量不要这么尝试
const NewIndex = React.memo(Index, () => true);

不要通过上面的方式来组织组件渲染

打破渲染限制

  • forceUpdate

类组件更新如果调用的是 forceUpdate 而不是 setState ,会跳过 PureComponent 的浅比较和 shouldComponentUpdate 自定义比较。其原理是组件中调用 forceUpdate 时候,全局会开启一个 hasForceUpdate 的开关。当组件更新的时候,检查这个开关是否打开,如果打开,就直接跳过 shouldUpdate 。

  • context 穿透

上述的几种方式,都不能本质上阻断 context 改变,而带来的渲染穿透,所以选择了消费 context ,就要承担 context 改变,带来的更新作用。

渲染控制流程图

渲染控制流程图