本篇文章主要用useState方法介绍react中hooks使用链表结构实现原理。
react内部实现hooks底层用的是链表结构,先看一下hooks的数据结构,如下(简化版):
const hook = {
// 当前hook的值
memoizedState: any;
// 指向下一个hook
next: hook;
}
然后看一下此段代码最终会生成的hooks结构是什么样的?
function TestUseState() {
const [count, setCount] = useState(1);
const [name, setName] = useState('rocky');
return [setCount, setName];
}
// 最终生成的hooks链表结构如下:
const hooks = {
memoizedState: 1,
next: {
memoizedState: 'rocky',
next: null
}
}
下面看如何实现hooks链表
// 存储第一个hook
let firstHook = null;
// 存储最后一个hook
let lastHook = null;
// 首次渲染完成
let mounted = false;
// 当前正在执行计算的 hook
let currentUpdateHook = null;
// 创建hooks链表方法
function createHookLinked() {
const hook = {
memoizedState: null,
next: null,
};
// 第一次
if (lastHook === null) {
firstHook = lastHook = hook;
} else {
lastHook = lastHook.next = hook;
}
return lastHook;
}
// 最后一个updater,也就是最新的那个
interface queue {
last: updater;
}
// updater
interface updater {
action: Function | any; // 更新函数
next: updater; // 下一个updater
}
function dispatchAction(queue, action) {
const updater = { action, next: null };
const last = queue.last;
// 第一次链表为空,将当前更新作为第一个,并将next指向自己,环状链表
if (last === null) {
updater.next = updater;
} else {
// 这里的操作是在环状链表中添加一个节点,并且继续保持环状
const first = last.next;
// 如果first !== null 说明是环状
if (first !== null) {
// 1.最新的updater对象的next指向前一个,前一个就是环状链表的第一个节点
updater.next = first;
}
// 2.再把最后一个updater的next指向新加入的updater,这样和上面1 首尾就链接起来构成新的环状链表了
last.next = updater;
}
// 将queue的last指向最新的updater对象上,queue.last永远指向最后一个updater
queue.last = updater;
}
// 更新state
function updateState() {
if (currentUpdateHook === null) {
currentUpdateHook = firstHook;
}
const queue = currentUpdateHook.queue;
const last = queue.last;
let first = last !== null ? last.next : null;
let newState = currentUpdateHook.memoizedState;
if (first !== null) {
let update = first;
do {
// 执行每一次更新,去更新状态
const action = update.action;
// 函数则调用
if (typeof action === 'function') {
newState = action(newState);
} else {
newState = action;
}
update = update.next;
} while (update !== null && update !== first);
}
currentUpdateHook.memoizedState = newState;
// 关键代码,执行一轮调用之后要把更新队列清空,在下一轮的调用中重新添加队列
queue.last = null;
// 一个组件执行完成后,currentUpdateHook.next指向的是null 后面没有hook了
currentUpdateHook = currentUpdateHook.next;
const dispatch = queue.dispatch;
// 返回最新的状态和修改状态的方法
return [newState, dispatch];
}
function useState(initialState) {
if (mounted) {
return updateState();
}
const hook = createHookLinked();
hook.memoizedState = initialState;
const queue = hook.queue = {
last: null,
dispatch: null,
};
const dispatcher = queue.dispatch = dispatchAction.bind(null, queue);
return [hook.memoizedState, dispatcher];
}
借用一张图来看看整体结构
hooks中为什么不能用if 看下面代码:
let mounted = false;
function App() {
if(!mounted){
const [name,setName] = useState('aaa');
const [age,setAge] = useState(1);
mounted = true;
}
const [str,setStr] = useState('str');
console.log(str);
return <div onClick={()=>setName('hello')}>点击<div>
}
为什么点击按钮的时候consoe.log输出的不是'str',而是'hello'?
因为调用setName的时候整个函数会重新执行,hook从第一个开始取值name,通过next取第二个值age,然后再next取str。此时mounted=true所以if判断不会走,但是取值还是从第一个hook的值name开始,就把name这个字段值取出给了str,最终打印'hello'。
如果文章内容有误欢迎指正,谢谢。