Preface
-
MobX 已經存在 6.0 版本
- 相較於 5.0 版本,6.0 將默認禁用 5.0 版本的 decorate,它將使用新的 API 去替代 decorate. 參見:MobX EN 6.X
如果你想從 MobX 4.x/5.x 版本遷移到 6.x 版本…唔,我知道這在大型項目中很困難,所以我們提供了一個解決方法:MobX 6.x 版本(它在這段:Migrating an entire code-base from decorators to makeObservablemight be challenging)
而使用非裝飾器的原因除了它們不是標準的以外,還有因為裝飾器無法和 React Hook 一起結合使用,即:裝飾器只能用於裝飾 class component,而不是裝飾函數式組件。
What's the MobX ?
MobX 是一個狀態管理 library,;同樣的,有相似作用的有:Redux,Vuex,它們都是對狀態進行管理,只不過可能其實現過程不同。
其中 MobX 是通過透明的函數響應式編程 (transparently applying functional reactive programming - TFRP) 來對狀態進行管理並使得狀態管理變得簡單和可擴展
MobX 背後的哲學:任何源自應用狀態的東西都應該自動地獲得。
TIP: React 和 MobX 是一對強力組合。React 通過提供機制把應用狀態轉換為可渲染組件樹並對其進行渲染。而 MobX 提供機制來存儲和更新應用狀態供 React 使用。
Use MobX in React
-
首先需要安裝 mobx 和 mobx-react
TIP: mobx-react-lite 更適用於 react hook,即:沒有 class components 的項目,雖然 mobx-react 已經集成了 mobx-react-lite,但是它更輕便。
下載使用:
npm i --save mobx-react-lite# 建議使用 yarn yarn add mobx --save yarn add mobx-react --save # or npm install --save mobx mobx-react-
值得一提的是:如果你完全使用 MobX 6.x 版本,那麼你可能不需要使用到以下的將裝飾器編譯,這是因為 MobX 6.x 版本將默認不啓用裝飾器,你也完全不需要使用它。
-
- 在
tsconfig.json中啓用編譯器選項"experimentalDecorators": true -
安裝 babel-preset-mobx 和 babel-plugin-transform-decorators-legacy
npm i --save-dev babel-preset-mobx babel-plugin-transform-decorators-legacy並在
.babelrc中添加以下配置:{ "presets": [ "mobx", "es2015", "stage-1 ], "plugins": ["transform-decorators-legacy"] }TIP:插件的順序很重要:
transform-decorators-legacy應該放在首位。 -
然後在文件中導入即可,如:
import { observer } from "mobx-react";import { observable, computed, action } from "mobx";
參考文檔:
- Mobx-啓用裝飾器語法
Api CN-MobX 5.x | EN-MobX 6.x
您可以購買 Mobx 6 的備忘單,它只有一頁,但是它含有 Mobx 中所有重要的的 API,並且針對 MobX 6.x 進行了更新,包括 mobx-react/-lite.
makeObservable(MobX 6.x)
用法
makeObservable(target, annotations?, options?)
PS:makeObservable 只能用於 class component 中
介紹
target
makeObservable 可以用來捕獲現有對象的屬性並使得這些屬性成為可觀察屬性,我們可以把任何 JS 對象包括 class 的實例(這和 observable 不同,observable 永遠無法觀察 class 實例)傳遞到 makeObservable 的第 1 個參數 target 中,
但請注意:通常,我們會把 makeObservable 用在 class 的 constructor,並使得它的第 1 個參數 target 為 this:
class A {
constructor() {
makeObservable(this,{})
}
}
功能性並且具有參數的函數不需要任何註釋,如:findUsersOlderThan(age: number): User[]
原文:Methods that derive information and take arguments (for example findUsersOlderThan(age: number): User[]) don't need any annotation.
從 reaction 調用它們時,將會跟蹤它們的讀取操作,但是不會記住它們的輸出,以防內存泄漏
原文:Their read operations will still be tracked when they are called from a reaction, but their output won't be memoized to avoid memory leaks.
看看這個: MobX-utils computedFn {🚀}
annotations?
makeObservable 的第 2 個參數通常是一個對象,用來映射第 1 個參數對象(通常為 this)的每個屬性,我們為這些屬性賦予 mobx 中的各種 api,標誌着它們的用處:
// 在 class 中使用 makeObservable 的實例
import { makeObservable, observable, computed, action } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
}
// 在工廠函數中使用 makeAutoObservable 的實例
// 注意:這裏用的是 makeAutoObservable
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
/**
* NOTE:class 也可以利用 makeAutoObservable。
* 這裏之所以使用 makeAutoObservable,是為了演示這些示例之間的差異,僅僅説明了如何將 MobX 應用於不同的編程樣式。
*/
options?
makeObservable 和 makeAutoObservable 它們的第 3 個參數的行為是一樣的。
makeAutoObservable(Mobx 6.x)
用法
makeAutoObservable(target, overrides?, options?)
PS:makeAutoObservable 只能用於 class component 中
介紹
makeAutoObservable 類似於 steroids 上的 makeObservable,
原文:makeAutoObservableis likemakeObservableon steroids
因為它會默認推斷所有屬性,其推斷規則為:Interference rules
但是你仍然可以使用帶有特定註釋的 overrides 參數來覆蓋默認的推斷行為。
TIP:使用 makeAutoObservable 和使用 makeObservable 相比,makeAutoObservable 函數更加緊湊且易於維護,因為新成員不必明確提及。
Note
makeAutoObservable 無法用在具有 super() 或子類上。
makeObservable VS makeAutoObservable
makeAutoObservable() 和 makeObservable() 相比,其結構更加緊湊與容易維護,這是因為 new members don't have to be mentioned explicitly(新成員不必明確提及)。
Howerver,在具有 super 或 subclassed(子類中) 的 class 中,無法使用 makeAutoObservable(),否則會在編譯完成後運行報錯:
Error: [MobX] 'makeAutoObservable' can only be used for classes that don't have a superclass,有如下示例:
class A {
constructor() {
makeAutoObservable(this)
}
};
class B extends A {
constructor() {
super()
makeAutoObservable(this) //error,這會在編譯完成後運行時失敗
}
}
參考文檔
- makeAutoObservable(Mobx 6.x)
CN-(@)observable|EN-observable
用法
observable(source, overrides?, options?)EN-MobX 6.xobservable(value)CN-MobX 5.x@observable classProperty = valueCN-MobX 5.x
NOTE:MobX 6.x 中,@observable 是可選的。
以上的三種用法其行為是等價的,只不過 MobX 6.x 中,似乎多了兩個參數,我將會在後面解釋。
在這裏,筆者始終建議使用第 1 種寫法,它更為的安全以及夠新(2020-12-11),或者説是標準。
介紹
在本節中,我將以 MobX 6.x 中的
observable去介紹。
observable(source, overrides?, options?)MobX 6.x
source
當我們調用 observable 函數時,向之傳遞 source 參數,那麼 mobx 就會觀察到整個 source 對象一次,並且 mobx 將會克隆你傳過來的 source 參數對象(克隆的對象是一個 Proxy(代理) 對象),同時會觀察 source 對象的所有成員(觀察 source,而非克隆 source 的對象),使它們變為可觀察屬性,這類似於 makAutoObservable 的實現。
簡單來説: observable() 將返回一個源對象的 Proxy 對象,這意味着:你向 observable() 傳遞的 source 即使在未來又添加了成員,那麼該成員也將自動的成為 mobx 的可觀察成員(除非禁用了 Proxy 用法),
sure,我們也可以向 observable() 中傳入集合類型,如:Set、Map、Array。同樣的,mobx 也會克隆這些集合類型(一個 Proxy)並將之轉為可觀察對象,即:這些集合類型即使在未來添加了成員,這些添加的成員也將轉為可觀察成員。
overrides?
你可以提供一個 overrides 映射(override map)來為特定成員指定註釋(annotations),這個行為類似於 makeAutoObservable:
makeObservable(this, {
value: observable,
double: computed,
increment: action
})
// 直觀示例
observable({
setAge(){}
},{
setAge:action
})
在以上的直觀示例中,我們使用向 observable 傳入第 2 個參數(overrides)來改變 MobX 的 默認註釋推斷規則,使得 observable 觀察的對象中的 setAge() 的註釋從 autoAction(默認) 改變為 action(自設置)。
options?
The above APIs take an optional
optionsargument which is an object that supports the following options:
autoBind: trueautomatically binds all created actions to the instance.deep: falseusesobservable.refby default, rather thanobservableto create new observable members.name: <string>gives the object a debug name that is printed in error messages and reflection APIs.
使用 done 排除屬性或方法
你可以使用 false 把一個正在處理的屬性或方法排除,PS:這需要你手動去做它,使用 done:boolean 去標誌一個需要排除的屬性或方法。
import { observable } from "mobx"
const todosById = observable({
"TODO-123": {
title: "find a decent task management system",
done: false
}
})
todosById["TODO-456"] = {
title: "close all tickets older than two weeks",
done: true
}
const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
/**
In contrast to the first example with makeObservable, observable supports adding (and removing) fields to an object. This makes observable great for collections like dynamically keyed objects, arrays, Maps and Sets.
與 makeObservable 的第一個示例不同,observable 支持向對象添加(和刪除)字段。這使得 observable 非常適合於動態鍵控對象、數組、映射和集合等集合。
*/
PS:這點在 makeAutoObservable 中 In particular false can be used...
Note
make(Auto)Observable and observable 的主要區別
make(Auto)Observable and observable 的主要區別是:observable 的第一個參數將接收一個需要被觀察的對象,同時它還會創建這個可觀察對象的克隆對象(Proxy 對象)。
第 2 個不同點就是:observable 會創建一個 Proxy 對象,來防止你把一個對象視作動態的查找映射(即:防止你在未來往這個對象上繼續添加屬性),這是因為創建的這個 Proxy 對象能夠捕獲未來添加的屬性。
簡單來説:observable 會創建一個 Proxy 對象,來捕獲[被代理對象]以後可能添加的屬性,使得你使用 observable 觀察到的對象在以後添加屬性時,這些屬性也將是可觀察的。
TIP:如果你想要使可觀察的對象具有一個規則結構,並且你保證其中所有成員都已預先聲明,那麼我們建議使用 makeObservable,因為非代理對象的速度更快,並且非代理對象更加容易 debugger 和 console.log 中進行審查。
因此,在工廠函數中推薦使用 make(Auto)Observable API。
值得一提的是:未來有可能通過 {proxy: false} 作為一個 observable 的選項得到一個非代理的克隆。
基本類型和類實例無法將之轉為可觀察的數據
observable 無法將基本類型(原始值)和類實例(new class())轉變成可觀察的數據。
其中前者(基本類型轉為可觀察的數據)在 MobX 中,沒有任何方法可以將之轉為可觀察對象;而後者(類實例)在 MobX 中存在方法(如:observe)將之轉為可觀察的數據,只是 observable 做不到罷了。
而 MobX 無法把基本類型轉為可觀察的數據的原因很簡單:在 JS 中,基本類型(原始值)是不可改變的/%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B(%E5%8E%9F%E5%A7%8B%E7%B1%BB%E5%9E%8B).md#%E5%AE%9A%E4%B9%89)-見這裏,這條規則是定死的!
而把類實例傳遞給 observable 或一個已經使用 observable 觀察的對象作為其屬性,都永遠不會使得類實例成為一個可觀察的數據是因為:讓 class 中的成員轉為可觀察的狀態是 class constructor 的責任。
且即使你將一個可觀察的對象(observable(obj))中的某個屬性——一個基本類型的值或一個類實例,當作 props 傳遞給子組件,即使該子組件是響應式的(@observer class... / observer(class)),也無法在你改變 obj.value 時,去進行響應。
可用的方法
示例
組件中屬性無法更新?
Reference
- EN-observable MobX 6.x
- CN-observable MobX 5.x
Interference rules
概念
MobX 中存在自己的一套推斷註釋的規則。
簡單來説就是:當你使用 observable 或 make[Auto]Observable 去觀察一個對象時,那麼 MobX 將對該對象中的屬性、函數、get() 等進行默認註釋,其中默認註釋推斷規則如下:
-
所有包含 function 值的成員(屬性)都將使用
autoAction進行註釋。TIP:這是類似繼承鏈的關係,即:對象的屬性若是一個對象,在該子對象中若存在 function,則也會註釋為:
autoAction - 所有 get 屬性都將註釋為:
computed - 所有其他字段都將註釋為:
observable - [機翻]作為生成器函數的任何(繼承的)成員都將被標註為“流”。(請注意,在某些transpiler配置中,生成器功能是無法檢測到的,如果流程沒有按預期工作,請確保明確指定“流程”。)
- 在
overrides參數中將不會註釋帶有false的成員標記,如:使用只讀字段(標識符)
原文:
- Any (inherited) member that contains a
functionvalue will be annotated withautoAction.- Any
getter will be annotated withcomputed.- Any other own field will be marked with
observable.- Any (inherited) member that is a generator function will be annotated with
flow. (Note that generators functions are not detectable in some transpiler configurations, if flow doesn't work as expected, make sure to specifyflowexplicitly.)- Members marked with
falsein theoverridesargument will not be annotated. For example, using it for read only fields such as identifiers.
PS:在 makeAutoObservable 一節中,我們已經提到過!
示例
一個更直觀且闡述了 mobx 中(註釋)推斷規則的例子:
var person = observable({
/**
* mobx 會將以下的 3 個成員(屬性)註釋為:observable
*/
name: "John",
age: 42,
showAge: false,
// mobx 將推斷該 get 的註釋為:computed
get labelText() {
return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
},
/** 動作
* 本來 mobx 將會推斷 setAge() 函數的註釋為:autoAction,
* 但由於我們傳遞給 observable() 的第 2 個參數將之重新設置了註釋
* 所以此時 mobx 會將 setAge() 註釋為:action
*/
setAge(age) {
this.age = age;
}
}, {
setAge: action
// 其他屬性默認為 observable / computed
});
Reactions(反應) & Derivations(衍生)
CN-(@)observer(MobX 5.x) | EN-observer(MobX 6.x)
用法
-
class MyComponent extends React.Component { ... }) observer(MyComponent) // 等價於 const MyComponent = () => observer( (prpos:any)=>{...} ) // 建議使用 -
// 裝飾器是可選的!MobX 6.x 中也是如此。 @observer class MyComponent extends React.Component { ... }) observer(React.createClass({ ... }))observer((props, context) => ReactElement)observer(class MyComponent extends React.Component { ... }+
以上 5 中用法其效果是等價的,但是前面兩種是最常見的用法。
不過在這裏,筆者始終建議使用第一種,即:不要在 MobX 中使用裝飾器(這是非標準的,參見:從 MobX 4/5 遷移到 6)
PS:也請根據實際情況選擇 Mobx 的相應版本。
介紹
observer 函數/裝飾器可以用來將 React 組件轉變成響應式組件。 類似於 class 的組件中使用 makeAutoObservable(this) 使得當前 class 組件的實例變為響應式的。
它用 mobx.autorun 包裝了組件的 render 函數,以確保當任何組件的渲染中(render 中)使用的[被觀察的數據]變化時都可以強制刷新組件。
簡單來説就是:當組件的 render 中存在的[被觀察的狀態]發生改變時,將會強制刷新組件,即:重新執行一次 render,這和 setState() 或 useState Hook 的行為一致,它們都會重新刷新組件(重新執行一次 render)。
並且 observer 還會確保當組件中的[被觀察的狀態]沒有發生改變時,組件不會重新進行渲染,因此:即使組件中存在[被觀察的狀態],但是該狀態從未改變過,那麼該組件將永遠不會因為這個狀態導致重新渲染。
observer 不關心可觀察狀態是怎麼存於組件中的,它只需要讀取組件中可觀察狀態即可。
Note 注意點
observer 是 mobx-react 包中的
observer 是由單獨的 mobx-react or mobx-react-lite(mobx-react 集成 mobx-react-lite) 包提供的。
確保 observer 是第 1 個裝飾器或函數
當 observer 需要組合其它裝飾器或高階組件時,請確保 observer 是最深處(第一個應用)的裝飾器,否則它可能什麼都不做。
禁止將 observervable 的值傳遞到非 observer 組件
不要將使用 observable 觀察到的值的 Proxy 傳遞到一個非 observer 的組件中。參見:MobX 6.x-Don't pass observables into components that aren't observer
class Todo {
title = "test"
done = true
constructor() {makeAutoObservable(this)}
}
const todo = new Todo();
const TodoView = observer( ({ todo }: { todo: Todo }) =>
// 錯誤!GridRow 組件將不會因為 todo.titie 或 todo.done 改變從而重新渲染
return <GridRow data={todo} />
// 正確!讓 TodoView 組件檢查 Todo 可觀察狀態的改變,並傳遞 JS 原始數據結構。
return <GridRow data={{
title: todo.title,
done: todo.done
}} />
// 正確,使用工具函數 'toJs' 當然也是好的,但是前一個更為簡單直白。
return <GridRow data={toJS(todo)} />
)
<TodoView todo={todo} />
- EN-toJS-MobX 6.x
- CN-toJS-MobX 5.x
以上示例中正確的原因:您可以看看此處,以及這麼做的原因。
/*
* 這是因為當 observable 等函數去裝飾狀態(數據)時,會返回一個 Proxy 對象,而 mobx
* 正是通過這個 Proxy 去監聽數據改變並作出對應處理的。
* 但是你如果將一個 Proxy 對象傳遞給一個非 observer 的 React 組件這是沒有任何用處的,
* 所以你需要以普通的 JS 數據結構傳遞給非 observer 組件,使得包裹非 observer 組件的
* observer 組件能觀察在非 observer 組件上的變化,而不是讓非 observer 組件自己去觀察!
* 這樣,才能使得 observable 值更新時,非 observer 組件也能進行重新渲染。
*/
其原理是:GridRow 是屬於 TodoView 的子組件,當 GirdRow 中的 Proxy 的原始數據發生改變後,TodoView 將會因為 observer() 去強制更新 render,而這裏的 render() 所執行的代碼包含 GridRow,所以也就相當於去更新了 GridRow.
參見更多示例
observer 組件訪問其他模塊中的 observable
當我們使用 observer 去使一個 React 組件變成響應式時,那麼在該響應式組件的渲染函數中(可能是 class 組件的 render 或函數式組件的 return),
若存在當前模塊(當前組件所屬文件)的可觀察狀態,那麼若該可觀察狀態改變,則響應式組件的的渲染函數將會強制重新執行。
若還存在其他模塊(其他文件)的可觀察狀態(如:通過 inject 注入過來的可觀察數據),那麼若我們直接在渲染函數中使用其他模塊的可觀察狀態(如:props.store(訪問 inject 注入的 stroe)),
即使其他模塊的可觀察狀態通過某種方式改變,當前響應式組件也並不會強制重新執行渲染函數。
這通常是因為你在 observer 組件的渲染函數中訪問的是其他模塊的 observable 函數返回的 Proxy 對象,而非直接訪問其他模塊的 observable 值,如:
// store.tsx
class Store {
todos: any = [] // 存放所有 list
constructor() {makeAutoObservable(this)}
}
const store = new Store()
export default store
// a.tsx 修改前
const TodoHeader = inject('store')(observer(
(props: any) =>(<div> {console.log(props.store.todos)}</div>)
))
在 a.tsx 中,當 props.store.todos 發生改變時,TodoHeader 並不會重新渲染。
這是因為:TodoHeader 的渲染函數中訪問的是 props.store.todos 的 Proxy 對象,而不是其本身,但是對於 MobX 來説,只有使用 observable 直接觀察的 value 才能使得 Reactions 進行響應。
所以自然的,這裏的 TodoHeader 並不會重新渲染;若要讓它重新渲染,則必須使得 TodoHeader 訪問到 store.todos 的本身,而非 Proxy 對象,做如下更改:
// a.tsx 修改後
const TodoHeader = inject('store')(observer(
(props: any) =>(<div> {console.log(...props.store.todos)}</div>)
// 上行做法有時也無法觸發組件響應,可能是因為一些其他原因,所以有更好的寫法,即:訪問 store.todos 下的具體的某個 observable 值。
(props: any) =>(props.store.todos.map((todo) => { console.log(todo.finished) }))
))
應該將 observer 用於有訪問 observable value 的所有組件
在使用 observer 裝飾/包裹組件時,應該確保該組件的渲染函數中有訪問某個 observable,否則我們不應該使用 observer.
參考文檔
- [CN-[@]observer](https://cn.mobx.js.org/refguide/observer-component.html#observer)(MobX 5.x)
- EN-observer(MobX 6.x)
- https://cn.mobx.js.org/refguide/api.html
CN-(@)autorun(MobX 5.x) | EN-autorun(MobX 6.x)
用法
-
autorun(() => { sideEffect }, options?)MobX 5.xoptions?
-
autorun(effect: (reaction) => void)MobX 6.x實際上使用起來和 MobX 5.x 是一樣的,參見:MobX 6.x Example
介紹
autorun 創建一個響應式函數:當提供的函數中,可觀察的狀態改變時,就調用的響應式函數。
autorun 主要是用來執行 啓動效果 (initiating effects) 的一個函數,記住 :永遠不要用 autorun 去產生一個新值,這是 computed 該做的事情。
使用 autorun 時,會立即調用一次提供的函數。
autorun 函數只會觀察[提供的函數中所使用的數據],即:autorun 本身雖然會返回一個函數,但是調用它是無效的:
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
// 輸出 '6',這是因為使用 autorun 時,會立即調用一次提供的函數。
var disposer = autorun(() => console.log(sum.get()));
numbers.push(4); // 輸出 '10'
// 調用 autorun 返回的函數沒有任何用處
disposer();
// 不會再輸出任何值。`sum` 不會再重新計算。
numbers.push(5);
CN-reaction | EN-reaction
用法
-
// MobX 5.x https://cn.mobx.js.org/refguide/reaction.html#reaction reaction( () => data, // 稱之為: data funciton (data, reaction) => { sideEffect }, // 稱之為: effect function options? ) -
// MobX 6.x https://mobx.js.org/reactions.html#reaction reaction( () => value, // 稱之為: data funciton (value, previousValue, reaction) => { sideEffect }, // 稱之為: effect function options? )
以 MobX 5.x 介紹
reaction 接收 3 個參數(2 個函數,1 個配置選項):
-
data funciton
data function用來追蹤observablevalue,並返回一個 data(你想要返回的任何數據),然後此 data 將會作為effect function的第 1 個參數, -
effect function: function(data, reaction)
effect function接收 2 個參數:-
data
data function的返回值 -
reaction
當前的 reaction,可以用來在執行期間清理
reaction
effect function是用來執行動作的,且effech funciton僅僅只對data function中訪問(存在)的observable做出反應。 -
- options?(MobX 5.x)
返回值:返回一個清理函數
介紹
reaction 類似於 autorun,但不同的是:對於如何追蹤 observable 的數據提供了更為細粒度的控制。
reaction 的第 1 個參數函數的返回值只要改變(一開始賦予的初始值不算改變其返回值),則第 2 個參數函數就會執行;若第 1 個參數函數的返回值永遠未改變,則第 2 個參數就永遠不會執行。
示例
import { observable, action, reaction, computed } from 'mobx';
import { observer } from "mobx-react";
import { Component } from 'react';
import ReactDOM from "react-dom";
// 觀察一個對象
let obj = observable({
hungryLevel: 100,
})
// 使得 Animal 變成響應式的
@observer
class Animal extends Component {
constructor(props: any) {
super(props)
// reaction 也可以放在組件外面
reaction(
() => this.isHungry,
isHungry => {
// 如果飢餓水平 < 50,則輸出:我餓了
if (isHungry) { console.log("我餓") }
else { console.log("我不餓") }
console.log("目前飢餓水平:", obj.hungryLevel)
})
}
// 飢餓水平 累減 10
@action reduceEnergy() { obj.hungryLevel -= 10 }
// 當飢餓水平 < 50,則返回 true(我餓了),反之我不餓
@computed get isHungry() { return obj.hungryLevel < 50 }
render() { return (<div></div>) }
}
const giraffe = new Animal("")
console.log("現在開始改變可觀察狀態:obj.hungryLevel")
for (let i = 0; i < 10; i++) {
giraffe.reduceEnergy()
}
ReactDOM.render(
// <Computed />,
<Animal />,
document.getElementById('root')
);
/** 將會輸出:
* 現在開始改變可觀察狀態:obj.hungryLevel
* 我餓
* 目前飢餓水平: 40
*/
CN-when | EN-when
用法
-
// MobX 5.x when( predicate: () => boolean, effect?: () => void, options? ) -
// MobX 6.x when( predicate: () => boolean, effect?: () => void, options? ) -
// MobX 6.x when( predicate: () => boolean, options? ): Promise以上 3 種用法都是常見的,只不過第 1 種是 MobX 5.x 的,而第 2、3 種用法屬於 MobX 6.x。
介紹
when將一直觀察給定的predicate function,當predicate function的返回值為 true 時,effect function就會自動執行。且
when函數將會返回一個handler,你可以使用這個handler去手動使得when不再觀察predicate function。有意思的是,若你不提供第 2 個參數:
effect function,那麼when函數將會返回一個 Promise,謹慎使用 Reactions
理解 Reactions
不論是
observer、reaction,以及autorun等 Reactions,它們都只會對observable/可觀察狀態做出響應,其中存在的一些坑是非常危險的,它們可能會導致你的程序不會預期執行。你可以看看這個小節:observer 組件訪問其他模塊中的
observable">observer組件訪問其他模塊中的observableCN-(@)computed(MobX 5.x) | EN-computed(MobX 6.x)
用法
computed(() => expression)computed(() => expression, (newValue) => void)computed(() => expression, options)@computed({equals: compareFn}) get classProperty() { return expression; }-
@computed get classProperty() { return expression; }以下為 MobX 6.x 的用法:參見這裏
computed(annotation)computed(options)(annotation)-
computed(fn, options?)介紹
computed專門用來根據現有的狀態(通常指:observablevalue)或其他計算值衍生出一個新值的computed是高度優化過後的,盡情隨意使用,不用擔心性能問題;注意:不要將
computed和autorun搞混,儘管它們都是響應式調用的表達式;即:若你想產生一個可以用於 observer 的新值,則請使用computed;但若你並不想產生一個新值,而只是想響應式的達到一個效果,則使用autorun,如:打印日誌、發送網絡請求等的 effect.使用
computed修飾過的表達式(通常是一個計算屬性(屬性函數),如:get/set)將是響應式的,當computed的 expression 中存在可觀察的狀態改變時,則computed整個表達式將會執行,而如果computed的 value 中不存在或存在的可觀察的狀態未發生改變,則computed表達式將不會執行——這就是所謂的響應式。:凡是使用
computed修飾的 value 都無法枚舉,因為計算屬性是不可枚舉的!示例
// MobX 5.x import {observable, computed} from "mobx";
class OrderLine {
@observable price = 0;
@observable amount = 1;
constructor(price) {
this.price = price;
}
// 計算屬性;會產生新值
@computed get total() {
return this.price * this.amount;
}
}
以上示例可以改成 `decorate` 方式:
// MobX 5.x
import {decorate, observable, computed} from "mobx";
class OrderLine {
price = 0;
amount = 1;
constructor(price) {
this.price = price;
}
get total() {
return this.price * this.amount;
}
}
decorate(OrderLine, {
price: observable,
amount: observable,
total: computed
})
我們也可以使用 MobX 6.x 書寫此示例:
// MobX 6.x,注:6.x 版本中,decorate 已移除
import {observable, computed, makeAutoObservable} from "mobx";
class OrderLine {
price = 0;
amount = 1;
constructor(price) {
this.price = price;
makeAutoObservable(this,{
price:observable,
amount:observable,
total:computed
})
}
get total() {
return this.price * this.amount;
}
}
## Action(MobX 5.x | MobX 6.x)
### [CN-(@)action(MobX 5.x)](https://suprise.github.io/mobx-cn/refguide/action.html) | [EN-action(MobX 6.x)](https://mobx.js.org/actions.html)
#### 用法
1. `action(fn)`
2. `@action.bound(function() {}`
3. `@action classMethod() {}`
4. `action(name, fn)`
5. `@action.bound classMethod() {}`
6. `@action(name) classMethod () {}`
7. `@action boundClassMethod = (args) => { body }`
8. `@action(name) boundClassMethod = (args) => { body }`
9. `action` *(annotation)* MobX 6.x
以上的用法都是常用的用法,你可以任意選擇你喜歡的。
#### 介紹
我們將改變應用狀態的動作稱之為“行為”,由此可見,任何應用都有行為。
在 MobX 中,所有行為都應該使用 action() 或 @action 將之包裹/註釋,當然即使你不這麼做,MobX 僅僅只會警示你,而不會讓你程序編譯失敗,但是這並不是好行為。
**使用 Action 讓你代碼更加易閲讀,清晰,代碼結構更優!**並且使用 Action 會給你有效的提示信息。
Action 返回一個使用 `untracked` , `transaction` and `allowStateChanges` 包裹的函數。
#### `async` 行為 和`runInAction`
`action` 隻影響**當前運行**的函數,不是當前被**調度**(不是調用)的函數。
如:一個`setTimeout`,promise `.then` 或 `async` 構造,在這些回調函數中將有狀態改變,那麼這些回調應該使用 `action` 包裹/註釋 =>
// 改變狀態的行為,應該使用 action 包裹,注意:並非包裹 setTimeOut,否則 setTimeout 將不會運行
setTimeout(action(()=>{
this.setState({
name:'yomua',
time:'2020-12-21 18:20'
})
}),1000)
### [action.bound](https://mobx.js.org/actions.html#actionbound)(MobX 6.x)
### [runInAction](https://mobx.js.org/actions.html#runinaction)(MobX 6.x)
### [使用 flow 替代 async/await(可閲讀知識)](https://mobx.js.org/actions.html#using-flow-instead-of-async--await-)(MobX 6.x)
### [Cancelling flows](https://mobx.js.org/actions.html#cancelling-flows-)(MobX 6.x)
## [tool-funciton API of mobx-react](https://github.com/mobxjs/mobx-react)
### inject [CN-inject](https://cn.mobx.js.org/refguide/api.html#inject-mobx-react-%E5%8C%85) | [EN-inject](https://github.com/mobxjs/mobx-react#provider-and-inject)
#### 用法
1. ```tsx
@inject("注入的屬性名")
MyComponent:你的組件
-
@inject(callback) callback 基本用法:(allStore,nextProps?,nextContext?)=>additionalProps MyComponent:你的組件callback 接收 3 個參數,且自身返回一個對象(additionalProps),該對象中的屬性就是我們能在組件中使用
this.props.propName訪問到的注入的屬性的值-
allStore
將所有可用的屬性(離當前使用 @inject 最近的 Provider 組件中的屬性)放入到該參數對象中
- nextProps?
- nextContext?
返回值:
{ value1:(allStore as any).Provider 組件上的屬性名 value2:(allStore as any).Provider 組件上的屬性名 ... }小示例:
@inject( allStore=> { value1:(allStore as any).Provider 組件上的屬性名, value2:(allStore as any).Provider 組件上的屬性名 } ) class A extends Component<{...}> { render() { return( <div> {this.props.value1} {this.props.value2} ... </div> ) } } -
-
@inject("store1", "store2") @observer MyComponent:你的組件這種是
@inject和@observer的組合寫法。請記住:
@inject始終在最外面,因為它屬於外部裝飾,而@observer屬於內部裝飾。 inject("store")(observer(MyComponent))
在以上用法中,前兩種是最常見的用法,第 3 個則是 @inject 和 @observer 的組合寫法,第 4 個則是不用裝飾器的寫法。
筆者始終建議使用第 4 種寫法,因為它夠簡潔,而且它是最新的(對於 MobX 6.x 來説)
介紹
mobx-react 提供的 inject 函數實現了 React 提供的 Context 機制,它可以讓我們在一個組件樹中,不必使得中間組件幫忙傳遞數據,就能使的頂層直接傳遞數據給底層,
其用法為:使用 mobx-react 提供的 Provider 組件,將之作為一個組件的根組件,那麼 Provider 組件包裹的組件以及一系列相關組件都能通過 inject,將 Provide 組件上的屬性注入到組件樹的任意組件中
從而在組件中通過 this.props.Provider 屬性名 訪問 Provider 組件的屬性。
下面看看這一個簡單的示例吧:使用 Provider 和 inject 完成數據傳遞
示例
使用 Provider 和 inject 完成數據傳遞(MobX 6.x)
環境:
"dependencies": {
"mobx": "^6.0.4",
"mobx-react": "^7.0.5",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"typescript": "^4.1.3",
},
import React, { Component } from 'react';
import { makeAutoObservable } from 'mobx';
import { Provider, inject } from 'mobx-react';
import ReactDOM from 'react-dom';
// 數據結構(Store)
class Todo {
todos = [
{ id: 1, title: '任務1', finished: false },
{ id: 2, title: '任務2', finished: false },
];
constructor() { makeAutoObservable(this) }
// computed 屬性(用來判斷)
get unfinishedTodoCount() { return this.todos.filter(todo => !todo.finished).length; }
}
let todoList = new Todo(); // 獲取 Store
// todoList 將可以通過 @inject 注入 <Yomua /> 一系列相關的組件樹中
const Testinject = () => (<Provider todoList={todoList}><Yomua /></Provider>)
const Yomua = () => (<ZY />) // 中間組件
const ZY = () => (<YHW />) // 中間組件
/** 使用 @inject(callback) 將 Provider 上的所有可用 Store 經過提煉之後注入到 YHW 組件中
* 注意:@inject 只能用於 class,或者説裝飾器只能用於 class 組件。
* 使用 @inject('todoList') 這種方式也可以
* 這裏的 allStore 指的是:<Provider /> 上所有要傳遞的 props
*/
@inject(allStore => (
// 可以通過 this.props.todoList 就能訪問到 Provider.todoList 屬性。
{ todoList: (allStore as any).todoList }
))
class YHW extends Component<{ todoList?: any }> {
render() {
// 返回 Todo.title
return (<div>{this.props.todoList.todos.map(todo => { console.log(todo.title) })}</div>)
}
}
export default Testinject;
ReactDOM.render(
<Testinject />,
document.getElementById('id')
)
/** 控制枱將輸出:
* 任務1
任務2
*/
在以上示例中,顯然的,我們的組件樹為:Provider 包裹 Yomua -> ZY -> YHW,而我們的目的是:將 Provider 上的 todoList 直接傳遞到 YHW 中,使得中間組件(Yomua、ZY)不需要幫忙傳遞。
在這裏,我們巧妙地利用了 mobx-react 提供的 inject 函數完成了這個目的。
即:我們使用 Provider 將 Yomua 包裹,使得 Provider 上的所有屬性都能通過 inject 傳遞給 Yomua 的一系列相關組件,然後我們在想要注入 Provider 的屬性的組件那,使用 @inject()/inject() 將 Provider 的屬性注入到當前組件,這樣,在當前組件的 JSX 中就可以使用 {this.props.xx} 訪問 Provider 的屬性!
真是方便的解決方案!
但是請記住,在使用 Provider 前不妨考慮組合組件?因為 Context 機制會使得組件複用性變差!
使用 inject 來完成一個簡單示例(非 @inject)
// 將 stroe 注入到 ComponentName 組件中,注意:這是標準的寫法!
const ComponentName = inject('store')(
// observer 是必要的
observer(
(props: any) => (<div>...</div>)
)
)
Reference
- CN-inject
- EN-Provider and inject
Pit
在 MobX6.x 中消除使用裝飾器的警告
警告提示:xxx 是新提案,以後可能刪除。消除它:
-
在 setting.json 中添加:
"javascript.implicitProjectConfig.experimentalDecorators": true, -
然後在項目內部的 tsconfig.json(若沒有則新建)中的 compilerOptions 中添加:
"compilerOptions":{ "experimentalDecorators": true, "emitDecoratorMetadata": true } - 最後轉到 IDE 的 settings(preferences - settings 或 ctrl+,),搜索 experimentalDecorators,在顯示的界面中,選中複選框,將之打勾
TIP:你可能需要重啓 IDE => ctrl+shift+p 搜索 reload window,然後點擊它即可。
注意:即使消除了警告,但是由於 Mobx 5.x 及以下版本使用的裝飾器都是可能在未來被改變,所以想要一勞永逸的解決,建議使用:MobX 6.x 版本
注意:在 MobX 6.x 中,decorator 已經重新添加,這意味着你仍然可以在 MobX 6 中使用裝飾器,參見:Mobx 6.x EN-第 5 點(For Typescript users)
參考文檔:
- Experimental decorators warning in TypeScript compilation
- How to restart VScode after editing extension's config?
- MobX 6.x
MobX 6.x 中刪除了 decorate
decorate API已在MobX 6中刪除,需要 makeObservable 在目標類的構造函數中替換。makeObservable 接受相同的參數。
Mobx and Redux
Reference
- Rdux 和 Mobx 區別
- Mobx 較為靈活,Redux 有點類似於 Vuex
Translate
we now recommend
mobx-react-liteovermobx-reactfor (greenfield) projects that don't use class components=> 對於不使用 class components 的項目,我們推薦使用
mobx-react-lite,而非mobx-react,
observable
概念和用法翻譯
observable(source, overrides?, options?)
可以把 observable 註釋作為一個函數調用,以便使得 mobx 立即觀察到整個對象。
mobx 將會克隆 observable() 中的第一個參數:source 對象,並且 mobx 會觀察 source 對象的所有成員(觀察 source,而非克隆 source 的對象),這類似於 makAutoObservable 的實現。
同樣的,你可以提供一個 overrides 參數映射(override map)來指定特定成員的註釋。
observable() 將返回一個 Proxy 對象,這意味着:你向 observable() 傳遞的 source 即使在未來又添加了成員,那麼該成員也將自動的成為 mobx 的可觀察成員(除非禁用了 Proxy 用法)
也可以向 observable() 中傳入集合類型,如:Set、Map、Array,同樣的,mobx 也就克隆這些集合類型(一個 Proxy)並將之轉為可觀察對象,即:這些集合類型即使在未來添加了成員,這些添加的成員也將轉為可觀察成員。
TIP 翻譯
make(Auto)Observable and observable 的主要區別是:observable 的第一個參數將接收一個需要被觀察的對象,同時它還會創建這個可觀察對象的克隆。
第 2 個不同點就是:observable 會創建一個 Proxy 對象,來防止你把一個對象視作動態的查找映射,這是因為創建的這個 Proxy 對象能夠捕獲未來添加的屬性。
簡單來説:observable 會創建一個 Proxy 對象,來捕獲被代理對象以後可能添加的屬性,使得你使用 observable 觀察到的對象在以後添加屬性時,這些屬性也將是可觀察的。
如果你想要使可觀察的對象具有一個規則結構,並且其中所有成員都是預先聲明的,那麼我們建議使用 makeObservable,因為非代理對象的速度更快,並且非代理對象更加容易 debugger 和 console.log 中進行審查。
因此,在工廠函數中推薦使用 make(Auto)Observable API,請注意:未來有可能通過 {proxy: false} 作為一個 observable 的選項得到一個非代理的克隆。
Reference
- MobX CN 5.X => 中文文檔目前只更新到 5.x | 2020-12-09
-
MobX EN 6.X
- MobX-6
- Announcing mobx 6(官方發佈)
-
Proposal: About drop decorators
PS:裝飾器最後並沒有從 MobX 6.x 中刪除,它只是默認禁用了。你可以詳見此處開啓它:消除新提案(裝飾器)的警告