函数组件
function App() {
const [num, updateNum] = useState(0);
window.updateNum = updateNum;
return num;
}
调用window.updateNum(1)可以将视图中的0更新为1
对于如下函数组件
function App() {
const [num, updateNum] = useState(0);
function increment() {
setTimeout(() => {
updateNum(num + 1);
}, 1000);
}
return <p onClick={increment}>{num}</p>;
}
点击5次 结果仍为1
hook如何保存数据
FunctionComponent的render本身只是函数调用。
那么在render内部调用的hook是如何获取到对应数据呢?
比如:
useState获取state
useRef获取ref
useMemo获取缓存的数据
答案是:
每个组件有个对应的fiber节点(可以理解为虚拟DOM),用于保存组件相关信息。
每次FunctionComponent render时,全局变量currentlyRenderingFiber都会被赋值为该FunctionComponent对应的fiber节点。
所以,hook内部其实是从currentlyRenderingFiber中获取状态信息的。
多个hook如何获取数据
我们知道,一个FunctionComponent中可能存在多个hook,比如:
function App() {
// hookA
const [a, updateA] = useState(0);
// hookB
const [b, updateB] = useState(0);
// hookC
const ref = useRef(0);
return <p></p>;
}
那么多个hook如何获取自己的数据呢?
答案是:
currentlyRenderingFiber.memoizedState中保存一条hook对应数据的单向链表。
对于如上例子,可以理解为:
const hookA = {
// hook保存的数据
memoizedState: null,
// 指向下一个hook
next: hookB
// ...省略其他字段
};
hookB.next = hookC;
currentlyRenderingFiber.memoizedState = hookA;
当FunctionComponent render时,每执行到一个hook,都会将指向currentlyRenderingFiber.memoizedState链表的指针向后移动一次,指向当前hook对应数据。
这也是为什么React要求hook的调用顺序不能改变(不能在条件语句中使用hook) —— 每次render时都是从一条固定顺序的链表中获取hook对应数据的。
useState执行流程
我们知道,useState返回值数组第二个参数为改变state的方法。
在源码中,他被称为dispatchAction。
每当调用dispatchAction,都会创建一个代表一次更新的对象update:
const update = {
// 更新的数据
action: action,
// 指向下一个更新
next: null
};
对于如下例子
function App() {
const [num, updateNum] = useState(0);
function increment() {
updateNum(num + 1);
}
return <p onClick={increment}>{num}</p>;
}
调用updateNum(num + 1),会创建:
const update = {
// 更新的数据
action: 1,
// 指向下一个更新
next: null
// ...省略其他字段
};