React之Hooks

Hooks

  • 在packages/react/src/ReactHooks.js目录下可以看见所有hooks,都是一个函数,内部调用了resolveDispatcher方法,然后使用对应函数名的方法
js 复制代码
export function useXXX(...args) {
  const dispatcher = resolveDispatcher()
  return dispatcher.useXXX(...)
}

function resolveDispatcher() {
  const dispatcher = ReactSharedInternals.H
  return dispatcher
}
  • 在packages/react-reconciler/src/ReactFiberHook.js下有个renderWithHooks函数
js 复制代码
export function renderWithHooks(current: Fiber | null, workInProgress: Fiber) {
  ReactSharedInternals.H =
      current === null || current.memoizedState === null
        ? HooksDispatcherOnMount
        : HooksDispatcherOnUpdate;
}
const HooksDispatcherOnMount = {
  useCallback: mountCallback,
  useContext: mountContext,
  useEffect: mountEffect,
  useImperativeHandle: mountImperativeHandle,
  useLayoutEffect: mountLayoutEffect,
  useInsertionEffect: mountInsertionEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState
}
const HooksDispatcherOnUpdate = {
  useCallback: updateCallback,
  useContext: readContext,
  useEffect: updateEffect,
  useImperativeHandle: updateImperativeHandle,
  useInsertionEffect: updateInsertionEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
}

Hook

  • hooks是以链表形式存储在fiber的memoizedState字段上,currentHook变量是当前fiber所属的列表,workInProgressHook变量是当前将要被添加进workInProgress fiber的列表
  • 你可以理解成同时存在两棵 Fiber 树,一棵 Current 树,对应着目前已经渲染到页面上的内容;另一棵是 WorkInProgress 树,记录着即将发生的修改。将这两个 FiberNode 的 alternate 属性分别指向对方。
  • 函数组件的 Hooks 也是在渲染阶段执行的。除了useContext ,Hooks 在挂载后,都会形成一个由 Hook.next 属性连接的单向链表,而这个链表会挂在 FiberNode.memoizedState 属性上。
js 复制代码
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
function mountWorkInProgressHook() {
  const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
  }
  // 下面这部分充分说明了为什么hook必须在顶层,如果出现在if这种语句中,被添加的顺序可能不确定
  if (workInProgressHook === null) {
    // This is the first hook in the list
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

function updateWorkInProgressHook() {
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }
  
  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }
  
    if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.
    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
    }
    currentHook = nextCurrentHook;
    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

callback

  • 可以将回调函数缓存起来,并且只在依赖项数组中的依赖项发生变化时才会重新创建新的函数实例,从而避免不必要的重新渲染。应用场景如下:渲染父子组件,父组件给子组件传递了一个函数,后续父组件更新,导致重新执行父函数,创建了新的函数实例传递给子组件,导致子组件的不必要的更新。
  • 不建议对任何函数都使用该hook,毕竟进行缓存也需要一定性能。
js 复制代码
function mountCallback(cb, deps) {
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps;
  hook.memoizedState = [cb, nextDeps]
  return cb
}
function updateCallback(cb, deps) {
  const hook = updateWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  const prevState = hook.memoziedState
  if (nextDeps !== null) {
    const prevDeps = prevState[1]
    if (areHookInputsEqual(nextDeps, prevDeps)) { // 对比依赖是否发生变化
      return prevState[0];
    }
  }
  hook.memoizedState = [cb, nextDeps];
  return cb;
}

memo

  • 与useCallback类似,也是用于缓存的,也可以避免子组件的冗余渲染,不过缓存的是计算结果。
