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
通过数组实现,初始化的时候,创建两个数组:states 与 setters ,设置光标 cursor 为 0
states
setters
cursor
0
useState
setter
state
[state, setter]
每次更新 state 时,都是通过调用 setter 函数修改对应的 state 值,这种对应关系是通过 cursor 闭包来实现的
看一个例子:
function RenderFunctionComponent() { const [firstName, setFirstName] = useState("Rudi"); const [lastName, setLastName] = useState("Yardley"); return ( <Button onClick={() => setFirstName("Fred")}>Fred</Button> ); }
1、初始化
初始化的时候,会创建两个数组: state和setters,把光标的位置设为0.
2、第一次渲染
调用useState时,第一次渲染,会将一个set函数放入setters数组中,并且把初始state放入到state数组中.
3、后续渲染
每一次重新渲染,光标都会重新设为0,然后从对应的数组中读取状态和set函数
示例来自:react hooks进阶与原理
代码实现:
let states = [] let setters = [] let firstRun = true let cursor = 0 // 使用工厂模式生成一个 createSetter,通过 cursor 指定指向的是哪个 state function createSetter(cursor) { return function(newVal) { // 闭包 states[cursor] = newVal } } function useState(initVal) { // 首次 if(firstRun) { states.push(initVal) setters.push(createSetter(cursor)) firstRun = false } let state = states[cursor] let setter = setters[cursor] // 光标移动到下一个位置 cursor++ // 返回 return [state, setter] }
使用
function App() { // 每次重置 cursor cursor = 0 return <RenderFunctionComponent /> } function RenderFunctionComponent() { const [firstName, setFirstName] = useState("Rudi"); const [lastName, setLastName] = useState("Yardley"); return ( <Button onClick={() => setFirstName("Fred")}>Fred</Button> ); }
在真实的 React Hooks 中,这种关系其实是通过链表实现的,首先我们需要明确以下内容:
在 React 中最多会同时存在两棵 Fiber 树。当前屏幕上显示内容对应的 Fiber 树称为 current Fiber 树,正在内存中构建的 Fiber 树称为 workInProgress Fiber 树。current Fiber 树中的 Fiber 节点被称为 current fiber ,workInProgress Fiber 树中的 Fiber 节点被称为 workInProgress fiber ,他们通过 alternate 属性连接。
Fiber
current Fiber
workInProgress Fiber
current fiber
workInProgress fiber
alternate
// workInProgressHook 指针,指向当前 hook 对象 let workInProgressHook = null // workInProgressHook fiber,这里指的是 App 组件 let fiber = { stateNode: App, // App 组件 }
初始化阶段,react hooks 做的事情,在一个函数组件第一次渲染执行上下文过程中,每个 react hooks执行,都会产生一个 hook 对象,并形成链表结构,绑定在 workInProgress 的 memoizedState 属性上
workInProgress
memoizedState
// workInProgressHook 指针,指向当前 hook 对象 let workInProgressHook = null // workInProgressHook fiber,这里指的是 App 组件 let fiber = { stateNode: App, // App 组件 memoizedState: null // hooks 链表,初始为 null } // 是否是首次渲染 let isMount = true
然后 react hooks 上的状态,绑定在当前 hooks 对象的 memoizedState 属性上。
hooks
// 每个 hook 对象,例如 state hook、memo hook、ref hook 等 let hook = { memoizedState: initVal, // 当前state的值,例如 useState(initVal) action: null, // update 函数 next: null // 因为是采用链表的形式连接起来,next指向下一个 hook }
首次渲染时,生成 hook 对象,形成链表结构,绑定在 workInProgress 的 memoizedState 属性上,代码实现:
hook
function useState(initVal) { let hook // 首次会生成 hook 对象,并形成链表结构,绑定在 workInProgress 的 memoizedState 属性上 if(isMount) { // 生成当前 hook 对象 hook = { memoizedState: initVal, // 当前state的值,例如 useState(initVal) action: null, // update 函数 next: null // 因为是采用链表的形式连接起来,next指向下一个 hook } // 绑定在 workInProgress 的 memoizedState 属性上 if(!fiber.memoizedState) { // 如果是第一个 hook 对象 fiber.memoizedState = hook // 指针指向当前 hook // workInProgressHook = hook } else { // 如果不是, 将 hook 追加到链尾 workInProgressHook.next = hook // workInProgressHook 指向下一个,鸡 hook // workInProgressHook = workInProgressHook.next } workInProgressHook = hook } }
对于一次函数组件更新,当再次执行 hooks 函数的时候,比如 useState(0) ,首先要从 current 的 hooks 中找到与当前 workInProgressHook ,对应的 current hook
useState(0)
current
workInProgressHook
current hook
function useState(initVal) { let hook // 首次会生成 hook 对象,并形成链表结构,绑定在 workInProgress 的 memoizedState 属性上 if(isMount) { // ... } else { // 拿到当前的 hook hook = workInProgressHook // workInProgressHook 指向链表的下一个 hook workInProgressHook = workInProgressHook.next } }
接下来 hooks 函数执行的时候,把最新的状态更新到 workInProgressHook ,保证 hooks 状态不丢失
function useState(initVal) { let hook // 首次会生成 hook 对象,并形成链表结构,绑定在 workInProgress 的 memoizedState 属性上 if(isMount) { // ... } else { // ... } // 状态更新,拿到 current hook,调用 action 函数,更新到最新 state let baseState = hook.memoizedState // 执行 update 函数 if(hook.action) { // 更新最新值 let action = hook.action // 如果是 setNum(num=>num+1) 形式 if(typeof action === 'function') { baseState = action(baseState) } else { baseState = action } // 清空 action hook.action = null } // 更新最新值 hook.memoizedState = baseState // 返回最新值 baseState、dispatchAction return [baseState, dispatchAction(hook)] } // action 函数 function dispatchAction(hook) { return function (action) { hook.action = action } }
完整实现:
// workInProgressHook 指针,指向当前 hook 对象 let workInProgressHook = null // workInProgressHook fiber,这里指的是 App 组件 let fiber = { stateNode: App, // App 组件 memoizedState: null // hooks 链表,初始为 null } // 是否是首次渲染 let isMount = true function schedule() { workInProgressHook = fiber.memoizedState const app = fiber.stateNode() isMount = false return app } function useState(initVal) { let hook // 首次会生成 hook 对象,并形成链表结构,绑定在 workInProgress 的 memoizedState 属性上 if(isMount) { // 每个 hook 对象,例如 state hook、memo hook、ref hook 等 hook = { memoizedState: initVal, // 当前state的值,例如 useState(initVal) action: null, // update 函数 next: null // 因为是采用链表的形式连接起来,next指向下一个 hook } // 绑定在 workInProgress 的 memoizedState 属性上 if(!fiber.memoizedState) { // 如果是第一个 hook 对象 fiber.memoizedState = hook } else { // 如果不是, 将 hook 追加到链尾 workInProgressHook.next = hook } // 指针指向当前 hook,链表尾部,最新 hook workInProgressHook = hook } else { // 拿到当前的 hook hook = workInProgressHook // workInProgressHook 指向链表的下一个 hook workInProgressHook = workInProgressHook.next } // 状态更新,拿到 current hook,调用 action 函数,更新到最新 state let baseState = hook.memoizedState // 执行 update if(hook.action) { // 更新最新值 let action = hook.action // 如果是 setNum(num=>num+1) 形式 if(typeof action === 'function') { baseState = action(baseState) } else { baseState = action } // 清空 action hook.action = null } // 更新最新值 hook.memoizedState = baseState // 返回最新值 baseState、dispatchAction return [baseState, dispatchAction(hook)] } // action 函数 function dispatchAction(hook) { return function (action) { hook.action = action } }
测试:
// 调度函数,模拟 react scheduler function schedule() { workInProgressHook = fiber.memoizedState const app = fiber.stateNode() isMount = false return app } function App() { const [num, setNum] = useState(0) return { onClick() { console.log('num: ', num) setNum(num+1) } } } // 测试结果 schedule().onClick(); // 'num: ' 0 schedule().onClick(); // 'num: ' 1 schedule().onClick(); // 'num: ' 2
更近一步,其实 useState 有两个阶段,负责初始化的 mountState 与负责更新的 updateState,在 mountState 阶段会创建一个 state hook.queue 对象,保存负责更新的信息(包含 pending,待更新队列),以及一个负责更新的函数 dispatchAction (就是 setNum ,第三个参数就是 queue)
mountState
updateState
hook.queue
pending
dispatchAction
setNum
queue
// 因此,实际的 hook 是这样的 // 每个 hook 对象,例如 state hook、memo hook、ref hook 等 let hook = { memoizedState: initVal, // 当前state的值,例如 useState(initVal) queue: { pending: null }, // update 待更新队列, 链表的形式存储 next: null // 因为是采用链表的形式连接起来,next指向下一个 hook } // 调用updateNum实际上调用这个,queue就是当前hooks对应的queue。 function dispatchAction(queue, action) { // 每一个任务对应一个update const update = { // 更新执行的函数 action, // 与同一个Hook的其他更新形成链表 next: null, }; // ... }
每次更新的时候(updateState)都会创建一个 update 对象,里面记录了此次更新的信息,然后将此update 放入待更新的 pending 队列中,最后,dispatchAction 判断当前 fiber 没有处于更新阶段
update
fiber
如果处于渲染阶段,那么不需要我们在更新当前函数组件,只需要更新一下当前 update 的expirationTime 即可
expirationTime
没有处于更新阶段,获取最新的 state ,和上一次的 currentState ,进行浅比较
currentState
scheduleUpdateOnFiber
实现代码就不写了,感兴趣的可以实现一下,欢迎补充
react hooks进阶与原理
「react进阶」一文吃透react-hooks原理
80行代码实现一个简易版useState
极简Hooks实现
The text was updated successfully, but these errors were encountered:
No branches or pull requests
基础版 useState
简单实现:只是数组
通过数组实现,初始化的时候,创建两个数组:
states
与setters
,设置光标cursor
为0
useState
时,创建一个setter
函数放入setters
中,并初始化一个state
放入states
中cursor
为0
,通过cursor
从states
与setters
获取[state, setter]
返回每次更新
state
时,都是通过调用setter
函数修改对应的state
值,这种对应关系是通过cursor
闭包来实现的看一个例子:
1、初始化
初始化的时候,会创建两个数组: state和setters,把光标的位置设为0.
2、第一次渲染
调用useState时,第一次渲染,会将一个set函数放入setters数组中,并且把初始state放入到state数组中.
3、后续渲染
每一次重新渲染,光标都会重新设为0,然后从对应的数组中读取状态和set函数
示例来自:react hooks进阶与原理
代码实现:
使用
进阶版 useState
简单实现:只是链表
在真实的 React Hooks 中,这种关系其实是通过链表实现的,首先我们需要明确以下内容:
在 React 中最多会同时存在两棵
Fiber
树。当前屏幕上显示内容对应的 Fiber 树称为current Fiber
树,正在内存中构建的Fiber
树称为workInProgress Fiber
树。current Fiber
树中的Fiber
节点被称为current fiber
,workInProgress Fiber
树中的Fiber
节点被称为workInProgress fiber
,他们通过alternate
属性连接。初始化阶段,react hooks 做的事情,在一个函数组件第一次渲染执行上下文过程中,每个 react hooks执行,都会产生一个 hook 对象,并形成链表结构,绑定在
workInProgress
的memoizedState
属性上然后 react hooks 上的状态,绑定在当前
hooks
对象的memoizedState
属性上。首次渲染时,生成
hook
对象,形成链表结构,绑定在workInProgress
的memoizedState
属性上,代码实现:对于一次函数组件更新,当再次执行
hooks
函数的时候,比如useState(0)
,首先要从current
的hooks
中找到与当前workInProgressHook
,对应的current hook
接下来
hooks
函数执行的时候,把最新的状态更新到workInProgressHook
,保证hooks
状态不丢失完整实现:
测试:
优化版:useState 是如何更新的
优化版:useState 是如何更新的
更近一步,其实
useState
有两个阶段,负责初始化的mountState
与负责更新的updateState
,在mountState
阶段会创建一个state
hook.queue
对象,保存负责更新的信息(包含pending
,待更新队列),以及一个负责更新的函数dispatchAction
(就是setNum
,第三个参数就是queue
)每次更新的时候(
updateState
)都会创建一个update
对象,里面记录了此次更新的信息,然后将此update
放入待更新的pending
队列中,最后,dispatchAction
判断当前fiber
没有处于更新阶段如果处于渲染阶段,那么不需要我们在更新当前函数组件,只需要更新一下当前
update
的expirationTime
即可没有处于更新阶段,获取最新的
state
,和上一次的currentState
,进行浅比较scheduleUpdateOnFiber
调度渲染当前fiber
实现代码就不写了,感兴趣的可以实现一下,欢迎补充
参考
react hooks进阶与原理
「react进阶」一文吃透react-hooks原理
80行代码实现一个简易版useState
极简Hooks实现
The text was updated successfully, but these errors were encountered: