之前用 Redux 比较多,一直听说 Mobx 能让你体验到在 React 里面写 Vue 的感觉,今天打算尝试下 Mobx 是不是真的有写 Vue 的感觉。
题外话
在介绍 MobX 的用法之前,先说点题外话,我们可以看一下 MobX 的中文简介。在 MobX 的中文网站上写着:
“MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。
![](https://i-blog.csdnimg.cn/blog_migrate/d827c496dec8a6b03f4f2698d8879029.png)
“战火洗礼的库” 怎么看都感觉很奇怪,读起来很拗口????,而且网上很多介绍 MobX 的文章都是这么写的,在 github 翻阅其 README 发现写的是:
“MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).
可以看到作者原本要表达的意思是 MobX 是经过了许多的测试,拥有比较强的健壮性。下面是通过谷歌翻译的结果,看起来也比中文网的表达要准确一些。
![](https://i-blog.csdnimg.cn/blog_migrate/618bfac5c000ff8f67f1731b1d6de25b.png)
虽然,我的英文水平也很菜,还是会尽量看官方的文档,这样可以避免一些不必要的误解。
如何使用?
言归正传,MobX 现在的最新版是 6.0,这个版本的 API 相比于之前有了极大的简化,可以说更加好用了。之前的版本是装饰器风格的语法糖,但是装饰器在现在的 ES 规范中并不成熟,而且引入装饰器语法也在增加打包后的代码体积。综合考虑后,MobX 6.0 取消了装饰器语法的 API。
响应式对象
MobX 通过 makeObservable
方法来构造响应式对象,传入的对象属性会通过 Proxy
代理,与 Vue 类似,在 6.0 版本之前使用的是 Object.defineProperty
API,当然 6.0 也提供了降级方案。
import { configure, makeObservable, observable, action, computed } from 'mobx'
// 使用该配置,可以将 Proxy 降级为 Object.defineProperty
configure({ useProxies: "never" });
// 构造响应对象
const store = makeObservable(
// 需要代理的响应对象
{
count: 0,
get double() {
return this.count * 2
},
increment() {
this.count += 1
},
decrement() {
this.count -= 1
}
},
// 对各个属性进行包装,用于标记该属性的作用
{
count: observable, // 需要跟踪的响应属性
double: computed, // 计算属性
increment: action, // action 调用后,会修改响应对象
decrement: action, // action 调用后,会修改响应对象
}
)
我们在看看之前版本的 MobX,使用装饰器的写法:
class Store {
@observable count = 0
constructor() {
makeObservable(this)
}
@action increment() {
this.count++;
}
@action decrement() {
this.count--;
}
@computed get double() {
return this.count * 2
}
}
const store = new Store()
这么看起来,好像写法并没有得到什么简化,好像比写装饰器还要复杂点。下面我们看看 6.0 版本一个更强大的 API:makeAutoObservable
。
makeAutoObservable
是一个更强大的 makeObservable
,可以自动为属性加上对象的包装函数,上手成本直线下降。
import { makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
get double() {
return this.count * 2
},
increment() {
this.count += 1
},
decrement() {
this.count -= 1
}
})
计算属性
MobX 的属性与 Vue 的 computed
一样,在 makeAutoObservable
中就是一个 getter
,getter
依赖的值一旦发生变化,getter
本身的返回值也会跟随变化。
import { makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
get double() {
return this.count * 2
}
})
当 store.count
为 1 时,调用 store.double
会返回 2。
修改行为
当我们需要修改 store 上的响应属性时,我们可以通过直接重新赋值的方式修改,但是这样会得到 MobX 的警告⚠️。
const store = makeAutoObservable({
count: 0
});
document.getElementById("increment").onclick = function () {
store.count += 1
}
![](https://i-blog.csdnimg.cn/blog_migrate/bd29f188e88507b5aba4dd65ba5ceb56.png)
MobX 会提示,在修改响应式对象的属性时,需要通过 action 的方式修改。虽然直接修改也能生效,但是这样会让 MobX 状态的管理比较混乱,而且将状态修改放到 action 中,能够让 MobX 在内部的事务流程中进行修改,以免拿到的某个属性还处于中间态,最后计算的结果不够准确。
makeAutoObservable
中的所有方法都会被处理成 action。
import { makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
get double() {
return this.count * 2
},
increment() { // action
this.count += 1
},
decrement() { // action
this.count -= 1
}
})
不同于 Vuex,将状态的修改划分为 mutation 和 action,同步修改放到 mutation 中,异步的操作放到 action 中。在 MobX 中,不管是同步还是异步操作,都可以放到 action 中,只是异步操作在修改属性时,需要将赋值操作放到 runInAction
中。
import { runInAction, makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
async initCount() {
// 模拟获取远程的数据
const count = await new Promise((resolve) => {
setTimeout(() => {
resolve(10)
}, 500)
})
// 获取数据后,将赋值操作放到 runInAction 中
runInAction(() => {
this.count = count
})
}
})
store.initCount()
如果不调用 runInAction
,则可以直接调用本身已经存在的 action。
import { runInAction, makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
setCount(count) {
this.count = count
},
async initCount() {
// 模拟获取远程的数据
const count = await new Promise((resolve) => {
setTimeout(() => {
resolve(10)
}, 500)
})
// 获取数据后,调用已有的 action
this.setCount(count)
}
})
store.initCount()
监听对象变更
无论是在 React 还是在小程序中想要引入 MobX,都需要在对象变更的时候,通知调用原生的 setState/setData
方法,将状态同步到视图上。
通过 autorun
方法可以实现这个能力,我们可以把 autorun
理解为 React Hooks 中的 useEffect
。每当 store 的响应属性发生修改时,传入 autorun
的方法(effect
)就会被调用一次。
import { autorun, makeAutoObservable } from 'mobx'
const store = makeAutoObservable({
count: 0,
setCount(count) {
this.count = count
},
increment() {
this.count++
},
decrement() {
this.count--
}
})
document.getElementById("increment").onclick = function () {
store.count++
}
const $count = document.getElementById("count")
$count.innerText = `${store.count}`
autorun(() => {
$count.innerText = `${store.count}`
})
每当 button#increment
按钮被点击的时候,span#count
内的值就会自动进行同步。????查看完整代码。
![](https://i-blog.csdnimg.cn/blog_migrate/3e3df2bcec5c8cb12a96e7bdbf3c74bf.png)
除了 autorun
,MobX 还提供了更精细化的监听方法:reaction
、 when
。
const store = makeAutoObservable({
count: 0,
setCount(count) {
this.count = count
},
increment() {
this.count++
},
decrement() {
this.count--
}
})
// store 发生修改立即调用 effect
autorun(() => {
$count.innerText = `${store.count}`
});
// 第一个方法的返回值修改后才会调用后面的 effect
reaction(
// 表示 store.count 修改后才会调用
() => store.count,
// 第一个参数为当前值,第二个参数为修改前的值
// 有点类似与 Vue 中的 watch
(value, prevValue) => {
console.log('diff', value - prevValue)
}
);
// 第一个方法的返回值为真,立即调用后面的 effect
when(() => store.count > 10, () => {
console.log(store.count)
})
// when 方法还能返回一个 promise
(async function() {
await when(() => store.count > 10)
console.log('store.count > 10')
})()
总结
MobX 的介绍到这里就结束了,本文只是大致的列举了一下 MobX 的 API,希望大家能有所收获。后续打算再深入研究下 MobX 的实现,等我研究好了,再写篇文章来分享。
最近组建了一个江西人的前端交流群,如果你也是江西人可以加我微信ruochuan12 拉你进群。
常驻推荐阅读
若川知乎高赞:有哪些必看的 JS库?
我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?
常驻末尾
你好,我是若川,江西人~(点击蓝字了解我)历时一年只写了一个学习源码整体架构系列 有哪些必看的JS库:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux
关注
若川视野
,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习我的博客地址:https://lxchuan12.gitee.io 欢迎收藏
觉得文章不错,可以 分享、点赞、在看 呀^_^另外欢迎
留言
交流~
小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间
【源码精选】
按钮,欢迎点击阅读,也可以星标我的公众号,便于查找