为什么说 React 的响应粒度是组件级别的 ?

80 阅读4分钟

著有《React18 设计原理》《javascript地月星》等多个专栏。 欢迎关注。

创作不易,内容有帮助记得 ❤️点赞,⭐️收藏 ,🔥评论 ~

本文全部都是原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解 ~

推荐指数(值得一读):⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

原文 👉 juejin.cn/post/763192…

React 的响应粒度是组件级别的

这个概念表达的是,节点是否有变化、选择 bailout 还是 reconcile 的判断方式 ——— lanes 和 react ele.props,这个判断方式是组件级别的。

current.memoizedProps 和 workInProgress.pendingProps 的值就是react_ele.props

function MyComp(){
    let [xx,setXX] = useState();
    return (<div onClick=setXX>
               <div>
                    <p>{xx}</<p>
               </div>
            </div>)
}

父组件中:
<div>
  <MyComp {父组件传入的属性没有发生任何变化} />
</div>

组件useState发生了变化,

  • 首先,遍历 MyComp Fiber,这个组件的父组件没有变化,所以 MyComp Fiber 的 ele.props 没有变化, oldProps===newProps 。但是组件 useState 有变化,所以 MyComp Fiber 的 lanes 是被标记的。执行 renderWithHooks 得到新的 react ele。组件和内部元素的 react ele 变成新的对象。

  • 接着,遍历组件内的元素 Fiber,使用 ele.prors 判断,是父组件重新生成的,于是 oldProps !== newProps,整个组件都需要 reconcile。——— 没有做到精准感知元素 Fiber 的 lanes,而是使用整个组件的 react ele。这样子,虽然只是 p 使用了 state,其实只要协调p就好了,但实际上包括 div 全部都reconcile了。不会 bailout div,然后 reconcile p,而是整个组件所有的元素都 reconcile。

这个过程中, react ele 是以组件为单位生成的,只有组件类型的 Fiber 会执行 renderWithHooks(整个应用的 jsx->react ele 不是一次性生成的。而是按组件生成,遍历到组件 Fiber 的时候生成组件的 ele)。至于 lanes,只有组件 lanes 会有效。

产生的问题,1. 如果一个很庞大的组件(元素数量很多,层级很深),它只有 1 个状态发生了简单的变化,并且只有 1 个子元素中简单的使用了,但却会导致整个组件,包括子组件全部重新渲染,即使子组件没有变化。2. 如果子组件也有变化,但是等级不高,按设计意图,不同等级 lanes 应该分批处理,但父组件的更新把子组件一起更新了,违背了分级处理的设计意图。例如父组件 lanes =1,子组件 lanes=64。

这两种情况都会有性能问题。对于 1,应该把组件变小,或者把这个状态分离到独立子组件。对于 2,应该给子组件缓存。

状态的声明和状态的使用最大的问题:只有组件有 state。元素 Fiber只能没有自己的 state。元素Fiber只能使用组件 Fiber 的 state。

观察 MyComp 的例子,一个最大的现象:p 使用的实际是 MyComp 的 state,而不是自己的 state。这个 state 是归 MyComp 管的,这个状态的变化是 MyComp 的状态的变化!

  1. 组件 Fiber,能在自己函数“体内”写useState,lanes 能标记。
  2. 元素 Fiber,没有自己的函数声明去写 useState;元素使用的是组件的 state;这不算作自己的 lanes。例如 p没有自己的“身体”写useState。p 使用的是组件的状态。

所以,虽然 lanes 是标记节点有更新,但实际上元素的 lanes 一直是 0。 (React 有时候会直接把组件的 lanes 同步给组件内元素的 lanes,但实际上元素 lanes 没什么用处)

useState是组件级别的,这就是为什么 lanes 一定是组件级别的。

props 、react ele 和 lanes 都来自 state

  1. 点击 --> 组件 state --> lanes!=0 (lanes 来自 state)
  2. 点击 --> 组件 state --> lanes!=0 --> 组件 react ele 重新生成 (ele 来自 state)
  3. 点击 --> 组件 state --> lanes!=0 --> 组件 react ele 重新生成 --> 子组件 props 是新的对象 --> 子组件 react ele 也重新生成 (props 来自 state)
    (<MyComp2 view = {xx}/>,如果子组件使用了 props,props 就是父组件 state。此处说的props是这个props。不是react ele.props)
  4. 另一种变化来源是上下文 context-provider

可以认为,它们都是组件级别的。

总结

遍历当前节点,准备创建当前子节点的时候进行的、“是否要协调”的判断。

  • cur.lanes 判断自己状态变化。
  • ele.props 判断自己属性的变化。(属性来自“父“组件状态)

不论是自己的状态发生变化,还是自己的属性发生变化,都要重新协调自己名下的子节点。

元素类型的“父“组件是元素所在的组件自身。

举例:
如果遍历的是组件,准备创建组件的子 Fiber, cur.lanes 判断组件的状态。ele.props 判断组件的属性(父组件的状态)。
如果遍历的是元素,准备创建元素的子 Fiber,ele.props 判断元素的属性(组件的状态)。

结论:是不是要协调其实不看自己,看组件的状态、父组件的状态。状态是组件级的响应粒度。