Redux 有多棒?
那我们的同情心又在哪呢?为什么我们的反应总是 “你错误地使用了它” 而不是 “我们可以把它设计地更容易去使用” 呢?
这里有个函数式编程界的相关现象,我喜欢叫它 Monad 指南的诅咒:解释它们是怎么工作的是非常简单的,但是解释清楚它们这么做是有意义的就出乎意料地困难了。
在这篇文章中你真的要读到一段 monad 指南?
Moand 是一个在 Haskell 常见的开发模式,在计算机中的很多地方都被广泛使用 - 列表,错误处理,状态,时间,输入输出。这里有个语法糖,你可以以 do
表达式的形式像输入指令代码一样来输入一系列的 monad 操作,就好像 javascript 中的 generator 可以让异步函数看起来像同步一样。
第一个问题是,用 monad 用来做什么来描述 monad 是不准确的。Haskell 曾引入 Monad 以解决副作用和顺序计算,但是事实上 monad 作为一个抽象概念并不能解决副作用和顺序化,它们是一系列规则,规定了一组函数如何交互,并没有什么固定的含义。关联性的概念适用于算术集合操作、列表合并和 null 传播,但是它完全独立于这些操作。
第二个问题是在一些小问题上,用 monad 来解决问题更繁琐了 - 至少看起来更复杂了 - 相比于指令式操作而言。给一个可选类型指定它的 Maybe Type
明显比验证一个模糊的 null
类型更安全,但是这又会让代码变得更难看。使用 Either
类型来进行错误处理通常比那些随处可能 throw
错误的代码更容易理解,但是 throw 操作的确比手动传值更简洁。而副作用 - 状态,IO 等 - 在指令式语言中更是微不足道的。函数式编程爱好者们(包括我)会说副作用在函数式语言中太简单了,但是让别人相信任何一种语言很简单本身就是一件很难的事。
而 monad 真正的价值只能在宏观尺度体现出来 - 并不是这些用例都遵循着 monad 规则,但是这些用例都遵循着同样的规则。能够作用于一个用例的操作就可以作用于每个用例:把一对列表压缩成一个存储着对值的列表就和把一对 promise 函数融合成一个处理两个结果的 promise 是“一样的”。
所以呢?
现在 Redux 有同样的问题 - 它很难学习并不是因为它很难反而是因为它太简单。理解并不是认知的障碍,而要相信它的核心设计理念,我们才能通过归纳来延伸其它的知识。
这种思想是很难共享的,因为核心思想是无趣的真理(避免副作用)或者做一些无意义的抽象((prevState, action) => nextState
)。任何单独的例子都不会对这种理解有任何帮助,因为这些例子只是展示了 Redux 的细节但并不能展现它的核心思想。
一旦我们开始✨接受别人的思想✨,我们中的很多人就会立刻忘掉自己之前的一些想法。我们忘记了我们的理解只能从我们自己一次又一次的失败和误解中获得。
所以你的建议是?
我觉得我们应该承认我们遇到了这个问题。Redux 是一种简单却不容易的语言。这是一种可以理解的设计选择,但是仍旧是一种权衡。对于一门牺牲了某些简单性来让它更便于使用的语言,还是有很多人都会从中获益的。但是,很多大型社区甚至不觉得这是一种已经做出的权衡。
我认为对比 React 和 Redux 是一件很有意思的事,因为广泛来说 React 是更复杂的,它有着明显更多 API 接口,同时它也在某种意义上更容易使用和理解。而 React 唯一必须的 API 接口是 React.createElement
和 ReactDOM.render
- 状态,组件生命周期,甚至 DOM 事件可以在别的地方处理。React 中的这些特性让它变得更复杂,但是也让它变得更出色。
“原子化状态”是个抽象概念,在你理解它之后可以指导你的开发,但是不管你理不理解这个概念,你都可以在 React 组件中调用 setState
,来实现原子化状态管理。这并不是一个完美的解决方案 - 彻底替换状态或者强制更新有着比它更高的效率,而且它是一个异步调用的方法还会产生一些 bug - 但是 React 将 setState
作为一个调用的方法而不是一个专业术语是一个很好的做法。
Redux 的开发组和社区都强烈反对增加 Redux 的 API 数量,但是现在将一堆小型开发库融合在一起的做法对于专家而言是乏味的,而对于新手而言是费解的。如果 Redux 不能内置一些小功能来对常见情况做一些支持,那么我们需要一个“更好”的框架在常见情况下来取代它。Jumpsuit 可以作为一个不错的开始 - 它将“action”和“state”的概念转化为了可调用的方法,同时保留了它们多对多的特性 - 但是事实上,这个库其实并不关心这个优化本身。
讽刺的是:Redux 存在的意义 是“开发者体验”:Dan 建立了 Redux 因为他希望理解和重建 Elm 的时光穿越调试。但是随着它开发了它自己的特性 - 进入了 React 生态系统的 OOP 运行环境 - 它牺牲了一些开发者的体验以换取可配置性。这让 Redux 得以蓬勃发展,但是这是个人性化开发框架明显的缺失。我们,Redux 社区,准备好了吗?
感谢 Matthew McVickar, a pile of moss, Eric Wood, Matt DuLeone, 和 Patrick Thomson review 本文。
备注:
[1] 为什么要在 React / JS 和 OOP 之间做明显的区分?JavaScript 是面向对象的,但是不是基于类(class-based)的。
OOP 类似于函数式编程,是一种方法,不是某个语言特性。有些语言对于 OOP 支持地特别好,或者有一些专门为 OOP 定制的标准库,但是如果你对它的了解够深,你可以用任何语言写出面向对象风格的代码。
JavaScript 有一种数据类型 Object,同时 JS 中大多数数据类型可以以 Object 的形式来处理和解析,从这种角度来说你可以对任何数据类型调用某些同样的方法,除了 null
和 undefined
。但是在 ES6 的 Proxy 出现之前,每个 Object 中调用的“方法”类似于一种字典查找,foo.bar
总是去查找 foo 对象中的“bar”属性或者它的原型链。而比如在 Ruby 这种语言中,foo.bat
会发一条消息 :bar
到 foo 对象中 - 这条消息可以被拦截或解析,它并不是必须做一个字典查找。
Redux 是一种基于 JavaScript 已存在的对象系统上更慢和更复杂的对象系统,reducer 和 middleware 相当于保存着状态的 JavaScript 对象的拦截器和解析器。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。
网友评论