选择mobx作为React App状态管理工具

512 阅读4分钟

增长决定使用mobx和react结合做一个新的模块。 有几个考量的点,性能(减少rerender),开发效率(符合直觉),流行。

redux配合immer也可以,但是开发起来有点麻烦,不考虑。这也是有其他状态管理工具出现的原因。 image.png npm trends 对比之后,发现除了redux方案之外,最流行的是zustand 和 mobx,zustand增长迅猛,已经是hooks时代最佳搭配,4年时间赶上7年mobx的start。

但就单纯论功能的话,mobx有更多api和特性可用,项目也是B端项目,体积也不用考虑,最后选择mobx。

选了mobx之后,还有几个要考虑的点

  1. mutable理念的mobx,与immutable理念的react hook,是否可以结合使用

mobx官方说可以,但是实际上,在zustand那里有说明

Don't disregard it because it's cute. It has quite the claws, lots of time was spent dealing with common pitfalls, like the dreaded zombie child problemreact concurrency, and context loss between mixed renderers. It may be the one state-manager in the React space that gets all of these right.

我猜大概率mobx还是有些难以克服的结合问题,毕竟mutable和immutable混合着用,肯定会有些特别情况。 这里就需要一些取舍了。 例如antd是配合hooks比较好用的,那么这种情况,就是把mobx的一些状态同步到hooks的写法上。

其他情况,就使用mobx。 例如,在useEffect中使用autoRun执行副作用。这样就不用手工管理dependency array了,非常好用。 mobx.js.org/react-integ…

useEffect( () => autorun(() => { 
    if (timer.secondsPassed > 60) 
        alert("Still there. It's a minute already?!!") 
}), [] )

又例如,利用useEffect 将props同步给mobx object。

function Measurement({ unit }) {
    const state = useLocalObservable(() => ({
        unit, // the initial unit
        length: 0,
        get lengthWithUnit() {
            // lengthWithUnit can only depend on observables, hence the above conversion with `useAsObservableSource`
            return this.unit === "inch" ? `${this.length / 2.54} inch` : `${this.length} cm`
        }
    }))

    useEffect(() => {
        // sync the unit from 'props' into the observable 'state'
        state.unit = unit
    }, [unit])

    return <h1>{state.lengthWithUnit}</h1>
}

2. 性能

对比Vue, React更容易因为rerender的问题导致性能问题。React.memo, useCallback, useMemo等api,都是为了优化性能问题而出现的。 有一篇文章how-to-improve-state-management-in-react-with-mobx 这样写

In our initial approach, we had encapsulated these sources as states in the code and read them inside components using Context. Context is slow and inefficient because every component has to be re-rendered again, even if many changes are not relevant to it. With changes in real-time frequency, it could only get worse. And while we needed this map experience to meet our high standards - i.e., seamless and smooth to the user and the lowest impact on performance and resources- React Hooks caused flickers, delays, and unresponsiveness.

他说,使用React Context + hooks,因为每次修改context,都会全量Render,导致性能问题。当然可以解决,就人工去处理太麻烦了。 有一篇很牛逼的文章,讲解N种解决rerender的问题。# React re-renders guide: everything, all at once

而对于mobx,就简单得多,在函数组件加上observer()包裹即可。

MobX ensures that only the components that reference those states are refreshed while the rest of the screen remains unchanged.

Mobx自动收集Componet渲染依赖的state,当这些state变化的时候,通知相关的Component rerender。所以呢,用了mobx,就可以跟React.memo 88了。

不过,这个自动收集也是有一些规则的,就是渲染函数执行的时候,必须要访问到依赖state的getter,否则,在if包裹的某个state变化了,Component仍然不更新。

  1. ide/ts支持,文档 mobx也有一个opionioned的选项,就是mobx-state-tree(mst),但是我踩过坑,决定放弃使用了。 1) mst的文档缺少升级说明。例如types.maybe在旧版本destroy变成null,但是新版就变成undefeined了。还有其他break chang也没有。 2) mst的ide/ts支持不友好。写代码的时候,会发现self.之后无法继续自动提示view或者action。体验很差。虽然官方也给了一种能work的写法,但是写法对开发人员不友好。另外,它的ts类型非常复杂,webstorm无法解释,只有vscode才能解释。有时候使用webstorm 的git工具去review代码的时候,效率就比较低。

当然mst好的地方是,定义好了类型(ts或者jsdoc也可以替代这个)还有一些最佳实践,例如只有一个store,不能循环引用,还有volatile, destroy一些内存管理的手段。用mobx,也可以遵守类似的规范去开发。