React中理解並使用Redux

靜水流深醬發表於2019-03-17
1.為什麼要使用redux

在這裡插入圖片描述

  1. 像父子元件之間相互傳值相互呼叫的情況,並且值的適用範圍僅限於父子元件之間,這時不需要使用Redux.
  2. 當某個子元件去更新某種狀態時,比如更新組織機構資料。而其他的頁面又需要依賴這些資料時,此時可以考慮使用redux,把這些狀態值放入到redux中進行管理。
2.redux工作的過程圖

在這裡插入圖片描述

3.redux的工作流程圖

在這裡插入圖片描述

4.Redux簡介

Redux是針對JavaScript應用的可預測狀態容器

  1. 可預測性(predictable)

  2. 狀態容器(state container)

  3. JavaScript應用

在這裡插入圖片描述

首先我們需要弄清Redux模型中的幾個組成物件:action 、reducer、store :

  • action:官方的解釋是action是把資料從應用傳到 store 的有效載荷,它是 store 資料的唯一來源;要通過本地或遠端元件更改狀態,需要分發一個action;
  • reducer:action發出了做某件事的請求,只是描述了要做某件事,並沒有去改變state來更新介面,reducer就是根據action的type來處理不同的事件;
  • store:store就是把action和reducer聯絡到一起的物件,store本質上是一個狀態樹,儲存了所有物件的狀態。任何UI元件都可以直接從store訪問特定物件的狀態。

​ 在React中的元件是無法直接更動state(狀態)的包含值,要透過setState方法來進行更動,這有很大的原因是為了Virtual DOM(虛擬DOM)的所設計,這是其中一點。另外在元件的樹狀階層結構,父元件(擁有者)與子元件(被擁有者)的關係上,子元件是隻能由父元件以props(屬性)來傳遞屬性值,子元件自己本身無法更改自己的props,這也是為什麼一開始在學習React時,都會看到大部份的例子只有在最上層的元件有state,而且都是由它來負責進行當資料改變時的重新渲染工作,子元件通常只有負責呈現資料。

​ 當然,有一個很技巧性的方式,是把父元件中的方法宣告由props傳遞給子元件,然後在子元件觸發事件時,呼叫這個父元件的方法,以此來達到子元件對父元件的溝通,間接來更動父元件中的state。不過這個作法並不直覺,需要事先規範好兩邊的方法。在簡單的應用程式中,這溝通方式還可行,但如果是在有複雜的元件巢狀階層結構時,例如層級很多或是不同樹狀結構中的子元件要互相溝通時,這個作法是派不上用場的。

​ 在複雜的元件樹狀結構時,唯一能作的方式,就是要將整個應用程式的資料整合在一起,然後獨立出來,也就是整個應用程式領域的資料部份。另外還需要對於資料的所有更動方式,也要獨立出來。這兩者組合在一起,就是稱之為”應用程式領域的狀態”,為了區分元件中的狀態(state),這個作為應用程式領域的永續性資料集合,會被稱為store(儲存)。

說明:以上兩段來自慕課網對Redux的總結。

5.Redux簡單示例(轉載):

配置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
複製程式碼

首先,刪除src資料夾中除index.js以外的所有檔案。開啟index.js,刪除所有程式碼,鍵入以下內容:

import { createStore } from "redux";

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

const store = createStore(reducer);
複製程式碼

上面程式碼的意思是:

  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;
}
複製程式碼

目前我們建立的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.jsconst 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.jsconst 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.jsexport 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.jsexport 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'));
…
複製程式碼
6.Redux在專案開發中使用:

​ 使用者發出 Action,Reducer 函式算出新的 State,View 重新渲染。但是,一個關鍵問題沒有解決:非同步操作怎麼辦?

Action 發出以後,Reducer 立即算出 State,這叫做同步

但是在實際開發中action大部分的情況是呼叫介面傳送非同步請求,也就是說:

Action 發出以後,過一段時間再執行 Reducer,這就是非同步

redux-thunk 是一個比較流行的 redux 非同步 action 中介軟體

1.匯入thunk: import thunk from 'redux-thunk'。

2.匯入中介軟體: import {createStore,applyMiddleware} from 'redux'。

3.建立store:let store = createStore(reducer函式,applyMiddleware(thunk))。

4.啟用redux-thunk中介軟體,只需要在createStore中加入applyMiddleware(thunk)就可以。

5.建立action 建立函式,利用redux-thunk 幫助你統一了非同步和同步 action 的呼叫方式(把非同步過程放在 action 級別解決)。

相關使用示例:

//1.相關元件的引入
import { createStore ,applyMiddleware} from 'redux'; 
import thunk from 'redux-thunk'; 

//2.新建store怎麼加入中介軟體
const store = createStore(counter,applyMiddleware(thunk));
 
//3.action函式怎麼使用  
export  function addGunAsync(){
	//thunk外掛的作用,這裡可以返回函式
	return dispatch =>{
		//非同步結束後,手動執行dispatch
		setTimeout(() => {
            // addGUN()時你的action
		  	dispatch(addGUN())
		}, 2000)
	}
}
複製程式碼

關於mapStateToProps 、mapDispatchToProps 和connect的用法可以參考 www.cnblogs.com/hanmeimei/p…

借鑑相關文章: blog.csdn.net/xiangzhihon… blog.csdn.net/qq_42606051…

相關文章