想象你经营着两家餐厅。第一家是米其林星级西餐厅:客人点菜后,服务员把订单从传菜口递进后厨,只有主厨能根据菜谱规则更新食材库存,任何人不能直接动冰箱。第二家是街角的现代咖啡馆:咖啡师既是服务生又是库存管理员,客人说要加一份浓缩,他直接往库存本上记一笔,系统自动把新数字同步给所有店员。前者就是 Redux 的运作方式——用严格的单向流程换取每一步都可追踪;后者是 Pinia 的运作方式——用直观灵活的 API 换取开发效率。两条路都能把餐厅管得井井有条,但适合的团队和场景截然不同。这篇文章就沿着“后厨与库存”这条线,带你彻底看懂 Redux 与 Pinia 的设计哲学、异步方案、调试工具、TypeScript 支持和迁移路径,帮你做出清醒的技术选型。

岗位关联

状态管理是前端从“能用”走向“好用”的分水岭。学透 Redux 与 Pinia 的取舍逻辑,你才能在面试与架构设计中,把“选型”讲成一道有依据的推理题,而不是一句跟风的口头禅。

目录
  1. 可视化演示
  2. 核心概念对比
  3. Redux设计哲学
  4. Pinia设计哲学
  5. 异步中间件
  6. 开发调试工具
  7. TypeScript支持
  8. 技术选型与迁移
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示两种主流状态库的核心差异:Redux单向数据流与reducer纯函数、Pinia组合式API直接修改,以及DevTools时间旅行调试。

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

Redux单向数据流 action→reducer→state单向流转,纯函数保证可预测 中间件处理异步逻辑(thunk/saga) state = reducer(state, action)

核心概念对比

两家餐厅都要回答同一个问题:今天各个灶台还剩多少食材?这就是“共享状态”。西餐厅的规定是:所有变动必须走完“客人→服务员→传菜口→主厨→库存本”这条固定路线;咖啡馆则允许咖啡师直接改自己的小库存,但每次改动都会被自动记录并同步到所有看板。

前端应用同理:登录状态、购物车、主题色被很多组件共享,如果各改各的,页面就会互相矛盾。Redux 和 Pinia 都提供“单一数据源”的仓库(store),差别在于状态变更的路径长短。Redux 是严格的单向数据流,Pinia 是“响应式 + 直接修改”。

Redux 中,组件通过 dispatch 发出 action,action 经过 reducer 计算出新的 state,新 state 通知视图更新。Pinia 中,组件读取 state 即建立响应式依赖,写 store.count++ 就能触发更新,也可以用 action 封装复杂逻辑。两者都解决了多组件共享与状态可预测的问题,只是 Redux 用“路径约束”换确定,Pinia 用“自由”换效率。

所以,点一份“加蛋”的订单,西餐厅主厨不会在原来库存单上划掉重写,而是照着规则重抄一份新单据。这条看似繁琐的路线,正是 Redux 可预测性的来源;而咖啡馆的直接改动,则是 Pinia 高效开发的秘密。

Statenew=Reducer(Stateold,Action)\displaystyle State_{new} = Reducer(State_{old}, Action)
这句话是状态管理的“总菜谱”:新库存 = 主厨规则 ×(旧库存 + 点菜单)。
  1. State_old(当前库存)应用此刻的全局状态快照,比如购物车里的三件商品。
    StateoldState_{old}
  2. Action(点菜单)描述“发生了什么”的普通对象,如购物车加购动作。
    Action={type,  payload}Action = \{type,\; payload\}
  3. Reducer(后厨规则)一个纯函数,输入旧状态与动作,输出全新状态。
    Reducer(Stateold,  Action)Reducer(State_{old},\; Action)
  4. State_new(新库存)计算出的新状态对象,交给视图层订阅消费。
    StatenewState_{new}

Redux设计哲学

西餐厅能保持米其林水准,靠的是主厨桌上那本厚厚的手册:规则一,不可修改原有单据;规则二,同样输入永远得到同样输出。任何厨师想动库存,都必须提交一张符合格式的点菜单——这就是 Redux 的哲学。

Redux 维护一棵全局状态树(store),组件不能直接改它,只能通过 dispatch 送出 action 对象。action 必须有 type 字段,可选 payload 携带数据。reducer 是纯函数:不修改传入的 state,而是复制后生成新状态,且不产生任何副作用,不发请求、不写文件。

正因为纯,同一组 action 序列在任何机器上重放,结果都完全一致。Redux Toolkit 在此基础上提供 createSlice,自动生成 action 和 reducer,把样板代码压缩到最低,让“手册”更容易维护。

主厨手册的每一条规则都是“纯”的,所以哪怕换一个完全陌生的厨师,只要照手册操作,菜的味道还是一样。这就是 Redux 的哲学:用纪律换确定性。

Action={type:T,  payload:P}\displaystyle Action = \{type: T,\; payload: P\}
action 就像一张标准化的点菜单:菜名(type)必须有,备注(payload)看情况加。
  1. type(菜品种类)必填的字符串字段,标识动作类别,如 'cart/add'。
    type:Stringtype: String
  2. payload(配菜信息)可选字段,携带新数据,如商品 ID 与数量。
    payload:anypayload: any
  3. Action(点菜单)完整描述一次意图的最小单元,可被序列化与记录。
    Action={type:String,  payload:any}Action = \{type: String,\; payload: any\}

Pinia设计哲学

咖啡馆的打法完全不同:咖啡师直接往库存本上写“牛奶-1”,系统自动把新数字推给所有看板。老板不要求统一格式的订单,只要求每个人都清楚自己负责的区域。Pinia 正是这种“轻管理”哲学的代码化身。

Pinia 是 Vue 官方推荐的状态库,通过 defineStore 创建一个仓库,内部明确划分 state(数据)、getters(派生数据)、actions(操作)。组件里直接读取或修改 state,Vue 的响应式系统负责追踪和同步,不需要手动订阅。

Pinia 同时支持 options 风格(类似 Vue 组件选项)和 setup 风格(类似组合式函数)。它的 action 可以自由 await 异步任务,不需要额外中间件,支持 store 之间相互调用,类型上几乎零成本获得完整推断。相比 Redux 的“先定义协议再干活”,Pinia 更接近“边写边用”的直觉。

咖啡馆的做法牺牲了一点“纪律感”,换来了上手速度和代码量的大幅缩减。如果你团队像一群默契的咖啡师,Pinia 会让你如虎添翼。

Piniastore={state,  getters,  actions}\displaystyle Pinia_{store} = \{state,\; getters,\; actions\}
一个 Pinia 仓库就是一张分区库存表:数据、报表、操作三类内容各占一列。
  1. state(食材库存)响应式数据源,存放所有共享变量的当前值。
    state={count:0}state = \{count: 0\}
  2. getters(每日报表)基于 state 的派生计算属性,带缓存效果。
    getters=f(state)getters = f(state)
  3. actions(操作手册)封装业务逻辑的方法,内部可改 state 也可异步。
    actions={increment()→void}actions = \{increment() \rightarrow void\}

异步中间件

后厨高压时刻的真正考验是“缺料”:牛奶没了,得打电话给供应商。西餐厅的规矩不允许主厨边做菜边打电话,于是有了两种外援——特派员(thunk)和管理者(saga)。

redux-thunk 的思路:让 action creator 可以返回一个函数,这个函数拿到 dispatch 和 getState 后,先自行 await 接口请求,再按需 dispatch 真正的 action。它轻量直白,适合简单异步,比如一次登录请求。

redux-saga 的思路:借用 Generator 函数,用一个副作用“管理进程”监听所有 action,遇到特定信号时执行并发请求、取消、重试、竞态等复杂流程,通过 yield put/call/take 描述副作用。它强大但概念多。Pinia 则没有中间件概念:action 本身就是 async 函数,await 完再赋值 state,最贴近直觉。

简单补货找特派员,复杂排程找管理者,而咖啡馆老板只会说:你自己去进货,回来记个账就行。选择异步方案前,先想清楚你后厨的复杂度。

Thunk=f(dispatch,  getState)\displaystyle Thunk = f(dispatch,\; getState)
thunk 就是主厨派出的特派员:先出去打电话给供应商(异步),回来再把结果写进点菜单。
  1. dispatch(传菜口)thunk 内部随时可派发普通 action 对象。
    dispatch:(Action)→voiddispatch: (Action) \rightarrow void
  2. getState(查库存本)读取当前全局状态的函数,用于条件判断。
    getState:()→StategetState: () \rightarrow State
  3. Thunk(特派员)先跑完外部异步任务,再回来派发真正的 action。
    Thunk:(dispatch,  getState)→PromiseThunk: (dispatch,\; getState) \rightarrow Promise

开发调试工具

米其林餐厅天花板上装着摄像机。客人投诉“汤太咸”时,经理可以回放录像,精确到哪一秒、哪个厨师、加了哪勺盐。这就是 Redux DevTools 的“时间旅行”体验。

因为 Redux 的每一步变更都是“action → reducer → 新 state”的纯记录,DevTools 可以把整串 action 像录像一样倒带、暂停、跳到任意节点,查看那一刻的完整 state 快照,还能对比前后差异。这是 Redux 最具说服力的杀手锏。

Pinia 借助 Vue DevTools 也能追踪 state 变更和 action 调用,能做基础的时间线回放,但“任意截点状态回放”的成熟度不如 Redux——直接修改 state 的粒度不如 action 序列规整。可追踪性与自由灵活,往往此消彼长。

摄像头回放的价值,在于“每一步都被忠实记录”。如果你需要这种级别的复盘能力,比如金融或复杂表单应用,Redux 的严密流程就是你的录像带。

Statet=Reducer∘Reducer∘⋯∘Reducer⏟t 次(State0)\displaystyle State_{t} = \underbrace{Reducer \circ Reducer \circ \cdots \circ Reducer}_{t\ 次}(State_{0})
时间旅行 = 把记录下来的所有点菜单从头再走一遍,走到第 t 步停下来看那一刻的库存。
  1. State_0(开业库存)应用启动时创建的最初状态快照。
    State0State_{0}
  2. Reducer^t(连做t次规则)把记录的 action 序列依次套用 reducer,重复 t 次。
    Reducer∘Reducer∘⋯∘Reducer⏟t 次\underbrace{Reducer \circ Reducer \circ \cdots \circ Reducer}_{t\ 次}
  3. State_t(第t刻库存)回放到第 t 步时的完整状态快照。
    StatetState_{t}

TypeScript支持

想象仓库货架上的标签:有的货架写着“鸡蛋:12个”,有的只写“杂货”。咖啡师的系统给每个货架自动生成标签,而西餐厅需要有人把所有标签手写一遍并随时维护。这就是两者在 TypeScript 体验上的差距。

Pinia 的类型支持几乎零成本:从 defineStore 的 state/getters/actions 自动推断出完整类型,组件里 store.count 的提示、赋值校验全部开箱即用,不需要额外声明。

Redux 因为状态分散在 reducer、action、selector 多处,类型需要手动搭桥:用 ReturnType<typeof reducer> 推导 RootState,用 typeof store.dispatch 得到 AppDispatch,还要为 action 定义联合类型。RTK 的 createSlice 已大幅改善体验,但整体心智负担仍比 Pinia 重。

标签写得好,新人也能立刻上手。TypeScript 支持不是可选项,而是大型项目的刚需:图省事选 Pinia,需要精细控制选 Redux + RTK,但两者都远好过没有任何类型的状态管理。

RootState=ReturnType<typeof reducer>\displaystyle RootState = ReturnType<\text{typeof } reducer>
这一行像给整个库存系统贴了一张总标签:全局状态的类型,直接从 reducer 函数的返回值里提取。
  1. typeof reducer(手册的类型)取出 reducer 函数本身的函数类型。
    typeof reducer\text{typeof } reducer
  2. ReturnType(返回类型提取)从函数类型中萃取其返回值的类型。
    ReturnType<F>\text{ReturnType}<F>
  3. RootState(全局状态类型)全局 state 的类型,供 selector 与 dispatch 复用。
    RootState=ReturnType<typeof reducer>RootState = ReturnType<\text{typeof } reducer>

技术选型与迁移

如果米其林餐厅想改成咖啡馆,老板不会扔掉菜谱,而是把“烹饪逻辑”保留下来,把“传菜流程”简化掉。Redux 迁 Pinia 也是同理:状态计算逻辑是资产,调度管道可以替换。

选型先看团队:团队熟 React 且项目复杂、重视调试确定性,Redux + RTK 依然是最稳选择;团队在 Vue 生态、看重开发速度,Pinia 是官方推荐;中小型项目或原型验证,Pinia 几乎总是更划算。

迁移时:把原 state 初始值搬进 Pinia 的 state;把 selector 翻译成 getters;把 thunk/saga 里的业务逻辑整理成 action(内部 await);把 connect/useSelector 替换为直接在组件里引入 store。saga 的取消与竞态逻辑需要显式设计成 AbortController 或任务状态机,这是迁移中唯一容易翻车的地方。

迁移的本质不是“推翻重来”,而是把同一套烹饪智慧放进更轻的管道。选型与迁移,最终都要落在“团队能长期维护”这个唯一标准上。

StorePinia={state=s0,  getters=Selector,  actions=Thunk∪Saga}\displaystyle Store_{\text{Pinia}} = \{state = s_{0},\; getters = Selector,\; actions = Thunk \cup Saga\}
迁移三件套:初始库存搬进新仓库,每日报表改成 getters,异步流程整理成 action。
  1. state(初始库存)迁移时直接复用原 Redux 的初始状态。
    state=s0state = s_{0}
  2. getters(派生报表)把原 selector 改造成 Pinia 计算属性。
    getters=Selectorgetters = Selector
  3. actions(新操作台)把 thunk/saga 的异步逻辑整理成 async action。
    actions=Thunk∪Sagaactions = Thunk \cup Saga

AI 应用场景

AI 对话助手中的流式会话状态

阶段:前端状态架构与异步流处理

解决的问题:传统写法把 messages、loading、streaming 状态散落在组件里,流式返回时容易丢消息或重复渲染。用 Redux 把“发送→逐块接收→追加”建模成 action 序列,中间件负责 SSE 流,任何一次异常都能在 DevTools 里回放定位。

具体案例:某企业 Copilot 前端,用户提交 prompt 后 dispatch 'chat/send',redux-saga 发起流式请求,每收到一段增量文本就 put 'chat/append',组件只从 store 读取会话。某次回答中断时,工程师通过时间旅行回放发现是超时 action 未取消导致。

AI 训练监控大屏的实时指标

阶段:实时数据可视化与状态同步

解决的问题:训练指标每秒推送,用事件总线或手动 ref 同步极易出错。Pinia 的响应式 store 让后端推送直接落地为 state,图表组件自动重绘,watch 触发告警,数据管道几乎为零。

具体案例:一个分布式训练看板,WebSocket 把 loss 和学习率写入 Pinia store,折线图组件通过 storeToRefs 解构出指标,数据到达即更新。工程师新增 GPU 温度指标时,只加一个 state 字段和一张卡片组件,数据管道完全不动。

低代码 AI 工作流编辑器的撤销重做

阶段:复杂编辑器状态编排

解决的问题:用户拖拽节点、连线、改参数,撤销重做是公认难点。借助 Redux,把每个编辑操作建模为 action,时间旅行天然等价于撤销/重做栈。

具体案例:一个 AI 工作流构建器,用户把“文本生成”节点拖到画布并连线,每次操作都 dispatch 对应 action。按 Ctrl+Z 时直接把指针回退一步并应用当时的 state,多步撤销零成本,开发效率远超手写历史栈。

Python 演示

python83 行
# -*- coding: utf-8 -*-"""用 Python 模拟 Redux 核心与 Pinia 风格状态管理""" from typing import Any, Callable, Dict, List  # ---------- 模拟 Redux 核心 ----------class ReduxStore:    def __init__(self, reducer: Callable, initial_state: Dict[str, Any]) -> None:        self._reducer = reducer        self._state = initial_state        self._listeners: List[Callable] = []     def get_state(self) -> Dict[str, Any]:        return dict(self._state)     def dispatch(self, action: Dict[str, Any]) -> None:        # 唯一的变更入口, 生成全新状态快照        self._state = self._reducer(self._state, action)        for listener in self._listeners:            listener()     def subscribe(self, listener: Callable) -> Callable:        self._listeners.append(listener)         def unsubscribe() -> None:            self._listeners.remove(listener)         return unsubscribe  def counter_reducer(state: Dict[str, Any], action: Dict[str, Any]) -> Dict[str, Any]:    if action["type"] == "add":        return {"count": state["count"] + action["payload"]}    if action["type"] == "reset":        return {"count": 0}    return state  # ---------- 模拟 Pinia 风格 ----------class PiniaLikeStore:    def __init__(self) -> None:        self._count = 0        self._listeners: List[Callable] = []     def increment(self, step: int = 1) -> None:        # 直接修改状态, 不经过 reducer        self._count += step        self._notify()     def reset_count(self) -> None:        self._count = 0        self._notify()     def _notify(self) -> None:        for listener in self._listeners:            listener()     def watch(self, listener: Callable) -> None:        self._listeners.append(listener)  def main() -> None:    # Redux 风格: 每次 dispatch 都产生一个全新快照    store = ReduxStore(counter_reducer, {"count": 0})    history: List[Dict[str, Any]] = []    store.subscribe(lambda: history.append(store.get_state()))     store.dispatch({"type": "add", "payload": 3})    store.dispatch({"type": "add", "payload": 2})    store.dispatch({"type": "reset"})    print("Redux 时间旅行快照:", history)     # Pinia 风格: 直接修改 + 通知订阅者    pstore = PiniaLikeStore()    pstore.watch(lambda: print("Pinia 当前状态:", pstore._count))    pstore.increment(5)    pstore.increment(2)    pstore.reset_count()  if __name__ == "__main__":    main()
目的:用两个极简类复刻 Redux 的“reducer 纯函数 + dispatch 快照”与 Pinia 的“直接修改 + 订阅通知”,直观对比两种状态管理哲学。
  1. self._state = self._reducer(self._state, action)状态只能通过 reducer 计算得出,相当于主厨按手册更新库存,绝不原地涂改。
  2. store.subscribe(lambda: history.append(store.get_state()))每次状态变化都记录一份快照,为时间旅行“录像”提供素材。
  3. store.dispatch({"type": "add", "payload": 3})派发一个普通对象 action,描述“发生了什么”,而不是“怎么改”。
  4. pstore._count += stepPinia 风格直接修改状态,跳过 action 与 reducer 的管道,代码更直观。
  5. def unsubscribe() -> None:subscribe 返回退订函数,防止监听器泄漏,这是真实状态库的必备设计。

常见误区

误区Pinia 里写 `store.count++` 就能改状态,那我直接在组件里随便改多爽,根本不用学 Redux 那套 action、reducer 的思想。

正解Pinia 的直接修改只是把管道变短,不等于没有规则。当多个组件都要改同一个状态、且改动有先后依赖时,散落各处的直接修改会让 bug 极难定位。建议把复杂业务放进 action,让 action 成为唯一修改入口。Redux 的纪律是硬约束,Pinia 的纪律靠自觉,团队规模越大越需要约定。

为何常见:Pinia 的宣传主打“直观、少样板”,初学者容易把“少写代码”误读为“不需要设计”。

误区Redux 又绕又啰嗦,肯定是老古董;Pinia 是新东西,所有新项目都该用它。

正解选型由约束条件决定,不由新旧决定。React 大型项目、跨团队协作、强调试诉求下,Redux + Redux Toolkit 依然是成熟可靠的选择;Pinia 是 Vue 官方推荐,在 Vue 项目里是默认首选。脱离技术栈谈“过时”没有意义。

为何常见:社交平台上大量对比文章喜欢用“新 vs 旧”制造流量,忽略了具体上下文和团队背景。

误区thunk 只能处理简单异步,saga 功能强大,我干脆全用 saga,以后什么场景都不怕。

正解工具选择取决于异步流程的真实复杂度。90% 的登录、拉列表用 thunk 就够;只有出现并发竞态、取消、重试、事件编排时才需要 saga。盲目引入 saga 会让团队多学一套 Generator 心智模型,反而拖慢开发与维护。

为何常见:看到 saga 功能强大就产生“能力越强越好”的错觉,忽略了学习成本与维护成本。

误区Redux DevTools 能时间旅行,那我是不是可以穿越回去把 bug 修了,状态就永远对了?

正解时间旅行不是篡改历史,而是把已记录的 action 序列从头重新执行一遍,在第 t 步停下来查看那一时刻的状态快照。它只能帮你复现和分析问题,不能替你修掉未来的 bug。能这样做的前提恰恰是 reducer 的纯函数特性——同样的输入永远得到同样的输出。

为何常见:“时间旅行”这个词太有画面感,让人误以为它可以真正改写过去。

面试题

Q1. Redux 的 reducer 必须满足哪个核心条件?

单选题 · easy

  • A.允许副作用如修改外部变量
  • B.纯函数且结果仅由入参决定← 正确答案
  • C.只能处理同步类型的逻辑
  • D.可以直接原地修改原有状态

答案:B。在“后厨规则”比喻里,reducer 就是主厨手册。它必须保证同样的库存和同样的点菜单,永远得到同样的新库存,还不能偷偷打电话(副作用)、不能在原单据上涂改(不可变)。A 允许副作用不满足纯函数;C 混淆了“同步”与“纯”;D 破坏不可变性。

Q2. redux-thunk 的核心能力是什么?

单选题 · mid

  • A.让 action 可以是一个函数← 正确答案
  • B.让 reducer 变成异步执行
  • C.完全替代 redux-saga 功能
  • D.自动生成 action 类型定义

答案:A。thunk 等于主厨派出的特派员:它可以出去打电话给供应商(异步),回来后把结果写进点菜单再交给主厨,也就是让 action creator 返回函数。B 错在 reducer 始终是纯函数;C 过度夸大,复杂流程仍需 saga;D 是 Redux Toolkit 的职责。

Q3. 哪些特性支撑了 Redux 时间旅行的实现?

多选题 · mid

  • A.action 是纯对象可以序列化← 正确答案
  • B.reducer 无副作用且可重放← 正确答案
  • C.store 变量可以被直接修改
  • D.组件必须使用类组件写法

答案:AB。时间旅行像厨房录像回放,要求每个 action 都能完整记录(可序列化,A),且 reducer 重放同一序列能得到完全一致的结果(纯函数,B)。如果 state 被直接改过(C),录像就失真了;组件写法(D)与回放机制无关。

Q4. Pinia 中更新 state 的推荐做法是?

单选题 · easy

  • A.必须派发一个 action 对象
  • B.直接修改或调用 action← 正确答案
  • C.只能在 getters 中更新
  • D.需要额外引入中间件库

答案:B。Pinia 是咖啡馆模式:咖啡师可以直接在库存本上写“牛奶-1”(直接修改),也可以调用封装好的“补货流程” action。A 是 Redux 的规矩;C 中 getters 是派生计算,不能作为更新入口;D 中 Pinia 不需要中间件。

Q5. Redux 相比 Pinia 最核心的差异在于?

单选题 · mid

  • A.强制单向流程换取可预测性← 正确答案
  • B.完全禁止任何异步操作
  • C.不支持跨组件共享状态
  • D.状态只能存放在本地磁盘

答案:A。两者核心差异是“变更路径”:Redux 像米其林餐厅必须走完固定的传菜路线,换来每一步都可追踪;Pinia 像咖啡馆直接改库存,换来效率与直观。B、C、D 都是捏造的缺陷——Redux 支持异步、解决共享、也不会把状态存磁盘。

Q6. 什么是 Redux 的单一全局状态树?

单选题 · easy

  • A.整个应用只有一棵状态树← 正确答案
  • B.每个组件维护独立状态
  • C.状态存储在浏览器数据库
  • D.所有数据都通过 props 传递

答案:A。单一全局状态树好比全餐厅只有一本库存总账,任何灶台缺料都查同一本账。B 是组件私有状态,恰好是 Redux 想收敛的;C 是持久化方案,不是状态树;D 是 props 透传,容易产生钻透问题。

Q7. 从 Redux 迁移到 Pinia 的合理映射是?

多选题 · hard

  • A.state 初始化直接复用原值← 正确答案
  • B.selector 改造成 getters← 正确答案
  • C.thunk 原样放进中间件
  • D.saga 可以原封不动直接运行

答案:AB。迁移像米其林改咖啡馆:菜谱(纯逻辑)保留,传菜管道替换。A 把初始库存搬进新仓库,B 把每日报表(selector)转成 getters,都正确。C 错在 Pinia 没有中间件概念;D 错在 saga 的取消、竞态逻辑需要手工重构成 AbortController 或任务状态机,不能原样运行。

Q8. redux-saga 依赖哪个语言特性?

单选题 · mid

  • A.Promise 链式回调函数
  • B.Generator 与 yield 语法← 正确答案
  • C.Web Worker 多线程机制
  • D.原生事件总线 EventBus

答案:B。saga 的“管理者”能一边监听一边挂起等待,靠的是 Generator 函数的 yield——执行到 yield 就暂停,收到信号再继续。A 是 thunk 的底层思路;C 与多线程无关;D 是架构模式,不是语言特性。

Q9. 下列属于 Pinia 相对优势的是?

多选题 · hard

  • A.更少的样板代码和直观 API← 正确答案
  • B.TypeScript 类型自动推断← 正确答案
  • C.天生支持时间旅行回放
  • D.中间件生态比 Redux 更庞大

答案:AB。Pinia 的优势是“少写标签、自动推断”:定义仓库后,state 和 action 的类型自动生成,组件里输入提示开箱即用。C 错在 Pinia 没有 Redux 那种成熟的 action 回放能力;D 错在 Redux 的中间件生态反而更庞大。

Q10. 大型 React 项目极重视状态可预测性,更合理的选择是?

单选题 · extreme

  • A.优先 Pinia 因为 API 更简洁
  • B.选 Redux 搭配 Redux Toolkit← 正确答案
  • C.两者都无法胜任大型项目
  • D.必须用原生 Context 全替代

答案:B。选型看场景:大型 React 项目 + 高可预测性诉求,Redux + Toolkit 的约束、DevTools 时间旅行和庞大生态仍是“最不坏”的选择。A 的 Pinia 属于 Vue 生态,灵活风格换不来强约束;C 太绝对;D 中 Context 只适合浅层共享,复杂状态会引发大面积重渲染。