首页 / 资讯中心 / 文章详情

Easy Peasy 快速入门:用 React Hooks 风格从零构建全局状态管理

Easy Peasy 快速入门:用 React Hooks 风格从零构建全局状态管理 ★ FEATURED ARTICLE
前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载本篇技术指南基于 easy-peasy 官方教程文档 quick-start.md 展开围绕其核心 API 使用流程创建 Store、绑定 Provider、读取状态、定义与分发 Action、用 Thunk 封装副作用、用 computed 派生状态、持久化与重新水合逐步讲解。读完本文你将能够从零搭建一个具备状态读写、异步副作用、派生数据与本地持久化的完整 React 状态管理方案并能结合仓库源码理解其底层实现原理。Easy Peasy 是对 Redux 的一层抽象见 README.md它在保留 Redux 架构保障与生态的同时用模型即普通 JavaScript 对象的方式消除了配置与样板代码你不需要手写 reducer、action type 常量或 dispatch 映射只需在一个纯对象模型上声明状态、action、thunk 与 computed 属性即可。本教程对应的仓库中提供了大量可直接对照的完整示例如 simple-todo、kanban、nextjs-todo 等。第一步创建 StorecreateStoreEasy Peasy 的核心概念是model模型一个纯 JavaScript 对象其属性直接描述你的状态结构与行为。将它传给createStore即可得到一个 store 实例。import { createStore } from easy-peasy; const store createStore({ todos: [], });modelObject必填描述整个状态树的模型对象。configObject可选用于定制 store 行为的配置项。提示你的模型可以像你的业务一样复杂、嵌套。随着状态结构规模的增长完全可以把它拆分为多个模块再用import组合起来。仓库中的 kanban 示例 与 reduxtagram 示例 都展示了多模块模型的组织方式。从源码看 createStore 的配置能力从 create-store.js 的源码结构可以推断createStore(model, options)支持以下常用配置项devTools是否接入 Redux DevTools开发环境下默认开启也可传入自定义配置对象disableImmer关闭 Immer 的 mutation 追踪能力默认falseinitialState为 store 提供初始状态常用于服务端渲染或测试时注入数据injections注入依赖如 API 服务对象供 thunk 通过helpers.injections使用middleware追加额外的 Redux 中间件name为 store 命名用于持久化缓存键多 store 场景必须唯一version标记模型版本用于让旧版本的持久化数据失效mockActions进入模拟分发模式测试用enhancers/compose/reducerEnhancer面向高级场景的增强器配置。仓库中的 reduxtagram 示例 就展示了devTools与initialState的实际用法const store createStore(storeModel, { devTools: import.meta.env.DEV ? { name: Easy Peasy Reduxtagram } : false, initialState: { commentsModel: { comments }, postsModel: { posts } }, });创建好的 store 会返回一个增强过的 Redux store 实例除了标准的dispatch、getState、subscribe之外还附带了addModel、removeModel、reconfigure、getActions、persist等扩展 API见 create-store.js。第二步将 Store 绑定到 React 应用StoreProvider要让组件能访问到 store需要把应用包在StoreProvider组件中并把 store 实例传给它。import { createRoot } from react-dom/client; import { StoreProvider } from easy-peasy; import { store } from ./store; createRoot(rootEl).render( StoreProvider store{store} App / /StoreProvider, );StoreProvider的作用是向整棵组件树提供 store 的 React Context这样应用中所有 store hooks 都能访问到该 store见 store-provider.md 与 provider.js。仓库示例 simple-todo/src/main.tsx 展示了在 React 19 的createRoot下的标准写法。第三步在组件中读取状态useStoreStateuseStoreStatehook 允许你从 store 中选取状态它接收一个选择器函数import { useStoreState } from easy-peasy; function Todos() { const todos useStoreState((state) state.todos); return ( ul {todos.map((todo) ( li{todo.text}/li ))} /ul ); }选择器接收整个 store 状态并返回你需要的部分。从 hooks.js 的源码可以看出useStoreState基于 React 的useSyncExternalStore实现并做了选择器记忆化只有当选择器返回的引用发生变化时默认通过引用相等refEquality判断才会触发重渲染。你还可以传入第二个参数自定义相等性判断函数实现更精细的渲染控制。在 simple-todo 示例 中组件一次性取出了remainingTodos与completedTodos两个派生状态用于渲染列表kanban 示例 中则通过useStoreState((state) state[list])动态选取某个看板列的状态。第四步定义 Action 来更新状态action在模型里放置一个action即可声明对状态的更新逻辑import { createStore, action } from easy-peasy; const store createStore({ todos: [], addTodo: action((state, payload) { state.todos.push({ text: payload, done: false }); }), });action 的回调会收到两个参数state与该 action 绑定的局部状态对象你可以直接对它进行修改mutationpayload分发该 action 时携带的数据可选。关键点你不需要手动返回新状态。直接修改state参数即可——Easy Peasy 在底层通过 Immer 与 helpers.js 中action的定义。这也是 Easy Peasy 零样板的核心所在没有 action type 常量没有 switch-case reducer。源码佐证与进阶细节分发机制从 actions.js 可以看到每个 action 会被包装为一个 action creator分发时携带type、payload与config最终调用 store 的dispatch。action 的分发是同步执行的。action 内 console.log 输出 Proxy 的问题由于 Immer 使用 Proxy 追踪变更直接在 action 里console.log(state)会打印出 Proxy 对象而非真实数据。此时可以用debug(state)工具获取真实值见 action.md 与 helpers.jsimport { action, debug } from easy-peasy; const model { myAction: action((state, payload) { console.log(debug(state)); }), };反模式一不要解构state参数。const { todos } state或action(({ todos }, payload) ...)会跳出 Immer 的 Proxy 追踪导致状态无法正确更新。反模式二不要在 action 中执行副作用。action 应当是同步、纯的只做状态更新。API 请求等副作用应封装进 thunk见下文第六步。第五步分发 ActionuseStoreActions组件中通过useStoreActionshook 拿到 action 并分发import { useStoreActions } from easy-peasy; function AddTodoForm() { const addTodo useStoreActions((actions) actions.addTodo); const [value, setValue] React.useState(); return ( input onChange{(e) setValue(e.target.value)} value{value} / button onClick{() addTodo(value)}Add Todo/button / ); }在这个例子中我们解析出addTodoaction把它绑定到 Add Todo 按钮的点击事件上。按钮被点击时会分发addTodoaction并把新待办项的文本作为 payload 传入。从 hooks.js 的源码看useStoreActions接收一个选择器函数选择器拿到的是整个 store 的所有 actions通过store.getActions()可以从中精确选取所需的 action也可以一次性解构多个 action。仓库示例 simple-todo/src/App.tsx 中const { toggleTodo } useStoreActions((actions) actions)与 kanban 中useStoreActions((actions) actions[list])都是典型用法。第六步用 Thunk 封装副作用thunkthunk用来封装副作用如 API 请求同时它又能分发 action 来更新状态import { action, thunk } from easy-peasy; const model { todos: [], addTodo: action((state, payload) { state.todos.push(payload); }), saveTodo: thunk(async (actions, payload) { const { data } await axios.post(/todos, payload); actions.addTodo(data); }), };thunk 的回调接收三个参数actions与 thunk 同层级的本地 actions用于分发 action 更新状态payload分发时传入的数据helpers进阶工具对象包含dispatch、fail、getState、getStoreActions、getStoreState、injections、meta等详见 thunk.md。在示例中thunk 内发起 API 请求随后利用返回的数据分发addTodoaction 来更新状态。thunk 可以异步async/await或返回Promise也可以同步适合封装if/else等流程逻辑thunk 的返回值会透传给分发方因此调用方可以链式等待异步完成见 thunks.js。从源码看 thunk 的三种状态从 thunks.js 的源码可以看到每次分发 thunk 时 Easy Peasy 会自动派发start开始、success成功事件 action失败时通过helpers.fail(error)主动标记派发fail失败事件 action。这些事件 action 不修改状态但能在 Redux DevTools 中清晰展示异步流程并允许其他监听器针对 thunk 的特定状态做出反应。依赖注入需要给 thunk 提供外部依赖如 API 客户端时可以在创建 store 时通过injections配置注入thunk 中通过helpers.injections取用const store createStore(model, { injections: { todosService, }, }); // thunk 内部 saveTodo: thunk(async (actions, payload, { injections }) { const { todosService } injections; await todosService.save(payload); }),仓库示例 simple-todo/src/store/model.ts 中的saveTodosthunk 与onTodosChangedthunkOn监听器展示了实际项目中 thunk 与 thunkOn 的组合用法。第七步在组件中分发 ThunkuseStoreActionsthunk 与 action 的访问方式完全一致同样通过useStoreActionshookimport { useStoreActions } from easy-peasy; function AddTodoForm() { const saveTodo useStoreActions((actions) actions.saveTodo); const [value, setValue] React.useState(); return ( input onChange{(e) setValue(e.target.value)} value{value} / button onClick{() saveTodo(value)}Add Todo/button / ); }由于异步 thunk 返回Promise你可以在分发处链式处理后续逻辑例如保存成功后跳转页面const onSaveClick () saveTodo(Learn easy peasy).then(() { // redirect on success history.push(/done); });第八步用 computed 派生状态computed状态管理中派生状态是常见需求购物篮组件可能需要计算总价分页组件可能需要总条目数。computedAPI 正是为此设计import { computed } from easy-peasy; const store createStore({ todos: [{ text: Learn easy peasy, done: true }], completedTodos: computed((state) state.todos.filter((todo) todo.done)), });computed 的属性会被当作普通状态一样被组件读取见 computed.md。Easy Peasy 会在底层完成所需的优化让 computed 属性保持高性能。从源码看 computed 的缓存与惰性求值从 computed-properties.js 的源码可以看出其设计惰性求值computed 只有在被组件或getState真正访问时才计算通过Object.defineProperty定义 getter 实现输入缓存记录上次的输入state resolvers 的结果与计算结果当 store 引用未变且输入相等时直接返回缓存值避免重复计算相等性判断通过fast-deep-equal对新旧计算结果做深度比较结果未变化时复用旧引用从而避免触发无谓的重渲染。computed 的进阶用法state resolvers状态解析器computed支持第二种签名computed(stateResolverFns, computeFn)将多个特定状态片段甚至整棵 store 状态作为计算输入适合引用模型其他部分的状态见 computed.mdproductsInBasket: computed( [ (state) state.productIds, (state, storeState) storeState.products.items, ], (productIds, products) productIds.map((id) products[id]), ),支持运行时参数computed 本身不接受运行时参数但可以让 computed 返回一个函数来间接实现。注意只有返回的函数本身会被记忆化如需对函数结果做 memo可配合memoizerific等工具见 computed.md。两个已知注意事项在 action 内访问 computed 会得到缓存值为了保障惰性求值computed 在 reducer 执行期间不会被重新计算会返回上一次的缓存结果从状态中解构 computed 的潜在问题当 computed 依赖外部状态或外部数据如Math.random()时不要通过解构useStoreState((state) state.session)的方式取用否则可能不会触发重渲染应改为直接选择该 computed 属性useStoreState((state) state.users.isLoggedIn)。第九步使用 computed 属性computed 属性与普通状态一样通过useStoreState访问import { useStoreState } from easy-peasy; function CompletedTodos() { const completedTodos useStoreState((state) state.completedTodos); return ( {completedTodos.map((todo) ( Todo todo{todo} / ))} / ); }当 computed 的输入状态变化时组件会自动重新渲染。仓库示例 simple-todo/src/store/model.ts 中定义了completedTodos与remainingTodos两个 computed 属性并在 App.tsx 中同时消费演示了典型的派生列表场景。第十步持久化状态persist如果希望持久化全部或部分状态可以使用persistAPIimport { persist } from easy-peasy; const store createStore( persist({ count: 1, inc: action((state) { state.count 1; }), }), );这一简单的包裹就会让 Easy Peasy 把 store 状态保存进sessionStorage并且每次状态变化时都会持久化。下次创建 store 时Easy Peasy 会查找已持久化的数据若存在则用它重新水合rehydrate状态。从源码看持久化实现从 persistence.js 可以看到存储层的封装逻辑默认存储浏览器环境下默认使用sessionStorage也可以通过配置切换到localStorage或传入自定义存储引擎对象需实现getItem/setItem/removeItem序列化写入浏览器存储时统一JSON.stringify({ data })读取时JSON.parse(...).data异步队列持久化是异步的内部通过队列保证多个持久化操作不会并发执行isPersistingnextPersistOperation见 persistence.js并在空闲时通过requestIdleCallback调度非浏览器环境同步执行。persist的第二个参数是配置对象支持allow只持久化指定 key、deny排除指定 key、mergeStrategy重新水合时的合并策略默认mergeDeep可选mergeShallow/overwrite、migrations按版本迁移持久化数据、transformers自定义数据转换例如把Map/Set转成 JSON 可序列化结构。createStore的version配置还可以在模型大改时强制丢弃旧版本数据详见 persist.md。另外store 实例还暴露了store.persist.flush()立即执行排队中的持久化操作常用于应用卸载前保证数据落盘与store.persist.clear()清空已持久化数据等 API见 create-store.js。仓库中的 kanban 示例 就是createStore(persist(storeDefinition))的完整应用。等待重新水合完成useStoreRehydrated Suspense重新水合是异步的。可以使用useStoreRehydratedhook 确保水合完成后再渲染依赖持久化状态的组件。在 v7 中该 hook 在水合期间会suspend挂起因此需要把依赖的子树包在Suspense边界内import { Suspense } from react; import { useStoreRehydrated } from easy-peasy; const store createStore(persist(model)); function Main() { useStoreRehydrated(); return App /; } function Root() { return ( Suspense fallback{divLoading.../div} Main / /Suspense ); }React 会在水合完成前展示 fallback完成后渲染真实内容。从 hooks.js 的源码可以看到useStoreRehydrated内部通过use(store.persist.resolveRehydration())等待水合 Promise 完成。v7 破坏性变更提醒在 v6 及更早版本中该 hook 返回布尔值水合中为false、完成后为true调用方通常写isRehydrated ? Main / : Loading /。v7 改为挂起语义请改用Suspense fallback{Loading /}实现同样的等待门详见 use-store-rehydrated.md 与 upgrading-from-v6。进阶让 Quick Start 升级到 TypeScript以上示例均为 JavaScript 写法。若使用 TypeScript可以通过createTypedHooks生成类型完备的 hooks。仓库 simple-todo 示例 展示了标准做法import { createStore, createTypedHooks } from easy-peasy; import todosStore, { TodosModel } from ./model; const store createStoreTodosModel(todosStore); const typedHooks createTypedHooksTodosModel(); export const useStoreActions typedHooks.useStoreActions; export const useStoreDispatch typedHooks.useStoreDispatch; export const useStoreState typedHooks.useStoreState; export default store;在模型定义中用Action、Thunk、Computed等泛型标注各个属性见 simple-todo/src/store/model.tsexport interface TodosModel { todos: Todo[]; completedTodos: Computedthis, Todo[]; remainingTodos: Computedthis, Todo[]; addTodo: Actionthis, Todo; toggleTodo: Actionthis, Todo; saveTodos: Thunkthis, Todo[]; }这样组件中的useStoreState、useStoreActions都会获得完整的类型推导。更详细的 TypeScript 用法可参考 TypeScript 教程 与 typescript-api 文档。总结从 quick-start.md 出发我们完整走完了 Easy Peasy 的核心 API 链路createStore定义模型 →StoreProvider注入应用 →useStoreState读取状态 →action同步更新 →thunk异步副作用 →computed派生数据 →persistuseStoreRehydrated持久化。整个过程没有任何 reducer 样板代码所有状态更新都通过直接 mutation 表达并由 Immer 在底层转换为不可变更新computed 与持久化也由框架在内部完成优化。如果你想继续深入推荐按顺序阅读仓库中的进阶文档primary-api 教程、extended-api 教程并对照 action、thunk、computed、persist 等 API 文档以及 createStore 与 store-config 中关于配置项的完整说明。仓库根目录的 README.md 也提供了简明扼要的总览帮助你把 Quick Start 学到的能力应用到实际项目中。赞分享前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载相关推荐如何快速上手Uncle小说5分钟安装与配置完整教程如何快速上手Uncle小说5分钟安装与配置完整教程 Uncle小说是一款功能强大的PC版全网小说下载器及阅读器支持文本小说与有声小说可下载mobi、epu桌面应用Cursor AI 权限管理工具解锁智能编程助手的完整潜力Cursor AI 权限管理工具解锁智能编程助手的完整潜力 在当今快速发展的AI编程领域Cursor AI已经成为众多开发者不可或缺的智能助手。然而许多用开发工具CLIJeecgBoot 入门教程5 分钟搭建一套可上线的企业级低代码系统JeecgBoot 入门教程5 分钟搭建一套可上线的企业级低代码系统 JeecgBoot 是一款 Java 企业级 AI 低代码平台一句话生成前后端代码、表低代码后端前端AI 应用大模型RAG工作流自动化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站