成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

為 MobX 開啟 Time-Travelling 引擎

琛h。 / 735人閱讀

摘要:將收集到的實例及各類做標識并做好關系映射。但這并不意味著在序列化不友好的體系里不能實現(xiàn)從中喚醒應用。最后作為一個多范式的信徒,在一出現(xiàn)便取代了我心中在前端狀態(tài)管理領域的地位。

原文鏈接

注意:本文并非 mobx-state-tree 使用指南,事實上全篇都與 MST(mobx-state-tree) 無關。

前言

了解 mobx-state-tree 的同學應該知道,作為 MobX 官方提供的狀態(tài)模型構建庫,MST 提供了很多諸如 time travel、hot reload 及 redux-devtools支持 等很有用的特性。但 MST 的問題在于過于 opinioned,使用它們之前必須接受它們的一整套的價值觀(就跟 redux 一樣)。

我們先來簡單看一下 MST 中如何定義 Model 的:

import { types } from "mobx-state-tree"

const Todo = types.model("Todo", {
    title: types.string,
    done: false
}).actions(self => ({
    toggle() {
        self.done = !self.done
    }
}))

const Store = types.model("Store", {
    todos: types.array(Todo)
})

老實講我第一次看到這段代碼時內(nèi)心是拒絕的,主觀實在是太強了,最重要的是,這一頓操作太反直覺了。直覺上我們使用 MobX 定義模型應該是這樣一個姿勢:

import { observable, action } from "mobx"
class Todo {
    title: string;
    @observable    done = false;

    @action
    toggle() {
        this.done = !this.done;
    }
}

class Store {
    todos: Todo[]
}

用 class-based 的方式定義 Model 對開發(fā)者而言顯然更直觀更純粹,而 MST 這種“主觀”的方式則有些反直覺,這對于項目的可維護性并不友好(class-based 方式只要了解最基本的 OOP 的人就能看懂)。但是相應的,MST 提供的諸如 time travel 等能力確實又很吸引人,那有沒有一種方式可以實現(xiàn)既能舒服的用常規(guī)方式寫 MobX 又能享受 MST 同等的特性呢?

相對于 MobX 的多 store 和 class-method-based action 這種序列化不友好的范式而言,Redux 對 time travel/action replay 這類特性支持起來顯然要容易的多(但相應的應用代碼也要繁瑣的多)。但是只要我們解決了兩個問題,MobX 的 time travel/action replay 支持問題就會迎刃而解:

收集到應用的所有 store 并對其做 reactive 激活,在變化時手動序列化(snapshot)。完成 store -> reactive store collection -> snapshot(json) 過程。

將收集到的 store 實例及各類 mutation(action) 做標識并做好關系映射。完成 snapshot(json) -> class-based store 的逆向過程。

針對這兩個問題,mmlpx 給出了相應的解決方案:

DI + reactive container + snapshot (收集 store 并響應 store 變化,生成序列化 snapshot)

ts-plugin-mmlpx + hydrate (給 store 及 aciton 做標識,將序列化數(shù)據(jù)注水成帶狀態(tài)的 store 實例)

下面我們具體介紹一下 mmlpx 是如何基于 snapshot 給出了這兩個解決方案。

Snapshot 需要的基本能力

上文提到,要想為 MobX 治下的應用狀態(tài)提供 snapshot 能力,我們需要解決以下幾個問題:

收集應用的所有 store

MobX 本身在應用組織上是弱主張的,并不限制應用如何組織狀態(tài) store、遵循單一 store(如redux) 還是多 store 范式,但由于 MobX 本身是 OOP 向,在實踐中我們通常是采用 MVVM 模式 中的行為準則定義我們的 Domain Model 和 UI-Related Model(如何區(qū)別這兩類的模型可以看 MVVM 相關的文章或 MobX 官方最佳實踐,這里不再贅述)。這就導致在使用 MobX 的過程中,我們默認是遵循多 store 范式的。那么如果我們想把應用的所有的 store 管理起來應該這么做呢?

在 OOP 世界觀里,想管理所有 class 的實例,我們自然需要一個集中存儲容器,而這個容器通常很容易就會聯(lián)想到 IOC Container (控制反轉(zhuǎn)容器)。DI(依賴注入) 作為最常見的一種 IOC 實現(xiàn),能很好的替代之前手動實例化 MobX Store 的方式。有了 DI 之后我們引用一個 store 的方式就變成這樣了:

import { inject } from "mmlpx"
import UserStore from "./UserStore"

class AppViewModel {
    @inject() userStore: UserStore
    
    loadUsers() {
        this.userStore.loadUser()
    }
}

之后,我們能很容易地從 IOC 容器中獲取通過依賴注入方式實例化的所有 store 實例。這樣收集應用所有 store 的問題就解決了。

更多 DI 用法看這里 mmlpx di system

響應所有 store 的狀態(tài)變化

獲取到所有 store 實例后,下一步就是如何監(jiān)聽這些 store 中定義的狀態(tài)的變化。

如果在應用初始化完成后,應用內(nèi)的所有 store 都已實例完成,那么我們監(jiān)聽整個應用的變化就會相對容易。但通常在一個 DI 系統(tǒng)中,這種實例化動作是 lazy 的,即只有當某一 Store 被真正使用時才會被實例化,其狀態(tài)才會被初始化。這就意味著,在我們開啟快照功能的那一刻起,IOC 容器就應該被轉(zhuǎn)換成 reactive 的,從而能對新加入管理的 store 及 store 里定義的狀態(tài)實行自動綁定監(jiān)聽行為。

這時我們可以通過在 onSnapshot 時獲取到當前 IOC Container,將當前收集的 stores 全部 dump 出來,然后基于 MobX ObservableMap 構建一個新的 Container,同時 load 進之前的所有的 store,最后對 store 里定義的數(shù)據(jù)做遞歸遍歷同時使用 reaction 做 track dependencies,這樣我們就能對容器本身(Store 加入/銷毀)及 store 的狀態(tài)變化做出響應了。如果當變化觸發(fā) reaction 時,我們對當前應用狀態(tài)做手動序列化即可得到當前應用快照。

具體實現(xiàn)可以看這里:mmlpx onSnapshot

從 Snapshot 中喚醒應用

通常我們拿到應用的快照數(shù)據(jù)后會做持久化,以確保應用在下次進入時能直接恢復到退出時的狀態(tài) ── 或者我們要實現(xiàn)一個常見的 redo/undo 功能。

在 Redux 體系下這個事情做起來相對容易,因為本身狀態(tài)在定義階段就是 plain object 且序列化友好的。但這并不意味著在序列化不友好的 MobX 體系里不能實現(xiàn)從 Snapshot 中喚醒應用。

想要順利地 resume from snapshot,我們得先達成這兩個條件:

給每個 Store 加上唯一標識

如果我們想讓序列化之后的快照數(shù)據(jù)順利恢復到各自的 Store 上,我們必須給每一個 Store 一個唯一標識,這樣 IOC 容器才能通過這個 id 將每一層數(shù)據(jù)與其原始 Store 關聯(lián)起來。

在 mmlpx 方案下,我們可以通過 @Store@ViewModel 裝飾器將應用的 global state 和 local state 標記起來,同時給對應的模型 class 一個 id:

@Store("UserStore")
class UserStore {}

但是很顯然,手動給 Store 命名的做法很愚蠢且易出錯,你必須確保各自的命名空間不重疊(沒錯 redux 就是這么做的[攤手])。

好在這個事情有 ts-plugin-mmlpx 來幫你自動完成。我們在定義 Store 的時候只需要這么寫:

@Store
class UserStore {}

經(jīng)過插件轉(zhuǎn)換后就變成:

@Store("UserStore.ts/UserStore")
class UserStore {}

通過 fileName + className 的組合通常就可以確保 Store 命名空間的唯一性。更多插件使用信息請關注 ts-plugin-mmlpx 項目主頁 .

Hyration

從序列化的快照狀態(tài)中激活應用的 reactive 系統(tǒng),從靜態(tài)恢復到動態(tài)這個逆向過程,跟 SSR 中的 hydration 非常相似。實際上這也是在 MobX 中實現(xiàn) Time Travelling 最難處理的一步。不同于 redux 和 vuex 這類 Flux-inspired 庫,MobX 中狀態(tài)通常是基于 class 這種充血模型定義的,我們在給模型脫水再重新注水之后,還必須確保無法被序列化的那些行為定義(action method)依然能正確的與模型上下文綁定起來。單單重新綁定行為還沒完,我們還得確保反序列化之后數(shù)據(jù)的 mobx 定義也是跟原來保持一致的。比如我之前用 observable.ref 、observable.shallowObservableMap 這類有特殊行為的數(shù)據(jù)在重注水之后能保持原始的能力不變,尤其是 ObservableMap 這類非 object Array 的不可直接序列化的數(shù)據(jù),我們都得想辦法能讓他們重新激活回復原狀。

好在我們整個方案的基石是 DI 系統(tǒng),這就給我們在調(diào)用方請求獲取依賴時提供了“做手腳”的可能。我們只需要在依賴被 get 時判斷其是否由從序列化數(shù)據(jù)填充而來的,即 IOC 容器中保存的 Store 實例并非原始類型的實例,這時候便開啟 hydrate 動作,然后給調(diào)用方返回注水之后的 hydration 對象。激活的過程也很簡單,由于我們 inject 時上下文中是有 store 的類型(Constructor)的,所以我們只要重新初始化一個新的空白 store 實例之后,使用序列化數(shù)據(jù)對其進行填充即可。好在 MobX 只有三種數(shù)據(jù)類型,object、array 和 map,我們只需要簡單的對不同類型做一下處理就能完成 hydrate:

if (!(instance instanceof Host)) {

    const real: any = new Host(...args);

    // awake the reactive system of the model
    Object.keys(instance).forEach((key: string) => {
        if (real[key] instanceof ObservableMap) {
            const { name, enhancer } = real[key];
            runInAction(() => real[key] = new ObservableMap((instance as any)[key], enhancer, name));
        } else {
            runInAction(() => real[key] = (instance as any)[key]);
        }
    });

    return real as T;
}

hydrate 完整代碼可以看這里:hyrate

應用場景

相較于 MST 的快照能力(MST 只能對某一 Store 做快照,而不能對整個應用快照),基于 mmlpx 方案在實現(xiàn)基于 Snapshot 衍生的功能時變得更加簡單:

Time Travelling

Time Travelling 功能在實際開發(fā)中有兩種應用場景,一種是 redo/undo,一種是 redux-devtools 之類提供的應用 replay 功能。

在搭載 mmlpx 之后 MobX 實現(xiàn) redo/undo 就變得很簡單,這里不再貼代碼(其實就是 onSnapshotapplySnapshot 兩個 api),有興趣的同學可以查看 mmlpx todomvc demo (就是文章開頭貼的 gif 效果) 和 mmlpx 項目主頁。

類似 redux-devtools 的功能實現(xiàn)起來相對麻煩一點(其實也很簡單),因為我們要想實現(xiàn)對每一個 action 做 replay,前提條件是每個 action 都有一個唯一標識。redux 里的做法是通過手動編寫具備不同命名空間的 action_types 來實現(xiàn),這太繁瑣了(參考Redux數(shù)據(jù)流管理架構有什么致命缺陷,未來會如何改進?)。好在我們有 ts-plugin-mmlpx 可以幫我們自動的幫我們給 action 起名(原理同自動給 store 起名)。解決掉這個麻煩之后,我們只需要在 onSnapshot 的同時記錄每個 action,就能在 mobx 里面輕松的使用 redux-devtool 的功能了。

SSR

我們知道,React 或 Vue 在做 SSR 時,都是通過在 window 上掛載全局變量的方式將預取數(shù)據(jù)傳遞到客戶端的,但通常官方示例都是基于 Redux 或 Vuex 來做的,MobX 在此之前想實現(xiàn)客戶端激活還是有些事情要解決的。現(xiàn)在有了 mmlpx 的幫助,我們只需要在應用啟動之前,使用傳遞過來的預取數(shù)據(jù)在客戶端應用快照即可基于 MobX 實現(xiàn)客戶端狀態(tài)激活:

import { applySnapshot } from "mmlpx"

if (window.__PRELOADED_STATE__) {
    applySnapshot(window.__PRELOADED_STATE__)
}
應用 crash 監(jiān)控

這個只要使用的狀態(tài)管理庫具備對任一時間做完整的應用快照,同時能從快照數(shù)據(jù)激活狀態(tài)關系的能力就能實現(xiàn)。即檢查到應用 crash 時按下快門,將快照數(shù)據(jù)上傳云端,最后在云端平臺通過快照數(shù)據(jù)還原現(xiàn)場即可。如果我們上傳的快照數(shù)據(jù)還包括用戶前幾次的操作棧,那么在監(jiān)控平臺對用戶操作做 replay 也不成問題。

最后

作為一個“多 store”范式的信徒,MobX 在一出現(xiàn)便取代了我心中 Redux 在前端狀態(tài)管理領域的地位。但苦于之前 MobX 多 store 架構下缺乏集中管理 store 的手段,其在 time travelling 等系列功能的開發(fā)體驗上一直有所欠缺?,F(xiàn)在在 mmlpx 的幫助下,MobX 也能開啟 Time Travelling 功能了,Redux 在我心中最后的一點優(yōu)勢也就蕩然無存了。

文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96031.html

相關文章

  • 前端每周清單第 55 期: MobX 4 特性概覽,iOS Hacks 分享, 分布式事務詳解

    摘要:異步剪貼板操作過去的數(shù)年中,各瀏覽器基本上都在使用來進行剪貼板交互。而提供了新的,則為我們提供了另一種異步式的剪貼板操作方式,本文即是對該機制與接口規(guī)范的詳細介紹。 showImg(https://segmentfault.com/img/remote/1460000013854167); 前端每周清單第 55 期: MobX 4 特性概覽,iOS Hacks 分享, 分布式事務詳解 ...

    zombieda 評論0 收藏0
  • 前端每周清單第 50 期: AngularJS and Long Term Support, Web

    摘要:在該版本發(fā)布之后,開發(fā)團隊并不會繼續(xù)發(fā)布新的特性,而會著眼于進行重大的錯誤修復。發(fā)布每六個星期,團隊就會創(chuàng)建新的分支作為發(fā)布通道,本文即是對新近發(fā)布的版本進行簡要介紹。 showImg(https://segmentfault.com/img/remote/1460000013229009); 前端每周清單專注前端領域內(nèi)容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點;分為新聞熱...

    DobbyKim 評論0 收藏0
  • mobx遇到的問題

    摘要:谷歌版目前尚未啟用對實驗語法的支持有道版目前尚未啟用對實驗語法的支持很明顯,答案出來了,我使用的編輯器沒有開啟這個實驗性語法。看來跟編輯器無關了,我的思維得轉(zhuǎn)換一下,去谷歌了。 使用mobx的react項目 showImg(https://segmentfault.com/img/bVbkR3k?w=1038&h=284); 圖片有提示,編譯失敗,告訴問題所在,老兄,語法錯誤了。把第二...

    Binguner 評論0 收藏0
  • React結合TypeScript和Mobx初體驗

    摘要:結合編輯器可以推導變量對應的類型以及內(nèi)部的結構,提高代碼的健壯性和可維護性。通過充分利用時間回溯的特征,可以增強業(yè)務的可預測性與錯誤定位能力。對于對象的哪部分需要成為可觀察的,提供了細粒度的控制。 showImg(https://segmentfault.com/img/bVba6Ts?w=1218&h=525); 為什么要使用TypeScript 偵測錯誤 通過靜態(tài)類型檢測可以盡早檢...

    dreambei 評論0 收藏0
  • 【用故事解讀 MobX 源碼(四)】裝飾器 和 Enhancer

    摘要:所以這是一篇插隊的文章,用于去理解中的裝飾器和概念。因此,該的作用就是根據(jù)入?yún)⒎祷鼐唧w的描述符。其次局部來看,裝飾器具體應用表達式是,其函數(shù)簽名和是一模一樣。等裝飾器語法,是和直接使用是等效等價的。 ================前言=================== 初衷:以系列故事的方式展現(xiàn) MobX 源碼邏輯,盡可能以易懂的方式講解源碼; 本系列文章: 《【用故事解...

    maybe_009 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<