摘要:前言很多同學(xué)用過了,也用過了,但還是覺得不稱心要不要自己造一個(gè)一百行來代碼就基本搞定,其實(shí),自己造的框架實(shí)不實(shí)用,并不重要,重要的是思想??偩€根據(jù)路由表,調(diào)用對(duì)應(yīng)的進(jìn)行處理。
1 前言
很多同學(xué)用過了Flux,也用過了Redux,但還是覺得不稱心?要不要自己造一個(gè)?一百行來代碼就基本搞定,So easy, so good!
其實(shí),自己造的框架實(shí)不實(shí)用,并不重要,重要的是思想。有了設(shè)計(jì)框架的思想后,再去看人家的框架,就會(huì)更多地關(guān)注人家為何要這么設(shè)計(jì)?好處在哪?弊端在哪?是否有改進(jìn)的地方?明白了框架設(shè)計(jì)者的想法,才能更好地使用框架。
現(xiàn)在,咱們就一起來設(shè)計(jì)一個(gè)React框架,這個(gè)框架具備以下幾個(gè)的特點(diǎn):
單向數(shù)據(jù)流:業(yè)務(wù)數(shù)據(jù)從UI層觸發(fā),經(jīng)處理到Module層便結(jié)束,不再需要人為地將數(shù)據(jù)反映到UI層。
消息機(jī)制:組件與服務(wù)之間通過消息總線完成,包括組件與組件之間的嵌套關(guān)系。
咱們給這個(gè)框架起個(gè)響亮的名字——Rebus(React-Bus)。這里的Bus不是公交車的Bus,是計(jì)算機(jī)基礎(chǔ)原理中“Bus”(總線)。很顯然,咱們要用“消息總線”這樣的思想,實(shí)現(xiàn)ReactJs的單向數(shù)據(jù)流開發(fā)模式。一句話概括咱們的框架:Rebus是一個(gè)基于消息總線的,單向數(shù)據(jù)流的,ReactJs開發(fā)框架。
這里是用Rebus寫的一個(gè)TodoMVC實(shí)例:https://github.com/odebo/todomvc-rebus(看在我的代碼寫得如此粗糙的份上,大蝦們賞顆星星鼓勵(lì)鼓勵(lì)下唄)。
2 什么是單向數(shù)據(jù)流模式什么是“單向數(shù)據(jù)流模式”?這個(gè)概念對(duì)很多人來說可能有點(diǎn)陌生。下面是Facebook的Flux官網(wǎng)(http://facebook.github.io/flux/)提供的說明圖:
好像有點(diǎn)抽象?那咱們先補(bǔ)補(bǔ)腦,看看什么是雙向數(shù)據(jù)流模式。
什么是雙向數(shù)據(jù)模式?簡單地說就是UI層的一個(gè)操作經(jīng)過UI層(View)、控制層(Control)、模式層(Model),做完增、刪、改、查等處理后,還得反過來,手動(dòng)地將增刪改查后的數(shù)據(jù)反映到UI層上。這就雙向數(shù)據(jù)流模式。
而Flux中所謂的單向數(shù)據(jù)流模式是指:UI層監(jiān)聽?wèi)?yīng)用的“狀態(tài)”,當(dāng)一個(gè)操作(Action)經(jīng)過Dispatch(分發(fā)器)、Store(狀態(tài)容器),最后更新了“狀態(tài)”,UI層自動(dòng)根據(jù)“狀態(tài)”的變化而更新界面。
這里的“狀態(tài)”是指一個(gè)應(yīng)用某個(gè)時(shí)刻的某個(gè)狀態(tài):比如左側(cè)菜單欄展開與否——狀態(tài);導(dǎo)航中高亮項(xiàng)是誰——狀態(tài);用戶是否登錄,用戶是誰——狀態(tài);Table中多少個(gè)Item,分別是什么內(nèi)容——狀態(tài)。
簡單地說,單向數(shù)據(jù)流就是單向綁定,UI層與狀態(tài)綁定,當(dāng)狀態(tài)發(fā)生變化,UI層自動(dòng)更新。
可能有的同學(xué)會(huì)問,既然有AngularJs這樣的雙向綁定的MVVM模式,還搞什么單向綁定模式,聽起來弱爆了。雙向綁定肯定比單向綁定高大上得多。
這個(gè)問題不太好下結(jié)論,雙向綁定固然有雙向綁定的好處,但也有它的弊端。而相比單向數(shù)據(jù)流的邏輯處理思路更加單純清晰。
3 Rebus 框架的數(shù)據(jù)流模型理解了單向數(shù)據(jù)流后,咱們給出Rebus框架的數(shù)據(jù)流模式(如下圖)。概括起來就三個(gè)步驟:
UI層觸發(fā)的一個(gè)Action。
Rebus總線根據(jù)Action路由表選擇對(duì)應(yīng)的Service進(jìn)行處理。
Service處理后,更新狀態(tài)(State),結(jié)束。
這里的Services層指的是業(yè)務(wù)服務(wù)層,提供業(yè)務(wù)處理接口,包括對(duì)狀態(tài)的修改,對(duì)后臺(tái)數(shù)據(jù)的異步處理等等。如果覺得這一層太厚,可以分離出專門的Modle接口層。但不管怎樣,一個(gè)業(yè)務(wù)操作從UI層到最后修改狀態(tài)便結(jié)束,數(shù)據(jù)流方向只有一個(gè)。
但光這么說還是太抽象了,咱們直接上代碼,看看在TodoMVC這個(gè)例子中,添加一個(gè)新的Todo這個(gè)操作是怎么被處理的。
是不是挺簡單,簡潔的三層結(jié)構(gòu),清晰的數(shù)據(jù)流:
ReactJs組件只負(fù)責(zé)渲染和觸發(fā)Action,具體誰來響應(yīng)Action,它不管。
Rebus總線根據(jù)Action路由表,調(diào)用對(duì)應(yīng)的Service進(jìn)行處理。
Service層進(jìn)行完邏輯處理后,通過Rebus.setState()方法更新狀態(tài)。
但你一定會(huì)問:React組件是怎么監(jiān)聽狀態(tài)的變化的?其實(shí)很簡單,直接看代碼:比如咱們希望添加新的Todo后,TodoBody組件會(huì)自動(dòng)更新。所以TodoBody組件應(yīng)該監(jiān)聽狀態(tài)“todos”的變化。
4 Rebus中的action用過Flux的同學(xué)都知道Flux中有個(gè)叫Dispatch的模塊,用來dispatch各種Action。而咱們的Rebus.execute()的作用與Dispatch.dispatch()差不多(如下圖)。
不一樣的是Rebus.execute(actionHead, arg1,arg2,…)的第一個(gè)參數(shù)是action頭,其它參數(shù)直接跟在action頭后面。Action頭中包含兩個(gè)信息:要做什么?從哪里來?
“從哪里來”這個(gè)參數(shù)很重要,因?yàn)樗o咱們開發(fā)、調(diào)試提供了極大的便利。試想下,在Action路由表中,咱們能夠很清晰地看出一個(gè)Action將會(huì)到哪個(gè)Service處理,但沒法直觀地看出一個(gè)Action是從哪里觸發(fā)的,而且同樣的Action可能由不同的組件觸發(fā),這是沒法從Action路由表中直觀看出來的。
所以,咱們給Rebus增加了一個(gè)調(diào)試功能,只要打開這個(gè)功能,便可以打印Action信息。
另外,如果一個(gè)Action被觸發(fā),卻沒在路由表中找到這個(gè)Action的路由,Rebus會(huì)通過打印錯(cuò)誤信息的方式提醒開發(fā)者。
自從Action有了源信息,領(lǐng)導(dǎo)再也不用擔(dān)心我找不到代碼的出處了,歐耶!
5 Rebus中的Action路由表Action路由表這個(gè)概念在Flux與Redux中沒有,但也很好理解,就是一個(gè)很直觀的路由配置信息表。它是在Web應(yīng)用開始初始化時(shí),加載進(jìn)來的。
在這張Action路由表中,你可以直觀地添加、修改、跟蹤一個(gè)Action會(huì)被哪個(gè)Service處理。當(dāng)你希望某個(gè)Action被另一個(gè)Service處理時(shí),直接在這個(gè)Action路由表中進(jìn)行修改便是。
另外,在這個(gè)Action路由表中,咱們可以通過and()讓一個(gè)Action觸發(fā)多個(gè)service,如上圖的第29行。咱們寫了一個(gè)日志服務(wù)TodoLog.logAddTodo,希望系統(tǒng)處理ADD_TODO的同時(shí)也記錄這個(gè)事件。咱們就可以通過and()函數(shù)將這個(gè)服務(wù)綁定到ADD_TODO這條路由后面,and()的參數(shù)是一個(gè)數(shù)據(jù),意思可以綁定多個(gè)服務(wù)。
但是,必須提醒的是,不建議and()中的服務(wù)也修改State,除非你肯定and()中的服務(wù)修改的State與Rebus.connet()中的服務(wù)修改的State的監(jiān)聽者沒有任何交集。所以,再三提醒a(bǔ)nd()中只綁定跟State無關(guān)的服務(wù),比如一些日志服務(wù)、系統(tǒng)統(tǒng)計(jì)服務(wù)等。
可能你會(huì)問,一個(gè)Web應(yīng)用就一張Action路由表嗎?是的,也許在后續(xù)的版本中咱們可以支持多個(gè)Action路由表。但一張路由表也有它的好處——唯一性。比如你設(shè)置了某個(gè)Action的路由,結(jié)果另一個(gè)同事在另一張路由表中也設(shè)置了同名的Action路由,一開始獨(dú)立開發(fā)時(shí)可能沒有問題,一旦整合在一起,問題就出現(xiàn)了。所以,只有一張路由表是有好處的,大點(diǎn)沒關(guān)系。
6 Rebus中的組件咱們都知道ReactJs的一大特征就是支持JSX語法,這使得JS代碼中可以直接寫“類標(biāo)簽代碼”,而且一個(gè)組件能夠被嵌套在另一個(gè)組件中,并接受從上級(jí)組件傳遞進(jìn)來的參數(shù)。
這種一層一層嵌套的寫法雖然很直觀,但也很蛋疼。就拿上面Redux實(shí)現(xiàn)的Header組件添加新Todo這個(gè)操作,執(zhí)行的是傳遞進(jìn)來的回調(diào)函數(shù)addTodo(…)。
這么做有幾個(gè)問題:
1)寫代碼時(shí),到底是先約定Header組件要執(zhí)行的回調(diào)函數(shù)叫addTodo,寫上級(jí)組件時(shí)按約定傳遞叫addTodo的參數(shù)?還是先寫好上級(jí)組件,根據(jù)上級(jí)組件傳遞的參數(shù)名來執(zhí)行回調(diào)函數(shù)?到底是先有蛋還是先有雞?
2)果上級(jí)組件傳參時(shí)傳錯(cuò)了,或者子組件寫回調(diào)函數(shù)時(shí)名稱寫錯(cuò)了,如何跟蹤代碼,只知道光從代碼上看,我TM怎么知道這個(gè)回調(diào)函數(shù)是從哪個(gè)組件傳進(jìn)來的?雖然現(xiàn)在有些工具能夠直接在瀏覽器上查看組件之間的嵌套關(guān)系,但那也是在應(yīng)用能夠正常跑起來的情況才能Debug。
3)組件與組件之間的關(guān)系是通過硬編碼實(shí)現(xiàn),如果現(xiàn)在有個(gè)子組件需要替換,可是這個(gè)子組件被嵌入在多個(gè)組件中,試問這得怎么找?
組件嵌套是ReactJs的一大亮點(diǎn),但也是很多人認(rèn)為ReactJs不適合做大型項(xiàng)目的原因。但我覺得這并不是ReactJs的問題,我們完全可以其他途徑解決上面這些問題。比如咱們的Rebus,組件與組件之間不會(huì)直接嵌套,而是跟調(diào)用后臺(tái)Service一樣,通過Rebus.execute()方法,發(fā)起一個(gè)Action。比如TodoApp這個(gè)上層組件,它嵌套了TodoHead/TodoBody/TodoFoot這三個(gè)子組件,但你會(huì)發(fā)現(xiàn)TodoApp組件是通過execute了三個(gè)分別叫GET_TODOHEAD、GET_TODOBODY、GET_TODOFOOT的Action來引入三個(gè)子組件,具體引入是怎么的組件,它并不關(guān)心。
Rebus總線根據(jù)Action路由表(rebus.route.js),分別找到這三個(gè)Action對(duì)應(yīng)實(shí)現(xiàn)者(在這里咱們通過一個(gè)“組件工廠”CompFactory來響應(yīng)這些Action)。當(dāng)我們需要替換組件時(shí),只需要在Action路由表中做出修改便是。
換句話說,在Rebus總線面前,每個(gè)組件都是平等的。組件只會(huì)跟Rebus總線溝通,不會(huì)直接嵌入其它組件,也不會(huì)被嵌到其它組件中?!敖M件樹”這個(gè)概念在Rebus是通過Action消息來實(shí)現(xiàn)的,是一種“動(dòng)態(tài)嵌套”關(guān)系。
7 Rebus中的State在Flux/Redux中,應(yīng)用的各種狀態(tài)以一棵“狀態(tài)樹”的形式都是從根組件上灌進(jìn)去,所有子組件的狀態(tài)一律從這個(gè)根組件上繼承下來(不管組件樹的結(jié)構(gòu)有多深)。這樣做的好處就是一旦某個(gè)狀態(tài)發(fā)生變化,React組件自動(dòng)從上到下進(jìn)行更新。
但是,這么做真的好嗎?并不是說一個(gè)應(yīng)用就一棵狀態(tài)樹這個(gè)想法不好,我也贊同這種設(shè)計(jì),因?yàn)闋顟B(tài)是Web應(yīng)用中最重要但又非常容易混亂的信息,“唯一性”對(duì)狀態(tài)來說,非常重要。
可是如果所有子組件的狀態(tài)都是從根組件一層一層傳遞進(jìn)來的話,至少會(huì)有兩個(gè)問題:
組件之間的耦合性高,難以并行開發(fā):子組件的狀態(tài)是由父組件決定。那到底先寫父組件還是先寫子組件?
狀態(tài)變化后,難以跟蹤變化的組件:假設(shè)你的某個(gè)操作修改了某個(gè)狀態(tài),但這個(gè)狀態(tài)的變化會(huì)導(dǎo)致哪些組件更新了?光從Store中是看不出的,也無法跟蹤,只能從根組件一層一層往下查,看看這個(gè)State被傳遞到哪個(gè)組件中。
在Rebus中,咱們同樣維系著一棵“狀態(tài)樹”,并在應(yīng)用初始化的時(shí)就加載進(jìn)來的。
但不同的是,組件的狀態(tài)不是從上級(jí)組件中傳遞進(jìn)來,是通過Rebus獲得的,而且組件有權(quán)決定自己關(guān)心哪個(gè)State的變化。
這樣做有幾個(gè)好處:
方便并行開發(fā):因?yàn)榻M件之間沒有太過的耦合性。狀態(tài)都是通過Rebus獲得的,大部分情況下都是直接返回狀態(tài)樹中的某個(gè)狀態(tài),這樣的“淺處理”非常適用于復(fù)雜系統(tǒng)開發(fā)中。
方便單元測試:由于組件直接與狀態(tài)綁定(監(jiān)聽),要對(duì)一個(gè)組件進(jìn)行單元測試,直接修改這個(gè)組件綁定的狀態(tài)便是,即是沒有上級(jí)組件的存在,也不影響測試。
方便維護(hù)代碼: 從上面的代碼中可以清晰地看出某個(gè)組件監(jiān)聽哪些狀態(tài),但反過來,某個(gè)狀態(tài)被哪些組件監(jiān)聽了?從組件的代碼中是沒法直觀看出來的。這個(gè)問題也不應(yīng)該通過查閱代碼的形式來解決,而應(yīng)該通過咱們的Rebus來解決。咱們可以給Rebus增加一個(gè)方法,打印每一個(gè)State的監(jiān)聽者。如下圖:
現(xiàn)在咱們既可以清晰地看出一個(gè)組件監(jiān)聽了哪些狀態(tài),也能看出一個(gè)狀態(tài)被哪些組件監(jiān)聽。這為代碼的調(diào)試與維護(hù)提供極大的方便。
另外,我們可以輕松地打印出某個(gè)時(shí)刻的狀態(tài)樹或具體某個(gè)狀態(tài)的值。
8 總結(jié)先給有耐心看到這里的人鼓個(gè)掌……然后也給我自己鼓個(gè)掌……因?yàn)閷?duì)于一個(gè)拖延癥極度病患者來說,用業(yè)余時(shí)間寫這么一篇技術(shù)貼真心不容易。當(dāng)我寫這句話的時(shí)候,距離這個(gè)帖子的第一句話,整整隔了一個(gè)月!——大哥,你是一禪指敲鍵盤的嗎?
言歸正傳,總結(jié)下咱們這個(gè)Rebus框架的特點(diǎn):
實(shí)現(xiàn)了單向數(shù)據(jù)流模式,邏輯層次結(jié)構(gòu)淺,思路清晰。
React組件職責(zé)單一,只負(fù)責(zé)渲染與響應(yīng)交互。
以路由表的形式控制Action數(shù)據(jù)的流向,直觀、易維護(hù)。
React組件之間通過消息的形式實(shí)現(xiàn)動(dòng)態(tài)嵌套。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78606.html
摘要:二面休息過后,就來了第二位面試官,面我運(yùn)維的,運(yùn)開嘛,如果沒有運(yùn)維知識(shí)肯定是不行的。后來的對(duì)話中,面試官也表示,可能之前做的更多的是的工作,對(duì)于容器這塊不熟悉關(guān)系也不是很大。整個(gè)三面大概也持續(xù)了要有不到一個(gè)小時(shí)。 今天給大家分享我曾經(jīng)在愛奇藝的面試,過程還是比較有意思的,可以給大家一些參考 聊騷階段 嗲妹妹:你好,我是愛奇藝的HR,我們正在招聘運(yùn)維開發(fā)崗位,請(qǐng)問您最近有在看工作機(jī)會(huì)嗎...
摘要:希望大家在這浮夸的前端圈里,保持冷靜,堅(jiān)持每天花分鐘來學(xué)習(xí)與思考。 今天的React題沒有太多的故事…… 半個(gè)月前出了248個(gè)Vue的知識(shí)點(diǎn),受到很多朋友的關(guān)注,都強(qiáng)烈要求再出多些React相前的面試題,受到大家的邀請(qǐng),我又找了20多個(gè)React的使用者,他們給出了328道React的面試題,由我整理好發(fā)給大家,同時(shí)發(fā)布在了前端面試每日3+1的React專題,希望對(duì)大家有所幫助,同時(shí)大...
摘要:后來的對(duì)話中,面試官也表示,可能之前做的更多的是的工作,對(duì)于容器這塊不熟悉關(guān)系也不是很大。 showImg(https://segmentfault.com/img/remote/1460000018525265?w=1718&h=808); 這次給大家講講我2年前去愛奇藝面試高級(jí)運(yùn)維開發(fā)崗位的經(jīng)歷,希望對(duì)大家?guī)硪恍椭?公眾號(hào)「Python專欄」后臺(tái)回復(fù):自動(dòng)化運(yùn)維平臺(tái),獲取整套...
摘要:的關(guān)鍵構(gòu)成梳理了一下,需要配合的庫去使用,是因?yàn)橐鉀Q通信問題。還有各個(gè)事件之間,有可能存在依賴關(guān)系,事件后,也觸發(fā)。相比于傳統(tǒng)的事件系統(tǒng),融入了不少的思想。中,將會(huì)是最大的門檻之一。 從學(xué)習(xí)React到現(xiàn)在的一點(diǎn)感受 我覺得應(yīng)該有不少同學(xué)和我一樣,上來學(xué)React,覺得甚是驚艷,看著看著,發(fā)現(xiàn)facebook 安利了一個(gè)flux,圖畫的巨復(fù)雜,然后各種例子都有用這個(gè)東西,沒辦法,硬著...
摘要:另外,內(nèi)置的函數(shù)在經(jīng)過一系列校驗(yàn)后,觸發(fā),之后被更改,之后依次調(diào)用監(jiān)聽,完成整個(gè)狀態(tài)樹的更新??偠灾?,遵守這套規(guī)范并不是強(qiáng)制性的,但是項(xiàng)目一旦稍微復(fù)雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進(jìn)階漫談的第二篇,這一篇主要分析redux的思想和應(yīng)用,同樣參考了網(wǎng)絡(luò)上的大量資料,但代碼同樣都是自己嘗試實(shí)踐所得,在這里分享出來,僅供一起學(xué)習(xí)(上一篇地址:個(gè)人博客/s...
閱讀 2100·2021-11-11 16:55
閱讀 1453·2021-09-28 09:36
閱讀 1062·2019-08-29 15:21
閱讀 1600·2019-08-29 14:10
閱讀 2786·2019-08-29 14:08
閱讀 1657·2019-08-29 12:31
閱讀 3271·2019-08-29 12:31
閱讀 1018·2019-08-26 16:47