Mobx采用单向数据流:
动作
改变
状态
,从而改变
衍生
(视图)
当
状态
改变时所有
衍生
都会进行原子级的自动更新,所以永远不可能观察到中间值
计算值
是延迟更新的,任何不在使用状态的计算值将不会更新,直到需要它进行副作用(I / O)操作时。 如果视图不再使用,那么它会自动被垃圾回收
所有的
计算值
都应该是
纯净
的,它们不应该用来改变
状态
(使用纯函数生成计算值)
import { observable, autorun } from 'mobx';
var todoStore = observable({
todos: [],
get completedCount() {
return this.todos.filter(todo => todo.completed).length;
autorun(function() {
console.log("Completed %d of %d items",
todoStore.completedCount,
todoStore.todos.length
todoStore.todos[0] = {
title: "Take a walk",
completed: false
todoStore.todos[0].completed = true;
Action
@action.bound
对类方法添加改装饰器的效果是,将其中的this绑定为当前实例,也因此对于箭头函数形式的类方法无需使用该装饰器(它根据作用域链自动绑定了当前实例)
runInAction
这是个简单的工具函数,它接收代码块并在(异步的)动作中执行。这对于即时创建和执行动作非常有用,例如在异步过程中。runInAction(f)
是 action(f)()
的语法糖。
异步action
关于action非常重要的一点是:当它运行时,只会在触发当前运行环境内(或者说当前执行栈)的observable状态的更新(一种特殊情况是async/await函数,虽然看上去代码块是同一个执行栈内,但实际上每一个await都会触发一个新的异步函数的执行栈,所以只有第一个await之前的代码会触发状态更新)。这意味着当配置了只允许action修改状态时,以下情况下状态更新会出问题
在action中调用其它非action函数
在action中执行setTimeout、promise.then、async/await语句等异步代码,如果这些代码在改变状态时没有使用action
异步action的可选方法包括
将异步回调函数封装成action
在异步回调函数中修改状态的部分,使用runInAction执行
异步代码写在generator函数中,并使用mobx.flow封装(function*代替async、yield代替await)
mobx.configure({ enforceActions: true })
class Store {
@observable githubProjects = []
@observable state = "pending"
fetchProjects = flow(function * () {
this.githubProjects = []
this.state = "pending"
try {
const projects = yield fetchGithubProjectsSomehow()
const filteredProjects = somePreprocessing(projects)
this.state = "done"
this.githubProjects = filteredProjects
} catch (error) {
this.state = "error"
不能将js基础类型数据变为observable,这是做不到的,mobx只能监听引用类型数据
在react组件中应用@observer,实质是用autorun封装了其render方法,使其对observable状态的变动做出反应及时更新
在 ES5 中没有继承数组的可靠方法,因此 observable 数组继承自对象。 这意味着一般的库没有办法识别出 observable 数组就是普通数组(比如Array.isArray),使用 isObservableArray(observable)
来检查是否是 observable 数组。
计算值默认在未被观察时会暂停计算,除非设置为强制保持活动
reactions只有当其观察的所有状态都被垃圾回收了才会被垃圾回收,所以当不再需要使用它们的时候,推荐使用清理函数(这些方法返回的函数)来停止它们继续运行
在React项目中的实战方式
首先需要有若干个Mobx Store用于:存储应用状态数据(即state,及其衍生状态computed values)、提供更新状态的能力(action)
在某个父组件通过Provider将Store下发,使得子组件能够获取到
通过inject和observer方法封装子组件,将组件变为Mobx视图并将指定的Store注入进其props中
被注入的组件从props中拿到store后获取其state进行业务渲染,获取其action用于更新state,同时触发组件重渲染
class Store {
@observable count: number = 0;
@action.bound
setCount = (newCount: number): void => {
this.count = newCount;
export default Store;
import { Provider, observer } from 'mobx-react';
import CountStore from '@store/count';
const countStore = new CountStore();
const Parent: React.FC = () => {
const stores = { countStore };
return (
<Provider {...stores}>
<Child />
</Provider>
export default Parent;
import { inject, observer } from 'mobx-react';
const Child: React.FC = ({ countStore }) => {
const { count, setCount } = countStore;
const handleClick = () => setCount(count + 1);
return (
<div onClick={handleClick}>{count}</div>
export default inject('countStore')(observer(Child));
使用React Hooks
与上述方式不同之处在于,不是通过inject注入+props获取的方式,而是直接在子组件中使用hooks来获取Store,这样的好处在于将Store从组件的props中剥离出来,ts类型定义更方便(如果采用上面的代码,会发现Child组件的props中未定义countStore属性而导致ts类型错误)
mport * as React from 'react';
import { MobXProviderContext } from 'mobx-react';
const useMobxStores = <T extends Record<string, any>>(): T => {
return React.useContext<T>(MobXProviderContext as React.Context<T>);
export default useMobxStores;
import { inject, observer } from 'mobx-react';
import CountStore from '@store/count';
interface Stores {
countStore: CountStore;
const Child: React.FC = () => {
const { countStore } = useMobxStores<Stores>()
const { count, setCount } = countStore;
const handleClick = () => setCount(count + 1);
return (
<div onClick={handleClick}>{count}</div>
export default observer(Child);
复制代码