Redux 快速上手指南

Redux簡介

若是要用一句話來歸納Redux,那麼可使用官網的這句話:Redux是針對JavaScript應用的可預測狀態容器。此句話雖然簡單,但包含了如下幾個含義:node

  • 可預測性(predictable): 由於Redux用了reducer與純函數(pure function)的概念,每一個新的state都會由舊的state建來一個全新的state。於是全部的狀態修改都是」可預測的」。
  • 狀態容器(state container): state是集中在單一個對象樹狀結構下的單一store,store便是應用程序領域(app domain)的狀態集合。
  • JavaScript應用: 這說明Redux並非單指設計給React用的,它是獨立的一個函數庫,可通用於各類JavaScript應用。

Redux基於簡化版本的Flux框架,Flux是Facebook開發的一個框架。在標準的MVC框架中,數據能夠在UI組件和存儲之間雙向流動,而Redux嚴格限制了數據只能在一個方向上流動。react

具體的模型圖以下圖所示: 
這裏寫圖片描述ios

爲了說明整個模型的運做流程,首先咱們須要弄清Redux模型中的幾個組成對象:action 、reducer、store。數據庫

  • action:官方的解釋是action是把數據從應用傳到 store 的有效載荷,它是 store 數據的惟一來源;要經過本地或遠程組件更改狀態,須要分發一個action;
  • reducer:action發出了作某件事的請求,只是描述了要作某件事,並無去改變state來更新界面,reducer就是根據action的type來處理不一樣的事件;
  • store:store就是把action和reducer聯繫到一塊兒的對象,store本質上是一個狀態樹,保存了全部對象的狀態。任何UI組件均可以直接從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配置

配置Redux開發環境的最快方法是使用create-react-app工具。在開始以前,確保已經安裝並更新了nodejs、npm和yarn。下面以生成一個redux-shopping項目並安裝Redux爲例。

若是沒有安裝create-react-app工具,請使用下面的命令先執行安裝操做。

npm install -g create-react-app
  • 1

而後,在使用下面的命令建立redux-shopping項目。

create-react-app redux-shopping

cd redux-shopping
yarn add redux
  • 1
  • 2
  • 3
  • 4

首先,刪除src文件夾中除index.js之外的全部文件。打開index.js,刪除全部代碼,鍵入如下內容:

import { createStore } from "redux";

const reducer = function(state, action) {
  return state;
}

const store = createStore(reducer);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面代碼的意思是:

  1. 從redux包中引入createStore()方法;
  2. 建立了一個名爲reducer的方法,第一個參數state是當前保存在store中的數據,第二個參數action是一個容器,用於: 
    type - 一個簡單的字符串常量,例如ADD, UPDATE, DELETE等。 
    payload - 用於更新狀態的數據。
  3. 建立一個Redux存儲區,它只能使用reducer做爲參數來構造。存儲在Redux存儲區中的數據能夠被直接訪問,但只能經過提供的reducer進行更新。

目前,state爲undefined或null,要解決這個問題,須要分配一個默認的值給state,使其成爲一個空數組。例如:

const reducer = function(state=[], action) {
  return state;
}
  • 1
  • 2
  • 3

目前咱們建立的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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

接下來,咱們將爲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());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

接下來,咱們能夠在終端中執行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;
  }
}

…
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

咱們繼續來分析一下代碼。一個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 }
  }
}
…
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在這裏,咱們定義了一個函數,返回一個JavaScript對象。在咱們分發消息以前,咱們添加一些代碼,讓咱們可以監聽store事件的更改。

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

unsubscribe();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接下來,咱們經過分發消息到store來向購物車中添加商品。將下面的代碼添加在unsubscribe()以前:

…
store.dispatch(addToCart('Coffee 500gm', 1, 250));
store.dispatch(addToCart('Flour 1kg', 2, 110));
store.dispatch(addToCart('Juice 2L', 1, 250));
  • 1
  • 2
  • 3
  • 4

下面是整個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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

保存代碼後,Chrome會自動刷新,能夠在控制檯中確認新的商品已經添加了。 
這裏寫圖片描述

代碼拆分

會發現,index.js中的代碼逐漸變得冗雜。因此,接下來咱們對上面的項目進行一個組織拆分,使之成爲Redux項目。首先,在src文件夾中建立一下文件和文件夾,文件結構以下:

src/
├── actions
│ └── cart-actions.js
├── index.js
├── reducers
│ ├── cart-reducer.js
│ ├── index.js
│ └── products-reducer.js
└── store.js
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

而後,咱們把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 }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
// src/reducers/products-reducer.js

export default function(state=[], action) {
  return state;
}
  • 1
  • 2
  • 3
  • 4
  • 5
// 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;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
// 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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
// src/store.js

import { createStore } from "redux";
import rootReducer from './reducers';

let store = createStore(rootReducer);

export default store;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
// 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工具進行調試

Redux擁有不少第三方的調試工具,可用於分析代碼和修復bug。最受歡迎的是time-travelling tool,即redux-devtools-extension。設置它只須要三個步驟。

  1. 首先,在Chrome中安裝Redux Devtools擴展;
  2. 而後,在運行Redux應用程序的終端裏使用Ctrl+C中止服務器。並用npm或yarn安裝redux-devtools-extension包;
yarn add redux-devtools-extension

一旦安裝完成,咱們對store.js稍做修改都會反映到結果上。例如,咱們還能夠把src/index.js中日誌相關的代碼刪除掉。返回Chrome,右鍵單擊該工具的圖標,打開Redux DevTools面板。 
這裏寫圖片描述

這裏寫圖片描述

能夠看到,Redux Devtools很強大。你能夠在action, state和diff(方法差別)之間切換。選擇左側面板上的不一樣action,觀察狀態樹的變化,你還能夠經過進度條來播放actions序列。

集成React

若是你的項目使用的是React,那麼Redux能夠很方便的與React集成。

  1. 首先,中止服務器,並安裝react-redux包,安裝的命名以下:
yarn add react-redux
  1. 接下來,在index.js中加入React代碼。咱們還將使用Provider類將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

相關文章
相關標籤/搜索