当前位置:主页 > 查看内容

react hooks源码分析:useState

发布时间:2021-05-13 00:00| 位朋友查看

简介:前言 自从react 官方发布react hooks以来 项目开发组件时几乎都是使用函数式组件来开发。在使用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。

image

如果在条件语句中申明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上 这样每次函数更新的时候状态都不会丢失。


总结


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

image



本文转自网络,原文链接:https://developer.aliyun.com/article/784027
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
上一篇:端云互联 3.0 击破云原生开发的痛点 下一篇:没有了

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