Skip to content

Fre 框架 Hooks 实现机制深度解析

Q1: 这个 hooks 实现方式上,在 fiber 上挂 hooks 在用的时候怎么用的呢,怎么消费这个 hooks list?

A1: Hooks 在 Fiber 上的存储与消费机制

Hooks 在 Fiber 上的存储结构

每个 fiber 节点上都有一个 hooks 对象,包含三个数组:

typescript
interface Hooks {
  list: HookList[]      // 存储所有 hooks 的状态数据(useState、useMemo、useRef 等)
  effect: HookEffect[]  // 存储 useEffect 的副作用
  layout: HookEffect[]  // 存储 useLayout 的副作用
}

基于 Cursor 的顺序访问机制

typescript
let cursor = 0

export const resetCursor = () => {
  cursor = 0
}

export const getSlot = <T extends HookList = HookList>(cursor: number) => {
  const current: Fiber = useFiber()
  const hooks = current.hooks || (current.hooks = { list: [], effect: [], layout: [] })
  if (cursor >= hooks.list.length) {
    hooks.list.push([] as any)
  }
  return [hooks.list[cursor], current] as unknown as [Partial<T>, Fiber]
}

组件渲染时的 Hook 消费流程

  1. 重置 cursor:每次组件重新渲染时,resetCursor() 将 cursor 重置为 0
  2. 设置上下文resetFiber(fiber) 设置当前 fiber 为全局上下文
  3. 执行组件函数(fiber.type as FC)(fiber.props) 执行组件函数
  4. 顺序访问:组件内部调用的每个 hook 都通过 getSlot(cursor++) 获取对应位置的状态

具体 Hook 的消费示例

useState 为例:

typescript
export const useReducer = <S, A>(
  reducer?: Reducer<S, A>,
  initState?: S | (() => S)
): [S, Dispatch<A>] => {
  const [hook, current] = getSlot<HookReducer>(cursor++)  // 获取当前位置的 hook 数据
  if (hook.length === 0) {
    hook[0] = isFn(initState) ? initState() : initState   // 首次调用时初始化状态值
  }
  hook[1] = (value: A | Dispatch<A>) => {
    // 创建 dispatch 函数,状态改变时触发组件更新
    let v = reducer ? reducer(hook[0], value as any) : isFn(value) ? value(hook[0]) : value
    if (hook[0] !== v) {
      hook[0] = v
      if (typeof window !== 'undefined') {
        update(current)  // 触发重新渲染
      }
    }
  }
  return hook as Required<HookReducer>  // 返回 [状态值, dispatch函数]
}

副作用 Hook 的执行时机

typescript
const bubble = (fiber: Fiber) => {
  if (fiber.isComp) {
    if (fiber.hooks) {
      side(fiber.hooks.layout)                           // 同步执行 layout effects
      schedule(() => side(fiber.hooks.effect) as undefined) // 异步执行 effects
    }
  }
}

const side = (effects?: HookEffect[]) => {
  effects.forEach((e) => e[2] && e[2]())  // 清理上一次的副作用
  effects.forEach((e) => (e[2] = e[0]()))  // 执行新的副作用函数
  effects.length = 0                       // 清空副作用队列
}

Q2: hooks 的值是怎么给到 ui 的呢?

A2: Hooks 值传递给 UI 的完整流程

1. Hook 值的返回机制

当组件函数执行时,hooks 直接返回存储在 fiber.hooks.list 中的当前状态值:

typescript
export const useState = <T>(initState: T | (() => T)) => {
  return useReducer<T, SetStateAction<T>>(null, initState)  // 返回 [状态值, dispatch函数]
}

2. 组件函数执行与值传递

typescript
const updateHook = (fiber: Fiber) => {
  resetCursor()
  resetFiber(fiber)
  fiber.node = fiber.node || fragment(fiber)
  try {
    let children = (fiber.type as FC)(fiber.props)  // 执行组件函数,获取 JSX
    reconcileChildren(fiber, simpleVnode(children))  // 处理返回的 JSX
  } catch (e) {
    // 错误处理
  }
}

3. 值传递的详细过程

以具体组件为例:

javascript
function App() {
  const [list, setList] = useState([1, 2, 3]);  // hook 返回当前状态值
  return (
    <div>
      {list.map((d) => (  // 直接使用 hook 返回的值构建 JSX
        <span>{d}</span>
      ))}
      <button onClick={() => setList(list.concat(4))}>+</button>
    </div>
  );
}

执行流程:

  1. useState([1, 2, 3])fiber.hooks.list[0] 获取状态值 [1, 2, 3]
  2. list.map((d) => <span>{d}</span>) 使用状态值创建子元素数组
  3. 返回包含这些子元素的 JSX 结构
  4. JSX 被转换为 fiber 节点树,最终渲染为 DOM

4. 状态更新触发重新渲染

当用户交互触发状态更新时:

typescript
hook[1] = (value: A | Dispatch<A>) => {
  let v = reducer ? reducer(hook[0], value as any) : isFn(value) ? value(hook[0]) : value
  if (hook[0] !== v) {
    hook[0] = v        // 更新 hook 状态
    update(current)    // 触发组件重新渲染
  }
}

重新渲染时会重新执行组件函数,获取更新后的状态值,生成新的 JSX,通过 diff 算法更新 DOM。


Q3: useState([1, 2, 3])fiber.hooks.list[0][0] 这个怎么知道从 0 获取的?

A3: 基于 Cursor 的位置索引机制

全局 Cursor 变量

typescript
let cursor = 0

export const resetCursor = () => {
  cursor = 0
}

这个全局的 cursor 变量追踪当前正在处理的 hook 的索引位置。

组件渲染时 Cursor 的重置

typescript
const updateHook = (fiber: Fiber) => {
  resetCursor()  // 每次组件重新渲染时,cursor 重置为 0
  resetFiber(fiber)
  // ...
  let children = (fiber.type as FC)(fiber.props)  // 执行组件函数
}

Hook 调用时 Cursor 的递增

typescript
export const useReducer = <S, A>(...) => {
  const [hook, current] = getSlot<HookReducer>(cursor++)  // 关键:cursor++
  // ...
}

具体执行顺序示例

假设有这样一个组件:

javascript
function App() {
  const [count, setCount] = useState(0);        // 第1个hook调用
  const [name, setName] = useState('hello');    // 第2个hook调用  
  const [list, setList] = useState([1, 2, 3]);  // 第3个hook调用
  
  return <div>{count} {name} {list.join(',')}</div>;
}

首次渲染

  1. resetCursor()cursor = 0
  2. useState(0)getSlot(0) → 访问 fiber.hooks.list[0]cursor++cursor = 1
  3. useState('hello')getSlot(1) → 访问 fiber.hooks.list[1]cursor++cursor = 2
  4. useState([1, 2, 3])getSlot(2) → 访问 fiber.hooks.list[2]cursor++cursor = 3

重新渲染时

  1. resetCursor()cursor = 0
  2. useState(0)getSlot(0) → 访问 fiber.hooks.list[0](获取之前保存的状态)
  3. useState('hello')getSlot(1) → 访问 fiber.hooks.list[1]
  4. useState([1, 2, 3])getSlot(2) → 访问 fiber.hooks.list[2]

为什么必须保持调用顺序

这就是 React Hooks 规则的原因:

javascript
// ❌ 错误:条件性调用 hooks
function BadComponent({ condition }) {
  if (condition) {
    const [count, setCount] = useState(0);  // 有时在位置0,有时不存在
  }
  const [name, setName] = useState('hello'); // 位置会变化!
}

// ✅ 正确:始终相同顺序调用
function GoodComponent({ condition }) {
  const [count, setCount] = useState(condition ? 0 : null);
  const [name, setName] = useState('hello');
}

Q4: 所以这个 fiber 是用来描述一个组件的对吗?

A4: Fiber 描述虚拟 DOM 树中的所有节点类型

Fiber 的作用范围

Fiber 不仅仅描述组件,而是描述虚拟 DOM 树中的每个节点,包括:

typescript
export type Fiber = FiberHost | FiberComp | FiberBase

export interface FiberHost extends FiberBase {
  type?: string           // HTML 元素类型,如 'div', 'span'
  props: PropsOf<string>
  isComp: false
}

