认识
一、认识
use
是一个 React Hook
,它可以让你读取类似于 Promise
或 context
的资源的值。与其他 React Hook
不同的是,可以在循环和条件语句(如 if
)中调用 use
。但需要注意的是,调用 use
的函数仍然必须是一个组件或 Hook
。
当使用 Promise
调用 use Hook
时,它会与 Suspense
和错误边界 集成。当传递给 use
的 Promise
处于 pending
时,调用 use
的组件也会 挂起。如果调用 use
的组件被包装在 Suspense
边界内,将显示后备 UI
。一旦 Promise
被解决,Suspense
后备方案将被使用 use Hook
返回的数据替换。如果传递给 use
的 Promise
被拒绝,将显示最近错误边界的后备 UI
。
二、细节
use
可以接收的数据类型为:
-
Thenable
: 用户传入一个Promise
, 将这个Promise
包装为Thenable
。Thenable
有四种状态-
untracked
-
pending
-
fulfilled
-
rejected
-
-
ReactContext
: 类似于useContext
, 读取Context
数据
三、执行函数组件
在 React.js
的 beginWork
阶段, 遇到 FunctionComponent
类型的 Fiber
, 会执行 updateFunctionComponent
。updateFunctionComponent
内部调用 renderWithHooks
。主要工作如下:
-
重置状态: 函数组件执行前, 重置当前正在处理的
hook
、当前正在使用的hook
-
记录当前组件
fiber
: 通过currentlyRenderingFiber
变量记录当前组件fiber
-
选择对应阶段的
Hook
:mount
阶段选择HooksDispatcherOnMount
作为Hooks Map
,update
阶段选择HooksDispatcherOnUpdate
作为Hooks Map
。 函数组件mount
阶段 创建hook
数据结构, 存储到fiber.memoizedState
或者hook.next
, 初次建立其hooks
与fiber
之间的关系。函数组件update
阶段 找到当前函数组件fiber
, 找到当前hook
, 更新hook
状态。 -
执行函数组件: 执行我们真正函数组件,所有的
hooks
将依次执行, 每个hook
内部要根据currentlyRenderingFiber
读取对应的内容, -
重置状态: 执行完
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;
}