多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

30 Seconds of Code:编写可读性更强的 Redux Reducer 实战指南

30 Seconds of Code:编写可读性更强的 Redux Reducer 实战指南 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载本文围绕 30 Seconds of Code 仓库中 React 文章集合里的 redux-readable-reducers 一文展开系统讲解如何把日渐膨胀、难以维护的 Redux reducer 重构为类型常量集中、Action 结构统一、业务逻辑按单一职责拆分的可读代码。读完本文你将掌握一套可直接落地的 reducer 重构路线图并了解本仓库中与之配套的测试实践。说明本文示例基于 Redux其中描述的问题在 Redux 中更为常见。但由于这些问题并不局限于 Redux如果你正在为代码的复杂度、可读性和可测试性而头疼文中的思路与解法同样适用于其他状态管理方案。一、从一个典型的 Redux Reducer 说起在处理状态state时我们常常会遇到三类问题复杂度难以控制、代码可读性下降、测试难以编写。很多时候这些问题并不难解决——只要退一步找到问题的根源。先看一个典型的 Redux reducer 示例下文所有改进都围绕它展开请先理解它再继续阅读const initialState { id: null, name: , properties: {}, }; const generateID () Math.floor(Math.random() * 1000); const reducer (state initialState, action) { switch (action.type) { case createID: return { ...state, id: generateID(), }; case setName: return { ...state, name: action.name, }; case addProperty: return { ...state, properties: { ...state.properties, [action.propertyName]: action.propertyValue, }, }; case removeProperty: return { ...state, properties: Object.keys(state.properties).reduce((acc, key) { if (key ! action.propertyName) acc[key] state.properties[key]; return acc; }, {}), }; default: return state; } };这个 reducer 管理着一个简单实体一个可生成的id、一个可修改的name以及一组可增删的properties。当前代码量不大但已经隐约露出了几处隐患。二、识别问题复杂度、心智负担与硬编码对上面的示例进行分析可以发现三类典型的坏味道1. 每个 action 的逻辑都嵌套在 reducer 内复杂度随 action 数量线性膨胀。当前代码并不算复杂但随着应用需要处理更多 action typeswitch分支会越来越多reducer函数体持续膨胀可读性快速恶化。2. 每个 action 的结构不一致增加维护者的认知负担。示例中setName依赖action.name而addProperty/removeProperty依赖action.propertyName与action.propertyValue。后续维护者必须记住每个 action 各自携带哪些键心智负担很大而且不同 action 键名散落各处也容易拼错。另一个潜在隐患是当action.type本身也需要作为数据写入 state例如 state 里需要存一个type字段时type这个命名空间被占用会带来命名冲突的困扰。3.action.type的值被硬编码在 reducer 内部。createID、setName这类字符串散落在 reducer 里在其它文件、组件中派发 action 时难以记忆、难以同步改一处漏一处是常见事故。这看起来是最小的问题却也是最容易修复的因此我们先从它入手。三、第一步提取 Action Types 常量解决硬编码字符串最直接的方式是把所有action.type取值提取到一个集中定义的对象中。这样既能消除拼写错误又能让 dispatch 端与 reducer 端共享同一份常量定义const ACTION_TYPES { CREATE_ID: createID, SET_NAME: setName, ADD_PROPERTY: addProperty, REMOVE_PROPERTY: removeProperty };把常量集中到一处之后reducer 与组件中的 dispatch 都引用ACTION_TYPES.XXX类型值的变化只需修改一处。这也是本仓库中 redux-readable-reducers 一文首推的改动成本最低、收益立竿见影。四、第二步统一 Action 结构payload 约定示例中各个 action 对象除了共享type键外其余结构五花八门。为了降低记忆负担、减少踩坑应统一所有 action 的结构——把整个 action 的数据负载放到顶层payload键下所有传给 action 的值都嵌套其中// 传入 reducer 函数的任意 action 的统一结构 const action { // 任意一个已定义的 action type type: ACTION_TYPES.CREATE_ID, // 将 name、propertyValue、propertyKey 等全部嵌套进该对象 payload: { /* ... */ } }例如setName的 payload 为{ name }addProperty的 payload 为{ propertyName, propertyValue }。刚接入这种结构时可能会觉得不直观多了一层嵌套但请先耐心看下去——它正是下一步“抽取嵌套逻辑”能顺利实施的前提。这种{ type, payload }的约定与 Redux 社区常见的 Flux Standard ActionFSA规范思路一致所有 action 对外只暴露type与payload两个顶层键状态更新的数据来源变得可预期。五、第三步抽取嵌套逻辑为单一职责函数前面两步改造最终服务于最关键的一步——把原先嵌套在switch分支中的逻辑抽成各自独立的纯函数。每个case对应一个函数函数只负责自己那一个 action type 的状态更新const createID state ({ ...state, id: generateID(), }); const setName (state, { name }) ({ ...state, name, }); const addProperty (state, { propertyName, propertyValue }) ({ ...state, properties: { ...state.properties, [propertyName]: propertyValue, }, }); const removeProperty (state, { propertyName }) { const properties Object.keys(state.properties).reduce((acc, key) { if (key ! propertyName) acc[key] state.properties[key]; return acc; }, {}); return { ...state, properties }; };注意setName、addProperty、removeProperty都直接对payload进行解构取值如{ name }、{ propertyName, propertyValue }这正是第四节统一payload结构带来的直接红利函数签名整齐划一全部是(state, payload)。每个函数只承担一个职责与某个 action type 相关的全部复杂度都被收拢进对应的小函数中。测试这些小型纯函数也远比测试一个臃肿的 reducer 容易——它们聚焦于单一任务没有嵌套在庞大的switch里输入输出清晰、可独立验证。六、整合重构后的完整代码将上述三步改动合并最终代码如下const initialState { id: null, name: , properties: {}, }; const ACTION_TYPES { CREATE_ID: createID, SET_NAME: setName, ADD_PROPERTY: addProperty, REMOVE_PROPERTY: removeProperty }; const generateID () Math.floor(Math.random() * 1000); const createID state ({ ...state, id: generateID(), }); const setName (state, { name }) ({ ...state, name, }); const addProperty (state, { propertyName, propertyValue }) ({ ...state, properties: { ...state.properties, [propertyName]: propertyValue, }, }); const removeProperty (state, { propertyName }) { const properties Object.keys(state.properties).reduce((acc, key) { if (key ! propertyName) acc[key] state.properties[key]; return acc; }, {}); return { ...state, properties }; }; const reducer (state initialState, action) { switch (action.type) { case ACTION_TYPES.CREATE_ID: return createID(state, action.payload); case ACTION_TYPES.SET_NAME: return setName(state, action.payload); case ACTION_TYPES.ADD_PROPERTY: return addProperty(state, action.payload); case ACTION_TYPES.REMOVE_PROPERTY: return removeProperty(state, action.payload); default: return state; } };重构后的reducer变成了一个纯粹的分发器dispatcher只负责把action.type映射到对应的处理函数不再包含任何具体业务逻辑具体逻辑全部下沉到createID、setName、addProperty、removeProperty四个单一职责函数中各自的复杂度被有效隔离。小提示保持命名一致。原文档最终示例中常量引用写作TYPES.xxx、createId(state, ...)与前面定义的ACTION_TYPES和createID存在命名不一致。上述版本统一为ACTION_TYPES与createID读者在实际项目中也应确保常量名、函数名的大小写风格全局一致避免同样的困惑。七、仓库内的配套实践与延伸阅读在 30 Seconds of Code 仓库中这篇 reducer 文章归属于 React 文章集合见 content/collections/react/index.yaml并且与它配套的还有一篇 Redux 主题的测试文章。1. 让拆分后的 reducer 更好测。拆分出单一职责函数后每个状态更新函数都可以作为纯函数单独断言。本仓库的 testing-redux-connected-components 一文展示了如何在组件层配合 Redux store 测试——它通过createStore(reducer, initialState)创建测试 store再用 React Testing Library 的render与Provider包裹被测组件从而在真实 reducer 参与的情况下验证 UI 行为。如果你的 reducer 已经在本文的路线下被拆分成小函数那么组件测试中传入的initialState与 dispatch 后的状态断言都会更加直观。2. 了解文章的来源与路由。这篇 reducer 文章最早发布在/articles/s/react-redux-readable-reducers路径下仓库通过 content/redirects.yaml 将其 301 重定向到当前的 redux-readable-reducers保证历史链接依然有效。3. 参考文章的元数据格式。该文章使用仓库统一的 snippet 前置元数据格式参考 content/snippets/react/snippet-template.md包括title、shortTitle、language: react、tags: [logic]、excerpt、listed与dateModified等字段方便搜索引擎与站点列表如 React 集合中的 testing.yaml自动聚合与检索。总结Redux reducer 的复杂度问题不是靠“更小心地写”就能避免的而应通过结构性改进根治提取ACTION_TYPES常量消灭硬编码字符串统一{ type, payload }的 action 结构降低维护者的记忆负担将每个 case 抽取为单一职责函数让 reducer 退化为纯分发器业务逻辑各归其位。三步改造之后reducer 的可读性、可维护性与可测试性都会显著提升配合本仓库中关于 Redux 组件测试的配套实践可以形成一套完整的状态管理开发与验证闭环。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐30-seconds-of-code 实战编写一份专业可用的 CSS 打印样式表Print Stylesheet30 seconds of code 实战编写一份专业可用的 CSS 打印样式表Print Stylesheet 打印网页内容虽不常见但 打印样式表p教程文档30 Seconds of Code 实战指南写出高质量 Pull Request 的 5 个技巧30 Seconds of Code 实战指南写出高质量 Pull Request 的 5 个技巧 写出一手好代码只是工作的一半。本文基于 30 Second教程文档30 seconds of code 文章编写规范读懂 snippet-template 内容模板与 Frontmatter30 seconds of code 文章编写规范读懂 snippet template 内容模板与 Frontmatter 30 seconds of co教程文档上一篇终极Winlator性能优化指南如何让Android手机流畅运行Windows游戏下一篇MOOTDX实战宝典5个痛点场景的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表