- 自动获取源自应用state的数据
- 机制:事件调用actions,使用actions来修改state,然后自动更新,更新的值会触发对应的值变化
- 安装:mobx的介绍
- 搭档:React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。
核心概念
- Observable state(可观察的状态)
- Computed values(计算值)
- React组件-@observer
- Reactions(反应)
- Actions(动作)
- 自定义reactions
优点
- 简单,可扩展
- 保证参照完整性
- 高效
要点
- 将应用变成响应式的三个步骤
- 定义状态并使其可观察
- 使用observable
- 创建视图以响应状态的变化
- 使用@observer
- 更改状态
- 定义状态并使其可观察
observable
- 定义:使用@observable装饰器给现有的数据结构添加可观察功能
用法
observable(value)
@observable classProperty = value
value
- Map: 会返回一个新的Observable Map
1
2const map = observable.map({ key: "value"});
map.set("key", "new value"); - 数组:会返回一个Observable Array
1
const list = observable([1,2,3])
- 没有原型的对象:会被克隆并且所有属性都会被转换成可观察的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const person = observable({
// 观察的属性
name: "cc",
age: '23',
show: false,
// 计算属性
get labelText() {
return this.show ? `${this.name} age: ${this.age}` : this.name
}
// 动作
setAge(age) {
this.age = age
}
}, {
setAge: action
})
autorun(() => console.log(person.labelTExt)) - 有原型的对象(原值类型值和引用类型值):会返回一个Boxed Observable。在构造函数中使用extendObservable 或在类中使用@observable
- 获取当前值 : .get()
- 更新值: .set()
- 监听值的更改:antorun(()=>{})
- eg:const temperature = observable.box(20);temperature.set(25)
- Map: 会返回一个新的Observable Map
decorators
- 不使用observable,然后单独使用decorate来另外定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Person {
name = "John"
age = 42
showAge = false
get labelText() {
return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
}
setAge(age) {
this.age = age;
}
}
// 使用 decorate 时,所有字段都应该指定
decorate(Person, {
name: observable,
age: observable,
showAge: observable,
labelText: computed,
setAge: action
})
(@)computed
- 定义:使用@computed装饰器或(extend)Observable时调用的getter/setter函数来定义数据发生变化时自动更新的值
@computed 与 autorun 的区别
- @computed:响应式调用的表达式,响应式的产生一个可以被其它observer使用的值
- autorun:响应式调用的表达式,与@computed区别,不产生新值,只想要效果
getter 与 setter
- 正常情况下的示例:@computed get total(){ retuan this.price * this.amount}
- 正常情况下setter也不需要注解,因为是一个自动的动作
- setter不能用来直接改变计算属性的值,但是可以用来逆向衍生
- 永远在getter之后定义setter
- observable.object 和 extendObservable 都会自动将 getter 属性推导成计算属性,所以这两种情况不用添加装饰器
1
2
3
4
5
6
7
8
9
10
11const orderLine = observable.object({
price:0,
amount:1,
get total() {
retuan this.price * this.amount
}
set total(total) {
// 从total中推导出price
this.price = total / this.amount
}
})
computed(expression) 函数(不常用)
- 使用场合:传递一个“在observable.box中”的计算值
1
2
3
4
5
6
7import {observable, computed} from "mobx";
var name = observable.box("John");
var upperCaseName = computed(() =>
name.get().toUpperCase()
);
var disposer = upperCaseName.observe(change => console.log(change.newValue));
name.set("Dave");
第二参数对象的可选参
- name:字符串, 在 spy 和 MobX 开发者工具中使用的调试名称
- context:在提供的表达式中使用的 this
- set:要使用的setter函数。
- equals:比较前一个值和后一个值的函数,相等不评估
- requiresReaction:推荐为true
- keepAlive:慎用,会造成内存泄露
内置比较器
- comparer.identity: 使用恒等 (===) 运算符来判定两个值是否相同。
- comparer.default: 等同于 comparer.identity,但还认为 NaN 等于 NaN
- comparer.structural: 执行深层结构比较以确定两个值是否相同。
Autorun(自定义reactions)
- 第二参数对象的可选参
- delay:对reaction函数去抖动的数字,默认为0不抖动,以毫秒为单位
- name:字符串,用于在例如spy事件中用作reaction的名称
- onError: 处理reaction的错误
- scheduler:设置自定义调度器已决定如何调度autorun函数的重新运行
When(自定义reactions)
- 语法:when(predicate: () => boolean, effect?: () => void, options?)
- when 观察并运行给定的 predicate,直到返回true。 一旦返回 true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。
when-promise
1 | // 不提供effect返回一个promise |
Reaction(自定义reactions)
- antorun的变种,与anturun的区别,创建时reaction函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行。在执行时访问任何observable都不会被追踪。
- 用法:reaction(() => data, (data, reaction) => { sideEffect }, options?)
- 简单理解:reaction是computed(expression).observe(action(sideEffect)) 或 autorun(() => action(sideEffect)(expression)) 的语法糖。
用法解析
- 参数1为data函数,用来追踪并返回数据作为第二个函数的输入。
- 参数2为effect函数,接收的两个参数分别为data函数返回的值和当前的reaction,当前的reaction用来在执行期间清理reaction。effect函数仅对data函数中访问的数据作出反应
- 返回一个清理函数
options可选参
- fireImmediately: 布尔值,用来标识效果函数是否在数据函数第一次运行后立即触发。默认值是 false
- 其他的同autorun一样
@observer
- 用来将 React 组件转变成响应式组件,observer由mobx-react包提供
1
2
3
4
5@observer class Timer extends React.component {
render (
return <span>seconds:{this.props.timerData.second}</span>
)
} - 当observer需要组合其它装饰器或高阶组件时,请确保observer是最深处(第一个应用)的装饰器,否则不起效
- 使用@observer装饰器 与 observer(class Timer … {})效果一样
- 陷阱:使用以下方式初始化,组件不会有反应。值需要通过引用来传递而不是通过字面量值来传递
- React.render(
<Timer timerData={timerData.secondsPassed} />
, document.body)
- React.render(
- 无状态函数组件:const Timer = observer(({timerData}) => seconds:{timerData.second})
- 可观察的局部组件状态:在使用@observer的类中使用@observable来观察属性。响应式状态会被render调用,也会调用componentWillUpdate 和 componentDidUpdate
使用 inject 将组件连接到提供的 stores
- 用法:inject(stores)(component) 或 @inject(stores) class Component…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19const colors = observable({
foreground: '#000',
background: '#fff'
});
const App = () =>
<Provider colors={colors}>
<app stuff... />
</Provider>;
const Button = inject("colors")(observer(({ colors, label, onClick }) =>
<button style={{
color: colors.foreground,
backgroundColor: colors.background
}}
onClick={onClick}
>{label}</button>
));
// 稍后..
colors.foreground = 'blue';
// 所有button都会更新
componentWillReact(生命周期钩子)
- 当组件因为它观察的数据发生了改变,它会重新渲染,这时componentWillReact会被触发,方便组件追溯并找到导致渲染的操作
MobX 会对什么作出反应?
- MobX 会对在执行跟踪函数期间读取的任何现有的可观察属性做出反应。
- 追踪函数:是computed表达式、observer组件的render()和when、reaction 和anturun的第一个入参函数
- 过程:只追踪哪些在函数执行时被读取的observable
- 读取:对象属性的间接引用,可用. 或 [] 形式完成
- 只追踪同步地访问数据
- MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪
1
2
3
4
5
6
7
8
9
10// 不接受参数,避免创建额外组件
const MyComponent = ({ message }) =>
<SomeContainer
title = {() =>
<Observer>
{() => <div>{message.title}</div>}
</Observer>
}
/>
message.title = "Bar" - 避免在本地字段中缓存 observable
- 不会有反应的
- 从observable获取的值,但是在追踪函数之外
- 在异步调用的代码块中读取的observable
Action
用法
- action(fn)
- action(name, fn)
- @action classMethod() {}
- @action(name) classMethod () {}
- @action boundClassMethod = (args) => { body }
- @action(name) boundClassMethod = (args) => { body }
- @action.bound classMethod() {}
- action.bound 不要和箭头函数一起使用;箭头函数已经是绑定过的并且不能重新绑定。
- 使用
- 应该永远只对修改状态的函数使用动作。 只执行查找,过滤器等函数不>应该被标记为动作
编写异步 Actions
@action 只对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应
runInAction(name?, thunk)
- 接收代码块并在异步动作中执行。对于即时创建和执行动作非常有用
promise:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20mobx.configure({ enforceActions: true }) // 不允许在动作之外进行状态修改
class Store {
@observable githubProjects = []
@observable state = "pending"
@action
fetchProjects() {
this.githubProjects = []
this.state = "pending"
fetchGithubProjectsSomehow().then(this.fetchSuccess, this.fetchError)
}
// 内联方法,缺点不能进行类型推导
action("fetchSuccess", projects => {
const filteredProjects = somePreprocessing(projects)
this.githubProjects = filteredProjects
this.state = "done"
})
action("fetchError",(error) => {
this.state = "error"
})
}async/await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23mobx.configure({ enforceActions: true }) // 不允许在动作之外进行状态修改
class Store {
@observable githubProjects = []
@observable state = "pending"
@action
async fetchProjects() {
this.githubProjects = []
this.state = "pending"
try {
const projects = await fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
// await 之后,再次修改状态需要动作:
runInAction(() => {
this.state = "done"
this.githubProjects = filteredProjects
})
} catch (error) {
runInAction(() => {
this.state = "error"
})
}
}
}flows
使用 function * 来代替 async,使用 yield 代替 await
其它与async/await差不多,只能作为函数使用,不能作为装饰器
工具函数
toJS(value, options?)
- 作用:递归地将一个observable对象转换成javascript解构。支持observable数组、对象、映射和原始类型
- options:
- exportMapsAsObjects 是否将observable映射序列化为对象(true) 或JavaScript Map对象(false).默认true
- detectCycles 如果监测到循环,则重现使用已经序列化的对象。可以防止无限递归。默认true
extendObservable(target, properties, decorators?, options?)
- 作用:用来向已存在的目标对象添加observable属性。属性映射中的所有键对都会导致目标上的新的observable属性被初始化为给定值。属性映射中的任意getters都会转化成计算属性。
- 使用deep:false使得新的属性变成浅的。即阻止它们的值自动转换成observables。
- observable.object(object) 实际上是 extendObservable({}, object) 的别名
创建observable数据结构和reactions
- Atoms可以用来通知MobX某些observable数据源被观察或发生了改变。当数据源被使用或不再使用时,MobX会通知atom
- 多数场景下,可以通过创建普通的observable并使用工具onBecomeObserved 或 onBecomeUnobserved在Mobx开始追踪observable时接收通知来避免创建atom
Intercept & Observe
- 作用:用来监测单个observable的变化。 intercept可以在变化作用域observable之前监测和修改变化。observe允许在observable变化之后拦截改变。
intercept(target,propertyName?, interceptor)
- target: 监测的observable
- propertyName: 可选参数,用来指定某个属性进来拦截。注意,intercept(user.name, interceptor) 和 intercept(user, “name”, interceptor) 根本是完全不同的。前者尝试给 user.name(或许根本不是一个 observable) 里面的当前值添加一个拦截器,而后者拦截 user 的 name 属性的变化。
- interceptor: 在每次变化作用于 observable 后调用的回调函数。接收一个用来描述变化的对象。
Observe(target, propertyName?, listener, invokeImmediately?)
- target: 观察的 observable
- propertyName: 可选参数,用来指定某个属性进行观察。注意,observe(user.name, listener) 和 observe(user, “name”, listener) 根本是完全不同的。前者观察 user.name(或许根本不是一个 observable) 里面的当前值,而后者观察 user 的 name 属性。
- listener: 在每次变化作用于 observable 后调用的回调函数。接收一个用来描述变化的对象,除了装箱的 observable,它调用listener 的两个参数: newValue、oldValue。
- invokeImmediately: 默认是 false。如果想直接使用 observable 的状态(而不是等待第一次变化)调用 listener 的话,把它设置为 true。不是所有类型的 observable 都支持。
- 该函数返回一个disposer函数,当调用时取消观察者。
- observe 会响应对应的变动,而像是 autorun 或 reaction 则会对新值做出响应