摘要:本周精读内容是重新思考。数据流对数据缓存,性能优化,开发体验优化都有进一步施展的空间,拥抱插件生态是一个良好的发展方向。
本周精读内容是 《重新思考 Redux》。
1 引言《重新思考 Redux》是 rematch 作者 Shawn McKay 写的一篇干货软文。
dva 之后,有许多基于 redux 的状态管理框架,但大部分都很局限,甚至是倒退。但直到看到了 rematch,总算觉得 redux 社区又进了一步。
这篇文章的宝贵之处在于,抛开 Mobx、RXjs 概念,仅针对 redux 做深入的重新思考,对大部分还在使用 redux 的工程场景非常有帮助。
2 概述比较新颖的是,作者给出一个公式,评价一个框架或工具的质量:
工具质量 = 工具节省的时间/使用工具消耗的时间
如果这样评估原生的 redux,我们会发现,使用 redux 需要额外花费的时间可能超过了其节省下来的时间,从这个角度看,redux 是会降低工作效率的。
但 redux 的数据管理思想是正确的,复杂的前端项目也确实需要这种理念,为了更有效率的使用 redux,我们需要使用基于 redux 的框架。作者从 6 个角度阐述了基于 redux 的框架需要解决什么问题。
简化初始化redux 初始化代码涉及的概念比较多,比如 compose thunk 等等,同时将 reducer、initialState、middlewares 这三个重要概念拆分成了函数方式调用,而不是更容易接受的配置方式:
const store = preloadedState => { return createStore( rootReducer, preloadedState, compose(applyMiddleware(thunk, api), DevTools.instrument()) ); };
如果换成配置方式,理解成本会降低不少:
const store = new Redux.Store({ instialState: {}, reducers: { count }, middlewares: [api, devTools] });
笔者注:redux 的初始化方式非常函数式,而下面的配置方式就更面向对象一些。相比之下,还是面向对象的方式更好理解,毕竟 store 是一个对象。instialState 也存在同样问题,相比显示申明,将 preloadedState 作为函数入参就比较抽象了,同时 redux 对初始 state 的赋值也比较隐蔽,createStore 时统一赋值比较别扭,因为 reducers 是分散的,如果在 reducers 中赋值,要利用 es 的默认参数特性,看起来更像业务思考,而不是 redux 提供的能力。简化 Reducers
redux 的 reducer 粒度太大,不但导致函数内手动匹配 type,还带来了 type、payload 等理解成本:
const countReducer = (state, action) => { switch (action.type) { case INCREMENT: return state + action.payload; case DECREMENT: return state - action.payload; default: return state; } };
如果用配置的方式设置 reducers,就像定义一个对象一样,会更清晰:
const countReducer = { INCREMENT: (state, action) => state + action.payload, DECREMENT: (state, action) => state - action.payload };支持 async/await
redux 支持动态数据还是挺费劲的,需要理解高阶函数,理解中间件的使用方式,否则你不会知道为什么这样写是对的:
const incrementAsync = count => async dispatch => { await delay(); dispatch(increment(count)); };
为什么不抹掉理解成本,直接允许 async 类型的 action 呢?
const incrementAsync = async count => { await delay(); dispatch(increment(count)); };
笔者注:我们发现 rematch 的方式,dispatch 是 import 进来的(全局变量),而 redux 的 dispatch 是注入进来的,乍一看似乎 redux 更合理,但其实我更推崇 rematch 的方案。经过长期实践,组件最好不要使用数据流,项目的数据流只用一个实例完全够用了,全局 dispatch 的设计其实更合理,而注入 dispatch 的设计看似追求技术极致,但忽略了业务使用场景,导致画蛇添足,增加了不必要的麻烦。将 action + reducer 改为两种 action
redux 抽象的 action 与 reducer 的指责很清晰,action 负责改 store 以外所有事,而 reducer 负责改 store,偶尔用来做数据处理。这种概念其实比较模糊,因为往往不清楚数据处理放在 action 还是 reducer 里,同时过于简单的 reducer 又要写 action 与之匹配,感觉过于形式化,而且繁琐。
重新考虑这个问题,我们只有两类 action:reducer action 与 effect action。
reducer action:改变 store。
effect action:处理异步场景,能调用其他 action,不能修改 store。
同步的场景,一个 reducer 函数就能处理,只有异步场景需要 effect action 处理掉异步部分,同步部分依然交给 reducer 函数,这两种 action 职责更清晰。
不再显示申明 action type不要在用一个文件存储 Action 类型了,const ACTION_ONE = "ACTION_ONE" 其实重复写了一遍字符串,直接用对象的 key 表示 action 的值,再加上 store 的 name 为前缀保证唯一性即可。
同时 redux 建议使用 payload key 来传值,那为什么不强制使用 payload 作为入参,而要通过 action.payload 取值呢?直接使用 payload 不但视觉上减少代码数量,容易理解,同时也强制约束了代码风格,让建议真正落地。
Reducer 直接作为 ActionCreatorredux 调用 action 比较繁琐,使用 dispatch 或者将 reducer 经过 ActionCreator 函数包装。为什么不直接给 reducer 自动包装 ActionCreator 呢?减少样板代码,让每一行代码都有业务含义。
最后作者给出了一个 rematch 完整的例子:
import { init, dispatch } from "@rematch/core"; import delay from "./makeMeWait"; const count = { state: 0, reducers: { increment: (state, payload) => state + payload, decrement: (state, payload) => state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } }; const store = init({ models: { count } }); dispatch.count.incrementAsync(1);3 精读
我觉得本文基本上把 redux 存在的工程问题分析透彻了,同时还给出了一套非常好的实现。
细节的极致优化首先是直接使用 payload 而不是整个 action 作为入参,加强了约束同时简化代码复杂度:
increment: (state, payload) => state + payload;
其次使用 async 在 effects 函数中,使用 this.increment 函数调用方式,取代 put({type: "increment"})(dva),在 typescript 中拥有了类型支持,不但可以用自动跳转代替字符串搜索,还能校验参数类型,在 redux 框架中非常难得。
最后在 dispatch 函数,也提供了两种调用方式:
dispatch({ type: "count/increment", payload: 1 }); dispatch.count.increment(1);
如果为了更好的类型支持,或者屏蔽 payload 概念,可以使用第二种方案,再一次简化 redux 概念。
内置了比较多的插件rematch 将常用的 reselect、persist、immer 等都集成为了插件,相对比较强化插件生态的概念。数据流对数据缓存,性能优化,开发体验优化都有进一步施展的空间,拥抱插件生态是一个良好的发展方向。
比如 rematch-immer 插件,可以用 mutable 的方式修改 store:
const count = { state: 0, reducers: { add(state) { state += 1; return state; } } };
但是当 state 为非对象时,immer 将不起作用,所以最好能养成 return state 的习惯。
最后说一点瑕疵的地方,reducers 申明与调用参数不一致。
Reducers 申明与调用参数不一致比如下面的 reducers:
const count = { state: 0, reducers: { increment: (state, payload) => state + payload, decrement: (state, payload) => state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } };
定义时 increment 是两个参数,而 incrementAsync 调用它时,只有一个参数,这样可能造成一些误导,笔者建议保持参数对应关系,将 state 放在 this 中:
const count = { state: 0, reducers: { increment: payload => this.state + payload, decrement: payload => this.state - payload }, effects: { async incrementAsync(payload) { await delay(); this.increment(payload); } } };
当然 rematch 的方式保持了函数的无副作性质,可以看出是做了一些取舍。
4 总结重复一下作者提出工具质量的公式:
工具质量 = 工具节省的时间/使用工具消耗的时间
如果一个工具能节省开发时间,但本身带来了很大使用成本,在想清楚如何减少使用成本之前,不要急着用在项目中,这是我得到的最大启发。
最后感谢 rematch 作者精益求精的精神,给 redux 带来进一步的极致优化。
5 更多讨论讨论地址是:精读《重新思考 Redux》 · Issue #83 · dt-fe/weekly
如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/94948.html
摘要:引言本周精读的文章是,看看作者是如何解释这个多态性含义的。读完文章才发现,文章标题改为的多态性更妥当,因为整篇文章都在说,而使用场景不局限于。更多讨论讨论地址是精读的多态性如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。 1 引言 本周精读的文章是:surprising-polymorphism-in-react-applications,看看作者是如何解释这个多态性含...
摘要:更容易将组件的与状态分离。也就是只提供状态处理方法,不会持久化状态。大体思路是利用共享一份数据,作为的数据源。精读带来的约定函数必须以命名开头,因为这样才方便做检查,防止用判断包裹语句。前端精读帮你筛选靠谱的内容。 1 引言 React Hooks 是 React 16.7.0-alpha 版本推出的新特性,想尝试的同学安装此版本即可。 React Hooks 要解决的问题是状态共享,...
摘要:大公司广泛使用的开源库,并且有一定国际影响力,而且大厂也有成功开源历史经验的话,就会增加说服力。总结下次技术选型讨论时,可以拿出规则一条一条比对了然后技术选型只是基础库,利用这些基础可以维护好自己的开源库,把更多时间用在创造业务价值上。 1 引言 作者给出了从 12 个角度全面分析 JS 库的可用性,分别是: 特性。 稳定性。 性能。 包生态。 社区。 学习曲线。 文档。 工具。 发...
摘要:本周精读内容是逃离地狱。精读仔细思考为什么会被滥用,笔者认为是它的功能比较反直觉导致的。同时,笔者认为,也不要过渡利用新特性修复新特性带来的问题,这样反而导致代码可读性下降。 本周精读内容是 《逃离 async/await 地狱》。 1 引言 终于,async/await 也被吐槽了。Aditya Agarwal 认为 async/await 语法让我们陷入了新的麻烦之中。 其实,笔者...
阅读 1749·2021-09-28 09:43
阅读 1110·2021-09-23 11:22
阅读 2706·2021-09-14 18:05
阅读 1822·2019-08-30 15:52
阅读 2811·2019-08-30 10:55
阅读 2006·2019-08-29 16:58
阅读 1322·2019-08-29 16:37
阅读 3030·2019-08-29 16:25