若是要用一句話來歸納Redux,那麼可使用官網的這句話:Redux是針對JavaScript應用的可預測狀態容器。此句話雖然簡單,但包含了如下幾個含義:node
Redux基於簡化版本的Flux框架,Flux是Facebook開發的一個框架。在標準的MVC框架中,數據能夠在UI組件和存儲之間雙向流動,而Redux嚴格限制了數據只能在一個方向上流動。react
具體的模型圖以下圖所示:
ios
爲了說明整個模型的運做流程,首先咱們須要弄清Redux模型中的幾個組成對象:action 、reducer、store。數據庫
在Redux中,全部的數據(好比state)被保存在一個被稱爲store的容器中 ,在一個應用程序中只能有一個store對象。當一個store接收到一個action,它將把這個action代理給相關的reducer。reducer是一個純函數,它能夠查看以前的狀態,執行一個action而且返回一個新的狀態。npm
其實說到Redux,就不得不提到Flux,不管是Flux或其餘以Flux架構爲基礎延伸發展的函數庫(Alt、Reflux、Redux…)都是爲了要解決同一個問題:App state的管理。redux
無論什麼應用程序都須要有App state(應用程序狀態),不管是在一個須要用戶登陸的應用,要有全局的記錄着用戶登陸的狀態,或是在應用程序中不一樣操做介面(組件)或各類功能上的數據溝通,都須要用到它。axios
屬性React.js的同窗都知道,React被設計爲一個MVC架構中的View(視圖)的函數庫,但實際上它能夠做的事情比MVC中的View(視圖)還要更多,它甚至能夠做相似Model(模型)或Controller(控制器)的事情。設計模式
同時,在React中的組件是沒法直接更動state(狀態)的包含值,要透過setState方法來進行更動,這有很大的緣由是爲了Virtual DOM(虛擬DOM)的所設計,這是其中一點。另外在組件的樹狀階層結構,父組件(擁有者)與子組件(被擁有者)的關係上,子組件是隻能由父組件以props(屬性)來傳遞屬性值,子組件本身自己沒法更改本身的props,這也是爲何一開始在學習React時,都會看到大部份的例子只有在最上層的組件有state,並且都是由它來負責進行當數據改變時的從新渲染工做,子組件一般只有負責呈現數據。數組
固然,有一個很技巧性的方式,是把父組件中的方法聲明由props傳遞給子組件,而後在子組件觸發事件時,調用這個父組件的方法,以此來達到子組件對父組件的溝通,間接來更動父組件中的state。不過這個做法並不直覺,須要事先規範好兩邊的方法。在簡單的應用程序中,這溝通方式還可行,但若是是在有複雜的組件嵌套階層結構時,例如層級不少或是不一樣樹狀結構中的子組件要互相溝通時,這個做法是派不上用場的。服務器
在複雜的組件樹狀結構時,惟一能做的方式,就是要將整個應用程序的數據整合在一塊兒,而後獨立出來,也就是整個應用程序領域的數據部份。另外還須要對於數據的全部更動方式,也要獨立出來。這二者組合在一塊兒,就是稱之爲」應用程序領域的狀態」,爲了區分組件中的狀態(state),這個做爲應用程序領域的持久性數據集合,會被稱爲store(存儲)。
說明:以上兩段來自慕課網對Redux的總結。
單向數據流是Flux架構的核心設計,其流程示意圖以下:
這個數據流的位於最中心的設計是一個AppDispatcher(應用發送器),你能夠把它想成是個發送中心,不論來自組件何處的動做都須要通過它來發送。每一個store會在AppDispatcher上註冊它本身,提供一個callback(回調),當有動做(action)發生時,AppDispatcher(應用發送器)會用這個回調函數通知store。
因爲每一個Action(動做)只是一個單純的對象,包含actionType(動做類型)與數據(一般稱爲payload),咱們會另外須要Action Creator(動做建立器),它們是一些輔助函數,除了建立動做外也會把動做傳給Dispatcher(發送器),也就是調用Dispatcher(發送器)中的dispatch方法。
Dispatcher(發送器)的用途就是把接收到的actionType與數據(payload),廣播給全部註冊的callbacks。它這個設計並不是是首創的,這在設計模式中相似於pub-sub(發佈-訂閱)系統,Dispatcher則是相似Eventbus的概念。
配置Redux開發環境的最快方法是使用create-react-app
工具。在開始以前,確保已經安裝並更新了nodejs、npm和yarn。下面以生成一個redux-shopping項目並安裝Redux爲例。
若是沒有安裝create-react-app
工具,請使用下面的命令先執行安裝操做。
npm install -g create-react-app
而後,在使用下面的命令建立redux-shopping項目。
create-react-app redux-shopping cd redux-shopping yarn add redux
首先,刪除src文件夾中除index.js之外的全部文件。打開index.js,刪除全部代碼,鍵入如下內容:
import { createStore } from "redux"; const reducer = function(state, action) { return state; } const store = createStore(reducer);
上面代碼的意思是:
目前,state爲undefined或null,要解決這個問題,須要分配一個默認的值給state,使其成爲一個空數組。例如:
const reducer = function(state=[], action) { return state; }
目前咱們建立的reducer是通用的,那麼咱們如何使用多個reducer呢?此時咱們可使用Redux包中提供的combineReducers函數。作以下內容修改:
// src/index.js import { createStore } from "redux"; import { combineReducers } from 'redux'; const productsReducer = function(state=[], action) { return state; } const cartReducer = function(state=[], action) { return state; } const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); let store = createStore(rootReducer);
接下來,咱們將爲reducer定義一些測試數據。
// src/index.js … const initialState = { cart: [ { product: 'bread 700g', quantity: 2, unitCost: 90 }, { product: 'milk 500ml', quantity: 1, unitCost: 47 } ] } const cartReducer = function(state=initialState, action) { return state; } … let store = createStore(rootReducer); console.log("initial state: ", store.getState());
接下來,咱們能夠在終端中執行npm start
或者yarn start
來運行dev服務器,並在控制檯中查看state。
如今,咱們的cartReducer什麼也沒作,但它應該在Redux的存儲區中管理購物車商品的狀態。咱們須要定義添加、更新和刪除商品的操做(action)。此時咱們能夠作以下的一些定義:
// src/index.js … const ADD_TO_CART = 'ADD_TO_CART'; const cartReducer = function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } } …
咱們繼續來分析一下代碼。一個reducer須要處理不一樣的action類型,所以咱們須要一個SWITCH語句。當一個ADD_TO_CART類型的action在應用程序中分發時,switch中的代碼將處理它。
接下來,咱們將定義一個action,做爲store.dispatch()的一個參數。action是一個Javascript對象,有一個必須的type和可選的payload。咱們在cartReducer函數後定義一個:
… function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } } …
在這裏,咱們定義了一個函數,返回一個JavaScript對象。在咱們分發消息以前,咱們添加一些代碼,讓咱們可以監聽store事件的更改。
… let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
接下來,咱們經過分發消息到store來向購物車中添加商品。將下面的代碼添加在unsubscribe()以前:
… store.dispatch(addToCart('Coffee 500gm', 1, 250)); store.dispatch(addToCart('Flour 1kg', 2, 110)); store.dispatch(addToCart('Juice 2L', 1, 250));
下面是整個index.js文件的源碼:
// src/index.js import { createStore } from "redux"; import { combineReducers } from 'redux'; const productsReducer = function(state=[], action) { return state; } const initialState = { cart: [ { product: 'bread 700g', quantity: 2, unitCost: 90 }, { product: 'milk 500ml', quantity: 1, unitCost: 47 } ] } const ADD_TO_CART = 'ADD_TO_CART'; const cartReducer = function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } } function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } } const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); let store = createStore(rootReducer); console.log("initial state: ", store.getState()); let unsubscribe = store.subscribe(() => console.log(store.getState()) ); store.dispatch(addToCart('Coffee 500gm', 1, 250)); store.dispatch(addToCart('Flour 1kg', 2, 110)); store.dispatch(addToCart('Juice 2L', 1, 250)); unsubscribe();
保存代碼後,Chrome會自動刷新,能夠在控制檯中確認新的商品已經添加了。
會發現,index.js中的代碼逐漸變得冗雜。因此,接下來咱們對上面的項目進行一個組織拆分,使之成爲Redux項目。首先,在src文件夾中建立一下文件和文件夾,文件結構以下:
src/ ├── actions │ └── cart-actions.js ├── index.js ├── reducers │ ├── cart-reducer.js │ ├── index.js │ └── products-reducer.js └── store.js
而後,咱們把index.js中的代碼進行整理:
// src/actions/cart-actions.js export const ADD_TO_CART = 'ADD_TO_CART'; export function addToCart(product, quantity, unitCost) { return { type: ADD_TO_CART, payload: { product, quantity, unitCost } } }
// src/reducers/products-reducer.js export default function(state=[], action) { return state; }
// src/reducers/cart-reducer.js import { ADD_TO_CART } from '../actions/cart-actions'; const initialState = { cart: [ { product: 'bread 700g', quantity: 2, unitCost: 90 }, { product: 'milk 500ml', quantity: 1, unitCost: 47 } ] } export default function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } default: return state; } }
// src/reducers/index.js import { combineReducers } from 'redux'; import productsReducer from './products-reducer'; import cartReducer from './cart-reducer'; const allReducers = { products: productsReducer, shoppingCart: cartReducer } const rootReducer = combineReducers(allReducers); export default rootReducer;
// src/store.js import { createStore } from "redux"; import rootReducer from './reducers'; let store = createStore(rootReducer); export default store;
// src/index.js import store from './store.js'; import { addToCart } from './actions/cart-actions'; console.log("initial state: ", store.getState()); let unsubscribe = store.subscribe(() => console.log(store.getState()) ); store.dispatch(addToCart('Coffee 500gm', 1, 250)); store.dispatch(addToCart('Flour 1kg', 2, 110)); store.dispatch(addToCart('Juice 2L', 1, 250)); unsubscribe();
整理完代碼以後,程序依然會正常運行。如今咱們來添加修改和刪除購物車中商品的邏輯。修改cart-actions.js和cart-reducer.js文件:
// src/reducers/cart-actions.js … export const UPDATE_CART = 'UPDATE_CART'; export const DELETE_FROM_CART = 'DELETE_FROM_CART'; … export function updateCart(product, quantity, unitCost) { return { type: UPDATE_CART, payload: { product, quantity, unitCost } } } export function deleteFromCart(product) { return { type: DELETE_FROM_CART, payload: { product } } }
// src/reducers/cart-reducer.js … export default function(state=initialState, action) { switch (action.type) { case ADD_TO_CART: { return { ...state, cart: [...state.cart, action.payload] } } case UPDATE_CART: { return { ...state, cart: state.cart.map(item => item.product === action.payload.product ? action.payload : item) } } case DELETE_FROM_CART: { return { ...state, cart: state.cart.filter(item => item.product !== action.payload.product) } } default: return state; } }
最後,咱們在index.js中分發這兩個action:
// src/index.js … // Update Cart store.dispatch(updateCart('Flour 1kg', 5, 110)); // Delete from Cart store.dispatch(deleteFromCart('Coffee 500gm')); …
Redux擁有不少第三方的調試工具,可用於分析代碼和修復bug。最受歡迎的是time-travelling tool,即redux-devtools-extension。設置它只須要三個步驟。
yarn add redux-devtools-extension
一旦安裝完成,咱們對store.js稍做修改都會反映到結果上。例如,咱們還能夠把src/index.js中日誌相關的代碼刪除掉。返回Chrome,右鍵單擊該工具的圖標,打開Redux DevTools面板。
能夠看到,Redux Devtools很強大。你能夠在action, state和diff(方法差別)之間切換。選擇左側面板上的不一樣action,觀察狀態樹的變化,你還能夠經過進度條來播放actions序列。
若是你的項目使用的是React,那麼Redux能夠很方便的與React集成。
yarn add react-redux
目前,已經完成了集成的第一部分。能夠啓動服務器以查看效果。第二部分涉及到使用剛剛安裝的react-redux包中的幾個方法。經過這些方法將React組件與Redux的store和action相關聯。此外,還可使用Express和Feathers這樣的框架來設置API。
在Redux中,咱們還能夠安裝其餘一些包,好比axios等。咱們React組件的state將由Redux處理,確保全部組件與數據庫API的同步。想要查看更多的內容,能夠訪問下面的連接Build a CRUD App Using React, Redux and FeathersJS