Redux入門

Redux是什麼?

一個狀態(State)管理器,集中管理應用中所有組件的狀態。
所有組件的狀態保存在一個對象裏。
雖然Redux與React沒有任何直接關係,但是Redux的主要用途(幾乎是唯一用途)是爲React服務,用於組件的狀態管理,簡化複雜應用下組件狀態的調用關係。


Redux主要用途?

1)提供一個狀態集中管理的容器。
2)簡化組件依賴關係,使可以共享或改變所有組件的狀態。解決Redux更改子組件狀態層層調用的繁瑣。
3)狀態的改變,會引起視圖的改變,所以如果想改變視圖(View),不管涉及誰的改變,統一找Redux就行了,因爲狀態都在他那裏集中管理。
4)Redux 規定, 一個 State 對應一個 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什麼樣,反之亦然。


如何使用?

1)創建唯一的一個store:

    import { createStore } from 'redux';

    //reducer在後面解釋,createStore需要傳入reducer或reducer集合。
    const reducer = function(state, action) {
            return state;
    }

    const store = createStore(reducer);

2)獲取state

    const state = store.getState();

3)改變state,需要發送action通知。
儘管Redux可以通過setState改變組件狀態,但是在redux中只能用dispatch來發送action改變狀態。
action 就是 任何人 發出的通知,表示 State 應該要發生變化了。
發送通知的方法是store.dispatch(action)。如下:

    import { createStore } from 'redux';
    const store = createStore(fn);
    store.dispatch({
        type: 'ADD_TODO',
        payload: 'Learn Redux'
    });

Actiond的結構:

    const action = {
        type: 'ADD_TODO',
        payload: 'Learn Redux'
    };

type是必須的,用於識別一個action,payload是可選的,攜帶任何要傳遞的數據。
可以寫一些輔助函數來創建action,避免dispatch看着太複雜。

4)Reducer
Store 收到 Action 以後,必須給出一個新的 State,這樣 View 纔會發生變化。這種 State 的計算過程就叫做 Reducer。
Reducer 是一個函數,它接受 Action 和當前 State 作爲參數,返回一個新的 State。
這個函數,是在store創建是傳入的,就是上面創建描述的:const store = createStore(fn),也就是:

        const store = createStore(reducer)

以後每當store.dispatch發送過來一個新的 Action,就會自動調用 Reducer,得到新的 State。

Reducer 函數最重要的特徵是,它是一個純函數。
純函數是函數式編程的概念,必須遵守以下一些約束。

        不得改寫參數
        不能調用系統 I/O 的API
        不能調用Date.now()或者Math.random()等不純的方法,因爲每次會得到不一樣的結果

由於 Reducer 是純函數,就可以保證同樣的State,必定得到同樣的 View。但也正因爲這一點,Reducer 函數裏面不能改變 State,必須返回一個全新的對象

5)store.subscribe()

Store 允許使用store.subscribe方法設置監聽函數,一旦 State 發生變化,就自動執行這個函數。

    import { createStore } from 'redux';
    const store = createStore(reducer);
    store.subscribe(listener);

顯然,只要把 View 的更新函數(對於 React 項目,就是組件的render方法或setState方法)放入listen,就會實現 View 的自動渲染(這個要看React-Redux)。

store.subscribe方法返回一個函數,調用這個函數就可以解除監聽。

        let unsubscribe = store.subscribe(() =>
            console.log(store.getState())
        );

        unsubscribe();

Store 的實現

以上介紹了 Redux 涉及的基本概念,可以發現 Store 提供了三個方法。

    store.getState()
    store.dispatch()
    store.subscribe()

    import { createStore } from 'redux';
    let { subscribe, dispatch, getState } = createStore(reducer);

createStore方法還可以接受第二個參數,表示 State 的最初狀態。這通常是服務器給出的。

    let store = createStore(todoApp, window.STATE_FROM_SERVER)

上面代碼中,window.STATE_FROM_SERVER就是整個應用的狀態初始值。注意,如果提供了這個參數,它會覆蓋 Reducer 函數的默認初始值。

下面是createStore方法的一個簡單實現,可以瞭解一下 Store 是怎麼生成的。(以下代碼僅僅用於理解原理,屬於進階教程)

    const createStore = (reducer) => {
        let state;
        let listeners = [];

        const getState = () => state;

        const dispatch = (action) => {
            state = reducer(state, action);
            listeners.forEach(listener => listener());
        };

        const subscribe = (listener) => {
            listeners.push(listener);
            return () => {
                listeners = listeners.filter(l => l !== listener);
            }
        };

        dispatch({});

        return { getState, dispatch, subscribe };
    };          

Reducer 的拆分

Reducer 函數負責生成 State。由於整個應用只有一個 State 對象,包含所有數據,對於大型應用來說,這個 State 必然十分龐大,導致 Reducer 函數也十分龐大。

