经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » React » 查看文章
React18系列commit从0实现源码解析
来源:jb51  时间:2023/1/18 8:42:01  对本文有异议

正文

本系列是讲述从0开始实现一个react18的基本版本。由于React源码通过Mono-repo 管理仓库,我们也是用pnpm提供的workspaces来管理我们的代码仓库,打包我们使用rollup进行打包。

仓库地址

本章节主要是讲解我们如何将上一节得到的fiberNode树,渲染到页面中。React中commit分为下面三个阶段,这节我们讲解简单的commit阶段:

  • beforeMutation阶段
  • mutation阶段
  • layout阶段

这节主要讲解commit其中的mutation placement阶段:

我们经常把Renderer工作的阶段被称为commit阶段。在commit阶段,会将我们上一讲中生成的各种flags提交(commit)到宿主环境UI中。我们前端通常使用的是ReactDOM进行处理。这节我们简单的实现了一个ReactDom包,看看是如何把调和和浏览器环境连起来的。

桥梁

React中,react-reconcilerreact-dom是2个不同的包,react-dom主要是提供浏览器宿主相关的方法。回想我们每次在使用react开发项目的过程中,通过下方代码调用:

  1. ReactDom.createRoot(root).render(<App />)

所以需要提供createRoot方法。返回一个render方法,接收ReactElement元素。

  1. export function createRoot(container: Container) {
  2. const root = createContainer(container);
  3. return {
  4. render(element: ReactElementType) {
  5. updateContainer(element, root);
  6. },
  7. };
  8. }

内部通过createContainerupdateContainer建立2个包的连接。具体的流程可以查看我们之前的章节。

commit Effect

在上一节中,我们在workLoop执行完后后得到了一个带有标记的wip fiber tree(如下图所示), 在commit阶段,我们要通过这个fiber树将内容渲染到屏幕中。

commitRoot执行

将最后生成的finishedWork传递给commitRoot, 然后根据顶部的flagssubtreeFlags来判断是否有渲染节点:

  1. function commitRoot(root: FiberRootNode) {
  2. const finishedWork = root.finishedWork;
  3. if (finishedWork === null) {
  4. return;
  5. }
  6. // 重置
  7. root.finishedWork = null;
  8. // 判断是否存在子阶段需要执行的操作
  9. const subtreeHasEffect =
  10. (finishedWork.subtreeFlags & MutationMask) !== NoFlags; // 子节点是否有更新
  11. const rootHasEffect = (finishedWork.flags & MutationMask) !== NoFlags; // 根节点是否更新
  12. if (subtreeHasEffect || rootHasEffect) {
  13. // beforeMutation
  14. // mutation Placement
  15. commitMutationEffects(finishedWork);
  16. root.current = finishedWork;
  17. // layout
  18. } else {
  19. root.current = finishedWork;
  20. }
  21. }

commitMutationEffects执行

我们知道div对应的fiberNode是标有flags = placement的,所以在hostFiberNode中的subtreeHasEffect是有值的,所以会走到commitMutationEffects分支。那它内部到底是有什么作用呢?

commitMutationEffects执行的主要作用就是找到对应flags的fiberNode, 并执行相应的Dom操作。

  • 我们需要向下遍历找到最底部的subtreeFlags不为0的fiberNode的子fiberNode节点
  • 由于父节点的subtreeFlags存在,不代表child对应的flags存在,可能是child对应的sibling节点的flags存在
  • 所以在找到对应的subtreeFlagschild的fiberNode后,需要在向上遍历,查找对应的sibling节点。

commitMutationEffectsOnFibers

对每一个fiberNode我们会执行commit Effects的操作。

  1. const commitMutationEffectsOnFibers = (finishedWork: FiberNode) => {
  2. const flags = finishedWork.flags;
  3. if ((flags & Placement) !== NoFlags) {
  4. commitPlacement(finishedWork);
  5. finishedWork.flags &= ~Placement;
  6. }
  7. // flags update
  8. // flags childDeletion
  9. };
  10. const commitPlacement = (finishWork: FiberNode) => {
  11. if (__DEV__) {
  12. console.warn("执行commitPlacement操作", finishWork);
  13. }
  14. // parentDom 插入 finishWork对应的dom
  15. // 1. 找到parentDom
  16. const hostParent = getHostParent(finishWork);
  17. if (hostParent !== null) {
  18. appendPlacementNodeIntoContainer(finishWork, hostParent);
  19. }
  20. };

其中有一个getHostParent是获取到对应父Container(容器元素),对应浏览器环境就是Dom,

getHostParent执行

找父contianer容器,向上递归 主要是分为2大类。第一类是对应HostComponent(类似<div>), 第二类就是根节点(#root), 他们分别对应不同的位置。

  1. function getHostParent(fiber: FiberNode): Container | null {
  2. let parent = fiber.return;
  3. while (parent) {
  4. const parentTag = parent.tag;
  5. // HostComponent HostRoot
  6. if (parentTag === HostComponent) {
  7. return parent.stateNode as Container;
  8. }
  9. if (parentTag === HostRoot) {
  10. return (parent.stateNode as FiberRootNode).container;
  11. }
  12. parent = parent.return;
  13. }
  14. if (__DEV__) {
  15. console.warn("未找到HostParent");
  16. }
  17. return null;
  18. }

appendPlacementNodeIntoContainer执行

当我们上一步找到父container后,接下来就是要把自身的stateNode插入到父container中,然后渲染的界面上。

appendPlacementNodeIntoContainer 这个函数接受2个参数,第一个是当前fiberNode, 第二个父container。想想什么样的fiberNode需要渲染到屏幕中。目前demo层级来看,只有下面2个节点需要展示到屏幕中。

  • HostComponent
  • HostText

所以appendPlacementNodeIntoContainer需要向下找到对应的节点。然后插入到contaienr中

  1. function appendPlacementNodeIntoContainer(
  2. finishedWork: FiberNode,
  3. hostParent: Container
  4. ) {
  5. // fiber Host
  6. if (finishedWork.tag === HostComponent || finishedWork.tag === HostText) {
  7. appendChildToContainer(hostParent, finishedWork.stateNode);
  8. return;
  9. }
  10. const child = finishedWork.child;
  11. if (child !== null) {
  12. appendPlacementNodeIntoContainer(child, hostParent);
  13. let sibling = child.sibling;
  14. while (sibling !== null) {
  15. appendPlacementNodeIntoContainer(sibling, hostParent);
  16. sibling = sibling.sibling;
  17. }
  18. }
  19. return null;
  20. }

例子:

我们通过一个特定的例子,来讲解commit mutation中具体的执行过程。比如:我们有如下的结构,更新的flags标记如下:

commitMutationEffects会找到subtreeFlags值不为0的顶层fiberNode,然后开始向下遍历,直到找到world fiberNode

递归向上的执行commitMutationEffectsOnFibers,先自己,然后sibling, 然后return

commitMutationEffectsOnFibers: 例如world fiberNode,自身flags = 1,然后会通过getHostParent找到类型为HostComponentdiv fiberNode, 然后把自己的stateNode对应的dom, 通过appendChild插入到div fiberNodestateNode中。

这样一直向上,最终就会把div fiberNode对应的dom元素,appendroot中。

以上就是React18系列commit从0实现源码解析的详细内容,更多关于React18系列commit源码的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号