想象一下你家楼下那家老字号的厨房:在 React 16.8 之前,class 组件就像一口巨大的灶台,所有食材、火候、碗碟都要堆在同一个灶面上,煮个面要写一堆生命周期代码;而函数组件呢,更像一个一次性纸碗,只能接收客人递来的口味单(props),连“刚才加了几勺盐”这种状态都记不住。React Hooks 的诞生,就是把这间老厨房改造成了模块化操作台:useState 是贴好标签的食材罐,useEffect 是自动感应水管,useRef 是墙上的记号笔,useContext 是公共调料架……每一件工具各司其职,函数组件一下子从纸碗成长为能独立掌勺的大厨。 本篇文章会沿着这条“厨房改造”的进化路线,带你逐一认识这些工具,理解它们为什么存在、怎么用、以及最常见的翻车现场。读完你不仅会写 Hooks,更会明白 React 团队当年为什么要打破 class 这口大锅。

岗位关联

无论你做 AI 应用前端、模型工具链界面还是数据可视化产品,组件状态与副作用管理都是绕不开的基建;吃透 Hooks,相当于拿到现代前端厨房的标准厨具使用证,能少写几千行面条式代码。

目录
  1. 可视化演示
  2. 1. 诞生背景:从大灶台到模块化厨房
  3. 2. useState:给灶台贴上食材罐
  4. 3. useEffect:自动感应水管
  5. 4. 性能优化:保鲜盒与预制菜
  6. 5. 引用与共享:冰箱贴与公共调料架
  7. 6. 自定义 Hook:菜谱封装
  8. 7. 规则与陷阱:厨房安全条例
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示React Hooks体系架构、useState驱动的状态更新与重渲染循环,以及useMemo/useCallback的性能优化效果对比。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Hooks体系架构 useState管理状态,useEffect处理副作用 useMemo/useCallback优化性能,useRef管理引用 St + Fx + Mo + Rf + Cx 五大核心Hook覆盖组件状态、副作用、性能、引用和跨组件通信

1. 诞生背景:从大灶台到模块化厨房

在 Hooks 出现之前,React 的 class 组件就像一间传统的大灶台:你要把数据请求、事件绑定、定时器、表单校验全部堆在同一块灶面上,不同团队写出的代码层层叠叠,最后连打开锅盖都要小心翼翼。更尴尬的是函数组件,它只是一只一次性纸碗,只能接收 props 并直接返回界面,哪怕只是想记住“我加过几勺盐”,也无能为力。

这种割裂让 React 社区发明了 render props、高阶组件、mixin 等各种补丁,但这些方案像用胶带贴漏水的水龙头,贴着贴着反而把灶台缠成了毛线团。终于,React 16.8 决定彻底改造厨房:与其逼着函数组件模仿 class,不如造一套可以自由插拔的模块化工具,让函数组件也能优雅地管理状态和副作用。这套工具,就是 Hooks。

Hooks 的进化,本质上是把“组件=纯函数”的理想真正落地:厨房还是那间厨房,但每个能力都被装进了可以自由组合的工具抽屉。接下来我们一件一件打开它们。

UI=Render(state,props)\displaystyle \text{UI} = \text{Render}(\text{state}, \text{props})
这一行是 React 的心法:无论 class 还是 Hooks,最终都是把状态和外部参数“煮”成界面;Hooks 只是让函数组件也能拿稳这两样食材。
  1. UI(端上桌的面)组件最终输出给用户的界面,也就是 DOM 树或组件树。
    UI=最终界面输出\text{UI} = \text{最终界面输出}
  2. Render(一次烹饪过程)组件函数执行的完整过程,class 版靠实例方法,Hooks 版直接调用函数。
    Render:(state,props)→UI\text{Render}: (\text{state}, \text{props}) \to \text{UI}
  3. state 与 props(食材与订单)state 是组件自己保存的可变数据,props 是父组件传入的外部参数。
    state=内部食材,props=外部订单\text{state} = \text{内部食材}, \quad \text{props} = \text{外部订单}

2. useState:给灶台贴上食材罐

模块化厨房的第一件工具,是贴好标签的食材罐。你只需要写一行 const [count, setCount] = useState(0),就把 count 放进了带标签的罐子,setCount 则是一把换料勺。以后想更新数量,就抓起换料勺操作一次,下一轮烹饪自然会用上新食材。

