# react 简易版 useState 实现方式
# 前言:
react
的 hooks
是应用的很常见的一个技术,而 useState
更是一个很常见的 hook
, 这里介绍其实现方式。写法实际上是 react 源码的简化版。
# 思路:
那么首先我们需要知道 hooks
的基础用法。对于 useState
来说比较常见的用法是
function APP(){ | |
const [val,setVal]=useState(initialVal); | |
... | |
setVal((val)=>{ | |
... | |
}) | |
} |
为了方便起见,我们采用了简单的 isMount
来记录组件是初次加载还是重新渲染,利用 workInProgressHook
来记录当前工作的 hooks
, 利用 schedule
函数来进行调度重新渲染
let isMount = true; | |
let workInProgressHook = null; | |
const fiber = { | |
stateNode: APP, | |
// 保存函数组件 hooks 上的 state,class 组件中上的 (链表) | |
memoizedState: null, | |
}; | |
function schedule() { | |
workInProgressHook = fiber.memoizedState; | |
const testRes = fiber.stateNode(); | |
isMount = false; | |
// 便于看结果将其返回 | |
return testRes; | |
} | |
function APP() { | |
const [count, setCount] = useState(0); | |
const [num,setNum]=useState(10); | |
console.log(isMount?'APP初始化':'重新加载','count对应的值是',count,'num对应的值是',num) | |
return { | |
addOne() { | |
setCount( | |
(count)=>{ | |
return count + 1; | |
}); | |
}, | |
addTen() { | |
setNum( | |
(num)=>{ | |
return num + 10; | |
}); | |
}, | |
}; | |
} | |
function useState(initialState) { | |
//TODO 补全内容。 | |
} | |
window.app = schedule(); |
由于 useState
可能会调用多次,所以必须要将其存储起来,才能保证每次获得的值是一一对应。 react
采用的是链表的方式进行存储的,每个组件都有一个 fiber
与之对应, fiber
上的 memoizedState
就是对应存储的 state
。
对于 useState
来说,需要返回当前的值,为了记录这个值,采用了 memoizedState
进行记录,当初次加载时创建一个 hook
, 其中包含初始值,和修改操作列表。
然后将其挂在 fiber.memoizedState
的尾部。需要读取的时候,遍历链表获取对应的值。
function useState(initialState) { | |
let hook; | |
if (isMount) { | |
hook = { | |
memoizedState: initialState, // 初始值 | |
next: null, | |
queue:{ // 对应的更改的操作 | |
pending:null | |
} | |
}; | |
// 是第一个 hook 的话,进行赋值 | |
if (!fiber.memoizedState) { | |
fiber.memoizedState = hook; | |
} else { | |
// 记录到尾部 | |
workInProgressHook.next = hook; | |
} | |
// 记录当前链表的尾部 | |
workInProgressHook = hook; | |
} else { | |
hook = workInProgressHook; | |
workInProgressHook = workInProgressHook.next; | |
} | |
// 获取初始状态 | |
let baseState=hook.memoizedState; | |
//TODO , 返回修改值的函数 | |
... | |
return [baseState,fun()]; |
然后就是具体修改值的函数,如下所示,因为修改的函数可能会多次执行,所以其存储结构采用的是环状列表来实现。对应于上面的 queue
hook = { | |
memoizedState: initialState, // 初始值 | |
next: null, | |
queue:{ // 对应的更改的操作 | |
pending:null | |
} | |
}; |
其每一项都包含一个我们传入的 action
。
function dispatchAction(queue,action){ | |
// 环状链表实现 | |
const update={ | |
action, | |
next:null | |
} | |
if(queue.pending===null){ | |
update.next=update; | |
}else{ | |
update.next=queue.pending.next; | |
queue.pending.next.next=update; | |
} | |
// 指向该链表的最后一个元素 | |
queue.pending=update; | |
// 触发更新 | |
schedule(); | |
} |
完整的代码如下图所示
let isMount = true; | |
let workInProgressHook = null; | |
const fiber = { | |
stateNode: APP, | |
// 保存函数组件 hooks 上的 state,class 组件中上的 (链表) | |
memoizedState: null, | |
}; | |
function schedule() { | |
workInProgressHook = fiber.memoizedState; | |
const testRes = fiber.stateNode(); | |
isMount = false; | |
// 便于看结果给一个返回值 | |
return testRes; | |
} | |
function APP() { | |
const [count, setCount] = useState(0); | |
const [num,setNum]=useState(10); | |
console.log(isMount?'APP初始化':'重新加载','count对应的值是',count,'num对应的值是',num) | |
return { | |
addOne() { | |
setCount( | |
(count)=>{ | |
return count + 1; | |
}); | |
}, | |
addTen() { | |
setNum( | |
(num)=>{ | |
return num + 10; | |
}); | |
}, | |
}; | |
} | |
function useState(initialState) { | |
let hook; | |
if (isMount) { | |
hook = { | |
memoizedState: initialState, | |
next: null, | |
queue:{ | |
pending:null | |
} | |
}; | |
// 是第一个 hook 的话,进行赋值 | |
if (!fiber.memoizedState) { | |
fiber.memoizedState = hook; | |
} else { | |
// 记录到尾部 | |
workInProgressHook.next = hook; | |
} | |
// 记录当前链表的尾部 | |
workInProgressHook = hook; | |
} else { | |
hook = workInProgressHook; | |
workInProgressHook = workInProgressHook.next; | |
} | |
// 获取初始状态 | |
let baseState=hook.memoizedState; | |
// 如果存在更新 | |
if(hook.queue.pending){ | |
let firstUpdate=hook.queue.pending.next; | |
// 遍历链表进行更新 | |
do{ | |
const firstAction=firstUpdate.action; | |
console.log('baseState',baseState,firstAction,firstAction(baseState)) | |
baseState=firstAction(baseState); | |
firstUpdate=firstUpdate.next; | |
}// 环装链表的终止条件,不等于自身 | |
while(firstUpdate!==hook.queue.pending.next); | |
hook.queue.pending=null; | |
} | |
hook.memoizedState=baseState; | |
return [baseState,dispatchAction.bind(null,hook.queue)] | |
} | |
function dispatchAction(queue,action){ | |
// 环状链表实现 | |
const update={ | |
action, | |
next:null | |
} | |
if(queue.pending===null){ | |
update.next=update; | |
}else{ | |
update.next=queue.pending.next; | |
queue.pending.next.next=update; | |
} | |
// 指向该链表的最后一个元素 | |
queue.pending=update; | |
// 触发更新 | |
schedule(); | |
} | |
window.app = schedule(); |
useState 演示地址