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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM