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