理解 React,但不理解 Redux,該如何通俗易懂的理解 Redux?
網上文章都看不太懂啊,什麼action,reducer的,求解釋
解答這個問題並不困難:唯一的要求是你熟悉React。
不要光聽別人描述名詞,理解起來是很困難的。從需求出發,看看使用React需要什麼:1. React有props和state: props意味著父級分發下來的屬性,state意味著組件內部可以自行管理的狀態,並且整個React沒有數據向上回溯的能力,也就是說數據只能單向向下分發,或者自行內部消化。理解這個是理解React和Redux的前提。
2. 一般構建的React組件內部可能是一個完整的應用,它自己工作良好,你可以通過屬性作為API控制它。但是更多的時候發現React根本無法讓兩個組件互相交流,使用對方的數據。然後這時候不通過DOM溝通(也就是React體制內)解決的唯一辦法就是提升state,將state放到共有的父組件中來管理,再作為props分發回子組件。3. 子組件改變父組件state的辦法只能是通過onClick觸發父組件聲明好的回調,也就是父組件提前聲明好函數或方法作為契約描述自己的state將如何變化,再將它同樣作為屬性交給子組件使用。這樣就出現了一個模式:數據總是單向從頂層向下分發的,但是只有子組件回調在概念上可以回到state頂層影響數據。這樣state一定程度上是響應式的。4. 為了面臨所有可能的擴展問題,最容易想到的辦法就是把所有state集中放到所有組件頂層,然後分發給所有組件。5. 為了有更好的state管理,就需要一個庫來作為更專業的頂層state分發給所有React應用,這就是Redux。讓我們回來看看重現上面結構的需求:a. 需要回調通知state (等同於回調參數) -&> actionb. 需要根據回調處理 (等同於父級方法) -&> reducerc. 需要state (等同於總狀態) -&> store對Redux來說只有這三個要素:
a. action是純聲明式的數據結構,只提供事件的所有要素,不提供邏輯。b. reducer是一個匹配函數,action的發送是全局的:所有的reducer都可以捕捉到並匹配與自己相關與否,相關就拿走action中的要素進行邏輯處理,修改store中的狀態,不相關就不對state做處理原樣返回。c. store負責存儲狀態並可以被react api回調,發布action.當然一般不會直接把兩個庫拿來用,還有一個binding叫react-redux, 提供一個Provider和connect。很多人其實看懂了redux卡在這裡。a. Provider是一個普通組件,可以作為頂層app的分發點,它只需要store屬性就可以了。它會將state分發給所有被connect的組件,不管它在哪裡,被嵌套多少層。b. connect是真正的重點,它是一個科里化函數,意思是先接受兩個參數(數據綁定mapStateToProps和事件綁定mapDispatchToProps),再接受一個參數(將要綁定的組件本身):mapStateToProps:構建好Redux系統的時候,它會被自動初始化,但是你的React組件並不知道它的存在,因此你需要分揀出你需要的Redux狀態,所以你需要綁定一個函數,它的參數是state,簡單返回你關心的幾個值。mapDispatchToProps:聲明好的action作為回調,也可以被注入到組件里,就是通過這個函數,它的參數是dispatch,通過redux的輔助方法bindActionCreator綁定所有action以及參數的dispatch,就可以作為屬性在組件裡面作為函數簡單使用了,不需要手動dispatch。這個mapDispatchToProps是可選的,如果不傳這個參數redux會簡單把dispatch作為屬性注入給組件,可以手動當做store.dispatch使用。這也是為什麼要科里化的原因。做好以上流程Redux和React就可以工作了。簡單地說就是:1.頂層分髮狀態,讓React組件被動地渲染。
2.監聽事件,事件有權利回到所有狀態頂層影響狀態。關鍵是Redux並不「通俗」。
要明白Redux,就要明白為什麼需要Redux。
圖一,這是一個模塊化的項目,這是它的模塊「間」通信
圖二,這是另一個項目,去除了模塊「間」通信,通信統一通過中間那個方塊,模塊只向這個方塊發送通信請求,由方塊決定這個請求如何處理,是改變方塊內部的某個狀態,還是讓另一個模塊來處理這個請求。
Redux的實現,是很不「通俗」的。有比他更通俗的實現,一直就存在的pubsub,這兩年說的要替代Redux的MobX(observable data)。這些東西的架構目的是類似的:更好維護的模塊間通信。
action 這些是由模塊發起的通信請求,其實就是一個數據對象。
reducer 就是方塊內部對這些數據對象的篩選處理過程,為什麼叫reducer,想像一下有多個模塊依次請求改變方塊內一個狀態,效率起見,這個狀態的最終值是最後一個請求的值,所以用一個reducer來「整理歸總」這樣的請求,狀態改變一次就夠了。
Redux 畢竟是從 Flux 進化而來的,所以不如先看看這個問題下面的回答:如何理解 Facebook 的 flux 應用架構? - 前端開發
然後你就對 Flux/Redux 的意義大致有所了解了。要打比喻的話, @haochuan 的答案也已經解釋的比較直白了。這裡我想補充下 Redux 具體的 API 為什麼是這個樣子,它有哪些獨特的設計上的考量:
1. 強調數據的 immutability,但是又不想強制使用 Immutable.js,因此採用了 reducer 這樣的設計,當接收到 action 的時候,不是原地修改數據,而是返回一份全新的數據。這個設計巧妙的地方在於不依賴任何庫就模擬了 immutable data,但是 reducer 寫起來就比較蛋疼,很依賴目前尚在 stage 2 的 object spread 語法。這個設計也有較大一部分原因是因為 React 的渲染機制,immutable 的數據對於 React 的渲染優化非常有利。
2. Redux 的一大賣點就是幾乎所有的部件都能熱重載。為了能熱重載,各個部件都需要儘可能的減少對 singleton 模塊的依賴。舉例來說你不會在 action creator 裡面直接引用 store.dispatch,而是通過 redux-thunk 返回一個函數,這個函數會獲得一個 dispatch 函數(有點像依賴注入)。這樣 action creator 本身不和任意 store 有直接關係,而是在組件里實際使用時才用 bindActionCreators 和具體的 store 實例綁定。這一點也使得 Redux 對 server-side rendering 友好(因為每一個請求都需要一個全新的 store 實例,如果部件依賴 store singleton 就會很難做)
3. 各個部件最好都是 pure function,至少也得嚴格限制副作用。這可以使得各部件容易維護、移植、測試。為了在 action creators 裡面減少副作用,可以將副作用按類別抽取到 middlewares 當中去管理。
4. Single state tree,方便對整個狀態樹打快照以及 time-travel。另一個賣點。
Redux 骨子裡還是延續了 Flux 的思想,並且引入了大量函數式編程的影響。它的 API 有時候感覺很繁瑣,有點繞圈子,一方面是因為這些設計上的取捨,一方面是因為過分強調 functional composition,恨不得所有的 API 都是一個套一個的函數。然而本質上來說,這些函數式的東西並不是 Flux 所必須的元素。相比之下如 Reflux 則是完全的實用主義,對以上這些沒有沒有考慮,API 怎麼直接怎麼來,然而也就沒有 hot-reload / time-travel 這樣的 fancy 功能了。
最後,還是不得不提一下 Vuex:GitHub - vuejs/vuex: Flux-inspired Application Architecture for Vue.js. 倒不是為了安利,只是因為從設計上來說,Vuex 還是受到了 Redux 不少的影響,但是在 Redux 的優點和 API 的直接簡單之間做了一個平衡:不強求 immutability,不強求 functional API,但是依然保證部件的可測試、可熱重載、time-travel,並且對數據獲取默認優化(類似 redux + reselect)。高度抽象後就是一個函數:
function Redux(action, state, reducer) {return reducer(action, state);
}
然後這麼用:
Redux("INCR", 1, function (action, state) { if (action === "INCR") { return state + 1;}
return state; // Unknown action.});action 就是你要乾的事;state 是當前的狀態;reducer 是個純函數,給定一定的參數會得出一定的結果,這個結果就是新的 state。首先感謝樓主這個問題讓我在歡樂中寫了這篇文章:關於Redux到底是個什麼鬼 - _haochuan的RandomNotes - 知乎專欄
為了本著回答問題的原則,特地把文章內容貼過來供參考。
=============== 我是分割線 ===============
=============== 我是分割線 ============================== 我是分割線 ===============我們故事的主人公,小明。
小明大學剛畢業,擺脫了宿舍的集體生活,自己在外面租了個一室一廳的小公寓住。
這是客廳的平面圖:
一天小明邀請小馬來家裡做客。小馬說:呀你家的傢具擺放位置好奇特!這種通過眼睛看到的視覺效果,就是通過React來實現的。每一個傢具都是一個component,各種不同的components組成了一個我們的web的『頁面』,或者說是所謂的『view"。
又有一天小馬又來家裡做客,街邊買了50個肉串和10個大腰子,準備和小明一起邊擼串兒邊看人類和電腦下棋的電視節目。但是小馬突然發現,莫名其妙的你妹為什麼在這老子坐下來邊吃東西邊看電視貌似是無法滿足的需求啊。這種想法來源於小馬與各種傢具(components)的一些交互。之後小馬跟小明說,我們能不能想點辦法來解決這個問題呢?
通過激烈的討論和商議,小明和小馬決定重新擺放傢具的位置,然後畫出了圖紙如下:
有了圖紙就要準備幹活了。第二天,小明叫來了李雷和韓梅梅來幫忙,小明說:
「李雷,幫我把電視搬到沙發正對面然後靠牆的地方」
「韓梅梅,幫我把桌子向沙發方向平移5米」十分鐘之後,房間內傢具的位置變成了像圖紙的那樣。
問題解決了,第三天。小明,小馬,李雷,韓梅梅四個小夥伴在家裡快樂的吃起了火鍋。
完。
=============== 我是分割線 ===============
先讓我們回顧下整個故事:
小馬發現傢具的擺放不合理 ---&> 畫出規劃圖紙 ---&> 小明給李雷和韓梅梅分配任務 ---&> 李雷和韓梅梅動手搬傢具 ---&> 傢具布局改變
再來說下Redux里的幾個核心概念,這裡我們把React也加進來:- view(React)
- store(state)
- action
- reducer
- view(React) = 傢具的擺放在視覺的效果上
- store(state) = 每個傢具在空間內的坐標(如:電視的位置是x:10, y: 400)
- action = 小明分配任務(誰應該幹什麼)
- reducer = 具體任務都幹些什麼(把電視搬到沙發正對面然後靠牆的地方)
所以這個過程應該是這樣的:
view ---&> action ---&> reducer ---&> store(state) ---&> view
如果放入一個web app中,首先store(state)決定了view,然後用戶與view的交互會產生action,這些action會觸發reducer因而改變state,然後state的改變又造成了view的變化。理解Redux其實主要就理解3個概念:
1. Action
Action說白了就是一個帶type屬性的JavaScript對象,長下面這樣:
{
type: ADD_TODO,
text: "Build my first Redux app"
}
2.Store
Store就是我們存取狀態數據的地方,外加可以訂閱狀態數據改變時觸發的事件。
3.Reducer
唯一不好理解的可能就是reducer了,首先它的命名並不代表它的含義,完全是因為它和:
// JavaScript 中 Array 的 reduce 方法
[0, 1, 2, 3, 4].reduce( (prev, curr) =&> prev + curr );
// reducer
(previousState, action) =&> newState
因為它和reduce方法中的回調函數長得很像,所以就叫reduce + r(類比會開車的就叫老司機,所以會「開車」的都叫老司機),沒有別的道理可講(之前中文文檔對此翻譯可能有誤,不過我已經修正了)。如果你感覺這種命名方法有歧義還容易引起誤會,那麼你可以選擇去給redux提交pr,指出一種更為合理的命名方法。
更詳細的解釋可以查閱Redux中的reducer到底是什麼,以及它為什麼叫reducer?
首先需要明白 Redux 的單一狀態樹的概念,所謂的單一狀態樹,就是指「所有的 state 都以一個對象樹的形式儲存在一個單一的 store 中。」比如我們有這麼一個狀態樹(或者你叫它狀態對象也行):
{
text : "Hello world"
}
Hello world &&
{
text : "Hello Stark"
}
Hello Stark &&
function changeText(){
return {
type: "CHANGE_TEXT",
newText: "Hello Stark"
}
}
(state, action) =&> newState
const initialState = {
text : "Hello world"
}
function Reducer(state=initialState, action) {
switch(action.type) {
case "CHANGE_TEXT":
return {
text : "Hello Stark"
}
default:
return state;
}
}
Store 就是把 Reducer 和 action 聯繫到一起的對象。Store 有以下職責:
- 維持應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 註冊監聽器;
import { createStore } from "redux"
//這裡的 Reducer 就是剛才的 Reducer 函數
let store = createStore(Reducer);
store.dispatch( changeText() );
store.getState(); // { text : "Hello Stark" }
- 搭配 React | Redux 中文文檔
- 用React+Redux+ES6寫一個最傻瓜的Hello World
想了半天好像實在想不出什麼非程序員視角能夠解釋 Redux 精妙之處的例子,不過突然想到之前看過的一篇 A cartoon intro to Redux 還挺有意思的。
基本沒有任何代碼,解釋了 Redux 中各個角色的關係,以及怎麼互相聯繫、組織,甚至還討論了 Redux 和 Flux 的區別,值得一看。
原文:A cartoon intro to Redux a€」 Code Cartoons
譯文:GitHub - jasonslyvia/a-cartoon-intro-to-redux-cn: 看漫畫,學 Redux。不寫一行代碼,輕鬆看懂 Redux 原理!附送一些 Redux 的基礎概念:- Redux 的核心是一個 store。
- store 是一個 JavaScript 對象,通過 Redux 提供的 createStore(reducers) 方法創建。
- store 有兩個核心方法: .getState() 和 .dispatch()。
- .getState() 返回一個 JavaScript 對象,代表 store 當前的狀態。
- .dispatch() 接受一個 action 作為參數,將這個 action 分發給所有訂閱了更新的 reducer。
- action 是一個 JavaScript 對象,通常包含了 type、payload 等欄位,用於描述發生的事件及相關信息(使用 Redux 中間件可以讓你 dispatch 其它類型的 action,此處不展開)。
- reducer 是一個 JavaScript 函數,函數簽名為 (previousState, action) =&> newState,即接受 previousState 和 action 兩個參數,根據 action 中攜帶的信息對 previousState 做出相應的處理,並返回一個新的 state。
都是函數式編程的常見設計模式如果真的不懂話,可以考慮曲線救國 學學函數式思想
剛進新公司,花了一天的時間學了下react和redux,說一點淺顯的理解。react在數據為王的網路世界中,數據永遠都是頁面的核心,那麼對於數據管理的模式,react採用的是單向數據流模式,單向也就是數據只能從一個方向流向另外一個方向而不能反過來,如果把dom想像成一顆樹,單向數據流就是將數據自上向下的流動,為了讓數據流到儘可能多的dom中,肯定要把數據儘可能放的高一點。這裡的數據可以簡單理解為state,而對於流到下面的數據,dom通過props接收。這樣模式就很顯而易見了,儘可能高的組件對state進行更新,子組件的props也會隨即更新,數據單向流動,這時候如果想通過子組件來反向更新state,就要通過上層組件傳遞一個函數,在函數中通過setState等方法來達到反向數據的更新。其實到這已經可以應付一些簡單的應用了,但是對於複雜的應用,組件之間的數據通信有可能是交叉而又錯綜複雜的,這時候就希望通過一種統一的方式將數據好好管理起來,那麼出現了redux。redux有三劍客action,reducer,storestore,把它想像成一個物流倉庫中心,數據就是一個個包裹,為了便於對包裹的管理,需要給它們進行包裝,比如打上標籤,標明這是聯邦快遞,還是申通快遞,你是要寄快遞還是退快遞,要發到江浙滬,還是發到偏遠地帶…那對於一個新來的包裹,首先要做的第一步就是包裝,這一步由action完成,第二步,應該是根據他的標籤做針對性的處理,圓通的快遞員處理圓通快遞,申通的快遞員處理申通的快遞,這一步,由reducer完成,他根據action.type做針對性的處理。如果我想向物流倉庫中心發一個快遞,就通過store的dispatch方法,先給包裹包裝,然後丟到物流中心由一個個快遞員(reducer)進行處理。到這裡講述的是物流中心的運作模式,那物流中心該如何去對接現實世界(react)呢。答案是通過connect。connect作為現實世界和物流中心的管道,擁有絕對寶貴的信息,其中之一就是他能實時得知任何一個包裹的物流進度,這個包裹是在攬件,還是因為某20停在一個神奇的地方,這些信息connect通過一個函數作為參數,比如這個函數叫select,在這個函數中有一個state的參數,這個參數就存放著整個物流中心包裹實時物流情況,絕對寶貴,傳說只要誰掌握了它,誰就能成為王的男人…那麼select就可以對這個信息做各種處理,比如某人發了一個圓通快遞,從上海發往北京,那select就可以針對這個包裹的state進行實時更新,他的每一次更新,都可以通過單向數據流的方式傳遞給那個發快遞的人(子組件)。那麼比如有這樣一個需求,北京想要得知整個中國的天天快遞物流情況,該怎麼辦?很簡單,通過redux進行統一管理:整個中國每一個人發包裹都要通過唯一的物流中心,這個物流中心暴露一個dispatch方法,用於發快遞,比如你要寄天天快遞去北京,你就要這樣dispatch(tiantian({target:"beijing",msg:"神秘大禮包"})),將快遞丟到物流中心,物流中心的天天快遞員(reducer)便會對其進行處理。那麼作為掌握第一手寶貴信息的connect,會實時得知來了一個快遞,select通過對state進行分析發現。哎嘿,是天天快遞,於是更新了天天快遞的物流信息,通過props的方式傳遞給北京,於是北京就能實時掌握全國的天天快遞物流信息了就醬對了,我不是快遞公司,我是外賣公司逃)
各路大神解釋我都看了一下,很深刻。
我嘗試用更簡單的方式來描述。
如果所有的東西都寫在組件裡面,那麼你寫出的組件大約是這樣的。
class View extends React.Component{
doSth(){this.setState({sth:"biubiu"})}
doSth1(){this.setState({sth1: "..."})}
doSth2(){this.setState({sth2: "..."})}
render(){ return (...) }
}
嗯,現在有3個狀態(state)了,看起來還可以,代碼結構還比較清晰。
如果state有10個了,那麼你可能有點慌了。
如果有20個,那麼review你代碼的人可能有點慌了。
如果用 Redux 大致就是這樣的。
以下是偽代碼
class View extends React.Component{
constructor(){
this.store = ReduxInstance // 怎麼創建redus,請查文檔
this.store.subscribe(next =&> this.setState(next))
}
doSth1(this.store.dispatch({type:"doSth1"}))
doSth2(this.store.dispatch({type:"doSth2"}))
// ....
render(){// use this.state}
}
最直接的,將model相關的邏輯與view強制分離。
view負責拿到數據就渲染,不要在view中寫成千上萬行。
dispatch負責分發任務。
reducer與actions負責處理數據。
如果你看這上面比較眼熟的話,那你應該看過mvc相關資料。
flux發布時,無數人嘲笑facebook真會起名字,mvc改成flux立馬又成了一個概念。
我也覺得是啊....
至於redux其他特點,我覺得無關緊要,或者說對前端影響並不那麼直接。
比如純函數、immutable data、多組件共享一個store、屬性傳遞什麼的。
就mvc來說,backbone做得更早,也更徹底一些。
redux抄襲自the elm architecture
就是把model改名叫store了然後把update改名叫reducer了,foldp嘛,fold翻譯到js就是reduce嘛然後把case of閹割成switch了然後union type沒有,就用action object裝所以他和flux架構有關係嗎?我覺得除了名字改的比較貼近外並沒有什麼關係。先用Reflux入門,我就是redux不大會用,後來就用了reflux
理解redux可以說必須理解一部分的函數編程,因為redux=reducer + flux,其核心是actions reduce為一個單一state過程的flux實現,也就是 (state, action)=&>newState
其最大的好處是,state的演變變得可預測,於是可以有類似time travel 的工具實現,而其他的flux庫就比較難了。但這也使redux在做一些事情的時候顯得很墨跡,沒辦法。所有的內部結構設計, API和相關組件都是為這個核心目標服務的我覺得你只是單純的英文理解能力不夠好而已。
# Redux的核心要點
我們先不要管那些聽上去就雲里霧裡的`store`、`action`、`reducer`,而是捋一捋一個應用的使用,這裡就前一篇文章中新建的Todos為例:
&當我們打開應用的時候,頁面上得有一些東西,比如未完成的事項、新增todo的加號按鈕等等,這個沒問題吧,雖然我們不知道背後的原理,但是有一點是確定的:有一個東西在控制著要顯示的東西,我們稱之為老大哥。如果能聽到計算機在說話的一定是這樣的:呀,老大哥,有人來了,怎麼辦?老大哥說不要方,把未完成的todos顯示到屏幕上,還有加號。當然我們沒有聽到計算機說了什麼,只是看到了屏幕上有一些東西。然後我們想新建一個todo,怎麼辦?當然是先點擊一下加號,這個沒問題吧,雖然我們不知道背後的原理,但是有一點是確定的:我們點擊了一下這個加號的動作被捕捉到了。如果能聽到計算機在說話的一定是這樣的:呀,這個兄弟點擊了一下,點擊的是加號,老大哥怎麼辦?老大哥說這是個送分題啊,交給老二去回答。老二:呀,我知道他點了這個加號,我是不是得做點啥?我猜他點這個加號他一定是想要新建一個todo,怎麼辦呢?怎麼呢?不要慌,先把表單顯示到屏幕上讓他輸入內容吧先,嗯,對,就這麼辦,老大哥我知道怎麼了。當然我們沒有聽到計算機說了什麼,只是在點擊加號按鈕以後就出現了新建todo的表單。[觀眾:你說的這是啥我為啥要關心這?]
雖然例子可能不是完全貼切,但這就是我們的`store`、`action`、`reducer`。應用的狀態都由老大哥`store`控制,狀態的改變需要發起一個動作`action`,描述動作該如何改變狀態就需要老二即處理器`reducer`。這個基本上是大白話了,接下來用JavaScript語言來下定義:`store`: 一個對象,它包含著應用的所有信息。`action`: 一個對象,包含著動作的信息(至少要包含一個`type`屬性,用來描述發生了什麼事)。`reducer`: 一個函數,確定針對特定的動作,狀態要如何改變,這個函數必須有兩個參數,當前的狀態和動作,返回值只有一個就是變化後的狀態。redux基本概念:
1、單一數據源;2、state只讀;3、reducer是純函數。redux的作用:
提供一個工程化的管理前端數據流向的解決方案。redux基於react的應用場景1、action:只是一個對象(Object),通常寫成export const action = (data) =&> {
//僅僅返回一個對象
return {
type: "TEST",
data
}
}
2、reducer:接收action發送的指令,保存數據到指定的state中,通常寫成:
const initData = {
data: []
}
export const reducer = (state = initData, action) =&> {
switch(action.type) {
case: "TEST":
return {
...state,
data: action.data
}
}
}
3、store:負責管理所有reducer裡面的state
4、component:reducer裡面的state,通過props的方式傳遞到component,當通過action更新了state之後,會觸發component重新渲染,這裡會採用react的diff演算法,避免了額外渲染的性能問題,通常寫成//通過connect中間件建立連接
class myComponent extends Component {
render() {
const {data} = this.props.reducer
return (
&
{data}
&
}
}
5、總結一下,redux要開發者做的事情就是遵循這樣一個數據流去管理數據的更新,任何數據的變動都需要發送一個action,然後dispatch到reducer,再重新渲染組件,保證每個狀態都是可預測的,對於大型項目開發和團隊開發比較友好。
來上一篇最近翻譯的文章,其實所謂之 Flux / Redux, 不就是 「命令模式」 嘛~
原文地址:Redux and The Command Pattern (Apr 7, by Abhi Aiyer)
另外可以到我的博客看雙語版哦:【譯】Redux 和 命令模式
======
據我所知,軟體行業有兩件必然確定的事情:
1. 框架永遠都在變化而,我們都曾在那裡,惡性循環:
如果事情總是在變化,那麼作為一名軟體工程師,你的工作就是持續不斷的學習,以及在你決定使用的庫或框架上做出正確的押注。
2. 設計模式是軟體工程的基礎
既然我們已經知道了框架永遠都會變化,那我們就必須堅持優秀軟體的基礎:設計模式。
設計模式就代表著最佳實踐,這些實踐都是由經驗豐富的軟體開發者所使用並總結出來的。設計模式教會你如何思考。學習設計模式的主要好處就是,你在面對問題的時候能夠更加快速地提出解決方案,如果你的同事也有設計模式方面的知識的話,那麼每個人就是在說著同樣一種語言。雖然現在我在提倡設計模式,但他們並不是最終的解決方案。只要你對於特定問題能有更好的解決方案,那麼即使你並沒有使用任何設計模式也是 okay 的。
History Lesson | 歷史的教誨
對於設計模式我們可以追溯到 Christopher Alexander,Pattern Language (中譯名:《建築模式語言》))的作者,Alexander 意識到,隨著時間的推移,某些方法所創造的結構可以達成效率。當時,由於 Alexander 的工作,其他出版物也開始出來了。其中很棒的一本讀物就是 Design Patterns: Elements of Reusable Object-Oriented Software (中譯名:《設計模式:可復用面向對象軟體的基礎》))。這本書描述了給常見的軟體開發問題提供解決方案的各種模式。
對於 JavaScript 開發者來說,這兒有一本來自於 Addy Osmani 的好書。你可以在這裡在線查看。
Command Pattern | 命令模式
在設計真正解耦合的整潔系統架構時,命令模式是一種非常棒的模式。這種模式的緣由就是為了能夠在未來的某個時刻執行某個部分業務邏輯。在這裡我想要特別提及命令模式的原因,是因為我認為這是 Redux 模式的根源。現在就讓我們來走進命令模式,然後將其「翻譯」成 Redux。
首先要理解命令模式當中的一些基本要素:Receiver, Command,以及 Executor。
The Receiver | 接收器接收器 的職責就是保存我們的業務邏輯。每當給到一個命令,它都能知道如何滿足相應的要求。
想像一下我們正在銷售特斯拉的新車型 Model 3。讓我們寫些代碼來描述一下這是如何工作的:
/**
+ Request information about the car
+ @param model - model of car
+ @param id - id of car
**/
function requestInfo(model, id) {
return `${model} with id: ${id}`;
}
/**
+ Buy the car
+ @param model - model of car
+ @param id - id of car
**/
function buyVehicle(model, id) {
return `You purchased ${model} with id: ${id}`;
}
/**
+ Arrange viewing for car
+ @param model - model of car
+ @param id - id of car
**/
function arrangeViewing(model, id) {
return `You have successfully booked a viewing of ${model} (${id})`;
}
在傳統的命令模式下,我們通常會將這些信息包裹在一個對象當中。
const TeslaSalesControl = {
buyVehicle,
requestInfo,
arrangeViewing
}
export default TeslaSalesControl;
The Command | 命令
命令這會包含行為調用時的一些信息,及其所需要的參數,通常就表示為一個對象。
const sampleCommand = {
action: "arrangeViewing",
params: ["Tesla 3", "1337"]
};
如你所見,一個命令就定義著一個行為, 這與我們在控制對象當中的方法是一致的。在上面的例子中,我們的命令就是執行 「arrangeViewing」 行為。與此同時它也給 arrangeViewing 傳入了兩個必需的參數:model 和 carId。
The Executor | 執行器接下來我們需要做的就是一個執行命令的介面。讓我們來給 Sales 控制器加上一個執行函數。對這個函數來說我想要實現一個能夠用於接收的通用執行器,以及接收器。執行器的職責就是傳入命令給接收器,並且調用我們的業務邏輯。
/**
+ A generic execute function
+ Takes a receiver and a command
**/
export default function execute(receiver, command) {
return receiver[command.action] receiver[command.action](...command.params);
}
現在我們無論在何時何地都可以執行這些命令了。
Make things happen | 讓奇蹟發生import execute from "executor.js";
import TeslaSalesControl from "receiver.js";
// Arrange a viewing
execute(TeslaSalesControl, {
action: "arrangeViewing",
param: ["Model S", "123"]
});
// Request Info
execute(TeslaSalesControl, {
action: "requestInfo",
param: ["Model S Battery", "123342"]
});
// Buy a Car!
execute(TeslaSalesControl, {
action: "buyVehicle",
param: ["Tesla 3", "23243425"]
});
就是這樣,現在讓我們來對比一下 Redux! 在 Redux 中:
The Store = The Receiver | Store 即接收器
Store 會根據 「reducers」 進行初始化,描述 Store 是如何變化的。這些 reducers 都是一些純函數,每當被調用的時候都會返回一個新的 state,而不會導致莫名其妙地發生變化。這使得我們的代碼具有高度的可預測性以及可測試性。
import { combineReducers } = "redux";
function arrangeViewing(state, action) {
switch(action.type) {
case "ARRANGE_VIEWING":
const { model, id } = action.data;
return `${model} and ${id}`
default:
return ""
}
}
function requestInfo(state, action) {
switch(action.type) {
case "REQUEST_INFO":
const { model, id } = action.data;
return `${model} and ${id}`
default:
return ""
}
}
function buyVehicle(state, action) {
switch(action.type) {
case "BUY_VEHICLE":
const { model, id } = action.data;
return `${model} and ${id}`
default:
return false
}
}
const rootReducer = combineReducers({
arrangeViewing,
requestInfo,
buyVehicle
});
export default rootReducer;
import { applyMiddleware, createStore } from "redux";
import createLogger from "redux-logger";
import ReduxThunk from "redux-thunk";
import rootReducer from "../imports/client/reducers/rootReducer";
// create a logger
const logger = createLogger();
const middleware = [ReduxThunk, logger];
const Store = createStore(rootReducer, {}, applyMiddleware(...middleware));
export default Store;
The Action = The Command | Action 即命令
Action 對象則代表著對命令的描述,以及它在執行 state 更改時所需要的參數。
const sampleActionObject = {
type: "BUY_CAR",
data: {
model: "TESLA",
id: "1234"
}
}
Dispatch = Executor | Dispatch 即執行器
普通的 Flux 和 Redux 之間的區別,就在於 dispatch 屬於 store 當中的一個方法。Store 可以直接分派 action 從而改變我們應用程序的 state。
import Store from "store";
Store.dispatch({
type: "ARRANGE_VIEWING",
data: {
model: "Model S",
id: "123"
}
});
Store.dispatch({
type: "REQUEST_INFO",
data: {
model: "Model S Battery",
id: "123342"
}
});
Store.dispatch({
type: "BUY_VEHICLE",
data: {
model: "TESLA 3",
id: "23243425"
}
});
如你所見,非常相似是不是!?弄懂了命令模式可以讓 Redux 的學習變得容易很多!相信我!
總之,設計模式會幫助你掌握應用程序架構的本質!
==========如果這篇譯文讓你懂了 Redux 的話,麻煩點個贊吧,(*^__^*) 嘻嘻……Redux 是一個用來管理數據的類庫, action/ store/reducer 各有其定義我這裡就不多說了,如果不願意看英文文檔可以看中文的。
https://github.com/camsong/redux-in-chinese
它之所以搞那麼複雜,目的是為了
1. 避免業務組件直接修改數據2. 避免業務組件自己管理數據3. 無副作用的純函數可以追溯好處是不少,但有點複雜和麻煩。我覺得redux的思想很簡單,其實說白了就是一個reduce函數,state不斷通過reducer處理生成新的state
推薦閱讀:
※一個 ul 里有若干 li,點擊 li 時能方便地知道這是 ul 中的第幾個 li 嗎?
※相比Angular,Avalon有什麼缺點呢?
※瀏覽器允許的並發請求資源數是什麼意思?
※有哪些利於前端新手練習、理解JS的獨立小項目?
※如何知道瀏覽器當前執行的JS代碼的位置?
TAG:Web開發 | 前端開發 | JavaScript | 編程 | 前端入門 |