一个库的诞生一般情况下都是为了解决某个特定的问题,但更多情况下它会承受它所不能承受的压力
--某前端萌新
话说什么时候要用 redux 呢
虽然某作者曾经说过 If you are not sure whether you need it, you probably not.,但如今的 2020 年我只想说一句,大人时代变了 orz
先大概了解一下 redux 做了什么事情吧,先盗一张图

这应该是一张被嫖了八百遍的图了,即使不是这张应该也在什么地方看到过类似的,一个非常经典的 flux 架构图
虽然看图已经可以很清楚的弄明白 redux 的基本流程,但由于某些原因,还是想很冗余的描述一下相关过程(笑
对没错,这个原因就是为了水字数~
redux 的数据流是单向的,虽然很多人都说过,但这么重要的事情说一百遍也不为过,因为单向的数据流可以让 redux 做到一个很重要的事情。划重点啦,那就是 1.时序可控,这个词就当是我自创对好了,如有雷同纯属巧合~
接下来仔细观察这么一个数据流,总结成一句话就是 只能通过 dispatch action 的方式去更新 store,view 只能展示 store 中的数据 对于这张图上所标注的,也可以通过 view 去 dispatch action 实际上并不是什么重要的事情,可以转换一种说法就是 dispatch action 的方式并不会影响整个数据流正常工作,你甚至可以直接在浏览器控制台去 dispatch action,实际上偶尔也会这么做 orz
也正式因为单向数据流这种略微有些复杂的特性,换句话说就是天然的松耦合架构,非常适合用来做 2.项目工程化 相关的东西,架构级别的强制规范限制了自由操作数据的方式~
实际上只是这两个特性已经足够可以让我们在任何项目中都使用 redux,唯一的顾虑只是当前项目期望的工程化程度,以及 redux 和相关工具链这一丢丢的代码体积 orz
最后是在 react 中使用 redux 的场景下,嘛,实际的工程一定会比理论模型复杂很多很多,如果简单的将理论模型的 view 看作是 react component 的话,那 redux 的另外一个好处就是 3.数据中心化,换句话说就是,虽然 react 的不同组件是有嵌套级别的,但在数据获取上大家都是平级的。嵌套和参数透传在任何场景下都是让人感觉到很麻烦的事情,redux 可以换种方式去解决这个问题~
可能还是要说明一下,因为 redux 提供了极高的自由度,所以这篇文章对于 redux 的用法有着极为浓重的个人色彩,redux 本身只是 flux 模型的一个实现,而 flux 的 store 在存储数据方面又是极为自由的 orz
大概可以这个样子来食用 redux
如果想知道某种库的标准食用方式,那么官方文档一定是最正确的途径 = = react-redux.js.org/
因为网上搜到的所有攻略,都可能存在滞后性,只有官方文档才是最靠谱的攻略 orz
所以我还是简单的说一下某种思路吧,就是如果要去食用 redux,总有那边一些必须要写的东西,毕竟是基于 flux 架构,然后发现需要什么就去翻文档找到对应的处理方式(有了 ts 之后越来越懒得翻文档了,大多数时候连蒙带猜就知道要怎么用了
- 首先,你肯定需要一个 store,所以去找一个
createStore或者configureStore的函数,总之会有一个函数可以生成一个 store 让你使用 - 另外,你可能需要一个容器去存放 store,这样可以让任何一个 view 都可以拿到
dispatcher和store,当然,这个在 react 中是必须要写的,就是那个需要在Provider中设置一个 store 的那个东西,然后可以通过connect或者useSelector/useDispatch拿到相对应的工具 - 好了,对于上面那张图,好像只缺了 action,因为 action 已经是一种很具体的业务实现,所以 action 经常会承受它所不能承受的重量
- 简单起见,就让 action 只做更新 store 的操作,在 react 中大概是这么个样子
{ type: "TEMP_ACTION_ADD_COUNT" },为了简单所以就是一个异常纯净的 action - 突然发现现在还只有一个空的 store,所以需要来初始化一个 store 的模块吧,在 react 中它的名字叫 state,描述更新逻辑的东东叫 reducer,
const initState = { count: 1 } - reducer 的具体使用方式还是看文档吧,这是一个萌新程序员的必备技能 orz,一个最简化版的 reducer 可以这么写
const reducer = (state = initState, action) => action.type === "TEMP_ACTION_ADD_COUNT" ? { ...state, count: state.count + 1} : state - 是不是还缺了什么呢?对,还没有把写好的 reducer 塞到 store 中,所以按照官方文档的方式把 reducer 塞进去就好了 orz
虽然这段代码我并没有直接运行过,但或许可能应该大概是可以正常使用的
PS:我并没有看过 redux 相关源码,以上纯属瞎猜
来一张草图简单说明一下基本的数据流向,不要在意细节 = =

不会吧不会吧,不会真的有人能看懂我想要表达什么吧,一些十分明显的直接引用就不做标注了,都说了是草图了 orz
如果哪位小伙伴很有好奇心的话,可以发现 redux 和 react-redux 是两个库,因为描述数据更新和在哪里使用这些数据本身就是一件耦合很低的事情(大概
好像还缺了什么?没错是让 redux 封神的 middleware(纯个人看法),然后我也并没有打算废话要如何写一个 middleware,但手写 middleware 也算是一个基础艺能(手动狗头
到底要用 redux 解决什么问题啊喂
有木有小伙伴还记得最开始说的,什么时候要用 redux 呢?其实这里要说的是同一个问题
对于一个 react 的基本概念来说,react 是很希望 ui 组件可以保持一定的纯净性,大概的感觉就是以声明式的写法来描述 ui 状态,所有 ui 的变化都会是依赖于某些数据的变化,这大概就是传说中的数据驱动吧(笑
我感觉可能要来对比一下有木有使用 redux 在操作异步数据时候的感觉

在组件 ui 状态一致,不做任何封装的情况下,ui 层面的写法大概会是这么个样子,实际情况大概率要比这个复杂很多,如果封装做的不好的话估计会更乱 = =
可以很容易看出,在 ui 层的写法,引入 redux 会比不引入要简洁了很多,所以要说引入 redux 会比不引入的代码更简洁吗?并不是
如果只是这种个例的话,引入 redux 之后的 ui 组件,实际上只描述了 ui 相关的逻辑,但不引入的话,这个组件不仅描述了 ui 逻辑,而且还描述了相关数据的业务逻辑,这里就会引出一个极其复杂的工程化问题,简单概括一下就是 处理业务复杂度这个过程本身就会增加业务复杂度,不过这里并不会对这个问题做太详细的讨论 orz
对,就是因为引入 redux 的成本很高(学习成本/业务复杂度成本),所以可能某些业务复杂度极低极低的项目,并不太合适引入 redux,仔细品味这里的极低两个字(笑
PS: 其实通过 hooks 抽象封装也可以做到 redux 类似的功能,只是类似,随着业务复杂度上升到达了 hooks 很难满足某些特定需求的时候,就需要做一个类似于 redux 的东西,所以为什么不一开始就直接用 redux 呢
好了,可以回到小标题上来了,redux 解决了什么问题呢?
一个很主观的看法,redux 提供了完全独立的数据抽象层,赋予了前端处理复杂数据的能力,说到底就是万一后端偷懒了,前端不至于代码崩得体无完肤,业务上的事情谁说的准呢(笑
建立在这个前提下,我理想中的 store 中,只会处理 ui 逻辑无关的数据业务逻辑,类似于 redux 和 react-redux 的这种感觉
那么问题来了,如果我想处理全局的 ui 状态怎么办呢?react 不是提供了一个小型的 reducer 么,ui 的事情全都交给 ui 库来处理就好了 orz (这也是一个纯主观看法
PSPS:数据层和ui层解耦的成本很高很高很高,对这也是一个工程化问题
业务复杂度不并会消失...
在引入 redux 之后,ui 层倒是清爽了很多,但这些必须要处理的业务逻辑去哪了呢?反正不会消失就对了
如果只看之前的 redux 工作流,看起来只能在 reducer 中处理相关的业务逻辑的样子,但是很多文章都会指出 reducer 要纯净纯净再纯净,即使做了 action creator 的封装,action 也很难处理复杂的业务逻辑,所以就需要自己来封装一个解析 action 的 middleware 了
middleware(emmmmmm),和 redux 差不多的感觉,只要你满足了它最基本的语法要求 const mddileware = store => next => action => next(action),它可以做任何你想让它做的事情~
什么?不封装可以吗?可以倒是可以了,但声明式代码更好维护不是嘛(手动狗头
这里并不打算讨论怎么写一个 middleware,只是想简单分析一下要怎么设计这样一个 action,毕竟最复杂的业务逻辑都要在 action 里面处理了,如果不好好做规划 action 只会变成另外一个 useEffect 或者其它让人一看到就非常头疼的代码
大多数前端业务,大多数时候都是在处理 http 请求,这种请求被我称之为 简单异步,只要是只有 http 请求的项目,都可以归类于简单异步中,所以哪些处理复杂异步用的 rxjs/saga 之类的,就暂时不用考虑了
所以 action 尽量要把异步/promise 相关的逻辑封装进去,即一个 action 应该是这个样子
const action = {
type: "TEST_TYPE",
meta: { xxx },
target: xxxApi.getXXX,
success: "TEST_SUCCESS",
failure: "TEST_FAILURE",
}
对,就像这样,把一个异步请求拍平了之后就会发现,哇,数据好多,还没开始处理业务逻辑就已经这么复杂了,好烦 T T 这个结构的优化,最多只能将 success 和 failure 合并为一个数组或者一个结构了吧大概,而且并不简单会比这种结构有优势(筋疲力尽的笑
你永远不知道业务想要往 action 里面塞什么东西,所以务必保证每一个字段的设置都具有一定的通用性 orz
action 的设计很难,因为要满足所有简单异步情况的声明式写法,而且存在另外一个非常严重的问题:action 封装的过少等于没封装,复杂业务逻辑还是暴露在外面;而封装的过多就会产生很多看起来非常‘魔幻’的代码,会很大程度的增加这个项目的学习成本甚至维护成本,当然啦,毕竟这里是处理复杂业务逻辑的地方,多少也要给点难度才能对得起核心的地位
如果不是要坚持声明式写法直接在 target 中 根据情况 dispatch action 不香嘛
可能只是感觉声明式写法更好维护一些,稍微从测试角度看一下,只需要测试了相关的 middleware 可以正常工作,剩下的每次修改只需要进行配置业务就完全 ok 了
当然不是说完全没有过程式逻辑,只是说可以提供一种方案可以将同类代码放在一起管理,这又是一个工程化问题(瘫
没办法,单独拉出来 redux 来看它只是一个全局状态的管理库而已,如果引入了 redux 的话,一般项目中的复杂度都在 redux 中,这并不是因为 redux 不好用,而是因为业务本身就是这么复杂,但原则上,如果有 埋点/数据缓存 相关的比较通用的业务逻辑的话,做一些公用的封装或许会好一些 orz
redux 不做封装真的太难用了
好吧,说了这么多,我只是想推一个自己的 redux 封装库而已
没有测试代码/没有经过工程验证/存在一些已知的 bug/生产环境不可用,这个库存在了这么多的问题,但我还是要推一下,因为没有好用的封装库/符合我期望的封装库,不然我真的懒得造这么一个轮子 = =
比较完善的是,ui 层的调用很优雅,写起来也很舒服,就像这样

好吧,其实里面还封装了很多乱七八糟的东西 immer/reselect 提升体验
action 还在补全 ing,虽然很久很久没有更新过这个玩具了,而且也木有文档,这里就先不吹了,等我补完了肯定吹爆(手动斜眼
本质上代码阅读体验差,嵌套是原罪,所以期望对异步的封装可以解决大部分的嵌套问题,把代码拍平,反正我很是希望我维护的业务代码中都是这种写法 orz
贴上链接,有生之年估计会写完 github.com/frontend-ki… ,storybook 估计可以跑起来玩一玩
脱离业务谈 redux 的用法等于是在开玩笑,对我就是喜欢开玩笑(笑
完结撒花~