export function Demo() {
const [count, updateCount] = useState(1)
const [name, updateName] = useState('a')
const handleClick = () => {
updateCount(count => count +1)
updateCount(count => count +1)
const handleChange = () => {
updateName(name => name +'a')
updateName(name => name +'b')
return (<>
<p>useState简易实现</p>
<button onClick={handleClick}>ADD</button>
<button onClick={handleChange}>Change</button>
通过定义useState这个函数,useState的返回值是 [count, updateCount],count永远保存的最新的值,updateCount是用来更新这个count的方法。updateCount的改变会触发组件重新render。对name的更新同理。所以react hooks工作流程大致如下:
1、通过一些方式产生更新,并且更新会使得组件重新render(函数组件Demo1重新执行)
2、组件render时useState返回的是更新后的结果
其中,更新分为mount和update两个阶段:
1、mount时赋予初始值(initialValue),产生mount的更新
2、通过点击button让值加1,产生update的更新
开始实现一个setState
react hooks采用单向链表的形式进行链接,所以需要一个存储hooks链表的地方,这个地方就是fiber对象,定义一个简单fiber对象如下:
let fiber = {
memoizedState: null,
stateNode: Demo
让fiber的stateNode执行函数组件,这样就可以通过调用fiber.stateNode()让函数组件重新执行;通过fiber.memoizedState以链表的形式记录第一个hook到最后一个hook的信息
既然有链表,就得有指针,记录当前工作流下执行的是哪个hook,所以定义一个变量workInProgressHook作为指针。
定义一个变量isMount区分是在挂载阶段还是更新阶段
let isMount = true
let workInProgressHook = null
然后定义一个调度器schedule来执行组件rerender,并且每次重新执行,需要将workInProgressHook 复位指向第一个hook (这其实也是为什么每次更新,都是按顺序执行一遍hooks)
function schedule(){
workInProgressHook = fiber.memoizedState
fiber.stateNode()
这时,让我们简单的写下useState代码,大概逻辑如下:
function useState(initialState) {
let hook;
if (isMount) {
} else {
定义hook对象,并且初始化让fiber.memoizedState指向第一个useState,然后第二个hook进来(执行第二个useState),让第一个指向第二个
代码如下:
function useState(initialState){
let hook
if (isMount) {
hook = {
memoizedState:initialState,
next:null
if(!fiber.memoizedState){
fiber.memoizedState = hook
}else{
workInProgressHook.next = hook
workInProgressHook = hook
} else {
let baseState = hook.memoizedState;
return [baseSate,dispatchAction]
既然react hook是通过每次更新是组件rerender,那么我们就需要定义一个更新对象,用来存储每次更新的信息,由于更新的时候可能是同时更新了多次,如下所示调用了三次setCount:
const handleClick = () => {
updateCount(count => count +1)
updateCount(count => count +1)
updateCount(count => count +1)
模拟react对更新操作的处理,会将这些更新同样的用连起来,通过环状单向链表的形式,使用环状的目的是,react中对更新操作有优先级的概念,环状链表可以方便定位到任何一次高优先级的更新位置去执行,而暂时摒弃低优先级的更新。定义更新的结构如下:
const update = {
action,
next: null
问: 何时进行更新对象的链接?更新的信息存在哪?
答: 将更新的信息存储在对应的hook对象中,在hook对象中添加一个属性queue,queue的pending指针就指向更新的链表。在调用dispatchAction时将这些更新链接起来。
所以我们更新下hook对象结构,更新后的hook对象结构:
hook = {
queue: {
pending: null
memoizedState: initialState,
next: null
环状单向链表示意图:
定义dispatchAction 方法如下:
function dispatchAction (queue,action) { //调用hook的更新方法,实现更新(queue是存放update信息的地方,通过改变queue。pending和其next指针完成链接)
// 创建update
const update = {
action,
next: null
// 环状单向链表操作
if (queue.pending === null) {
update.next = update
} else {
update.next = queue.pending.next
queue.pending.next = update
queue.pending = update
schedule()
环状链表串联示意图:
每调用一次dispatchAction让这些更新对象串联起来,通过schedule使组件重新执行,然后在useState里执行挂载在hook的更新
function useState(initialState) {
let hook
if (isMount) {
hook = {
queue: {
pending: null
memoizedState: initialState,
next: null
if (!fiber.memoizedState) {
fiber.memoizedState = hook
} else {
workInProgressHook.next = hook
// 移动workInProgressHook指针
workInProgressHook = hook
} else {
// update时找到对应hook
hook = workInProgressHook
// 移动workInProgressHook指针
workInProgressHook = workInProgressHook.next
let baseState = hook.memoizedState
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next
const action = firstUpdate.action
baseState = action(baseState)
firstUpdate = firstUpdate.next
} while (firstUpdate !== hook.queue.pending)
hook.queue.pending = null
hook.memoizedState = baseState
return [baseState, dispatchAction.bind(null, hook.queue)]
通过链表的形式连接hooks对象,将链表的信息存储在fiber对象的memoizedState中以供更新使用,每次调用dispatchAction时将更新操作连接,更新信息存在对应hook对象的queue中,然后schedule重新执行函数组件,进入useState,查看对应的hook的queue,执行。最后将结果返回。
clone一份react源码,找到ReactFiberHooks.old.js,搜索useState,可以看到useState存在多个HooksDispatcher里,找到对应mountState、updateState方法的实现,可以看到与简易的useState实现思路是一致的。
对比与React的区别:
1、React Hooks没有使用isMount变量,而是在不同时机使用不同的dispatcher。(react 源码里搜:HooksDispatcher)
2、React Hooks有中途跳过更新的优化手段。
3、React Hooks有批量更新batchedUpdates,当在click中触发三次updateNum,精简React会触发三次更新,而React只会触发一次。
4、React Hooks的update有优先级概念,可以跳过不高优先的update,简易实现虽然模拟react有单向环状链表,但是其实并没有利用到环状结构的优势。
参考文章:
@魔法师卡颂
www.yuque.com/liangxincha…
react.iamkasong.com/process/fib…