不过这里有一个新手最容易踩的坑:换料是异步的。你刚把 setCount(count + 1) 递出去,当前这锅面的 count 并不会立刻变成新值,而是要等这一轮 render 结束后,React 才把更新队列里的任务统一执行,然后开启新一轮 render。这个机制就像厨房里所有订单先贴到墙上,厨师不可能一边颠锅一边瞬间换掉手里的食材。

还有个重要的哲学:更新时要保持不可变。比如往数组里加一项,不要用 push 改原数组,而要 setList([...list, newItem]) 返回一个新数组。食材罐的规矩是:换料时就换一张新标签,绝对不允许在旧标签上涂改,否则 React 可能根本发现不了变化。

Snext=updater(Sprev)\displaystyle S_{\text{next}} = \text{updater}(S_{\text{prev}})
useState 的核心是“旧状态喂给 updater,产生新状态”:你是直接给新值,还是给一个基于旧值计算的函数,React 都会用结果去替换下一次渲染读取到的 count。
  1. S\_next(下一锅的食材量)下一次渲染时组件读到的状态值,也就是更新完成后展示给用户的数据。
    Snext=新状态快照S_{\text{next}} = \text{新状态快照}
  2. updater(换料勺)setCount 接收的新值或函数;如果传函数,React 会按队列顺序依次执行。
    updater:Sprev↦Snext\text{updater}: S_{\text{prev}} \mapsto S_{\text{next}}
  3. S\_prev(当前锅里的旧食材)本轮渲染时的状态快照,同一轮里多次更新都基于它计算。
    Sprev=当前渲染快照S_{\text{prev}} = \text{当前渲染快照}

3. useEffect:自动感应水管

如果说 useState 是食材罐,那么 useEffect 就是厨房里的自动感应水管。它专门处理那些“不是纯渲染”的脏活:拉取数据、订阅事件、手动改 DOM、启动定时器。你把这根水管接到组件上,React 会在页面渲染完成后自动打开水龙头,执行你传入的副作用函数。

这根水管的精妙之处在于“先关旧阀,再开新阀”。假设你订阅了某个聊天房间,房间号从 A 变成 B,那么 useEffect 会先执行上一次返回的清理函数(退订 A),再执行新的副作用(订阅 B)。当组件卸载时,它还会执行最后一次清理,相当于关掉总水阀,避免内存泄漏和无效更新。

Effectn=run(depsn),cleanupn−1→Effectn\displaystyle \text{Effect}_n = \text{run}(\text{deps}_n), \quad \text{cleanup}_{n-1} \to \text{Effect}_n
这是副作用的生命周期地图:每次依赖变化都会触发“清理旧水管 → 放新水”的循环;空依赖数组则意味着只在挂载后放一次水,卸载时关一次总阀。
  1. Effect\_n(第 n 次放水)第 n 次需要执行的副作用操作,比如请求数据、订阅事件、操作 DOM。
    Effectn=第 n 次副作用任务\text{Effect}_n = \text{第 n 次副作用任务}
  2. run(deps\_n)(按开关执行)只有当依赖数组里的值与上次不同时才执行新的副作用;没有依赖数组时每次渲染都执行。
    run:depsn→副作用\text{run}: \text{deps}_n \to \text{副作用}
  3. cleanup\_{n-1} → Effect\_n(先关旧阀)下一次副作用开始前,先执行上一次返回的清理函数;组件卸载时也会执行最后一次清理。
    cleanupn−1⇒Effectn 执行\text{cleanup}_{n-1} \Rightarrow \text{Effect}_n \text{ 执行}

4. 性能优化:保鲜盒与预制菜

当厨房越来越忙,你需要保鲜盒和预制菜来减少重复劳动。useMemo 是保鲜盒,用来缓存“昂贵的计算值”:比如一个几千条数据的过滤结果,如果依赖没有变,就没必要每次渲染都重新算一遍。useCallback 则是预制菜包,用来缓存“函数的引用”:父组件每次渲染都会生成新函数,子组件如果用 memo 做浅比较,拿到新引用就会被迫重新渲染,而缓存后的函数能帮我们拦住这种无效更新。

这两个工具本身并不神奇,神奇的是它们的依赖控制:你告诉 React 这道菜依赖哪些食材,只要食材没换,就直接端上一模一样的盒饭,省去重新下锅的时间。

m={calc(deps),deps 变化old,deps 未变\displaystyle m = \begin{cases} \text{calc}(\text{deps}), & \text{deps 变化} \\ \text{old}, & \text{deps 未变} \end{cases}
useMemo 和 useCallback 的核心都是“依赖不变,结果就复用”。前者缓存值,后者缓存函数,但判断逻辑完全一致。
  1. m(保鲜盒里的成品菜)useMemo 返回的缓存结果,可能是一个数组、对象或字符串。
    m=缓存的计算结果m = \text{缓存的计算结果}
  2. calc(deps)(重新炒菜)只有当依赖项变化时才重新执行昂贵计算。
    calc:deps→m\text{calc}: \text{deps} \to m
  3. old(昨天留下的盒饭)依赖不变时直接复用上一次的缓存,避免重复计算和多余渲染。
    old=上一次缓存值\text{old} = \text{上一次缓存值}

5. 引用与共享:冰箱贴与公共调料架

厨房里还有两种特殊的“读工具”:useRef 和 useContext。useRef 像冰箱贴,你可以随时在 .current 上写一句话、改一个定时器 ID,但改它不会触发组件重新渲染。它最适合保存那些“变了也不想让 UI 重画”的临时值,比如 setInterval 的句柄、上一次的输入值。

useContext 则是公共调料架。如果每个楼层之间都要靠服务员一层层传调料,那会很累;直接在公共调料架上放一瓶共享酱料,任何层级的组件伸手就能取。在 React 里,最上面的 <Context.Provider value={...}> 负责“上架”,子孙组件用 useContext(MyContext) 直接“取货”。

read={ref.current,useRefprovider.value,useContext\displaystyle \text{read} = \begin{cases} \text{ref.current}, & \text{useRef} \\ \text{provider.value}, & \text{useContext} \end{cases}
这里的 read 是组件取到的东西:useRef 读自己的冰箱贴,useContext 读公共调料架;区别在于前者不触发渲染,后者会在 Provider 值变化时自动通知所有消费者重新渲染。
  1. read(你取到的东西)组件最终拿到的数据,可能是 DOM 节点、定时器 ID,也可能是主题色或用户信息。
    read=本次读到的值\text{read} = \text{本次读到的值}
  2. ref.current(墙上的冰箱贴)useRef 返回对象上的可变引用,改它不会通知 React 重画页面。
    ref.current=可变数据位\text{ref.current} = \text{可变数据位}
  3. provider.value(公共调料架上的当前调料)Provider 上绑定的值,它更新时所有消费者组件都会收到通知并重新渲染。
    provider.value=共享上下文值\text{provider.value} = \text{共享上下文值}

6. 自定义 Hook:菜谱封装

当你发现“取数据”这个动作在十个页面里重复出现,正确的做法不是继续复制粘贴,而是把它写成一张菜谱——也就是自定义 Hook。自定义 Hook 本质上还是一个函数,但它内部调用了 useState、useEffect 等官方 Hook,对外暴露统一入口,比如 useChatMessages()、useLocalStorage(key)。

自定义 Hook 的命名必须以 use 开头,这既是社区约定,也是让 ESLint 插件能自动检查 Hook 规则的关键。它就像把食材、火候、调料全部写进一张卡片:主厨只要按菜谱操作,不必关心内部细节。更棒的是,不同 Hook 可以互相组合,一个 useUser 可以内部调用 useState 和 useContext,像乐高一样拼出复杂逻辑。

useCustom(x)=combine(useState,useEffect,… )\displaystyle \text{useCustom}(x) = \text{combine}(\text{useState}, \text{useEffect}, \dots)
自定义 Hook 的公式可以说:入参是菜谱需要的原料,函数体是烹饪步骤,返回值是成品菜。
  1. useCustom(x)(菜谱的封面与原料)自定义 Hook 的名称和参数,通常暴露一个或多个状态与操作方法。
    useCustom(x)=高层封装\text{useCustom}(x) = \text{高层封装}
  2. combine(……)(菜谱内部步骤)自由组合官方 Hooks,如 useState、useEffect、useMemo,它们共同完成一段完整逻辑。
    combine=useState⋅useEffect⋅…\text{combine} = \text{useState} \cdot \text{useEffect} \cdot \dots
  3. 返回值(端出来的成品)通常返回 [value, action] 数组或对象,让调用方按需取用。
    return=(v,a)\text{return} = (v, a)

7. 规则与陷阱:厨房安全条例

厨房越模块化,安全条例越重要。Hooks 的底层实现依赖“每次渲染时按完全相同的顺序调用”这一假设:React 会在组件上挂一连串 Hook 节点,用数组下标记录每个 Hook 的状态。如果你把 useState 放进 if 或提前 return,下标就对不上,状态就会错乱,这也是为什么 Hooks 必须在顶层调用。

另一个高频陷阱是闭包旧值:effect 或回调函数里读到的变量,可能停留在某次渲染的快照里,而不是“最新值”。解决办法很简单——把用到的一切外部变量都写进依赖数组,或者用 useRef 保存一个始终最新的引用。依赖数组不是摆设,它是水管和保鲜盒的“保鲜期标签”。

orderimount=orderievery render,i=1,…,n\displaystyle \text{order}_i^{\text{mount}} = \text{order}_i^{\text{every render}}, \quad i = 1, \dots, n
这条公式说明:每个 Hook 在首次挂载和后续每一次渲染中的调用顺序必须保持一致;一旦被条件语句破坏,React 就无法把状态和对应的 Hook 对上号。
  1. order\_i^{mount}(首次挂载的位置)第 i 个 Hook 第一次挂载时在组件里的调用顺序。
    orderimount=第 i 个挂载下标\text{order}_i^{\text{mount}} = \text{第 i 个挂载下标}
  2. order\_i^{every render}(每次渲染的位置)第 i 个 Hook 在后续渲染中的调用顺序,必须和挂载时一致。
    orderievery render=第 i 个渲染下标\text{order}_i^{\text{every render}} = \text{第 i 个渲染下标}
  3. i = 1, ……, n(全部 Hook 的编号)一个组件里所有 Hook 的集合,数量一旦发生跳变,等式立刻失效。
    n=Hook 总数n = \text{Hook 总数}

AI 应用场景

大模型对话页的流式消息管理

阶段:前端交互层

解决的问题:AI 对话产品需要接收流式 token,传统方式是用 class 组件把连接、累积消息、停止生成逻辑散落在多个生命周期函数里,极难维护。用 useState 累积消息内容,用 useEffect 管理 WebSocket 或 SSE 订阅,用 useRef 保存中止控制器,再封装成 useChatStream,页面代码会非常干净。

具体案例:在 ChatGPT 风格页面中,用户点击发送后,前端建立 SSE 连接;每收到一个 token,调用 setMessages(prev => [...prev, token]) 追加内容;组件卸载或用户停止生成时,清理函数负责断开连接。整个逻辑统一收敛到自定义 Hook 中,任何一个新页面都能直接复用。

AI 训练任务实时看板

阶段:数据可视化与监控

解决的问题:训练过程中的 loss、acc 每秒钟刷新一次,如果直接用 setState 更新并重新过滤上千条指标曲线,页面会频繁重渲染甚至卡顿。用 useEffect 开启轮询,用 useMemo 聚合/降采样指标数据,用 useRef 保存轮询定时器,是标准解法。

具体案例:一个分布式训练监控面板,前端每 5 秒拉取一次指标快照,useEffect 负责更新最新数据,useMemo 只对需要的窗口区间做平滑计算;切到其他页面时,定时器随组件卸载自动清除。

多租户主题与模型配置共享

阶段:全局状态与用户偏好

解决的问题:同一个 AI 平台可能有多个大模型供应商、不同主题色、不同语言环境。用 useContext 包装全局配置,任何层级的组件都能直接读取,不需要从顶层一路 props 透传。

具体案例:管理后台的“设置中心”修改主题后,顶栏 Provider 的值更新,所有消费 useContext(ThemeContext) 的页面自动切换配色;修改模型服务地址后,所有请求组件立即拿到新的 endpoint。

Python 演示

python54 行
from typing import Any, Callable, List, Tuple  class MiniHooks:    def __init__(self, render_fn):        self.render_fn = render_fn        self.hook_slots: List[Any] = []        self.effect_queue: List[Tuple[Callable[[], None], Any]] = []        self.cursor = 0     def useState(self, initial):        idx = self.cursor        self.cursor += 1        if len(self.hook_slots) == idx:            self.hook_slots.append(initial)        value = self.hook_slots[idx]         def set_value(next_value):            if callable(next_value):                self.hook_slots[idx] = next_value(self.hook_slots[idx])            else:                self.hook_slots[idx] = next_value            self.render()         return value, set_value     def useEffect(self, fn, deps=None):        self.effect_queue.append((fn, deps))     def render(self):        self.cursor = 0        self.effect_queue = []        print("---- render ----")        ui = self.render_fn()        print("UI:", ui)        for fn, deps in self.effect_queue:            fn()  state = {}  def App():    count, set_count = app.useState(0)    label = "count=" + str(count)    app.useEffect(lambda: print("effect after render:", label), [count])    state["set_count"] = set_count    return label  app = MiniHooks(App)app.render()state["set_count"](2)state["set_count"](lambda v: v + 1)
目的:用一个小型 Python 运行时模拟 React Hooks 的“渲染 → 状态更新 → 副作用”流程,帮助你从底层理解 useState 的排队更新与 useEffect 的执行时机。
  1. self.hook_slots: List[Any] = []用列表保存每个 Hook 节点的状态,相当于 React 内部的状态数组。
  2. idx = self.cursor每次调用 useState 都取当前光标位置,保证多次渲染时的调用顺序一致。
  3. if len(self.hook_slots) == idx:首次挂载时才初始化状态,后续渲染直接读取已保存的值。
  4. def set_value(next_value):模拟 setCount,负责修改 Hook 槽位并触发下一次渲染。
  5. if callable(next_value):支持函数式更新,如 setCount(v => v + 1)。
  6. app.useEffect(lambda: print(...), [count])模拟副作用注册,渲染结束后再统一执行。
  7. state["set_count"] = set_count把更新函数暴露到外部,模拟按钮点击等事件触发状态更新。

常见误区

误区我总是把依赖数组写成 `[]`,让 effect 只执行一次,这样性能肯定最好。

正解空依赖数组意味着 effect 里捕获到的所有外部变量都永远停留在首次渲染,之后的数据变化它根本看不见。这就像把水管焊死,只放一次水,后面厨房再需要水也毫无反应。依赖数组应当完整列出 effect 中使用的外部变量,必要时可以把一个 effect 拆成多个。

为何常见:很多教程用 [] 控制请求只发一次,新手就误以为“依赖越少越好”,忽略了闭包过期问题。

误区给每个函数和每个计算都包一层缓存,页面一定会更快。

正解缓存本身需要存储和比较依赖,等于给每道菜都套一层保鲜膜;如果计算非常简单,或者依赖频繁变化,缓存反而成为负担。useMemo 和 useCallback 应该只用在“计算昂贵”或“函数参与子组件重渲染优化”的场景。

为何常见:看到“缓存”二字就联想到性能提升,忽略了 React 内部每次渲染都要做依赖比较的隐形成本。

误区我把 `ref.current` 改了,为什么页面上的数字没变?

正解useRef 就是冰箱贴,写上去完全不会通知 React 重画 UI。它适合保存定时器、DOM 引用、上一次的值等“不需要驱动视图”的数据;真想触发渲染,必须搭配 useState 或强制更新。

为何常见:因为 useRef 和 useState 都返回“可以持有数据的容器”,初学者很容易把两者混为一谈。

误区`use` 开头只是一个约定,我把函数写成 `fetchData` 也一样能跑。

正解运行时确实不会因为名字报错,但 React 官方的 ESLint 插件必须靠 use 前缀来识别哪些函数是自定义 Hook,进而检查 useState、useEffect 的调用位置是否合法。一旦名字不符合规范,规则检查就会失效,很多隐藏问题会漏掉。

为何常见:因为“能跑”不等于“正确”,新手往往只关心运行结果,忽略了工具链的静态检查能力。

误区封装一个普通工具函数,在里面调用 `useState` 也可以在组件外使用。

正解Hooks 只能从函数组件或自定义 Hook 的顶层调用。普通函数没有 React 渲染流程中的“挂载时机”,也没有稳定的状态槽位,调用 useState 会直接报错。这就像在厨房里装一根没有接进总水管的开关,按钮再漂亮也出不了水。

为何常见:自定义 Hook 本质也是函数,容易让人误以为“函数里就能用 Hook”,忽略了它必须参与组件渲染链路这个前提。

面试题

Q1. React Hooks 正式随哪个版本发布?

单选题 · easy

  • A.React 15.0
  • B.React 16.8← 正确答案
  • C.React 17.0
  • D.React 18.0

答案:B。React 16.8 是 Hooks 的正式发布版本,它让函数组件拥有了状态和副作用能力。选项 B 正确;A、C、D 都不是 Hooks 引入的版本。

Q2. useState 的返回值结构是?

单选题 · easy

  • A.当前状态与更新函数← 正确答案
  • B.初始状态与默认值
  • C.组件状态与外部属性
  • D.渲染结果与虚拟节点

答案:A。useState 返回一个数组,第一项是当前状态,第二项是更新状态的函数,就像食材罐和换料勺。B、C、D 描述的都是其他概念,不是 useState 的返回值。

Q3. 调用 setCount(count + 1) 后,count 会在什么时候变化?

单选题 · mid

  • A.调用后立刻同步变化
  • B.在下次渲染时生效← 正确答案
  • C.在组件卸载时生效
  • D.在事件循环结束时生效

答案:B。setState 的更新是异步排队的,当前渲染里的 count 不会立刻变,要等 React 完成更新调度并触发下一次渲染后,组件才能读到新值。B 最准确;A 是常见误解,D 描述的是批处理现象而不是状态生效时机。

Q4. 以下哪些 Hooks 常用于性能优化?

多选题 · mid

  • A.useMemo 缓存结果← 正确答案
  • B.useCallback 缓存函数← 正确答案
  • C.useReducer 转换状态
  • D.useContext 共享数据

答案:AB。useMemo 缓存计算结果,useCallback 缓存函数引用,两者都用来减少重复计算和子组件重渲染。useReducer 是状态管理工具,useContext 是共享数据工具,它们的主要目的不是性能优化。

Q5. useEffect(fn, []) 中的空依赖数组表示什么?

单选题 · mid

  • A.每次渲染后都执行
  • B.仅挂载后执行一次← 正确答案
  • C.仅卸载时执行一次
  • D.依赖为 undefined 时执行

答案:B。空依赖数组告诉 React“没有任何外部食材需要监控”,因此 effect 只在组件首次挂载后执行一次,相当于只放一次水。A 是没有依赖数组时的行为,C 只描述了清理阶段,D 不存在这种语义。

Q6. 修改 useRef 返回对象的 .current 值会怎样?

单选题 · hard

  • A.立即触发组件重新渲染
  • B.不触发重新渲染← 正确答案
  • C.触发父子组件重新渲染
  • D.使 effect 重新执行

答案:B。useRef 是可变引用容器,修改 .current 不会进入 React 的更新流程,因此不会触发重渲染,也不会自动让 effect 重新执行。它适合保存那些不需要驱动视图的临时数据。

Q7. 以下哪些属于 React Hooks 的强制规则?

多选题 · hard

  • A.只能在函数组件顶层调用← 正确答案
  • B.只能从组件或自定义 Hook 调用← 正确答案
  • C.允许在条件语句中调用
  • D.必须配合 class 组件使用

答案:AB。Hooks 必须在渲染顶层调用,且只能从函数组件或自定义 Hook 中调用,否则 React 无法保证调用顺序和状态槽位。C 会破坏 Hook 顺序,D 与 Hooks 设计目的完全相反。

Q8. 自定义 Hook 必须以 use 开头的主要原因是什么?

单选题 · hard

  • A.运行时强制校验名称
  • B.便于 ESLint 插件识别← 正确答案
  • C.能自动获得缓存能力
  • D.必须配合 useMemo 使用

答案:B。use 前缀不是运行时硬性要求,而是为了帮助 ESLint 插件识别自定义 Hook,从而检查 Hooks 的使用规则。它不是性能开关,也不强制依赖 useMemo。

Q9. useContext 主要解决的问题是?

单选题 · easy

  • A.跨层级组件共享数据← 正确答案
  • B.替换所有 useState
  • C.提高组件渲染速度
  • D.绑定原生 DOM 事件

答案:A。useContext 让组件直接读取最近的 Provider 提供的值,避免逐层传递 props,是典型的“公共调料架”方案。它不负责状态存储、性能优化或事件绑定。

Q10. 关于 useEffect 清理函数,下列说法正确的是?

多选题 · hard

  • A.下次 effect 执行前触发← 正确答案
  • B.组件卸载时触发← 正确答案
  • C.首次渲染后必定触发
  • D.依赖未变时不触发← 正确答案

答案:ABD。清理函数会在下一个 effect 执行前以及组件卸载时执行;如果依赖没有变化,上一次的清理函数就不会运行。首次渲染后不会有“上一次 effect”需要清理,所以 C 错误。