js 复制代码
function mountMemo(nextCreate, deps) {
  const hook = mountWorkInProgressHook()
  const nextDeps = deps === undefined ? null : deps
  const nextValue = nextCreate()
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}
function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const prevState = hook.memoizedState;
  // Assume these are defined. If they're not, areHookInputsEqual will warn.
  if (nextDeps !== null) {
    const prevDeps: Array<mixed> | null = prevState[1];
    if (areHookInputsEqual(nextDeps, prevDeps)) {
      return prevState[0];
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

effect

  • 对应到 Class 组件,那么 useEffect 就涵盖了 ComponentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法。会在每次组件 render 完后判断依赖并执行。
  • useEffect 这样会产生副作用的 Hooks,会额外创建与 Hook 对象一一对应的 Effect 对象,赋值给 Hook.memoizedState 属性。此外,也会在 FiberNode.updateQueue 属性上,维护一个由 Effect.next 属性连接的单向链表,并把这个 Effect 对象加入到链表末尾。
  • 渲染完后异步执行,后续会按顺序先执行useEffect定义的的清除函数,然后执行其useEffect定义的副作用并保留清除函数供下次调用。
js 复制代码
function mountEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  currentlyRenderingFiber.flags |= fiberFlags;
  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    createEffectInstance(),
    nextDeps,
  );
}

function updateEffectImpl(
  fiberFlags: Flags,
  hookFlags: HookFlags,
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const effect: Effect = hook.memoizedState;
  const inst = effect.inst;

  // currentHook is null on initial mount when rerendering after a render phase
  // state update or for strict mode.
  if (currentHook !== null) {
    if (nextDeps !== null) {
      const prevEffect: Effect = currentHook.memoizedState;
      const prevDeps = prevEffect.deps;
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.flags |= fiberFlags;

  hook.memoizedState = pushEffect(
    HookHasEffect | hookFlags,
    create,
    inst,
    nextDeps,
  );
}

function mountEffect(create, deps) {
  mountEffectImpl(
      PassiveEffect | PassiveStaticEffect,
      HookPassive,
      create,
      deps,
    );
}

function updateEffect(create, deps) {
  updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}

layoutEffect

  • 和useEffect类似,不过是渲染之前同步执行。应用场景:需要在浏览器绘制前获取 DOM 元素的大小或位置,或者在浏览器绘制前修改 DOM。
  • 源码和Effect一致,打上的flag不一致。
js 复制代码
function mountLayoutEffect(create, deps) {
   return mountEffectImpl(UpdateEffect | LayoutStaticEffect;, HookLayout, create, deps);
}

functionn updateLayoutEffect(create, deps) {
  return updateEffectImpl(UpdateEffect, HookLayout, create, deps);
}

ref

  • useRef 看作是在函数组件之外创建的一个容器空间。在这个容器上,我们可以通过唯一的 current 属设置一个值,从而在函数组件的多次渲染之间共享这个值。
  • 使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的
  • 还可以保存某个 DOM 节点的引用。
js 复制代码
function mountRef(initialValue) {
  const hook = mountWorkInProgressHook();
  const ref = {current: initialValue};
  hook.memoizedState = ref;
  return ref;
}

function updateRef(initialValue) {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

state

  • 让函数组件具有维持状态的能力。在一个函数组件的多次渲染之间,这个 state 是共享的。
js 复制代码
function mountState(initialState) {
  const hook = mountStateImpl(initialState);
  const queue = hook.queue;
  const dispatch = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ));
  queue.dispatch = dispatch;
  return [hook.memoizedState, dispatch];
}

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}
相关推荐
好名字08218 分钟前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
隐形喷火龙20 分钟前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_7482411233 分钟前
Selenium之Web元素定位
前端·selenium·测试工具
等一场春雨35 分钟前
springboot 3 websocket react 系统提示,选手实时数据更新监控
spring boot·websocket·react.js
风无雨39 分钟前
react杂乱笔记(一)
前端·笔记·react.js
鑫~阳1 小时前
快速建站(网站如何在自己的电脑里跑起来) 详细步骤 一
前端·内容管理系统cms
egekm_sefg1 小时前
webrtc学习----前端推流拉流,局域网socket版,一对多
前端·学习·webrtc
m0_748234341 小时前
前端工作中问题点拆分
前端
艾斯特_1 小时前
JavaScript甘特图 dhtmlx-gantt
前端·javascript·甘特图
北海天空1 小时前
reactHooks到底钩到了什么?
前端·react.js