前端的场景愈来愈复杂,现阶段,新项目都会采用Vue
、Angular
、React
之一来管理数据到视图的映射关系,它们都有本身管理组件状态、生命周期的独特机制,可是在复杂场景下,仍是会采用像Vuex
、Ngrx
、Redux
这样的状态容器来管理重要的全局状态。前端
我工做最主要用的仍是React
,在项目中,我使用Mobx
来做为React
状态管理的补充,加快编码的效率,本文主要记录一些Mobx
的用法。react
Mobx
是一个状态管理库,在状态依赖的描述上面有独特的优点,就像是在写公式同样,它能让开发者更简洁的声明描述属性状态的依赖关系,自动的完成相关依赖的更新、引发反作用。数组
Mobx
的使用很灵活,能够将observable
的特性直接做用在一个对象中,也能够声明在类中,甚至直接写入React
组件类的属性中(在Mobx
的视角中与类没有区别)。性能优化
直接使用 observable
包装的对象,会得到Mobx
给予的能力。bash
import * as mobx from "mobx";
// 声明一个对象是 observable
const myObj = mobx.observable({
a: 1,
b: 3,
get c() {
return this.b * 2;
}
});
// 注册一个反作用函数
mobx.autorun(() => {
console.log("a", myObj.a);
});
mobx.autorun(() => {
console.log("c", myObj.c);
});
// 改变这个对象的属性
myObj.a = false;
myObj.a = "hello";
myObj.b = 4;
/** 依次输出 a 1 c 6 a false a hello c 8 */
复制代码
已经能够看出Mobx
的一些特性了dom
myObj.b
,变化时不会触发只与a
有关的反作用。c
这个getter
属性会被autorun
反作用记录到关于b
的依赖,当b
发生变化,关联c
的反作用也会被触发。知道以上的规则,就能够直接在项目中尝试它了。异步
observable
import { observable, computed, action, autorun, flow } from "mobx";
// 使用属性装饰器声明
class SimpleStore {
@observable a = 1;
@observable c = 2;
@computed get b() {
return this.a * this.a + 1;
}
@action setA(a) {
this.a = a;
}
asyncUpdate = flow(function*() {
const next1 = yield new Promise(res => setTimeout(() => res(3), 1000));
this.a = next1;
const next2 = yield new Promise(res => setTimeout(() => res(4), 1000));
this.a = next2;
});
}
const store = new SimpleStore();
autorun(() => {
console.log(store.a);
});
store.setA(2);
store.asyncUpdate();
/** 依次输出 1 2 // setA(2) 3 // asyncUpdate() 1s 4 // asyncUpdate() 2s */
复制代码
从类的observable
声明中又能看出一些东西来:async
Mobx
相关的功能,相比直接使用对象,须要输入的工做量会大一些,可是能够对其运做有更细粒度的控制observable
的能力,在字段上加上@observable
,该字段就会被反作用记录到。@action
是用来声明改变@observable
字段的方法。若是开启了如下配置,将强制使用@action
方法来修改属性,不然会报错。mobx.configure({
enforceActions: true
});
复制代码
flow
是 Mobx
提供用于修饰异步action
的方法。其实就是一个async/await
方法的Generator
实现,最棒的特性就是这个方法返回一个 Promise,是能够取消的。react
组件中使用 Mobx
由于 react
组件须要监听 observable
的变化, render
的逻辑其实就是反作用,使用 autorun
的正确方式是引入 mobx-react
库。导入 observer
这个高阶组件,来自动完成 autorun
的注册与销毁。ide
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer } from "mobx-react";
@observer // 高阶组件,让react组件的render在mobx的autorun上下文中运行
class Counter extends React.Component {
// 能够直接使用 observable 装饰使用,代替 react 本身的 state,更新属性比setState要直接一些
@observable count = 0;
@observable unused = 0;
handleInc = () => this.count++;
handleDec = () => this.unused--;
render() {
console.log("render");
return (
<div>
{this.count}
<button onClick={this.handleInc}>+</button>
<button onClick={this.handleDec}>-</button>
</div>
);
}
}
复制代码
上例中,对 count
的更新会强制组件更新,对 unused
的更新不会致使从新渲染,由于 render
仅仅声明了对 count
的使用, render
又被高阶组件用 autorun
包装过,autorun
其实有返回值,用于销毁这个反作用,不过被 react
的 unmount
生命周期自动销毁了。函数
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
// 使用 observer 直接包装函数组件
const Counter = observer(() => {
// 使用 useLocalStore 建立一个局部的 observable
const local = useLocalStore(() => ({
count: 0,
unused: 0,
handleInc() {
this.count++;
},
handleDec() {
this.unused--;
}
}));
console.log("render");
return (
<div>
{local.count}
<button onClick={() => local.handleInc()}>+</button>
<button onClick={() => local.handleDec()}>-</button>
</div>
);
});
复制代码
注意:使用 observer
: 这个 autorun
的上下文仅仅用于当前 render
直接访问的属性,若是对 observable
属性的访问发生在子元素的 props
且为函数时,须要手动使用 <Observer render={()=><JSX>...</JSX>}/>
将其放入新的 autorun
上下文中,不然更新不会生效。
observable
对象能够直接在外面建立 observable
对象或类,再用 observer
消费它。这里介绍一下使用全局 Store
的方式。
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
const store1 = observable({
a: 1,
b: "hello",
incA() {
this.a++;
},
repeatB() {
this.b += this.b;
},
asyncIncA() {
setTimeout(() => {
this.a++;
}, 1000);
}
});
// 主要代码
//// {
const stores = {
store1
};
type TStore = typeof stores;
const storeCtx = React.createContext<TStore>(stores);
const StoreProvider = ({ children }) => (
<storeCtx.Provider value={stores}>{children}</storeCtx.Provider>
);
const useSore = () => React.useContext(storeCtx);
//// }
// 主要代码
const UsingStore = observer(() => {
const { store1 } = useSore();
return (
<div>
<div>a:{store1.a}</div>
<div>b:{store1.b}</div>
<button onClick={() => store1.incA()}>incA</button>
<button onClick={() => store1.asyncIncA()}>asyncIncA</button>
<button onClick={() => store1.repeatB()}>repeatB</button>
</div>
);
});
@observer
class UsingStoreInClass extends React.Component {
static contextType = storeCtx;
render() {
const { store1 } = this.context as TStore;
return (
<div>
<div>a:{store1.a}</div>
<div>b:{store1.b}</div>
<button onClick={() => store1.incA()}>incA</button>
<button onClick={() => store1.repeatB()}>repeatB</button>
</div>
);
}
}
const App = () => {
return (
<>
<StoreProvider>
<UsingStore />
<br />
<UsingStoreInClass />
<br />
</StoreProvider>
</>
);
};
复制代码
主要的代码段就是建立一个 stores
并放入 Context
,以后类组件和函数组件都用 observer
装饰,从 Context
拿出这个全局状态使用,一旦这个全局状态有更新,相关的组件都会被通知到并从新 render
。
这里仍是要多说一句,不要把应用的所有状态放在全局 Store
里面,这样状态管理的难度会大大增长,内存资源的释放每每不到位,应该交由局部的状态让 react
的生命周期函数来帮咱们作这些事情,尤为是 React v16.8
提供的 Hooks
就是不错的选择 ,应该只把一些 关键的全局可变状态 放入全局 Store
,好比用户信息。
当使用 observable
包装一个对象或属性时,会递归的将其转换成 observable
,在 console.log
查看调试的时候很不方便,充满了 Proxy
(若是用的是 Mobx 5.x
),可使用 mobx.toJS
来将其转换成一个普通的对象
有些时候,递归将属性转成 observable
粒度太细了,很不必,其实也能够减小这部分的 Proxy
开销,方法是使用对属性使用 observable.ref
、observable.shallow
,或者对属性直接用 observable.object
、observable.array
、observable.map
建立时传入 option
{deep:false}
来调节。
一些复杂场景下,计算属性每每是根据依赖异步获取的,使用 computed
显得不合适,可使用多个 observable
并用 reaction
来执行获取逻辑。
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable, reaction, autorun } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
class ChainDemo {
@observable a = 0;
@observable b = 0;
@observable c = 0;
@observable d = 0;
init = () => {
const disposer = [
reaction(
() => {
const val = this.a;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.b = await p;
}
),
reaction(
() => {
const val = this.b;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.c = await p;
}
),
reaction(
() => {
const val = this.c;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.d = await p;
}
)
];
return () => disposer.forEach(d=>d());
};
}
const chain = new ChainDemo();
chain.init();
autorun(() => {
console.log(chain.a, chain.b, chain.c, chain.d);
});
chain.a = 2;
/**
* 0 0 0 0
* 2 0 0 0
* 2 3 0 0
* 2 3 4 0
* 2 3 4 5
* /
复制代码
上例中使用了几个延迟计算取值,状态根据咱们描述的 react
链逐步更新,变化快时能够配合 flow
和 debounce
作更加细粒度,可控的性能优化。
react
生命周期管理局部的 observable
状态接着上面的代码继续看这个例子,一系列的 reaction
返回了不少的 disposer
用于销毁反作用,因此把这个 init
直接放在 useEffect
去执行简直是完美,利用组件的生命周期完成状态的初始化和销毁。
const Comp = observer(() => {
const [state] = useState(() => new ChainDemo());
useEffect(state.init, [state]);
});
复制代码
以为不错就点个赞呐~