跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


use 是一个 React Hook,它可以让你读取类似于 Promisecontext 的资源的值。与其他 React Hook 不同的是,可以在循环和条件语句(如 if)中调用 use。但需要注意的是,调用 use 的函数仍然必须是一个组件或 Hook

当使用 Promise 调用 use Hook 时,它会与 Suspense 和错误边界 集成。当传递给 usePromise 处于 pending 时,调用 use 的组件也会 挂起。如果调用 use 的组件被包装在 Suspense 边界内,将显示后备 UI。一旦 Promise 被解决,Suspense 后备方案将被使用 use Hook 返回的数据替换。如果传递给 usePromise 被拒绝,将显示最近错误边界的后备 UI

二、细节


use 可以接收的数据类型为:

  1. Thenable: 用户传入一个 Promise, 将这个 Promise 包装为 ThenableThenable 有四种状态

    • untracked

    • pending

    • fulfilled

    • rejected

  2. ReactContext: 类似于 useContext , 读取 Context 数据

三、执行函数组件


React.jsbeginWork 阶段, 遇到 FunctionComponent 类型的 Fiber, 会执行 updateFunctionComponentupdateFunctionComponent 内部调用 renderWithHooks。主要工作如下:

  1. 重置状态: 函数组件执行前, 重置当前正在处理的 hook、当前正在使用的 hook

  2. 记录当前组件 fiber: 通过 currentlyRenderingFiber 变量记录当前组件 fiber

  3. 选择对应阶段的 Hook: mount 阶段选择 HooksDispatcherOnMount 作为 Hooks Map, update 阶段选择 HooksDispatcherOnUpdate 作为 Hooks Map函数组件mount 阶段 创建 hook 数据结构, 存储到 fiber.memoizedState 或者 hook.next, 初次建立其 hooksfiber 之间的关系。函数组件 update 阶段 找到当前函数组件 fiber, 找到当前 hook, 更新 hook 状态。

  4. 执行函数组件: 执行我们真正函数组件,所有的 hooks 将依次执行, 每个 hook 内部要根据 currentlyRenderingFiber 读取对应的内容,

  5. 重置状态: 执行完 Component, 立马重置 currentlyRenderingFiber, 防止函数组件外部调用。置当前正在处理的 hook、当前正在使用的 hook

3.1 /packages/react-reconciler/fiberHooks.js

export function renderWithHooks(workInProgress, Component, lane) {
renderLane = lane;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;

const current = workInProgress.alternate;

if (current !== null) {
// update
currentDispatcher.current = HooksDispatcherOnUpdate;
} else {
// mount
currentDispatcher.current = HooksDispatcherOnMount;
}

const props = workInProgress.pendingProps;
const children = Component(props);

currentHook = null;
renderLane = NoLane;
workInProgressHook = null;
currentlyRenderingFiber = null;

return children;
}

3.2 /packages/react-reconciler/fiberHooks.js

/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useMemo: mountMemo,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useCallback: mountCallback,
useTransition: mountTransition
};

/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useMemo: updateMemo,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useCallback: updateCallback,
useTransition: updateTransition
};

四、函数组件 mount 阶段 Hook


/**
* @description: Mount 阶段 Hooks 实现
*/
const HooksDispatcherOnMount = {
use,
useRef: mountRef,
useState: mountState,
useEffect: mountEffect,
useContext: readContext,
useTransition: mountTransition
};
function use(usable) {
if (usable !== null && typeof usable === 'object') {
if (typeof usable.then === 'function') {
const thenable = usable;
return trackUsedThenable(thenable);
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context = usable;
return readContext(context);
}
}
throw new Error('不支持的 use 参数' + usable);
}

五、函数组件 update 阶段 Hook


/**
* @description: Update 阶段 Hooks 实现
*/
const HooksDispatcherOnUpdate = {
use,
useRef: updateRef,
useState: updateState,
useEffect: updateEffect,
useContext: readContext,
useTransition: updateTransition
};
function use(usable) {
if (usable !== null && typeof usable === 'object') {
if (typeof usable.then === 'function') {
const thenable = usable;
return trackUsedThenable(thenable);
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context = usable;
return readContext(context);
}
}
throw new Error('不支持的 use 参数' + usable);
}

六、thenable


如果 use 接收的数据类型为 Thenable 时, 有如下逻辑

let suspendedThenable = null;

export const SuspenseException = new Error(
'这不是真实的错误, 是 Suspense 用来中断渲染的'
);

function noop() {}

export function getSuspenseThenable() {
if (suspendedThenable === null) {
throw new Error('应该存在 suspendedThenable, 这是一个 Bug');
}
const thenable = suspendedThenable;
suspendedThenable = null;
return thenable;
}

