摘要:當(dāng)樹變異時(shí),連接的部分將作出反應(yīng)并更新以反映變化。接下來,我們必須對(duì)這些行動(dòng)狀態(tài)發(fā)生的變化作出反應(yīng)。這可用于將工作流轉(zhuǎn)換為其他狀態(tài)。將其視為產(chǎn)生價(jià)值的可觀察物。構(gòu)建可觀察數(shù)據(jù)掌握數(shù)據(jù)變更方法高階應(yīng)用實(shí)例
前兩部分側(cè)重于MobX的基本構(gòu)建塊。 有了這些塊,我們現(xiàn)在可以通過MobX的角度開始解決一些真實(shí)場(chǎng)景。 這篇文章將是一系列應(yīng)用我們迄今為止所見概念的例子。
當(dāng)然,這不是一個(gè)詳盡的清單,但應(yīng)該讓你體會(huì)到應(yīng)用MobX角度所需要的心理轉(zhuǎn)變。 所有示例都是在沒有@decorator (裝飾器)語法的情況下創(chuàng)建的。 這允許您在Chrome控制臺(tái),Node REPL或支持臨時(shí)文件的WebStorm等IDE中進(jìn)行嘗試。
改變思維方式當(dāng)您學(xué)習(xí)某些庫或框架背后的理論并嘗試將其應(yīng)用于您自己的問題時(shí),您最初可能會(huì)畫一個(gè)空白。 它發(fā)生在像我這樣的普通人身上,甚至是最好的人。 寫作界稱之為“Writer’s block”,而在藝術(shù)家的世界里,它就是“Painter’s block”。
我們需要的是從簡單到復(fù)雜的例子來塑造我們的思維方式。 只有看到應(yīng)用程序,我們才能開始想象解決我們自己的問題的方法。
對(duì)于MobX,它首先要了解您有一個(gè)reactive object-graph這一事實(shí)。 樹的某些部分可能依賴于其他部分。 當(dāng)樹變異時(shí),連接的部分將作出反應(yīng)并更新以反映變化。
思維方式的轉(zhuǎn)變是將手頭的系統(tǒng)設(shè)想為一組反應(yīng)性變動(dòng) + 一組相應(yīng)的結(jié)果。
效果可以是由于反應(yīng)性變化而產(chǎn)生輸出的任何事物。 讓我們探索各種現(xiàn)實(shí)世界的例子,看看我們?nèi)绾斡肕obX建模和表達(dá)它們。
問題:我們?cè)趹?yīng)用程序中有一些必須記錄到服務(wù)器的一次性操作。 我們希望跟蹤執(zhí)行這些操作的時(shí)間并發(fā)送分析。
1、是對(duì)建立狀態(tài)模型。 我們的行為是有限的,我們只關(guān)心它執(zhí)行一次。 我們可以使用動(dòng)作方法的名稱建立對(duì)應(yīng)的布爾值類型狀態(tài), 這是我們可觀察到的狀態(tài)。
const actionMap = observable({ login: false, logout: false, forgotPassword: false, changePassword: false, loginFailed: false });
2、接下來,我們必須對(duì)這些行動(dòng)狀態(tài)發(fā)生的變化作出反應(yīng)。 因?yàn)樗鼈冎辉谏芷谥邪l(fā)生過一次,所以我們不會(huì)使用長期運(yùn)行的效果,如autorun()或reaction()。 我們也不希望這些效果在執(zhí)行后存在。 好吧,這給我們留下了一個(gè)選擇:....
....
....
Object.keys(actionMap) .forEach(key => { when( () => actionMap[key], () => reportAnalyticsForAction(key) ); }); function reportAnalyticsForAction(actionName) { console.log("Reporting: ", actionName); /* ... JSON API Request ... */ }
在上面的代碼中,我們只是循環(huán)遍歷actionMap中的鍵并為每個(gè)鍵設(shè)置when()副作用。 當(dāng)tracker-function(第一個(gè)參數(shù))返回true時(shí),副作用將運(yùn)行。 運(yùn)行效果函數(shù)(第二個(gè)參數(shù))后,when()將自動(dòng)處理。 因此,沒有從應(yīng)用程序發(fā)送多個(gè)報(bào)告的問題!
3、我們還需要一個(gè)MobX動(dòng)作來改變可觀察狀態(tài)。 請(qǐng)記?。河肋h(yuǎn)不要直接修改您的observable。 始終通過action來做到這一點(diǎn)。
對(duì)上面的例子來說,如下:
const markActionComplete = action((name) => { actionMap[name] = true; }); markActionComplete("login"); markActionComplete("logout"); markActionComplete("login"); // [LOG] Reporting: login // [LOG] Reporting: logout
請(qǐng)注意,即使我將登錄操作標(biāo)記觸發(fā)兩次,也沒有發(fā)送日志報(bào)告。 完美,這正是我們需要的結(jié)果。
它有兩個(gè)原因:
login標(biāo)記已經(jīng)為true,因此值沒有變化
此外,when()副作用已被觸發(fā)執(zhí)行,因此不再發(fā)生追蹤。
Example 2: 作為工作流程的一部分啟動(dòng)操作問題:我們有一個(gè)由幾個(gè)狀態(tài)組成的工作流程。 每個(gè)狀態(tài)都映射到某些任務(wù),這些任務(wù)在工作流到達(dá)該狀態(tài)時(shí)執(zhí)行。
1、從上面的描述中可以看出,唯一可觀察的值是工作流的狀態(tài)。 需要為每個(gè)狀態(tài)運(yùn)行的任務(wù)可以存儲(chǔ)為簡單映射。 有了這個(gè),我們可以模擬我們的工作流程:
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"); } });
請(qǐng)注意,我們正在存儲(chǔ)一個(gè)名為state的實(shí)例變量,該變量跟蹤工作流的當(dāng)前和先前狀態(tài)。 我們還傳遞state->task的映射,存儲(chǔ)為taskMap。
2、現(xiàn)在有趣的部分是關(guān)于監(jiān)控工作流程。 在這種情況下,我們沒有像前一個(gè)例子那樣的一次性操作。 工作流通常是長時(shí)間運(yùn)行的,可能在應(yīng)用程序的生命周期內(nèi)。 這需要autorun或reaction()。
只有在轉(zhuǎn)換到狀態(tài)時(shí)才會(huì)執(zhí)行狀態(tài)任務(wù)。 因此我們需要等待對(duì)this.state.next進(jìn)行更改才能運(yùn)行任何副作用(任務(wù))。 等待更改表示使用reaction()因?yàn)樗鼉H在跟蹤的可觀察值更改值時(shí)才會(huì)運(yùn)行。 所以我們的監(jiān)控代碼如下所示:
class Workflow { /* ... */ monitorWorkflow() { reaction( () => this.state.next, (nextState) => { const task = this.taskMap[nextState]; if (task) { task(this); } } ) } }
reaction()第一個(gè)參數(shù)是跟蹤函數(shù),在這種情況下只返回this.state.next。 當(dāng)跟蹤功能的返回值改變時(shí),它將觸發(fā)效果功能。 效果函數(shù)查看當(dāng)前狀態(tài),從this.taskMap查找任務(wù)并簡單地調(diào)用它。
請(qǐng)注意,我們還將工作流的實(shí)例傳遞給任務(wù)。 這可用于將工作流轉(zhuǎn)換為其他狀態(tài)。
workflow.transitionTo("start"); workflow.transitionTo("finalize"); // [LOG] Running START // [LOG] Running FINALIZE /* ... after 500ms ... */ // [LOG] Running END
有趣的是,這種存儲(chǔ)一個(gè)簡單的observable的技術(shù),比如this.state.next和使用reaction()來觸發(fā)副作用,也可以用于:
通過react-router進(jìn)行路由
在演示應(yīng)用程序中導(dǎo)航
基于模式在不同視圖之間切換
Example 3: 輸入更改時(shí)執(zhí)行表單驗(yàn)證問題:這是一個(gè)經(jīng)典的Web表單用例,您需要驗(yàn)證一堆輸入。 如果有效,允許提交表單。
1、讓我們用一個(gè)簡單的表單數(shù)據(jù)類對(duì)其進(jìn)行建模,其字段必須經(jīng)過驗(yàn)證。
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是我們以前從未見過的。 通過在我們的類實(shí)例(this)上應(yīng)用它,我們得到一個(gè)ES5相當(dāng)于創(chuàng)建一個(gè)@observable類屬性。
class FormData { @observable firstName = ""; /* ... */ }
2、接下來,我們需要監(jiān)視這些字段何時(shí)發(fā)生變化并運(yùn)行一些驗(yàn)證邏輯。 如果驗(yàn)證通過,我們可以將實(shí)體標(biāo)記為有效并允許提交。 使用計(jì)算屬性跟蹤有效性本身:有效。
由于驗(yàn)證邏輯需要在FormData的生命周期內(nèi)運(yùn)行,因此我們將使用autorun()。 我們也可以使用reaction()但我們想立即運(yùn)行驗(yàn)證而不是等待第一次更改。
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發(fā)生更改時(shí)自動(dòng)觸發(fā)。 請(qǐng)注意,要使MobX正確跟蹤您的observable,您必須使用解除引用。
runValidation()是一個(gè)異步調(diào)用,這就是我們返回一個(gè)promise的原因。 在上面的示例中,它并不重要,但在現(xiàn)實(shí)世界中,您可能會(huì)調(diào)用服務(wù)器進(jìn)行一些特殊驗(yàn)證。 當(dāng)結(jié)果返回時(shí),我們將設(shè)置錯(cuò)誤observable,這將反過來更新有效的計(jì)算屬性。
如果你有一個(gè)耗時(shí)較大的驗(yàn)證邏輯,你甚至可以使用autorunAsync(),它有一個(gè)參數(shù)可以延遲執(zhí)行去抖動(dòng)。
2、好吧,讓我們的代碼付諸行動(dòng)。 我們將設(shè)置一個(gè)簡單的控制臺(tái)記錄器(通過autorun())并跟蹤有效的計(jì)算屬性。
const instance = new FormData(); // Simple console logger autorun(() => { // input的每一次輸入,結(jié)果都會(huì)觸發(fā)error變更,autorun隨即執(zhí)行 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()立即運(yùn)行,您將在開頭看到兩個(gè)額外的日志,一個(gè)用于instance.errors,一個(gè)用于instance.valid,第1-2行。 其余四行(3-6)用于現(xiàn)場(chǎng)的每次更改。
每個(gè)字段更改都會(huì)觸發(fā)runValidation(),每次都會(huì)在內(nèi)部返回一個(gè)新的錯(cuò)誤對(duì)象。 這會(huì)導(dǎo)致instance.errors的引用發(fā)生更改,然后觸發(fā)我們的autorun()以記錄有效標(biāo)志。 最后,當(dāng)我們?cè)O(shè)置了所有字段時(shí),instance.errors變?yōu)閚ull(再次更改引用)并記錄最終的“Valid = true”。
4、簡而言之,我們通過使表單字段可觀察來進(jìn)行表單驗(yàn)證。 我們還添加了額外的errors屬性和有效的計(jì)算屬性來跟蹤有效性。 autorun()通過將所有內(nèi)容捆綁在一起來節(jié)省時(shí)間。
問題: 我們有一組已注冊(cè)的組件,我們希望在所有組件都加載后跟蹤。 每個(gè)組件都將公開一個(gè)返回 promise的load()方法。 如果promise解析,我們將組件標(biāo)記為已加載。 如果它拒絕,我們將其標(biāo)記為失敗。 當(dāng)所有這些都完成加載時(shí),我們將報(bào)告整個(gè)集是否已加載或失敗。
1、我們先來看看我們正在處理的組件。 我們正在創(chuàng)建一組隨機(jī)報(bào)告其負(fù)載狀態(tài)的組件。 另請(qǐng)注意,有些是異步的。
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設(shè)計(jì)可觀察狀態(tài)。 組件的load()不會(huì)按特定順序完成。 所以我們需要一個(gè)可觀察的數(shù)組來存儲(chǔ)每個(gè)組件的加載狀態(tài)。 我們還將跟蹤每個(gè)組件的報(bào)告狀態(tài)。
當(dāng)所有組件都已報(bào)告時(shí),我們可以通知組件集的最終加載狀態(tài)。 以下代碼設(shè)置了可觀察量。
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()來設(shè)置我們的可觀察狀態(tài)。 reported和load的計(jì)算屬性跟蹤組件完成其加載的時(shí)間。 mark()是我們改變可觀察狀態(tài)的動(dòng)作方法。
順便說一句,建議在需要從您的observables派生值的任何地方使用computed。 將其視為產(chǎn)生價(jià)值的可觀察物。 計(jì)算值也會(huì)被緩存,從而提高性能。 另一方面,autorun和reaction不會(huì)產(chǎn)生價(jià)值。 相反,它們提供了創(chuàng)建副作用的命令層。
3、為了啟動(dòng)跟蹤,我們將在Tracker上創(chuàng)建一個(gè)track()方法。 這將觸發(fā)每個(gè)組件的load()并等待返回的Promise解析/拒絕。 基于此,它將標(biāo)記組件的負(fù)載狀態(tài)。
when()所有組件都已reported時(shí),跟蹤器可以報(bào)告最終加載的狀態(tài)。 我們?cè)谶@里使用,因?yàn)槲覀冋诘却龡l件變?yōu)檎妫╰his.reported)。 報(bào)告的副作用只需要發(fā)生一次,非常適合when()。
以下代碼負(fù)責(zé)以上事項(xiàng):
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()實(shí)際上不是解決方案的一部分,但用于記錄報(bào)告。 這是了解我們的解決方案是否有效的好方法。
4、現(xiàn)在我們來測(cè)試一下:
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
記錄的輸出顯示其按預(yù)期工作。 在組件報(bào)告時(shí),我們記錄每個(gè)組件的當(dāng)前加載狀態(tài)。 當(dāng)所有人報(bào)告時(shí),this.reported變?yōu)閠rue,我們看到“All Components Loaded”消息。
希望上面的一些例子讓你體會(huì)到在MobX中的思考。
設(shè)計(jì)可觀察狀態(tài)
設(shè)置變異動(dòng)作方法以更改可觀察狀態(tài)
放入跟蹤功能(when,autorun,reaction)以響應(yīng)可觀察狀態(tài)的變化
上述公式應(yīng)該適用于需要在發(fā)生變化后跟蹤某些內(nèi)容的復(fù)雜場(chǎng)景,這可能導(dǎo)致重復(fù)1-3步驟。
Part 1 - 構(gòu)建可觀察數(shù)據(jù)
Part 2 - 掌握數(shù)據(jù)變更方法
Part 3 - 高階應(yīng)用實(shí)例
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/97505.html
摘要:高效的模式提供了一種簡單而強(qiáng)大的方法來管理客戶端狀態(tài)。允許屬性本身可觀察,但不允許其任何子節(jié)點(diǎn)。默認(rèn)情況下,僅將引用更改視為更改。構(gòu)建可觀察數(shù)據(jù)掌握數(shù)據(jù)變更方法高階應(yīng)用實(shí)例 起因 很早之前看到的一篇關(guān)于mobx的文章,之前記得是有人翻譯過的,但是怎么找都找不到,故花了點(diǎn)時(shí)間通過自己那半桶水的英文水平,加上Google翻譯一下,對(duì)于初學(xué)者,以及mobx的開發(fā)者提供些許幫助。 這里針對(duì)已經(jīng)...
摘要:有了這個(gè),下一步就是開始對(duì)變化作出反應(yīng)。請(qǐng)注意,此延遲通知僅適用于當(dāng)前函數(shù)范圍中的。最快的方法是提供功能。只有當(dāng)返回的數(shù)據(jù)發(fā)生變化時(shí),才會(huì)執(zhí)行副作用。最棒的部分是它會(huì)在運(yùn)行后自動(dòng)處理副作用。構(gòu)建可觀察數(shù)據(jù)掌握數(shù)據(jù)變更方法高階應(yīng)用實(shí)例 在上一部分中,我們研究了如何設(shè)置MobX狀態(tài)樹并使其可觀察。 有了這個(gè),下一步就是開始對(duì)變化作出反應(yīng)。 坦率地說,這就是有趣的開始! MobX保證只要您的...
摘要:然鵝在過去的兩個(gè)月里,對(duì)的理解發(fā)生了一波三折的變化。發(fā)布自版本發(fā)布之后,一直致力于提升版本迭代速度,盡可能地通過小的更新來修復(fù)存在的問題。 推薦 1. 深入淺出 React 高階組件 https://zhuanlan.zhihu.com/p/... 由高階函數(shù)引申高階組件,高階組件是接受 React 組件作為輸入,輸出一個(gè)新的 React 組件的組件,本文介紹了在 React 工程中如...
摘要:前端每周清單半年盤點(diǎn)之與篇前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項(xiàng)目巔峰人生等欄目。與求同存異近日,宣布將的構(gòu)建工具由遷移到,引發(fā)了很多開發(fā)者的討論。 前端每周清單半年盤點(diǎn)之 React 與 ReactNative 篇 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為...
閱讀 3327·2021-11-19 09:40
閱讀 3060·2021-09-09 09:32
閱讀 873·2021-09-02 09:55
閱讀 1454·2019-08-26 13:23
閱讀 2530·2019-08-26 11:46
閱讀 1297·2019-08-26 10:19
閱讀 2136·2019-08-23 16:53
閱讀 1129·2019-08-23 12:44