• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • React 通关秘籍

    • 1.关于本小册
    • 2.一网打尽组件常用Hook
    • 3.Hook的闭包陷阱的成因和解决方案
    • 4.React组件如何写TypeScript类型
    • 5.React组件如何调试
    • 6.受控模式VS非受控模式
    • 7.组件实战:迷你Calendar
    • 8.组件实战:Calendar日历组件(上)
    • 9.组件实战:Calendar日历组件(下)
    • 10.快速掌握Storybook
    • 11.React组件如何写单测
    • 12.深入理解Suspense和ErrorBoundary
    • 13.组件实战:Icon图标组件
    • 14.组件实战:Space间距组件
    • 15.React.Children和它的两种替代方案
    • 16.三个简单组件的封装
    • 17.浏览器的5种Observer
    • 18.组件实战:Watermark防删除水印组件
    • 19.手写react-lazyload
    • 20.图解网页的各种距离
    • 21.自定义hook练习
    • 22.自定义hook练习(二)
    • 23.用react-spring做弹簧动画
    • 24.react-spring结合use-gesture手势库实现交互动画
    • 25.用react-transition-group和react-spring做过渡动画
    • 26.快速掌握Tailwind:最流行的原子化CSS框架
    • 27.用CSSModules避免样式冲突
    • 28.CSSInJS:快速掌握styled-components
    • 29.react-spring实现滑入滑出的转场动画
    • 30.组件实战:Message全局提示组件
    • 31.组件实战:Popover气泡卡片组件
    • 32.项目里如何快速定位组件源码
    • 33.一次超爽的React调试体验
    • 34.组件实战:ColorPicker颜色选择器(一)
    • 35.组件实战:ColorPicker颜色选择器(二)
    • 36.组件实战:onBoarding漫游式引导组件
    • 37.组件实战:Upload拖拽上传
    • 38.组件实战:Form表单组件
    • 39.React组件库都是怎么构建的
    • 40.组件库实战:构建esm和cjs产物,发布到npm
    • 41.组件库实战:构建umd产物,通过unpkg访问
    • 42.数据不可变:immutable和immer
    • 43.基于ReactRouter实现keepalive
    • 44.Historyapi和ReactRouter实现原理
    • 45.ReactContext的实现原理和在antd里的应用
    • 46.ReactContext的性能缺点和解决方案
    • 47.手写一个Zustand
    • 48.原子化状态管理库Jotai
    • 49.用react-intl实现国际化
    • 50.国际化资源包如何通过Excel和GoogleSheet分享给产品经理
    • 51.基于react-dnd实现拖拽排序
    • 52.react-dnd实战:拖拽版TodoList
    • 53.ReactPlayground项目实战:需求分析、实现原理
    • 54.ReactPlayground项目实战:布局、代码编辑器
    • 55.ReactPlayground项目实战:多文件切换
    • 56.ReactPlayground项目实战:babel编译、iframe预览
    • 57.ReactPlayground项目实战:文件增删改
    • 58.ReactPlayground项目实战:错误显示、主题切换
    • 59.ReactPlayground项目实战:链接分享、代码下载
    • 60.ReactPlayground项目实战:WebWorker性能优化
    • 61.ReactPlayground项目实战:总结
    • 62.手写MiniReact:思路分析
    • 63.手写MiniReact:代码实现
    • 64.手写MiniReact:和真实React源码的对比
    • 65.React18的并发机制是怎么实现的
    • 66.Ref的实现原理
    • 67.低代码编辑器:核心数据结构、全局store
    • 68.低代码编辑器:拖拽组件到画布、拖拽编辑json
    • 69.低代码编辑器:画布区hover展示高亮框
    • 70.低代码编辑器:画布区click展示编辑框
    • 71.低代码编辑器:组件属性、样式编辑
    • 72.低代码编辑器:预览、大纲
    • 73.低代码编辑器:事件绑定
    • 74.低代码编辑器:动作弹窗
    • 75.低代码编辑器:自定义JS
    • 76.低代码编辑器:组件联动
    • 77.低代码编辑器:拖拽优化、Table组件
    • 78.低代码编辑器:Form组件、store持久化
    • 79.低代码编辑器:项目总结
    • 80.快速掌握ReactFlow画流程图
    • 81.ReactFlow振荡器调音:项目介绍
    • 82.ReactFlow振荡器调音:流程图绘制
    • 83.ReactFlow振荡器调音:合成声音
    • 84.AudioContext实现在线钢琴
    • 85.React服务端渲染:从SSR到hydrate
    • 86.小册总结

我们写了很多 React 组件,也学了一些 React 生态的库,用 React 这方面没啥问题了。

但想要深入掌握 React,理解它的实现原理也是必要的。

而理解 React 实现原理的最好方式就是写一个 Mini React。

这节我们先来分析下思路。

我们在组件里通过 JSX 描述页面:

jsx 会被 babel 或者 tsc 等编译器编译成 render function,也就是类似 React.createElement 这种:

所以之前写 React 组件都必须有一行 import * as React from 'react',因为编译后会用到 React 的 api。

你可以在 babel 的 playground 试一下。

但后来改为了这种 render function:

由 babel、tsc 等编译工具自动引入一个 react/jsx-runtime 的包,

把这里的 React Runtime 切换成自动引入的就可以看到:

所以现在写组件就可以不引入 React 了。

然后 render function 执行后产生 React Element 对象,也就是常说的虚拟 dom。

也就是这样的流程:

vdom (React Element)是一个通过 chilren 串联起来的树。

之后 React 会把 React Element 树转换为 fiber 结构,它是一个链表:

React Element 只有 children 属性来链接父子节点,但是转为 fiber 结构之后就有了 child、sibling、return 属性来关联父子、兄弟节点。

有同学说,这 fiber 结构看起来不也是一棵树么,为啥叫链表?

因为按照 child、sibling、sibling、return、sibling、return 之类的遍历顺序,可以把整个 vdom 树变成线性的链表结构:

这样一个循环就可以处理完。

react 在处理 fiber 链表的时候通过一个叫 workInProgress 的指针指向当前 fiber 节点。

而 react 之所以能实现并发特性,就是基于 fiber 的链表结构。

因为之前的 React Element 树里只有 children,没有 parent、sibling 信息,这样只能一次性处理完,不然中断了就找不到它的 parent 和 sibling 节点了。

但是 fiber 不同,它额外保存了 return、sibling 节点,这样就算打断了也可以找到下一个节点继续处理。

所以现在完全可以先处理这个 fiber 树的某几个节点,然后暂停,处理其它的 fiber 树,之后再回来继续处理:

这也就是 React 所谓的并发。

浏览器里是通过 Event Loop 跑一个个 task。

如果某个 task 执行时间过长,就会阻塞渲染,导致丢帧,也就是页面卡顿。

之前直接基于 React Element 递归渲染的时候,很容易计算量过多导致页面卡顿。

而改成 fiber 结构再渲染之后,可以在每次渲染 fiber 节点之前判断是否超过一定的时间间隔,是的话就放到下个任务里跑,这样就不会阻塞渲染了。

有个两种架构对比的例子,我们来试一下:

https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html

先看下之前的:

用 Performance 录制下:

可以看到有很多超过 50ms 的长任务:

再看下 fiber 架构版本的 react

https://claudiopro.github.io/react-fiber-vs-stack-demo/fiber.html

可以看到,每个任务都是固定的时间内跑完的。

这就是 react 的时间分片机制。

怎么实现的呢?

很简单,fiber 链表的处理是可以打断的,每次处理一个节点:

然后处理下个节点之前判断下当前时间片还有没有空余时间,有的话继续 performUnitOfWork 处理下个 fiber 节点。

否则放到下一次任务里跑。

这个时间片的判断就是通过当前时间和任务开始时间点的差值。

体会到 fiber 架构的好处了么?

通过记录 parent、slibling 信息,让树变成链表,可以打断。每次处理一个 fiber 节点,处理每个 fiber 节点前判断是否到了固定的时间间隔,也就是时间分片,通过时间分片把处理 fiber 的过程放到多个任务里跑,这样页面内容多了也不会导致卡顿。

我们实现 Mini React 的话,这个时间分片机制可以直接用浏览器的 requestIdleCallback 的 api 来做。

知道了 fiber 架构的好处之后,我们继续来看 React 渲染流程。

JSX 通过 babel、tsc 等编译成 render function,执行后变成 React Element 的树。

然后 React Element 转成 fiber 结构,这个过程叫做 reconcile。

之前 React Element 是这样的:

会变成这样的 fiber 节点:

之后会根据 fiber 的类型做不同的处理:

function 组件、Provider、Lazy 组件等类型的 fiber 节点,都会做相应的处理。

比如 function 组件的 fiber 节点,会调用函数,拿到返回值,之后继续 reconcile 它的 children:

当然,reconcile 并不只是创建新的 fiber 节点,当更新的时候,还会和之前的 fiber 节点做 diff,判断是新增、修改、还是删除,然后打上对应的标记。

reconcile 完之后,fiber 链表也就构建好了,并且在每个 fiber 节点上保存了当前一些额外的信息。

比如 function 组件要执行的 effect 函数。

之后会再次遍历构建好的这个 fiber 链表,处理其中的 effect,根据增删改的标记来更新 dom,这个阶段叫做 commit。

这样,React 的渲染流程就结束了。

整体分为两大阶段:

render 阶段:把 React Element 树(也可以叫 vdom) 转成 fiber 链表的 reconcile 过程,由 Scheduler 负责调度,通过时间分片来把计算分到多个任务里去。

commit 阶段:reconcile 结束就有了完整的 fiber 链表,再次遍历这个 fiber 链表,执行其中的 effect、增删改 dom等。

其实 commit 阶段也分成了三个小阶段:

  • before mutation:操作 dom 之前
  • mutation:操作 dom
  • layout:操作 dom 之后。

比如 useEffect 的 effect 函数会在 before mutation 前异步调度执行,而 useLayoutEffect 的 effect 函数是在 layout 阶段同步执行。

React 团队按照操作 dom 前后来分了三个小阶段,更清晰了一点。

再就是 ref,在 mutaion 阶段更新了 dom,所以在 layout 阶段就可以拿到 ref 了。

当然,我们实现的时候对 commit 阶段不用分的那么细。

总结

这节我们简单分析了下 React 的渲染流程。

JSX 通过 babel、tsc 等编译器编译成 render function,然后执行后产生 React Element 的树。

React Element 的树会转成 fiber 链表,这个过程叫做 reconcile,由 React 的 Scheduler 调度。

reconcile 每次只处理一个 fiber 节点,通过时间分片把 reconcile 的过程分到多个任务跑,这样 fiber 树再大也不会阻塞渲染。

reconcile + schedule 这个过程叫做 render 阶段,而之后会进入 commit 阶段。

commit 阶段会遍历构建好的 fiber 链表,执行 dom 操作,还有函数组件的 effect 等。

它按照更新 dom 前后,分了 before mutation、mutation、layout 三个小阶段。

这就是 React 的 fiber 架构的好处和渲染流程,下节我们按照这个流程来写下 Mini React。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
61.ReactPlayground项目实战:总结
Next
63.手写MiniReact:代码实现