React16:Fiber架构学习总结
您好,我是沧沧凉凉,是一名前端开发者,目前在掘金、知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。
本篇文章紧跟着前一篇文章,前文中谈到了Fiber节点,那么由Fiber节点构成的树就叫做Fiber树,同时它也对应着DOM树,而如果要用Fiber树更新DOM树,那么这个时候就需要用到一个“双缓存”技术。
试想一下,如果仅仅一颗Fiber树,那么每次需要更新界面上的DOM树的时候,都需要通过diff算法对Fiber树进行增删改,一旦所需要更改的节点过多,可能会造成界面闪烁的情况,就像电视一样,如果每一个画面都是在清除电视上显示的图像才进行绘制的话,那就会不停的闪烁。
所以在React中最多会存在两颗Fiber树。
- 当前屏幕上显示的DOM对应的Fiber树叫做:current Fiber树,树中节点叫做current fiber。
- 正在内存中进行构建的Fiber树叫做workInProgress Fiber树,树中的节点叫做workInProgress fiber。
这两个树之间的节点通过alternate
属性连接。
注意:上面的概念非常重要!后面在讲解diff算法的时候,会反复提到,所以一定要有一个印象!可能现在看起来比较抽象,不过没关系,下面会具体讲解如何变化。
下面这两个概念也非常重要,基本贯穿了整个React深入学习,分别为:
- mount时:首次执行ReactDOM.render时。
- update时:触发状态更新时。
这里提前剧透一下:在react-hooks的原理部分,mount时和update时,react调用的hook的方法是不同的,比如说useState,在mount时会调用对应的mountState方法,而在update时会调用updateState方法。
mount
mount代表的是应用初始化的时候,这点
在调用ReactDOM.render方法后,会调用createFiberRoot方法,创建FiberRoot节点,然后调用createHostRootFiber方法,创建HostRootFiber节点。
在createFiberRoot源码中是这么写的:
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
var root = new FiberRootNode(containerInfo, tag, hydrate);
// 这里执行了 createHostRootFiber 生成了 HostRootFiber
var uninitializedFiber = createHostRootFiber(tag);
// 将 FiberRoot 的 current 指向 HostRootFiber
root.current = uninitializedFiber;
// 将 HostRootFiber 的 stateNode 指向 FiberRoot
uninitializedFiber.stateNode = root;
// 下面是更新队列相关的,暂时不用管
initializeUpdateQueue(uninitializedFiber);
return root;
}
为什么上面的源码看起来不一样?因为他们是来自react-dom
打包后的文件react-dom.development.js
中。
从上面的源码中,可以很清晰的看到FiberRoot节点指向HostRootFiber节点的过程。
这个FiberRoot节点不同于普通的Fiber对象,它单独实例化了一个对象FiberRootNode,而createHostRootFiber方法就是实例化的一个普通的Fiber对象,即FiberNode。
至于FiberRootNode对象我们暂时就不深入研究,我们先来看一下FiberNode对象中有些什么东西:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// 作为静态数据结构的属性
// 在总结JSX的时候会提到
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// 用于连接其他Fiber节点形成Fiber树
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
// 作为动态的工作单元的属性,也就是和Hooks相关
// 在后面总结Hooks的时候会提到
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 调度优先级相关
// 在后面总结lane模型的时候会提到
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 指向该Fiber在另一次更新时对应的Fiber
this.alternate = null;
}
这里多说一点,React的源码看着有点像TypeScript,其实它是使用的自家的静态类型检查器Flow,所以语法上可能和TypeScript还是有点区别,不过这不妨碍我们对源码的阅读。
从Fiber对象中可以看出,它具有非常多的属性,在后面的总结文章中,我会提到大部分属性究竟是用来做什么的,所以现在看看就好,千万不要一头扎进去,就像一瞬间搞清楚这些属性的作用,随着往后深入,这些属性的作用也会逐步在你的脑海中形成一个较为清晰的印象。
其中FiberRoot是整个应用的根节点,HostRootFiber是<App />
所在组件树的根节点。
在应用中,可以多次调用ReactDOM.render方法,生成不同的组件树,它们拥有不同的HostRootFiber,但是整个应用的根节点只有一个,那就是FiberRoot节点。
这个时候FiberRoot的current会指向页面上已渲染内容对应Fiber树
,即current Fiber树
。
update
我们看一下下面的代码:
function App() {
const [count, setCount] = useState(0);
return <p onClick={() => setCount(count + 1)}>{count}</p>;
}
即点击p标签,
可以看到,当页面的状态更新的时候,在控制台的元素中只有p标签发现了变化,而决定workInProgress fiber树中的创建是否可以复用current Fiber树中的节点,这个过程就是靠diff算法来进行实现,
点击p标签后,整体流程如下:
正如上面所说的,在Fiber中最多有两棵树,一颗叫做current Fiber树,一颗叫做workInProgress Fiber树,当workInProgress Fiber树构建完成后交给Renderer
渲染在页面上后,应用根节点的current
指针指向workInProgress Fiber树
,那么此时的workInProgress Fiber树
就会变成current Fiber树
。
也就是通过这两棵树之间的替换,来完成DOM的更新。
最后
本文的内容不多,但是起到了一个承上启下的作用,主要是为了引出后面的内容。
同样,本文参考了React技术揭秘,省略了一些我感觉在现阶段理解比较困难的内容,如果有兴趣还是推荐去阅读一下原文。
可能看到这里你对于Fiber架构已经大致有一个概念了,但是你可能还有一些疑问,其实包括我现在在整理学习到的知识的时候都还有非常多的疑问,但是我相信随着我逐步整理,我的疑问会变得越来越少。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!