mobx知识梳理
1、observable
1.1 引用类型
observable 可以观察所有类型的数据,其中对于 object、array、map 等类型,经过 observable 之后,生成全新的 Observable 类型数据,
但是仍然保留了相应获取数据的方法,比如
imoprt { observable } from "mobx";
var obj = observable({
x:1,
y:2
})
console.log(obj);//不再是一个单纯的object对象,进行了包裹处理
console.log(obj.x);
1.2 基础类型
使用 observable.box 包裹
var num = observable.box(20);
var str = observable.box("hello");
// 使用set修改其值
num.set(50);
str.set("world");
console.log(num.get(), str.get()); //50,world
在使用 class 类装饰器定义变量的时候 不用使用 observable.box,因为@observable 已经在内部做了封装区分基础类型和引用类型
imoprt { observable } from "mobx";
class Store{
@observable array = [];
@observable string = "hello";
}
computed
1、基本用法
computed 可以将多个可观察数据合并成一个观察数据
imoprt { observable,computed } from "mobx";
class Store{
@observable array = [];
@observable string = "hello";
@observable number = 0;
}
var store = new Store();
var foo = computed(function(){return store.string +':' +store.number})
console.log(foo.get());
2、computed 监听数据变化
imoprt { observable,computed } from "mobx";
class Store{
@observable array = [];
@observable string = "hello";
@observable number = 0;
}
var store = new Store();
var foo = computed(function(){return store.string +':' +store.number})
foo.observe(function(change){ //监听到数据变化
console.log(change)
})
store.string = "world";
store.number = 30;
computed 可以引用其他的 computed 值
3、在 class 类中使用
imoprt { observable,computed } from "mobx";
class Store{
@observable array = [];
@observable string = "hello";
@observable number = 0;
@computed get mixed(){ //这时就不能监听数据发生变化的事件了,所以引出了 autorun
return store.string +':' +store.number
}
}
autorun
可以监听其使用的观察数据,发生变化时触发事件
imoprt { observable,computed,autorun } from "mobx";
class Store{
@observable string = "hello";
@observable number = 0;
@computed get mixed(){ //这时就不能监听数据发生变化的事件了,所以引出了 autorun
return store.string +':' +store.number
}
}
var store = new Store();
autorun(()=>{
console.log(store.string +':' +store.number)
})
store.string = "world";
store.number = 30;
还可以监听 computed 中涉及到的变量变化
imoprt { observable,computed,autorun } from "mobx";
class Store{
@observable string = "hello";
@observable number = 0;
@computed get mixed(){ //这时就不能监听数据发生变化的事件了,所以引出了 autorun
return store.string +':' +store.number
}
}
var store = new Store();
autorun(()=>{
console.log(store.mixed)
})
store.string = "world";
store.number = 30;
when
when 有两个参数,第一个参数为 boolean 值,只有为 true 的时候才去执行第二个参数
注意:第一个参数必须是根据可观察数据计算得到的 boolean 值
imoprt { observable,computed,autorun,when } from "mobx";
class Store{
@observable bool = false;
}
var store = new Store();
when(()=>store.bool,()=>console.log("it is true"))
store.bool = true;
reaction
reaction 传入两个参数,第一个参数作为第二个函数的入参,第一次初始化可观察数据时,不会触发第二个函数参数
这样在初始化数据的时候,首先执行 reaction 的第一个参数,在第一个参数相关数据变化的时候,执行第二个函数参数。
使用场景:比如在没有数据的时候,我们不想执行保存缓存的逻辑,在有数据之后,才去进行后面的保存
imoprt { observable,computed,autorun,when,reaction } from "mobx";
class Store{
@observable bool = false;
@observable string = "hello";
@observable number = 0;
}
var store = new Store();
reaction(()=>[store.strting,store.number],arr=>console.log(arr))
store.string = "world";
store.number = 30;
mobx:修改可观察数据(action)
1、基本用法
如果使用
store.string = "world";
store.number = 30;
的形式,会执行两遍 reaction,使用 @action 修饰的函数后,执行一次
imoprt { observable,computed,autorun,when,reaction } from "mobx";
class Store{
@observable bool = false;
@observable string = "hello";
@observable number = 0;
@action bar(){
this.string = "world";
this.number = 30;
}
}
var store = new Store();
reaction(()=>[store.strting,store.number],arr=>console.log(arr))
store.bar();
2、使用 action.bound 来绑定上下文
一般用在将方法作为 callback
imoprt { observable,computed,autorun,when,reaction } from "mobx";
class Store{
@observable bool = false;
@observable string = "hello";
@observable number = 0;
@action.bound bar(){
this.string = "world";
this.number = 30;
}
}
var store = new Store();
reaction(()=>[store.strting,store.number],arr=>console.log(arr))
var myBar = store.bar;
myBar();
3、 runInAction
imoprt { runInAction,observable,computed,autorun,when,reaction } from "mobx";
class Store{
@observable bool = false;
@observable string = "hello";
@observable number = 0;
}
var store = new Store();
reaction(()=>[store.strting,store.number],arr=>console.log(arr))
runInAction(()=>{
store.string = "world";
store.number = 30;
})
4、tips
在工作台上打开 element,选择某个元素,出现$0,然后在控制台输入$0 即可获取到该元素
5、设置严格模式
强制改动 mobx 中变量使用 mobx 中的 @action 方式,避免在其他地方改动
入口 js 文件
import { configure } from "mobx";
configure({ enforceActions: "observed" });
这样在组件中直接修改 mobx 中可观察变量就会报错。
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.props.BirdStore.birds.unshift(bird);
};
6、 toJS
可以把代理 对象改成常规数组 console.log(toJS(store.birds));
如果没有初始值时,computed 有可能有问题,比如下面代码,在渲染 firstBirds 时总是 undefined
constructor() {
this.birds = [];
}
@computed get firstBirds() {
return `这是第一个鸟的名字:${this.birds[0]}`;
}
此时使用 toJS
constructor() {
this.birds = [];
}
@computed get firstBirds() {
return "第一只鸟的名字: " + toJS(this.birds)[0]
}
7、使用 autorun 的时机
当使用 autorun 时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。 相比之下,computed(function) 创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的。 经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,请使用 autorun。 其余情况都应该使用 computed。
在连接 react-mobx 时,通过 react 组件改变 action 响应,改变 state 值,不会触发 autorun
比如 stores 中
import { computed, observable, autorun, action } from "mobx";
class BirdStore {
@observable birds;
constructor() {
this.birds = ["qiuzhi99"];
}
@action addBird = (bird) => {
this.birds.unshift(bird);
};
@computed get firstBirds() {
return `这是第一个鸟的名字:${this.birds[0]}`;
}
}
const store = new BirdStore();
export default store;
autorun(() => {
console.log("hello");
console.log(store.birds);
});
在 react 组件中触发改变 birds
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.props.BirdStore.addBird(bird); //这里
};
不会触发 autorun,但是如果 autorun 监听了 computed 属性就可以执行 autorun
autorun(() => {
console.log("hello");
console.log(store.firstBirds);
});
此外,组件执行 render 需要监听到 store 中的可观察数据 observable
state 中的可观察对象最好有初始值,否则容易出问题
有多个 store 的时候
可以在每个 store 文件中先 new
import { observable, computed, action } from "mobx";
import TodoStore from "./TodoStore";
class TodoListStore {
@observable todos = [];
@computed
get unfinishedTodoCount() {
return this.todos.filter((todo) => !todo.finished).length;
}
@action
addTodo(title) {
this.todos.push(new TodoStore(title));
}
}
export default new TodoListStore();
然后在 store/index.js 文件中
import BirdStore from "./BirdStore";
import TodoListStore from "./TodoListStore";
export default {
BirdStore,
TodoListStore,
};
在组件中引入
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import stores from "./stores";
import { configure } from "mobx";
configure({ enforceActions: "observed" });
ReactDOM.render(<App {...stores} />, document.getElementById("root"));
使用 mobx-react 中的 Provider 将 stores 中的值传递到组件中去,避免在跟组件中使用 props 传递
入口文件 src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import stores from "./stores";
import { configure } from "mobx";
import { Provider } from "mobx-react";
configure({ enforceActions: "observed" });
ReactDOM.render(
<Provider {...stores}>
<App />
</Provider>,
document.getElementById("root")
);
由于 provider 引入了多个 store,所以要在组件中进行 inject
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import { observer, inject } from "mobx-react";
import DevTools from "mobx-react-devtools";
import Fun from "./Fun";
@inject("BirdStore") //先注入store,后面才能用到该BirdStore
@observer //观察class模式
class App extends Component {
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.props.BirdStore.addBird(bird);
};
render() {
return (
<div className="App">
<DevTools />
<header className="App-header">
<Fun />
{this.props.BirdStore.firstBird}
<form onSubmit={(e) => this.handleSubmit(e)}>
<input
type="text"
placeholder="Enter your bird name"
ref={(input) => (this.bird = input)}
/>
<button>Add Bird</button>
</form>
</header>
</div>
);
}
}
export default App;
无状态组件使用 mobx
import React from "react";
import { observer, inject } from "mobx-react";
//使用函数形式引入 store和observer进行函数包裹
const Fun = inject(
"TodoListStore",
"BirdStore"
)(
observer((props) => {
console.log("fun");
return <div>{props.TodoListStore.firstTodo}</div>;
})
);
export default Fun;
如果组件中每次使用 this.props.BirdStore 很繁琐:
handleSubmit = (e) => {
this.props.BirdStore.addBird(bird);
};
可以优化:
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.store.addBird(bird);
}
get store() {
return this.props.BirdStore
}
还可以把
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import { observer } from "mobx-react";
import { observer, inject } from "mobx-react";
@inject("TodoListStore", "BirdStore")
@observer
class App extends Component {
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.props.BirdStore.addBird(bird);
};
render() {
console.log("update");
return <div className="App"></div>;
}
}
export default App;
上面写法修改下
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import { observer } from "mobx-react";
class App extends Component {
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.props.BirdStore.addBird(bird);
};
render() {
console.log("update");
return <div className="App"></div>;
}
}
export default inject("TodoListStore", "BirdStore")(observer(App));
不要使用老版本写法:@observer['TodoListStore','BirdStore']
该方法包含了 inject 和 observer,但是在新版本中已经不再试用
最后还可以使用compose
库实现,但是在 hooks 中最好不要用了,不再更新该库了
import React, { Component } from "react";
import logo from "./logo.svg";
import "./App.css";
import { observer, inject } from "mobx-react";
import DevTools from "mobx-react-devtools";
import Fun from "./Fun";
import { compose } from "recompose";
// @inject('BirdStore', 'TodoListStore')
// @observer
class App extends Component {
handleSubmit = (e) => {
e.preventDefault();
const bird = this.bird.value;
this.store.addBird(bird);
// this.props.BirdStore.birds.unshift(bird);
};
get store() {
return this.props.BirdStore;
}
render() {
console.log("render");
return (
<div className="App">
<DevTools />
<header className="App-header">
<Fun />
{this.store.firstBird}
<form onSubmit={(e) => this.handleSubmit(e)}>
<input
type="text"
placeholder="Enter your bird name"
ref={(input) => (this.bird = input)}
/>
<button>Add Bird</button>
</form>
</header>
</div>
);
}
}
export default compose(inject("BirdStore", "TodoListStore"), observer)(App);
可以通过下面链接,调试接口
https://cnodejs.org/api
store 中异步请求的四种方式
import { observable, action, runInAction, flow } from "mobx";
class TopicStore {
@observable topics = [];
// 方式1
loadTopics() {
fetch("https://cnodejs.org/api/v1/topics")
.then((response) => response.json()) //response.json()转成json格式
.then(({ data }) => {
this.saveTopics(data);
});
}
@action
saveTopics(data) {
//这里改变的观察值,所以在这里使用了action
this.topics = data;
}
// 方式2,使用了runInAction,相当于随时随地使用了 action,不用在函数外层使用装饰器 @action
loadTopicsInline() {
fetch("https://cnodejs.org/api/v1/topics")
.then((response) => response.json())
.then(({ data }) => {
runInAction(() => {
this.topics = data;
});
});
}
// 方式3,使用async + await
loadTopicsAsync = async () => {
const response = await fetch("https://cnodejs.org/api/v1/topics");
const json = await response.json();
runInAction(() => {
this.topics = json.data;
});
};
// 4,使用了类似于Generator 函数,不用在调用runInAction
loadTopicsGenerator = flow(function* () {
const response = yield fetch("https://cnodejs.org/api/v1/topics");
const json = yield response.json();
this.topics = json.data;
});
}
export default new TopicStore();
action.bound 可以绑定 this 到当前的 class 上
【貌似在使用 settimeout 和 setInterval 时 this 容易出错】
class Ticker {
@observable tick = 0;
// 在函数定义的时候就绑定了正确的 this
@action.bound
increment() {
this.tick++; // 'this' 永远都是正确的
}
}
// window
const ticker = (window.ticker = new Ticker());
setInterval(ticker.increment, 1000);
可以通过 decorate 修饰 class,把一个正常的 class 类快速变成 mobx 可观察的类
import { decorate, observable, action, computed } from "mobx";
class ReviewStore {
reviewList = [
{ review: "This is a nice article", stars: 2 },
{ review: "A lovely review", stars: 3 },
];
addReview(e) {
this.reviewList.push(e);
}
get reviewCount() {
return this.reviewList.length;
}
get averageScore() {
let avr = 0;
this.reviewList.map((e) => (avr += e.stars));
return Math.round((avr / this.reviewList.length) * 100) / 100;
}
}
decorate(ReviewStore, {
reviewList: observable,
addReview: action,
reviewCount: computed,
averageScore: computed,
});
export default new ReviewStore();
无状态函数式组件可以通过下面方式 inject 到 mobx
import React, { Component } from "react";
import { inject, observer } from "mobx-react";
function Review({ data }) {
return <li className="list-group-item">{data.review}</li>;
}
function Reviews() {
console.log(this.props.ReviewStore);
return (
<div className="reviewsWrapper">
<ul className="list-group list-group-flush">
{this.props.ReviewStore.reviewList.map((e, i) => (
<Review key={i} data={e} />
))}
</ul>
</div>
);
}
export default inject("ReviewStore")(observer(Reviews)); //这里
可参考视频教程:
【1】https://www.qiuzhi99.com/playlists/react-mobx.html
【2】https://www.imooc.com/learn/1012