如何評價數據流管理架構 Redux?

Redux 好在哪?用單一的State的狀態樹能構建大型的應用嗎?

目前(rackt/redux · GitHub) star 超1W,超過了flux、reflux 等同類數據流管理架構


1月17日更新,來填坑了……

首先感謝大家的謬讚,正如 @Reducer (這名字,真不是專門為了 Redux 取的么……)所說,我的答案都是在吐槽 Redux 生態圈云云,並沒有講到 Redux 本身。

&> 本來寫了很長的答案,但是寫著寫著感覺好像變成了 Redux 入門指南……所以全數刪除,只寫自己對 Redux 的評價與感受。

Redux 對我來說,最耀眼的不是什麼自動熱部署,也不是酷炫的 redux-devtools,而是它最基礎的定位 —— 一個可預測的狀態容器(predictable state container)。

為什麼?

在沒有 Flux/Redux 之前,我們的 React 應用架構的 Model 層使用的是 Backbond.Model。對於有 Backbone 基礎的人來說,整合 React 與 Backbone 的 Model 簡直易如反掌。

一開始的時候我們也用的很爽,React 組件 componentDidMount 的時候初始化 Model,並監聽 Model 的 change 事件,當 Model 發生改變時調用 React 組件的 setState 方法重新 render 整個組件,最後在組件 componentWillUnmount 的時候取消監聽並銷毀 Model。

然而當應用變得複雜的時候,一切都亂套了。有一個 userName 數據,這貨到底是父組件當做 props 傳下來的呢,還是自己 Model 中保存的呢,還是自己 state 中用戶輸入的呢?完全懵逼。

更可怕的是,產品經理改了需求,需要在一個子組件中展示一些原本在這個組件中展示的信息。很多人(包括我)為了圖方便就把自己組件的 Model 直接當做 props 傳給了子組件……

不敢想像,這樣發展下去,Model 中一個欄位發生變化觸發 change 事件的時候,到底有多少 React 組件會發生 re-render。

這就是為什麼,可預測,Predictable,在大型前端應用中變得那麼重要!

而 Redux 是怎麼做到可預測的呢?三點:

  1. 單一數據源,Single Source of Truth(也即題干中提到的 「單一的 State 狀態樹」)
  2. 所有數據都是只讀的,要想修改數據,必須 dispatch 一個 action 來描述什麼發生了改變
  3. 當處理 action 時,必須生成一個新的 state,不得直接修改原始對象

首先,單一數據源保證了整個應用中的數據都保存在同一個 Store 中,而這些數據在用在界面渲染時,都是作為 React 組件的 props。因此你在 render() 方法里想要渲染一個欄位的時候,不需要再糾結它到底是 state 還是 props 還是在 Model 中,因為它肯定是 props。(當然由於 Redux 推薦的通過 combineReducers 方式 compose reducer,實際在一個組件中確定數據是來自哪個子 reducer 還是要費點神,這個目前主要依賴良好的組件結構和標準的命名規範來解決)

而對我來說,感受最直觀的是第二點,因為在 Flux 之前,你已經習慣了修改一行數據自動重繪界面(如調用 this.setState)的簡單與方便(估計使用 MVVM 的同學體會更深)。

突然間告訴你說,你不能直接修改數據!要想關閉這個彈框?你必須 dispatch 一個類型為 CLOSE_DIALOG 的 action,然後你的 reducer 必須在識別到這個 action 後將 store 里 dialogActive 的欄位置為 false,然後你的組件必須判斷當 props.dialogActive 為 false 時讓彈框隱藏掉。

原本可能一行代碼

this.refs.dialog.hide();

會膨脹成 Component、Reducer 和 ActionCreator 三大角色幾十行代碼。(具體其中的原理和操作過程這裡就不細展開了)

但是,但是,這種痛苦在前期打基礎的時候絕對是值得的。因為當你的應用擴展之後,你可能需要的不僅是「關閉一個對話框」,而是「當點擊確定按鈕時提交數據到伺服器端,若成功則關閉對話框並刷新列表中的數據,若失敗則彈框提示用戶出錯信息」。

這個時候,Redux 強大的可預測性就體現的淋漓盡致了。因為 Redux,你清楚的知道什麼發生了改變(action),改變之後的數據是什麼樣的(store/state),以及發生了哪些改變(redux-devtool 中的 action 記錄)。

最後,因為 Redux 的第三點特性(每次返回新的 state,而不是修改原始數據),配合強大的 devtool,你可以看到下面的場景:

上圖就是我說的「隨著應用的擴展」後一個對話框操作的全過程:

打開一個對話框 =&> 輸入一些表單信息 =&> 提交 =&> 提交成功 =&> 關閉對話框 =&> 重新載入列表 =&> 列表載入成功

簡直不能再清晰明了!甚至你不看我的代碼,都知道我的業務邏輯是怎麼執行的,再也不會出現一個 Model 更新之後不知道哪些 View 會隨之更新的情況了。

以上就是我覺得 Redux 最贊的一點:可預測性

當然,實際在開發 Redux 應用的過程中是非常痛苦的,尤其是在前期,沒有成熟的開發模式,生態圈不夠完善,缺乏大型應用的成熟案例等等,讓我們開發效率異常低下。

但是當我們慢慢摸著石頭過到河中間,所以痛點都找到化解的方式之後,好像突然架起了一座橋,開發效率chua的一下就上去了。

其實我認為 Redux 最能提升的不是開發效率,而是維護效率。若干年以後,你看到上圖 redux-devtools 中列印出來的信息,還是能清楚的知道這個業務過程是怎樣的。

大概就是這樣吧,大家還想知道什麼也可以在評論中提出。

以下是原答案:

==============================================================

現在是12月20日凌晨5點43分,大約 3 個月之前我負責牽頭搭建了團隊里第一個基於 Redux 的項目。

是的,這是一個內部項目,所以我們上了很多新技術來試水,而 Redux 則是大家最關心的能否在項目中實際應用併產生價值的技術(這裡我用了「技術」來稱呼 Redux,鑒於 Flux 這種既不是庫又不是框架的玩意兒,我已經不能愉快的給 Redux 定性了)。

當然項目最後成功上線,你知道對於大公司的工程師來說,項目延期是很致命的。

而關於項目本身沒什麼好說的,並不是簡單的像博客只是渲染內容,也沒有複雜到像 Gmail 一樣在單頁應用里承載了無數的功能。

那 Redux 呢?

這麼說吧,大概 4 個小時之前,因為要新起另一個基於 Redux 的項目,我心想著升級一下依賴,就敲了一行

tnpm update --save

。。。

。。。。

。。。。

。。

結果項目到現在還沒跑起來,這才過了三個月啊!!

所以我就想到了這個早就有人邀請我的問題……

大概看了一眼,版本號變化如下

redux 3.0.0 -&> 3.0.5 // 看起來最正常的一個
react-redux 2.1.2 -&> 4.0.1 // 什麼鬼??!!
redux-devtools 2.1.2 -&> 3.0.0 // 也沒好到哪兒去
react-router 1.0.0-rc1 -&> 1.0.2 // 1.0 正式版終於出了
redux-router 1.0.0-beta3 -&> 0.1.0 // 尼瑪還帶版本往回退的??

不僅如此,一些 devDependencies 更是變得令人髮指,好不容易研究明白了 react-hot-loader,結果作者又發明了一套 react-transform;好不容易把 redux-devtools 用熟了,結果 API 變得完全不同……

總的來說,我的內心是奔潰的。

天快亮了,先寫到這兒吧。如果大家看到這裡還沒有被 Redux 嚇到還有興趣的話,後面再接著更新。

0314 更新:剛剛接觸 Redux 的同學,可以看看這篇文章 GitHub - jasonslyvia/a-cartoon-intro-to-redux-cn: 看漫畫,學 Redux。不寫一行代碼,輕鬆看懂 Redux 原理!


一套很優秀的框架。

主要優點:

1. 大幅降低了 Facebook 官方的 Flux 實現的冗餘和不必要的複雜度,整體結構更為清晰和容易理解。

2. 在 react-redux的配合下,完全分離了對數據的修改操作( Action / Reducer ) 和對數據的更新( Selector ),使得開發時可以在不考慮數據修改的情況下,優先完成整體視圖邏輯,然後在添加對數據的修改操作等業務邏輯時幾乎不用修改視圖邏輯代碼

3. 單一數據源( Store )的模式使得數據管理、持久層方案選擇和可調試性( Redux-Dev-Tools )都非常方便

主要缺點:

1. 對從 OOP 開發轉過來的程序猿來說,函數式編程的概念接受起來需要一點門檻。

2. JavaScript 對不變對象的支持並不是特別的友好,無論是引入 immutable.js 還是 ES6 的解構語法糖有時候都覺得 Reducer 里的代碼讀起來有些費力,特別是對剛接觸 ES6的同學來說。

3. 所有的 rackt · GitHub 旗下的框架,比如 rackt/react-router · GitHub 和 rackt/redux · GitHub ,以及 React 本身,都流露出一種 「 老子就是要做最牛逼的東西,向下兼容這種事情根本就不是老子考慮的問題 」 的態度。而且很多時候不是簡單的不向下兼容,而是給人一種回爐重做的感覺。針對項目開發,一定要慎重選擇版本。關於這一點 @楊森 可能會有話要說,他的博客里對 react-router 的教程已經更新了 N 版,仍然多次趕不上官方的 API 變化速度。

綜合結論:

Redux 非常優秀,但是目前來看,比較適合喜歡折騰、自學能力強、熟練閱讀 GitHub 上的官方英文文檔並於官方在 issue 里談笑風生的開發者去學習。當然,也許再過幾個月,API 真的穩定了,然後諸位大神的中文文檔也普及了,就能在國內有更大的發展了吧


或許適合大型前端項目,像我這種懶人,自己開發個項目還是看mobx比較順眼些,起碼能少敲好多代碼。

GitHub - mobxjs/mobx: Simple, scalable state management.


@楊森 吐槽了一下react生態圈發展太快,工具迭代不向下兼容的問題。完全跑題好不。redux本身不只是能寫react。還能寫vue,ng,backbone等等庫。我就自己寫了個redux-vue。全面優化了項目亂78糟的數據流,配合redux-act相當相當愉快的讓整個代碼結構更清晰,模塊更專註自己的業務數據。即使把框架切換到react、ng也是完全兼容的。現在生態發展好快,跟風最好考慮一下自己的項目進度。即使不使用生態鏈中的工具一樣可以使用傳統的測試工具,如mocha等等。redux要說缺點嘛,個人覺得主要在於思維的轉變,與框架的適配需要自己去寫。應用場景上有一定局限性,操作多個數據流,包括非同步流時較繁瑣。哈哈當然這些不是什麼大問題,自己寫過小函數批處理一下也是可以的。另外說一下,把action都自動dispatch了,會有不錯的體驗哦


因為題主問的是如何評價,所以這裡就不細講redux的思想了,只講講我的評價。

  1. Redux是一個核心簡單,擁有很精妙思想的框架。
  2. 非常建議任何有專業精神的前端程序都至少嘗試學習一下redux
  3. 只用redux去解決那些適合用它解決的業務。不要什麼事情都往redux里塞。redux-router之類的東西,簡直是毒瘤。
  4. 其優勢在大型項目更易體現。在中小型項目,redux帶來的好處並不比它帶來的麻煩多多少。組織業務代碼還有很多很好的方式。
  5. 如果要在實際項目中使用,學習成本不僅限於redux核心概念,更重要的是一整套生態圈和中間件。不要覺得文檔看完了就能玩好了。
  6. 我個人認為,在組件化和npm的時代,「全局」是罪惡的,包括全局狀態
  7. 處理列表是個渣。immutablejs也救不了它。
  8. 我已棄坑。


事件傳值,的原始理論很成功的封裝成1萬star 的框架, 的確牛. 我是說短時間變成1萬3的star的確牛,至於框架redux...

非同步action 怎麼還沒解決, 這也叫框架, action 其實這個名字就有問題 不如叫事件event,事件的合集才是action .

寫個項目樣板代碼比有用代碼還多, 是的人人都要構建超大大規模項目,

改個代碼,增加功能絞盡腦汁想reduce在拆分, 公司倒閉了還沒想出來如何規劃超大大規模項目.

架構師最愛,招人有理由了, 做頁面的, 做css, 寫jsx, 寫action 寫store的, 寫reducer 的全部獨立, 我終於不用幹活了


實現同類的功能,幾行就足夠了:

class Store {
constructor(initState) {
this.state = initState;
}
dispatch(action, ...args) {
this.state = action(this.state, ...args) || this.state;
}
}

用例:

let store = new Store({});

function action_foo(state, a1, a2) {
return {...state,
foo: a1 + a2,
};
}

store.dispatch(action_foo, 2, 3);

console.log(store.state);

redux里的action type、 reducer等概念都簡化成一個action函數,結果就是沒什麼boilerplate了,但又保留了redux的優點。

而且絕大部分的action,都是和某個組件高度耦合的,於是action代碼可以和組件代碼放一起,其他組件需要時再export / import。不需要時就直接刪掉dispatch調用,不需要寫switch case。

因為action就是個函數,所以非同步、中間件等都不難實現,就寫一些高階函數即可。

有人說redux浮誇,我有同感……

=== update ===

舉個「當點擊確定按鈕時提交數據到伺服器端,若成功則關閉對話框並刷新列表中的數據,若失敗則彈框提示用戶出錯信息」。」的例子

let store = new Store({});

let ev_show_dialog = (state, msg) =&> {
return {
dialog_show: true,
dialog_message: msg,
};
};

let ev_update_list = (state, data) =&> {
return {
list_data: data,
};
};

let ev_close_dialog = (state) =&> {
return {
dialog_show: false,
};
};

let ev_toggle_dialog = (state) =&> {
return {
dialog_show: $filter(show =&> !show), // $filter包裝了一個closure,在merge的時候,state.dialog_show會傳入該closure
};
};

let ev_submit = (state, ok_cb, fail_cb) =&> {
send_some_data_to_server((resp) =&> {
if (resp.ok) {
ok_cb(resp);
} else {
fail_cb(resp);
}
});
};

let ev_click_button = (state) =&> {
store.emit(ev_close_dialog); // event允許組合,相當於在redux的reducer里直接dispatch另一個action,所有對state的修改都會生效
store.emit(ev_submit, (resp) =&> {
store.emit(ev_update_list, resp.data);
}, (resp) =&> {
store.emit(ev_show_dialog, resp.error_message);
});
return {
button_clicks: $append(new Date()), // 記錄點擊時間
};
};

// bind ev_click_button to component

=== update ===

不過這個實現和redux一樣,在dispatch時是不可以dispatch另一個action的。因為傳遞給action的state,在action中dispatch另一個action後,已經不是最新的了。基於這箇舊的state返回state的話,另一個action的修改是沒有包括的。

解決方法有兩種。第一種是不兼容的,就是不傳遞this.state給action,而是傳遞() =&> this.state,這樣action就可以獲得最新的state,從而將其他action的改動納入;

還有一種是兼容的,就是不替換舊的state,而是合併,就是將

this.state = action(this.state, ...args) || this.state;

改成

this.state = merge(this.state, action(this.state, ...args));

不過merge函數的實現就不是幾行的事了。


最近剛好在寫 jcouyang/react-most (已更名 xreact) 路過怒答

為什麼不? 我在 README#why-not(英文) 中也有說

簡單的說

不好

其實Andr?? Staltz已經說的極是,另外我補充一點點

1. switch case 很難看

2. switch case 很難看

3. switch case 很難看

4. switch case 其實包含了多個 reducers,這些 reducers 不可組合

5. switch case 你以為你是 pattern matching 嗎

6. 那些概念是什麼鬼,真的需要一本 Gitbook

嗎?源代碼都比這本書薄,其實redux乾的事情一句話就能搞定

動作(action) 變換成 state 轉換函數(reducer),然後放到一個統一的地方(store)來 setState

7. 說實在的,並沒有看到數據流怎麼清晰,一堆事件handler而已
8. async action 這麼難解決嗎
好的地方(還都是抄的)
1. 全局 state(從 om 學的)
2. HOC(Higher Order Component, 從 react 作者那學的https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)
3. reducer 其實就是 elm 的 update

所以總之,只抄到了elm的面子上的東西,沒有ADT和Pattern matching還是差得好遠。

相比之下,強烈建議試試 Cycle.js ,確實是真正的響應式函數式(FRP)數據流的正確姿勢,代碼 clean 而且只需要聲明式,定義數據流就好。當然也歡迎試試 react-most,大概是照搬 cycle 的姿勢到 react

如果不習慣FRP,GitHub - paldepind/functional-frontend-architecture: A functional frontend framework. 有著更接近於elm的做法(elm終於擺脫FRP了)


"JavaScript的社區以幼稚和愚昧著稱。你經常發現一些非常基本的常識,被JavaScript"專家"們當成了不起的發現似的,在大會上宣講。"——王垠


Redux 核心概念


Redux 傳遞數據的方式其實跟 React 用 props 傳遞數據的方式是不同的,react 的缺點就是要傳無數個 props 下去,父級的組件要關心很多子級組件的需求,子孫一多讓人煩躁。Redux 其實是一個狀態樹,你想改哪個,就用 connect attach 上去改,媽媽再也不用擔心傳數據給我了。redux 主要還是各個組件通訊用,別人不 care 的狀態維護在自己的 state 裡面就可以了。


認真看了下尤大師和唐大師之爭,好有趣。。

不是要前端簡單化嗎?看看我開源的 hyy1115/react-redux-webpack

從組件初始化渲染到發送一個action,更新state,都做了優化,讓每個步驟簡單明了,非同步用async/await 就能解決了。比較適合redux新手或者需要敏捷開發的項目。


no redux, mobx(mobserver) please


Redux 實際上就是把後端 MVC 搬到前端

圖來自 Redux 莞式教程:GitHub - kenberkeley/redux-simple-tutorial: Redux 莞式教程。本教程深入淺出,配套入門、進階源碼解讀以及文檔注釋豐滿的 Demo 等一條龍服務


說實話,boilerplate code 並不少,但是後期非常強大,面對project manager 各種feature 狂轟濫炸,redux + react 依舊可以讓你的代碼結構清晰明了,這一點是其它許多框架做不到的。看什麼看,說你呢Angular


http://www.w3ctech.com/topic/1561 推薦閱讀深入淺出redux……


轉個微博上看到的圖,無限繁榮的生態圈,話說這吐槽我給滿分


一圖勝千言:

有些工具嘛,一兩個人玩玩用不用無所謂,但是很多人參與的大項目里就需要強制使用,免得大家瞎搞搞得太亂:畢竟我們都知道這個副作用是很危險的,要好好封裝起來保證安全啊!

你問我用不用?我當然想用啊,但是最近缺合適的項目……有推薦的嗎?


其實Redux就是只關注數據處理邏輯,嚴格地將數據獨立出來,和渲染、用戶輸入等其他邏輯分離。沒覺得是什麼特別高深的事情。但老外總是能將一個簡單的思想發展成生態圈,令人佩服。


&<以我了解的一點點React+Redux&>

Redux和Flux的單向數據流模式是一樣的,不同的是Redux的核心思想是全局可預測狀態容器,也就是一個Store(Flux中可以有很多Store)。

Redux專註於構造數據管理高度一致性的前端大型應用,一致性帶來的方便是大型項目的多人協作、維護管理相對容易。缺點是必須遵守Redux既定的數據管理模式,增加了相當的代碼量而且相當不靈活。

很多時候根本就不需要用Redux,比如下面這些場景

1.每個View的數據來源都比較簡單,那比如React官方推的Flux就已經很好了,Redux的全局狀態容器反而有點冗餘;

2.沒有管理維護的負擔,也就是一次性的項目;

3.Redux還是有些學習成本的,因為是全局一致的規範,肯定要盡量保證團隊都能了解Redux的設計模式。比如Redux的非同步操作需要額外學習Redux中間件,中間件這種高階函數理解起來可能會有點麻煩,官方提供的內置中間件又不一定滿足所有需求,比如很多時候會設計一個Action有 PENDING、SUCCESS和FAILURE三種情況,View層根據三種情況的數據情況做出不同的響應。

但是如果是需要長期維護開發、數據管理又比較複雜,那會發現Redux提供的數據管理和各部分之間的松耦合的方式很好用的。


推薦閱讀:

Vue和React的使用場景和深度有何不同?
Redux有哪些最佳實踐?
Weex 和 React Native 的根本區別在哪裡?
前端自學,目前可以用react寫一些項目,但是不知道目前現在在前端上的水平,希望可以獲得指點?
初級前端er如何學習react.js?

TAG:JavaScript | React | Redux |