Redux基本使用
純函數:1.函數內部不能依賴函數外部變量;2.不能產生副作用,在函數內部改變函數外部的變量
React只幫我們解決了DOM的渲染過程,State還是要由我們自己來管理——redux可幫助我們進行管理
Redux三大特點
1.單一數據源:整個應用的狀態存儲在一個單一的對象樹,且該對象樹只存儲在一個store中
2.State是只讀的:狀態是不可直接修改的,必須通過觸發一個“action”來發起狀態的變更
3.使用純函數來完成狀態變更:通過reducer將舊state和actions聯系在一起,并返回一個新的state;不產生任何副作用。
Redux測試項目搭建
step1:項目初始化
npm init
step2:安裝redux
npm i redux
step3:創建store倉庫,并存儲數據
const { createStore } = require('redux');
const {reducer} = require('./reducer');// 創建的store
const store = createStore(reducer);module.exports = store;
step4:創建reducer函數
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")// 初始化的數據
const initialState = {name: "why",counter: 100
}function reducer(state = initialState, action) {switch(action.type) {case CHANGE_NAME:return { ...state, name: action.name }case ADD_NUMBER:return { ...state, counter: state.counter + action.num }default:return state}
}module.exports = reducer
注意:在該代碼中,我們將action的類型封裝到了constants.js文件中,方便復用
const ADD_NUMBER = "add_number"
const CHANGE_NAME = "change_name"module.exports = {ADD_NUMBER,CHANGE_NAME
}
step5:通過action來修改state
將它封裝到一個單獨的文件中
const { ADD_NUMBER, CHANGE_NAME } = require("./constants")const changeNameAction = (name) => ({type: CHANGE_NAME,name
})const addNumberAction = (num) => ({type: ADD_NUMBER,num
})module.exports = {changeNameAction,addNumberAction
}
然后在使用的組件中進行調用
const store = require("./store")
const { addNumberAction, changeNameAction } = require("./store/actionCreators")const unsubscribe = store.subscribe(() => {console.log("訂閱數據的變化:", store.getState())
})// 修改store中的數據: 必須action
store.dispatch(changeNameAction("kobe"))
store.dispatch(changeNameAction("lilei"))
store.dispatch(changeNameAction("james"))unsubscribe()// 修改counter
store.dispatch(addNumberAction(10))
store.dispatch(addNumberAction(20))
store.dispatch(addNumberAction(30))
最終,通過拆分代碼我們會形成4個文件:
- store/index.js文件
- store/reducer.js文件
- store/actionCreators.js文件
- store/constants.js文件
React中使用Redux
redux使用過程:首先會在一個中心的store里面存儲我們對應的狀態,然后就可以讓一些組件中store中訂閱一些數據;然后也可以在組件中通過dispatch來派發一些action,這些action就會到達Reducer里面,它就會自動執行該函數,并返回一個新的state對象,再根據新的state來更新數據。數據更新后就會自動告訴訂閱者,我現在數據發生改變了,需要拿新數據,界面再重新渲染
step1:創建一個項目并安裝redux
step2:創建store
step3:在組件中使用
import React, {PureComponent } from 'react'
import Home from './pages/Home'
import Profile from './pages/profile'
import "./style.css"
import store from './store'export class App extends PureComponent {constructor() {super()this.state = {count: store.getState().count}}componentDidMount() {// 訂閱storestore.subscribe(() => {const state = store.getState()this.setState({count: state.count})})}render() {const {count} = this.statereturn (<div><h2>App Count: {count}</h2><div className="pages"><Home /><Profile /></div></div>)}
}export default App
React-Redux
redux官方幫助我們提供了 react-redux 庫,可簡化在react中使用redux的過程
npm i react-reduxyarn add react-redux
Provider
在src/index.js中導入Provider,并包裹根組件→將Redux的store傳遞給整個應用程序→使得所有組件可訪問Redux的狀態
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode><Provider store={store}><App /></Provider></React.StrictMode>
);
connect
connect是React-redux提供的一個高階組件(HOC),用于將React組件與Redux的倉庫聯系起來;
它不會修改原始組件,而是返回一個新的、連接了Redux的組件
作用:
將Redux的狀態(state) 和(dispatch)映射到組件的props中
通過 mapStateToProps 和 mapDispatchToProps,可以自定義組件需要的狀態和操作
import axios from "axios"
import * as actionTypes from "./constants"export const calcNumber = (num) => {return {type:actionTypes.CALC_NUMBER,num}
}
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { calcNumber } from '../store/actionCreators'export class About extends PureComponent {calcNumber(num) {this.props.calcNumber(num)}render() {const {count,banners, recommends} = this.propsreturn (<div><h2>About:{count}</h2><button onClick={e => this.props.calcNumber(6)}>+6</button><button onClick={e => this.props.calcNumber(-6)}>-6</button><button onClick={e => this.props.calcNumber(10)}>+10</button><button onClick={e => this.props.calcNumber(-5)}>-5</button><div className="banners"><h2>輪播圖數據:</h2><ul>{banners.map(item => {return <li key={item.acm}>{item.title}</li>})}</ul></div><div className="recommend"><h2>推薦數據</h2><ul>{recommends.map(item => {return <li key={item.acm}>{item.title}</li>})}</ul></div></div>)}
}function mapStateToProps(state){return {count: state.count,banners:state.banners,recommends:state.recommends}
}const mapDispatchToProps = (dispatch) => {return {calcNumber(num) {dispatch(calcNumber(num))}}
}// connect()返回值是一個高階組件
export default connect(mapStateToProps,mapDispatchToProps)(About)
組件中進行異步操作
網絡請求可以在class組件的componentDidMount中發送
store/home.js?
import axios from "axios"
import * as actionTypes from "./constants"export const changeBanners = (banners) => {return {type:actionTypes.CHANGE_BANNERS,banners}
}export const changeRecommends = (recommends) => {return {type:actionTypes.CHANGE_RECOMMENDS,recommends}
}
Home.jsx?
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import axios from 'axios'
import { changeBanners, changeRecommends } from '../store/actionCreators'export class Category extends PureComponent {componentDidMount() {axios.get("http://123.207.32.32:8000/home/multidata").then(res => {const banners = res.data.data.banner.listconst recommends = res.data.data.recommend.listthis.props.changeBanners(banners)this.props.changeRecommends(recommends)})}render() {return (<div><h2>Category Page:</h2></div>)}
}const mapDispatchToProps = (dispatch) => {return {changeBanners(banners) {dispatch(changeBanners(banners))},changeRecommends(recommends) {dispatch(changeRecommends(recommends))}}
}export default connect(null,mapDispatchToProps)(Category)
redux中進行異步操作
網絡請求的數據也屬于狀態管理的一部分,我們最好還是將它放在redux中來管理
在默認情況下,dispatch(action)中傳入的是一個對象→可通過reduc-thunk讓dispatch中傳入一個action函數
step1:安裝redux-thunk
npm i redux-thunk
step2:在創建store時傳入應用了middleware的enhance函數
- 通過applyMiddleware來結合多個Middleware, 返回一個enhancer;
- 將enhancer作為第二個參數傳入到createStore中;
import { createStore,applyMiddleware } from "redux";
import {thunk} from "redux-thunk"
import reducer from "./reducer";// 正常情況下,store.dispatch(object) 只能派發一個對象
// 想要派發函數store.dispatch(function) 需要做一個增強
const store = createStore(reducer,applyMiddleware(thunk))export default store
step3:定義返回一個函數的action
import axios from "axios"
import * as actionTypes from "./constants"export const changeBanners = (banners) => {return {type:actionTypes.CHANGE_BANNERS,banners}
}export const changeRecommends = (recommends) => {return {type:actionTypes.CHANGE_RECOMMENDS,recommends}
}export const fetchHomeMultidataAction = () => {// 如果是一個普通的action,那么我們需要返回一個對象// 問題:對象中不能直接拿到從服務器請求的數據// return {}return function(dispatch,getState) {// 異步操作:網絡請求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(changeBanners(banners))dispatch(changeRecommends(recommends))})}
}
step4:在組件中進行派發
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { fetchHomeMultidataAction } from '../store/actionCreators'export class Category extends PureComponent {componentDidMount() {this.props.fetchHomeMultidata()}render() {return (<div><h2>Category Page:</h2></div>)}
}const mapDispatchToProps = (dispatch) => {return {fetchHomeMultidata() {dispatch(fetchHomeMultidataAction())}}
}export default connect(null,mapDispatchToProps)(Category)
Redux模塊拆分
在開發中,如果將所有的數據都存儲在一個state中,隨著數據越來越多,會難以管理與維護;
我們可以對其進行模塊的拆分
combineReducers底層原理
redux給我們提供了一個combineReducers函數可以方便的讓我們對多個reducer進行合并
實現原理:
- 將我們傳入的reducers合并到一個對象中,最終返回一個combination函數
- 在執行combination函數的過程中,會通過判斷前后返回的數據是否相同來決定返回之前的state還是新的state
- 新的state會觸發訂閱者發生對應的刷新,而舊的state可以有效的組織訂閱者發生刷新
// 實現原理
function reducer(state={},action) {// 返回一個對象,作為store的狀態return {count:counterReducer(state.count,action),home:homeReducer(state.home,action),user:userReducer(state.user,action)}
}
Redux Toolkit(RTK)
簡介
它封裝了Redux的核心API,并提供了一些額外工具和約定,幫助我們更高效編寫Redux代碼
安裝:
npm install @reduxjs/toolkit react-redux
核心API:
- configureStore:它自動配置了 Redux 的
createStore
和applyMiddleware
,并預裝了一些常用的中間件(如redux-thunk
和redux-devtools-extension
) - createSlice:接受reducer函數的對象、切片名稱和初始狀態值,并自動生成切片reducer,并帶有相應的actions
- createAsyncThunk:接受一個動作類型字符串和一個返回承諾的函數,并生成一個pending/fulfilled/rejected基于該承諾分派動作類型的 thunk
基本使用
1.configureStore創建大倉庫store
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./modules/counter";const store = configureStore({reducer: {count: counterReducer,},
});export default store;
2.createSlice創建小倉庫
import { createSlice } from "@reduxjs/toolkit";const counterSlice = createSlice({name: "counter",initialState: {count: 100},reducers: {increment: (state, action) => {state.count += action.payload;},decrement: (state, action) => {state.count -= action.payload;}}
})export const{increment, decrement} = counterSlice.actions
export default counterSlice.reducer;
3.將模塊的reducer導入大倉庫中
4.在組件中使用數據——還是和之前一樣(Provider、connect)
異步使用
1.使用 createAsyncThunk 創建異步操作
2.?在?createSlice
?中處理異步操作的結果
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";export const fetchHomeMultidataAction = createAsyncThunk("home/multidata", async () => {const res = await axios.get("http://123.207.32.32:8000/home/multidata");// 返回結構,那么action狀態就會變成fulfilled狀態return res.data;
});const homeSlice = createSlice({name: "home",initialState: {banners: [],recommends: []},reducers: {changeBanners(state, { payload }) {state.banners = payload;},changeRecommends(state, { payload }) {state.recommends = payload;}},extraReducers: (builder) => {builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {console.log("fetchHomeMultidataAction pending");}).addCase(fetchHomeMultidataAction.fulfilled, (state, action) => {console.log("fetchHomeMultidataAction fulfilled");// 這里的數據不需要淺拷貝——因為內部的ImmutableJS重構了redux,并返回了一個新對象state.banners = action.payload.data.banner.list;state.recommends = action.payload.data.recommend.list;}).addCase(fetchHomeMultidataAction.rejected, (state, action) => {console.log("fetchHomeMultidataAction rejected");});}
});export const { changeBanners, changeRecommends } = homeSlice.actions;
export default homeSlice.reducer;
3.在組件中調用該函數即可——與原來使用一樣