請看下面的例子。

            const chatReducer = (state = defaultState, action = {}) => {
                const { type, payload } = action;  //解包
                switch (type) {
                    case ADD_CHAT:
                        return Object.assign({}, state, {
                            chatLog: state.chatLog.concat(payload)
                        });
                    case CHANGE_STATUS:
                        return Object.assign({}, state, {
                            statusMessage: payload
                        });
                    case CHANGE_USERNAME:
                        return Object.assign({}, state, {
                            userName: payload
                        });
                    default: return state;
                }
            };

上面代碼中,三種 Action 分別改變 State 的三個屬性。

ADD_CHAT:chatLog屬性
CHANGE_STATUS:statusMessage屬性
CHANGE_USERNAME:userName屬性
這三個屬性之間沒有聯繫,這提示我們可以把 Reducer 函數拆分。不同的函數負責處理不同屬性,最終把它們合併成一個大的 Reducer 即可。

    const chatReducer = (state = defaultState, action = {}) => {
        return {
            chatLog: chatLog(state.chatLog, action),
            statusMessage: statusMessage(state.statusMessage, action),
            userName: userName(state.userName, action)
        }
    };

上面代碼中,Reducer 函數被拆成了三個小函數,每一個負責接收對應的state、處理對應的action,返回對應的state

這樣一拆,Reducer 就易讀易寫多了。而且,這種拆分與 React 應用的結構相吻合:一個 React 根組件由很多子組件構成。這就是說,子組件與子 Reducer 完全可以對應。

Redux 提供了一個combineReducers方法,用於 Reducer 的拆分。你只要定義各個子 Reducer 函數,然後用這個方法,將它們合成一個大的 Reducer。

        import { combineReducers } from 'redux';

        const chatReducer = combineReducers({
            chatLog,
            statusMessage,
            userName
        })

        export default todoApp;

上面的代碼通過combineReducers方法將三個子 Reducer 合併成一個大的函數。

這種寫法有一個前提,就是 State 的屬性名必須與子 Reducer 同名。如果不同名,就要採用下面的寫法。

        const reducer = combineReducers({
            a: doSomethingWithA,
            b: processB,
            c: c
        })

        // 等同於
        function reducer(state = {}, action) {
            return {
                a: doSomethingWithA(state.a, action),
                b: processB(state.b, action),
                c: c(state.c, action)
            }
        }

總之,combineReducers()做的就是產生一個整體的 Reducer 函數。該函數根據 State 的 key 去執行相應的子 Reducer,並將返回結果合併成一個大的 State 對象。

下面是combineReducer的簡單實現(實現,告訴你原理,所以然)。

        const combineReducers = reducers => {
            return (state = {}, action) => {
                return Object.keys(reducers).reduce(
                    (nextState, key) => {
                        nextState[key] = reducers[key](state[key], action);
                        return nextState;
                    },
                    {} 
                );
            };
        };

你可以把所有子 Reducer 放在一個文件裏面,然後統一引入(這句話是乾貨)。

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

        const reducer = combineReducers(reducers)

究竟每個reducer如何處理對應的狀態,還要參考如下文字纔會更清楚:
https://blog.51cto.com/livestreaming/2313439

工作流程

本節對 Redux 的工作流程,做一個梳理。

Redux入門

首先,發出 Action。
Action可以從任何地方發出,觸發的原因可能是界面操作、時間、服務器消息等等,一個目的,告訴Store我要改變某種狀態。

    store.dispatch(action);

然後,Store 自動調用 Reducer,並且傳入兩個參數:當前 State 和收到的 Action。 Reducer 會返回新的 State 。

    let nextState = todoApp(previousState, action);

State 一旦有變化,Store 就會調用監聽函數。

// 設置監聽函數
store.subscribe(listener);
listener可以通過store.getState()得到當前狀態。如果使用的是 React,這時可以觸發重新渲染 View。

    function listerner() {
        let newState = store.getState();
        component.setState(newState);   
    }

實際上React-Redux有更自動化的方法,請參考:
https://daveceddia.com/how-does-redux-work/

Redux如何觸發組件view的改變?

這個是React-Redux的內容,需要把組件connect到redux中,如果沒有connect,就沒有組件view的自動改變。
就像這樣:

    function mapStateToProps(state) {
        return {
            count: state.count
        };
    }
    export default connect(mapStateToProps)(MyCompnent);

請參考:
https://daveceddia.com/how-does-redux-work/

Redux VS React-Redux

See, redux gives you a store, and lets you keep state in it, and get state out, and respond when the state changes. But that’s all it does. It’s actually react-redux that lets you connect pieces of the state to React components. That’s right: redux knows nothing about React at all.
請參考:
https://daveceddia.com/how-does-redux-work/

輔助閱讀:

https://reactnative.cn/docs/state/
https://segmentfault.com/a/1190000011474522

如果還不清楚,或者想進一步學習React-Redux,建議讀如下文檔,非常有用:
https://daveceddia.com/what-does-redux-do/
https://daveceddia.com/how-does-redux-work/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章