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

posted @ 2021-02-20 16:05  小猪冒泡  阅读(959)  评论(0编辑  收藏  举报