博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React 深度学习:ReactFiberHooks
阅读量:5846 次
发布时间:2019-06-18

本文共 13138 字,大约阅读时间需要 43 分钟。

path:packages/react-reconciler/src/ReactFiberHooks.js

类型定义

Dispatcher 分发器

export type Dispatcher = {  readContext
( context: ReactContext
, observedBits: void | number | boolean, ): T, useState
(initialState: (() => S) | S): [S, Dispatch
>], useReducer
( reducer: (S, A) => S, initialArg: I, init?: (I) => S, ): [S, Dispatch
], useContext
( context: ReactContext
, observedBits: void | number | boolean, ): T, useRef
(initialValue: T): { current: T}, useEffect( create: () => (() => void) | void, deps: Array
| void | null, ): void, useLayoutEffect( create: () => (() => void) | void, deps: Array
| void | null, ): void, useCallback
(callback: T, deps: Array
| void | null): T, useMemo
(nextCreate: () => T, deps: Array
| void | null): T, useImperativeHandle
( ref: { current: T | null} | ((inst: T | null) => mixed) | null | void, create: () => T, deps: Array
| void | null, ): void, useDebugValue
(value: T, formatterFn: ?(value: T) => mixed): void,};复制代码

Dispatcher 类型定义了 10 个 hook 方法的基本形式,并额外增加一个读取上下文的方法 readContext

Hooks 类型

export type HookType =  | 'useState'  | 'useReducer'  | 'useContext'  | 'useRef'  | 'useEffect'  | 'useLayoutEffect'  | 'useCallback'  | 'useMemo'  | 'useImperativeHandle'  | 'useDebugValue';复制代码

Update、UpdateQueue、Effect

type Update
= { expirationTime: ExpirationTime, action: A, eagerReducer: ((S, A) => S) | null, eagerState: S | null, next: Update
| null,};type UpdateQueue
= { last: Update
| null, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, lastRenderedState: S | null,};type Effect = { tag: HookEffectTag, create: () => (() => void) | void, destroy: (() => void) | void, deps: Array
| null, next: Effect,};export type FunctionComponentUpdateQueue = { lastEffect: Effect | null,};type BasicStateAction
= (S => S) | S;type Dispatch = A => void;复制代码

Hook

export type Hook = { memoizedState: any, baseState: any, baseUpdate: Update
| null, queue: UpdateQueue
| null, next: Hook | null,};复制代码

renderWithHooks

在 一文中我已经看到,最重要的部分是 ReactCurrentDispatcher 对象,它有一个 current 属性,这个属性是 Dispatcher 类型的对象。但到目前我们还没有见到这个对象的初始化,或者叫对象的具体实现。

通读 ReactFiberHooks.js 的代码,发现有几个方法中有对 ReactCurrentDispatcher.current 赋值的代码:

  • renderWithHooks
  • resetHooks
  • dispatchAction

当然,为了严谨,我用 IDEA 查找了所有对 ReactCurrentDispatcher.current 的引用,未发现其他有使用到的地方。

renderWithHooks

export function renderWithHooks(  current: Fiber | null,  workInProgress: Fiber,  Component: any,  props: any,  refOrContext: any,  nextRenderExpirationTime: ExpirationTime,): any {  renderExpirationTime = nextRenderExpirationTime;  currentlyRenderingFiber = workInProgress;  nextCurrentHook = current !== null ? current.memoizedState : null;  if (__DEV__) {    // do something  }  // The following should have already been reset  // currentHook = null;  // workInProgressHook = null;  // remainingExpirationTime = NoWork;  // componentUpdateQueue = null;  // didScheduleRenderPhaseUpdate = false;  // renderPhaseUpdates = null;  // numberOfReRenders = 0;  // sideEffectTag = 0;  // TODO Warn if no hooks are used at all during mount, then some are used during update.  // Currently we will identify the update render as a mount because nextCurrentHook === null.  // This is tricky because it's valid for certain types of components (e.g. React.lazy)  // Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.  // Non-stateful hooks (e.g. context) don't get added to memoizedState,  // so nextCurrentHook would be null during updates and mounts.  if (__DEV__) {    // do something  } else {    ReactCurrentDispatcher.current =      nextCurrentHook === null        ? HooksDispatcherOnMount        : HooksDispatcherOnUpdate;  }  let children = Component(props, refOrContext);  if (didScheduleRenderPhaseUpdate) {    do {      didScheduleRenderPhaseUpdate = false;      numberOfReRenders += 1;      // 从列表的开头重新开始      nextCurrentHook = current !== null ? current.memoizedState : null;      nextWorkInProgressHook = firstWorkInProgressHook;      currentHook = null;      workInProgressHook = null;      componentUpdateQueue = null;      if (__DEV__) {        // do something      }      ReactCurrentDispatcher.current = __DEV__        ? HooksDispatcherOnUpdateInDEV        : HooksDispatcherOnUpdate;      children = Component(props, refOrContext);    } while (didScheduleRenderPhaseUpdate);    renderPhaseUpdates = null;    numberOfReRenders = 0;  }  // We can assume the previous dispatcher is always this one, since we set it  // at the beginning of the render phase and there's no re-entrancy.  ReactCurrentDispatcher.current = ContextOnlyDispatcher;  const renderedWork: Fiber = (currentlyRenderingFiber: any);  renderedWork.memoizedState = firstWorkInProgressHook;  renderedWork.expirationTime = remainingExpirationTime;  renderedWork.updateQueue = (componentUpdateQueue: any);  renderedWork.effectTag |= sideEffectTag;  if (__DEV__) {    // do something  }  // This check uses currentHook so that it works the same in DEV and prod bundles.  // hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.  const didRenderTooFewHooks =    currentHook !== null && currentHook.next !== null;  renderExpirationTime = NoWork;  currentlyRenderingFiber = null;  currentHook = null;  nextCurrentHook = null;  firstWorkInProgressHook = null;  workInProgressHook = null;  nextWorkInProgressHook = null;  if (__DEV__) {    // do something  }  remainingExpirationTime = NoWork;  componentUpdateQueue = null;  sideEffectTag = 0;  // These were reset above  // didScheduleRenderPhaseUpdate = false;  // renderPhaseUpdates = null;  // numberOfReRenders = 0;  invariant(    !didRenderTooFewHooks,    'Rendered fewer hooks than expected. This may be caused by an accidental ' +      'early return statement.',  );  return children;}复制代码

上面的代码中有有两处赋值:

ReactCurrentDispatcher.current = __DEV__        ? HooksDispatcherOnUpdateInDEV        : HooksDispatcherOnUpdate;复制代码

ReactCurrentDispatcher.current = ContextOnlyDispatcher;复制代码

resetHooks

export function resetHooks(): void {  // We can assume the previous dispatcher is always this one, since we set it  // at the beginning of the render phase and there's no re-entrancy.  ReactCurrentDispatcher.current = ContextOnlyDispatcher;  // This is used to reset the state of this module when a component throws.  // It's also called inside mountIndeterminateComponent if we determine the  // component is a module-style component.  renderExpirationTime = NoWork;  currentlyRenderingFiber = null;  currentHook = null;  nextCurrentHook = null;  firstWorkInProgressHook = null;  workInProgressHook = null;  nextWorkInProgressHook = null;  if (__DEV__) {    // do something  }  remainingExpirationTime = NoWork;  componentUpdateQueue = null;  sideEffectTag = 0;  didScheduleRenderPhaseUpdate = false;  renderPhaseUpdates = null;  numberOfReRenders = 0;}复制代码
ReactCurrentDispatcher.current = ContextOnlyDispatcher;复制代码

dispatchAction

function dispatchAction
( fiber: Fiber, queue: UpdateQueue
, action: A,) { invariant( numberOfReRenders < RE_RENDER_LIMIT, 'Too many re-renders. React limits the number of renders to prevent ' + 'an infinite loop.', ); if (__DEV__) { // do something } const alternate = fiber.alternate; if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) ) { // This is a render phase update. Stash it in a lazily-created map of // queue -> linked list of updates. After this render pass, we'll restart // and apply the stashed updates on top of the work-in-progress hook. didScheduleRenderPhaseUpdate = true; const update: Update
= { expirationTime: renderExpirationTime, action, eagerReducer: null, eagerState: null, next: null, }; if (renderPhaseUpdates === null) { renderPhaseUpdates = new Map(); } const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); if (firstRenderPhaseUpdate === undefined) { renderPhaseUpdates.set(queue, update); } else { // Append the update to the end of the list. let lastRenderPhaseUpdate = firstRenderPhaseUpdate; while (lastRenderPhaseUpdate.next !== null) { lastRenderPhaseUpdate = lastRenderPhaseUpdate.next; } lastRenderPhaseUpdate.next = update; } } else { flushPassiveEffects(); const currentTime = requestCurrentTime(); const expirationTime = computeExpirationForFiber(currentTime, fiber); const update: Update
= { expirationTime, action, eagerReducer: null, eagerState: null, next: null, }; // Append the update to the end of the list. const last = queue.last; if (last === null) { // This is the first update. Create a circular list. update.next = update; } else { const first = last.next; if (first !== null) { // Still circular. update.next = first; } last.next = update; } queue.last = update; if ( fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork) ) { // The queue is currently empty, which means we can eagerly compute the // next state before entering the render phase. If the new state is the // same as the current state, we may be able to bail out entirely. const lastRenderedReducer = queue.lastRenderedReducer; if (lastRenderedReducer !== null) { let prevDispatcher; if (__DEV__) { prevDispatcher = ReactCurrentDispatcher.current; ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV; } try { const currentState: S = (queue.lastRenderedState: any); const eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute // it, on the update object. If the reducer hasn't changed by the // time we enter the render phase, then the eager state can be used // without calling the reducer again. update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { // Fast path. We can bail out without scheduling React to re-render. // It's still possible that we'll need to rebase this update later, // if the component re-renders for a different reason and by that // time the reducer has changed. return; } } catch (error) { // Suppress the error. It will throw again in the render phase. } finally { if (__DEV__) { ReactCurrentDispatcher.current = prevDispatcher; } } } } if (__DEV__) { // do something } scheduleWork(fiber, expirationTime); }}复制代码
if (__DEV__) {  prevDispatcher = ReactCurrentDispatcher.current;  ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;}复制代码

if (__DEV__) {    ReactCurrentDispatcher.current = prevDispatcher;}复制代码

但这都是开发环境的逻辑,不作过去阐述。

在上面三个方法中,我们看到 ReactCurrentDispatcher.current 分被赋予了如下值:

  • HooksDispatcherOnUpdate

  • ContextOnlyDispatcher

  • InvalidNestedHooksDispatcherOnUpdateInDEV

HooksDispatcherOnUpdate

const HooksDispatcherOnUpdate: Dispatcher = {  readContext,  useCallback: updateCallback,  useContext: readContext,  useEffect: updateEffect,  useImperativeHandle: updateImperativeHandle,  useLayoutEffect: updateLayoutEffect,  useMemo: updateMemo,  useReducer: updateReducer,  useRef: updateRef,  useState: updateState,  useDebugValue: updateDebugValue,};复制代码

ContextOnlyDispatcher

export const ContextOnlyDispatcher: Dispatcher = {  readContext,  useCallback: throwInvalidHookError,  useContext: throwInvalidHookError,  useEffect: throwInvalidHookError,  useImperativeHandle: throwInvalidHookError,  useLayoutEffect: throwInvalidHookError,  useMemo: throwInvalidHookError,  useReducer: throwInvalidHookError,  useRef: throwInvalidHookError,  useState: throwInvalidHookError,  useDebugValue: throwInvalidHookError,};复制代码
  • throwInvalidHookError 方法:

    function throwInvalidHookError() {    invariant(    false,    'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +      ' one of the following reasons:\n' +      '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +      '2. You might be breaking the Rules of Hooks\n' +      '3. You might have more than one copy of React in the same app\n' +      'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',    );}复制代码

    该方法直接抛出了一个错误,错误信息说明这是一个无效的 hook 调用,原因:

    • hook 只能在函数组件的主体内部调用
    • 有 React 版本和呈现器(如React DOM) 不匹配
    • 可能违反了 hook 的规则
    • 可能在同一个应用程序中有多个 React 副本

通过以上信息我们不难看出,ContextOnlyDispatcher 不过错误调用信息的载体,它暴露同样的方法以能够在错误地调用的钩子时能正确回退。

转载于:https://juejin.im/post/5d09754d6fb9a07eea327565

你可能感兴趣的文章
10410:Tree Reconstruction
查看>>
一个例子?
查看>>
微信内下载APK 微信浏览器apk下载的解决方案
查看>>
js_coding
查看>>
[linux]查看机器有几个cpu,是否支持64位
查看>>
eclipse 3.x中热部署WEB程序TOMCAT配置
查看>>
Linux平台Java调用so库-JNI使用例子
查看>>
PCM数据格式,多少字节算一帧
查看>>
高并发大流量专题---1、高并发大流量解决方案总结
查看>>
【转】Java集合间的相互转换
查看>>
python获取当前路径的方法
查看>>
利用pil库处理图像
查看>>
kbengine mmo源码(完整服务端源码+资源+完整客户端源码)
查看>>
java锁
查看>>
Spring Data JPA
查看>>
按比例缩小图片的CSS代码
查看>>
uva 10099 The Tourist Guide
查看>>
置换元素和非置换元素
查看>>
Selenium中的xpath定位
查看>>
思索问题
查看>>