Rematch: 重新设计 Redux

9 个月前

本文是借助网易见外的工具帮助完成翻译的,网易见外是基于NMT(神经网络机器翻译)技术的海外内容智能聚合平台 原文地址: https://hackernoon.com/redesigning-redux-b2baee8b8a38

到目前为止,状态管理不应该是一个已经解决了的问题吗?直觉上,开发者似乎知道一个隐藏的真相:状态管理似乎比它需要的更要难。这篇文章里,我们尝试回答一些你可能一直在问自己的问题:

  • 你真的需要一个状态管理库吗?
  • Redux的流行是否值得?为什么或者为什么不呢?
  • 我们能不能做一个更好的状态管理方案?如果能,怎么去做?

状态管理真的需要一个库吗?

作为一个前端开发人员,不仅仅是控制像素;真正的开发之美是知道在哪里存储你的状态。简而言之:这很复杂,但又不复杂。

让我们看看使用React等基于组件的视图框架/库时的选项:

image.07c531a2b833.png

1. Component State

存在于单个组件内部的状态。在React中,通过setState方法更新state

2. Relative State

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

3. Provider State

状态保存在根 provider (提供者) 组件中,并由 consumer (消费者) 在组件树的某个地方访问,而不考虑距离。在React中,通过context API可以实现。

很多状态都属于视图,因为它反映了UI。但是,其他反映你底层数据和逻辑的代码呢?

把所有内容都放到视图中会丧失关注点分离:它将你拴在到了某个javascript视图库上,这使得代码更难测试,可能最大的麻烦是:你必须不断思考和重新调整存储状态的位置。

由于设计经常改变,而且通常很难判断哪些组件需要哪个状态,所以状态管理变得复杂。最简单的选择就是从根组件中提供所有的状态,在这一点上,你最好选择下一个选项。

4. External State

状态可以移到视图库之外。然后,库可以使用提供者/消费者模式“连接”,以保持同步。

也许最受欢迎的状态管理库是Redux。在过去的两年里,它变得越来越受欢迎。那么,为什么大家对一个简单的库如此热爱呢?

Redux更具性能?答案是否定的。事实上,为了每一个必须处理的新动作(action),都会稍微慢一些。

Redux是否更简单?当然不是。

简单应当是纯javascript:比如 TJ Holowaychuk 在twitter上说,他的状态管理库是 {}

那么为什么不是每个人都使用 global.state={}

为什么是Redux?

在表层之下,Redux与TJ的根对象{}完全相同——只是包装在了一系列实用工具的流水线(pipeline)中。

image.11869c9658da.png

在Redux中,你不能直接修改状态。只有一种方法:将一个 动作(action)分派(dispatch) 到流水线中,最终更新状态。

在流水线中有两组侦听器:中间件(middleware)订阅(subscription) 。中间件是能够侦听传入的操作的函数,从而支持诸如“logger”、“devtools”或“syncWithServer”侦听器之类的工具。订阅是用来广播这些状态变化的函数。

最后,还原器(reducer) 的更新方法可以将状态变化分解为更小、更模块化和可管理的块。

Redux实际上可能比使用一个全局对象作为你的状态更简单。

可以将Redux看作一个具有前置/后置更新挂钩的全局对象,并简化了“还原(reducing)”下一个状态的方法。

但是不是Redux太复杂了?

是的。有几个明确的迹象表明API需要改进。这些可以用下面的公式来总结:

API的质量 = 代码行数 / 花在阅读文档上的时间

Redux是一个拥有陡峭学习曲线的小型库。对于每一个已经克服并受益于Redux的开发人员来说,他们都是在深入研究函数式编程,另一些潜在的开发者已经失败了,并且认为“这不是我要的,我要回到jQuery”。

使用jQuery你不需要理解“monad”是什么,你也不需要为了使用Redux去理解函数组合。

任何库的目的都是通过抽象来让更复杂的东西看起来简单。

我的意思并不是要怼 Dan Abramov(Redux的主要提交者之一)。Redux在它早期太稚嫩的时候就已经太受欢迎了。

  • 如何重构一个已经被数百万开发者使用的库?
  • 你如何决定为世界各地无数的项目引入不兼容的变更?

你不能。但是你可以通过大量的文档、教学视频和社区活动提供惊人的支持。Dan Abramov就是在这里获胜的。

或许还有另一种方法。

重新设计Redux

我认为Redux值得重写,并且带来了7处需要改进的地方。(译注:原文缺失第3点,实际只有6处)

1. 设置

让我们看一看左边的真实世界Redux示例的基本设置。

image.cd239a2f6a7a.png

许多开发人员在第一步后就在这里暂停,茫然地盯着深渊。 什么是 thunk?compose?一个函数能做到这些吗?

倘若Redux可以基于配置而非组合。设置可能看起来更像右边的示例。

2. 简化 reducers

Redux中的reducers可以通过一个转换,让我们远离已经习惯但不必要且冗长的switch语句。

image.9b0ba6a5a4bd.png

既然一个reducer是为了匹配动作的类型(action.type),那么我们可以对参数进行反转,以便让每个reducer都是一个接受状态和动作的纯函数。也许更简单,我们可以标准化动作,只传递状态和有效负载(payload)。

4. Async/Await 与 thunks

在Redux中,通常使用thunks来创建异步操作。在很多方面,一个 thunk 的工作方式看起来更像是一个聪明的黑客,而不是官方推荐的解决方案。跟我来往下看:

  1. 你分派一个动作(dispatch an action),它实际上是一个函数而不是预期的对象。
  2. 这个中间件检查每一个动作,看它是否是一个函数。
  3. 如果是,中间件调用该函数,并传入一些 store 的方法:dispatch 和 getState。

怎么会这样?一个简单的action 到底是作为一个动态类型的对象、一个函数,还是一个 Promise?这难道不是一种拙劣的实践吗?

image.4fd549d5633c.png

就像右边的例子一样,我们难道不能只是简单地使用 async/await 就可以了吗?

5. 两种 actions

当你思考这个问题的时候,实际上有两种actions:

  1. reducer action:触发一个reducer并改变状态。
  2. effect action:触发一个异步操作。这可能会调用reducer action,但是异步函数不会直接改变任何状态。

区分这两种类型的 actions 会更有帮助,并且不会让上述用法与“thunks”混淆。

6. 不再有动作类型(action.type)变量

为什么用不同的方式来对待 action creators 和 reducers 是一种标准做法?两者之一可以独立于另一方而存在吗?改变其中之一不会影响另一方吗?

action creators 和 reducers 是同一枚硬币的两面

const ACTION_ONE = 'ACTION_ONE'是分离 action creators 和 reducers 的一个多余的副产品。应将两者视为一体,并且不再需要文件导出类型的字符串。

7. reducers 就是 action creators

通过使用Redux的元素来分组,你可能会得到一个更简单的模式。

image.ddaed03a3f05.png

它可以自动地从reducer中确定action creator。毕竟,在这个场景中,reducer可以成为action creator

使用一个基本的命名约定,下面是可预测的:

  1. 如果一个reducer有“increment”的名字,那么类型就是“increment”。更妙的是,我们还给它增加了一个命名空间“count/increment”。
  2. 每个action都通过一个“payload”键传递数据。

image.10603689cb3b.png

现在,从 count.increment 中,我们可以以一个reducer生成action creator。

好消息:我们可以有一个更好的 Redux

这些痛点是我们开发Rematch的原因。

image.1ca43e01c0be.png

Rematch围绕Redux进行了封装,提供更简单的API,而不丢失任何可配置性。

image.446d2092df01.png

请参见下面的一个完整的Rematch示例:

image.7c7c06d058b1.png

在过去的几个月里,我一直在实际业务中使用Rematch。作为证明,我会说:

我从未花费如此少的时间去考虑状态管理。

Redux不会消失,也不应该消失。拥抱Redux之后的简单模式,学习曲线更平滑,样板代码(boilerplate)更少,认知开销也更少。

试试Rematch,看看你是否喜欢它。并且在github上给我们一个 star 以便让别人也知道。

4
推荐阅读