2.2 autorun
概念
autorun
直译就是自动运行的意思,那么我们要知道这两个问题:
- 自动运行什么?
即:自动运行传入 autorun
的参数函数。
import { observable, autorun } from 'mobx' class Store { @observable str = 'leo'; @observable num = 123; } let store = new Store() autorun(() => { console.log(`${store.str}--${store.num}`) }) // leo--123
可以看出 autorun
自动被运行一次,并输出 leo--123
的值,显然这还不是自动运行。
- 怎么触发自动运行?
当修改 autorun 中任意一个可观察数据即可触发自动运行。
// 紧接上部分代码 store.str = 'pingan' // leo--123 // pingan--123
现在可以看到控制台输出这两个日志,证明 autorun
已经被执行两次。
知识点:观察 computed 的数据
import { observable, autorun } from 'mobx' class Store { @observable str = 'leo'; @observable num = 123; @computed get all(){ return `${store.str}--${store.num}` } } let store = new Store() autorun(() => { console.log(store.all) }) store.str = 'pingan' // leo--123 // pingan--123
可以看出,这样将 computed
的值在 autorun
中进行观察,也是可以达到一样的效果,这也是我们实际开发中常用到的。
知识点:computed 与 autorun 区别
相同点:
都是响应式调用的表达式;
不同点:
@computed
用于响应式的产生一个可以被其他 observer 使用的值;autorun
不产生新的值,而是达到一个效果(如:打印日志,发起网络请求等命令式的副作用);@computed
中,如果一个计算值不再被观察了,MobX 可以自动地将其垃圾回收,而autorun
中的值必须要手动清理才行。
小结
autorun
默认会执行一次,以获取哪些可观察数据被引用。
autorun
的作用是在可观察数据被修改之后,自动去执行依赖可观察数据的行为,这个行为一直就是传入 autorun
的函数。
2.3 when
接收两个函数参数,第一个函数必须根据可观察数据来返回一个布尔值,当该布尔值为 true
时,才会去执行第二个函数,并且只会执行一次。
import { observable, when } from 'mobx' class Leo { @observable str = 'leo'; @observable num = 123; @observable bool = false; } let leo = new Leo() when(() => leo.bool, () => { console.log('这是true') }) leo.bool = true // 这是true
可以看出当 leo.bool
设置成 true
以后,when
的第二个方法便执行了。
注意
- 第一个参数,必须是根据可观察数据来返回的布尔值,而不是普通变量的布尔值。
- 如果第一个参数默认值为
true
,则when
函数会默认执行一次。
2.4 reaction
接收两个函数参数,第一个函数引用可观察数据,并返回一个可观察数据,作为第二个函数的参数。
reaction
第一次渲染的时候,会先执行一次第一个函数,这样 MobX 就会知道哪些可观察数据被引用了。随后在这些数据被修改的时候,执行第二个函数。
import { observable, reaction } from 'mobx' class Leo { @observable str = 'leo'; @observable num = 123; @observable bool = false; } let leo = new Leo() reaction(() => [leo.str, leo.num], arr => { console.log(arr) }) leo.str = 'pingan' leo.num = 122 // ["pingan", 122] // ["pingan", 122]
这里我们依次修改 leo.str
和 leo.num
两个变量,会发现 reaction
方法被执行两次,在控制台输出两次结果 ["pingan", 122]
,因为可观察数据 str
和 num
分别被修改了一次。
实际使用场景:
当我们没有获取到数据的时候,没有必要去执行存缓存逻辑,当第一次获取到数据以后,就执行存缓存的逻辑。
2.5 小结
computed
可以将多个可观察数据组合成一个可观察数据;autorun
可以自动追踪所引用的可观察数据,并在数据发生变化时自动触发;when
可以设置自动触发变化的时机,是autorun
的一个变种情况;reaction
可以通过分离可观察数据声明,以副作用的方式对autorun
做出改进;
它们各有特点,互为补充,都能在合适场景中发挥重要作用。
3. 修改可观察数据
在上一部分内容中,我们了解到,对可观察的数据做出反应的时候,需要我们手动修改可观察数据的值。这种修改是通过直接向变量赋值来实现的,虽然简单易懂,但是这样会带来一个较为严重的副作用,就是每次的修改都会触发 autorun
或者 reaction
运行一次。多数情况下,这种高频的触发是完全没有必要的。
比如用户对视图的一次点击操作需要很多修改 N 个状态变量,但是视图的更新只需要一次就够了。
为了优化这个问题, MobX 引入了 action
。
3.1 (@)action
action
是修改任何状态的行为,使用 action
的好处是能将多次修改可观察状态合并成一次,从而减少触发 autorun
或者 reaction
的次数。
可以理解成批量操作,即一次动作中包含多次修改可观察状态,此时只会在动作结束后,做一次性重新计算和反应。
action
也有两种使用方法,这里以 decorate
方式来介绍。
import { observable, computed, reaction, action} from 'mobx' class Store { @observable string = 'leo'; @observable number = 123; @action bar(){ this.string = 'pingan' this.number = 100 } } let store = new Store() reaction(() => [store.string, store.number], arr => { console.log(arr) }) store.bar() // ["pingan", 100]
当我们连续去修改 store.string
和 store.number
两个变量后,再运行 store.bar()
会发现,控制台值输出一次 ["pingan", 100]
,这就说明 reaction
只被执行一次。
知识点:action.bound
另外 action
还有一种特殊使用方法:action.bound
,常常用来作为一个 callback
的方法参数,并且执行效果也是一样:
import { observable, computed, reaction, action} from 'mobx' class Store { @observable string = 'leo'; @observable number = 123; @action.bound bar(){ this.string = 'pingan' this.number = 100 } } let store = new Store() reaction(() => [store.string, store.number], arr => { console.log(arr) }) let bar = store.bar; function foo(fun){ fun() } foo(bar) //["pingan", 100]
知识点:runInAction(name?, thunk)
runInAction
是个简单的工具函数,它接收代码块并在(异步的)动作中执行。这对于即时创建和执行动作非常有用,例如在异步过程中。runInAction(f)
是 action(f)()
的语法糖。
import { observable, computed, reaction, action} from 'mobx' class Store { @observable string = 'leo'; @observable number = 123; @action.bound bar(){ this.string = 'pingan' this.number = 100 } } let store = new Store() reaction(() => [store.string, store.number], arr => { console.log(arr) }) runInAction(() => { store.string = 'pingan' store.number = 100 })//["pingan", 100]
四、 Mobx-React 简单实例
这里以简单计数器为例,实现点击按钮,数值累加的简单操作,如图:
在这个案例中,我们引用 mobx-react
库来实现,很明显可以看出 mobx-react
是作为 mobx
和 react
之前的桥梁。
它将 react
组件转化为对可观察数据的反应,也就是将组件的 render
方法包装成 autorun
方法,使得状态变化时能自动重新渲染。
详细可以查看:www.npmjs.com/package/mob… 。
接下来开始我们的案例:
1. 安装依赖和配置webpack
由于配置和前面第二节介绍差不多,所以这里会以第二节的配置为基础,添加配置。
首先安装 mobx-react
依赖:
cnpm i mobx-react -D
修改 webpack.config.js
,在 presets
配置中添加 react
进来:
// ... 省略其他 - entry: path.resolve(__dirname, 'src/index.js'), + entry: path.resolve(__dirname, 'src/index.jsx'), module: { rules: [{ test: /\.jsx?$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { - presets: ['env'], + presets: ['env', 'react'], plugins: ['transform-decorators-legacy', 'transform-class-properties'] } } }] },
2. 初始化 React 项目
这里初始化一下我们本次项目的简单骨架:
// index.jsx import { observable, action} from 'mobx'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import {observer, PropTypes as observablePropTypes} from 'mobx-react' class Store { } const store = new Store(); class Bar extends Component{ } class Foo extends Component{ } ReactDOM.render(<Foo />, document.querySelector("#root"))
这些组件对应到我们最后页面效果如图:
2. 实现 Store 类
Store
类用于存储数据。
class Store { @observable cache = { queue: [] } @action.bound refresh(){ this.cache.queue.push(1) } }
3. 实现 Bar 和 Foo 组件
实现代码如下:
@observer class Bar extends Component{ static propTypes = { queue: observablePropTypes.observableArray } render(){ const queue = this.props.queue; return <span>{queue.length}</span> } } class Foo extends Component{ static propTypes = { cache: observablePropTypes.observableObject } render(){ const cache = this.props.cache; return <div><button onClick={this.props.refresh}>点击 + 1</button> 当前数值:<Bar queue={cache.queue} /></div> } }
这里需要注意:
- 可观察数据类型中的数组,实际上并不是数组类型,这里需要用
observablePropTypes.observableArray
去声明它的类型,对象也是一样。 @observer
在需要根据数据变换,而改变UI的组件去引用,另外建议有使用到相关数据的类都引用。- 事实上,我们只需要记住
observer
方法,将所有React
组件用observer
修饰,就是react-mobx
的用法。
4. 使用 Foo 组件
最后我们使用 Foo
组件,需要给它传递两个参数,这样 Bar
组件才能拿到并使用:
ReactDOM.render( <Foo cache={store.cache} refresh={store.refresh}/>, document.querySelector("#root") )
结尾
本文参考: