什么是Redux
Redux 是用于js應用的狀態管理庫,通常和React一起用。幫助開發者管理應用中各個組件之間的狀態,使得狀態的變化變得更加可預測和易于調試。
Redu也可以不和React組合使用。(通常一起使用)
Redux 三大原則
單一數據源
- 整個應用程序的state被存儲在一棵obj tree中,這個obj tree只存儲在一個store中;
- redux 并沒有強制讓我們不能創建多個Store,但這樣做不利于數據的維護;
- 單一的數據源可以讓整個應用程序的state變得方便維護,追蹤,修改。
State是只讀的
- 唯一修改state的方法是觸發action
- 這樣確保了View或網絡請求都不能直接修改state,他們只能通過action來描述自己想要如何修改state;
- 可以保證所有修改都被集中化處理,并按照嚴格的順序來執行,所以不必擔心race condition的問題。
使用純函數來執行修改
- 通過reducer將舊state和actions聯系在一起,返回一個新的State
- 隨著應用程序的復雜度增加,我們可以將reducer拆分成多個小的reducers,分別操作不同state tree 的一部分
- 但所有的reducers都應該是純函數,不能產生任何的副作用。
redux 如何使用
- 安裝react-redux:
yarn add react-redux
- 創建store 管理全局狀態
src/store/constants.js
export const ADD_NUMBER = 'add_number'
export const SUB_NUMBER = 'sub_number'
export const CHANGE_BANNERS = 'change_banners'
export const CHANGE_RECOMMENDS = 'change_recommends'
創建reducer管理狀態
src/store/reducers.js
import * as actionTypes from "./constants"const initialState = {counter: 100,banners: [],recommends: []
}
function reducer(state = initialState, action) {switch (action.type) {case actionTypes.ADD_NUMBER:return { ...state, counter: state.counter + action.num }case actionTypes.SUB_NUMBER:return { ...state, counter: state.counter - action.num }case actionTypes.CHANGE_BANNERS:return { ...state, banners: action.banners }case actionTypes.CHANGE_RECOMMENDS:return { ...state, recommends: action.recommends }default:return state}
}
export default reducer
src/store/index.js
import { createStore } from 'redux';
import reducer from './reducer';const store = createStore(reducer);export default store;
src/store/actionCreators.js
創建actionCreators,放修改狀態的函數
import * as actionTypes from "./constants"
import axios from "axios"export const addNumberAction = (num) => ({type: actionTypes.ADD_NUMBER,num
})export const subNumberAction = (num) => ({type: actionTypes.SUB_NUMBER,num
})export const changeBannersAction = (banners) => ({type: actionTypes.CHANGE_BANNERS,banners
})export const changeRecommendsAction = (recommends) => ({type: actionTypes.CHANGE_RECOMMENDS,recommends
})export const fetchHomeMultidataAction = () => {// 如果是一個普通的action, 那么我們這里需要返回action對象// 問題: 對象中是不能直接拿到從服務器請求的異步數據的// return {}return function(dispatch, getState) {// 異步操作: 網絡請求// console.log("foo function execution-----", getState().counter)axios.get("http://123.207.32.32:8000/home/multidata").then(res => {const banners = res.data.data.banner.listconst recommends = res.data.data.recommend.list// dispatch({ type: actionTypes.CHANGE_BANNERS, banners })// dispatch({ type: actionTypes.CHANGE_RECOMMENDS, recommends })dispatch(changeBannersAction(banners))dispatch(changeRecommendsAction(recommends))})}// 如果返回的是一個函數, 那么redux是不支持的// return foo
}
- 在項目index.js 根節點引用
src/index.js
import {Provider} from 'react-redux'
import store from 'react-redux'const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<React.StrictMode><Provider store={store}><App /></Provider></React.StrictMode>
);
- 在需要使用redux的頁面或組件中,通過connect高階組件映射到該組件的props中,解耦store和class組件的耦合。
redux中異步操作
redux也引入了中間件的概念:
- 目的是在dispatch的action和最終達到的reducer之間,擴展一些自己的代碼
- 比如日志記錄,調用異步接口,添加代碼調試功能等
發送異步網絡請求,可以添加對應的中間件
- 官網推薦 redux-thunk
redux-thunk 如何可以發送異步請求
- 默認情況下的dispatch(action),action需要是一個js對象
- redux-thunk可以讓dispatch(action函數),action可以是一個函數
- 該函數會被調用,并會傳給這個函數一個dispatch函數和getState函數
- dispatch函數用于我們之后再次派發action
- getState 函數考慮我們之后的一些操作需要依賴原來的狀態,用于讓我們可以獲取之前的一些狀態
如何使用redux-thunk
- 安裝redux-thunk
yarn add redux-thunk - 創建store時傳入應用了middleware的enhance函數
- 通過applyMiddleware來結合多個Middleware,返回一個enhancer;
- 將enhancer作為第二個參數傳入到createStore中;
// 通過applyMiddleware來結合多個Middleware, 返回一個enhancer
const enhancer = applyMiddleware(thunkMiddleware);
// 將enhancer作為第二個參數傳入到createStore中
const store = createStore(reducer, enhancer);
- 定義返回一個函數的action:
- 注意:這里返回一個函數
- 該函數在dispatch之后會被執行
const getHomeMultidataAction = () => {return (dispatch) => {axios.get("http://123.207.32.32:8000/home/multidata").then(res => {const data = res.data.data;dispatch(changeBannersAction(data.banner.list));dispatch(changeRecommendsAction(data.recommend.list));})}
}
combineReducers函數
- 事實上,redux給我們提供了一個combineReducers函數可以讓我們方便對多個reducer進行合并
- 那么combineReducers是如何實現的?
- 它也是將我們傳入的reducers合并到一個對象中,最終返回一個combination函數(相當于我們之前的reducer函數)
- 在執行combination函數的過程中,它會通過判斷前后返回的數據是否相同來決定返回之前的state還是新的state。
- 新的state會觸發訂閱者發生對應的刷新,而舊的state可以有效的組織訂閱者發生刷新。
Redux 基本原理
所有的狀態都以對象樹的方式(state)存放于單個store中。
唯一改變狀態樹(state tree)的方法是創建action:一個描述發生了什么的對象,并將其dispatch派發給store。要指定狀態樹如何響應action來進行更新,你可以編寫純reducer函數,這些函數根據舊的state和action來計算新state。
新的state被創建后,對象會自動傳遞給所有注冊了監聽器的組件,從而觸發組件的重新渲染,使得界面始終保持與當前的state對象一致。
Redux在React中具體的使用方法
官方建議,安裝其他兩個插件 Redux Toolkit和React-Redux
- React Toolkit(RTK):官方推薦編寫Redux邏輯的方式,是一套工具的集合,簡化書寫方式
- React-Redux:用來鏈接 Redux和React組件的中間件
- 安裝方式
npm install @reduxjs/toolkit react-redux
Redux Toolkit(RTK)
1. createSlice 函數
作用:創建一個Redux的slice。它接受一個包含reducer函數,slice名稱和初始狀態的配置對象,并返回一個包含reducer和action creators的對象。
參數:
- name:slice的名稱,用于標識狀態的一部分。
- initialState:slice的初始狀態,定義了狀態的初始值‘
- reducers:一個對象,包含一組同步的reducer函數,用于更新狀態。
返回值:
createSlice 返回一個包含以下屬性的對象: - name:slice的名稱
- reducer:一個reducer 函數,用于處理來自action creators的動作并更新狀態
- actions:一組action creators,用于創建派發給reducer的動作。
栗子 🌰:
import { createSlice } from '@reduxjs/toolkit';// 定義初始狀態
const initialState = {count: 0,
};// 創建一個 Redux slice
const counterSlice = createSlice({name: 'counter',initialState,reducers: {// 定義同步的 reducer 函數increment(state) {state.count += 1;},decrement(state) {state.count -= 1;},// 可以接受額外參數的 reducer 函數incrementByAmount(state, action) {state.count += action.payload;},},
});// 導出action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 導出reducer
export default counterSlice.reducer;
上述代碼使用 createSlice 函數創建一個名為 counter 的slice。包含一個名為 count的狀態和三個同步的reducer函數:increment,derement和incrementByAmount。
- 通過increment,decrement,incrementByAmount 派發動作
通過counterSlice.reducer 處理動作
configureStore 函數
作用:創建一個Redux store,它接受一個包含reducer函數和其他配置選項的對象,并返回一個Redux store 實例。
參數:
- reducer:一個或多個reducer函數,用于處理來自action creators 的動作并更新狀態
- 其他配置選項:包括 middleware,devTools 等,用于配置store的行為。
返回值: - configureStore 返回一個Redux store 實例,它包含以下屬性和方法:
- getState():用于獲取當前的狀態
- dispatch(action):用于派發一個動作,以觸發狀態的更新
- subscribe(listener):用于添加一個狀態變化的監聽器,當狀態發生變化時會被調用
- replaceReducer(nextReducer):用于替換當前的reducer
栗子🌰:
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './reducers'; // 導入根 reducer// 創建 Redux store
const store = configureStore({reducer: rootReducer,// middleware: getDefaultMiddleware => getDefaultMiddleware(), // 使用默認的中間件// devTools: process.env.NODE_ENV !== 'production', // 在開發環境啟用 Redux DevTools
});export default store
在栗子中,我們使用configureStore 函數創建了一個Redux store
我們傳入了一個根reducer rootReducer,它是一個包含所有reducer的對象。我們還配置了默認的中間件,并在開發環境下啟用了Redux DevTools。
react-redux
它將所有組件分為兩大類:UI 組件和容器組件。
- UI 組件:負責呈現頁面(React)
- 容器組件:負責管理數據和業務邏輯(Redux)
react-redux中常用的組件及方法
Provider 組件
作用:將Redux的store 傳遞給整個React應用程序,使得所有組件都能夠訪問到redux的狀態。通過provider,我們可以在任何地方使用redux的狀態和派發動作。
好處:在整個應用程序中,任何一個組件都可以通過connect函數或useSelector鉤子函數來訪問Redux store 中的狀態,而不需要手動將store傳遞給每一個組件。
- 簡化代碼:不需要在每一個組件中手動傳遞store,通過Provider,store可以在整個應用程序中自動傳遞給需要的組件。
- 避免prop drilling:避免了在組件層級結構中進行多層次的prop 傳遞,提高了代碼的可維護性和可讀性。
- 一致性:所有的組件都使用相同的redux store,保證了應用程序狀態的一致性。
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";import store from './store';
import { Provider } from 'react-redux';const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Provider store={store}><App /></Provider>
)
參考