本來說接下來會專一學nodejs,可是最新工做又學習了一些有意思的庫,於是就再寫下來作個簡單的入門,以前我寫過一篇文章,這個也算是做為一個補充吧. javascript
此次無非就是相似筆記,把認爲的一些關鍵點記下來,有些地方還沒用到就衹是描述一下,代碼有些本身寫的,有些文檔寫的很好就搬下來,想瞭解更多可看官網
Mobx中文文檔
Mobx英文文檔
Github 倉庫 html
PS:
2018/06/11 發現中文版有些關鍵地方沒翻譯,補充一下observable({})/observable.object(props, decorators?, options?)部分java
經過透明的響應式編程使狀態管理變得簡單和可擴展,背後哲學是任何源自應用狀態的東西都應自動得到,(包括UI,數據序列化,服務器通信等)node
React經過提供機制把應用狀態轉換成可渲染組件樹並對其渲染,優化UI渲染,就是經過使用虛擬DOM減小昂貴的DOM變化數量.
Mobx提供機制來存儲和更新應用狀態供React使用,優化應用狀態和React組件同步,經過使用響應式的虛擬依賴狀態圖表,讓其在須要的時候才更新而且保持最新.react
我的感受與Redux相比除了目的一致是管理應用狀態以外.無論是寫法仍是思想都大相徑庭.因為纔剛入門,這裡只說用法不講區別.git
咱們先看看這段代碼作了什麼,再分開詳細講解一下對應知識點github
import {observable, autorun} from 'mobx'; var todoStore = observable({ /* 一些觀察的狀態 */ todos: [], /* 推導值 */ get completedCount() { return this.todos.filter(todo => todo.completed).length; }, }); /* 觀察狀態改變的函數 */ autorun(function() { console.log( 'Completed %d of %d items', todoStore.completedCount, todoStore.todos.length ); }); /* ..以及一些改變狀態的動做 */ todoStore.todos[0] = { title: 'Take a walk', completed: false, }; // -> 同步打印 'Completed 0 of 1 items' todoStore.todos[0].completed = true; // -> 同步打印 'Completed 1 of 1 items'
官方流程圖:express
//標準用法 observable(value) //裝飾器用法 @observable classProperty = value
Observable 值能夠是JS基本數據類型、引用類型、普通對象、類實例、數組和映射. 匹配類型應用瞭如下轉換規則,但能夠經過使用調節器進行微調.編程
observable引用例如值可使用Boxed Observable observables.MobX 不會將一個有原型的對象自動轉換成可觀察的,由於這是它構造函數的職責.能夠在constructor使用extendObservable或者類型定義使用decorate替代.json
values: 能夠是對象、 數組或者字符串鍵的 ES6 map;
options:
const map = observable.map(new Map());
如下是MobX 提供方法:
這是遞歸的,因此數組中的全部(將來的)值都會是可觀察的.
options:
const ary = observable.array([1, 2, 4]);
注意:
observable.array 會建立一我的造數組(類數組對象)來代替真正的數組. 支持全部的原生方法,包括從索引的分配到包含數組長度.
如下是MobX 提供方法:
一個普通的 JavaScript 對象 (指不是使用構造函數建立出來的對象,而是以 Object 做爲其原型,或者根本沒有原型)傳遞給 observable 方法,對象的全部屬性都將被拷貝至一個克隆對象並將克隆對象轉變成可觀察的.
這是遞歸應用的,因此若是對象的某個值是一個對象或數組,那麼該值也將經過 observable 傳遞.
options:
const obj = observable.object({ key: "value"});
注意:
JavaScript 中的全部原始類型值都是不可變的,所以它們都是不可觀察的,box建立一個基於 ref 裝飾器的箱子.這意味着箱子裏的任何(未來)值都不會自動地轉換成 observable .
options:
1) name: 調試名稱,用於 spy 或者 MobX 開發者工具;
const box = observable.box('box'); box.observe(function(change) { console.log(change.oldValue, '->', change.newValue); });
如下是box提供方法:
定義 observable 屬性的行爲,默認爲對任意鍵值對使用 observable.deep,對 getters 使用 computed .
class Person { name = 'John'; } // 使用 decorate 時,全部字段都應該指定 (畢竟,類裏的非 observable 字段可能會更多) decorate(Person, { name: observable, });
任何源自狀態而且不會再有任何進一步的相互做用的東西就是衍生,衍生以多種形式存在:
MobX 區分兩種類型的衍生:
計算值(computed values)是能夠根據現有的狀態或其它計算值衍生出的值.若是你想響應式的產生一個能夠被其它 observer 使用的值,請使用 @computed.計算值在大多數狀況下能夠被 MobX 優化的,例如:
注意:
計算值的 setter:
class Test { @observable num = 0; @computed get total() { return this.num * 10; } set total(value) { this.num = value / 10; } } //OR const Test = observable.object({ num: 0; get total() { return this.num * 10; } set total(value) { this.num = value / 10; } })
某些狀況下,你須要傳遞一個「在box中」的計算值時,它多是有用的.
options:
MobX 提供了三個內置 comparer (比較器) :
const box = observable('box'), upperCaseName = computed(() => name.get().toUpperCase()), disposer = upperCaseName.observe(change => console.log(change.newValue)); box.set('Dave');
建立一個響應式函數,而該函數自己永遠不會有觀察者,調用後將接收一個參數,即當前 reaction(autorun),可用於在執行期間清理 autorun. 當使用 autorun 時,所提供的函數老是當即被觸發一次,而後每次它的依賴關係改變時會再次被觸發,相比之下computed(function)建立的函數只有當它有本身的觀察者時纔會從新計算,不然它的值會被認爲是不相關的;
options:
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(); //清理 autorun numbers.push(5); // 不會再輸出任何值.`sum` 不會再從新計算.
when 觀察並運行給定的 predicate,直到返回true. 一旦返回 true,給定的 effect 就會被執行,而後 autorunner(自動運行程序) 會被清理. 該函數返回一個清理器以提早取消自動運行程序.
class MyResource { constructor() { when( // 一旦... () => !this.isVisible, // ... 而後 () => this.dispose() ); } @computed get isVisible() { // 標識此項是否可見 } dispose() { // 清理 } }
若是沒提供 effect 函數,when 會返回一個 Promise .它與 async / await 能夠完美結合.
async function() { await when(() => that.isVisible) // 等等.. }
第一個數據函數是用來追蹤並返回數據做爲第二個做用函數的入參. 不一樣於 autorun 的是當建立時函數不會直接運行,只有在數據表達式首次返回一個新值後纔會運行. 在執行做用函數時訪問的任何 observable 都不會被追蹤.
reaction 返回一個清理函數,接收兩個參數,即當前的 reaction,能夠用來在執行期間清理 reaction .
options:
const todos = observable([ { title: "test1" }, { title: "test2" } ]); //會對長度變化做出反應 reaction(() => todos.length, length => console.log("reaction 1:", todos.map(todo => todo.title).join(", "))); //會對某個 todo 的 title 變化做出反應 reaction(() => todos.map(todo => todo.title), titles => console.log("reaction 2:", titles.join(", "))); // autorun 對它函數中使用的任何東西做出反應 autorun(() => console.log("autorun:", todos.map(todo => todo.title).join(", "))); // 輸出: // autorun: test1 action(() => { todos.push({title: "test3"}); })() // 輸出: // autorun: test1, test2, test3 // reaction 2: test1, test2, test3 // reaction 1: test1, test2, test3 action(() => { todos[1].title = 'test4'; })() // 輸出: // autorun: test1, test4, test3 // reaction 2: test1, test4, test3
observer 函數/裝飾器能夠用來將 React 組件/無狀態函數組件轉變成響應式組件. 它用 mobx.autorun 包裝了組件的 render 函數以確保任何組件渲染中使用的數據變化時均可以強制刷新組件.
import {observer} from "mobx-react"; var timerData = observable({ secondsPassed: 0 }); setInterval(() => { timerData.secondsPassed++; }, 1000); @observer class Timer extends React.Component { render() { return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> ) } }; //OR const Timer = observer(({ timerData }) => <span>Seconds passed: { timerData.secondsPassed } </span> );
@observer class Timer extends React.Component { @observable secondsPassed = 0 componentWillMount() { setInterval(() => { this.secondsPassed++ }, 1000) } render() { return (<span>Seconds passed: { this.secondsPassed } </span> ) } }
在React組件上引入可觀察屬性. 這意味着你能夠在組件中擁有功能一樣強大的本地狀態(local state),而不須要經過 React 的冗長和強制性的 setState 機制來管理. 響應式狀態會被 render 提取調用,但不會調用其它 React 的生命週期方法,除了 componentWillUpdate 和 componentDidUpdate . 若是你須要用到其餘 React 生命週期方法 ,只需使用基於 state 的常規 React API 便可.
const App = () => <Provider colors={colors}> <app stuff... /> </Provider>; const Button = inject("colors")(observer(({ colors, label }) => <button style={{ color: colors.foreground }} >{label}<button> ));
當組件從新渲染時被觸發,這使得它很容易追溯渲染並找到致使渲染的操做(action).
action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod () {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}
action能夠是任何用來修改狀態的東西,只執行查找,過濾器等函數不該該被標記爲action,以容許 MobX 跟蹤它們的調用.能夠有助於更好的組織代碼.
自動地將動做綁定到目標對象.與 action 不一樣的是不須要一個name參數,名稱將始終基於動做綁定的屬性.
因為箭頭函數已是綁定過的而且不能從新綁定,因此不能一塊兒使用
class Ticker { @observable tick = 0 @action.bound increment() { this.tick++ // 'this' 永遠都是正確的 } }
action 包裝/裝飾器只會對當前運行的函數做出反應,而不會對當前運行函數所調用的函數(不包含在當前函數以內)做出反應! 這意味着若是 action 中存在 setTimeout、promise 的 then 或 async 語句,而且在回調函數中某些狀態改變了,那麼這些回調函數也應該包裝在 action 中.
錯誤寫法,拋出異常
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action fetchProjects() { this.status = "yellow" toggleLight().then( res => { this.status = "green" }, err => { this.status = "red" } ) } }
//包裝修復在action
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action handleAjax() { this.status = "yellow" toggleLight().then( this.handleSuc, this.handleErr ) } @action.bound handleSuc(res){ this.status = "green" } @action.bound handleErr(err){ this.status = "red" } }
//另外一種內嵌寫法
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action handleAjax() { this.status = "yellow" toggleLight().then( action('handleSuc',res => { this.status = "green" }), action('handleErr',res => { this.status = "red" }) ) } }
runInAction(name?, thunk)
runInAction 是個簡單的工具函數,它接收代碼塊並在(異步的)動做中執行.這對於即時建立和執行動做很是有用.
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action handleAjax() { this.status = "yellow" toggleLight().then( runInAction(res => { this.status = "green" }), runInAction(res => { this.status = "red" }) ) } }
async / await
async / await 只是圍繞基於 promise 過程的語法糖. 結果是 @action 僅應用於代碼塊,直到第一個 await . 在每一個 await 以後,一個新的異步函數將啓動,因此在每一個 await 以後,狀態修改代碼應該被包裝成動做.
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action async handleAjax() { this.status = "yellow" toggleLight().then( try{ const result = await dosometings(); runInAction(res => { this.status = result; }), }catch(err){ runInAction(res => { this.status = "red"; }) } ) } }
flow內置概念
優勢是它在語法上基本與 async / await 是相同的 (只是關鍵字不一樣),而且不須要手動用 @action 來包裝異步代碼,這樣代碼更簡潔.
class TrafficLight { @observable status = "yellow" // "red" / "green" / "yellow" @action handleAjax = flow(function* () { this.status = "yellow" toggleLight().then( try{ const result = yield dosometings(); this.status = result; }catch(err){ this.status = "red"; } ) }) }
這些 API 都是響應式的,這意味着若是使用 set 進行添加,使用 values 或 keys 進行迭代,即使是新屬性的聲明均可以被 MobX 檢測到.
import { get, set, observable, values } from "mobx" const twitterUrls = observable.object({ "John": "twitter.com/johnny" }) autorun(() => { console.log(get(twitterUrls, "Sara")) // get 能夠追蹤還沒有存在的屬性 }) autorun(() => { console.log("All urls: " + values(twitterUrls).join(", ")) }) set(twitterUrls, { "Sara" : "twitter.com/horsejs"})
不想摘抄了,看文檔吧...
Mobx工具函數
不想摘抄了,看文檔吧...
Mobx貼士與技巧
(更多內容請自行查閱,本節到此爲止了.)