在之前更新视图是通过 update 函数,逻辑在useState中同样适用。

采取小步走的开发方式去实现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>
    )
}

step1.实现最简单的useState

实现思路是调用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];
}

注意:这里采用的事闭包的写法,函数中引用了外部的变量,会导致变量无法被销毁,所以当修改变量值时,其他引用此变量的地方也会修改

step2.当存在多个useState时,useState会覆盖之前的

因为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];
}

step3.兼容和性能优化

性能优化:

  1. 如果一个setState重复调用的话,每次更新值都会导致页面重新渲染更新。

    假设一个变量从10 → 11 → 12 → 13,中间会经历三次更新,但其实只需要更新一次,即10 → 13,中间的更新时没有必要的。

    所以可以将action存储起来,等到最后一次的时一次性调用完后拿到最新的值,参与更新。

  2. 如果值相对于上次没有变化,则不用更新