We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
阅读完本章,可以收获下面几点知识
建议在阅读主流程源码时,在主流程函数各个入口打个断点,走一遍主流程的源码会更有感觉
本章节所有案例都基于以下示例代码:
import React, { useReducer, useEffect, useState } from "react"; import { render } from "react-dom"; function reducer(state, action) { return state + 1; } const Counter = () => { const [count, setCount] = useReducer(reducer, 0); return ( <div onClick={() => { debugger; setCount(1); setCount(2); }} > {count} </div> ); }; render(<Counter />, document.getElementById("root"));
React 使用环状链表保存更新队列 queue={ pending: null },其中 pending 永远指向最后一个更新。比如多次调用 setState 时:
React
queue={ pending: null }
pending
setState
const [count, setCount] = useReducer(reducer, 0); setCount(1); // 生成一个更新对象:update1 = { action: 1, next: update1 } setCount(2); // 生成一个更新对象:update2 = { action: 2, next: update1 }
fiber 中存储的 queue 队列如下:
fiber
queue
环状链表简单实现如下,这个可以动手写一下,找找感觉
const queue = { pending: null }; // queue.pending永远指向最后一个更新 function dispatchAction(action) { const update = { action, next: null }; const pending = queue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; } // 队列 dispatchAction(1); dispatchAction(2);
假设我们有下面这段代码,React 每次执行到 hook 函数时,都会构造一个 hook 对象,并连接成一个链表
hook
const [count, setCount] = useReducer(reducer, 0); // 构造一个hook对象 hook1 = { memoizedState: 0, queue: { pending: null }, next: hook2 } const [count2, setCount2] = useReducer(reducer, 1000); // 构造一个hook对象 hook2 = { memoizedState: 1000, queue: { pending: null }, next: hook3 } useEffect(() => { // 构造一个hook对象,hook3 = { memoizedState: { create: callback }, next: null} console.log("useEffect"); }, []);
在 hook 对象中,hook.memoizedState 属性用于保存当前状态,比如 hook1.memoizedState 对应的就是 count。hook1.next 指向 hook2。hook1.queue保存的是调用 setCount 后的更新队列。
hook.memoizedState
hook1.memoizedState
count
hook1.next
hook2
hook1.queue
setCount
每个 hook 都会维护自己的更新队列 queue
注意!!!函数组件中,组件对应的 fiber 节点也有一个 memoizedState 属性,fiber.memoizedState 用于保存组件的 hook 链表
这里有两种方法,一种是通过容器节点root,一种是在源码中打断点
root
通过容器节点 root 查找对应的 fiber 节点
另一种方法是在源码中打断点,这个需要了解源码。在react-dom.development.js中搜索renderWithHooks方法,在 var children = Component(props, secondArg) 处打一个断点,然后在它下面一行再打一个断点,等 Component(props, secondArg) 函数执行完成,则 hook 链表构造完成,此时可以在控制台打印console.log(workInProgress)即可看到当前 fiber 节点的信息
react-dom.development.js
renderWithHooks
var children = Component(props, secondArg)
Component(props, secondArg)
console.log(workInProgress)
经过前面两小节的铺垫,我们对 hook.queue 以及 hook 有了初步印象。本节开始介绍 hook 源码主流程。
hook.queue
React 对于初次挂载阶段和更新阶段,hook 的流程处理不同。因此这里我分为三个阶段来介绍:
React 内部通过提供各个阶段的 HooksDispatcher 对象,抹平了 API 差异。比如 当我们调用 useReducer(reducer, 0) 时,我们不需要关心函数组件是第一次执行还是第 n 次执行。
HooksDispatcher
useReducer(reducer, 0)
React 源码内部维护一个全局变量 ReactCurrentDispatcher。在调用函数组件前,React会判断如果是第一次执行组件,即挂载阶段,则将 ReactCurrentDispatcher 变量设置为 HooksDispatcherOnMount,如果是更新阶段,则设置为 HooksDispatcherOnUpdate。这样当我们调用 useReducer(reducer, 0)时,实际上调用的是 HooksDispatcherOnMount.useReducer 或者 HooksDispatcherOnUpdate.useReducer
ReactCurrentDispatcher
HooksDispatcherOnMount
HooksDispatcherOnUpdate
HooksDispatcherOnMount.useReducer
HooksDispatcherOnUpdate.useReducer
这个阶段,函数组件第一次执行。这个阶段源码主流程图如下,建议在流程图中每个函数的入口处各打一个断点,并根据流程图走一遍 React 源码流程。
在整个流程中,最关键的是 renderWithHooks 方法,不管是初次挂载阶段还是更新阶段,都会走这个方法!!!。该方法最最最主要做了以下几件事情:
currentlyRenderingFiber
null
workInProgress.memoizedState = null
mountReducer
updateReducer
Counter
children
currentHook
workInProgressHook
mountWorkInProgressHook 方法主要就是构造 hook 链表
mountWorkInProgressHook
const Counter = () => { const [count, setCount] = useReducer(reducer, 0); return ( <div onClick={() => { debugger; setCount(1); setCount(2); }} > {count} </div> ); };
当我们点击按钮时,调用 setCount 方法,实际上调用的是 dispatchAction 方法,主要逻辑如下:
dispatchAction
update
update.eagerState
setCount(2)
currentState
scheduleUpdateOnFiber
这个阶段,函数组件第 2 次执行或者第 n(n > 2)次执行,这个阶段也是从 performUnitOfWork 开始。主流程如下:
performUnitOfWork
ReactFiberBeginWork.js
import { IndeterminateComponent, FunctionComponent, HostComponent, } from "./ReactWorkTags"; import { renderWithHooks } from "./ReactFiberHooks"; export function beginWork(current, workInProgress) { if (current) { switch (workInProgress.tag) { case FunctionComponent: return updateFunctionComponent( current, workInProgress, workInProgress.type ); default: break; } } else { switch (workInProgress.tag) { case IndeterminateComponent: return mountIndeterminateComponent( current, workInProgress, workInProgress.type ); default: break; } } } function updateFunctionComponent(current, workInProgress, Component) { const newChildren = renderWithHooks(current, workInProgress, Component); reconcileChildren(null, workInProgress, newChildren); return workInProgress.child; } function mountIndeterminateComponent(current, workInProgress, Component) { const children = renderWithHooks(current, workInProgress, Component); workInProgress.tag = FunctionComponent; // 初次渲染后,此时组件类型已经明确,因此需要修改tag reconcileChildren(null, workInProgress, children); return workInProgress.child; // null }
ReactFiberHooks.js,最主要的逻辑都在这个文件里面
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop"; const ReactCurrentDispatcher = { current: null, }; let workInProgressHook = null; // 当前工作中的新的hook指针 let currentHook = null; // 当前的旧的hook指针 let currentlyRenderingFiber; // 当前正在工作的fiber const HooksDispatcherOnMount = { useReducer: mountReducer, }; const HooksDispatcherOnUpdate = { useReducer: updateReducer, }; function updateReducer(reducer, initialState) { const hook = updateWorkInProgressHook(); const queue = hook.queue; // 更新队列 const lastRenderedReducer = queue.lastRenderedReducer; // 上一次reducer方法 const current = currentHook; const pendingQueue = queue.pending; if (pendingQueue !== null) { // 根据旧的状态和更新队列里的更新对象计算新的状态 const first = pendingQueue.next; // 第一个更新对象 let newState = current.memoizedState; // 旧的状态 let update = first; do { const action = update.action; newState = reducer(newState, action); update = update.next; } while (update !== null && update !== first); queue.pending = null; // 更新完成,清空链表 hook.memoizedState = newState; // 让新的hook对象的memoizedState等于计算的新状态 queue.lastRenderState = newState; } const dispatch = dispatchAction.bind(null, currentlyRenderingFiber, queue); return [hook.memoizedState, dispatch]; } function updateWorkInProgressHook() { let nextCurrentHook; if (currentHook === null) { // 如果currentHook为null,说明这是第一个hook const current = currentlyRenderingFiber.alternate; // 旧的fiber节点 nextCurrentHook = current.memoizedState; // 旧的fiber的memoizedState指向旧的hook链表的第一个节点 } else { nextCurrentHook = currentHook.next; } currentHook = nextCurrentHook; const newHook = { memoizedState: currentHook.memoizedState, queue: currentHook.queue, next: null, }; if (workInProgressHook === null) { // 说明这是第一个hook currentlyRenderingFiber.memoizedState = workInProgressHook = newHook; } else { workInProgressHook.next = newHook; workInProgressHook = workInProgressHook.next = newHook; } return workInProgressHook; } function mountReducer(reducer, initialState) { // 构建hooks单向链表 const hook = mountWorkInProgressHook(); hook.memoizedState = initialState; const queue = (hook.queue = { pending: null }); // 更新队列 const dispatch = dispatchAction.bind(null, currentlyRenderingFiber, queue); return [hook.memoizedState, dispatch]; } function mountWorkInProgressHook() { const hook = { // 创建一个hook对象 memoizedState: null, // 自己的状态 queue: null, // 自己的更新队列,环形列表 next: null, // 下一个更新 }; if (workInProgressHook === null) { currentlyRenderingFiber.memoizedState = workInProgressHook = hook; } else { workInProgressHook = workInProgressHook.next = hook; } return workInProgressHook; } // 不同的阶段useReducer有不同的实现 export function renderWithHooks(current, workInProgress, Component) { currentlyRenderingFiber = workInProgress; currentlyRenderingFiber.memoizedState = null; ReactCurrentDispatcher.current = current !== null ? HooksDispatcherOnUpdate : HooksDispatcherOnMount; const children = Component(); currentlyRenderingFiber = null; workInProgressHook = null; currentHook = null; return children; } function dispatchAction(currentlyRenderingFiber, queue, action) { const update = { action, next: null }; const pending = queue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; const lastRenderedReducer = queue.lastRenderedReducer; // 上一次的reducer const lastRenderState = queue.lastRenderState; // 上一次的state const eagerState = lastRenderedReducer(lastRenderState, action); // 计算新的state // 如果新的state和旧的state相同,则跳过更新 if (Object.is(eagerState, lastRenderState)) { return; } scheduleUpdateOnFiber(currentlyRenderingFiber); } export function useReducer(reducer, initialState) { return ReactCurrentDispatcher.current.useReducer(reducer, initialState); }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
阅读完本章,可以收获下面几点知识
建议在阅读主流程源码时,在主流程函数各个入口打个断点,走一遍主流程的源码会更有感觉
本章节所有案例都基于以下示例代码:
第一节 环状链表
React
使用环状链表保存更新队列queue={ pending: null }
,其中pending
永远指向最后一个更新。比如多次调用setState
时:fiber
中存储的queue
队列如下:环状链表简单实现如下,这个可以动手写一下,找找感觉
第二节 什么是 hook 链表
假设我们有下面这段代码,
React
每次执行到hook
函数时,都会构造一个hook
对象,并连接成一个链表在
hook
对象中,hook.memoizedState
属性用于保存当前状态,比如hook1.memoizedState
对应的就是count
。hook1.next
指向hook2
。hook1.queue
保存的是调用setCount
后的更新队列。每个
hook
都会维护自己的更新队列queue
注意!!!函数组件中,组件对应的 fiber 节点也有一个 memoizedState 属性,fiber.memoizedState 用于保存组件的 hook 链表
如何查看真实的 hook 链表?
这里有两种方法,一种是通过容器节点
root
,一种是在源码中打断点通过容器节点
root
查找对应的fiber
节点另一种方法是在源码中打断点,这个需要了解源码。在
react-dom.development.js
中搜索renderWithHooks
方法,在var children = Component(props, secondArg)
处打一个断点,然后在它下面一行再打一个断点,等Component(props, secondArg)
函数执行完成,则hook
链表构造完成,此时可以在控制台打印console.log(workInProgress)
即可看到当前fiber
节点的信息第三节 hook 源码流程
经过前面两小节的铺垫,我们对
hook.queue
以及hook
有了初步印象。本节开始介绍hook
源码主流程。React
对于初次挂载阶段和更新阶段,hook
的流程处理不同。因此这里我分为三个阶段来介绍:setState
执行,这个阶段就是构造hook
更新队列queue
的阶段React
内部通过提供各个阶段的HooksDispatcher
对象,抹平了 API 差异。比如 当我们调用useReducer(reducer, 0)
时,我们不需要关心函数组件是第一次执行还是第 n 次执行。React
源码内部维护一个全局变量ReactCurrentDispatcher
。在调用函数组件前,React
会判断如果是第一次执行组件,即挂载阶段,则将ReactCurrentDispatcher
变量设置为HooksDispatcherOnMount
,如果是更新阶段,则设置为HooksDispatcherOnUpdate
。这样当我们调用useReducer(reducer, 0)
时,实际上调用的是HooksDispatcherOnMount.useReducer
或者HooksDispatcherOnUpdate.useReducer
初次挂载阶段
这个阶段,函数组件第一次执行。这个阶段源码主流程图如下,建议在流程图中每个函数的入口处各打一个断点,并根据流程图走一遍
React
源码流程。在整个流程中,最关键的是
renderWithHooks
方法,不管是初次挂载阶段还是更新阶段,都会走这个方法!!!。该方法最最最主要做了以下几件事情:currentlyRenderingFiber
变量指向当前工作的fiber
节点。fiber
的hook
链表为null
。workInProgress.memoizedState = null
。更新阶段一样会重置hook
链表并重新生成HooksDispatcherOnMount
,更新阶段则设置为HooksDispatcherOnUpdate
。以此决定是调用mountReducer
还是updateReducer
Counter
,并将结果children
返回。并重置currentlyRenderingFiber
、currentHook
、workInProgressHook
为null
mountWorkInProgressHook
方法主要就是构造hook
链表触发更新阶段
当我们点击按钮时,调用
setCount
方法,实际上调用的是dispatchAction
方法,主要逻辑如下:update
,并加入hook
的更新队列queue
update.eagerState
缓存,这是React
的一种优化手段,当我们多次调用setCount(2)
,传的是相同的值时,React
不会再触发更新。update.eagerState
和上一次的currentState
相同,则不触发更新。否则调用scheduleUpdateOnFiber
触发更新更新阶段
这个阶段,函数组件第 2 次执行或者第 n(n > 2)次执行,这个阶段也是从
performUnitOfWork
开始。主流程如下:第四节 hook 主流程源码实现
ReactFiberBeginWork.js
ReactFiberHooks.js,最主要的逻辑都在这个文件里面
同步更新以及异步更新
The text was updated successfully, but these errors were encountered: