在使用useState时发现了一个问题,子组件多次触发修改后,再点击父组件发现子组件的值出现了问题。

代码结构

const Foo = () => {
    const [count, setCount] = React.useState(10)
    function handleClick() {
        setCount((c) => c + 1)
    }
    return (
        <div>
            Foo: {count}
            <button onClick={handleClick}>click1</button>
        </div>
    )
}

const App = () => {
    const [num, setNum] = React.useState(12)
    function handleClick() {
        setNum((c) => c + 1)
    }
    return (
        <div id='app'>
            hello React: {num}
            <button onClick={handleClick}>click</button>
            <div>
                <Foo></Foo>
            </div>
        </div>
    )
}

问题:

如下图所示操作,得到的结果应该分别是 13,12

Untitled

但实际得到的结果

Untitled

分析代码逻辑:

从操作上来看,问题出现在第二次点击的时候,第二次点击的是<App />组件,此时的流程是这样的:

  1. 触发了<App />组件中的setState方法并将action收集了起来,然后赋值给nextUnitOfFier后触发了组件的更新。

  2. 组件在更新过程中,遇到了<Foo />组件并进行解析,并执行了<Foo />组件中的useState方法。

  3. 在<Foo />组件中的useState方法中,取到了oldHook,并进行了对stateHook进行赋值。

    重点来了!

    按照正常的逻辑,此时的stateHook的数据应该是 {state: 12, queue: []}

    但实际上我们通过断点调试发现是 {state: 10, queue: [f]}

    Untitled

所以后面的逻辑是:

  1. 循环调用了 stateHook.queue 方法并给 stateHooks.state 赋值并return回去了。(所以stateHooks.state = 11)
  2. 所有的dom创建完成后执行挂载,最后就是看到的上述问题
function useState(initial) {
    let currentFiber = wipFiber;
    const oldHook = currentFiber.alternate?.stateHooks[stateHookIndex];
    const stateHook = {
        state: oldHook ? oldHook.state : initial,
        queue: oldHook ? oldHook.queue : [],
    };

    stateHook.queue.forEach((action) => {
        stateHook.state = action(stateHook.state);
    });

    stateHook.queue = [];
    stateHooks.push(stateHook);
    stateHookIndex++;

    currentFiber.stateHooks = stateHooks;

    function setState(action) {
        let eagerState =
            typeof action === "function" ? action(stateHook.state) : action;

        if (eagerState === stateHook.state) {
            return;
        }
        stateHook.queue.push(
            typeof action !== "function" ? () => action : action
        );

				// 有问题的代码
        wipRoot = {
            ...currentFiber,
            alternate: currentFiber,
        };

        nextUnitOfFier = wipRoot;
    }

    return [stateHook.state, setState];
}

解决:

分析完逻辑后就找到了问题: