初学 React 时,如何快速理解其核心原理?

238 阅读3分钟

前言

我认为理解一门新技术核心原理的高效方式就是:以最简洁的代码实现一遍。之前想要了解 webpack 原理、编译原理等内容时,一开始并不知道从何入手。后来我观看了B站上的 tiny-compiler 和 mini-webpack 教学视频,很快就理解了这些基本原理。掌握了核心原理后,再去学习一些细枝末节的内容就变得得心应手了。

拨开 React 的神秘面纱

而对于 React,我们应该如何用最简洁的方式实现一遍呢?

我们可以使用 Vite 创建一个 React 脚手架项目,去掉一些不太核心的代码。在观察这个项目结构并结合我们平时对 React 的理解之后,我们会发现运行这样一个项目至少需要实现以下这些 API 和功能:

  1. JSX 的转译
  2. React API
    1. createElement
    2. render
    3. 函数式组件
    4. 在整个运行过程中穿插的任务调度器和 fiber 架构
  3. ReactDOM API
    1. createRoot
  4. Hooks
    1. 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,这样就可以实现数据的初始化以及缓存。