著有《React18 设计原理》《javascript地月星》等多个专栏。 欢迎关注。
创作不易,有帮助别忘了点赞,收藏,评论 ~ 你的鼓励是我继续挖干货的动力。
本文全部都是原创内容,商业转载请联系作者获得授权,非商业转载需注明出处,感谢理解 ~
推荐指数(值得一读):⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
React 的响应粒度是组件级别的
这个概念表达的是,节点是否有变化、选择 bailout 还是 reconcile 的判断方式——— lanes 和 props,这个判断方式是组件级别的。
function MyComp(){
let [xx,setXX] = useState();
return (<div onClick=setXX>
<div>
<p>{xx}</<p>
</div>
</div>)
}
父组件中:
<div>
<MyComp {父组件传入的属性没有发生任何变化} />
</div>
组件useState发生了变化,
首先,遍历组件Fiber,oldProps===newProps,这个组件的父组件没有变化,所以组件的 props 没有变化。但是组件useState有变化,所以组件的 lanes 被标记。renderWithHooks得到新的react ele。组件和内部元素的react ele变成新的对象。
接着,遍历组件内的元素Fiber,使用 prors 判断,这时 props 的值是 react ele,于是 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 管的!
- 组件 Fiber,能在自己函数“体内”写useState,lanes 能标记。
- 元素 Fiber,没有自己的函数声明去写 useState;元素使用的是组件的 state;这不算作自己的 lanes。例如 p没有自己的“身体”写useState。p 使用的是组件的状态。
所以,虽然 lanes 是标记节点有更新,但实际上元素的 lanes 一直是 0。 (React 有时候会直接把组件的 lanes 同步给组件内元素的 lanes,但实际上元素 lanes 没什么用处)
useState是组件级别的,这就是为什么 lanes 一定是组件级别的。
props 、 react ele 和 lanes 都来自 state
- 点击-->组件 state --> lanes!=0
- 点击-->组件 state-->如果子组件使用了 props,props 就是 state。,子组件 props 就是父组件 state。父、子组件协调。
- 点击-->组件 state-->lanes!=0-->react ele 重新生成。整个组件协调。
- 另一种变化来源是上下文 context-provider
可以认为,它们都是组件级别的。