mini-react 实现
从 0 开始实现一个最简单版本的 React
- 支持 Function Component
- 支持 useState useReducer
从 入口 开始
import App from "./App.tsx"
ReactDOM.createRoot(document.getElementById("root")!).render(<App />)
function App() {
return <>This is App</>;
}
export default App;
先来实现一个 react-dom.js
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot
}
ReactDOMRoot.prototype.render = function (children) {
const root = this._internalRoot
console.log("sedationh render", root, children)
}
function createRoot(container) {
const root = { containerInfo: container }
return new ReactDOMRoot(root)
}
export default { createRoot }
render 函数可以拿到

<App /> 会 形成一个对象「Virtual DOM」,大概是下面的样子,更详细的内容 可看 或者 这里
这一层是由编译来做的,被转为一个函数调用,返回对象
{
type: 'marquee',
props: {
bgcolor: '#ffa7c4',
children: 'hi',
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element'),
}
支持 HostComponent、仅仅创建流程,最简 React
大白话就是只渲染 原生 DOM
ReactDOM.createRoot(document.getElementById("root")!).render(
<div>
<h1>App</h1>
<a href="https://baidu.com">baidu</a>
This is App
</div>
)
在 render 后 去 updateContainer
function updateContainer(element, container) {
const { containerInfo } = container
const fiber = createFiber(element, {
type: containerInfo.nodeName.toLocaleLowerCase(),
stateNode: containerInfo,
})
// 组件初次渲染
scheduleUpdateOnFiber(fiber)
}
ReactDOMRoot.prototype.render = function (children) {
const root = this._internalRoot
console.log("sedationh render", root, children)
+ updateContainer(children, root)
}
containerInfo 就是 document.getElementById("root")
const fiber = createFiber(element, {
type: containerInfo.nodeName.toLocaleLowerCase(),
stateNode: containerInfo,
})
这里用 containerInfo 直接创建了一个 root node 的 fiber
export function createFiber(vnode, returnFiber) {
createFiber 会利用 Virtual DOM 和 returnFiber 构建 fiber 结构
returnFiber 会给 return 构建 fiber 关系
// 第一个子fiber
child: null,
// 下一个兄弟节点
sibling: null,
// 父亲节点
return: returnFiber,
export function createFiber(vnode, returnFiber) {
console.log("sedationh createFiber", vnode, returnFiber)
const fiber = {
// 类型
type: vnode.type,
key: vnode.key,
// 属性
props: vnode.props,
// 不同类型的组件, stateNode也不同
// 原生标签 dom节点
// class 实例
stateNode: null,
// 第一个子fiber
child: null,
// 下一个兄弟节点
sibling: null,
// 父亲节点
return: returnFiber,
flags: Placement,
// 记录节点在当前层级下的位置
index: null,
}
if (isString(vnode.type)) {
fiber.tag = HostComponent
}
return fiber
}
形成的 fiber 会喂给 scheduleUpdateOnFiber
let wip = null // work in progress 当前正在工作中的
let wipRoot = null
export function scheduleUpdateOnFiber(fiber) {
wip = fiber
wipRoot = fiber
}
到这里,render 的调用栈就结束了,requestIdleCallback 登场
/**
- @param {IdleDeadline} idleDeadline
*/
function workLoop(idleDeadline) {
while (wip && idleDeadline.timeRemaining() > 0) {
performUnitOfWork()
}
if (!wip && wipRoot) {
commitRoot()
}
}
requestIdleCallback(workLoop)
这个可以理解为死循环一直执行 workLoop 函数,但只会在浏览器的渲染进程空闲的时候进行
RequestIdleCallback 简单的说,判断一帧有空闲时间,则去执行某个任务。 目的是为了解决当任务需要长时间占用主进程,导致更高优先级任务(如动画或事件任务),无法及时响应,而带来的页面丢帧(卡死)情况。 故 RequestIdleCallback 定位处理的是: 不重要且不紧急的任务。
workLoop 干两件事情
- performUnitOfWork
- 找不同,看看要干哪些活,准备活「工作单元(fiber)」
- commitRoot
- 干活啦,进行 DOM 更改
以下为 GPT 的说法
在 React Fiber 架构中,workLoop 函数是一个循环,用于驱动 React 应用程序的工作进程。它负责执行两个主要任务:performUnitOfWork 和 commitRoot。
-
performUnitOfWork:performUnitOfWork是workLoop的第一个任务。- 它的作用是执行当前工作单元(fiber)的工作并返回下一个工作单元。
- 一个工作单元代表了 React 中的一个组件或元素,需要进行处理、更新或渲染。
- 在执行工作单元期间,会根据不同的工作类型和组件类型执行相应的操作,如调用函数组件、类组件的生命周期方法,处理更新队列,创建子工作单元等。
- 当一个工作单元的工作完成后,
performUnitOfWork会返回下一个要执行的工作单元,以便继续进行下一轮的工作。
-
commitRoot:commitRoot是workLoop的第二个任务。- 它在所有工作单元都被处理完毕后被调用,用于将更新结果提交到实际的 DOM 中。
- 在
commitRoot中,React 会遍历整个 Fiber 树,将需要更新的 DOM 节点进行插入、更新或删除操作,以反映应用程序的最新状态。 - 这个过程通常涉及到底层的 DOM 操作,如创建新的 DOM 节点、更新属性、添加事件监听器等。
- 一旦
commitRoot执行完成,React 应用程序的界面就会得到更新,并呈现给用户。
通过 performUnitOfWork 和 commitRoot 的交替执行,React 能够以递增的方式处理组件的更新,同时保持对用户界面的响应和流畅度。这种增量更新的方式也是 React Fiber 架构的核心思想之一。
需要注意的是,workLoop 函数还可能执行其他任务,如处理错误、调度优先级等,但 performUnitOfWork 和 commitRoot 是其最重要的两个任务,负责驱动 React 应用程序的工作。
GPT 说法结束
function performUnitOfWork() {
const { tag } = wip
switch (tag) {
case HostComponent:
updateHostComponent(wip)
break
default:
break
}
// dfs
if (wip.child) {
wip = wip.child
return
}
let next = wip
while (next) {
if (next.sibling) {
wip = next.sibling
return
}
next = next.return
}
wip = null
}
performUnitOfWork 以 dfs 的方式走 fiber 链

遍历过程中,根据 fiber 结构中的 tag 进行区分
ReactWorkTags.js
export const FunctionComponent = 0
export const ClassComponent = 1
export const IndeterminateComponent = 2 // Before we know whether it is function or class
export const HostRoot = 3 // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4 // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5
export const HostText = 6
export const Fragment = 7
export const Mode = 8
export const ContextConsumer = 9
export const ContextProvider = 10
export const ForwardRef = 11
export const Profiler = 12
export const SuspenseComponent = 13
export const MemoComponent = 14
export const SimpleMemoComponent = 15
export const LazyComponent = 16
export const IncompleteClassComponent = 17
export const DehydratedFragment = 18
export const SuspenseListComponent = 19
export const ScopeComponent = 21
export const OffscreenComponent = 22
export const LegacyHiddenComponent = 23
export const CacheComponent = 24
export const TracingMarkerComponent = 25
updateHostComponent 的工作是两块
- updateNode -> 创建 DOM ,存在 stateNode 里,更新 DOM 属性、处理文本内容
- reconcileChildren 根据
Arrau<Vritual DOM>children 接着完善 fiber 链
export function updateHostComponent(wip) {
if (!wip.stateNode) {
wip.stateNode = document.createElement(wip.type)
updateNode(wip.stateNode, wip.props)
}
reconcileChildren(wip, wip.props.children)
}
export function updateNode(node, nextVal) {
Object.keys(nextVal).forEach((key) => {
if (key === "children") {
if (isStringOrNumber(nextVal[key])) {
node.textContent = nextVal[key]
}
} else {
node[key] = nextVal[key]
}
})
}
function reconcileChildren(wip, children) {
if (isStringOrNumber(children)) {
return
}
const newChildren = isArray(children) ? children : [children]
let previousNewFiber = null
for (let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i]
if (newChild == null) {
continue
}
const newFiber = createFiber(newChild, wip)
if (previousNewFiber === null) {
// head node
wip.child = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
}!
接下来看 commitRoot 的工作。以 dfs 的方式进行 DOM 改动,改动的流程就是找父亲节点,然后吧在 performUnitOfWork 中准备的 DOM 节点塞进去
function commitRoot() {
commitWorker(wipRoot)
wipRoot = null
}
function commitWorker(wip) {
if (!wip) {
return
}
// 1. 提交自己
const parentNode = getParentNode(wip.return)
const { flags, stateNode } = wip
if (flags & Placement && stateNode) {
parentNode.appendChild(stateNode)
}
// 2. 提交子节点
commitWorker(wip.child)
// 3. 提交兄弟
commitWorker(wip.sibling)
}
function getParentNode(wip) {
let p = wip
while (p) {
if (p.stateNode) {
return p.stateNode
}
p = p.return
}
}
至此,完成 「支持 HostComponent、仅仅创建流程,最简 React」
看效果 👇
ReactDOM.createRoot(document.getElementById("root")!).render(
<div>
<h1>App</h1>
<a href="https://baidu.com">baidu</a>
This is App
</div>
)

支持 FunctionComponent && ClassComponent && HostText
在 createFiber 的时候,增加 fiber.tag = FunctionComponent
export function createFiber(vnode, returnFiber) {
console.log("sedationh createFiber", vnode, returnFiber)
const fiber = {
// 类型
type: vnode.type,
key: vnode.key,
// 属性
props: vnode.props,
// 不同类型的组件, stateNode也不同
// 原生标签 dom节点
// class 实例
stateNode: null,
// 第一个子fiber
child: null,
// 下一个兄弟节点
sibling: null,
return: returnFiber,
flags: Placement,
// 记录节点在当前层级下的位置
index: null,
}
if (isString(vnode.type)) {
fiber.tag = HostComponent
}
if (isFunction(vnode.type)) {
fiber.tag = FunctionComponent
}
return fiber
}
然后在 performUnitOfWork 处理 FunctionComponent,调用函数,返回值作为 children 接着构建 fiber 链
function performUnitOfWork() {
const { tag } = wip
switch (tag) {
case HostComponent:
updateHostComponent(wip)
break
case FunctionComponent:
updateFunctionComponent(wip)
break
export function updateFunctionComponent(wip) {
const { type, props } = wip
const children = type(props)
reconcileChildren(wip, children)
}
接下来 ClassComponent 和 HostText
function performUnitOfWork() {
const { tag } = wip
switch (tag) {
case HostComponent:
updateHostComponent(wip)
break
case FunctionComponent:
updateFunctionComponent(wip)
break
case ClassComponent:
updateClassComponent(wip)
break
case HostText:
updateHostComponent(wip)
break
export function updateClassComponent(wip) {
const { type, props } = wip
const instance = new type(props)
const children = instance.render()
reconcileChildren(wip, children)
}
export function updateHostTextComponent(wip) {
wip.stateNode = document.createTextNode(wip.props.children)
}
解释下 HostText 「就是 同级 下有至少两个的节点、且有文本节点的情况」,如下面的 「有其他同级元素的文本」,「App」 不算
<div>
<h1>App</h1>
<a href="https://baidu.com">baidu</a>
有其他同级元素的文本
{/* @ts-ignore */}
<ClassComp />
</div>
「App」 的这种情况在 updateNode 的时候进行了处理
export function updateNode(node, nextVal) {
Object.keys(nextVal).forEach((key) => {
if (key === "children") {
if (isStringOrNumber(nextVal[key])) {
// STUDY: seda 文本节点处理
node.textContent = nextVal[key]
}
} else {
node[key] = nextVal[key]
}
})
}
并且会在 reconcileChildren 的时候进行返回
function reconcileChildren(wip, children) {
if (isStringOrNumber(children)) {
return
}
Fragment 的处理比较简单,略
引入最小堆和更新队列
前面提到
requestIdleCallback工作只有 20FPS,一般对用户来感觉来说,需要到 60FPS 才是流畅的, 即一帧时间为 16.7 ms,所以这也是react团队自己实现requestIdleCallback的原因。实现大致思路是在requestAnimationFrame获取一桢的开始时间,触发一个postMessage,在空闲的时候调用idleTick来完成异步任务。 — https://juejin.cn/post/6844904196345430023#heading-11
React Scheduler 为什么使用 MessageChannel 实现

基本思路可参考 https://github.com/kodecocodes/swift-algorithm-club/blob/master/Heap/README.markdown
先去除之前用 requestIdleCallback 的调用方式
function workLoop() {
while (wip) {
performUnitOfWork()
}
if (!wip && wipRoot) {
commitRoot()
}
}
// requestIdleCallback(workLoop)
workLoop 的触发时机是在
export function scheduleUpdateOnFiber(fiber) {
wip = fiber
wipRoot = fiber
scheduleCallback(workLoop)
}
scheduleCallback 负责调度我们的 workLoop
实现如下
import { Heap } from "./heap"
let taskIdCounter = 0
const taskHeap = new Heap((parent, child) => {
if (parent.expirationTime === child.expirationTime) {
return parent.id < child.id
}
return parent.expirationTime < child.expirationTime
})
export function scheduleCallback(callback) {
const currentTime = getCurrentTime()
const timeout = -1
const expirationTime = currentTime - timeout
const newTask = {
id: taskIdCounter,
callback,
expirationTime,
}
taskIdCounter += 1
taskHeap.add(newTask)
requestHostCallback()
}
const channel = new MessageChannel()
function requestHostCallback() {
channel.port1.postMessage(null)
}
channel.port2.onmessage = function () {
workLoop()
}
function workLoop() {
let currentTask = taskHeap.pop()
while (currentTask) {
const callback = currentTask.callback
callback()
currentTask = taskHeap.pop()
}
}
export function getCurrentTime() {
return performance.now()
}
我们传入的 workLoop 作为 scheduleCallback 的 callback 进入,被组织为 task 加入 taskHeap
添加 hooks 能力
useReducer
下面是我们测试自己写的 useReducer 的例子
function FunctionComp() {
const [cnt, dispatchCnt] = useReducer((state, action) => {
console.log("sedationh action", action, n++)
return state + 1
}, 0)
const [cnt2, dispatchCnt2] = useReducer((state, action) => {
console.log("sedationh action", action, n++)
return state + 2
}, 0)
return (
<div>
<button
onClick={() => {
dispatchCnt("action")
}}
>
dispatchCnt cnt1
</button>
{cnt}
<hr />
<button
onClick={() => {
dispatchCnt2("action")
}}
>
dispatchCnt cnt2
</button>
{cnt2}
<h1>FunctionComp</h1>
</div>
)
}
先简单处理下事件行为 「onClick」
export function updateNode(node, nextVal) {
Object.keys(nextVal).forEach((key) => {
if (key === "children") {
if (isStringOrNumber(nextVal[key])) {
node.textContent = nextVal[key]
}
} else if (key.slice(0, 2) === "on") {
const eventName = key.slice(2).toLowerCase()
node.addEventListener(eventName, nextVal[key])
} else {
node[key] = nextVal[key]
}
})
}
hook 的结构如下
hook = {
memoriedState: null, // 状态
next: null, // 下一个 hook
}
关键逻辑
- 找到当前 hook
- 找到当前 hook 所在的 fiber
- 在 hook 所在的 fiber 上标记 老 fiber「alternate」
- 利用 reducer 去更新新的 hook.memoriedState
- 利用 scheduleUpdateOnFiber 以当前更新 fiber 节点为 rootFiber 进行更新处理
export const useReducer = (reducer, initialState) => {
const hook = updateWorkInProgressHook()
if (!currentlyRenderingFiber.alternate) {
// 初次渲染
hook.memoriedState = initialState
}
const dispatch = (action) => {
hook.memoriedState = reducer(hook.memoriedState, action)
currentlyRenderingFiber.alternate = { ...currentlyRenderingFiber }
scheduleUpdateOnFiber(currentlyRenderingFiber)
}
return [hook.memoriedState, dispatch]
}
为了能满足上面的代码,需要在 fiber 上增加一些信息
export function createFiber(vnode, returnFiber) {
const fiber = {
...
// old fiber
alternate: null,
// function component, hook0
memoriedState: null,
}
...
let currentlyRenderingFiber = null
let workInProgressHook = null
function updateWorkInProgressHook() {
let hook
const current = currentlyRenderingFiber.alternate
if (!current) {
// 初次渲染
hook = {
memoriedState: null,
next: null,
}
if (!workInProgressHook) {
// hook0
workInProgressHook = currentlyRenderingFiber.memoriedState = hook
} else {
// hook1, hook2, hook3 ...
workInProgressHook = workInProgressHook.next = hook
}
} else {
// 更新
currentlyRenderingFiber.memoriedState = current.memoriedState
if (!workInProgressHook) {
// hook0
hook = workInProgressHook = currentlyRenderingFiber.memoriedState
} else {
// hook1, hook2, hook3 ...
hook = workInProgressHook = workInProgressHook.next
}
}
return hook
}
currentlyRenderingFiber 怎么拿到呢?
export const renderWithHooks = (wip) => {
currentlyRenderingFiber = wip
currentlyRenderingFiber.memoriedState = null
workInProgressHook = null
}
export function updateFunctionComponent(wip) {
renderWithHooks(wip)
const { type, props } = wip
const children = type(props)
reconcileChildren(wip, children)
}
接下来在 reconcileChildren 中进行 diff,进行更新标记
// 协调(diff)
// 创建新 fiber
function reconcileChildren(wip, children) {
if (isStringOrNumber(children)) {
return
}
const newChildren = isArray(children) ? children : [children]
let oldFiber = wip.alternate?.child
let previousNewFiber = null
for (let i = 0; i < newChildren.length; i++) {
const newChild = newChildren[i]
if (newChild == null) {
continue
}
const newFiber = createFiber(newChild, wip)
const same = isSame(oldFiber, newFiber)
if (same) {
Object.assign(newFiber, {
alternate: oldFiber,
stateNode: oldFiber.stateNode,
flags: Update,
})
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
if (previousNewFiber === null) {
// head node
wip.child = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
}
接下来完善 commit 环节
function commitWorker(wip) {
if (!wip) {
return
}
// 1. 提交自己
const parentNode = getParentNode(wip.return)
const { flags, stateNode } = wip
if (flags & Placement && stateNode) {
parentNode.appendChild(stateNode)
}
if (flags & Update && stateNode) {
updateNode(stateNode, wip.alternate.props, wip.props)
}
// 2. 提交子节点
commitWorker(wip.child)
// 3. 提交兄弟
commitWorker(wip.sibling)
}
因为需要比较,所有原来的 updateNode 也要改下
export function updateNode(node, prev, nextVal) {
Object.keys(prev).forEach((key) => {
if (key === "children") {
if (isStringOrNumber(nextVal[key])) {
node.textContent = ""
}
} else if (key.slice(0, 2) === "on") {
const eventName = key.slice(2).toLowerCase()
node.removeEventListener(eventName, prev[key])
} else {
node[key] = ""
}
})
Object.keys(nextVal).forEach((key) => {
if (key === "children") {
if (isStringOrNumber(nextVal[key])) {
// STUDY: seda 文本节点处理
node.textContent = nextVal[key]
}
} else if (key.slice(0, 2) === "on") {
const eventName = key.slice(2).toLowerCase()
node.addEventListener(eventName, nextVal[key])
} else {
node[key] = nextVal[key]
}
})
}
现在来整体看下流程
- 初次渲染 Function 组件
- render
- updateContainer
- scheduleUpdateOnFiber
- workLoop
- performUnitOfWork
- updateFunctionComponent
- renderWithHooks
- 调用 Function 组件
- useReducer
- reconcileChildren
- commitWorker
- 更新
- dispatch
- scheduleUpdateOnFiber
useState
思路是差不多的
export const useState = (initialState) => {
const hook = updateWorkInProgressHook()
if (!currentlyRenderingFiber.alternate) {
// 初次渲染
hook.memoriedState = initialState
}
const fiber = currentlyRenderingFiber
const dispatch = (newState) => {
hook.memoriedState = typeof newState === "function" ? newState(hook.memoriedState) : newState
fiber.alternate = { ...fiber }
fiber.sibling = null
scheduleUpdateOnFiber(fiber)
}
return [hook.memoriedState, dispatch]
}
完善更新能力
目前的更新能力都是通过位置来比较的,具体来说如
new abcd
old cd
a !== c b !== d
这俩都会被删除
export function createFiber(vnode, returnFiber) {
const fiber = {
...
// 处理删除
deletoins: null,
}
function reconcileChildren(returnFiber, children) {
if (isStringOrNumber(children)) {
return
}
const newChildren = isArray(children) ? children : [children]
let oldFiber = returnFiber.alternate?.child
let previousNewFiber = null
let newIndex
for (newIndex = 0; newIndex < newChildren.length; newIndex++) {
const newChild = newChildren[newIndex]
if (newChild == null) {
continue
}
const newFiber = createFiber(newChild, returnFiber)
const same = isSame(oldFiber, newFiber)
if (same) {
Object.assign(newFiber, {
alternate: oldFiber,
stateNode: oldFiber.stateNode,
flags: Update,
})
}
if (!same && oldFiber) {
// 删除节点
deleteChild(returnFiber, oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
if (previousNewFiber === null) {
// head node
returnFiber.child = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
/**
- new ab
- old abcdef
*
- 通过遍历 ab 是走不到 cdef 的
*/
if (newIndex === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber)
return
}
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
let childToDelete = currentFirstChild
while (childToDelete) {
deleteChild(returnFiber, childToDelete)
childToDelete = childToDelete.sibling
}
}