乐趣区

关于前端:MobX-上手指南

之前用 Redux 比拟多,始终据说 Mobx 能让你体验到在 React 外面写 Vue 的感觉,明天打算尝试下 Mobx 是不是真的有写 Vue 的感觉。

题外话

在介绍 MobX 的用法之前,先说点题外话,咱们能够看一下 MobX 的中文简介。在 MobX 的中文网站上写着:

MobX 是一个通过战火洗礼的库,它通过通明的函数响应式编程使得状态治理变得简略和可扩大。

“战火洗礼的库”怎么看都感觉很奇怪,读起来很拗口????,而且网上很多介绍 MobX 的文章都是这么写的,在 github 翻阅其 README 发现写的是:

MobX is a battle tested library that makes state management simple and scalable by transparently applying functional reactive programming (TFRP).

能够看到作者本来要表白的意思是 MobX 是通过了许多的测试,领有比拟强的健壮性。上面是通过谷歌翻译的后果,看起来也比中文网的表白要精确一些。

尽管,我的英文程度也很菜,还是会尽量看官网的文档,这样能够防止一些不必要的误会。

如何应用?

言归正传,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 中就是一个 gettergetter 依赖的值一旦发生变化,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}

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 内的值就会主动进行同步。???? 查看残缺代码。

除了 autorun,MobX 还提供了更精细化的监听办法:reactionwhen

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 的实现,等我钻研好了,再写篇文章来分享。

退出移动版