如何评价数据流管理框架 MobX ?

GitHub - mobxjs/mobx: Simple, scalable state management.
关注者
233
被浏览
43,806

12 个回答

大家对 MobX 的优点都总结得差不多了,知乎也有很多好文章总结其范式和实现,推荐没用过的人学习一下这种类 vue 的状态管理。


但是还是想给入坑的朋友提个醒,大家在安利的时候,可能不太会提这个库的缺点,但这对于他人选型来说还是很有用的。


现在开始吐槽一下 MobX 的缺陷,希望能抛砖引玉。


如果你还没有学过 MobX ,可以看这个例子理解一下:

import { observable, autorun } from 'mobx'

let ob = observable({ a: 1 }) // 创建一个可观察对象

// autorun 类似于 observe,会响应 ob.a 的变化自动执行
autorun(() => {
    console.log('autorun', ob.a)
})

ob.a = 2
// autorun 2

一般的用法差不多就是

  • 创建一个「可观察对象」
  • 用 autorun(handler) 包裹一个函数,handler 会在其内部的可观察对象变化后自动响应执行。
  • 修改可观察对象,handler 会再次自动响应

看起来要比 redux 少写很多代码,其背后的响应式理念也十分贴心。但 mobx 的这种方便之处,其实引起了很多的问题。这些问题不全是因为 mobx 实现的问题,更多和它暴露的 api 有关系,导致你会出一些「想当然」的错误。这种「想当然」,是你基于之前的编程经验,以为这样写能 work ,实际不能 work 导致的,这种时候你就会想怪罪库了。

大多数可能预见的坑,它都写在文档里面提醒你了,理解 MobX 对什么有反应。看了这个长长的文档你就知道,想写出一个能让 autorun 好好工作的、不出 bug 的 mobx 应用需要排多少雷了(求大牛科普肯定不会踩雷的写作姿势)。


你可以好好看下那个文档,下面稍微提几个开发中容易遇到的坑。


# autorun 收集不到依赖


import { observable, autorun } from 'mobx'

let x = 1
let ob = observable({ a: 1, b: 1 })

let sub1 = autorun(() => {
    console.log('log ob.a', ob)
})

let sub2 = autorun(() => {
    console.log('log ob', ob.a)
})

let sub3 = autorun(() => {
    if (ob.b === 2) {
        console.log('判断 ob.b', ob.a)
    }
})

let sub4 = autorun(() => {
    if (x === 2) {
        console.log('判断 x', ob.a)
    }
})

x = 2
ob.b = 2
// 判断 ob.b 1
ob.a = 3
// 判断 ob.b 3
// log ob.a 3


上面四种代码都是你非常有可能写出来的代码(你真的会有这种需求),但可以观察到 sub1 和 sub4 挂了,为什么呢?


因为 autorun 本质是做「依赖收集」的,执行 autorun(handler) 的时候,就会执行一遍handler。不抠实现细节,你可以假想存在 const o = { a: 1 }; let ob = mobx.observable(o) 会给 o 的属性 a 在 ob 中设置对应的 getter/setter ,而 autorun 的 handler 中,当我们调用 ob.a 时,就会触发设置的 getter,从而收集到这个依赖,使得 ob.a 被 set 之后,能通知 mobx 再次调用这个 autorun 的 handler。

所以在

  • 声明 sub1 的时候,我们没有访问 ob.a ,而只是访问了 ob,autorun 没有收集到 ob.a 的依赖。
  • 同理,声明 sub4 时,autorun 执行一遍 handler ,发现此时 x 是 1,没有执行后续的 console.log,于是也没有收集到任何依赖。即使以后 x 确是 === 2 ,这个 handler 也不会被执行第二次了...


知道了原理我们还是能避免写出 bug 的,但是这些「bug」未免也太像常规操作了不是吗?实践中你很容易在某处加个判断(尤其是加的层级比较深的时候),甚至一些异步操作,导致 autorun 收集(预期的)依赖失败,这些凭肉眼和直觉还是很难发现的。


## observable 和 plain object 容易混淆


mobx 为了使用方便,故意把访问操作 observable 对象的方式设计得和 plain object 很像,完全无感。但也引起了一些问题,使用者会混淆两者。


const x = observable([1, 2, 3])
x.map(x => x + 1) // 我们就像普通的数组一样操作 observable array

// but...
if (Array.isArray(x)) {
  x.push(4) // 不会被执行,因为 Array.isArray(observable([1, 2, 3])) === false
}


当然这是有解决方案的,比如 mobx 有转型方法,或者上 TypeScript (这下你知道为什么大多数靠谱的 mobx 应用都是用 ts 写的了吧...),或者约定 mobx 对象都要 $ 开头


redux 里用了 immutable.js 也有这种问题,不过后者还是在 api 上和 plain object 不同的,用 .get .set 才能访问和修改,使用起来你就能体会不同。


我个人对 mobx 背后的响应式理念非常喜欢,但是 mobx 显然还不够好,这些问题导致要想用好 mobx ,一方面要求你懂 mbox.autorun 进行依赖收集、observable 数据结构的原理,另一方面要求你要有良好的记忆力,才能时时刻刻记得规避写出 bug。也或者是我还没找到一个更好的书写姿势( 求大佬送佛送到西,安利完了以后给个避雷针保平安)。


回头再比较 redux。redux 写起来只是烦而已,数据流还是很清晰的。不懂 redux 底层原理也不要紧,最多写出性能不佳的代码。但是不懂 mobx 依赖收集的原理可不行,很有可能就直接写出 bug 了。


当然不是说就写原生的 redux 吧,redux + redux-observable 其实也可以试试来着,如果不介意引入 Rx 的话。

与 Redux 对比 Mobx 简单太多了,只要掌握 observable、observer、toJS、transaction等几个API就可以轻松上手,之后再进阶地理解下 computed、reaction、autoRun 等几个概念后对付一般中小型项目就完全ok了。合理使用它的话也无须做刻意的性能优化。

出于快速使用React开发业务的目的,最近几个项目一直都用Mobx。另外我们使用Mobx与React生态其他优秀项目如Ant Design、React-Dnd等配合也是完全没有问题的。

现在Mobx的中文资料其实也不少了,比2016年刚火起来时多了很多。