简介
useState是react里面最重要的hook之一,本文我们主要是基于react原理,实现一个在改变state后能将最新的状态React渲染到页面这样一个功能。
前置知识
正常我们的一个函数组件是这样的
我们前置知识主要涉及fiber,React是双缓存,React更新调度,下面代码就主要定义了一下这些知识
csharp
//react中的虚拟dom就是叫fiber
const fiber = {
node:App,
memoizedState:null,
}
//React是双缓存设计,一个是更新工作时使用的fiber,另外一个是负责渲染展示的fiber
let workInprogressFiber = null
//判断是不是初次渲染
let isMount = true
//调度React更新的函数,主要是更新node节点
function schedule(){
workInprogressFiber = fiber.memoizedState
//这里就触发了node节点的更新
fiber.node()
isMount = false
}
通过上面的定义,我们就有了一个最简单的React运行环境
useState
1、初始化hooks,记录每个useState的使用顺序
在我们每一次渲染的时候, 我们会使用isMount来判断是不是第一次渲染,有没有存在的fiber链。有存在的fiber就直接使用,没有存在的我们我把当前hooks添加到上一个hooks的后面。在这个过程中我们使用了workInprogressFiber,他相对于一个滑块,能够一直在上面获取到我们当前要使用的hooks
csharp
function useState(initValue){
let hooks = null
if(isMount){
//初次render,
hooks = {
memoizedState:initValue,
queue:null,//后面存放dispath(react的第二个函数)
next:null,
}
//判断是不是起始节点
if(fiber.memoizedState){
//前面已经有了节点,workInprogressFiber指向的是上一次进入useState的hooks地址
//将上一个hooks的指针指向当前hook,这样就能记录我们的每个useState的调用顺序
workInprogressFiber.next = hooks
}else{
//第一次进fiber,fiber还没有任何状态,
fiber.memoizedState = hooks
}
//workInprogressFiber指向下一个指针
workInprogressFiber = hooks
}else{
//在render阶段,我们调度更新的时候会将workInprogressFiber指向fiber.memoizedState,所以更新的时候就可以直接拿到需要的hooks
hooks = workInprogressFiber
//workInprogressFiber指向下面一个hooks下次进入就可以直接使用
workInprogressFiber = workInprogressFiber.next
}
...
...
}
2、useState的第二返回值,更新函数setState的实现
我们使用setState改变state的值时,都会调用setState,因此在调用setState的时候会调用schedule函数,开始更新。并且setState是可以多次调用的,我们使用一个环形链表,记录调用顺序。后面在useState中依次执行
arduino
//useState的第二个返回值
function setState(queue,action){
//这里的action可以是函数也可以是值
const update = {
action:action,
next:null
}
if(!queue){
update.next = update
}else{
update.next = queue.next
queue.next = update
}
queue = update
//模拟开始调度
schedule()
}
3、useState使用setState函数
这里主要是遍历queue,得到最后的值。需要再每次遍历过后清空queue队列。将最后的值复制到hooks
java
function useState(initValue){
let hooks = null
if(isMount){
...
}else{
...
}
let baseState = hooks.memoizedState
if(hooks?.queue){
//遍历queue,执行完所以的setState
const firstNode = hooks?.queue.next
do{
const action = firstNode.action
baseState = typeof action === 'function' ? action(baseState):baseState
firstNode = firstNode.next
}
while(firstNode !== hooks?.queue.next)
//清空queue
hooks.queue.pendding = null
}
//将值存回hooks
hooks.memoizedState = baseState
//使用bind函数将hooks.queue传入,后面就不必在传hooks.queue
return [baseState,setState.bind(null,hooks.queue)]
}