在之前更新视图是通过 update
函数,逻辑在useState中同样适用。
采取小步走的开发方式去实现useState
const Foo = () => {
const [count, setCount] = React.useState(10)
const [bar, setBar] = React.useState('bar')
function handleClick() {
setCount((c) => c + 1)
setBar((b) => b + 'bar')
}
return (
<div>
Foo: {count}
<div> bar: {bar} </div>
<button onClick={handleClick}>click</button>
</div>
)
}
实现思路是调用useState后获取到传过来值,调用setState函数,给state重新赋值,然后触发视图更新。
这样的会有一个问题:触发更新时会调用 updateFunctionComponent
****方法并再次调用函数组件来拿到最新的vdom,其中会重新触发 useState
方法,并传递初始值。这样当我们再次调用setState时的state的值相当于没有变化。
解决问题:将上一次更新的值存储起来,更新时会获取之前的fiber并通过alterante来创建新旧关系,所以可以将stateHook存储到当前fiber中。
function useState(initial) {
let currentFiber = wipFiber;
// 获取旧的vdom上有没有stateHook,有的话则使用上一个stateHook
const oldHook = wipRoot.alternate?.stateHook;
// 创建state的对象
const stateHook = {
state: oldHook ? oldHook.state : initial
};
// 给当前的fiber赋值,下次调用useState时先使用上一个
currentFiber.stateHook = stateHook;
function setState(action) {
// 调用传过来的函数,重新赋值
stateHook.state = action(stateHook.state);
// 触发更新
wipRoot = currentFiber;
wipRoot.alternate = currentFiber;
nextUnitOfFier = wipRoot;
}
return [stateHook.state, setState];
}
注意:这里采用的事闭包的写法,函数中引用了外部的变量,会导致变量无法被销毁,所以当修改变量值时,其他引用此变量的地方也会修改
因为useState重复调用时会将之前的state的值覆盖掉,所以可以新建一个数组的全局变量来存储useState,当更新时再依次调用即可。
function updateFunctionComponent(fiber) {
// 当处理函数组件时进行初始化,保证每一个函数组件的useState都是自己的
stateHooks = [];
stateHookIndex = 0;
wipFiber = fiber;
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
let stateHooks; // 存储stateHook
let stateHookIndex; // 保证调用时机
function useState(initial) {
let currentFiber = wipFiber;
// 依次调用
const oldHook = wipRoot.alternate?.stateHooks[stateHookIndex];
const stateHook = {
state: oldHook ? oldHook.state : initial
};
// 当调用useState时存储起来
stateHooks.push(stateHook);
stateHookIndex++;
// 给当前的fiber赋值
currentFiber.stateHooks = stateHooks;
function setState(action) {
stateHook.state = action(stateHook.state);
wipRoot = currentFiber;
wipRoot.alternate = currentFiber;
nextUnitOfFier = wipRoot;
}
return [stateHook.state, setState];
}
如果一个setState重复调用的话,每次更新值都会导致页面重新渲染更新。
假设一个变量从10 → 11 → 12 → 13,中间会经历三次更新,但其实只需要更新一次,即10 → 13,中间的更新时没有必要的。
所以可以将action存储起来,等到最后一次的时一次性调用完后拿到最新的值,参与更新。
如果值相对于上次没有变化,则不用更新