一、MobX 介绍
首先看下官网介绍:
MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。MobX背后的哲学很简单: 任何源自应用状态的东西都应该自动地获得。 其中包括UI、数据序列化、服务器通讯,等等。
核心重点就是:MobX 通过响应式编程实现简单高效,可扩展的状态管理。
React 和 Mobx 关系
React 和 MobX 相辅相成,相互合作。
官网介绍:
React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。
Mobx 工作流程
这里先了解下大概整理流程,接下来会结合代码,介绍每一个部分。
本文概要
本文使用的是 MobX 5 版本,主要将从以下几个方面介绍 MobX 的使用:
- 配置 Webpack 的 MobX 开发环境
- MobX 常用 API 介绍(主要介绍与可观察数据相关的操作)
- MobX 简单实例
二、配置 Webpack 的 MobX 开发环境
- 安装 webpack 和 babel 依赖包:
cnpm i webpack webpack-cli babel-core babel-preset-env babel-loader -D
- 安装 MobX 相关依赖包:
cnpm i mobx-react -D cnpm i babel-plugin-transform-class-properties -D cnpm i babel-plugin-transform-decorators-legacy -D
- webpack.config.js 中添加配置:
注意:transform-decorators-legacy
一定放在第一个。
const path = require('path') const config = { mode: 'development', entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js' }, module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['env'], plugins: ['transform-decorators-legacy', 'transform-class-properties'] } } }] }, devtool: 'inline-source-map' } module.exports = config
三、MobX 常用 API 介绍
1. 设置可观察数据(observable)
1.1 (@)observable
observable
是一种让数据的变化可以被观察的方法,底层是通过把该属性转化成 getter
/ setter
来实现的。。
observable
值可以是 JS原始数据类型、引用类型、普通对象、类实例、数组和映射。
observable 使用
- 对于JS原始类型(
Number
/String
/Boolean
), 使用observable.box()
方法设置:
const num = observable.box(99) const str = observable.box('leo') const bool = observable.box(true) // 获取原始值 get() console.log(num.get(),str.get(),bool.get()) // 99 "leo" true // 修改原始值 set(params) num.set(100); str.set('pingan'); bool.set(false); console.log(num.get(),str.get(),bool.get()) // 100 "pingan" false
- 对于数组、对象类型,使用
observable()
方法设置:
const list = observable([1, 2, 4]); list[2] = 3; list.push(5) // 可以调用数组方法 console.log(list[0], list[1], list[2], list[3]) // 1 2 3 5 const obj = observable({a: '11', b: '22'}) console.log(obj.a, obj.b) // 11 22 obj.a = "leo"; console.log(obj.a, obj.b) // leo 22
需要注意的是:应该避免下标越界去方法数组中的值,这样的数据将不会被 MobX 所监视:
const list = observable([1, 2, 4]); // 错误 console.log(list[9]) // undefined
因此在实际开发中,需要注意数组长度的判断。
- 对于映射(Map)类型,使用
observable.map()
方法设置:
const map = observable.map({ key: "value"}); map.set("key", "new value"); console.log(map.has('key')) // true map.delete("key"); console.log(map.has('key')) // false
@observable 使用
MobX 也提供使用装饰器 @observable
来将其转换成可观察的,可以使用在实例的字段和属性上。
import {observable} from "mobx"; class Leo { @observable arr = [1]; @observable obj = {}; @observable map = new Map(); @observable str = 'leo'; @observable num = 100; @observable bool = false; } let leo = new Leo() console.log(leo.arr[0]) // 1
相比于前面使用 observable.box()
方法对JS原始类型(Number
/String
/Boolean
)进行定义,装饰器 @observable
则可以直接定义这些类型。
原因是装饰器 @observable
更进一步封装了 observable.box()
。
2. 响应可观察数据的变化
2.1 (@)computed
计算值(computed values)是可以根据现有的状态或其它计算值进行组合计算的值。可以使实际可修改的状态尽可能的小。
此外计算值还是高度优化过的,所以尽可能的多使用它们。
可以简单理解为:它是相关状态变化时自动更新的值,可以将多个可观察数据合并成一个可观察数据,并且只有在被使用时才会自动更新。
知识点:使用方式
- 使用方式1:声明式创建
import {observable, computed} from "mobx"; class Money { @observable price = 0; @observable amount = 2; constructor(price = 1) { this.price = price; } @computed get total() { return this.price * this.amount; } } let m = new Money() console.log(m.total) // 2 m.price = 10; console.log(m.total) // 20
- 使用方式2:使用 decorate 引入
import {decorate, observable, computed} from "mobx"; class Money { price = 0; amount = 2; constructor(price = 1) { this.price = price; } get total() { return this.price * this.amount; } } decorate(Money, { price: observable, amount: observable, total: computed }) let m = new Money() console.log(m.total) // 2 m.price = 10; console.log(m.total) // 20
- 使用方式3:使用 observable.object 创建
observable.object
和 extendObservable
都会自动将 getter
属性推导成计算属性,所以下面这样就足够了:
import {observable} from "mobx"; const Money = observable.object({ price: 0, amount: 1, get total() { return this.price * this.amount } }) console.log(Money.total) // 0 Money.price = 10; console.log(Money.total) // 10
- 注意点
如果任何影响计算值的值发生变化了,计算值将根据状态自动进行变化。
如果前一个计算中使用的数据没有更改,计算属性将不会重新运行。 如果某个其它计算属性或 reaction 未使用该计算属性,也不会重新运行。 在这种情况下,它将被暂停。
知识点:computed 的 setter
computed
的 setter
不能用来改变计算属性的值,而是用来它里面的成员,来使得 computed
发生变化。
这里我们使用 computed
的第一种声明方式为例,其他几种方式实现起来类似:
import {observable, computed} from "mobx"; class Money { @observable price = 0; @observable amount = 2; constructor(price = 1) { this.price = price; } @computed get total() { return this.price * this.amount; } set total(n){ this.price = n + 1 } } let m = new Money() console.log(m.total) // 2 m.price = 10; console.log(m.total) // 20 m.total = 6; console.log(m.total) // 14
从上面实现方式可以看出,set total
方法中接收一个参数 n
作为 price
的新值,我们调用 m.total
后设置了新的 price
,于是 m.total
的值也随之发生改变。
注意:
一定在 geeter 之后定义 setter,一些 typescript 版本会认为声明了两个名称相同的属性。
知识点:computed(expression) 函数
一般可以通过下面两种方法观察变化,并获取计算值:
- 方法1: 将
computed
作为函数调用,在返回的对象使用.get()
来获取计算的当前值。 - 方法2: 使用
observe(callback)
来观察值的改变,其计算后的值在.newValue
上。
import {observable, computed} from "mobx"; let leo = observable.box('hello'); let upperCaseName = computed(() => leo.get().toUpperCase()) let disposer = upperCaseName.observe(change => console.log(change.newValue)) leo.set('pingan')
更详细的 computed
参数可以查看文档:《Computed 选项》。
知识点:错误处理
计算值在计算期间抛出异常,则此异常会被捕获,并在读取其值的时候抛出异常。
抛出异常不会中断跟踪,所有计算值可以从异常中恢复。
import {observable, computed} from "mobx"; let x = observable.box(10) let y = observable.box(2) let div = computed(() => { if(y.get() === 0) throw new Error('y 为0了') return x.get() / y.get() }) div.get() // 5 y.set(0) // ok div.get() // 报错,y 为0了 y.set(5) div.get() // 恢复正常,返回 2
小结
用法:
computed(() => expression)
computed(() => expression, (newValue) => void)
computed(() => expression, options)
@computed({equals: compareFn}) get classProperty() { return expression; }
@computed get classProperty() { return expression; }
还有各种选项可以控制 computed
的行为。包括:
equals: (value, value) => boolean
用来重载默认检测规则的比较函数。 内置比较器有:comparer.identity
,comparer.default
,comparer.structural
;requiresReaction: boolean
在重新计算衍生属性之前,等待追踪的observables
值发生变化;get: () => value)
重载计算属性的getter
;set: (value) => void
重载计算属性的setter
;keepAlive: boolean
设置为true
以自动保持计算值活动,而不是在没有观察者时暂停;