likes
comments
collection
share

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

作者站长头像
站长
· 阅读数 22

React Mount-- renderRootSync

学习源码不易,和大家一起共勉!

前置知识:理解JSX

原文链接:react.iamkasong.com/preparation…

原文链接:facebook.github.io/jsx/

jsxECMAScript的语法拓展,我们可以用jsx语法来更加灵活,自如的表达UI组件jsx最终都会被编译器转译成为ECMAScript

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图1:Route组件

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图二:Provider组件,业务组件

看到上图大家一定非常熟悉,这就是我们平时写的react组件,所以我对jsx的另一个理解就是组件化,无论是Route路由组件标签,Routes路由组件标签,Context.ProviderReact上下文组件标签,还是业务组件标签,jsx都可以使之嵌套,或者自由组合,还可以被编译器转译成结构化的vdom,并且在@babel/plugin-transform-react-jsx中jsx组件的值会被当作type使用。这样一来,我们就可以针对每一种组件标签,都写一套挂载逻辑和更新逻辑就可以实现jsx组件化。

前置知识:如何调试 React 源码

原文链接:react.iamkasong.com/preparation…

首先git clone拉取React 源代码 ->yarn安装依赖 ->yarn build构建 react / react-dom / schedulercjs包。yarn link创建这个三个包的软链接,npx create react app创建新项目。将新项目的react react-dom scheduler (yarn link react/react-dom)build好的cjs包 ,npm start启动项目,debugger或者打断点,即可以开始调试cjs文件。

在我们添加软链接的包(link)中调试Hello React

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图三:在打包好的cjs包当中console.log()

运行已经添加了link软链的新项目,控制台打印出Hello React

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图四:控制台打印出`Hello React`

React Mount

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync renderRootSync 及其涵盖的子调用就是React 初次Mount的核心过程。

图五:React初次Mount的函数调用栈

首先一上来`mount`的核心操作就是创建`FiberRootNode`节点,设置`current`属性,为初次挂载打好基础,因为初次挂载`其他fiber`节点都没有`current属性`。 > current属性这里先要有印象,下文还会提到。

那么,创建好FiberRootNode节点的结果是这样的:

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图六:创建好`FiberRootNode`节点的结果

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图七:创建好`FiberRootNode`节点的结果

然后:开始核心beginwork,下面是bedginwork及其核心操作的调用栈:

beginwork开始

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图八: bedginwork及其核心操作的调用栈

解释beginwork 拿到当前的Fiber节点,会去判断是什么类型的组件标签,是元素标签?(updateHostComponent),函数组件(updateFunctionComponent)? 类组件(updateClassComponent)?还是memo组件,context组件,对应好后去执行不同的update逻辑,之后拿到fiber.pendingProps.chidlren,也就是当前fiber节点的子虚拟dom,带着子虚拟dom去执行reconcileChildrenreconcileChildren主要是通过判断不同子虚拟dom的type,去创建子节点fiber,如果是children是数组vdom还会构建出这一层fiber节点child 和 sibling 以及return的关系,最后返回这一层的child节点,让child作为下一个beginwork的节点。

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图九:判断是什么类型的组件标签

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图十:reconcileChildren主要是通过判断不同子虚拟dom的`type`,去创建子节点`fiber`

beginwork的结果与总结

实例代码

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图十一:beginwork的结果

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图十二:beginwork的结果

**总结**:`beginwork`在`mount`阶段,最重要的作用就是带着当前的`fiber`节点和子虚拟dom执行`reconcileChildren`,每次`reconcile`都会利用`子虚拟dom`来构建好这一层的`fiber链表`(包括`fiber节点之间的child, sibling, return关系`)。

completeWork开始

当深度优先遍历到一个没有child的fiber节点时(这里是img),开始completework进行回溯,completework主要做了三部分工作:第一部分是:当前的fiber节点调用createInstance来创建真实dom,创建好真实dom之后,挂载到fiber.stateNode属性上, 第二部分是:调用appendAllChildren()去按child -> sibling的顺序去append这一层子节点的dom,第三部分是:更新该fiber节点真实dom上的属性,事件,样式,ref,比如:上述的实例中,img没有child,不会append,直接返回,div.appchild,会依次从div.app父fiber的child到sibling将子节点的真实dom去append到父节点上。

(注意completework是回溯,回溯到父节点的时候,子节点已经将子节点自身真实dom挂载到fiber节点上去了,父节点可以直接使用)所以回溯到div.app, div.app appendAllChildren()之后,我们打出Instance,也就是div.app的真实dom我们会发现,当回溯到div.app父节点的时候,所有的子节点都已经被append进去了)。

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图十三:打印出Instance

我们的子dom节点虽然已经建立好了联系,但是还没有渲染在web页面上,我们都知道在react当中,dom最终渲染在页面上是在commit mutation阶段,在commit阶段通过effectTag属性 追踪fiber副作用来对dom节点进行增删改移,不一样的是,初次节点的挂载逻辑就只在根节点的第一个child(<App/> --> function App)上去打effectTag,因为其他节点,初次挂载没有current属性(上文有提到),所以当根fiber节点非根节点带着他的childrenvdomreconceileChildren的时候,走了不一样的reconcile调用,所以只有根fiber节点的child子fiber节点才打上了effectTag。而其他节点没有,不过,这样也完全够用,因为初次挂载,每个节点都需要placement,所以commit mutation阶段的时候,直接将函数组件返回的第一个dom节点(div.app -> 函数fiber节点.child.stateNode属性) 插入到根节点(div#root -> FiberRootNode.containerInfo)就mount成功了。

[React Origin Code] 2022年来聊聊React Mount-- renderRootSync

图十四:根fiber节点和非根节点带着他的childrenvdom去reconceileChildren的时候,走了不一样的reconcile调用