前言
我认为理解一门新技术核心原理的高效方式就是:以最简洁的代码实现一遍。之前想要了解 webpack 原理、编译原理等内容时,一开始并不知道从何入手。后来我观看了B站上的 tiny-compiler 和 mini-webpack 教学视频,很快就理解了这些基本原理。掌握了核心原理后,再去学习一些细枝末节的内容就变得得心应手了。
拨开 React 的神秘面纱
而对于 React,我们应该如何用最简洁的方式实现一遍呢?
我们可以使用 Vite 创建一个 React 脚手架项目,去掉一些不太核心的代码。在观察这个项目结构并结合我们平时对 React 的理解之后,我们会发现运行这样一个项目至少需要实现以下这些 API 和功能:
- JSX 的转译
- React API
- createElement
- render
- 函数式组件
- 在整个运行过程中穿插的任务调度器和 fiber 架构
- ReactDOM API
- createRoot
- Hooks
- useState
如果你已经对 Vue 的原理很熟悉,那么重点理解 JSX 的转译、任务调度器、fiber 架构和 Hooks 的实现原理,就可以大致理解 React 是如何运行的以及它与 Vue 的主要区别。
说说我的理解
实现的过程可以参考build-your-own-react,也贴一下我实现的版本mini-react。这里简单说一下我在实现之后的一些理解。
要注意的是以下理解与真正的源码是有出入的,仅作为参考!
JSX 的转译
JSX 中的标签写法实际上是以下代码的语法糖,由 Babel 负责进行转译。当然,转译的过程我们不需要写代码实现,如果学习过 tiny-compiler,可以很容易地想象出转译过程中发生了什么。
React.createElement(component, props, ...children)
任务调度器和 fiber 架构
每个组件本身和其内部的普通 DOM 元素都有自己对应的 fiber 单元,基于这些单元进行 props 更新、事件绑定、diff、挂载、卸载等操作。
任务调度器可以通过 requestIdleCallback 将 fiber 单元的操作任务在浏览器渲染帧的空闲时间进行执行。因此,我们需要将原本是树形结构转成链表,以符合这种异步的调度方式。转换之后的链表的访问顺序与 N 叉树先序遍历的顺序一致。
为了建立新旧节点的关联,将更新前后的 fiber 单元一一对应地进行指针连接。
Hooks 之 useState
一开始学习 useState 的用法时,我们可能会比较困惑:明明在函数式组件被重新调用时 useState 也重新调用了,传入的初始值为什么只有第一次的时候起作用?
在自己实现一遍之后,会发现 state 会被挂在函数式组件对应的 fiber 节点上,而新旧节点之间是有指针关联的,在重新执行的时候,从旧节点上可以拿到旧的 state,这样就可以实现数据的初始化以及缓存。