reduxjs/toolkit(RTK)是 Redux 官方團隊推出的一個工具集,旨在簡化 Redux 的使用和配置。它于 2019 年 10 月 正式發布,此文章記錄一下redux的舊版本如何使用,以及引入等等。
文件目錄如下:
步驟
-
安裝依賴:
npm install redux react-redux redux-thunk
-
設置 Redux Store:
創建store.js
文件并配置createStore
。
使用combineReducers
合并多個 reducer。
使用applyMiddleware
添加thunk
中間件。 -
創建 Actions:
定義 action 類型和 action 創建函數。 -
創建 Reducers:
根據 action 類型更新 state。 -
連接 React 組件到 Redux Store:
使用Provider
組件將 store 提供給整個應用。
使用connect
函數將組件連接到 Redux store,并映射 state 和 dispatch 方法到組件的 props。 -
使用 Redux DevTools:
配置 Redux DevTools 以方便調試。
1. 安裝必要的依賴
安裝 redux
、react-redux
和 redux-thunk
(用于處理異步操作)。
npx create-react-app redux-test
cd redux-test
npm install redux react-redux redux-thunk
2. 設置 Redux Store
創建一個 store.js
文件來配置 Redux store
applyMiddleware
用于在 Redux store 中應用中間件,中間件在 Redux 的 action 被分發到 reducer 之前攔截這些 action,可以擴展 Redux 的功能,實現諸如異步操作(使用 redux-thunk 或 redux-saga)、路由控制、日志記錄等。
applyMiddleware工作原理
1.Action 分發:
當一個 action 被分發時,它首先會被傳遞給中間件。
2.中間件鏈:
中間件按照應用的順序形成一個鏈,每個中間件可以處理 action 并決定是否繼續傳遞給下一個中間件或 reducer。
3.處理邏輯:
每個中間件可以執行自定義邏輯,例如記錄日志、處理異步操作等。
4.傳遞 Action:
最終,action 會被傳遞給 reducer 進行狀態更新
src/redux/store.js
/**
* 該文件專門用于暴露一個store對象,整個應用只有一個store對象
* */
// 引入createStore,專門用于創建redux中最為核心的 store
import { createStore,applyMiddleware } from 'redux'
// 引入總的Reducers
import Reducers from './reducers/index'// 引入redux-thunk插件,用于支持異步action
// redux-thunk 允許你返回一個函數而不是一個普通的 action 對象。 ==>體現在action的返回值上,主要處理異步action
import { thunk } from 'redux-thunk'
// 引入redux-devtools-extension插件,用于支持redux調試工具
import { composeWithDevTools } from "redux-devtools-extension"
// 暴露store
export default createStore(Reducers,composeWithDevTools(applyMiddleware(thunk)))
src/redux/reducers/index.js
combineReducers
匯總所有的reducer變為一個總的reducer
/*** 該文件用于匯總所有的reducer為一個總的reducer**/
// 引入combineReducers
import { combineReducers } from "redux";
// 引入為Count組件服務的reducer
import count from "./count";
// 引入Person組件服務的的reducer
import persons from "./person";
// 匯總所有的reducer變成一個總的reducer
export default combineReducers({count,persons
})
// export default 全局暴露的對象,可以自定義暴露對象的名稱
3. 創建 Actions
src/common/common.js
// 該模塊僅用于定義常量,其他模塊導入該常量即可
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const ADD_INFO = 'personInfo'
src/redux/actions/count.js
import {INCREMENT,DECREMENT} from "../../common/common";
// import store from "./store";
/*** 該文件專門為Count組件生成action對象*
**/
/**
* 同步action:action的值為Object類型的一般對象
* */
export const increment = (data)=>({type: INCREMENT, data})
export const decrement = (data)=>({type: DECREMENT, data})
/*** 異步action:action的值為函數類型,異步action中可以調用同步action,也可以自己寫異步代碼**/
export const incrementAsync = (data,time)=>{// createStore(countReducer,applyMiddleware(thunk))// applyMiddleware中可以傳入多個中間件,中間件是一個函數,該函數的參數是store,所以此處無需傳入storereturn (dispatch)=>{// console.log(dispatch);setTimeout(()=>{// store.dispatch(incrementAction(data))dispatch(increment(data))},time)}
}
src/redux/actions/person.js
import { ADD_INFO } from "../../common/common";
export const addPerson = (personObj)=>({type:ADD_INFO,personObj})
4. 創建 Reducers
創建 countReducer.js
和 personReducer.js
文件來處理 actions 并更新 state。
src/redux/reducers/count.js
/*** 1.該文件適用于創建一個為Count組件服務的redux模塊中的reducer,reducer的本質是一個函數* 2.reducer函數會接到兩個參數,分別為:之前的狀態(preState),動作對象(action)* **/
import {INCREMENT,DECREMENT} from "../../common/common";let initialState = 0; // 數據初始化
export default function countReducer(preState =initialState, action) {// 從action對象中獲取:type,datalet { type, data } = action// console.log(preState,action); // 初次獲取0,"@@redux/INITx.1.n.3.n.9"// 根據type決定如何加工數據switch (type){case INCREMENT:// console.log(preState);return preState + data*1;case DECREMENT:return preState - data*1;default:return preState;// 初始化數據·}
}
src/redux/reducers/person.js
Redux 的 reducer 必須是純函數,這意味著它不能修改傳入的參數(即 preState),而是必須返回一個新的狀態對象。
純函數的定義:
- 不能改變傳遞過來的參數的原始值
- 只能通過返回值返回結果
- 狀態不可變性
// 數據參數
import { ADD_INFO } from "../../common/common"
const initPersonInfo =[] // 初始化列表數據
const personReducer = (preState=initPersonInfo,action)=>{let {type,personObj} = action // 此處解構switch (type){case ADD_INFO: // 若是添加數據// preState.unshift(personObj) // 修改的原數組,導致preState參數被改寫了, // personReducer就不是純函數;且preState的指向地址沒有發生變化,所以不會引起界面更新// return preState // vue中也是淺拷貝,但是vue中會進行深拷貝,所以界面更新// react 不會引起界面更新,因為指向地址并沒有發生變化;--淺拷貝--修改引用指針return [personObj,...preState] // 淺拷貝,此時preState指向新的地址,界面更新 // 界面的更新比較的是兩個對象的存儲位置,淺比較default:return preState}
}
// 純函數的定義:1.不能改變 傳遞過來的參數 的原始值 2.只能通過返回值返回結果
// 不管調用多少次,都只會返回一個結果
export default personReducer
// export default 語句的語法不允許直接在 export default 后面使用 const 或 let 聲明變量。
// export default 語句可以直接導出一個表達式或一個函數,但不能直接導出一個帶有 const 或 let 聲明的變量。
export default
語句可以直接導出一個表達式或一個函數,但不能直接導出一個帶有 const 或 let 聲明的變量。
5. 連接 React 組件到 Redux Store
src/App.jsx
import React, {Component} from 'react';
// 引入容器組件
import Count from './container/Count/index'
import Person from './container/Person/index'
// 引入store;傳遞給容器組件
class App extends Component {render() {return (<div><Count/><Person/></div>);}
}
export default App;
src/index.js
// 引入核心庫
import React from 'react';
// 創建根節點
import { createRoot } from 'react-dom/client';
import store from "./redux/store";
// 使用Provider組件, 將store傳遞給APP組件,全局狀態管理
import { Provider } from "react-redux"
// 引入文件
import App from './App';
// 創建容器
const container = document.getElementById('root'); // 外殼
const root = createRoot(container); // 創建根節點
root.render(/* 此處需要Provider包裹App,讓App所有的后代容器組件都能接收到store */<Provider store={store}><App /></Provider>
);
src/container/Count/index.jsx
connect
作用:
- 連接組件到 Redux Store:
connect 函數將 React 組件與 Redux store 連接起來,使得組件能夠訪問 store 中的狀態和分發 actions。 - 傳遞狀態到組件:
通過 mapStateToProps 函數,將 Redux store 中的狀態映射到組件的 props。 - 傳遞 dispatch 方法到組件:
通過 mapDispatchToProps 函數,將 dispatch 方法映射到組件的 props,使得組件能夠分發 actions。 - 優化渲染性能:
connect 會自動處理組件的訂閱和取消訂閱,確保組件只在相關狀態變化時重新渲染。
mapStateToProps
- mapStateToProps函數 返回的是一個對象;
- 返回的對象中的key就作為UI組件props的key,value就作為傳遞UI組件props的value
- mapStateToProps用于傳遞狀態
mapStateToDispatch
- mapStateToDispatch函數 返回的是一個對象;
- 返回的對象中的key就作為UI組件props的key,value就作為傳遞UI組件props的value
- mapStateToDispatch用于傳遞操作狀態的方法
// connect()(CountUI) ==> 容器container
// 使用connect()()創建并暴露一個Count的容器組件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
import React,{Component} from "react";
/*** 引入react-redux中connect函數,連接 UI組件和redyx(store)**/
import { connect } from "react-redux";
// store通過Provider傳遞引入
// 引入action
import {increment,decrement,incrementAsync} from "../../redux/actions/count"
// 容器通過 store 獲取狀態數據 ,傳遞給UI組件,UI組件通過props獲取
class Count extends Component{incrementNum = ()=>{// 函數let { value } = this.selectNumthis.props.increment(value)}decrementNum = ()=>{// 函數let { value } = this.selectNumthis.props.decrement(value)}// 奇數時加incrementOddNum = ()=>{// 函數let { value } = this.selectNumlet count = this.props.countif(count % 2 !== 0){this.props.increment(value)}}incrementAsync = ()=>{// 函數let { value } = this.selectNum // 字符串形式需要轉換,否則默認會字符串拼接this.props.incrementAsync(value,500)}render(){return(<div><h2>我是Count組件</h2><h1>求和的數值:{this.props.count}</h1><select ref={(c) => {this.selectNum = c}}><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button onClick={this.incrementNum}>+</button><button onClick={this.decrementNum}>-</button><button onClick={this.incrementOddNum}>求和為基數時再加+</button><button onClick={this.incrementAsync}>異步再加+</button></div>)}
}export default connect(state=>{// Uncaught Error: Objects are not valid as a React child (found: object with keys {countNum}). If you meant to render a collection of children, use an array instead.return { count:state.count} // 映射到UI組件的props,返回的數值必須是一個對象且需要取準確的對象,store 統一管理數據},{increment, decrement, incrementAsync}
)(Count)
src/container/Person/index.jsx
import React, {Component} from 'react';
import {addPerson} from '../../redux/actions/person'
import {connect} from 'react-redux'
// 使用nanoid 生成唯一id
import {nanoid} from "nanoid";
class Person extends Component {// 在類組件中,方法默認不會自動綁定 this。// 在構造函數中手動綁定 this// constructor(props) {// super(props);// this.addUserInfo = this.addUserInfo.bind(this); // 手動綁定this// }// addUserInfo (){// console.log(this.nameNode.value,this.ageNode.value,6665)// }// 使用箭頭函數來自動綁定 this。addUserInfo = ()=>{let name = this.nameNode.valuelet age = this.ageNode.valuelet id = nanoid()this.props.addPerson({id,name,age}) // 傳遞參數this.nameNode.value = ''this.ageNode.value = ''console.log(this.props)}render() {return (<div><h2>我是person組件</h2><input ref={c=>this.nameNode = c} type="text" placeholder="請輸入姓名" /><input ref={c=>this.ageNode = c} type="text" placeholder="請輸入年齡" /><button onClick={this.addUserInfo}>添加個人信息</button><ul>{ // 需要花括號遍歷數組this.props.persons.map((item)=>{return <li key={item.id}>id:{nanoid()}-姓名:{item.name} - 年齡:{item.age}</li>})}</ul></div>);}
}
export default connect(state=>({ persons:state.persons}),{addPerson})(Person)
6. 使用 Redux DevTools
參考鏈接:https://blog.csdn.net/pikaqiu_komorebi/article/details/145908046
自用
同步action,是指action的值為Object類型的一般對象。
異步action,是指action的值為函數 =?主要是因為函數能開啟異步任務。
const a = b?({data:b})
求和案例_redux精簡版
-
去除Count組件自身的狀態
-
src下建
-redux ( -store.js -count_reducer.js ) -
store.js:
1)reducer本質就是一個函數,接受:preState,action,返回加工后的狀態
2)reducer有兩個作用:初始化狀態,加工狀態
3)reducer被第一次調用時,是store自動觸發的,傳遞prestate是undefined -
count_reducer.js
1)reducer本質就是一個函數,接受:preState,action,返回加工后的狀態2)reducer有兩個作用:初始化狀態,加工狀態3)reducer被第一次調用時,是store自動觸發的,傳遞prestate是undefined
-
在
index.js
中檢測store中狀態的改變,一旦發生改變重新渲染
// 監聽redux中狀態的改變,如redux的狀態發生了改變,重新渲染App組件store.subscribe(()=>{root.render(<App />); // 渲染應用})
備注:redux只負責管理狀態,至于狀態的改變驅動著頁面的展示,需要自己去寫
求和案例_redux異步action 版
-
明確:延遲的動作不想交給組件的自身,交給action ? 異步動作
-
何時需要異步action:想要對狀態進行操作,但是具體的數據靠異步任務返回(非必須)
-
具體編碼:
1)yarn add redux-thunk,并配置在store中;使用redux的中間件 applyMiddleware ,用于添加中間件redux-thunk功能。2)創建action 的函數不再返回一般對象,而是一個函數,該函數中寫異步任務3)異步任務有結果后,分發一個同步的action 去真正操作數據
-
備注:異步action不是必須要寫的,完全可以自己等待異步任務的結果再去分發同步action
求和案例_react-redux基本使用
-
明確兩個概念
1)UI組件:不能使用任何redux的api,只負責頁面的呈現、交互等2)容器組件:負責和redux通信,并將結果交給UI組件
-
如何創建一個容器組件 — — — 靠react-redux的connect函數
connect( mapStateToProps,mapDispatchToProps )( UI組件 )- mapStateToProps:映射狀態,返回值是一個對象- mapStateToProps:映射操作狀態的方法,返回值是一個對象UI組件通過props獲取數據和方法;容器組件通過mapStateToProps,mapDispatchToProps傳遞
-
備注1:容器組件中的store是靠APP的props傳進去的,而不是在容器中直接引入
-
備注2:mapDispatchToProps,也可以是一個對象,redux-redux內部調用分發action
-
備注3:
容器組件能夠自動檢測store數據的變化
,可去除store.subscribe(()?{})方法 -
Provider能夠給APP中所有的容器傳遞store,無需手動對容器組件傳遞store對象
-
合并文件,將容器組件和UI組件放到一起
求和案例_react-redux優化
- 容器組件和UI組件混成一個文件
- 無需手動給容器組件傳遞store,在index.js中給包裹一個即可。
// 引入核心庫
import React from 'react';
// 創建根節點
import { createRoot } from 'react-dom/client';
import store from "./redux/store";
import { Provider } from "react-redux"
// 引入文件
import App from './App';
// 創建容器
const container = document.getElementById('root'); // 外殼
const root = createRoot(container); // 創建根節點root.render(<Provider store={store}><App /></Provider>
);
- 使用了react-redux后不用自己監測redux中狀態的改變,容器組件可自動監測
mapStateToDispatch
也可以簡單的寫成一個action對象,redux-redux可內部調用分發action- 一個組件要和redux “打交道” 要經過哪幾步
1)定義好UI組件——不暴露
2)引入connect生成容器組件,并暴露,寫法如下
3)在UI組件中通過this.props.xxxx讀取狀態和操作方法
connect(state?({key:value}), // 映射狀態;value數值要根據總的Reducers取值(key:xxxAction) // 映射操作狀態的方法
)(UI組件)
求和案例_react-redux數據共享版
- 定義一個Person組件,和Count組件通過redux共享數據;
- 為Person組件編寫:reducer、action、配置common常量
- 重點:Person的Reducer和Count的Reducer要使用combineReducers進行合并,合并后的總狀態是一個對象
// 引入createStore,專門用于創建redux中最為核心的 store
import { createStore,applyMiddleware,combineReducers } from 'redux'
// 引入為Count組件服務的reducer
import countReducer from "./reducers/count"
import personReducer from "./reducers/person"
// 引入redux-thunk插件,用于支持異步action
import { thunk } from 'redux-thunk'
// combineReducers匯總所有的reducer變為一個總的reducer
const allReducers = combineReducers({countNum:countReducer,addPerson:personReducer
})
// 暴露store
export default createStore(allReducers,applyMiddleware(thunk))
- 交給store的是總reducer,最后注意在組件中取出狀態的時候,取到位
求和案例_react-redux最終版
- 所有變量名稱盡量觸發對象的簡寫形式
- reducers文件夾中,編寫index.js專門用于匯總并暴露所有的reducer