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;
二、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生成器文件: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 });
四、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; } };
五、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;
下面是測試代碼,以下所示:
一、獲取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());