资讯动态

easy-peasy v7 并发特性实战:基于 React 19 的 Suspense 水合、Transition 与乐观更新示例解析

发布时间:2026/10/13 6:05:14 来源:尧图企业网站定制
前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载本文以仓库中的 v7-concurrent 示例 为骨架完整讲解 easy-peasy v7 引入的四个并发 Hook——useStoreRehydrated、useStoreTransition、useStoreDeferredState、useStoreOptimistic——在真实页面中的组合用法并深入到 src/hooks.js 与 tests 验证其底层实现原理。读完本文你将掌握如何在一套 easy-peasy 单页应用中用 Suspense 消除持久化水合闪烁、用非阻塞 Transition 派发 thunk、用旧值先渲染的延迟选择器保持输入流畅以及用乐观更新让异步操作即时反馈。一、示例概览一张页面覆盖 v7 全部并发 Hookv7-concurrent 示例 是一个单页演示应用其设计目标非常明确——在同一张页面上逐一演示 v7 新增的每一个并发 Hook。README 用一张表格给出了功能区块与 API 的对应关系功能区块演示的 APICounter计数器useStoreRehydratedSuspense基于 Suspense 的持久化分片水合Catalog search目录搜索useStoreTransition非阻塞 thunk 派发useStoreDeferredStatestale-while-fresh 选择器Todos待办useStoreOptimisticthunk 执行期间的乐观 UI页面结构见 examples/v7-concurrent/src/App.tsx顶层用Suspense fallback{pRehydrating persisted counter…/p}包裹Counter组件其余两个区块各自独立、用hr /分隔。三个区块互不干扰正好适合逐块对照学习。技术栈方面package.json 显示该示例基于react/react-dom19.x、easy-peasy^7.0.0-beta.2并以 Vite 6 TypeScript 5 搭建——也就是说示例中的四个 Hook 全部建立在 React 19 的并发原语use、useTransition、useDeferredValue、useOptimistic之上。二、运行示例按 v7-concurrent README 给出的方式运行yarn install yarn dev随后在浏览器打开 http://localhost:3000。package.json中定义了完整脚本可按需选用脚本作用yarn dev启动 Vite 开发服务器固定端口3000vite --port 3000yarn build先执行tsc类型检查再执行vite build产出生产构建yarn serve以vite preview预览生产构建产物yarn type-check仅运行tsc做类型检查README 特别强调了一个可验证的行为持久化的计数器写入localStorage点击 Increment 后刷新页面计数会先经过 Suspense fallback再恢复为刷新前的值。这正是第一区块要演示的基于 Suspense 的水合。三、Store 与类型化 Hook并发特性的使用前提3.1 Store Model三个分片各司其职examples/v7-concurrent/src/store/model.ts 定义了StoreModel包含counter、catalog、todos三个分片countercount: numberincrementaction基于 Immer 直接state.count 1catalogitemsCatalogItem[]、query字符串、setItems/setQuery两个 action以及loadItems: ThunkCatalogModel, number——用setTimeout 600ms模拟异步加载生成 2 万个目录条目todositemsTodo[]、同步addItemaction以及addItemAsync: ThunkTodosModel, { text: string }——用setTimeout 1200ms模拟网络往返完成后调用addItem真正提交todo 的id由crypto.randomUUID()生成。注意storeModel的组装方式model.tsexport const storeModel: StoreModel { counter: persist(counter), catalog, todos, };只有counter分片被persist()包裹——这是有意为之第一区块要演示持久化 Suspense 水合而catalog、todos是纯内存状态走普通的 action / thunk 流程。persist()是 easy-peasy 提供的持久化包装器支持storage、allow/deny、mergeStrategy、transformers、migrations等配置详见 website/docs/docs/api/persist.md 与底层实现 src/persistence.js此处使用默认配置即浏览器localStoragemergeDeep合并策略。Store 的创建则极其简单examples/v7-concurrent/src/store/index.tsimport { createStore } from easy-peasy; import { storeModel } from ./model; export const store createStore(storeModel);3.2 createTypedHooks一次性导出全部并发 Hook为了让组件获得完整的 TypeScript 推断示例没有直接 import easy-peasy 的全局 Hook而是通过createTypedHooksStoreModel()生成了类型化版本examples/v7-concurrent/src/store/hooks.tsimport { createTypedHooks } from easy-peasy; import type { StoreModel } from ./model; export const { useStoreActions, useStoreDispatch, useStoreState, useStore, useStoreRehydrated, useStoreTransition, useStoreDeferredState, useStoreOptimistic, } createTypedHooksStoreModel();从 src/hooks.js 可以看到createTypedHooks()就是把七个 Hook含useStore打包成一个对象返回而四个并发 Hook 均在其中。组件入口通过 main.tsx 中的StoreProvider store{store}提供 store 上下文随后所有页面组件统一从../store/hooks导入使用。四、区块一Suspense 水合 —— useStoreRehydrated4.1 要解决的问题异步存储导致的内容闪烁当配合异步存储引擎如 AsyncStorage使用persist时应用通常先以默认 state 渲染一帧异步读取完成后再用恢复的 state 重新渲染用户会看到明显的闪一下。useStoreRehydrated正是为此设计的它会在水合期间挂起suspend配合Suspense边界让 React 显示 fallback直到水合完成。官方文档明确指出这是 v7 的破坏性变更——v6 及之前该 Hook 返回布尔值水合中false、完成后truev7 改为挂起 恒返回true旧的布尔条件渲染写法仍可编译但loading 分支永远不可达应迁移到Suspense边界见 website/docs/docs/api/use-store-rehydrated.md。4.2 示例代码与实现原理区块一的组件examples/v7-concurrent/src/features/CounterRehydrate.tsx展示了最简洁的用法import { useStoreActions, useStoreRehydrated, useStoreState } from ../store/hooks; function Counter() { useStoreRehydrated(); const count useStoreState((state) state.counter.count); const increment useStoreActions((actions) actions.counter.increment); return ( section h21. Suspense rehydration/h2 p codeuseStoreRehydrated/code suspends until the persisted counter loads. Reload the page after incrementing — the value persists. /p pCount: strong{count}/strong/p button onClick{() increment()} typebuttonIncrement/button /section ); }组件在函数体第一行调用useStoreRehydrated()一旦水合未完成该调用即抛出让 React 挂起的信号上层的Suspensefallback 接管渲染水合完成后调用返回true真实 UI 渲染。源码实现只有三行src/hooks.jsexport function createStoreRehydratedHook(Context) { return function useStoreRehydrated() { const store useContext(Context); use(store.persist.resolveRehydration()); return true; }; }store.persist.resolveRehydration()返回一个在水合流程结束包括无持久化配置时的空流程后 resolve 的 Promiseuse()是 React 19 新增的、可在条件分支中直接消费 Promise / Context 的 API这正是让组件挂起的关键水合完成前 Promise 未 settleReact 向上寻找最近的Suspense边界完成后组件恢复渲染并返回true。水合流程本身在 src/persistence.js 的rehydrateStateFromPersistIfNeeded中它按持久化配置逐条读取存储同步存储直接取值异步存储等待 Promise然后依据mergeStrategyoverwrite/mergeShallow/mergeDeep把恢复的数据合并进初始 state最后调用runWithTransition(() replaceState(state))以 transition 的方式替换整棵状态树。对照实验把计数增加到非零值后刷新页面你会先看到 Rehydrating persisted counter… fallback随后直接看到恢复后的计数值——没有默认 0 → 真实值的闪烁。这正是本区块想要演示的核心体验。五、区块二非阻塞 thunk 与延迟选择器 —— useStoreTransition useStoreDeferredState区块二examples/v7-concurrent/src/features/CatalogSearch.tsx同时展示了两个 Hook 的配合是本文信息量最大的一段。5.1 useStoreTransition把派发包进 startTransition用法如下const [loadItems, isLoading] useStoreTransition( (actions) actions.catalog.loadItems, ); // 按钮点击时 button disabled{isLoading} onClick{() loadItems(20000)} typebutton {isLoading ? Loading 20,000 items… : Load 20,000 items} /buttonuseStoreTransition返回元组[wrappedActions, isPending]wrappedActions与mapActions返回的形态保持一致传入单个 action 时返回单个包装函数传入可嵌套的action 对象时每个函数叶子都会被递归包装非函数值原样透传isPending是布尔值在 React 处理 transition 期间为true结束后为false可用来驱动按钮禁用 / 文案切换等 UI 反馈。底层实现见 src/hooks.jswrapInTransition对函数叶子返回(...args) { let result; startTransition(() { result target(...args); }); return result; }即把目标 action / thunk 的调用放进startTransition。对 thunk 而言包装函数仍返回原始 Promise因此可以await拿到 thunk 的返回值——仓库测试 tests/use-store-transition.test.js 专门验证了这一点await expect(captured).resolves.toBe(done:one)并且测试覆盖了单 action、嵌套对象递归包装、thunk Promise 透传三种场景。效果loadItems内部先await600ms再一次性setItems注入 2 万个条目。由于派发发生在 transition 中渲染这批大列表的工作被标记为非紧急不会阻塞用户输入等更高优先级的更新isPending在整个期间保持true按钮显示 Loading 20,000 items…。5.2 useStoreDeferredStatestale-while-fresh 的延迟选择器紧接着的过滤器演示了第二个 Hookconst matches useStoreDeferredState((state) { const q state.catalog.query.toLowerCase(); if (!q) return state.catalog.items.slice(0, 50); return state.catalog.items .filter((item) item.name.toLowerCase().includes(q)) .slice(0, 50); });useStoreDeferredState等价于useStoreState(selector, equalityFn) React 的useDeferredValue实现见 src/hooks.js它先通过useStoreState取出最新选中值再经useDeferredValue延迟。当用户快速输入时React 会先带着上一次的旧值渲染保证输入框响应迅速同时在后台计算基于最新query的过滤结果计算完成后在下一次渲染中交付新值——这就是 README 所说的stale-while-fresh旧值保鲜选择器。值得注意的是这个选择器是纯派生计算不依赖query之外的任何状态。catalog的query字段通过setQueryaction 更新state.query query而matches的选择器在每次 state 变化时重新执行。当加载了 2 万个条目后filter(...).slice(0, 50)的复杂度足以让延迟渲染的价值显现输入体验不因过滤计算而卡顿。仓库对useStoreDeferredState的行为有完整测试tests/use-store-deferred-state.test.js初始渲染返回当前值action 派发后更新为新值并验证了equalityFn生效——当自定义相等函数认为前后选中值等价时组件不产生多余渲染延迟值保持旧值prev.id next.id时即使name变了也不触发重渲。选型提示useStoreDeferredState适合选择器昂贵 允许短暂展示旧值的场景如果选择器很廉价直接用普通的useStoreState即可见 website/docs/docs/api/use-store-deferred-state.md。六、区块三乐观更新 —— useStoreOptimistic区块三examples/v7-concurrent/src/features/TodoOptimistic.tsx演示了如何让异步提交先显示、后落地const [items, addOptimistic] useStoreOptimisticTodo[], Todo( (state) state.todos.items, // mapState被乐观渲染的 state (current, pending) [...current, pending], // updateFn如何把 pending 值合并进当前值 ); function handleSubmit() { if (!draft.trim()) return; const text draft; setDraft(); startTransition(async () { addOptimistic({ id: optimistic-${Date.now()}, text }); // ① 立即注入乐观条目 await addItemAsync({ text }); // ② 真实 thunk 提交 }); }渲染时用todo.id.startsWith(optimistic-)区分乐观条目并以斜体展示——这正是伪条目带 … 后缀、真条目正常展示的实现。useStoreOptimistic返回元组[optimisticState, addOptimistic]其底层实现src/hooks.js是把useStoreState(mapState)的返回值交给 React 19 的useOptimistic(value, updateFn)export function createStoreOptimisticHook(Context) { const useStoreStateForContext createStoreStateHook(Context); return function useStoreOptimistic(mapState, updateFn) { const value useStoreStateForContext(mapState); return useOptimistic(value, updateFn); }; }执行时序一目了然用户在 transition 中调用addOptimistic(伪条目)useOptimistic立即以updateFn(current, pending)产出乐观列表——新增的 todo 立刻出现在列表里addItemAsyncthunk 开始 1.2s 的模拟网络往返thunk 完成时调用actions.addItem({ id: crypto.randomUUID(), text })store 的items真实更新useStoreState返回新值useOptimistic随即丢弃乐观值、回到真实 store 值——乐观条目被真实条目替换。仓库测试 tests/use-store-optimistic.test.js 完整还原了这个过程thunk 挂起期间列表显示a,b-pendingresolveThunk()之后真实 action 提交列表变为a,b乐观值被替换。需要注意两条使用约束见 website/docs/docs/api/use-store-optimistic.mdaddOptimistic(pending)必须在 transition 内调用如startTransition或 React 的 form action否则乐观值不会被 React 采纳由于乐观值会在底层选中状态变化时被重置选择器应尽量选中受影响的最小 state 片段行为才最干净——示例中恰好选中state.todos.items与乐观操作的作用域一致。七、四个 Hook 的选型速查Hook底层 React 原语典型场景返回useStoreRehydrateduse()persist.resolveRehydration()异步存储水合期间的闪烁消除配合Suspensetrue挂起直到水合完成useStoreTransitionuseTransition()昂贵的、非紧急的 action/thunk 派发避免阻塞输入[wrappedActions, isPending]useStoreDeferredStateuseStoreStateuseDeferredValue昂贵选择器大列表过滤/派生的 stale-while-fresh 渲染延迟后的选中值useStoreOptimisticuseStoreStateuseOptimistic异步 thunk 提交前的即时反馈消息、插入、开关[optimisticState, addOptimistic]实现细节均位于 src/hooks.js四个 Hook 都支持通过createStoreContext/createTypedHooks复用工厂createStoreTransitionHook、createStoreDeferredStateHook、createStoreOptimisticHook、createStoreRehydratedHook与 easy-peasy 的整体设计保持一致。八、小结与延伸阅读v7-concurrent 示例 的价值在于把 v7 的四个并发 Hook 压缩进一个可运行的页面并且用localStorage持久化、2 万条目录数据、1.2s 异步 thunk 这些具体数据让每个 Hook 的效果肉眼可见。你可以直接运行yarn dev后逐个区块验证刷新计数器验证 Suspense 水合、点击 Load 20,000 items 后输入过滤验证 transition 延迟渲染、连续回车添加 todo 验证乐观更新。想进一步深挖可在本仓库中查阅Hook 完整 API 文档useStoreTransition、useStoreDeferredState、useStoreOptimistic、useStoreRehydrated底层实现src/hooks.jsHook 工厂与并发原语封装、src/persistence.js持久化水合与合并策略行为验证tests/use-store-transition.test.js、tests/use-store-deferred-state.test.js、tests/use-store-optimistic.test.js、tests/persist.test.jsv7 破坏性变更说明website/docs/docs/upgrading-from-v6/README.md。配合这些源码与测试你不仅能跑通示例还能在真实项目中按需挑选最合适的并发 Hook组合出既不阻塞交互、又无内容闪烁的高响应度界面。赞分享前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载相关推荐easy-peasy 官方示例仓库全解析从 Vite Todo 到 RSC 与 v7 并发 Hooks 的实战导览easy peasy 官方示例仓库全解析从 Vite Todo 到 RSC 与 v7 并发 Hooks 的实战导览 本文以 easy peasy 仓库中的 e前端Easy-Peasy 从 V3 升级指南新特性与重大变更解析Easy Peasy 从 V3 升级指南新特性与重大变更解析 前言为什么需要升级 还在为 React 状态管理的复杂性而头疼吗Easy Peasy V3前端easy-peasy 快速上手指南为 React 打造的零样板 Redux 状态管理含 v7 并发特性预览easy peasy 快速上手指南为 React 打造的零样板 Redux 状态管理含 v7 并发特性预览 easy peasy 是一个面向 React前端上一篇Vue3TypeScriptVite完美融合vue-vben-admin核心技术栈解析下一篇如何在Switch上使用wiliwili第三方B站客户端的完整使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