react hooks源码分析:useState

简介: 阅读react hooks源码了解工作原理,开阔思维,提升架构能力。

前言


自从react 官方发布react hooks来,项目开发组件时几乎都是使用函数式组件来开发。在使用hooks过程中可以起到简化代码并逻辑清晰,对比于类组件能更易于理解代码。网上也有很多关于这两种组件的优劣势对比,读者可自行去翻阅。本主主要是想通过阅读源码来了解这背后的原理。(因为我不想成为API工程师,哈哈哈)


在这篇文章中,我想通过源码的角度来分析下react hooks中的useState。假定读者已经对react hooks有一定的使用并初步了解。(如果不了解建议去官网学习,react官网


函数式组件和类组件


使用react开发基本上都是组件式开发,react组件分函数式组件和类组件。那函数式组件跟类组件的区别是什么。下面看两段代码


函数式组件

import React from 'react';


const App = (props) => {

return <div>hello {props.user}<div>;

}


类组件

import React from 'react';


class App extends React.Component {

 constructor(props) {

   super(props);

this.state = {

    name: 'Damon',

   };

 }


 componentDidMount() {}

 

 render() {

   return (

    <>

      <div>hello {this.props.user}</div>

  <div>hello {this.state.name}</div>

     </>

   )

 }

}

这两段代码除了没有自身的状态和生命周期外基本上算是等价的,那react发布hooks想解决的问题就是让函数式组件能拥有自身的状态和生命周期。

在hooks还没发布时,函数式组件一直都是作为UI组件负责渲染视图,没有自身的状态和 "生命周期函数" (当使用hooks开发时建议忘却生命周期函数的概念),类组件有自身的状态和生命周期函数可以处理复杂业务逻辑。

这两种组件有个本质的区别:函数式组件会捕获每次渲染时所用的值,类组件会创建一个实例来保存最新的值。

想深入了解的话可以阅读此文函数式组件与类组件有何不同?作者是Dan Abramov(react核心开发人员之一)。


hooks 初始阶段


分析源码首先从引入hooks开始,以useState为例子。

import { useState } from 'react';

react/src/ReactHooks.js

可以来看下useState这个方法做了什么 (简化了代码,去掉了类型和开发环境的提示)

export function useState(initialState){

 const dispatcher = resolveDispatcher();

 return dispatcher.useState(initialState);

}

调用useState等于是执行了 dispatcher.useState(initialState)

dispatcher是resolveDispatcher方法返回的,来看下这个方法做了什么。


resolveDispatcher

function resolveDispatcher() {

 const dispatcher = ReactCurrentDispatcher.current;

 return dispatcher;

}

ReactCurrentDispatcher

react/src/ReactCurrentDispatcher.js

const ReactCurrentDispatcher = {

 current: null,

};


export default ReactCurrentDispatcher;

其实看到这里第一阶段就已经结束了。ReactCurrentDispatcher.current初始化为null,先暂时记住这个接下来我们得从函数执行阶段来看。


useState 初始化


看react渲染逻辑兜兜转转到 react-reconciler/src/ReactFiberBeginWork.js    beginWork这个函数。benginWork函数通过传入的当前的Fiber来创建子Fiber节点。会根据Fiber.tag来判断生成不同的字节点。

function beginWork(

current, // 每次渲染的时候会产生一个current fiber树,commit阶段会替换成真实的dom

 workInProgress, // 更新过程中,每个Fiber都会有一个跟其对应的Fiber,在更新结束后current会和workInProgress交换位置

 renderLanes,

){

 //... 先不关心其他逻辑

switch (workInProgress.tag) {

   // 当function component第一次创建Fiber的时候,组件类型是 IndeterminateComponent (具体可以看createFiberFormTypeAndProps)

   case IndeterminateComponent: {

     return mountIndeterminateComponent(

       current,

       workInProgress,

       workInProgress.type,

       renderLanes,

     );

   }

}

}


function mountIndeterminateComponent(

_current,

 workInProgress,

 Component,

 renderLanes,

) {

//...

 

 value = renderWithHooks(

   null, // 第一次渲染是null

   workInProgress, // workInProgress fiber

   Component, // 组件本身

   props, // props

   context, // 上下文

   renderLanes,

 );

}


export function renderWithHooks(

 current,

 workInProgress,

 Component,

 props,

 secondArg,

 nextRenderLanes,

) {

 renderLanes = nextRenderLanes;

 currentlyRenderingFiber = workInProgress;


 workInProgress.memoizedState = null;

 workInProgress.updateQueue = null;

 workInProgress.lanes = NoLanes;

 

 // current === null 第一次渲染 current !== null 更新阶段

 ReactCurrentDispatcher.current =

   current === null || current.memoizedState === null

    ? HooksDispatcherOnMount

  : HooksDispatcherOnUpdate;

 

 // 函数组件被执行

 let children = Component(props, secondArg);


 // 这里的逻辑先放一放

 if (didScheduleRenderPhaseUpdateDuringThisPass) {}


 // 当你不在函数组件内部执行hooks时候会抛出异常, ContextOnlyDispatcher对象方法都是抛出异常的方法

 ReactCurrentDispatcher.current = ContextOnlyDispatcher;


 const didRenderTooFewHooks =

   currentHook !== null && currentHook.next !== null;


 renderLanes = NoLanes;

 currentlyRenderingFiber = (null: any);


 currentHook = null;

 workInProgressHook = null;


 didScheduleRenderPhaseUpdate = false;


 invariant(

   !didRenderTooFewHooks,

   'Rendered fewer hooks than expected. This may be caused by an accidental ' +

     'early return statement.',

 );


 if (enableLazyContextPropagation) {

   if (current !== null) {

     if (!checkIfWorkInProgressReceivedUpdate()) {

       const currentDependencies = current.dependencies;

       if (

         currentDependencies !== null &&

         checkIfContextChanged(currentDependencies)

       ) {

         markWorkInProgressReceivedUpdate();

       }

     }

   }

 }


 return children;

}

renderWithHooks  这个方法是函数执行的主要方法,首先是把memoizedState和updateQueue等于null,然后通过判断current是否为null来赋值不同的hooks对象,current为null说明是第一次渲染不为null说明是更新,这里第一次渲染跟更新是执行不同的hooks对象方法的。还记得第一阶段看到ReactCurrentDispatcher.current吗?就是在这里被赋值的。


Component 调用是我们的函数组件被执行了,我们写的逻辑就是在这里被执行的。hooks也会依次按照顺序执行。


hookDispatchOnMount 和 hookDispatchOnUpdate


hookDispatchOnMount是第一次渲染,hookDispatchOnUpdate是更新阶段。

const HooksDispatcherOnMount: Dispatcher = {

 readContext,


 useCallback: mountCallback,

 useContext: readContext,

 useEffect: mountEffect,

 useImperativeHandle: mountImperativeHandle,

 useLayoutEffect: mountLayoutEffect,

 useMemo: mountMemo,

 useReducer: mountReducer,

 useRef: mountRef,

 useState: mountState,

 useDebugValue: mountDebugValue,

 useDeferredValue: mountDeferredValue,

 useTransition: mountTransition,

 useMutableSource: mountMutableSource,

 useOpaqueIdentifier: mountOpaqueIdentifier,


 unstable_isNewReconciler: enableNewReconciler,

};


const HooksDispatcherOnUpdate: Dispatcher = {

 readContext,


 useCallback: updateCallback,

 useContext: readContext,

 useEffect: updateEffect,

 useImperativeHandle: updateImperativeHandle,

 useLayoutEffect: updateLayoutEffect,

 useMemo: updateMemo,

 useReducer: updateReducer,

 useRef: updateRef,

 useState: updateState,

 useDebugValue: updateDebugValue,

 useDeferredValue: updateDeferredValue,

 useTransition: updateTransition,

 useMutableSource: updateMutableSource,

 useOpaqueIdentifier: updateOpaqueIdentifier,


 unstable_isNewReconciler: enableNewReconciler,

};

我们先来看看第一次渲染的mountState做了什么。

function mountState(initialState){

 const hook = mountWorkInProgressHook();

 if (typeof initialState === 'function') {

   initialState = initialState();

 }

 hook.memoizedState = hook.baseState = initialState;

 const queue = (hook.queue = { // 更新队列

   pending: null, // 待更新

   interleaved: null,

   lanes: NoLanes,

   dispatch: null, // 更新函数

   lastRenderedReducer: basicStateReducer, // 获取最新的state

   lastRenderedState: initialState, // 最后一次的state

 });

 

 // dispatchActio负责更新ui的函数

 const dispatch = (queue.dispatch = (dispatchAction.bind(

   null,

   currentlyRenderingFiber,

   queue,

 ));

 return [hook.memoizedState, dispatch];

}

首先调用了mountWorkInProgressHook函数得到一个hook(待会来详细看看这个方法干了什么),然后判断initialState是否function,是的话执行得到state数据。接着把state赋值给了memoizedState和baseState。申明了一个queue对象,queue是个待更新队列,dispatch是负责更新的函数,具体怎么更新的在dispatchAction方法内可查看。


mountWorkInProgressHook

function mountWorkInProgressHook() {

 const hook: Hook = {

   memoizedState: null, // 不同的hook保存的不同 useState保存的是state useEffect保存的是                 effect对象

   baseState: null, // 最新的值

   baseQueue: null, // 最新的队列

   queue: null, // 待更新队列


   next: null, // 指向下一个hook对象

 };


 // react hooks的数据结构是链表的方式,具体的逻辑就在这里。

 if (workInProgressHook === null) {

   // 函数内的第一个hooks就会走到这里

   currentlyRenderingFiber.memoizedState = workInProgressHook = hook;

 } else {

   // 接下来每个hook都会被添加到链接到未尾

   workInProgressHook = workInProgressHook.next = hook;

 }

 return workInProgressHook;

}

每次执行一个hooks函数都会调用这个方法来创建一个hook对象,这个对象里保存了不同hook所对应的数据,最新的state数据,更新的队列和指向下一个hook的对象。


来看个例子,看看为什么不能在条件语句中申明hook

import React, { useState, useEffect, useRef } from 'react';


const App = () => {

const [name, setName] = useState('Damon');

const [age, setAge] = useState(23);

 if (age !== 23) {

   const Ref = useRef(null);

 }

 

 useEffect(() => {

  console.log(name, age);

 }, []);

 

 return (

  <div>

    <span>{name}</span>

    <span>{age}</span>

   </div>

 )

}


export default App;

当这个App组件被渲染的时候,workInProgressHook.memoizedState中会以链表的形式来保存这些hook。

如果在条件语句中申明hook,那么在更新阶段链表结构会被破坏,Fiber树上缓存的hooks信息就会和当前的workInProgressHook不一致,不一致的情况下读取数据可能就会出现异常。


dispatchAction

dispatchAction这个是负责更新的函数,在mountState中通过bind绑定然后赋值给了dispatch。dispatch就是上面例子结构出来的setName。我们来看看这个函数又干了什么。

function dispatchAction(

 fiber, // 当前的fiber数

 queue, // mountState申明的更新队列

 action, // 这个就是我们setState传进来的参数

) {

   

  // 计算 expirationTime

 const eventTime = requestEventTime();

 const lane = requestUpdateLane(fiber);


 // react更新中都会有一个update

 const update = {

   lane,

   action,

   eagerReducer: null,

   eagerState: null,

   next: null,

 };


 const alternate = fiber.alternate;

   // 判断是否处于渲染阶段

 if (

   fiber === currentlyRenderingFiber ||

   (alternate !== null && alternate === currentlyRenderingFiber)

 ) {

   didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;

   const pending = queue.pending;

   if (pending === null) {

     // This is the first update. Create a circular list.

     update.next = update;

   } else {

     update.next = pending.next;

     pending.next = update;

   }

   queue.pending = update;

 } else {

   if (isInterleavedUpdate(fiber, lane)) {

     const interleaved = queue.interleaved;

     if (interleaved === null) {

       // This is the first update. Create a circular list.

       update.next = update;

       // At the end of the current render, this queue's interleaved updates will

       // be transfered to the pending queue.

       pushInterleavedQueue(queue);

     } else {

       update.next = interleaved.next;

       interleaved.next = update;

     }

     queue.interleaved = update;

   } else {

     const pending = queue.pending;

     if (pending === null) {

       // This is the first update. Create a circular list.

       update.next = update;

     } else {

       update.next = pending.next;

       pending.next = update;

     }

     queue.pending = update;

   }


   if (

     fiber.lanes === NoLanes &&

     (alternate === null || alternate.lanes === NoLanes)

   ) {

     const lastRenderedReducer = queue.lastRenderedReducer;

     if (lastRenderedReducer !== null) {

       let prevDispatcher;

       try {

         const currentState = queue.lastRenderedState; // 上一次state

         // 这段逻辑会去进行浅对比,上一个state和当前state相等的话,就return界面不会更新

         const eagerState = lastRenderedReducer(currentState, action);

         update.eagerReducer = lastRenderedReducer;

         update.eagerState = eagerState;

         if (is(eagerState, currentState)) {

           return;

         }

       } catch (error) {

         // Suppress the error. It will throw again in the render phase.

       } finally {}

     }

   }

   

   // 渲染更新

   const root = scheduleUpdateOnFiber(fiber, lane, eventTime);


   if (isTransitionLane(lane) && root !== null) {

     let queueLanes = queue.lanes;

     queueLanes = intersectLanes(queueLanes, root.pendingLanes);

     const newQueueLanes = mergeLanes(queueLanes, lane);

     queue.lanes = newQueueLanes;

     markRootEntangled(root, newQueueLanes);

   }

 }


 if (enableSchedulingProfiler) {

   markStateUpdateScheduled(fiber, lane);

 }

}

这段代码还是比较复杂的,有一些和fiber相关的逻辑。但在这里关于hooks的就是会创建一个update对象然后添加到queue链表上面,然后会判断当前是否处于渲染阶段,不是的话就会去获取上一个state和当前的state进行浅对比,相等就会return不会执行更新,不相等就会执行scheduleUpdateOnFiber进行更新。

useState 更新

上面讲了初始阶段react会给ReactCurrentDispatcher.current赋值HooksDispatcherOnMount,更新阶段赋值HooksDispatcherOnUpdate。在更新阶段实际上调用的是updateState。

function updateState(initialState){

 return updateReducer(basicStateReducer, initialState);

}


function basicStateReducer(state, action){

 // 这里的action就是例子中的Damon。 const [name, SetName] = useState('Damon');

 return typeof action === 'function' ? action(state) : action;

}

其实useState就是个简化版的useReducer,看下useReducer干了些啥。


updateReducer

function updateReducer(

 reducer,

 initialArg,

 init,

){

  //这里是获取当前的hooks,每一次函数更新的时候都会执行到hook,这个方法会保证每次更新状态不丢失

 const hook = updateWorkInProgressHook();

  //拿到更新队列

 const queue = hook.queue;

 invariant(

   queue !== null,

   'Should have a queue. This is likely a bug in React. Please file an issue.',

 );


   // 调用lastRenderedReducer可获取到state

 queue.lastRenderedReducer = reducer;


   // 拿到当前的函数内的hook

 const current = currentHook;


 let baseQueue = current.baseQueue;


 const pendingQueue = queue.pending;

 if (pendingQueue !== null) {

   // 这里主要是把baseQueue和pendingQueue做了交换,然后赋值到current上。

   if (baseQueue !== null) {

     const baseFirst = baseQueue.next;

     const pendingFirst = pendingQueue.next;

     baseQueue.next = pendingFirst;

     pendingQueue.next = baseFirst;

   }

   current.baseQueue = baseQueue = pendingQueue;

   queue.pending = null;

 }


 if (baseQueue !== null) {

   const first = baseQueue.next;

   let newState = current.baseState;


   let newBaseState = null;

   let newBaseQueueFirst = null;

   let newBaseQueueLast = null;

   let update = first;

   // 这里会循环的遍历update

   do {

     const updateLane = update.lane;

     // 这里有涉及到优先级相关到逻辑

     if (!isSubsetOfLanes(renderLanes, updateLane)) {

       const clone = {

         lane: updateLane,

         action: update.action,

         eagerReducer: update.eagerReducer,

         eagerState: update.eagerState,

         next: null,

       };

       if (newBaseQueueLast === null) {

         newBaseQueueFirst = newBaseQueueLast = clone;

         newBaseState = newState;

       } else {

         newBaseQueueLast = newBaseQueueLast.next = clone;

       }

       currentlyRenderingFiber.lanes = mergeLanes(

         currentlyRenderingFiber.lanes,

         updateLane,

       );

       markSkippedUpdateLanes(updateLane);

     } else {

        // This update does have sufficient priority.

        // 这个更新有足够的优先级

       if (newBaseQueueLast !== null) {

         const clone = {

           lane: NoLane,

           action: update.action,

           eagerReducer: update.eagerReducer,

           eagerState: update.eagerState,

           next: null,

         };

         newBaseQueueLast = newBaseQueueLast.next = clone;

       }

       if (update.eagerReducer === reducer) {

         newState = update.eagerState;

       } else {

         const action = update.action;

         newState = reducer(newState, action);

       }

     }

     update = update.next;

   } while (update !== null && update !== first);


   if (newBaseQueueLast === null) {

     newBaseState = newState;

   } else {

     newBaseQueueLast.next = newBaseQueueFirst;

   }


   if (!is(newState, hook.memoizedState)) {

     markWorkInProgressReceivedUpdate();

   }

 

   // 替换hook上的值

   hook.memoizedState = newState;

   hook.baseState = newBaseState;

   hook.baseQueue = newBaseQueueLast;


   queue.lastRenderedState = newState;

 }


 const lastInterleaved = queue.interleaved;

 if (lastInterleaved !== null) {

   let interleaved = lastInterleaved;

   do {

     const interleavedLane = interleaved.lane;

     currentlyRenderingFiber.lanes = mergeLanes(

       currentlyRenderingFiber.lanes,

       interleavedLane,

     );

     markSkippedUpdateLanes(interleavedLane);

     interleaved = interleaved.next;

   } while (interleaved !== lastInterleaved);

 } else if (baseQueue === null) {

   queue.lanes = NoLanes;

 }


 const dispatch = queue.dispatch;

 return [hook.memoizedState, dispatch];

}

首先获取了当前正在工作的hook,然后把queue.pending合并到baseQueue,这样做其实是有可能新的更新还没有处理,一次更新中可能会有多个setName,所以需要把queue.pending中的update合并到baseQueue内。接着会通过循环遍历链表,执行每一次更新去得到最新的state,把hook对象的值更新到最新。然后返回最新的memoizedState和dispatch。这里的dispatch其实就是dispatchAction,dispatchAction主要是负责更新界面的函数。


updateWorkInProgressHook


react中每一个hook更新阶段都会调用updateWorkInProgressHook来获取当前的hook。

function updateWorkInProgressHook() {

 let nextCurrentHook;

 

 if (currentHook === null) {

   // 第一个hooks会走到这,然后在fiber memoizedState中获取

   // currentlyRenderingFiber.alternate 这个是当前的fiber树

   const current = currentlyRenderingFiber.alternate;

   if (current !== null) {

     nextCurrentHook = current.memoizedState;

   } else {

     nextCurrentHook = null;

   }

 } else {

   // 因为是链表数据结构 其他的hook直接从next获取

   nextCurrentHook = currentHook.next;

 }


 let nextWorkInProgressHook;

 if (workInProgressHook === null) {

   // 跟上面一样 第一次执行hooks时候

   nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;

 } else {

   nextWorkInProgressHook = workInProgressHook.next;

 }


 if (nextWorkInProgressHook !== null) {

   workInProgressHook = nextWorkInProgressHook;

   nextWorkInProgressHook = workInProgressHook.next;


   currentHook = nextCurrentHook;

 } else {


   invariant(

     nextCurrentHook !== null,

     'Rendered more hooks than during the previous render.',

   );

   currentHook = nextCurrentHook;


   // 创建一个新的hook

   const newHook: Hook = {

     memoizedState: currentHook.memoizedState,


     baseState: currentHook.baseState,

     baseQueue: currentHook.baseQueue,

     queue: currentHook.queue,


     next: null,

   };


   if (workInProgressHook === null) {

     // 第一个hook添加到memoizedState和workInProgressHook

     currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;

   } else {

     // 其他的hook添加到链表末尾

     workInProgressHook = workInProgressHook.next = newHook;

   }

 }

 return workInProgressHook;

}

这段代码的作用主要是当函数每次更新的时候都会执行到hook,需要从fiber树中找到对应的hook然后赋值到workInProgressHook上,这样每次函数更新的时候状态都不会丢失。


总结


最后来个分析的流程图吧(本人水平有限,哈哈哈。欢迎一起交流学习)


相关文章
|
25天前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
17天前
|
前端开发 JavaScript
React Hooks 全面解析
【10月更文挑战第11天】React Hooks 是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性,简化了状态管理和生命周期管理。本文从基础概念入手,详细介绍了 `useState` 和 `useEffect` 的用法,探讨了常见问题和易错点,并提供了代码示例。通过学习本文,你将更好地理解和使用 Hooks,提升开发效率。
52 4
|
19天前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
25天前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
4天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
16 2
|
9天前
|
前端开发 开发者
React 提供的其他重要 Hooks
【10月更文挑战第20天】React 提供了一系列强大的 Hooks,除了 `useRef` 之外,还有许多其他重要的 Hooks,它们共同构成了函数式组件开发的基础。
23 6
|
17天前
|
前端开发 JavaScript 开发者
React Hooks
10月更文挑战第13天
31 1
|
23天前
|
前端开发
|
18天前
|
存储 前端开发 JavaScript
React useState 和 useRef 的区别
本文介绍了 React 中 `useState` 和 `useRef` 这两个重要 Hook 的区别和使用场景。`useState` 用于管理状态并在状态变化时重新渲染组件,适用于表单输入、显示/隐藏组件、动态样式等场景。`useRef` 则用于在渲染之间保持可变值而不触发重新渲染,适用于访问 DOM 元素、存储定时器 ID 等场景。文章还提供了具体的代码示例,帮助读者更好地理解和应用这两个 Hook。
28 0
|
22天前
|
前端开发 JavaScript API
自定义React Hooks综合指南
本文介绍了React Hooks及其在组件开发中的作用,重点讲解了自定义Hook的创建和使用方法。通过实例展示了如何创建`useWindowWidth`、`useFetch`和`useForm`等自定义Hook,并分享了使用自定义Hook的最佳实践。文章强调了自定义Hook在提高代码复用性和组件可维护性方面的重要性。
38 0