export interface FiberComp extends FiberBase {
  type: FC               // 函数组件
  props: PropsOf<FC>
  isComp: true
}

Fiber 节点的分类

  1. 组件节点 (FiberComp):函数组件、类组件等
  2. HTML 元素节点 (FiberHost):div、span、button 等
  3. 文本节点 (TextFiber):纯文本内容
  4. Fragment 节点:文档片段

具体示例的 Fiber 树结构

javascript
function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="container">
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

对应的 fiber 树:

RootFiber (根节点)
└── App (FiberComp, isComp: true, type: App函数)
    └── div (FiberHost, isComp: false, type: 'div')
        ├── h1 (FiberHost, isComp: false, type: 'h1')
        │   └── "Count: 0" (TextFiber)
        └── button (FiberHost, isComp: false, type: 'button')
            └── "Click me" (TextFiber)

不同类型 Fiber 的处理方式

组件 Fiber (FiberComp)

  • 执行组件函数获取 JSX
  • 管理 hooks 状态
  • 处理副作用

HTML 元素 Fiber (FiberHost)

  • 创建真实 DOM 元素
  • 设置属性、样式、事件
  • 直接对应页面元素

只有组件 fiber 才会有 hooks

typescript
interface FiberBase {
  hooks?: Hooks    // 只有组件 fiber 才使用 hooks
  // ...
}

Q5: 所以其实可以理解为在处理一个 jsx 组件的时候,会执行组件函数的逻辑,把 hooks 函数执行的逻辑挂到组件类型的 fiber 的 hooks 上,然后在更新的时候因为有值了,就会直接拿这个新的值,对于副作用会在 bubble 阶段执行,那 host dom 节点类型的 fiber 是用来直接渲染 dom 吗?

A5: 完全正确!让我补充 Host DOM 节点的渲染机制

组件 Fiber 的处理流程

typescript
const updateHook = (fiber: Fiber) => {
  resetCursor()                                    // 重置 hook 索引
  resetFiber(fiber)                               // 设置当前 fiber 上下文
  fiber.node = fiber.node || fragment(fiber)      // 组件创建文档片段
  try {
    let children = (fiber.type as FC)(fiber.props) // 执行组件函数,hooks 在此时挂载
    reconcileChildren(fiber, simpleVnode(children)) // 处理返回的 JSX
  } catch (e) {
    // 错误处理
  }
}

当执行 (fiber.type as FC)(fiber.props) 时:

  • 组件函数内的所有 hooks 调用会将状态存储到 fiber.hooks.list
  • 副作用会被收集到 fiber.hooks.effectfiber.hooks.layout

副作用在 Bubble 阶段执行

typescript
const bubble = (fiber: Fiber) => {
  if (fiber.isComp) {
    if (fiber.hooks) {
      side(fiber.hooks.layout)                           // 同步执行 layout effects
      schedule(() => side(fiber.hooks.effect) as undefined) // 异步执行 effects
    }
  }
}

Host Fiber 直接对应 DOM 元素

1. 创建真实 DOM 元素

typescript
export const createElement = (fiber: FiberHost) => {
  const dom =
    fiber.type === '#text'
      ? document.createTextNode('')                    // 文本节点
      : fiber.lane & TAG.SVG
        ? document.createElementNS('http://www.w3.org/2000/svg', fiber.type) // SVG 元素
        : document.createElement(fiber.type)           // 普通 HTML 元素
  updateElement(dom, {}, fiber.props)                  // 设置属性和事件
  return dom
}

2. 在 Commit 阶段实际操作 DOM

typescript
export const commit = (fiber?: FiberFinish) => {
  // ...
  let { op, ref, cur } = fiber.action || {}
  let parent = fiber?.parent?.node
  
  if (op & TAG.INSERT || op & TAG.MOVE) {
    // 插入或移动 DOM 节点
    parent.insertBefore(cur?.node, ref?.node)
  }
  if (op & TAG.UPDATE) {
    // 更新 DOM 属性
    const node = fiber.node
    updateElement(
      node,
      (fiber.alternate as FiberHost)?.props || {},
      (fiber as FiberHost).props
    )
  }
  // ...
}

3. DOM 属性和事件的处理

typescript
export const updateElement = (dom: HTMLElementEx, aProps: PropsOf<string>, bProps: PropsOf<string>) => {
  jointIter(aProps, bProps, (name, a, b) => {
    if (name === 'style' && !isStr(b)) {
      // 处理样式对象
    } else if (name[0] === 'o' && name[1] === 'n') {
      // 处理事件监听器
      name = name.slice(2).toLowerCase()
      if (a) dom.removeEventListener(name, a)
      dom.addEventListener(name, b)
    } else if (name in dom && !(dom instanceof SVGElement)) {
      // 设置 DOM 属性
      dom[name] = b || ''
    } else {
      // 设置 HTML 属性
      dom.setAttribute?.(name, b)
    }
  })
}

Q6: Fre 框架是如何实现时间切片(Time Slicing)和可中断渲染的?

A6: 基于 shouldYield 的协作式调度机制

可中断的 reconcile 循环

typescript
const reconcile = (fiber?: Fiber) => {
  while (fiber && !shouldYield()) {  // 关键:检查是否需要让出控制权
    fiber = capture(fiber) as any
  }
  return fiber ? reconcile.bind(null, fiber) : null  // 返回继续函数,实现可恢复
}

调度机制

typescript
export const update = (fiber?: Fiber) => {
  if (!fiber.dirty) {
    fiber.dirty = true
    schedule(() => reconcile(fiber))  // 将 reconcile 任务加入调度队列
  }
}

核心思想

  • 每处理一个 fiber 节点后检查 shouldYield()
  • 如果需要让出控制权,返回继续函数
  • 通过 schedule 将剩余工作重新加入队列
  • 实现了可中断、可恢复的渲染过程

Q7: Fre 是如何处理组件的 memo 优化的?具体的 shouldUpdate 逻辑是什么?

A7: 基于 Props 浅比较的 Memo 机制

Memo 检查逻辑

typescript
export const isMemo = (fiber: Fiber) => {
  if (
    (fiber.type as FC).memo &&                    // 组件标记为 memo
    fiber.type === fiber.alternate?.type &&       // 组件类型相同
    fiber.alternate?.props                         // 存在旧的 props
  ) {
    let scu = (fiber.type as FC).shouldUpdate || shouldUpdate
    if (!scu(fiber.props, fiber.alternate.props)) {  // props 没有变化
      return true  // 跳过重新渲染
    }
  }
  return false
}

默认的 shouldUpdate 实现

typescript
const shouldUpdate = (
  a: Record<string, unknown>,
  b: Record<string, unknown>
) => {
  for (let i in a) if (!(i in b)) return true      // 旧 props 中有新 props 没有的属性
  for (let i in b) if (a[i] !== b[i]) return true  // 任何属性值发生变化
  return false  // 所有属性都相同
}

Memo 组件的处理流程

typescript
const capture = (fiber: Fiber) => {
  fiber.isComp = isFn(fiber.type)

  if (fiber.isComp) {
    if (isMemo(fiber)) {
      fiber.memo = false
      return sibling(fiber)  // 跳过当前组件,直接处理兄弟节点
    }
    // 正常处理组件...
  }
}

优化效果

  • 避免不必要的组件函数执行
  • 减少 hooks 重新计算
  • 跳过子树的 reconcile 过程

Q8: Fre 的错误边界(Error Boundary)是如何实现的?

A8: 基于 try-catch 和 fiber 树向上查找的错误处理机制

错误捕获和处理

typescript
const updateHook = (fiber: Fiber) => {
  resetCursor()
  resetFiber(fiber)
  fiber.node = fiber.node || fragment(fiber)
  try {
    let children = (fiber.type as FC)(fiber.props)
    reconcileChildren(fiber, simpleVnode(children))
  } catch (e) {
    if (e instanceof Promise) {
      return suspenseRender(fiber, e).sibling  // Suspense 处理
    } else {
      return errorBoundaryRender(fiber, e).child  // 错误边界处理
    }
  }
}

错误边界查找和渲染

