React的hook之useState简单实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React的hook之useState简单实现相关的知识,希望对你有一定的参考价值。

参考技术A 函数组件的fiber上,有着memorizedState属性,并且以 memorizedState: null, next: null 的链表形式保存着每一个hook函数状态。

形如:

fiber的alternate属性保存着上次渲染fiber

渲染到函数组件时,renderHook函数获取当前的函数fiber,初始化本轮渲染的属性

初始化本轮渲染的属性

react组件函数的执行会调用useState

updateWorkProgressHook函数处理和获取当前hook对应状态

和useState类似,只是dispatch方法里,每次新的值是用reducer函数的返回

极简hooks实现,与react的思路一样的useState

使用App函数模拟根组件

 function App() 
        const [num, updateNum] = useState(0);

        console.log(`$isMount ? "mount" : "update" num: `, num);
        button.textContent = num;
        return 
          click() 
            updateNum((num) => num + 1);
            updateNum((num) => num + 1);
          ,
        ;
      

使用app().click模拟点击事件。

全局变量

      let workInProgressHook; //指向当前的hooks对象
      let isMount = true;  //判断当前是mount还是update。
 

App对应的fiber

 // 状态保存在函数组件的fiber上
      // App组件对应的fiber对象
      const fiber = 
        // 保存该FunctionComponent对应的Hooks链表
        memoizedState: null,
        // 指向App函数
        stateNode: App,
      ;

useState函数

每个hooks会创建一个hooks对象,然后存放在fiber的memoizedState上形成链表。workInProgressHook就指向当前的hooks对象。

function useState(initialState) 
        //每个hook对应一个hooks对象
        let hook;
        if (isMount) 
          // ...mount时需要生成hook对象
          hook = 
            queue: 
              pending: null,
            ,
            memoizedState: initialState, //当前hooks对应的状态
            next: null, //指向下一个hooks对象
          ;
          //判断fiber上的链表
          if (!fiber.memoizedState) 
            fiber.memoizedState = hook;
           else 
            workInProgressHook.next = hook; //连接到fiber的hooks链表上
          
          workInProgressHook = hook; //workInProgressHook指向最新的hook对象
         else 
          // update的时候,从workInProgressHook中取出该useState对应的hook
          hook = workInProgressHook;
          // 指针往下移动
          workInProgressHook = workInProgressHook.next;
        

        let baseState = hook.memoizedState;
        let newState = baseState;
        if (hook.queue.pending) 
          // ...根据queue.pending中保存的update更新state

          // 获取第一个update对象。pending存放着update对象的环状链表,指向的永远是最后一个update
          let firstUpdate = hook.queue.pending.next;
          do 
            //执行update action
            const action = firstUpdate.action; // 假设所有的setState(()=>)都传递了一个函数
            newState = action(baseState); //每次传入的baseState都是老值
            firstUpdate = firstUpdate.next;
            //最后一个update执行完毕后跳出循环。
           while (firstUpdate !== hook.queue.pending.next);

          // 清空queue.pending
          hook.queue.pending = null;

          // 将更新后的state重新赋值hooks对象上。
          hook.memoizedState = newState || baseState;
        

        // dispatchAction的第一个参数就是当前hooks对象的queue
        return [newState, dispatchAction.bind(null, hook.queue)];
      

如上,setState是dispatchAction.bind(null, hook.queue),他的实现是:
每一个任务更新都会创建一个update对象,他与hooks对象可不同,update对象会通过next指针形成链表,存放在hooks对象queue.pending中,表示需要更新的操作。

 // 调用updateNum实际上调用这个,queue就是当前hooks对应的queue。
      function dispatchAction(queue, action) 
        // 每一个任务对应一个update
        const update = 
          // 更新执行的函数
          action,
          // 与同一个Hook的其他更新形成链表
          next: null,
        ;

        // 环状单向链表操作
        if (queue.pending === null) 
          update.next = update;
          // 连向自己
         else 
          // 形成环状
          update.next = queue.pending.next;
          queue.pending.next = update;
        
        // 将更新的任务保存在hooks上的queue,下次组件执行的时候获取然后更改state
        queue.pending = update; //永远指向最新的update

        // 模拟React开始调度更新
        schedule();
      

最后每次调用完setState,都会进入schedule调度阶段。

   // 模拟react调度,默认执行一次
      function schedule() 
        workInProgressHook = fiber.memoizedState;
        const app = fiber.stateNode(); //执行App函数
        isMount = false;
        return app;
      

每次调度都会重新执行App函数,然后App函数就会执行useState。然后取出存放在hooks.queue中需要更新的action,执行完后获取最新的state并且返回。

模拟试试



至此,这就是整个hooks的执行过程。

总结:

每个组件对应着一个fiber节点,每个hook对应一个hooks对象,hooks对象存放在fiiber节点上,而每次执行更新函数,会创建一个update,他通过next指针形成链表,存放在hooks对象上的queue.pending上,是一个环状链表,然后执行调度。调度会重新执行函数组件,然后重新执行useState函数,他会获取存放在hooks对象上的queue上的update链表,然后取出之后执行,获取新的状态state返回。
函数组件的状态存放在hooks对象上的memoizedState上,更新的action存放在hooks对象上queue的pending上,而hooks对象存放在fiber节点上。

与React的区别
        我们用尽可能少的代码模拟了Hooks的运行,但是相比React Hooks,他还有很多不足。以下是他与React Hooks的区别:

        React Hooks没有使用isMount变量,而是在不同时机使用不同的dispatcher。换言之,mount时的useState与update时的useState不是同一个函数。

        React Hooks有中途跳过更新的优化手段。

        React Hooks有batchedUpdates,当在click中触发三次updateNum,精简React会触发三次更新,而React只会触发一次。

        React Hooks的update有优先级概念,可以跳过不高优先的update。

        更多的细节,我们会在本章后续小节讲解。

学习文章出自:https://react.iamkasong.com/hooks/create.html#%E5%9C%A8%E7%BA%BFdemo

以上是关于React的hook之useState简单实现的主要内容,如果未能解决你的问题,请参考以下文章

react源码debugger-各个hooks的逻辑实现(useState和useEffect)

极简hooks实现,与react的思路一样的useState

react源码debugger-各个hooks的逻辑实现(useState和useEffect)

react源码debugger-各个hooks的逻辑实现(useState和useEffect)

react hook介绍

react hook介绍