05-Redux

前言

你并不一定需要 Redux 。 如果你的應用場景還不夠復雜,引入 Redux 只會徒增成本。

Flux 設計模式

學習 Redux 之前需要了解 Flux 設計模式。 Flux 模式是 Facebook 提出的一種架構模式,其中最重要的思想是單向數據流。

Flux
  • Action: 規范化所有的動作。數據的修改、UI 的變化都由 Action 觸發.
  • Dispatcher: Action 的分發器,將 Action 傳遞給 Store
  • Store:存放數據、狀態和邏輯處理。
  • View: 根據 Store 的變化進行渲染

Redux

本篇篇幅有限,主要介紹 Redux 中的一些核心概念幫助快速理解上手。網上有很多資料都詳細介紹了 Redux 的各個部分。React 小書 這份文檔詳細介紹了 React 和 Redux 的原理

Store & Action & Reducer & Dispatcher

Redux 采用單一數據源的模式,整個工程全局只有一個 Store。Store 內存儲全局數據和狀態,我們用 State 來表示。State 對外是只讀的。State 的修改只能通過接收不同的 Action 來觸發。我們寫一些純函數來處理不同的 Action,修改完值后返回一個新的 State,這種純函數叫 Reducer。

public reducer(state, action) {
    switch (action.type) {
        case type1: 
            return state1;
        case type2:
            return state2;
        default: 
            return state;
    }
}

Store 會有一個 dispatch 方法,用于分發 action 到對應的 reducer 上。

store.dispatch(action);

以上即是 Redux 最核心的一些概念,分別和 Flux 的設計圖的概念一一對應。但是僅有這些東西,在工程實踐上仍然不是很方便,下面我們再看下怎么把這些東西分別應用到業務中的各個場景下。

集成

yarn add redux react-redux

創建 Store

全局只有一個 Store,所以需要在進入應用之前創建 Store 對象,并作用到全局。

import { createStore  } from 'redux';

const store = createStore(reducer);

react-redux 庫提供了一個 Provider 組件,專門負責將 Store 作用于全局。

import { Provider } from 'react-redux';

function Application() {
    return (
        <Provider store={store}>
            <AppContaienr />
        </Provider>
    );
}

創建 Reducer

創建 store 對象時需要關聯上對應的 reducer. 前面說過,reducer 就是用于修改 state 的純函數.

export default (state = initialState, action: Action): State => {
    switch (action.type) {
        case ACTION_TYPE_INIT:
            return initialState;
        case ACTION_TYPE_ADD_TASK:
            return {
                ...state,
                tasks: addTask(state.tasks, action)
            };
        case ACTION_TYPE_EDIT_TASK:
            return {
                ...state,
                tasks: changeTask(state.tasks, action)
            }
    }
    return state;
}

業務邏輯比較龐大的時候,我們不可能把所有 action 判斷都放在一個 reducer 內,可以根據類型拆分成很多個 reducer。redux 提供了一個 combineReducers 的工具來做 reducer 的合并。

import {createStore, combineReducers} from 'redux';
import * as reducers from '../reducers';

const reducer = combineReducers(reducers);
const store = createStore(reducer);

Action

Action 其實只是一個普通對象,不過按照約定俗成的習慣,每個 action 都會有一個 type 屬性。

export interface Action {
    type: string;
    [prop: string]: any;
}

根據不同的場景創建不同的 Action 對象扔給 Store 做分發即可觸發 State 的需改。State 的變化最終又會反饋到 UI 的變化上。但是每次都手動 new 一個 Action 對象很麻煩,而且容易造成代碼耦合。我們可以設置一些函數專門用來創建不同的 action。

export function addTask(name: string, startTime: number, endTime: number, desc?: string, ): Action {
    return {
        type: ACTION_TYPE_ADD_TASK,
        name,
        startTime,
        endTime,
        desc,
    } as Action;
}

這類函數叫 ActionsCreators.

connect 函數

在前文中提到過 Smart 組件和 Dumb 組件。在開發中我們盡量將組件 Dumb 化,Smart 組件用于組合 Dumb 組件和實現業務邏輯。但是現在我們已經將業務邏輯遷移到了 reducer 中了,也就沒有了 Smart 組件存在的必要了。

剩下需要做的就是怎么將 Store 中的 State 與 Dumb 組件中的 props 關聯起來。這里需要用到 react-redux 中的 connect 輔助函數。

connect([mapStateToProps], [mapDispatchToProps])

connect 函數接受兩個可選參數。mapStateToProps 和 mapDispatchToProps.

mapStateToProps

Component 的渲染需要由父組件傳遞 props, props 的真實值存儲在 State 中,所以這里實現一個 mapStateToProps 函數,設置好當前 Component 所需要的 props 分別對應 state 中的哪些屬性。

(state: State) => ({ tasks: state.tasks })

mapDispatchToProps

當 Component 觸發了一些操作需要修改 State 時,需要反向發起 Action。 但是對于 Dumb 組件來說,它只需要關心把對應的數據傳遞出去即可。所以 Component 接受的應該是一個 function,觸發操作只需要調用 function。 而 function 是由外部實現,封裝一個 action,并調用 dispatch,這就是 mapDispatchToProps 函數。

dispatch => ({
    addTask: (name: string, startTime: number, endTime: number) => dispatch(addTaskAction(name, startTime, endTime))
})

再看下完整一點的代碼:

interface Prop {
    tasks: Array<Task>,
    navigation: NavigationScreenProp<NavigationState>,
    addTask: (name: string, startTime: number, endTime: number) => void;
}

class TodosScreen extends React.Component<Prop> {
    ...
}

export default connect((state: State) => ({
    tasks: state.tasks
}), dispatch => ({
    addTask: (name: string, startTime: number, endTime: number) => dispatch(addTaskAction(name, startTime, endTime))
}))
    (TodosScreen);

Redux 中間件

Redux 的中間件提供的是位于 action 被發起之后,到達 reducer 之前的擴展點。我們可以通過集成第三方或自己實現的中間件來擴展功能。

const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore);  //創建中間件
const store = createStoreWithMiddleware(reducer);   

可以根據需求加入不同類型的中間件。

參考

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容