1.為什麼要使用redux
- 像父子元件之間相互傳值相互呼叫的情況,並且值的適用範圍僅限於父子元件之間,這時不需要使用Redux.
- 當某個子元件去更新某種狀態時,比如更新組織機構資料。而其他的頁面又需要依賴這些資料時,此時可以考慮使用redux,把這些狀態值放入到redux中進行管理。
2.redux工作的過程圖
3.redux的工作流程圖
4.Redux簡介
Redux是針對JavaScript應用的可預測狀態容器 。
可預測性(predictable)
狀態容器(state container)
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);
複製程式碼
上面程式碼的意思是:
- 從redux包中引入createStore()方法;
- 建立了一個名為reducer的方法,第一個引數state是當前儲存在store中的資料,第二個引數action是一個容器,用於: type - 一個簡單的字串常量,例如ADD, UPDATE, DELETE等。 payload - 用於更新狀態的資料。
- 建立一個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.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'));
…
複製程式碼
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…