做者:北溟小魚hk
連接:https://www.zhihu.com/question/47686258/answer/107209140
來源:知乎
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
css
1、引子html
這是關於一把玄鐵重劍,一本經書,和一套輕功步法的故事。前端
讓咱們先從普通程序猿們的平常工做內容提及,html5
通常來講,程序猿們大部分時間關注的可能不是研發某個具體算法,這是算法工程師/數學家們擅長的東東。程序猿的工做主要是經過調用編程環境中現成的工具函數或接口來實現具體的應用功能,將各個底層接口或算法模塊用代碼有秩序地拼裝聯接起來,實現酷炫好用的產品功能,如同組裝一件樂高玩具同樣。node
序猿的不少工做每每不是圍繞某個高大上的具體算法(「咱們不生產算法,咱們只是算法的搬運工」),而是像代碼界的城管、或者清潔工同樣,關注怎樣組織文件結構,怎樣理清編程思路,怎樣命名變量,怎樣下降代碼耦合度,怎樣提升代碼的複用性和一致性,提升代碼的可讀性和健壯性,怎樣優化分工協做、減小溝通成本等等。無論是OOP、FP等編程思想,仍是MVC等設計模式、或是各類編程語言下的應用開發框架,不少都是爲了幫助程序猿完成這些髒活、累活兒。
具體到web應用開發而言,react以及他的好基友redux都是程序猿們出色的好幫手,所以讓衆多前端開發者一見鍾情,俺也不例外。react
事實上,react和redux從使用的角度來講,是如此輕量溫馨,以致於咱們能夠不把它們看成「開發框架」,而是一種編程模式,或是編程的「腳手架」,用起來很是「小清新」。這一點和angularjs這類「重口味」框架有很大區別(我不推薦使用angularjs,固然每一個人口味不一樣,最好本身上手體驗再作取捨)。其實本人接觸react比較晚,但一試用就有種血槽猛漲的感受,強烈建議還沒上手react的前端程序猿們試用一下!webpack
2、正文css3
咱們首先舉兩個栗子:程序員
(一)江湖舊事1angularjs
在web1.0的純網頁時代,前端開發實際上是比較happy的,這是由於網頁上幾乎不須要什麼交互,前端開發者基本上只須要根據後臺提供的數據將網頁內容排版呈現出來便可。用戶的交互行爲通常僅限於填寫一個表單,而後把數據提交到服務器,提交成功後,直接刷新整個頁面。
咱們以常見的todoList爲例,當須要添加一條todo任務的時候,用web1.0的思路,典型的流程是這樣的:
然而,當頁面交互變得豐富細膩、內容變得龐大複雜以後,這種基於服務器維護state,而後頁面總體刷新的web1.0方式存在兩個嚴重的缺陷:
正是因爲這兩個顯著缺陷,才致使ajax技術的出現。自從有了ajax通訊和局部頁面更新的機制之後,媽媽不再用擔憂咱們頁面總體刷新致使的用戶體驗問題了!因而前端開發大踏步地進入web2.0時代,大量交互細膩,內容豐富的SPA(single page application)也應運而生。但與此同時,前端開發的工做也今後變得苦逼起來。。。由於如今根據後臺數據排版生成頁面,如今只是最基本的工做。當用戶進行某種交互操做引發頁面狀態變化以後,爲了不頁面總體刷新,咱們須要當心翼翼地將各個相關的頁面局部元素揀選出來,作適當修改,再放回原處,動做通常不會太優雅。(腦補一下《加勒比海盜》中,把眼球拿出來擦一擦又塞回去的那個海盜。。)
當頁面邏輯簡單,交互行爲較少時,這種局部修改也許很容易搞定;然而,一旦頁面中各個元素的關係變得複雜,各類交互操做耦合起來以後,這種局部修改就會消耗大量腦細胞,而且咱們須要同時對用戶操做和服務器反饋作出響應,並確保頁面狀態和服務器狀態的一致性,因而咱們就很容易變得左支右絀、顧此失彼。相信很多前端工程師都有過相似的體驗。
總之,從上面的例子咱們得出兩個經驗:(二)江湖舊事2
其實,經過改變state,來讓view自動更新,這個想法一點也不新鮮。css中各類基於className的樣式聲明,就是典型表明(改變div的className至關於改變它對應的state, 尤爲是css3引入transition和animation以後,這種用css來偷懶的作法愈來愈常見),咱們再舉個例子,好比說咱們要作一個菜單:
生手碰到這個問題,也許會這樣作:
當用戶點擊按鈕的時候,先改變菜單名的色值,再改變按鈕的背景圖(由向下箭頭,改爲向上箭頭),最後改變下拉列表的顯示狀態,有必要的話,還須要作個列表下拉的動畫。當用戶再次點擊按鈕的時候,把全部操做倒着來一遍,恢復菜單收起的狀態,ok?
然而,對於好逸惡勞的前端老司機們來講,通常會這樣作:對整個菜單的容器定義兩個className,好比一個是"menu-close",一個是「menu-open」。在menu-close的狀態下,菜單名的色值爲黑色,按鈕背景圖爲向下箭頭,下拉列表是隱藏狀態;在menu-open的狀態下,菜單名的色值爲白色,按鈕背景圖是向上箭頭,下拉列表是顯示狀態;須要的話,再用css3 transition給下拉列表加個動畫過渡效果。當用戶點擊按鈕的時候,只須要改變整個菜單容器的className便可。全部的交互效果都用css「平鋪」的方式聲明出來,這樣作不只節省了多個手工步驟,並且更改整個容器的className是個單點操做,所以也方便維護和修改。這也是bootstrap框架裏慣用的一招,很是好使。
經過這個例子,咱們看到「改變state,讓view自動更新」的開發思想在純粹的前端領域也由來已久。它可讓開發者思惟更加清晰,代碼更好維護,幸福指數飆升!
然而用css className來實現state功能,其應用範圍是很是有限的。緣由很簡單,css只能改變DOM元素的樣式,卻不能改變網頁中DOM tree自己的結構(例如,例子1中todoList的增刪操做就涉及li元素的append或者remove),更沒有辦法直接和具體的業務數據相關聯。因而,knockoutjs、angularjs等前端框架紛紛登場,這些框架能夠系統地實現view 和 state(通常在這些框架裏稱爲ViewModel)的相互綁定,從而使代碼更有秩序,幫前端開發省去很多麻煩。即使這些框架約定不少,有些束手束腳的感受,但若是沒有另外一件大殺器重出江湖,程序猿們也能夠將就過了,奈何既生瑜,何生亮。。這是後話。
(三)重劍無鋒
好,拋開knockout、angular這些框架,如今若是讓咱們本身設計一套根據states(包括後臺業務數據和前端臨時數據,例如表單input值、某個面板的顯隱狀態等等)自動更新頁面的機制,咱們該怎麼辦呢?
對於每一種特定場景,咱們也許有不少種代碼方式來根據state自動更新頁面的某個局部view。可是,若是頁面交互足夠複雜,以致於咱們須要在頁面的不少地方不斷修修補補,而且這些「補丁」對應的state可能還彼此重疊,或者咱們但願能一勞永逸地解決全部view自動更新的問題,而且還不引入更多繁瑣的約定,彷佛除了刷新整個頁面沒有更好的辦法(這有點像逐幀動畫的原理,咱們通常不會真的根據物體運動軌跡一點一點修改畫面,造成運動效果,而是直接一幀一幀*重繪整個畫布,造成動畫效果!)
但前面咱們說過,像web1.0的作法同樣,重繪整個頁面對瀏覽器的性能損耗是很嚴重的,用戶體驗也很糟糕。。怎麼辦?怎麼辦?!我腦補着facebook的某個程序員在一個月黑風高的晚上坐在公司電腦前,抿了一口濃濃的咖啡,忽然靈光一現,伴着屏幕上忽明忽暗的幽幽藍光,在文本編輯器裏寫下這麼一行文字:可不能夠把瀏覽器裏的DOM tree克隆一份完整的鏡像到內存,也就是所謂的「virtual DOM」,當頁面的state發生變化之後,根據最新的state從新生成一份virtual DOM(至關於在內存裏「刷新」整個頁面),將它和以前的virtual DOM作比對(diff),而後在瀏覽器裏只渲染被改變的那部份內容,這樣瀏覽器的性能損耗和用戶體驗不就都不成問題了嗎?而咱們知道在絕大部分網頁應用中js引擎的性能和內存徹底沒有被充分利用,咱們正好能夠火力全開,利用js的這部分性能紅利,實現內存中virtual DOM的diff工做,完美!
因而React橫空出世。
話雖簡單,不過單單是在內存中模擬整個DOM tree,這個工做想一想就以爲頭大,因此不得不佩服facebook的那些前端大神們!表面上react花了很大氣力卻只作了view層跟virtual DOM相關的工做,但所謂「大巧不工、重劍無鋒」,實際上facebook祭出的這件大殺器讓「改變state,view自動更新」這種直觀樸素的想法有了堅實的基礎,「state-view」的開發模式今後一馬平川!正由於如此,伴隨着react的崛起,相似於redux這些專一於管理state的輕量級框架也變得煊赫一時起來。
有了React這把重劍,前端開發們第一次感受到彷佛又回到了web1.0美好的田園時代!而react很是具備表達力的jsx語法和完善的模塊化結構,又讓咱們以爲像生活在酷炫的將來時代!這是咱們的下一話題。( 此處應有《Back to The Future》的電影配樂)
(四)庖丁解牛(view的模塊化)
view的組件化和模塊化很是有利於分工協做、代碼的積累複用以及單元測試。這對於提升團隊開發的效率無疑具備很是重要的意義,這也是react廣受青睞的重要緣由之一,這一點就再也不贅述。這裏想換個角度,聊一下react的模塊化機制,對於開發者個體來講有什麼好處?
前面咱們提到,因爲React的「state-view」模式可讓開發者的大腦獲得一種「單向流」的溫馨體驗。那爲何單向流的思惟狀態更加溫馨呢?
這是由於在**單向流**狀態下,要解決的問題如同一個函數映射,已知什麼(好比state)是固定不變的,要獲得什麼(好比view)是定義明確,而人的思惟很是習慣於這種定義明確的、沒有「分叉」和「環路」的函數式問題。
也就是說,讓人抓狂崩潰的每每是那些包含「分叉」或「環路」的非函數式問題,這個時候大腦不得不思前想後,謀劃全局,進入一種「多線程」工做狀態,而「單線程」做業對於大腦來講通常纔是更加輕鬆高效。所謂「單線程」,就是每時每刻只專一於一個問題 —— one at a time! React的"state-view"模式幫助咱們在開發view的時候,只須要專一於view(即關注頁面佈局樣式。view上的交互行爲怎樣對state產生反饋做用,咱們稍後再來討論),而React簡便的「模塊化」機制(即只須要寫不多量的boilerplate代碼,就能夠定義或引用一個新模塊),讓咱們能夠根據須要,將整個頁面的佈局樣式工做進一步拆分紅各個小模塊(view component),或者將各個小模塊組裝成大模塊,從而進一步深刻貫徹「one at a time」的原則,給大腦減負,所以這時程序猿很容易進入一種溫馨高效的狀態(心理學中甚至有個「心流」的概念用來描述這種狀態)。決定頁面呈現的state能夠經過模塊屬性(props)從父模塊傳遞到子模塊。這種"樹狀"分流機制,有點像植物將營養(state)從根部不斷運輸到細枝末葉的過程,如圖所示:
好,到目前爲止,咱們看到react已經幾乎完美地幫咱們理順了從state到view的開發流程。可是前面的討論過程當中,彷佛還有一朵「小烏雲」沒清理,那就是怎樣實現從view到state的反饋流程。也就是說,用戶在view上的交互行爲(好比點擊提交按鈕等)應當引發state改變的時候,這個流程該怎麼處理?這是咱們要聊的下一話題。
(五)易筋經(flux思想)
a. 幾點說明
若是要用一門武林絕學比喻flux思想,我首先想到的是易筋經。此功雖無固定招式,但意會以後,就會有種打通全身經絡、氣血通暢的感受,而且能夠將前端開發中的其餘武功串聯起來,運用自如 : )
關於flux首先有幾點須要說明:b. MVC模式
在講flux以前,咱們不得不首先提一下大名鼎鼎的MVC開發模式。
所謂MVC開發模式, 主要講的是在開發交互應用時,怎樣將不一樣功能的代碼拆分到不一樣文件或區塊,以便下降代碼的耦合度,提升代碼的可讀性和健壯性。簡單理解就是:要將 Model-View-Controller 這三部分代碼拆分到不一樣文件。
對於服務器端開發,Model指的是和處理業務數據相關的代碼,例如經過ORM實現數據庫的增刪改查等操做;View指的是和頁面組裝相關的代碼,主要是和各類模版引擎(例如Java Velocity、PHP Smarty、nodejs ejs等等)相關的代碼部分;Controller指的是和用戶交互行爲相關的代碼,具體到網站後臺應用,指的就是對各類http請求的handler,也就是根據不一樣**url路徑和http請求的參數**,將數據和模版綁定到一塊兒,最終造成頁面呈現給用戶。
對於網站前端開發,在web1.0時代,因爲js基本上只是個跑龍套的小角色,因此不須要什麼設計模式;可是隨着web應用功能變得愈來愈豐富、愈來愈複雜,js的地位也愈來愈靠近舞臺中心。在目前狀況下,若是沒有必定的設計模式做爲指導,其實很難開發出真正大型複雜的html5應用,也很難實現分工協做和持續維護。
因而MVC模式被很天然地引入到前端開發領域。也許某種意義上,前端開發的整個MVC,僅僅對應於後臺開發眼中的View部分;但其實,現在前端MVC思想的深刻性和重要性,對於整個web應用來講,其實一點也不遜色於服務器端的MVC。
具體而言,前端開發的Model至關於後臺數據的鏡像或緩存池,它和服務器端MVC中的Model概念一脈相承;View對應頁面的呈現,主要指的是和html、css相關的代碼,它和服務器端MVC中的View概念也很是相近。
顯著的差異來自於controller:在後臺應用中,用戶和服務器之間的交互是經過http請求實現的,所以後臺controller的表達形式是http請求的handler,而且和router(定義網站的url規則)緊密相關; 而前端應用中,用戶和網頁之間的交互主要是經過操做事件(例如點擊鼠標、鍵盤輸入等)實現的,所以前端的controller這裏能夠簡單理解爲各類交互事件的handler。
固然, 前端controller的概念是個大雜燴,好比angularjs中的controller被定義爲一個做用域($scope)的閉包, 參考 AngularJS文檔,這個閉包能夠和一段html模版綁定在一塊兒,最終將數據渲染到模版中造成頁面。大約正是由於這種將數據和模版綁定的功能,很是相似於後臺應用中的controller,所以不少框架包括angular將這種功能模塊稱爲controller。爲避免混淆,強調一下:後面咱們用controller指代 「凡是和交互事件handler相關的代碼單元」
backbonejs 是一個廣受歡迎的輕量級MVC前端框架,咱們先來看一個 Backbone.js Todo Example,下面是其View組件的代碼片段:
雖然這種View對Model直接修改的方式很是直截了當,適合小型web應用,然而一但web應用中存在多個Model,多個View,那麼Model和View之間的決定關係就可能變得混亂,難以駕馭,以下圖所示:
怎麼破??
c. Flux模式
前面咱們提到「單向流」的思惟狀態可讓大腦更加輕鬆駕馭,本質上而言,這也是爲何上面這種雜亂的雙向圖示讓咱們感到無所適從的緣由。咱們注意到:之因此圖示中 Model-View (MVC中的Model大致上能夠看做是前面提到的State)的「單向流」被破壞,是因爲修改Model的Controller代碼像一把黃豆同樣散落在了各個View組件的內部,若是能夠用某種方式把這些散落的代碼單獨收攏到一塊兒,是否是就讓這可讓這張圖示恢復秩序呢?好,咱們順着這個思路想下去。
如今咱們又能夠從服務器端的MVC模式中得到靈感了!由於咱們注意到,服務器端的controller一般也須要對不少Model產生修改,但在代碼結構中卻集中在一塊兒,沒有散落一地。緣由很簡單,因爲server和client是遠程通訊的關係,所以爲了儘可能減小通訊耦合,client每一個操做的所有信息都以http請求的形式被歸納成了精簡的「做用量」(action)。請求的url路徑約定了用戶的操做意圖(固然RESTful概念中,請求的method也能夠反映操做意圖),request參數表徵了該「意圖」的具體內容。正是基於這個action的抽象,client端的交互操做才能夠被集中轉移到server端的controller中作統一響應。
對比之下,咱們馬上發現上述代碼片段中前端MVC模式的「痛點」所在:不是MVC模式錯了,而是咱們壓根缺乏了一個和用戶交互行爲有關的action抽象!所以,對model的具體操做才無法從各個view組件中被剝離出來,放到一處。
參考http請求,咱們將要定義的action,須要一個typeName用來表示對model操做的意圖(相似於http請求的url路徑),還可能須要其餘字段,用來描述怎樣具體操做model(相似於http請求的參數)。
也就是說,當用戶在view上的交互行爲(例如點擊提交按鈕)應當引發Model發生變化時,咱們不直接修改model,而是簡單地dispatch一個action(其實跟常見的event機制沒有什麼區別)以表達修改model的意圖,這些action將被集中轉移給數據端(models),而後數據端會根據這些action作出須要的自我更新。同時,咱們考慮到react中view組件的樹狀分流結構,因此有以下圖所示:
圖中A表示Action,V表示View組件,Models部分的結構會進一步討論。稍微總結一下:從代碼層面而言,flux無非就是一個常見的event dispatcher,其目的是要將以往MVC中各個View組件內的controller代碼片段提取出來放到更加恰當的地方進行集中化管理,並從開發體驗上實現了溫馨清爽、容易駕馭的「單向流」模式。 因此我以爲,Flux與其說是對前端MVC模式的顛覆,倒不如說是對前端MVC思想的補充和優化。
但爲了區分於以往的MVC模式,並向facebook的貢獻表達敬意,後面咱們將把這種優化後的 Model-View-Controller 開發模式在React背景下正式稱爲Flux模式
好,易筋經Flux練到這裏,打完收工。到目前爲止,咱們看到React的獨孤九劍能夠經過View Component把頁面呈現進行「原子化」拆分(即上圖中蘭色區域的樹狀分流結構);Flux打通了State-View的任督二脈(綠色區域),並經過action抽象把用戶交互行爲進行了「原子化」拆分;那麼聯繫上面的圖示,咱們天然要問數據端(紫色區域)的處理,能否一樣被「原子化」拆分?這是咱們要聊的下一門武林絕學。
(六)凌波微步(數據端的「原子化」)
redux 登場
凌波微步指的是redux中的reducer機制,能夠用來將state端的數據處理過程做「原子化」拆分。redux是來自函數式編程(Functional Programming)的一朵奇葩,聽說頗有背景([參考連接](Prior Art | Redux) )。本人尚未深究過,但一接觸redux,就馬上被其reducer機制的輕盈小巧驚豔到(redux庫自己也只有幾kb,有必要的化,本身重寫也不是難事),所以稱其爲「凌波微步」。
reducer,從代碼上說,其實就是一個函數,具備以下形式:
(previousState, action) => newState
即,reducer做爲一個函數,能夠根據web應用以前的狀態(previousState)和交互行爲(經過flux中提到的action來表徵),決定web應用的下一狀態(newState),從而實現state端的數據更新處理。這個函數行爲和大名鼎鼎的「Map-Reduce」概念中的Reduce操做很是相似,於是稱這個函數爲「Reducer」。
"shut up and show me the code"
ok,咱們仍是以todoList應用爲例, 此處有[完整代碼](Example: Todo List)。這裏不打算詳細講解Redux的具體使用,而只想經過一個Redux對state數據進行操做的代碼片段,管窺一下reducer機制對數據進行拆分和組裝的簡潔過程。代碼片段以下:
visibilityFilter是和列表顯示狀態相關的另外一個reducer;combineReducers將visibilityFilter和todos合併爲整個應用的reducer,也就是todoApp。這個過程,從感受上也和react中view組件的合併過程很是相像。
createStore是一個工廠函數。經過它,todoApp(至關於一個數據處理的引擎)被裝配到整個應用的state容器,也就是store中。能夠經過store的getState方法獲取整個應用的state;同時,store也是一個event dispatcher,能夠經過其dispatch和subscribe方法,分別實現觸發action事件和註冊對action事件的響應函數。總言之,從概念上來講 Redux = Reducer + Flux
3、總結
全體亮相
好,如今React開發模式中的幾個核心概念已經所有出場亮相。咱們俯瞰一下整個開發流程:首先,react框架爲咱們理順了 store --> view 的「單向」工做流(store是state的容器);而後,redux框架爲咱們理順了 view --> store 的**「單向」**工做流。而且,react和redux都以組件化的形式能夠將各自負責的功能進行靈活地組裝或拆分,最大程度上確保咱們「一次只須要專一於一個局部問題」。具體來講,分爲如下步驟:
用圖示的方式來表達,即,
要你命三千
固然實際的react-redux開發步驟中也有很多變通,例如爲了能在適當條件下節省些參數傳遞的代碼,redux中提供了provider機制,它像《星際迷航》中的瞬間傳送機同樣,能夠繞過view組件的hierarchy把state一步到位地傳遞到末端view組件;再好比redux雖然鼓勵使用pure function(即不含side effect的函數), 但咱們通常仍是會經過side effect實現store和服務器端的通訊。這些變通,以爲是好事。畢竟再嚴格的框架,也經不起胡亂使用;再完美的框架,也很難說能包治百病,倒不如提供一些臺階,讓程序猿們去自由發揮吧。
除了React、Reducer、Flux這三駕馬車的主線情節外,react開發模式的周邊生態也有不少振奮人心的新鮮事物,例如:擁抱函數式編程和模塊化的es6語法,日趨成熟的模塊資源管理工具npm,自動化編程及打包神器webpack, 讓初始化性能和SEO再也不成爲問題的server-side rendering方案,將觸角伸到原生開發領域的React-Native系列項目等等,這些零零總總加起來,不禁得讓人想起《國產凌凌漆》中集各類武器於一身的終極武器 「要你命三千」!