export function trackUsedThenable(thenable) {
switch (thenable.status) {
case 'fulfilled':
return thenable.value;
case 'rejected':
throw thenable.reason;
default:
if (typeof thenable.status === 'string') {
thenable.then(noop, noop);
} else {
const pending = thenable;
pending.status = 'pending';
pending.then(
val => {
if (pending.status === 'pending') {
const fulfilled = pending;
fulfilled.status = 'fulfilled';
fulfilled.value = val;
}
},
error => {
if (pending.status === 'pending') {
const rejected = pending;
rejected.status = 'rejected';
rejected.reason = error;
}
}
);
}
break;
}

suspendedThenable = thenable;

throw SuspenseException;
}

如上所示, throw SuspenseException 手动抛出 SuspenseException 专属错误

七、renderRoot


renderRoot 中捕获 SuspenseException 专属错误, 并在下一次 renderRoot 中, 处理 SuspenseException, 触发再一次的更新渲染。

/**
* @description: 防止进入死循环
*/
let retries = 0;

function renderRoot(root, lane, shouldTimesSlice) {
if (workInProgressRootRenderLane !== lane) {
prepareFreshStack(root, lane);
}

do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
const thrownValue = workInProgressThrownValue;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(root, workInProgress, thrownValue, lane);
}


shouldTimesSlice ? workLoopConcurrent() : workLoopSync();
break;
} catch (e) {
console.log('workLoop renderRoot 发生错误', e);

retries++;

if (retries > 20) {
console.log('此时已经进入死循环,不再执行 workLoop');
break;
}

handleThrow(root, e);
}
} while (true);

if (workInProgressRootExitStatus !== RootInProgress) {
return workInProgressRootExitStatus;
}

if (shouldTimesSlice && workInProgress !== null) {
return RootInComplete;
}

if (!shouldTimesSlice && workInProgress !== null) {
console.log('renderRoot 结束之后 workInProgress 不应该存在');
}

return RootCompleted;
}

八、handleThrow


handleThrow 捕获错误过程如下:

function handleThrow(root, thrownValue) {
if (thrownValue === SuspenseException) {
thrownValue = getSuspenseThenable();
workInProgressSuspendedReason = SuspendedOnData;
}

workInProgressThrownValue = thrownValue;
}

九、throwAndUnwindWorkLoop


throwAndUnwindWorkLoop 处理错误如下:

function throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, lane) {
// 1. 重置 FunctionComponent 的全局变量
resetHooksOnUnwind();
// 2. use 请求返回后重新触发更新 / ErrorBoundary 捕获错误
throwException(root, thrownValue, lane);
// 3. unwind 流程
unwindUnitOfWork(unitOfWork);
}

十、throwException


throwException 触发更新的过程如下:

import { ShouldCapture } from './fiberFlags';
import { markRootPinged } from './fiberLanes';
import { getSuspenseHandler } from './suspenseContext';
import { markRootUpdated, ensureRootIsScheduled } from './workLoop';

function attachPingListener(root, wakeable, lane) {
let pingCache = root.pingCache;
let threadIDs;

if (pingCache === null) {
threadIDs = new Set();
pingCache = root.pingCache = new WeakMap();
pingCache.set(wakeable, threadIDs);
} else {
threadIDs = pingCache.get(wakeable);
if (threadIDs == undefined) {
threadIDs = new Set();
pingCache.set(wakeable, threadIDs);
}
}

if (!threadIDs.has(lane)) {
threadIDs.add(lane);

function ping() {
if (pingCache !== null) {
pingCache.delete(wakeable);
}

markRootPinged(root, lane);
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}

wakeable.then(ping, ping);
}
}

export function throwException(root, value, lane) {
// Error Boundary
// thenable
if (
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) {
const wakeable = value;
const suspenseBoundary = getSuspenseHandler();
if (suspenseBoundary) {
suspenseBoundary.flags |= ShouldCapture;
}

attachPingListener(root, wakeable, lane);
}
}

十一、unwindUnitOfWork


unwindUnitOfWork 过程如下:

function unwindUnitOfWork(unitOfWork) {
let incompleteWork = unitOfWork;

do {
const next = unwindWork(incompleteWork);
if (next !== null) {
workInProgress = next;
return;
}

const returnFiber = incompleteWork.return;
if (returnFiber != null) {
returnFiber.deletions = null;
}

incompleteWork = returnFiber;
} while (incompleteWork !== null);

workInProgress = null;
workInProgressRootExitStatus = RootDidNotComplete;
}

十二、unwindWork


import { popProvider } from './fiberContext';
import { popSuspenseHandler } from './suspenseContext';
import { ContextProvider, SuspenseComponent } from './workTags';
import { DidCapture, NoFlags, ShouldCapture } from './fiberFlags';

export function unwindWork(workInProgress) {
const flags = workInProgress.flags;

switch (workInProgress.tag) {
case SuspenseComponent:
popSuspenseHandler();

if (
(flags & ShouldCapture) !== NoFlags &&
(flags & DidCapture) === NoFlags
) {
workInProgress.flags = (flags & ~ShouldCapture) | DidCapture;
return workInProgress;
}
break;
case ContextProvider:
const context = workInProgress.type._context;
popProvider(context);
return null;
default:
return null;
}

return null;
}

十三、沉淀与思考