先简单来看一下useState的用法
function App() {
const [num, updateNum] = useState(0);
const [num2, updateNum2] = useState(0);
return (
<div>
<button id="btn1" onClick={updateNum(num => num + 1)}>按钮1</button>
<button id="btn2" onClick={updateNum2(num2 => num2 + 1)}>按钮2</button>
<div>{num}-{num2}</div>
</div>
)
}
在组件中,可以多次调用useState,那么怎么知道调用的是哪个hook呢,这里就需要使用链表来保存多个hook,每个组件对应的fiber上都有一条链表
let isMount = true; //是否是首次挂载
let workInProgressHook = null; //正在处理的hook
let fiber = {
stateNode: App,
//保存的是一条链表
memorizeState: null
}
function App() {
const [num, updateNum] = useState(0);
const [num2, updateNum2] = useState(0);
return {
onClick() {
updateNum(num => num + 1);
},
onClick2() {
updateNum2(num => num + 10);
}
}
}
调用useState,就是往链表上添加hook(挂载时),并返回initialState
function useState(initialState) {
let hook;
if(isMount) {
//首次挂载
//构造新的hook
hook = {
memorizeState: initialState,
next: null, //指向下一个hook
queue : {
pending: null
}
}
if(!fiber.memorizeState) {
//作为头节点
fiber.memorizeState = hook;
}else {
//非头节点
workInProgressHook.next = hook;
}
workInProgressHook = hook; //当前hook就是正在工作的节点
}
let baseState = hook.memorizeState;
//bind函数第二个参数相当于给dispatchAction加了默认值
return [baseState, dispatchAction.bind(null, hook.queue)];
}
以上就是hook在挂载时流程,可以看到有一个dispatchAction方法,dispatch方法就是我们执行的updateNum方法,它的作用就是往对应的hook上添加更新,并重新调度
function dispatchAction(queue, action) {
//目的:将更新加入到链表中,然后调度组件重新执行
const update = {
action,
next: null
}
if(!queue.pending) {
//环状链表,因为真实react中,每次更新是有优先级的
//u0 -> u0 ->u0
update.next = update;
}else {
//之前有更新,多次调用情况多次调用
// //u0 -> u0
// //u1 -> u0 -> u1
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update; //queue.pending始终指向最新的更新
//调度
schedule();
}
这里可以看到,hook.queue.pending是环状链表,需要进行环状链表的插入操作,并且queue.pending始终指向最新的更新,queue.pending指向的是更新的开始。插入操作完成后,接下来就是调度组件重新渲染了。
function schedule() {
//更新时workInProgressHook一开始指向的链表的头节点
workInProgressHook = fiber.memorizeState;
const app = App();
isMount = false;
return app;
}
调度之后,App会重新执行,hook也会重新执行,此时应该走的是更新流程(isMount = false),更新流程中做了什么呢,更新时需要计算新的state,并进行返回。因此这里我们补充useState中的逻辑,增加更新时的条件分支
function useState(initialState) {
let hook;
if(isMount) {
//首次挂载
//构造新的hook
hook = {
memorizeState: initialState,
next: null, //指向下一个hook
queue : {
pending: null
}
}
if(!fiber.memorizeState) {
//作为头节点
fiber.memorizeState = hook;
}else {
//非头节点
workInProgressHook.next = hook;
}
workInProgressHook = hook; //当前hook就是正在工作的节点
}else {
//更新
hook = workInProgressHook; //此时hook指向正在更新的hook
workInProgressHook = workInProgressHook.next;
}
//将环状链表剪开,计算新的state
let baseState = hook.memorizeState;
if(hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
}while(firstUpdate !== hook.queue.pending.next)
hook.queue.pending = null; //更新完成后清空更新
}
hook.memorizeState = baseState; //计算完成后,保存state,以便下次更新使用
//bind函数第二个参数相当于给dispatchAction加了默认值
return [baseState, dispatchAction.bind(null, hook.queue)];
}
至此,简易版的useState就完成了,大体的工作流程如下
最后,看一下完整的代码吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
let isMount = true; //是否是首次挂载
let workInProgressHook = null; //正在处理的hook
let fiber = {
stateNode: App,
//保存的是一条链表
memorizeState: null
}
function useState(initialState) {
let hook;
if(isMount) {
//首次挂载
//构造新的hook
hook = {
memorizeState: initialState,
next: null, //指向下一个hook
queue : {
pending: null
}
}
if(!fiber.memorizeState) {
//作为头节点
fiber.memorizeState = hook;
}else {
//非头节点
workInProgressHook.next = hook;
}
workInProgressHook = hook; //当前hook就是正在工作的节点
}else {
//更新
hook = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
//将环状链表剪开,计算新的state
let baseState = hook.memorizeState;
if(hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
}while(firstUpdate !== hook.queue.pending.next)
hook.queue.pending = null; //更新完成后清空更新
}
hook.memorizeState = baseState;
//bind函数第二个参数相当于给dispatchAction加了默认值
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function dispatchAction(queue, action) {
//将更新加入到链表中,然后调度组件重新执行
const update = {
action,
next: null
}
if(!queue.pending) {
//环状链表,因为真实react中,每次更新是有优先级的
//u0 -> u0 ->u0
update.next = update;
}else {
//之前有更新,多次调用情况多次调用
// // //u0 -> u0
// // //u1 -> u0 -> u1
// update.next = queue.pending.next;
// queue.pending.next = update;
}
queue.pending = update; //queue.pending始终指向最新的更新
//调度
schedule();
}
function schedule() {
workInProgressHook = fiber.memorizeState;
const app = App();
isMount = false;
return app;
}
function App() {
const [num, updateNum] = useState(0);
const [num2, updateNum2] = useState(0);
return {
onClick() {
updateNum(num => num + 1);
updateNum(num => num + 1);
updateNum(num => num + 1);
},
onClick2() {
updateNum2(num => num + 10);
}
}
}
window.app = schedule();
</script>
<script>
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
btn1.onclick = window.app.onClick;
btn2.onclick = window.app.onClick2;
</script>
</body>
</html>