typescript
const errorBoundaryRender = (fiber, error) => {
  const boundary = getBoundary(fiber, ErrorBoundary)  // 向上查找错误边界
  if (!boundary) throw error  // 没有找到边界,继续抛出错误
  
  const formatError = error instanceof Error ? error : new Error(error)
  reconcileChildren(
    boundary, 
    isFn(boundary.props.fallback) 
      ? boundary.props.fallback({ error: formatError })  // 执行 fallback 函数
      : simpleVnode(boundary.props.fallback)             // 直接渲染 fallback
  )
  return boundary
}

export const getBoundary = (fiber, name) => {
  let current = fiber.parent
  while (current) {
    if (current.type === name) return current  // 找到对应类型的边界组件
    current = current.parent
  }
  return null
}

错误处理流程

  1. 组件执行时捕获错误
  2. 沿 fiber 树向上查找 ErrorBoundary 组件
  3. 找到后渲染 fallback UI
  4. 没找到则继续向上抛出错误

Q9: Fre 的 Suspense 机制是如何实现异步组件加载的?

A9: 基于 Promise 和 WeakMap 的 Suspense 实现

Promise 检测和 Suspense 处理

typescript
const updateHook = (fiber: Fiber) => {
  // ...
  try {
    let children = (fiber.type as FC)(fiber.props)
    reconcileChildren(fiber, simpleVnode(children))
  } catch (e) {
    if (e instanceof Promise) {
      return suspenseRender(fiber, e).sibling  // 检测到 Promise,进入 Suspense 处理
    }
    // ...
  }
}

Suspense 渲染逻辑

typescript
const suspenseRender = (fiber, promise) => {
  const boundary = getBoundary(fiber, Suspense)  // 查找 Suspense 边界
  if (!boundary) throw promise  // 没有边界,继续向上抛出
  
  const primaryChildren = boundary.props.children
  const primaryChildFragment = {
    type: null,
    props: { children: primaryChildren },
    mode: MODE.OFFSCREEN,  // 主要内容设为离屏模式
    kids: [],
  }
  const fallbackFragment = simpleVnode(boundary.props.fallback)
  fallbackFragment.key = SUSPENSE_FALLBACK_KEY
  
  boundary.kids = []
  reconcileChildren(boundary, [primaryChildFragment, fallbackFragment])
  
  // Promise 状态管理
  let pSet = suspendPromiseMap.get(promise)
  if(!pSet) {
    suspendPromiseMap.set(promise, new Set([boundary]))
    promise.then(() => {
      const s = suspendPromiseMap.get(promise);
      ([...s]).filter(b => !(b.flag && (b.flag & TAG.REPLACE || b.flag & TAG.REMOVE)))
             .forEach(b => update(b))  // Promise 完成后重新渲染
    }).finally(() => suspendPromiseMap.delete(promise))
  } else pSet.add(boundary)
  
  return boundary.child
}

离屏模式处理

typescript
export const commit = (fiber?: FiberFinish) => {
  if (!fiber) return
  if(fiber.mode & MODE.OFFSCREEN) return commitSibling(fiber.sibling)  // 跳过离屏内容
  // 正常提交逻辑...
}

Suspense 工作流程

  1. 组件抛出 Promise
  2. 查找最近的 Suspense 边界
  3. 将主要内容设为离屏模式,渲染 fallback
  4. Promise 完成后重新渲染,显示主要内容

Q10: Fre 的 diff 算法具体是如何实现的?与 React 的 diff 有什么异同?

A10: 基于双端比较的高效 Diff 算法

Diff 算法主体结构

typescript
const diff = (aCh: Fiber[], bCh: Fiber[]) => {
  let aHead = 0, bHead = 0, aTail = aCh.length - 1, bTail = bCh.length - 1
  let bMap = {}, temp = [], actions = []

  // 第一步:从尾部开始比较
  while (aHead <= aTail && bHead <= bTail) {
    if (!same(aCh[aTail], bCh[bTail])) break
    clone(aCh[aTail], bCh[bTail])
    temp.push({ op: TAG.UPDATE })
    aTail--
    bTail--
  }

  // 第二步:从头部开始比较
  while (aHead <= aTail && bHead <= bTail) {
    if (!same(aCh[aHead], bCh[bHead])) break
    clone(aCh[aHead], bCh[bHead])
    actions.push({ op: TAG.UPDATE })
    aHead++
    bHead++
  }

  // 第三步:为剩余新节点建立 key 映射
  for (let i = bHead; i <= bTail; i++) {
    if (bCh[i].key) bMap[bCh[i].key] = i
  }

  // 第四步:处理中间的复杂情况
  while (aHead <= aTail || bHead <= bTail) {
    const aElm = aCh[aHead], bElm = bCh[bHead]
    
    if (aElm === null) {
      aHead++
    } else if (bTail + 1 <= bHead) {
      removeElement(aElm)  // 删除多余的旧节点
      aHead++
    } else if (aTail + 1 <= aHead) {
      actions.push({ op: TAG.INSERT, cur: bElm, ref: aElm })  // 插入新节点
      bHead++
    } else if (same(aElm, bElm)) {
      clone(aElm, bElm)
      actions.push({ op: TAG.UPDATE })
      aHead++
      bHead++
    } else {
      const foundB = aElm.key ? bMap[aElm.key] : null
      
      if (foundB == null) {
        removeElement(aElm)  // 旧节点在新列表中不存在
        aHead++
      } else {
        if (bHead <= foundB) {
          actions.push({ op: TAG.INSERT, cur: bElm, ref: aElm })
          bHead++
        } else {
          clone(aElm, bCh[foundB])
          actions.push({ op: TAG.MOVE, cur: aElm, ref: aCh[aHead] })  // 移动节点
          aCh[aHead] = null
          aHead++
        }
      }
    }
  }

  // 第五步:合并操作序列
  for (let i = temp.length - 1; i >= 0; i--) {
    actions.push(temp[i])
  }
  return actions
}

节点比较和克隆

typescript
const same = (a: Fiber, b: Fiber) => a && b && (a.type === b.type && a.key === b.key)

function clone(a: Fiber, b: Fiber) {
  b.hooks = a.hooks    // 复制 hooks 状态
  b.ref = a.ref        // 复制 ref
  b.node = a.node      // 复用 DOM 节点
  b.kids = a.kids      // 复制子节点
  a.flag = TAG.REPLACE
  b.alternate = a      // 建立新旧 fiber 关联
}

与 React Diff 的异同

相同点

  • 都基于 key 和 type 进行节点比较
  • 都采用启发式算法,时间复杂度 O(n)
  • 都支持节点的增删改移操作

不同点

  • Fre 采用双端比较,React 16+ 采用单向遍历
  • Fre 的实现更简洁,代码量更少
  • React 有更复杂的优先级调度机制

Q11: Fre 中的 useEffect 和 useLayoutEffect 有什么区别?执行时机是怎样的?

A11: 基于调度机制的同步/异步副作用执行

副作用的收集

typescript
export const useEffect = (cb: EffectCallback, deps?: DependencyList) => {
  return effectImpl(cb, deps!, 'effect')
}

export const useLayout = (cb: EffectCallback, deps?: DependencyList) => {
  return effectImpl(cb, deps!, 'layout')
}

const effectImpl = (
  cb: EffectCallback,
  deps: DependencyList,
  key: 'effect' | 'layout'
) => {
  const [hook, current] = getSlot<HookEffect>(cursor++)
  if (isChanged(hook[1], deps)) {
    hook[0] = cb
    hook[1] = deps
    current.hooks[key].push(hook as Required<HookEffect>)  // 分别收集到不同队列
  }
}

执行时机的差异

typescript
const bubble = (fiber: Fiber) => {
  if (fiber.isComp) {
    if (fiber.hooks) {
      side(fiber.hooks.layout)                           // 同步执行 layout effects
      schedule(() => side(fiber.hooks.effect) as undefined) // 异步执行 effects
    }
  }
}

const side = (effects?: HookEffect[]) => {
  effects.forEach((e) => e[2] && e[2]())  // 先执行清理函数
  effects.forEach((e) => (e[2] = e[0]()))  // 再执行副作用函数
  effects.length = 0                       // 清空队列
}

执行时机对比

Hook执行时机是否阻塞渲染使用场景
useLayoutEffectDOM 更新后同步执行DOM 测量、同步 DOM 操作
useEffectDOM 更新后异步执行数据获取、订阅、手动 DOM 操作

