重新设计 Redux

Redux 学习起来很困难?写起代码来很啰嗦?
一起来看看 Rematch 的作者对 Redux 的思考和简化吧~

原文:《Redesigning Redux》, Shawn McKay

都过了这么多年了,状态管理的问题难道不应该早就被解决了么?
个人直觉,开发者似乎都承认一个潜规则,那就是状态管理问题似乎比想象的更复杂。在本文,我们将一起探讨你可能已经遇到的问题:

  • 我们真的需要一个状态管理库么?
  • Redux 的流行是否实至名归?为什么?
  • 我们是否能够提出更好的状态管理方案?如果是,怎么做?

真的需要状态管理库?

前端开发并不仅仅是左右移动像素点。开发的真正艺术是掌握如何管理状态。
简单粗暴的答案是:状态管理是复杂的,但是并非那么复杂。

以基于组件的视图框架/库为例,比如 React,让我们来看看我们有哪些选择:

State Options

1. 组件状态

存在于单个组件内部的状态。在 React 中,就是指使用 setState 来更新的 state

2. 相关状态

父组件传递给子组件的状态。在 React 中,就是指通过属性传递给子组件的 props

3. 供给状态

保存在根提供者中、通过组件树传递给消费者的状态。在 React 中,对应于 context API

大多数的状态都是存在于视图中的,因为它是用来反映用户界面的。那么,对于反映底层数据和逻辑的其它状态,又属于谁呢?

如果把所有的状态都填塞到视图中,那么就严重违背了关注点分离原则。它会把你牢牢地绑在一个 JS 视图库中,使得代码难以测试。更有甚者,可能会为你带来大麻烦,因为你必须持续不断的思考和调整状态的存储之处。

由于架构设计的改变,状态管理会随之变得复杂,并且通常很难判断哪些组件需要哪些状态。最简单的做法是,在根组件上包含所有状态。如果真要这么做的话,那么选用下一种方式会更好。

4. 外部状态

状态是可以从视图库中移出来的,然后可以使用提供者/消费者模式把状态重新连接回视图库。

Redux 也许是最流行的状态管理库。它在过去的 2 年时间里取得了巨大的流行度。那么,为什么一个简单的库会获得如此之多的关注呢?

是因为它的性能更高么?其实并不是。实际上,它反而让每一个必须处理的回调变得更慢了些。

那是因为它更简单?也决定不是。

论简单的话,那么纯 JS 才是。正如 TJ 所说:

TJ Twitter

那为什么大家不直接使用 global.state = {} 呢?

为什么使用 Redux

本质上,Redux 跟 TJ 所说的是同一件事,只不过 Redux 封装了一些管道工具而已。

Redux Store Pipeline

在 Redux 中,我们不能直接修改状态。修改状态的唯一方式是分发(Dispatch)一个动作(Action)到管道中,管道会自动根据动作去更新状态。

从上图可以看到,管道的中有 2 个监听器集合:中间件(Middleware)和订阅(Subscription)。中间件一些是监听动作的函数,用来处理类似日志记录、开发工具和发起服务请求等功能。订阅则是一些用来广播状态变更的函数。

最后,合成器(Reducer)函数负责把状态变更拆分成更小、更模块化、更容易管理的代码块。

和使用一个全局对象相比,Redux 确实简化了开发过程。

综上,我们可以把 Redux 看成是一个全局对象,该对象不仅提供了状态更新前/后钩子,而且能够以简单的方式合成新状态。

不觉得 Redux 过于复杂么?

的确过于复杂。在平时的开发过程中,有一些不可否认的迹象,可以用来判断框架 API 是否需要改进。这些迹象可以归纳为下面这个公式:

Quality of API

其中,节省的时间是指使用该框架来开发所消耗的时间,学习的时间则是阅读框架文档、学习教程和掌握新概念的总时间。

Redux 本身是个精简的库,但是其学习曲线却很陡峭。虽然有不少开发者能够克服深入学习函数式编程的困难并从 Redux 获益良多,但是也有很多开发者望而却步,宁愿重新使用 jQuery。

在 jQuery 中,你并不需要理解什么是 “comonad”,也没必要理解通过函数组合来管理状态。

任何框架或者库的目的都应该是把复杂的事物抽象得更加简单。

当然我这么说并不是想指责 Redux 的作者 Dan Abramov 。Redux 在其刚诞生初期就已经变得非常流行,没有足够的时间来精雕细琢。

  • 我们怎么能随便重构一个已经被成千上万开发者使用的库呢?
  • 我们又怎么能合理发布会影响到不计其数项目的重大变更呢?

没人可以。但是我们可以提供更好的支持,比如完善文档、推广视频教程和扩大社区等。在这些方面,Dan Abramov 确实做得很棒。

又或者,还有另一种方式可以改变现状。

重新设计 Redux

在我看来,重写 Redux 是有其必要性的,至少有以下 6 个方面可以改进得更友好。

1. 初始化过程

让我们来看看一个基本的 Redux 初始化过程,如下图左边所示:

setup

很多开发者走到这里一步就开始止步了,简直是一看三不知。什么是 chunkcompose 又是什么鬼?一个函数还能这么使用?

如果 Redux 是基于配置而不是函数组合的话,那么像右边那样的初始化过程明显看起来更加合理。

2. 简化状态合成器

Redux 中的状态合成器能够使用一个 switch 来代替多个不必要的 switch

reducers

假如状态合成器是根据动作的类型来匹配的,那么我们可以用逆向思维,把合成器变成一个接受 stateaction 两个参数的纯函数。也许还可以更简单些,我们可以把动作规划化,并且只传递状态和一个数据载荷。

3. 使用 Async/Await 代替 Thunks

在 Redux 中,Thunks最通用的做法就是用来创建异步动作。从多角度来看,这种用法更像是一个聪明的黑客所采用的用法,而不是一种官方推荐的用法。我们一步一步来看:

  1. 首先分发了一个动作,然而实际上却是一个函数而不是期望的对象
  2. Thunk 中间件检查每一个动作,看它是否是一个函数
  3. 如果是函数,那么中间件调用该函数,同时把 dispatchgetState 方法传参进去

真的需要这样么?把一个简单的动作在运行时识别为对象、函数甚至是 Promise,这难道不是糟糕的实践么?

async & await

如上图右边所示,难道我们就不能只使用 async/await ?

4. 两种类型的动作

如果我们认真想想的话,确实有两种类型的动作:

  1. 合成器动作(Reducer action): 触发合成器然后改变状态
  2. 副作用动作(Effect action):触发一个异步动作。这可能也称为合成器动作,但是异步函数其实并没有直接改变任何状态。

因此,把这两种类型的动作区分开来会更有用,同时也不容易与 Thunks 搞混。

5. 不再需要定义动作类型变量

为什么我们的标准实践要把动作生成器和状态合成器区分开来呢?能否只用其中一个呢?改变其中一个又是否会影响到另一个?

在我看来,动作生成器和状态合成器就是硬币的两个面。

const ACTION_ONE = 'ACTION_ONE' 可以说是把动作生成器和状态合成器分开的冗余产物。如果我们把这两者合二为一,那么就不会有那么多文件专门导出这些类型字符串了。

6. 合二为一的合成器

按照使用方式,把 Redux 中所涉及的概念进行合并分组,那么我们可以得出下面这个更简单的模式。

simpler pattern

在这种模式中,状态合成器是可以自动确定与之对应的动作生成器。因为,此时状态合成器可以自动变成动作生成器。

通过简单的命名约定,以下行为都会变得可预测:

  1. 如果状态合成器命名为 increment,那么动作类型就是 increment。更好的做法是加上命名空间: count/increment
  2. 每个动作都通过 payload 属性来传递数据。

action creator

这样的话,对于 count.increment,我们就可以自动的从状态合成器推导出动作生成器。

好消息:更棒的 Redux

以上的通电就是我们创建 Rematch 的原因。

rematch

Rematch 对 Redux 进行了封装,提供更简单的 API,但又不失任何可配置性的特点。

rematch model

下面是一个完整的使用例子:

rematch example

我已经把 Rematch 应用在生成环境中有好几个月了。作为小白鼠,我的体验是:

状态管理从未变得如此简单、高效。

Redux 并没有被抛弃,而且也不应该被抛弃。
只是,我们应该以更低的学习成本,更少的样板代码和更少的认知成本,来拥抱 Redux 背后的简单哲学。

赶紧试一试 Rematch 吧!
万一一不小心你就爱上它了呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值