使用 Valtio 和 MobX
虽然 Valito 和 MobX 背后的 工作原理不一样,Valito 经常 被 拿来 和 MobX进行比较。这两者在 绑定 React上,有一定的相似性。它们俩都是基于可变式更新模型,允许开发者之间改变状态的。而JS本身是允许可变式更新的,所以Valtio 和 MobX 对于 开发者来说,是更加自然和好用的。
然而,Valtio 和 MobX 在 优化重新渲染上,是非常不同的。
Valito是 基于 钩子函数进行 重新渲染优化的,而 MobX则是采用了 高阶组件。
在本小节,我们会把基于 MobX 的应用 转化为 给予 Valito 的应用,从而比较这两个库的异同。
使用了 Valito 和 MobX的例子
首先,我们要从 MobX 引入两个基础函数:
import { makeAutoObservable } from "mobx";
import { observable } from "mobx-react";
接下来,我们要实现一个 计时器的逻辑。我们要创建一个相关的类,并将它实体化。
class Timer {
secondsPassed = 0;
constructor () {
makeAutoObservable(this);
},
increase() {
this.secondsPassed +=1;
}
reset() {
this.secondsPassed = 0;
}
}
const myTimer = new Timer();
这个类有一个字符串属性和两个函数属性。makeAutoObservable可以使myTimer 这个 实例 成为一个可观察对象。
我们可以在任何地方调用这些函数属性。比如,我们可实现一个计时方法:
setInterval(() => {
myTimer.inrease();
}, 1000)
然后,我们要实现一个使用了该 timer 的组件:
const TimerView = observer(({ timer }: { timer: Timer }) => (
<button onClick={() => timer.reset()}>
Seconds passed: {timer.secondsPassed}
</button>
));
这个observer 函数,是一个高阶组件。它能够读取 在 渲染函数中要使用的 timer.secondsPassed,并确保在 timer.secondsPassed 变动时, 发生 重新 渲染。
最后,我们要实现传入了 Timer 实例 给 TimerView 的 App组件:
const App = () => (
<>
<TimerView timer={myTimer} />
</>
)
如下图所示,如果你运行了这个应用,它会展示已经过去的秒数。而点击重置按钮后,数字就会清零。
那么,用Valito来实现,会所如何?
首先,我们要从 Valtio 库 引入两个 函数:
import { proxy, useSnapshot } from "valtio";
我们使用 proxy 函数来定义 myTimer 实例:
const myTimer = proxy({
secondesPassed: 0,
increase: () => {
myTimer.secondesPassed += 1;
},
reset: () => {
myTimer.secondesPassed = 0;
}
})
它有一个 数字类型的 secondsPassed 属性,和两个用来更新状态的 函数属性。
然后,我们就可以实现计时功能了:
setInterval(() => {
myTimer.inrease();
}, 1000)
这个计时函数的用法,和 MobX里的是一样的。
然后,我们要实现的,是使用了 useSnapshot 的 TimerView 组件:
const TimerView = ({ timer }: { timer: typeof myTimer }) => {
const snap = useSnapshot(timer);
return (
<button onClick={() => timer.reset()}>
Seconds passed: {snap.secondsPassed}
</button>
);
};
在Valito里,useSnapshot 用于读取 render 函数中要使用的 状态,并在 这些状态变化时,进行重新渲染。
最后,我们要实现App 组件,它看起来 和 之前的App 组件一样:
const App = () => (
<>
<TimerView timer={myTimer} />
</>
)
然后,这段代码实现的功能和 用 Mobx 实现的 计时功能一样。
接下来,我们讨论这 两个库的不同。
比较 Valito 和 MobX的例子
这两个库大体看起来 差不多,但还是有两个显著的不同点:
- 第一个不同是更新方法。虽然这两者都是基于 可变式更新模型,但是 MobX 是基于 类 进行 更新的,而 Valito 是 基于 对象 进行更新的。这是最显著的不同,而 Valtio 并不 关心 更新方式。
Valito 运行 开发者 把 更新 函数 抽离出来。刚刚实现的myTimer对象 可以这样实现:
// timer.js
const timer = proxy({ secondesPassed: 0 });
export const increase = () => {
timer.secondesPassed += 1;
}
export const reset = () => {
timer.secondesPassed += 0;
}
export useSecondsPassed = () =>
useSnapshot(timer).secondesPassed;
因为proxy 赋予了我们进行 可变式 更新的能力,我们可以把 这两个 更新函数 从 timer 对象里 抽离出来。这样做的好处是,可以进行代码拆分、降低包提及,并删除一些无用的 代码。
- 第二个不同是优化渲染的方式。MobX 采用的是 观察者模式,而 Valtio 采用的 是 钩子模式。这两种模式各有其利弊。观察者模式的可预测性更强,而 钩子模式 对于并发渲染更加友好。实现这些模式也是非常不同的。当然,采用哪种模式,也是开发者个人偏好的问题。
在这个小节,我们比较了 MobX 和 Valito的 异同。接下来,我们要比较 Zustand, Jotai 和 Valito。
比较 Zustand,Jotai 和 MobX
在本章中,我们已经对比了以下几对状态管理库:
- 在 "Zustand 与 Redux 的区别" 一节中对比了 Zustand 和 Redux
- 在 "理解何时使用 Jotai 和 Recoil" 一节中对比了 Jotai 和 Recoil
- 在 "使用 Valtio 和 MobX" 一节中对比了 Valtio 和 MobX
我们之所以对比这些组合,是因为它们之间存在一些相似性。在本节中,我们将对 Zustand、Jotai 和 Valtio 进行对比。
首先,这三个库均由 Poimandres GitHub 组织(github.com/pmndrs)提供。该… GitHub 组织推出三个微型状态管理库,这听起来可能有违直觉,但它们的风格各不相同。这三个库还共享一个核心理念:它们的 API 表面都非常精简。三者都尽力提供极简的 API 接口,并让开发者能够根据需求自由组合这些 API。 不过,这三个库之间究竟有哪些区别呢?
有两个方面:
*状态驻留在哪里?在 React 中存在两种方式。一种是模块状态,另一种是组件状态。模块状态是在模块级别创建且不属于 React 的状态。组件状态是在 React 组件生命周期中创建并由 React 控制的状态。Zustand 和 Valtio 是为模块状态设计的。而 Jotai 则是为组件状态设计的。例如,以 Jotai 的 atoms 为例,下面是 countAtom 的定义:
const countAtom = atom (0);
这个 countAtom 变量保存的是一个配置对象,并不保存值。atom 的值存储在 Provider 组件中。因此,countAtom 可以在多个组件中重复使用。而用模块状态实现相同的行为则较为复杂。使用 Zustand 和 Valtio 时,我们最终可能需要借助 React Context。另一方面,从 React 外部访问组件状态在技术上是不可行的,我们可能需要某种模块状态来连接组件状态。 选择使用模块状态还是组件状态取决于应用程序的需求。通常,使用模块状态或组件状态来管理全局状态即可满足应用需求,但在少数情况下,同时使用这两种状态可能更为合理。
- 状态更新风格是怎样的?Zustand 和 Valtio 之间存在一个主要区别。Zustand 基于不可变状态模型,而 Valtio 基于可变状态模型。不可变状态模型的约定是对象一旦创建就不能更改。假设你有一个状态变量,如 state = { count: 0 }。如果想在不可变状态模型中更新 count,需要创建一个新对象。因此,将 count 加 1 应该是 state = { count: state.count + 1 }。而在可变状态模型中,可以直接 ++state.count。这是因为 JavaScript 对象本质上是可变的。 不可变模型的优势在于可以通过比较对象引用判断是否有变化,这对大型嵌套对象的性能优化很有帮助。由于 React 大多基于不可变模型,因此采用相同模型的 Zustand 具有良好的兼容性,这也使得 Zustand 成为一个非常轻量级的库。另一方面,使用可变状态模型的 Valtio 需要填补两种模型之间的差异。 总之,Zustand 和 Valtio 采用了不同的状态更新风格。可变更新风格非常便捷,尤其是在处理深度嵌套对象时。可以回顾第 9 章 “用例场景 3 - Valtio” 中 “此方法的优缺点” 一节的示例。
这三个库之间存在一些细微差别,但重要的是它们基于不同的原则。如果我们要从中选择一个,需要看哪一个原则更符合我们的应用需求和思维模式。
概要
在本章中,我们总结了本书所介绍的三种全局状态管理库之间的差异。它们之所以不同,是因为基于不同的模型。
从本质上讲,微型状态管理涉及为特定问题选择合适的解决方案和库。微型状态管理要求你了解自己面临的问题是什么,以及有哪些可用的解决方案。我们希望本书涵盖的一些主题能帮助开发者找到合适的解决方案。