Q12: Fre 是如何实现 Context 机制的?useContext 的查找逻辑是什么?

A12: 基于 Fiber 树向上查找的 Context 实现

Context 创建

typescript
export const createContext = <T>(value: T): ContextType<T> => {
  const C = ({ value, children }: Parameters<ContextType<T>>[0]) => {
    const ref = useRef(value)
    const subs = useMemo(() => new Set<() => void>(), EMPTY_ARR)  // 订阅者集合
    
    if (ref.current !== value) {
      ref.current = value
      subs.forEach(cb => cb())  // 值变化时通知所有订阅者
    }
    return children
  }
  C.initialValue = value
  return C as ContextType<T>
}

Context 消费

typescript
export const useContext = <T>(ctx: ContextType<T>) => {
  const update = useReducer(null, null)[1] as SubscriberCb  // 获取更新函数
  let subs: Set<SubscriberCb>

  useEffect(() => () => subs?.delete(update), EMPTY_ARR)  // 组件卸载时取消订阅

  const fiber = getBoundary(useFiber(), ctx)  // 向上查找 Context Provider
  return fiber
    ? (subs = (fiber.hooks.list[1][0]).add(update),      // 订阅更新
      (fiber.hooks.list[0][0] as { current: T }).current) // 返回当前值
    : ctx.initialValue  // 没找到 Provider,返回默认值
}

Context 查找机制

typescript
export const getBoundary = (fiber, name) => {
  let current = fiber.parent
  while (current) {
    if (current.type === name) return current  // 找到匹配的 Context Provider
    current = current.parent
  }
  return null  // 没找到,返回 null
}

Context 工作流程

  1. Provider 组件存储值和订阅者集合
  2. Consumer 通过 getBoundary 向上查找最近的 Provider
  3. 找到后订阅更新,返回当前值
  4. Provider 值变化时通知所有订阅者重新渲染

Q13: Fre 的性能优化策略有哪些?如何避免不必要的重新渲染?

A13: 多层次的性能优化策略

1. Fiber 复用和状态保持

typescript
function clone(a: Fiber, b: Fiber) {
  b.hooks = a.hooks    // 复用 hooks 状态
  b.ref = a.ref        // 复用 ref
  b.node = a.node      // 复用 DOM 节点
  b.kids = a.kids      // 复用子节点引用
  a.flag = TAG.REPLACE
  b.alternate = a      // 建立新旧关联
}

2. 组件级别的 Memo 优化

typescript
export const isMemo = (fiber: Fiber) => {
  if (
    (fiber.type as FC).memo &&
    fiber.type === fiber.alternate?.type &&
    fiber.alternate?.props
  ) {
    let scu = (fiber.type as FC).shouldUpdate || shouldUpdate
    if (!scu(fiber.props, fiber.alternate.props)) {
      return true  // 跳过重新渲染
    }
  }
  return false
}

3. 时间切片避免长时间阻塞

typescript
const reconcile = (fiber?: Fiber) => {
  while (fiber && !shouldYield()) {  // 定期检查是否需要让出控制权
    fiber = capture(fiber) as any
  }
  return fiber ? reconcile.bind(null, fiber) : null
}

4. 精确的依赖比较

typescript
export const isChanged = (a: DependencyList | undefined, b: DependencyList) => {
  return (
    !a ||
    a.length !== b.length ||
    b.some((arg, index) => !Object.is(arg, a[index]))  // 使用 Object.is 精确比较
  )
}

5. 批量更新机制

typescript
export const update = (fiber?: Fiber) => {
  if (!fiber.dirty) {
    fiber.dirty = true           // 标记为脏,避免重复调度
    schedule(() => reconcile(fiber))  // 批量调度更新
  }
}

性能优化总结

  • 节点复用:最大化复用现有 fiber 和 DOM 节点
  • 智能跳过:通过 memo 和依赖比较避免不必要的计算
  • 时间切片:避免长时间阻塞主线程
  • 批量处理:合并多个更新操作
  • 精确比较:使用 Object.is 进行准确的值比较

Released under the MIT License.