残缺高频题库仓库地址:https://github.com/hzfe/awesome-interview

残缺高频题库浏览地址:https://febook.hzfe.org/

相干问题

  • React Hooks 是什么
  • React Hooks 是怎么实现的
  • 应用 React Hooks 须要留神什么

答复关键点

闭包 Fiber 链表

Hooks 是 React 16.8 的新增个性。它能够让你在不编写 class 的状况下应用 state 以及其余的 React 个性。

Hooks 次要是利用闭包来保留状态,应用链表保留一系列 Hooks,将链表中的第一个 Hook 与 Fiber 关联。在 Fiber 树更新时,就能从 Hooks 中计算出最终输入的状态和执行相干的副作用。

应用 Hooks 的注意事项:

  • 不要在循环,条件或嵌套函数中调用 Hooks。
  • 只在 React 函数中调用 Hooks。

知识点深刻

1. 简化实现

React Hooks 模仿实现

该示例是一个 React Hooks 接口的简化模仿实现,能够理论运行察看。其中 react.js 文件模仿实现了 useStateuseEffect 接口,其基本原理和 react 理论实现相似。

2. 比照剖析

2.1 状态 Hook

模仿的 useState 实现中,通过闭包,将 state 保留在 memoizedState[cursor]。 memoizedState 是一个数组,能够按程序保留 hook 屡次调用产生的状态。

let memoizedState = [];let cursor = 0;function useState(initialValue) {  // 首次调用时,传入的初始值作为 state,后续应用闭包中保留的 state  let state = memoizedState[cursor] ?? initialValue;  // 对游标进行闭包缓存,使得 setState 调用时,操作正确的对应状态  const _cursor = cursor;  const setState = (newValue) => (memoizedState[_cursor] = newValue);  // 游标自增,为接下来调用的 hook 应用时,援用 memoizedState 中的新地位  cursor += 1;  return [state, setState];}

理论的 useState 实现通过多方面的综合思考,React 最终抉择将 Hooks 设计为程序构造,这也是 Hooks 不能条件调用的起因。

function mountState<S>(  initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {  // 创立 Hook,并将以后 Hook 增加到 Hooks 链表中  const hook = mountWorkInProgressHook();  // 如果初始值是函数,则调用函数获得初始值  if (typeof initialState === "function") {    initialState = initialState();  }  hook.memoizedState = hook.baseState = initialState;  // 创立一个链表来寄存更新对象  const queue = (hook.queue = {    pending: null,    dispatch: null,    lastRenderedReducer: basicStateReducer,    lastRenderedState: initialState,  });  // dispatch 用于批改状态,并将此次更新增加到更新对象链表中  const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch =    (dispatchAction.bind(null, currentlyRenderingFiber, queue): any));  return [hook.memoizedState, dispatch];}

2.1 副作用 Hook

模仿的 useEffect 实现,同样利用了 memoizedState 闭包来存储依赖数组。依赖数组进行浅比拟,默认的比拟算法是 Object.is

function useEffect(cb, depArray) {  const oldDeps = memoizedState[cursor];  let hasChange = true;  if (oldDeps) {    // 比照传入的依赖数组与闭包中保留的旧依赖数组,采纳浅比拟算法    hasChange = depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));  }  if (hasChange) cb();  memoizedState[cursor] = depArray;  cursor++;}

理论的 useEffect 实现:

function mountEffect(  create: () => (() => void) | void,  deps: Array<mixed> | void | null): void {  return mountEffectImpl(    UpdateEffect | PassiveEffect, // fiberFlags    HookPassive, // hookFlags    create,    deps  );}function mountEffectImpl(fiberFlags, hookFlags, create, deps): void {  // 创立hook  const hook = mountWorkInProgressHook();  const nextDeps = deps === undefined ? null : deps;  // 设置 workInProgress 的副作用标记  currentlyRenderingFiber.flags |= fiberFlags; // fiberFlags 被标记到 workInProgress  // 创立 Effect, 挂载到 hook.memoizedState 上  hook.memoizedState = pushEffect(    HookHasEffect | hookFlags, // hookFlags 用于创立 effect    create,    undefined,    nextDeps  );}

3. Hooks 如何与 Fiber 独特工作

在理解如何工作之前,先看看 Hook 与 Fiber 的局部构造定义:

export type Hook = {  memoizedState: any, // 最新的状态值  baseState: any, // 初始状态值  baseQueue: Update<any, any> | null,  queue: UpdateQueue<any, any> | null, // 环形链表,存储的是该 hook 屡次调用产生的更新对象  next: Hook | null, // next 指针,之下链表中的下一个 Hook};
export type Fiber = {  updateQueue: mixed, // 存储 Fiber 节点相干的副作用链表  memoizedState: any, // 存储 Fiber 节点相干的状态值  flags: Flags, // 标识以后 Fiber 节点是否有副作用};

与上节中的模仿实现不同,实在的 Hooks 是一个单链表的构造,React 按 Hooks 的执行程序顺次将 Hook 节点增加到链表中。上面以 useState 和 useEffect 两个最罕用的 hook 为例,来剖析 Hooks 如何与 Fiber 独特工作。

在每个状态 Hook(如 useState)节点中,会通过 queue 属性上的循环链表记住所有的更新操作,并在 updade 阶段顺次执行循环链表中的所有更新操作,最终拿到最新的 state 返回。

状态 Hooks 组成的链表的具体构造如下图所示:

在每个副作用 Hook(如 useEffect)节点中,创立 effect 挂载到 Hook 的 memoizedState 中,并增加到环形链表的开端,该链表会保留到 Fiber 节点的 updateQueue 中,在 commit 阶段执行。

副作用 Hooks 组成的链表的具体构造如下图所示:

参考资料

  1. Why Do React Hooks Rely on Call Order?
  2. React hooks: not magic, just arrays