摘要:當樹變異時,連接的部分將作出反應并更新以反映變化。接下來,我們必須對這些行動狀態發生的變化作出反應。這可用于將工作流轉換為其他狀態。將其視為產生價值的可觀察物。構建可觀察數據掌握數據變更方法高階應用實例
前兩部分側重于MobX的基本構建塊。 有了這些塊,我們現在可以通過MobX的角度開始解決一些真實場景。 這篇文章將是一系列應用我們迄今為止所見概念的例子。
當然,這不是一個詳盡的清單,但應該讓你體會到應用MobX角度所需要的心理轉變。 所有示例都是在沒有@decorator (裝飾器)語法的情況下創建的。 這允許您在Chrome控制臺,Node REPL或支持臨時文件的WebStorm等IDE中進行嘗試。
改變思維方式當您學習某些庫或框架背后的理論并嘗試將其應用于您自己的問題時,您最初可能會畫一個空白。 它發生在像我這樣的普通人身上,甚至是最好的人。 寫作界稱之為“Writer’s block”,而在藝術家的世界里,它就是“Painter’s block”。
我們需要的是從簡單到復雜的例子來塑造我們的思維方式。 只有看到應用程序,我們才能開始想象解決我們自己的問題的方法。
對于MobX,它首先要了解您有一個reactive object-graph這一事實。 樹的某些部分可能依賴于其他部分。 當樹變異時,連接的部分將作出反應并更新以反映變化。
思維方式的轉變是將手頭的系統設想為一組反應性變動 + 一組相應的結果。
效果可以是由于反應性變化而產生輸出的任何事物。 讓我們探索各種現實世界的例子,看看我們如何用MobX建模和表達它們。
問題:我們在應用程序中有一些必須記錄到服務器的一次性操作。 我們希望跟蹤執行這些操作的時間并發送分析。
1、是對建立狀態模型。 我們的行為是有限的,我們只關心它執行一次。 我們可以使用動作方法的名稱建立對應的布爾值類型狀態, 這是我們可觀察到的狀態。
const actionMap = observable({ login: false, logout: false, forgotPassword: false, changePassword: false, loginFailed: false });
2、接下來,我們必須對這些行動狀態發生的變化作出反應。 因為它們只在生命周期中發生過一次,所以我們不會使用長期運行的效果,如autorun()或reaction()。 我們也不希望這些效果在執行后存在。 好吧,這給我們留下了一個選擇:....
....
....
Object.keys(actionMap) .forEach(key => { when( () => actionMap[key], () => reportAnalyticsForAction(key) ); }); function reportAnalyticsForAction(actionName) { console.log("Reporting: ", actionName); /* ... JSON API Request ... */ }
在上面的代碼中,我們只是循環遍歷actionMap中的鍵并為每個鍵設置when()副作用。 當tracker-function(第一個參數)返回true時,副作用將運行。 運行效果函數(第二個參數)后,when()將自動處理。 因此,沒有從應用程序發送多個報告的問題!
3、我們還需要一個MobX動作來改變可觀察狀態。 請記住:永遠不要直接修改您的observable。 始終通過action來做到這一點。
對上面的例子來說,如下:
const markActionComplete = action((name) => { actionMap[name] = true; }); markActionComplete("login"); markActionComplete("logout"); markActionComplete("login"); // [LOG] Reporting: login // [LOG] Reporting: logout
請注意,即使我將登錄操作標記觸發兩次,也沒有發送日志報告。 完美,這正是我們需要的結果。
它有兩個原因:
login標記已經為true,因此值沒有變化
此外,when()副作用已被觸發執行,因此不再發生追蹤。
Example 2: 作為工作流程的一部分啟動操作問題:我們有一個由幾個狀態組成的工作流程。 每個狀態都映射到某些任務,這些任務在工作流到達該狀態時執行。
1、從上面的描述中可以看出,唯一可觀察的值是工作流的狀態。 需要為每個狀態運行的任務可以存儲為簡單映射。 有了這個,我們可以模擬我們的工作流程:
class Workflow { constructor(taskMap) { this.taskMap = taskMap; this.state = observable({ previous: null, next: null }); this.transitionTo = action((name) => { this.state.previous = this.state.next; this.state.next = name; }); this.monitorWorkflow(); } monitorWorkflow() { /* ... */ } } // Usage const workflow = new Workflow({ start() { console.log("Running START"); }, process(){ console.log("Running PROCESS"); }, approve() { console.log("Running APPROVE"); }, finalize(workflow) { console.log("Running FINALIZE"); setTimeout(()=>{ workflow.transitionTo("end"); }, 500); }, end() { console.log("Running END"); } });
請注意,我們正在存儲一個名為state的實例變量,該變量跟蹤工作流的當前和先前狀態。 我們還傳遞state->task的映射,存儲為taskMap。
2、現在有趣的部分是關于監控工作流程。 在這種情況下,我們沒有像前一個例子那樣的一次性操作。 工作流通常是長時間運行的,可能在應用程序的生命周期內。 這需要autorun或reaction()。
只有在轉換到狀態時才會執行狀態任務。 因此我們需要等待對this.state.next進行更改才能運行任何副作用(任務)。 等待更改表示使用reaction()因為它僅在跟蹤的可觀察值更改值時才會運行。 所以我們的監控代碼如下所示:
class Workflow { /* ... */ monitorWorkflow() { reaction( () => this.state.next, (nextState) => { const task = this.taskMap[nextState]; if (task) { task(this); } } ) } }
reaction()第一個參數是跟蹤函數,在這種情況下只返回this.state.next。 當跟蹤功能的返回值改變時,它將觸發效果功能。 效果函數查看當前狀態,從this.taskMap查找任務并簡單地調用它。
請注意,我們還將工作流的實例傳遞給任務。 這可用于將工作流轉換為其他狀態。
workflow.transitionTo("start"); workflow.transitionTo("finalize"); // [LOG] Running START // [LOG] Running FINALIZE /* ... after 500ms ... */ // [LOG] Running END
有趣的是,這種存儲一個簡單的observable的技術,比如this.state.next和使用reaction()來觸發副作用,也可以用于:
通過react-router進行路由
在演示應用程序中導航
基于模式在不同視圖之間切換
Example 3: 輸入更改時執行表單驗證問題:這是一個經典的Web表單用例,您需要驗證一堆輸入。 如果有效,允許提交表單。
1、讓我們用一個簡單的表單數據類對其進行建模,其字段必須經過驗證。
class FormData { constructor() { extendObservable(this, { firstName: "", lastName: "", email: "", acceptTerms: false, errors: {}, get valid() { // this becomes a computed() property return (this.errors === null); } }); this.setupValidation(); // We will look at this below } }
extendObservable()API是我們以前從未見過的。 通過在我們的類實例(this)上應用它,我們得到一個ES5相當于創建一個@observable類屬性。
class FormData { @observable firstName = ""; /* ... */ }
2、接下來,我們需要監視這些字段何時發生變化并運行一些驗證邏輯。 如果驗證通過,我們可以將實體標記為有效并允許提交。 使用計算屬性跟蹤有效性本身:有效。
由于驗證邏輯需要在FormData的生命周期內運行,因此我們將使用autorun()。 我們也可以使用reaction()但我們想立即運行驗證而不是等待第一次更改。
class FormData { setupValidation() { autorun(() => { // Dereferencing observables for tracking const {firstName, lastName, email, acceptTerms} = this; const props = { firstName, lastName, email, acceptTerms }; this.runValidation(props, {/* ... */}) .then(result => { this.errors = result; }) }); } runValidation(propertyMap, rules) { return new Promise((resolve) => { const {firstName, lastName, email, acceptTerms} = propertyMap; const isValid = (firstName !== "" && lastName !== "" && email !== "" && acceptTerms === true); resolve(isValid ? null : {/* ... map of errors ... */}); }); } }
在上面的代碼中,autorun()將在跟蹤的observables發生更改時自動觸發。 請注意,要使MobX正確跟蹤您的observable,您必須使用解除引用。
runValidation()是一個異步調用,這就是我們返回一個promise的原因。 在上面的示例中,它并不重要,但在現實世界中,您可能會調用服務器進行一些特殊驗證。 當結果返回時,我們將設置錯誤observable,這將反過來更新有效的計算屬性。
如果你有一個耗時較大的驗證邏輯,你甚至可以使用autorunAsync(),它有一個參數可以延遲執行去抖動。
2、好吧,讓我們的代碼付諸行動。 我們將設置一個簡單的控制臺記錄器(通過autorun())并跟蹤有效的計算屬性。
const instance = new FormData(); // Simple console logger autorun(() => { // input的每一次輸入,結果都會觸發error變更,autorun隨即執行 const validation = instance.errors; console.log(`Valid = ${instance.valid}`); if (instance.valid) { console.log("--- Form Submitted ---"); } }); // Let"s change the fields instance.firstName = "Pavan"; instance.lastName = "Podila"; instance.email = "pavan@pixelingene.com"; instance.acceptTerms = true; // 輸出日志如下 // Valid = false // Valid = false // Valid = false // Valid = false // Valid = false // Valid = true // --- Form Submitted ---
由于autonrun()立即運行,您將在開頭看到兩個額外的日志,一個用于instance.errors,一個用于instance.valid,第1-2行。 其余四行(3-6)用于現場的每次更改。
每個字段更改都會觸發runValidation(),每次都會在內部返回一個新的錯誤對象。 這會導致instance.errors的引用發生更改,然后觸發我們的autorun()以記錄有效標志。 最后,當我們設置了所有字段時,instance.errors變為null(再次更改引用)并記錄最終的“Valid = true”。
4、簡而言之,我們通過使表單字段可觀察來進行表單驗證。 我們還添加了額外的errors屬性和有效的計算屬性來跟蹤有效性。 autorun()通過將所有內容捆綁在一起來節省時間。
問題: 我們有一組已注冊的組件,我們希望在所有組件都加載后跟蹤。 每個組件都將公開一個返回 promise的load()方法。 如果promise解析,我們將組件標記為已加載。 如果它拒絕,我們將其標記為失敗。 當所有這些都完成加載時,我們將報告整個集是否已加載或失敗。
1、我們先來看看我們正在處理的組件。 我們正在創建一組隨機報告其負載狀態的組件。 另請注意,有些是異步的。
const components = [ { name: "first", load() { return new Promise((resolve, reject) => { Math.random() > 0.5 ? resolve(true) : reject(false); }); } }, { name: "second", load() { return new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve(true) : reject(false); }, 1000); }); } }, { name: "third", load() { return new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.25 ? resolve(true) : reject(false); }, 500); }); } }, ];
2、下一步是為Tracker設計可觀察狀態。 組件的load()不會按特定順序完成。 所以我們需要一個可觀察的數組來存儲每個組件的加載狀態。 我們還將跟蹤每個組件的報告狀態。
當所有組件都已報告時,我們可以通知組件集的最終加載狀態。 以下代碼設置了可觀察量。
class Tracker { constructor(components) { this.components = components; extendObservable(this, { // Create an observable array of state objects, // one per component states: components.map(({name}) => { return { name, reported: false, loaded: undefined }; }), // computed property that derives if all components have reported get reported() { return this.states.reduce((flag, state) => { return flag && state.reported; }, true); }, // computed property that derives the final loaded state // of all components get loaded() { return this.states.reduce((flag, state) => { return flag && !!state.loaded; }, true); }, // An action method to mark reported + loaded mark: action((name, loaded) => { const state = this.states.find(state => state.name === name); state.reported = true; state.loaded = loaded; }) }); } }
我們回到使用extendObservable()來設置我們的可觀察狀態。 reported和load的計算屬性跟蹤組件完成其加載的時間。 mark()是我們改變可觀察狀態的動作方法。
順便說一句,建議在需要從您的observables派生值的任何地方使用computed。 將其視為產生價值的可觀察物。 計算值也會被緩存,從而提高性能。 另一方面,autorun和reaction不會產生價值。 相反,它們提供了創建副作用的命令層。
3、為了啟動跟蹤,我們將在Tracker上創建一個track()方法。 這將觸發每個組件的load()并等待返回的Promise解析/拒絕。 基于此,它將標記組件的負載狀態。
when()所有組件都已reported時,跟蹤器可以報告最終加載的狀態。 我們在這里使用,因為我們正在等待條件變為真(this.reported)。 報告的副作用只需要發生一次,非常適合when()。
以下代碼負責以上事項:
class Tracker { /* ... */ track(done) { when( () => this.reported, () => { done(this.loaded); } ); this.components.forEach(({name, load}) => { load() .then(() => { this.mark(name, true); }) .catch(() => { this.mark(name, false); }); }); } setupLogger() { autorun(() => { const loaded = this.states.map(({name, loaded}) => { return `${name}: ${loaded}`; }); console.log(loaded.join(", ")); }); } }
setupLogger()實際上不是解決方案的一部分,但用于記錄報告。 這是了解我們的解決方案是否有效的好方法。
4、現在我們來測試一下:
const t = new Tracker(components); t.setupLogger(); t.track((loaded) => { console.log("All Components Loaded = ", loaded); }); // first: undefined, second: undefined, third: undefined // first: true, second: undefined, third: undefined // first: true, second: undefined, third: true // All Components Loaded = false // first: true, second: false, third: true
記錄的輸出顯示其按預期工作。 在組件報告時,我們記錄每個組件的當前加載狀態。 當所有人報告時,this.reported變為true,我們看到“All Components Loaded”消息。
希望上面的一些例子讓你體會到在MobX中的思考。
設計可觀察狀態
設置變異動作方法以更改可觀察狀態
放入跟蹤功能(when,autorun,reaction)以響應可觀察狀態的變化
上述公式應該適用于需要在發生變化后跟蹤某些內容的復雜場景,這可能導致重復1-3步驟。
Part 1 - 構建可觀察數據
Part 2 - 掌握數據變更方法
Part 3 - 高階應用實例
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97505.html
摘要:高效的模式提供了一種簡單而強大的方法來管理客戶端狀態。允許屬性本身可觀察,但不允許其任何子節點。默認情況下,僅將引用更改視為更改。構建可觀察數據掌握數據變更方法高階應用實例 起因 很早之前看到的一篇關于mobx的文章,之前記得是有人翻譯過的,但是怎么找都找不到,故花了點時間通過自己那半桶水的英文水平,加上Google翻譯一下,對于初學者,以及mobx的開發者提供些許幫助。 這里針對已經...
摘要:有了這個,下一步就是開始對變化作出反應。請注意,此延遲通知僅適用于當前函數范圍中的。最快的方法是提供功能。只有當返回的數據發生變化時,才會執行副作用。最棒的部分是它會在運行后自動處理副作用。構建可觀察數據掌握數據變更方法高階應用實例 在上一部分中,我們研究了如何設置MobX狀態樹并使其可觀察。 有了這個,下一步就是開始對變化作出反應。 坦率地說,這就是有趣的開始! MobX保證只要您的...
摘要:然鵝在過去的兩個月里,對的理解發生了一波三折的變化。發布自版本發布之后,一直致力于提升版本迭代速度,盡可能地通過小的更新來修復存在的問題。 推薦 1. 深入淺出 React 高階組件 https://zhuanlan.zhihu.com/p/... 由高階函數引申高階組件,高階組件是接受 React 組件作為輸入,輸出一個新的 React 組件的組件,本文介紹了在 React 工程中如...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發了很多開發者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為...
閱讀 3206·2021-11-19 09:40
閱讀 3005·2021-09-09 09:32
閱讀 792·2021-09-02 09:55
閱讀 1393·2019-08-26 13:23
閱讀 2403·2019-08-26 11:46
閱讀 1229·2019-08-26 10:19
閱讀 2054·2019-08-23 16:53
閱讀 1072·2019-08-23 12:44