1 工作流程
例如:
function App() {
const [count, setCount] = useState(0);
return <span onClick={() => setCount(count => count + 1)}>{count}</span>;
}
ReactDOM.render(<App />, document.getElementById("root"));
- 在状态更新章节提到,当调用ReactDOM.render会产生mount的更新并创建update对象,在点击span时会触发setCount的更新调用dispatchAction方法并创建update对象。随后会重新render,在render阶段会重新执行函数组件本书,重新调用useState重新计算新的state。
2 函数组件的update对象
- 函数组件节点的update对象和ClassComponent及HostRoot类型节点的update对象并不相同,在现在这个例子中,我们先简化为如下格式:
const update = {
action, // 更新执行的函数,即setCount(count => count + 1)中的箭头函数,
// 也可直接传入新的数据setCount(2),这里简化只实现传入回调函数的情况;
next: null // 与同一个Hook的其他更新形成链表
}
- 若是下面这种情况,则会生成3个update并用next连接形成环状链表
function App() {
const [count, setCount] = useState(0);
const handleSpanClick = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
};
return <p onClick={handleSpanClick}>{num}</p>;
}
ReactDOM.render(<App />, document.getElementById("root"));
3 创建update
通过调用dispatchAction方法创建update并连接形成环状链表
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 = update;
}
queue.pending = update;
run();//模拟重新触发render
}
4 状态保存
- 生成的update对象保存在updateQueue上
- 对于hostComponent,在completeWork阶段会形成一个数组updateQueue:[要改变属性的key,对应的keyValue];
- 对于ClassComponent和HostRoot则保存在queue的shared.pending属性中形成单向环状链表;
const fiber = {
memoizedState: null, // 保存Hooks链表
stateNode: App
};
5 hook的数据结构
- fiber的memoizedState保存了hooks的链表(每一个hook对应一个useState),hook的memoizedState保存了该hook的state数据;
hook = {
// 保存update的queue,即上文介绍的queue
queue: {
pending: null
},
// 保存hook对应的state
memoizedState: initialState,
// 与下一个Hook连接形成单向无环链表
next: null
}
6 模拟ReactDOM.render运行
isMount = true; //实际是用current判断是mount还是update
let workInProgressHook = null;//指向当前正在执行的hook
function run() {
workInProgressHook = fiber.memoizedState; //重置为fiber保存的第一个Hook
fiber.stateNode(); // 模拟render阶段调用renderWithHooks
isMount = false;
}
7 计算state
function useState(initialState) {
let hook;
if (isMount) {
//创建hook
hook = {
queue: {
pending: null,
},
memoizedState: initialState,//初始化state
next: null,
};
if (!fiber.memoizedState) {
fiber.memoizedState = hook; //创建第一个hook
} else {
workInProgressHook.next = hook;
}
workInProgressHook = hook;
} else {
hook = workInProgressHook;
//更新下一次调用useState时的hook指针,函数内部多个useState方法从上往下同步依次执行
//当一个函数组件内部多次调用useState时,只要每次执行函数本身时useState的调用顺序及数量保持一致,
//那么在更新时始终可以通过workInProgressHook找到当前useState在mount阶段创建的保存在内存中的hook对象。
workInProgressHook = workInProgressHook.next;
}
let baseState = hook.memoizedState;
if (hook.queue.pending) {
//此hook上存在需要计算的update
let firstUpdate = hook.queue.pending.next; //hook.queue.pending保存了最后一个update,环状链表
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending.next); //遍历这条链表
hook.queue.pending = null; //代表这次update已经计算完成;
}
hook.memoizedState = baseState;
//这里dispatchAction利用bind绑定了第一个参数queue,并返回一个新的函数,该函数在调用setXxx()时接收action回调函数
return [baseState, dispatchAction.bind(null, hook.queue)];
}
8 模拟运行并调试
function App() {
const [count, setCount] = useState(0);
console.log({ isMount }, { count });
//为简化,不返回jsx对象
return {
onClick() {
setCount((count) => count + 1);
},
};
}
window.app = run();
控制台打印如下: