React: 研究Redux的使用

1、簡介react

在上一篇文章中,大概講了下Flux設計模式的使用,在末尾順便提了一些基於Flux的腳本庫,其中Redux已經毋庸置疑地成爲了衆多腳本庫的翹楚之一。是的,Redux是基於Flux開發的,Redux庫的尺寸很是小,代碼量少,核心目的還是處理數據流變化的問題。Redux並不徹底是Flux,它只是類Flux的腳本庫,它移除了Dispatcher這個概念,如今的結構是包含Store、Action、Action生成器、以及用於修改State的Action對象。在Redux中,對於State的管理採用了單個不可變對象來表示,同時還引入了Reducer純函數來根據當前的State和Action返回一個新的State:(state, action) => newState。如今來對每個成分結構進行分析。數據庫

 

2、Statenpm

 在傳統的React開發中,將State分佈到每個組件中基本上是通用的作法,這麼作的好處是明顯的,每一個State數據由本身的組件去管理,須要那個組件作修改就讓對應的組件去處理,可是,隨着數據的增長,弊端也是明顯的,管理應用程序的總體State可能就變得異常困難,由於要考慮到每一個組件都將使用其內部的setState方法來改變自身的State,可能瞭解更新命令源也會變得更加複雜。然而,在Redux中則推薦將State儘可能存放到少數幾個對象中,這中處理方式幾乎成了一條規則。如今來對比這兩種不一樣的State管理分佈,以下所示。能夠發現Redux將全部的State數據都存儲到了單個對象中,簡化了在應用程序中查看和修改State的方式。編程


經過Redux,使得State管理與React徹底剝離了,Redux將會直接管理這些State。從圖中能夠看出,Redux Store中全部的數據都植根於一個對象:State樹。單個對象中的每個鍵表示State樹的一個分支。用圖能夠形象地表示以下:json

用代碼表示以下:redux

//state_data.js

//
存儲的數據
//注意事項:後面本示例不處理kind對象,將subs補全爲subjects
const state_data = { names:[ { "name_id":"1", "name":"張三", }, { "name_id":"2", "name":"李四", } ], subjects:[ { "subject_id":"1", "subject":"數學", }, { "subject_id":"2", "subject":"語文", } ], scores:[ { "score_id":"1", "score": 90, }, { "score_id":"2", "score": 95, } ] };

 

3、Action設計模式

根據Redux的規則,在上面已經肯定了應用程序State的基本結構,數據都存放到了一個不可變的對象data中。不可變意味着data對象不可修改,此時只能經過總體替換的方式來更新data對象進而達到更新State的目的。在前面Flux中說過,Action是更新應用程序State的惟一方式,在Redux也是如此。對於State的每一種更新行爲Action都是抽象的,開發者能夠確認完Action後將其動詞化。它的類型是字符串,一般由大寫字母表示,並用下劃線代替空格,作到見名知意爲好。以下所示:數組

//action_constants.js

//將Action行爲動詞化 const action_constants = { ADD_NAME:"ADD_NAME", //添加姓名 DELETE_NAME:"DELETE_NAME", //刪除姓名 UPDATE_NAME:"UPDATE_NAME", //更新姓名 ADD_SUBJECT:"ADD_SUBJECT", //添加科目 DELETE_SUBJECT:"DELETE_SUBJECT", //刪除科目 UPDATE_SUBJECT:"UPDATE_SUBJECT", //更新科目 ADD_SCORE:"ADD_SCORE", //添加分數 DELETE_SCORE:"DELETE_SCORE", //刪除分數 UPDATE_SCORE:"UPDATE_SCORE" //更新分數 }; export default action_constants;

一個Action就是一個JavaScript對象,它至少包含一個類型字段,以下所示:網絡

//一個Action對象
{ type:"ADD_NAME" }

有的時候,開發者會把Action的type拼寫錯誤,畢竟是字符串。爲了解決這個問題,JavaScript的常量就派上了用場。將Action定義爲常量可使得開發者充分利用IDE智能提示和代碼補全功能。這個一個預防失誤的不錯選擇。以下所示:app

//使用常量後,IDE都會有提示,開發很是方便
import C from './action_constants'

{
    type: C.ADD_NAME
}
{
    type: C.UPDATE_NAME
}

Action是以JavaScript語法的形式提供更新某個State所需的一系列指令的,例如某些數據須要更新,某些數據須要刪除,某些數據須要添加等等,這些數據多是一個對象,也多是一個字段,這類的數據統稱爲Action的有效載荷數據。Action是小而美的數據包,可以方便地告知Redux如何修改State。舉例以下所示,能夠發現第一個刪除行爲的Action的有效載荷數據不包括name字段。

// 刪除姓名的Action對象
// type是必需要的,以便確認什麼行爲
// name_id也是必需要的,以便確認刪除目標
{
    type: C.UPDATE_DELETE,
    name_id:"1"
}


// 添加姓名的Action對象
// type是必需要的,以便確認什麼行爲
// name_id也是必需要的,新的標識
// name也是必需要的,新的姓名
{
    type: C.ADD_NAME,
    name_id:"3",
    name:"王五"
}

在Redux中,當經過store.dispatch()函數給每種Action派發執行時,都須要開發者去定義一個Action對象,這個過程雖然說不難可是也是複雜的。開發者徹底能夠經過給每種Action類型添加一個Action生成器來簡化Action的邏輯。以下所示:

//action_builder.js

import C from './action_constants'

//生成一個」添加姓名「的action
export const addName = (new_name_id, new_name) =>
    ({
        type:C.ADD_NAME,
        name_id:new_name_id
        name:new_name
    });

//生成一個」刪除姓名「的action
export const deleteName = (name_id) =>
    ({
        type:C.DELETE_NAME,
        name_id
    });

//生成一個」更新姓名「的action
export const updateName = (name_id, name) =>
    ({
        type:C.UPDATE_NAME,
        name_id,
        name
    });

 

 四、Reducer

雖然如今整個State樹都是存儲到了單個對象中,可是它的模塊化程度仍是不高,例如開發者想用模塊化的方式描述對象。Redux就是經過函數來實現模塊化的,函數被用來更新部分State樹中的內容,這些函數被稱爲Reducer。它會把當前的State和Action當作參數,而後返回一個新的State對象。Reducer的主要目的就是實現State樹中部分數據的更新,包括葉子節點和分支。每個Reducer都是模塊化的體現,而後能夠將多個Reducer合成一個Reducer,來處理應用程序中任意給定的Action的全部State更新。如圖所示:names數組的Reducer處理的Action包括ADDNAME、DELETENAME、UPDATENAME。name對象的Reducer處理的Action包括ADDNAME和UPDATENAME。能夠看出,接收的參數爲數組,返回的也是數組,接收的參數是對象返回的也是一個對象。

每個Reducer均可以被合成或者組合成單個的Reducer函數,以便在Store中使用。例如names數組的Reducer是由name對象的Reducer合成的。正是這種合成和單個函數的特色,提供了一種能夠更新整個State樹和處理任何接收的Action的方式。注意,Reducer合成不是必需的,這是一種推薦作法,咱們也能夠建立一個Reducer函數來處理全部的Action,可是這樣就會失去模塊化和函數式編程帶來的優點。 用代碼表示以下:

//action_reducer.js

import C from './action_constants'

//處理name對象的action的Reducer函數
export const name = (state={}, action) => {
    
    switch (action.type) {
        case C.ADD_NAME:
            return {
                name_id:action.name_id,
                name:action.name
            };
        case C.UPDATE_NAME:
            return (state.name_id !== action.name_id) ? state : {
                ...state,
                name:action.name
            };
        default:
            return state;
    }
};

//處理names數組的action的Reducer函數
export const names = (state=[], action) => {
    
    switch (action.type) {
        case C.ADD_NAME:
            return [
                ...state,
                name({}, action)
            ];
        case C.DELETE_NAME:
            return state.filter(
                name => name.name_id !== action.name_id
            );
        case C.UPDATE_NAME:
            return state.map(
                name => name(name, action)
            );
        default:
            return state;
    }
};

  

、Store

同Flux的工做原理同樣,Store負責完成應用程序中State數據的保存和處理全部State更新的地方。不一樣的是,Flux設計模式中能夠支持多個Store共存,每個Store只專一於特定的數據集。而Redux中只有一個Store,它會把當前的State和Action傳遞給每個單獨的Reducer函數進而來更新State。在使用Redux以前,須要在項目中安裝和配置Redux,步驟以下:

一、npm安裝redux相關組件【開發者根據須要安裝須要的組件】

// redux: 本地數據存儲空間,至關於本地數據庫 
// react-redux: 幫助完成數據訂閱 
// redux-thunk:幫助實現異步action
npm install --save redux react-redux redux-thunk

// redux-logger: redux的日誌中間件
npm install --save-dev redux-logger

 

二、yarn配置package.json包

Redux提供了關於Store的相關API,例如能夠經過Redux的createStore函數經過傳入參數State或Reducer建立Store,Redux還有一個專門的combineReducers函數來將全部的Reducer構形成單個Reducer。如咱們所知,修改用戶應用程序State的惟一方式就是經過Store分發Action。Store提供了一個getState函數獲取保存到State數據。Store包含一個dispatch函數,能夠接收Action做爲參數,當用戶經過Store分發某個Action時,該Action將會被分配到與之相應的Reducer上,而後State就被更新了。Store還容許使用subscribe函數訂閱每次分發完一個Action後被觸發的句柄函數,固然這個訂閱函數會返回一個退訂函數,能夠用來退訂以前訂閱的監聽器。相關API以下所示:

//建立Store
export interface StoreCreator {
  <S, A extends Action, Ext, StateExt>(
    reducer: Reducer<S, A>,
    enhancer?: StoreEnhancer<Ext, StateExt>
  ): Store<S & StateExt, A> & Ext
  <S, A extends Action, Ext, StateExt>(
    reducer: Reducer<S, A>,
    preloadedState?: DeepPartial<S>,
    enhancer?: StoreEnhancer<Ext>
  ): Store<S & StateExt, A> & Ext
}
export const createStore: StoreCreator

//combineReducers合成函數
export function combineReducers<S>(
  reducers: ReducersMapObject<S, any>
): Reducer<S>
export function combineReducers<S, A extends Action = AnyAction>(
  reducers: ReducersMapObject<S, A>
): Reducer<S, A>

//獲取State數據
getState(): S

//集成自flux中的Dispatch,擁有Dispatch的全部公共函數
dispatch: Dispatch<A>

//訂閱函數,返回值是退訂函數
subscribe(listener: () => void): Unsubscribe

順便提一下,Redux不只提供了combineReducers函數來合成Reducer,還提供了compose函數。 使用compose函數也能夠將若干函數合成單個函數。它的功能很健壯,合成的函數執行的順序是從右往左執行的。舉例以下:

import { compose } from 'redux'
//需求:將scores數組的分數轉成列表而後使用逗號拼接成字符串並打印

//一、通常寫法
const scoreListString = store.getState().scores.map(s => s.score).join(",");
console.log(scoreListString);

//二、使用compose合成單函數
//首先,從State中獲取scores
//而後,綁定scores的映射函數map
//接着,生成一個score數組scoreList
//而後,使用逗號拼接字符串scoreListString
//最後,在控制檯上打印結果
const print = compose(
    scoreListString => console.log(scoreListString),
    scoreList => scoreList.join(","),
    map => map(s => s.score),
    scores => scores.map.bind(scores),
    state => state.scores
);
print(store.getState());

 

、中間件

Redux還提供了中間件這個概念,它負責Store的分發管道功能。在Redux中,中間件是由在分發某個Action過程當中一系列順序執行的若干高階函數構成的。這個高階函數容許用戶在某個Action被分發以前或以後,以及State被更新後,插入某些功能。每一箇中間件函數都是順序執行的,它們擁有訪問Action、dispatch函數,以及下一個next函數的權限。next函數將會觸發更新操做。在next函數被調用以前,State就會被更新。網絡上介紹的流程圖大體以下:

如今,咱們就在Store中使用中間件 。首先建立一個storeFactory工廠類,這個類專門用來管理Store的建立過程。而後,經過這個工廠類接着建立一個包含了日誌記錄的中間件的Store。storeFactory工廠類所在文件包含一個函數,並導出它,該函數用來對建立Store所需的數據進行分組。當須要用到Store時,直接經過這個函數進行建立便可:

//使用工廠類建立store
const store = storeFactory(initData);

在storeFactory類中插入中間件,須要用到redux中的applyMiddleware應用中間件函數。定義和插入logger中間件,以下所示:

//store_factory.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { name, names, subject, subjects, score, scores} from "./action_reducer";
import state_data from "./state_data";

//建立日誌中間件
const logger = store => next => action => {
    let result;
    console.groupCollapsed("dispatching", action.type);
    console.log('prev state', store.getState());
    console.log('action', action);
    result = next(action);
    console.log('next state', store.getState());
    console.groupEnd();
};

//建立store工廠函數
//0. 設置應用中間件applyMiddleware函數
//1. 插入中間件logger
//2. 合成Reducer函數
const storeFactory = (stateData=state_data) => 
    applyMiddleware(logger)(createStore)(
        combineReducers({name,subject,score,names,subjects,scores}),
        state_data
    )

export default storeFactory

分析結果:在logger中,在該Action被分發以前,打開了一個新的控制檯分組console.groupCollapsed,記錄了當前的State和Action。觸發了next管道上的Action順序執行中間件函數,最終到達Reducer。此時的State已經更新了,所以咱們記錄被修改過的State,最後在控制檯上分組顯示。console.groupEnd()表明中分組結束。

 

、綜合應用

下面是完整的代碼文件,以下所示: 

一、State數據文件:state_data.js

//存儲的數據
const state_data = {
    names:[
        {
            "name_id":"1",
            "name":"張三",
        },
        {
            "name_id":"2",
            "name":"李四",
        }
    ],
    subjects:[
        {
            "subject_id":"1",
            "subject":"數學",
        },
        {
            "subject_id":"2",
            "subject":"語文",
        }
    ],
    scores:[
        {
            "score_id":"1",
            "score": 90,
        },
        {
            "score_id":"2",
            "score": 95,
        }
    ]
};

export default state_data;
View Code

二、Action動詞文件:action_constants.js

//定義action類型的動詞
const action_constants = {
    ADD_NAME:"ADD_NAME",                //添加姓名
    DELETE_NAME:"DELETE_NAME",          //刪除姓名
    UPDATE_NAME:"UPDATE_NAME",          //更新姓名
    ADD_SUBJECT:"ADD_SUBJECT",          //添加科目
    DELETE_SUBJECT:"DELETE_SUBJECT",    //刪除科目
    UPDATE_SUBJECT:"UPDATE_SUBJECT",    //更新科目
    ADD_SCORE:"ADD_SCORE",              //添加分數
    DELETE_SCORE:"DELETE_SCORE",        //刪除分數
    UPDATE_SCORE:"UPDATE_SCORE"         //更新分數
};

export default action_constants;
View Code

三、Action生成器文件:action_builder.js

import C from './action_constants'

//生成一個"添加姓名"的action
export const addName = (new_name_id, new_name) =>
    ({
        type:C.ADD_NAME,
        name_id:new_name_id,
        name:new_name
    });

//生成一個"刪除姓名"的action
export const deleteName = (name_id) =>
    ({
        type:C.DELETE_NAME,
        name_id
    });

//生成一個"更新姓名"的action
export const updateName = (name_id, name) =>
    ({
        type:C.UPDATE_NAME,
        name_id,
        name
    });

//生成一個"添加科目"的action
export const addSubject = (new_subject_id, new_subject) =>
    ({
        type:C.ADD_SUBJECT,
        subject_id:new_subject_id,
        subject:new_subject
    });

//生成一個"刪除科目"的action
export const deleteSubject = (subject_id) =>
    ({
        type:C.DELETE_SUBJECT,
        subject_id
    });

//生成一個"更新科目"的action
export const updateSubject = (subject_id, subject) =>
    ({
        type:C.UPDATE_SUBJECT,
        subject_id,
        subject
    });

//生成一個"添加分數"的action
export const addScore = (new_score_id, new_score) =>
    ({
        type:C.ADD_SCORE,
        score_id:new_score_id,
        score:new_score
    });

//生成一個"刪除分數"的action
export const deleteScore = (score_id) =>
    ({
        type:C.DELETE_SCORE,
        score_id
    });

//生成一個"更新分數"的action
export const updateScore = (score_id, score) =>
    ({
        type:C.UPDATE_SCORE,
        score_id,
        score
    });
View Code 

四、Reducer函數文件:action_reducer.js

import C from './action_constants'

//處理name對象的action的Reducer函數
export const name = (state={}, action) => {

    switch (action.type) {
        case C.ADD_NAME:
            return {
                name_id:action.name_id,
                name:action.name
            };
        case C.UPDATE_NAME:
            return (state.name_id !== action.name_id) ? state : {
                ...state,
                name:action.name
            };
        default:
            return state;
    }
};


//處理names數組的action的Reducer函數
export const names = (state=[], action) => {

    switch (action.type) {
        case C.ADD_NAME:
            return [
                ...state,
                name({}, action)
            ];
        case C.DELETE_NAME:
            return state.filter(
                name => name.name_id !== action.name_id
            );
        case C.UPDATE_NAME:
            return state.map(
                nameObj => name(nameObj, action)
            );
        default:
            return state;
    }
};

//處理subject對象的action的Reducer函數
export const subject = (state={}, action) => {

    switch (action.type) {
        case C.ADD_SUBJECT:
            return {
                subject_id:action.subject_id,
                subject:action.subject
            };
        case C.UPDATE_SUBJECT:
            return (state.subject_id !== action.subject_id) ? state : {
                ...state,
                subject:action.subject
            };
        default:
            return state;
    }
};

//處理subjects數組的action的Reducer函數
export const subjects = (state=[], action) => {

    switch (action.type) {
        case C.ADD_SUBJECT:
            return [
                ...state,
                subject({}, action)
            ];
        case C.DELETE_SUBJECT:
            return state.filter(
                subject => subject.subject_id !== action.subject_id
            );
        case C.UPDATE_SUBJECT:
            return state.map(
                subjectObj => subject(subjectObj, action)
            );
        default:
            return state;
    }
};

//處理score對象的action的Reducer函數
export const score = (state={}, action) => {

    switch (action.type) {
        case C.ADD_SCORE:
            return {
                score_id:action.score_id,
                score:action.score
            };
        case C.UPDATE_SCORE:
            return (state.score_id !== action.score_id) ? state : {
                ...state,
                score:action.score
            };
        default:
            return state;
    }
};

//處理scores數組的action的Reducer函數
export const scores = (state=[], action) => {

    switch (action.type) {
        case C.ADD_SCORE:
            return [
                ...state,
                score({}, action)
            ];
        case C.DELETE_SCORE:
            return state.filter(
                score => score.score_id !== action.score_id
            );
        case C.UPDATE_SCORE:
            return state.map(
                scoreObj => score(scoreObj, action)
            );
        default:
            return state;
    }
};
View Code 

五、Store工廠類文件:store_factory.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { name, names, subject, subjects, score, scores} from "./action_reducer";
import state_data from "./state_data";

//建立日誌中間件
const logger = store => next => action => {
    console.groupCollapsed("dispatching", action.type);
    console.log('prev state:', store.getState());
    console.log('action:', action);
    next(action);
    console.log('next state:', store.getState());
    console.groupEnd();
};

//建立store工廠函數
//0. 設置應用中間件applyMiddleware函數
//1. 插入中間件logger
//2. 合成Reducer函數
const storeFactory = (stateData=state_data) =>
    applyMiddleware(logger)(createStore)(
        combineReducers({name, names, subject, subjects, score, scores}),
        state_data
    )

export default storeFactory;
View Code

 

下面是測試代碼,以下所示:

一、獲取state數據

//獲取state數據
import state_data from "./redux/state_data";
console.log(
"1————state_data:",state_data);

二、手動建立action,action中至少有一個type字段用於區分類別

//手動建立action,action中至少有一個type字段用於區分類別
import C from './redux/action_constants'

const manually_addName_Action = { type: C.ADD_NAME, name_id:"3", name:"王五"}; const manually_deleteName_Action = { type: C.DELETE_NAME, name_id:"2"}; const manually_updateName_Action = { type: C.UPDATE_NAME, name_id:"1", name:"趙六"}; console.log("manually_addName_Action:", manually_addName_Action); console.log("manually_deleteName_Action:", manually_deleteName_Action); console.log("manually_updateName_Action:", manually_updateName_Action); const manually_addSubject_Action = { type: C.ADD_SUBJECT, subject_id:"3", subject:"地理"}; const manually_deleteSubject_Action = { type: C.DELETE_SUBJECT, subject_id:"2"}; const manually_updateSubject_Action = { type: C.UPDATE_SUBJECT, subject_id:"1", subject:"政治"}; console.log("manually_addSubject_Action:", manually_addSubject_Action); console.log("manually_deleteSubject_Action:", manually_deleteSubject_Action); console.log("manually_updateSubject_Action:", manually_updateSubject_Action); const manually_addScore_Action = { type: C.ADD_SCORE, score_id:"3", score:50}; const manually_deleteScore_Action = { type: C.DELETE_SCORE, score_id:"2"}; const manually_updateScore_Action = { type: C.UPDATE_SCORE, score_id:"1", score:100}; console.log("manually_addScore_Action:", manually_addScore_Action); console.log("manually_deleteScore_Action:", manually_deleteScore_Action); console.log("manually_updateScore_Action:", manually_updateScore_Action);

三、使用生成器建立action

//使用生成器建立action
import {addName, deleteName, updateName, 
        addSubject, deleteSubject, updateSubject, 
        addScore, deleteScore, updateScore} from "./redux/action_builder";

const auto_addName_Action = addName("3","王五");
const auto_deleteName_Action = deleteName("2");
const auto_updateName_Action = updateName("1","趙六");
console.log("auto_addName_Action:", auto_addName_Action);
console.log("auto_deleteName_Action:", auto_deleteName_Action);
console.log("auto_updateName_Action:", auto_updateName_Action);

const auto_addSubject_Action = addSubject("3","地理");
const auto_deleteSubject_Action = deleteSubject("3");
const auto_updateSubject_Action = updateSubject("1","政治");
console.log("auto_addSubject_Action:", auto_addSubject_Action);
console.log("auto_deleteSubject_Action:", auto_deleteSubject_Action);
console.log("auto_updateSubject_Action:", auto_updateSubject_Action);

const auto_addScore_Action =  addScore("3", 50);
const auto_deleteScore_Action = deleteScore("3");
const auto_updateScore_Action = updateScore("1", 100);
console.log("auto_addScore_Action:", auto_addScore_Action);
console.log("auto_deleteScore_Action:", auto_deleteScore_Action);
console.log("auto_updateScore_Action:", auto_updateScore_Action);

四、Reducer函數,模塊化

//打印對象的Reducer和數組的Reducer
import { name, subject, score, names, subjects, scores } from "./redux/action_reducer";

console.log("name_reducer:",name);
console.log("subject_reducer:",subject);
console.log("score_reducer:",score);
console.log("names_reducer:",names);
console.log("subjects_reducer:",subjects);
console.log("scores_reducer:",scores);

 

五、合成多個Reducer成單個Reducer

// 合成多個Reducer爲單個Reducer
import {combineReducers} from "redux";

const combineReducer = combineReducers(
    { name, subject, score, names, subjects, scores});
console.log("combineReducer:",combineReducer);

六、定義store,分派action,觀察state數據的變化

// 定義store,分派action
import {createStore} from "redux";

const store = createStore(combineReducer, state_data);
console.log("store:",store);

store.dispatch(auto_addName_Action);
console.log("2————state_data:",store.getState());

七、插入中間件,在控制檯分組中觀察state數據變化以及logger打印插件的結果

//經過類方法建立store
import storeFactory from "./redux/store_factory";

const store = storeFactory();
console.log("store:", store);

//派發Action
store.dispatch(auto_addName_Action);
console.log("3————state_data:",store.getState());

store.dispatch(auto_deleteName_Action);
console.log("4————state_data:",store.getState());

store.dispatch(auto_updateName_Action);
console.log("5————state_data:",store.getState());

相關文章
相關標籤/搜索