MobX 上手指南

以前用 Redux 比较多,一直据说 Mobx 能让你体验到在 React 里面写 Vue 的感受,今天打算尝试下 Mobx 是否是真的有写 Vue 的感受。react

题外话

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

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

数据流

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

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。segmentfault

响应式对象

MobX 经过 makeObservable 方法来构造响应式对象,传入的对象属性会经过 Proxy 代理,与 Vue 相似,在 6.0 版本以前使用的是 Object.defineProperty API,固然 6.0 也提供了降级方案。promise

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,使用装饰器的写法:app

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,能够自动为属性加上对象的包装函数,上手成本直线降低。async

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
}

warn

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

image

相关文章
相关标签/搜索