'React 中的更新'
关于React源码-1 - 顶层API
参考和引用/使用的链接 ↓
编撰的时候 ReactJS 的版本已经来到了 v16.9
但是辅学的教程用的是 v16.6 所以会以 v16.6 为主的基础上参考 v16.9 的代码。
react-dom-render
用 create-react-app 创建的项目的项目入口 index.js 里都会有下面这个代码
1 | ReactDOM.render(<App />, document.getElementById('root')) |
也都清楚是调用了 ReactDOM 的 render 方法把 App 这个组件挂在 id 为 root 的节点上。
源码
在 packages/react-dom/src/client/ReactDOM.js 里
因为 v16.6 和 v16.9 的代码差异较大,采用的源码都是 v16.9 的。
尤其在 legacyRenderSubtreeIntoContainer() 里开始体现出来,不过其实就算看起来有差异,真正比较起来思路并没有变化,就连 TODO 注释都一样。。。
1 | const ReactDOM: Object = { |
可以看到 render 函数接收三个参数,分别为 React 元素、DOM 容器以及一个 callback 。
返回值是调用了 legacyRenderSubtreeIntoContainer 函数,除了把 render 的参数都传入之外,第一个和第四个参数分别为 null 和 false 。
1 | // 有做一些删减 |
调用函数之后,因为传入的 DOMContainer 是一个普通的 DOM 节点,所以 !root 会是 true 所以 16 行会进入 if 语句块,给 root 参数和 container 的 _reactRootContainer 属性进行了赋值。赋值操作引用了 legacyCreateRootFromDOMContainer() 函数,
1 | function legacyCreateRootFromDOMContainer( |
函数里又声明了一个变量 shouldHydrate ,如果传入的 forceHydrate 不为 true 的话就会调用 shouldHydrateDueToLegacyHeuristic(container),
1 | function shouldHydrateDueToLegacyHeuristic(container) { |
其实看完了这个函数,也清楚的知道每一步在做什么,甚至都知道如果容器是这个 < div id=”root” >< div/ > (这里为了不让md识别到标签,在标签里添加了空格,所以看起来会有点奇怪),shouldHydrateDueToLegacyHeuristic() 返回的就是 false ,也还是不清楚这里的意义是什么。
↑ 解决的 参考资料
其实这一步的意义在于 React 虽然知道调用的 ReactDOM.render() ,但是还是会通过这一步进行检测是否可以 hydrate 。如果这一步返回的是 true, 则会在 if (__DEV__) 里进行提醒,提醒你说这里建议使用 hydrate 。
最后移除了所有的子节点之后,返回了一个 ReactSyncRoot 实例。
1 | function ReactSyncRoot( |
createContainer 是 react-reconciler 包里的一个函数,react-reconciler 是进行与平台无关的节点操作的包。
传入的参数是分别是 container, LegacyRoot 以及 shouldHydrate 。
第 1、3 个没什么好说的 ,第二个 LegacyRoot 在 packages/shared/ReactRootTags.js 中定义 ,值为 0
1 | // 位置: packages/react-reconciler/ReactFiberReconciler.js |
再再再回过头,回到 legacyRenderSubtreeIntoContainer() , 给 fiberRoot 赋值
1 | fiberRoot = root._internalRoot; |
在之后对传入的 callback 函数进行了一些封装。
最后立即执行了 updateContainer() 函数。
1 | export function updateContainer( |
然后返回值调用了 updateContainerAtExpirationTime()
1 | export function updateContainerAtExpirationTime( |
然后返回值又调用了 scheduleRootUpdate()
1 | function scheduleRootUpdate( |
这里就完成了整个 ReactDOM.render(),虽然很多步骤里很多调用、定义都没搞清楚(调用分支树也太大了),但是已经把整个流程大致看了一遍了,最后在 scheduleRootUpdate() 创建了一个 update ,来实现更新。
fiberRoot
ReactDOM.render() 创建的一个对象。
- 包含了应用挂载的目标节点的信息(dom container)
- 记录整个应用更新过程的各种信息
在上面提到的 legacyRenderSubtreeIntoContainer() 通过好几层函数的引用,最后一层是 createContainer() ,生成了一个 fiberRoot。
1 | // 删除了 flow 的类型代码和注释 |
FiberRootNode 的 current 也是一个 fiber ,React 的节点结构是树状的,而每一个节点都会有一个对应的 fiber ,这里的 container 是节点的容器,也是节点树的 root ,所以这个 current 也就是 fiber 树的根(root)。
Fiber
每一个 ReactElement 都会有一个对应的 Fiber 对象。
Fiber 对象会记录节点的各种状态,比如说 class component 的 props 、state 会先在 Fiber 上,等节点更新了才会把 props 、state 放在节点的 this.state 、this.props 上。
还有就是因为每一个 ReactElement 都有对应的 Fiber ,Fiber 会串联整个应用形成节点树。
来看一下 Fiber 的源码
1 | function FiberNode( |
关于 fiber 的 child / sibling / return , 看图。
那么已经大概的浏览了一下 FiberRoot 和 Fiber 这两个东西。
既然是 v16 最亮眼的地方,应用起来也肯定很亮眼啦~
Update
Fiber 对象里有一个属性叫 updateQueue ,里面存放的东西,就是 update 。
update 用于记录组件状态的改变。
update 可以有多个同时存在,比如说有三个 setState() 则会有三个 update 存在 updateQueue 里,而不会说出现一个 update 就进行一次更新。
update 和 updateQueue 的数据结构
1 | export function createUpdate(expirationTime, suspenseConfig) { |
1 | export function createUpdateQueue(baseState){ |
update 和 updateQueue 的数据结构
在生成了一个 update 之后,调用了 enqueueUpdate(current, update)
1 | export function enqueueUpdate(fiber, update) { |
1 | function appendUpdateToQueue(queue, update) { |