Mobx4.X狀態管理入門

前言

本來說接下來會專一學nodejs,可是最新工做又學習了一些有意思的庫,於是就再寫下來作個簡單的入門,以前我寫過一篇文章,這個也算是做為一個補充吧. javascript

此次無非就是相似筆記,把認爲的一些關鍵點記下來,有些地方還沒用到就衹是描述一下,代碼有些本身寫的,有些文檔寫的很好就搬下來,想瞭解更多可看官網
Mobx中文文檔
Mobx英文文檔
Github 倉庫 html

PS:
2018/06/11 發現中文版有些關鍵地方沒翻譯,補充一下observable({})/observable.object(props, decorators?, options?)部分java

Mobx4.X

經過透明的響應式編程使狀態管理變得簡單和可擴展,背後哲學是任何源自應用狀態的東西都應自動得到,(包括UI,數據序列化,服務器通信等)node

React經過提供機制把應用狀態轉換成可渲染組件樹並對其渲染,優化UI渲染,就是經過使用虛擬DOM減小昂貴的DOM變化數量.
Mobx提供機制來存儲和更新應用狀態供React使用,優化應用狀態和React組件同步,經過使用響應式的虛擬依賴狀態圖表,讓其在須要的時候才更新而且保持最新.react

我的感受與Redux相比除了目的一致是管理應用狀態以外.無論是寫法仍是思想都大相徑庭.因為纔剛入門,這裡只說用法不講區別.git

官方代碼

咱們先看看這段代碼作了什麼,再分開詳細講解一下對應知識點github

  • observable(組件): 轉成響應式組件,會自動轉換應用狀態和更新;
  • get 函數: 計算值,根據現有的狀態或其它計算值衍生出的值;
  • autorun函數: 相似get,依賴關係改變時觸發;
  • action: 改變狀態,嚴格模式下全部修改操做都應該在action裡面執行;
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

State(狀態)

observable

//標準用法
observable(value)
//裝飾器用法
@observable classProperty = value

Observable 值能夠是JS基本數據類型、引用類型、普通對象、類實例、數組和映射. 匹配類型應用瞭如下轉換規則,但能夠經過使用調節器進行微調.編程

  • Map: 返回一個新的 Observable Map,不但對一個特定項的更改作出反應,並且對添加或刪除該項也作出反應;
  • 數組: 會返回一個 Observable Array;
  • 沒有原型的對象: 那麼對象會被克隆而且全部的屬性都會被轉換成可觀察的;
  • 有原型的對象: JavaSript 原始數據類型或者函數,observable會拋出錯誤,若是你想要建立一個獨立的

observable引用例如值可使用Boxed Observable observables.MobX 不會將一個有原型的對象自動轉換成可觀察的,由於這是它構造函數的職責.能夠在constructor使用extendObservable或者類型定義使用decorate替代.json

observable(new Map())/observable.map(values, options)

values: 能夠是對象、 數組或者字符串鍵的 ES6 map;
options:

  1. deep: 決定分配給 observable 映射的值會否經過 observable 來傳遞使其轉變成可觀察的;
  2. name: 調試名稱,用於 spy 或者 MobX 開發者工具;
const map = observable.map(new Map());

如下是MobX 提供方法:

  • toJS(): 將 observable 映射轉換成普通映射;
  • toJSON(): 返回此映射的淺式普通對象表示.(想要深拷貝,請使用 mobx.toJS(map));
  • intercept(interceptor): 能夠用來在任何變化做用於映射前將其攔截;
  • observe(listener, fireImmediately?): 註冊偵聽器,在映射中的每一個更改時觸發;
  • merge(values): 把提供對象的全部項拷貝到映射中.values 能夠是普通對象、entries 數組或者 ES6 字符串鍵的映射;
  • replace(values): 用提供值替換映射所有內容.是 .clear().merge(values) 的簡寫形式;

observable([])/observable.array(values, options)

這是遞歸的,因此數組中的全部(將來的)值都會是可觀察的.
options:

  • deep: 決定分配給 observable 映射的值會否經過 observable 來傳遞使其轉變成可觀察的;
  • name: 調試名稱,用於 spy 或者 MobX 開發者工具;
const ary = observable.array([1, 2, 4]);

注意:
observable.array 會建立一我的造數組(類數組對象)來代替真正的數組. 支持全部的原生方法,包括從索引的分配到包含數組長度.

  • 驗證類型方法的話返回不是數組.能夠經過使用 array.slice() 在 observable 數組傳遞給外部庫或者內置方法前建立一份淺拷貝;
  • sort 和 reverse 函數實現不會改變數組自己,而是返回一個排序過/反轉過的拷貝;

如下是MobX 提供方法:

  • intercept(interceptor): 能夠用來在任何變化做用於數組前將其攔截;
  • observe(listener, fireImmediately? = false): 監聽數組的變化.回調函數將接收表示數組拼接或數組更改的參數,它符合 ES7 提議.它返回一個清理函數以用來中止監聽器;
  • clear(): 從數組中刪除全部項;
  • replace(newItems): 用新項替換數組中全部已存在的項;
  • find(predicate: (item, index, array) => boolean, thisArg?): 基本上等同於 ES7 的 Array.find 提議;
  • findIndex(predicate: (item, index, array) => boolean, thisArg?): 基本上等同於 ES7 的 Array.findIndex 提議;
  • remove(value): 經過值從數組中移除一個單個的項.若是項被找到並移除的話,返回 true ;
  • peek(): 和 slice() 相似,返回一個有全部值的數組而且數組能夠放心的傳遞給其它庫,可是不建立保護性拷貝;

observable({})/observable.object(props, decorators?, options?)

一個普通的 JavaScript 對象 (指不是使用構造函數建立出來的對象,而是以 Object 做爲其原型,或者根本沒有原型)傳遞給 observable 方法,對象的全部屬性都將被拷貝至一個克隆對象並將克隆對象轉變成可觀察的.
這是遞歸應用的,因此若是對象的某個值是一個對象或數組,那麼該值也將經過 observable 傳遞.
options:

  • deep: 決定分配給 observable 映射的值會否經過 observable 來傳遞使其轉變成可觀察的;
  • name: 調試名稱,用於 spy 或者 MobX 開發者工具;
const obj = observable.object({ key: "value"});

注意:

  • [MobX 4及如下]當經過 observable 傳遞對象時,只有在把對象轉變 observable 時存在的屬性纔會是可觀察的. 稍後添加到對象的屬性不會變爲可觀察的,除非使用 set 或 extendObservable;
  • 只有普通的對象能夠轉變成 observable .對於非普通對象,構造函數負責初始化 observable 屬性. 要麼使用 @observable 註解[color=#b1b1b1](annotation,這個解釋不太懂??),要麼使用 extendObservable 函數;
  • 屬性的 getter 會自動轉變成衍生屬性,就像 @computed 所作的;
  • observable 是自動遞歸到整個對象的.在實例化過程當中和未來分配給 observable 屬性的任何新值的時候.Observable 不會遞歸到非普通對象中;
  • 更細粒度的控制,好比哪些屬性應該轉變成可觀察的和如何變成可觀察的,請參見裝飾器;

observable.box(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提供方法:

  • get(): 返回當前值;
  • set(value): 替換當前存儲的值並通知全部觀察者;
  • intercept(interceptor): 能夠用來在任何變化應用前將其攔截;
  • observe(callback: (change) => void, fireImmediately = false): 註冊一個觀察者函數,每次存儲值被替換時觸發.返回一個函數以取消觀察者.change是一個對象,其中包含 observable 的 newValue 和 oldValue .

裝飾器

定義 observable 屬性的行爲,默認爲對任意鍵值對使用 observable.deep,對 getters 使用 computed .

  • observable: observable.deep 的別名
  • observable.deep: 任何 observable 都使用的默認的調節器.它將任何(還沒有成爲 observable )數組,映射或純對象克隆並轉換爲 observable 對象,並將其賦值給給定屬性
  • observable.ref: 禁用自動的 observable 轉換,只是建立一個 observable 引用
  • observable.shallow: 只能與集合組合使用. 將任何分配的集合轉換爲 observable,但該集合的值將按原樣處理
  • observable.struct: 就像 ref, 但會忽略結構上等於當前值的新值
  • computed: 建立一個衍生屬性, 參見 computed
  • computed(options): 同 computed , 可設置選項
  • computed.struct: 與 computed 相同,可是隻有當視圖產生的值與以前的值結構上有不一樣時,才通知它的觀察者
  • action: 建立一個動做, 參見 action
  • action(name): 建立一個動做,重載了名稱
  • action.bound: 建立一個動做, 並將 this 綁定到了實例
class Person {
  name = 'John';
}
// 使用 decorate 時,全部字段都應該指定 (畢竟,類裏的非 observable 字段可能會更多)
decorate(Person, {
  name: observable,
});

Derivations(衍生)

任何源自狀態而且不會再有任何進一步的相互做用的東西就是衍生,衍生以多種形式存在:

  • 用戶界面
  • 衍生數據,好比剩下的待辦事項的數量.
  • 後端集成,好比把變化發送到服務器端.

MobX 區分兩種類型的衍生:

  • Computed values(計算值): 它們是永遠可使用純函數(pure function)從當前可觀察狀態中衍生出的值;
  • Reactions(反應): Reactions 是當狀態改變時須要自動發生的反作用.須要有一個橋樑來鏈接命令式編程(imperative programming)和響應式編程(reactive programming).或者說得更明確一些,它們最終都須要實現I / O 操做;

(@)computed

計算值(computed values)是能夠根據現有的狀態或其它計算值衍生出的值.若是你想響應式的產生一個能夠被其它 observer 使用的值,請使用 @computed.計算值在大多數狀況下能夠被 MobX 優化的,例如:

  • 前一個計算中使用的數據沒有更改,計算屬性將不會從新運行;
  • 某個其它計算屬性或 reaction 未使用該計算屬性,也不會從新運行. 在這種狀況下,它將被暫停;
  • 一個計算值再也不被觀察了,例如使用它的UI不復存在了,MobX 能夠自動地將其垃圾回收;

注意:

  • 計算屬性是不可枚舉的,它們也不能在繼承鏈中被覆蓋;
  • 可使用 observe 或 keepAlive 來強制保持計算值老是處於喚醒狀態;
  • observable.object 和 extendObservable 都會自動將 getter 屬性推導成計算屬性;
  • 若是計算值在其計算期間拋出異常,則此異常將捕獲並在讀取其值時從新拋出. 強烈建議始終拋出「錯誤」,以便保留原始堆棧跟蹤. 拋出異常不會中斷跟蹤,全部計算值能夠從異常中恢復.

計算值的 setter:

  • 不能用來直接改變計算屬性的值,可是它們能夠用來做「逆向」衍生.就是反向計算;
  • 必須在 getter 以後定義 setter,一些 TypeScript 版本會知道聲明瞭兩個具備相同名稱的屬性;
  • 這是一個自動的動做,只須要直接使用set xx(){};
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;
    }
})

computed(expression,options) 函數用法

某些狀況下,你須要傳遞一個「在box中」的計算值時,它多是有用的.
options:

  • name: 調試名稱,用於 spy 或者 MobX 開發者工具;
  • context: 在提供的表達式中使用的 this;
  • set: 要使用的setter函數. 沒有 setter 的話沒法爲計算值分配新值. 若是傳遞給 computed 的第二個參數是一個函數,那麼就把會這個函數做爲 setter;
  • equals: 默認值是 comparer.default .它充當比較函數.若是先後值相等,那麼觀察者就不會從新評估;
  • requiresReaction: 對於很是昂貴的計算值,推薦設置成 true .若是你嘗試讀取它的值,但某些觀察者沒有跟蹤該值(在這種狀況下,MobX 不會緩存該值),則會致使計算結果丟失,而不是進行昂貴的從新評估;
  • keepAlive: 若是沒有任何人觀察到,則不要使用此計算值. 請注意,這很容易致使內存泄漏,由於它會致使此計算值使用的每一個 observable ,並將計算值保存在內存中;

MobX 提供了三個內置 comparer (比較器) :

  • comparer.identity: 使用恆等 (===) 運算符來斷定兩個值是否相同;
  • comparer.default: 等同於 comparer.identity,但還認爲 NaN 等於 NaN ;
  • comparer.structural: 執行深層結構比較以肯定兩個值是否相同;
const box = observable('box'),
  upperCaseName = computed(() => name.get().toUpperCase()),
  disposer = upperCaseName.observe(change => console.log(change.newValue));
box.set('Dave');

Autorun(expression,options)

建立一個響應式函數,而該函數自己永遠不會有觀察者,調用後將接收一個參數,即當前 reaction(autorun),可用於在執行期間清理 autorun. 當使用 autorun 時,所提供的函數老是當即被觸發一次,而後每次它的依賴關係改變時會再次被觸發,相比之下computed(function)建立的函數只有當它有本身的觀察者時纔會從新計算,不然它的值會被認爲是不相關的;
options:

  • delay: 可用於對效果函數進行去抖動的數字(以毫秒爲單位).若是是 0(默認值) 的話,那麼不會進行去抖.
  • name: 調試名稱,用於 spy 或者 MobX 開發者工具;
  • onError: 用來處理 reaction 的錯誤,而不是傳播它們;
  • scheduler: 設置自定義調度器以決定如何調度 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();
//清理 autorun
numbers.push(5);
// 不會再輸出任何值.`sum` 不會再從新計算.

when(predicate: () => boolean, effect?: () => void, options?)

when 觀察並運行給定的 predicate,直到返回true. 一旦返回 true,給定的 effect 就會被執行,而後 autorunner(自動運行程序) 會被清理. 該函數返回一個清理器以提早取消自動運行程序.

class MyResource {
    constructor() {
        when(
            // 一旦...
            () => !this.isVisible,
            // ... 而後
            () => this.dispose()
        );
    }

    @computed get isVisible() {
        // 標識此項是否可見
    }

    dispose() {
        // 清理
    }
}

when-promise

若是沒提供 effect 函數,when 會返回一個 Promise .它與 async / await 能夠完美結合.

async function() {
    await when(() => that.isVisible)
    // 等等..
}

reaction(() => data, (data, reaction) => { sideEffect }, options?)

第一個數據函數是用來追蹤並返回數據做爲第二個做用函數的入參. 不一樣於 autorun 的是當建立時函數不會直接運行,只有在數據表達式首次返回一個新值後纔會運行. 在執行做用函數時訪問的任何 observable 都不會被追蹤.
reaction 返回一個清理函數,接收兩個參數,即當前的 reaction,能夠用來在執行期間清理 reaction .

options:

  • fireImmediately: 布爾值,用來標識效果函數是否在數據函數第一次運行後當即觸發.默認值是 false,若是一個布爾值做爲傳給 reaction 的第三個參數,那麼它會被解釋爲 fireImmediately 選項;
  • delay: 可用於對效果函數進行去抖動的數字(以毫秒爲單位).若是是 0(默認值) 的話,那麼不會進行去抖;
  • equals: 默認值是 comparer.default .它充當比較函數.若是先後值相等,那麼觀察者就不會從新評估;
  • name: 調試名稱,用於 spy 或者 MobX 開發者工具;
  • onError: 用來處理 reaction 的錯誤,而不是傳播它們;
  • scheduler: 設置自定義調度器以決定如何調度 autorun 函數的從新運行:
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

observer 函數/裝飾器能夠用來將 React 組件/無狀態函數組件轉變成響應式組件. 它用 mobx.autorun 包裝了組件的 render 函數以確保任何組件渲染中使用的數據變化時均可以強制刷新組件.

  • observer 是由單獨的 mobx-react 包提供的.確保 observer 是最深處(第一個應用)的裝飾器,不然它可能什麼都不作;
  • 若是傳遞給組件的數據是響應式的,observer還能夠防止當組件的 props 只是淺改變時的從新渲染,這個行爲與 React PureComponent 類似,不一樣在於這裏的 state 的更改仍然會被處理. 若是一個組件提供了它本身的 shouldComponentUpdate,這個方法會被優先調用;
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 便可.

inject組件鏈接stores

const App = () =>
  <Provider colors={colors}>
     <app stuff... />
  </Provider>;

const Button = inject("colors")(observer(({ colors, label }) =>
  <button style={{
      color: colors.foreground
    }}
  >{label}<button>
));

提供生命週期componentWillReact

當組件從新渲染時被觸發,這使得它很容易追溯渲染並找到致使渲染的操做(action).

  • 不接收參數;
  • 初始化渲染前不會觸發 (使用 componentWillMount 替代);
  • 對於 mobx-react@4+, 當接收新的 props 時並在 setState 調用後會觸發此鉤子;

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.bound

自動地將動做綁定到目標對象.與 action 不一樣的是不須要一個name參數,名稱將始終基於動做綁定的屬性.
因為箭頭函數已是綁定過的而且不能從新綁定,因此不能一塊兒使用

class Ticker {
    @observable tick = 0

    @action.bound
    increment() {
        this.tick++ // 'this' 永遠都是正確的
    }
}

編寫異步 Actions

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

這些 API 都是響應式的,這意味着若是使用 set 進行添加,使用 values 或 keys 進行迭代,即使是新屬性的聲明均可以被 MobX 檢測到.

  • values(thing): 將集合中的全部值做爲數組返回;
  • keys(thing): 將集合中的全部鍵做爲數組返回;
  • set(thing, key, value)/set(thing, { key: value }): 使用提供的鍵值對來更新給定的集合;
  • remove(thing, key): 從集合中移除指定的項.用於數組拼接;
  • has(thing, key): 若是集合中存在指定的 observable 屬性就返回 true;
  • get(thing, key): 返回指定鍵下的子項;
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工具函數

Mobx技巧與問題

不想摘抄了,看文檔吧...
Mobx貼士與技巧

(更多內容請自行查閱,本節到此爲止了.)

相關文章
相關標籤/搜索