自从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开始 以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方法返回的 来看下这个方法做了什么。
function resolveDispatcher() {
? const dispatcher ReactCurrentDispatcher.current;
? return dispatcher;
}
ReactCurrentDispatcherreact/src/ReactCurrentDispatcher.js
const ReactCurrentDispatcher {
? current: null,
};
export default ReactCurrentDispatcher;
其实看到这里第一阶段就已经结束了。ReactCurrentDispatcher.current初始化为null 先暂时记住这个接下来我们得从函数执行阶段来看。
看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是更新阶段。
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方法内可查看。
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这个是负责更新的函数 在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进行更新。
上面讲了初始阶段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干了些啥。
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主要是负责更新界面的函数。
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上 这样每次函数更新的时候状态都不会丢失。
最后来个分析的流程图吧 本人水平有限 哈哈哈。欢迎一起交流学习
本文转载自公众号读芯术(ID:AI_Discovery)。 用谷歌搜索数据科学家的基本技能,...
案例背景 迄今为止,阿里云一直与澳大利亚、新西兰的20所大学合作,包括悉尼大学...
Java 16新特性 2021年3月16日,甲骨文正式发布了Java 16!想当年JDK1.6新出的场景...
加密货币的概念对于许多人仍然是比较陌生的。而比特币、莱特币等加密货币的吸引...
功能介绍 根据输入条件过滤查询弹性伸缩组列表。查询结果分页显示。 可根据伸缩...
每当有人发布关于 python 处理 Excel 数据的文章,总会有人只看了标题就评论: v...
了解 Lua 如何处理数据的读写。 有些数据是临时的,存储在 RAM 中,只有在应用运...
如何自动部署应用? 支持Cloud-Init特性后,对使用弹性伸缩有哪些影响? 为什么...
TOP云 1 月 8 日讯,日前又一三声母 域名 “ 投融资 ”trz.com 以 72 万高价成功...
一、什么是实时计算 Flink 版? 实时计算 Flink 版(Alibaba Cloud Realtime Com...