mobx工作原理
推薦版本: "mobx-react": "4.1.0", "mobx": "^2.7.0 || ^3.0.0"
1 Mobx 要點
1.1 定義狀態並使其可觀察
可以任何數據結構來存儲狀態,如對象、數組、類,打上mobx的標記會變為可觀察。
import { observable } from 'mobx';
var appStore = observable({
timer: 0;
});
1.2 創建視圖響應狀態變化
mobx 以最小限度更新視圖,任何函數都可以成為響應式視圖觀察自身數據。
import {observer} from 'mobx-react';
@observer
class TimerView extends React.Component {
render() {
return (
<button onClick={this.onReset.bind(this)}>
Seconds passed: {this.props.appState.timer}
</button>
);
}
onReset() {
this.props.appState.resetTimer();
}
};
ReactDOM.render(<TimerView appState={appStore} />, document.body);
1.3 更改狀態
mobx會用簡單直觀的方式更改狀態,使用action(可以配置Mobx強制使用action更新)或者直接修改
2 概念及原則
2.1 State 狀態
狀態是驅動應用的數據。像待辦事務列表的特定狀態,還有像當前已選元素的視圖狀態。
狀態就像有數據的 Excel表格。
2.2 derivations 衍生
什么是衍生, 源自狀態並且不會再有進一步的相互作用的東西就是衍生。
- 用戶界面
- 衍生數據,剩下的待辦事項
- 后端集成,比如吧變化發送到服務器端
mobx 集成了兩種類型的衍生
Computed values計算屬性值
使用純函數從當前可觀察狀態中衍生出的值。
Reactions 反應
是當 State 改變時需要自動發生的副作用。。需要有一個橋梁鏈接 函數式編程 和 響應式編程
初次使用mobx會經常使用reactions,但是推薦使用computed,回到表格的概念,公式是計算值的衍生。
Actions 動作
動作 任意一段可以改變狀態的代碼。 用戶操作,后端數據推送,預定事件等。
動作 類似在表格單元格中輸入一個新值。 mobx中可以顯示的定義動作,@action .
3 原則
mobx支持單向的數據流,動作來改變狀態,從而 狀態State 的改變會更新受影響的視圖。
action ---> state ---> view
當 State 改變時,所有衍生都會進行原子級自動更新 因此不可能觀察到中間值
全部的 衍生 默認都是同步更新 因此在動作之后,可以安全的檢查計算值
計算值 是延遲更新的。 任何不在使用狀態的計算值都不會更新,直到需要時會進行 副作用(IO),不使用時會自動垃圾回收
計算值 不應該去改變狀態,應該是一個純潔的副作用。
4 核心API
主要api: Computed 、 observable 、 reactions 、 actions
4.1 observable
observable(value);
@observable property = value;
Observable 觀察的值可以是基本類型、引用類型、普通對象、類實例、數組和映射。
主要類型轉換規則,或者通過裝飾器微調(修飾class,函數)
- 如果被觀察 value 是ES6實例,會返回一個新的Observe Map,基於ES6。如果不只是在更改某個entry時修改
而是,在添加或刪除其他entry時做出反應,Observe Map 會很有用。 - 如果被觀察 value 是數組,會返回一個 Observe Array
- 如果 value 是么有原型的對象(對象可以滅有原型)或者原型是 Object.prototype ,對象會被克隆並且所有屬性會被轉換成可觀察的 Observe Object
- 如果 value 是有原型的,例如函數,數組,可以有4中方法處理 Boxed Observer
- 顯示的調用 observable.box(value) 有點神奇
- @observable 常用
- 調用 decorate()
- 類中引入 extendObservable() 來引入屬性 可用 裝飾器默認是有感染的,observalbe 被自動應用於數據結構包含的任何值,
observable 是 extendObservable(this, {prototype: value}) 的語法糖 observable.object(obj, decorator, option) 默認這些值都會轉換成可觀察
observable.array(obj, option) 會生成一個observable 數組,如果不想每個值都被觀測,可設置 {deep: false}
observable.map(obj, option) 無需局限於字符串
4.2 裝飾器 Decorator
可用裝飾的列表是這些:
- observable.deep 默認的 observable 裝飾器
- computed 創建一個衍生,就是能自動獲取已修改值的函數並返回新值
- action 創建 動作
- action.bound 創建有范圍的動作
class TodoList {
todos = {}
get unfinishedTodoCount() {
return values(this.todos).filter(todo => !todo.finished).length
}
addTodo() {
const t = new Todo()
t.title = 'Test_' + Math.random()
set(this.todos, t.id, t)
}
}
decorate(TodoList, {
todos: observable,
unfinishedTodoCount: computed,
addTodo: action.bound
}) // 對類 Observable 轉換
4.3 計算屬性 Computed
用法好幾種,看起來只有一些細微的差別:
- computed( () => expression)
- computed( () => expression, (newvalue) => void )
- computed( () => expression, option )
@computed({ equals: compareFn }) get property() { return expression; }
@computed get classProperty() { return expression; }
Computed 自帶很多操作屬性 控制 Computed 行為
- 比較器算法 equals: (value, value) => boolean 用來重載默認檢測規則的比較函數。 內置比較器有: comparer.identity, comparer.default, comparer.structural
- 追蹤 其他observable 類型屬性值,等待返回之后在做計算 requiresReaction: boolean 在重新計算衍生屬性之前,等待追蹤的 observables 值發生變化
- get: () => value
- set: (value) => void
- keepAlive: boolean 保持計算值活動,不光是在值發生變化之后。
4.4 動作 Actions
任何用來 修改狀態 的東西
建議在任何更改 observable 或者有副作用的函數上進行 Actions修飾
4.5 流處理 Flow
flow(function* (args) {})
flow() 接收 generator 函數作為他的唯一輸入
flow 的關鍵作用是 處理異步代碼時確保代碼被action包裝 ,因為正常的 observable state 對異步操作無法通過 enforceActions 檢查。
神奇的flow可以解決這個異步不跟蹤的問題
注意,異步函數必須是 generator ,而且在內部只能 yield promises
import { configure, flow } from 'mobx';
// 不允許在動作外部修改狀態 嚴格模式的意思
configure({ enforceActions: true });
class Store {
@observable githubProjects = [];
@observable state = "pending"; // "pending" / "done" / "error"
fetchProjects = flow(function* fetchProjects() { // <- 注意*號,這是生成器函數!
this.githubProjects = [];
this.state = "pending";
try {
const projects = yield someAsyncProcess(); // 用 yield 代替 await
const filteredProjects = somePreprocessing(projects);
// 異步代碼自動會被 `action` 包裝
this.state = "done";
this.githubProjects = filteredProjects;
} catch (error) {
this.state = "error";
}
})
}
Flows 可以撤銷,調用promise的cancel() 方法會停止異步狀態取值, 會繼續執行 finally 子句 。
5 observables 做出響應
5.1 computed
計算值是可以根據現有的狀態或其他計算值衍生的值。
概念上來講,他們和表格中的值十分相似,比如匯總80分以上的同學。
計算屬性 可以使實際可修改的值盡可能的小,計算屬性也是高度優化過的,可以多用
5.2 computed & autorun
聲明式的創建計算屬性,可以在類任意的屬性上使用裝飾器
import { observable, computed } from 'mobx';
class orderline {
@observable price = 10;
@observable amount = 1;
constructor(price) {
this.price = price;
}
@computed get total() {
return this.price * this.amount;
}
}
import { observable, autorun } from 'mobx'; const value = observable(0); const number = observable(100); autorun(() => { console.log(value.get()); }); value.set(1); value.set(2); number.set(101); // 0 1 2 不打印 101 yinwei number 未在autorun內部執行 number.get()/
Mobx 學習 基本寫法 * 此處聲明式的監控變量,與 ES6 的類修飾不同。
import { observable, action, computed, toJS } from 'mobx' import { observer } from 'mobx-react'
export default class InstanceStore { @observable value = 1
@action
modifyValue(v) {
this.value = v
}
@computed get getValue() {
return this.value * 10;
}
}
computed 直接獲取一個計算后的值。
如果一個值需要根據某個state計算,並且也需要被觀察則可以使用 @computed autorun 類似
autorun 用於執行一些和值變化有關的操作,比如異步請求,數據處理等
computed 用於根據已有的值,計算出新的值返回一個對觀察值追蹤的結果 var ins = new InstanceStore(); console.log('value form mobx computed', toJS(ins.getValue()))
autorun 在不需要繼續使用的情況可以進行垃圾回收
var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));
var disposer = autorun(() => console.log(sum.get())); // '6'
numbers.push(4); // '10'
disposer();
numbers.push(5); // 什么也不打印,因為disposer執行是不再對autorun reaction
過期狀態值方式如下
var ins = new InstanceStore();
console.log(toJS(ins.value),'get value from mobx');
dispatch 修改值 var ins = new InstanceStore(); ins.modifyValue(1000);
在組件內可以使用觀察者模式 @observer class routeCreate extends Component { constructor(props) { super(props); this.store = new InstanceStore(); } ... }
使用 observer 修飾組件,並且在render內部有 mobx 值的引用,組件會多一個生命周期 componentWillReact // redux 改變值的方式是通過拷貝原來的對象生成新的對象,觸發組件的componentWillReceiveProps // mobx 是以原始值的基礎上生成新的對象,之前的引用不變所以mobx 不會觸發ReceiveProps周期
異步處理
mobx 狀態值為同步更新。
export default class InstanceStore { @observable value = 1
@action
modifyValue(v) {
this.value = v;
setTimeout(this.valueAdd, 100);
}
@action.bound
valueAdd(v) {
this.value = v + 20;
}
}
// .bound 是js執行環境語法糖
// 過多action ? 需要簡化寫法
// mobx 自身提供了一個工具函數幫助跟新對應action中的值 runInAction
export default class InstanceStore {
@observable value = 1
@action
asyncModifyValue(v) {
this.value = v;
setTimeout(action('valueAdd', () => {
this.value = v + 20
}), 100);
}
@action
asyncModify(v) {
this.value = v;
setTimeout(runInAction(() => {
this.value = v + 20
}), 100);
}
}
// 異步action,action可以這樣寫
@asyncAction
changeValue() {
this.value = 0;
const data = yield Promise.resolve(1)
this.value = data;
}
toJS 將mobx state 序列轉換為js可識別類型?
更新action的約束
mobx 非強制使用action改變state;如果要加強制action觸發state可以通過
Mobx.configure({enforceActions: true}) 加限制條件,強制通過action更新,適用於大型項目
以下是遺留問題
* 1, mobx 是否是同步更新 是
* 2, mobx toJS是如何實現的
* 3,store對應單個變量,會按照類型預留數組空間,是什么原因
* 4,使用toJS獲取數據,需要在class名稱上面加 @observer 嗎
* 5,為什么mobx取值,是如此的簡介?,而且是支持多狀態
* 6,extendObservable 可以按照擴展的方式 裝飾函數或class里的對象