一、Redux的核心思想
1. 为什么需要Redux
- JavaScript开发的应用程序,已经变得越来越复杂了。JavaScript需要管理的状态越来越多,越来越复杂,这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动画,当前分野。
- 管理不断变化的state是非常困难的。
- React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理。无论是组件定义自己的state,还是组件之间的通信通过props进行传递,也包括通过Context进行数据之间的共享;React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定。
- Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态姑那里。
- Redux除了和React一起使用之外,它也可以和其他界面库一起来使用,并且它非常小。
2. Redux的核心理念
- Store
- store是一个仓库,用来存储数据,它可以获取数据,也可以派发数据,还能监听到数据的变化。
- action
- Redux要求我们通过action来更新数据;
- 所有数据的变化,必须通过派发(dispatch)action来更新;
- action是一个普通的JavaScript对象,用来描述这次的type和content。
- reducer
- reducer是一个纯函数;
- reducer做的事情就是将传入的state和action结合起来生成一个新的state。
3. Redux的三大原则
- 单一数据源
- 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个store中;
- Redux并没有强制让我们不能创建多个Store,但是那样做不利于数据的维护;
- 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改。
- state是只读的
- 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State;
- 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;
- 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题。
- 使用纯函数执行修改
- 通过reducer将旧state和actions联系在一起,并且返回一个新的State;
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同;state tree的一部分
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用。
二、Redux基本使用
1. Redux的使用过程
- 创建一个对象,作为我们要保存的状态;
- 创建Store来存储这个state
- 创建store时必须创建reducer
- 我们可以通过store.getState来获取当前的state
- 通过action来修改state
- 通过dispatch来派发action
- 通常action中都会有type属性,也可以携带其他的数据
- 修改reducer中的处理代码
- reducer是一个纯函数,不需要直接修改state
- 可以在派发action之前,监听store的变化
//redux代码
//1. 将派发的action生成过程放到一个actionCreators函数中
//2. 将定义的所有actionCreators的函数,放到一个独立的文件中:actionCreators.js
//3. actionCreators和reducer函数中使用字符串常量是一致的,所以将常量抽取到一个独立constants的文件中
//4. 将reducer和默认值(initialState)放到一个独立的reducer.js文件中,而不是在index.js
// --- store/index.js ---
const { createStore } = require("redux")
const reducer = require("./reducer.js")
// 创建的store
const store = createStore(reducer)
module.exports = store
// -- store/constants.js ---
// 常量
const ADD_NUMBER = "add_number"
const CHANGE_NAME = "change_num"
module.exports = {
ADD_NUMBER,
CHANGE_NAME
}
// --- store/reducer.js ---
const { CHANGE_NAME, ADD_NUMBER } = 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
// --- store/actionCreators.js ---
const { CHANGE_NAME, ADD_NUMBER } = require("./constants")
const changNameAction = (name) => ({
type: CHANGE_NAME,
name
})
const addNumberAction = (num) => ({
type: ADD_NUMBER,
num
})
module.exports = {
changNameAction,
addNumberAction
}
// ---------
const store = require("./store")
const { addNumberAction, changNameAction } = require("./store/actionCreators")
//获取state
console.log(store.getState());
//通过action修改state
store.dispatch(changNameAction("kobe"))
//订阅state
const unsubscribe = store.subscribe(() => {
console.log("订阅数据的变化:", store.getState());
})
//取消订阅
unsubscribe()
2. Redux的使用流程
3. react-redux使用
redux和react没有直接的关系,但是redux是和React结合的更好
- 安装react-redux
- npm install react-redux
- 代码
// --- index.js --- 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> );
--- about.jsx --- import React, { PureComponent } from 'react' import { connect } from "react-redux" import { addNumberAction, subNumberAction } from '../store/actionCreator' export class about extends PureComponent { calcNumber(num, isAdd) { if (isAdd) { this.props.addNumber(num) } else { this.props.subNumber(num) } } render() { const { counter } = this.props return ( <div> <h2>About Page: {counter}</h2> <div> <button onClick={e => this.calcNumber(6, true)}>+6</button> </div> </div> ) } } // 将state映射到props中 const mapStateToProps = (state) => ({ counter: state.counter }) // 将dispatch映射到props中 const mapDispatchToProps = (dispatch) => ({ addNumber: (num) => dispatch(addNumberAction(num)), subNumber: (num) => dispatch(subNumberAction(num)) }) // connect本身是一个高阶函数,接收两个函数作为参数,返回一个新的函数是高阶组件,接收一个组件作为参数 export default connect(mapStateToProps, mapDispatchToProps)(about)
4. Redux的异步操作
-
Redux通过中间件来进行异步操作
-
理解中间件
- 中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一下自己的代码,比如日志记录、调用异步接口、添加代码调试功能等等
- 推荐的中间件是redux-thunk
-
redux-thunk如何发送异步请求
- 默认情况的dispatch(action),action需要是一个JavaScript的对象
- redux-thunk可以让dispatch(action函数),action可以是一个函数
- 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数
-
如何使用redux
- 安装redux-thunk:npm install redux-thunk
- 在创建store时传入应用了middleware的enhance函数
const enhancer = applyMiddleware(thunk) const store = createStore(reducer, enhancer)
- 定义返回一个函数的action
export const fetchHomeMultiDataAction = () => { return (dispatch, getState) => { axios.get("xxxxxx").then(res => { const banners = res.data.data.banner.list const recommends = res.data.data.recommend.list dispatch(changeBannersAction(banners)) dispatch(changeRecommendsAction(recommends)) }) } }
-
reducer的模块划分
- Redux提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并
- 代码
const reducer = combineReducers({ counter: counterReducer, home: homeReducer }) export default reducer
三、Redux Toolkit
1. 认识Redux Toolkit
- Redux Toolkit包旨在成为编写Redux逻辑的标准方式,从而解决上面提到的问题,也将之称为”RTK“
- 安装Redux Toolkit
- npm install @reduxjs/toolkit react-redux
- Redux Toolkit的核心API
- configureStore
- 包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的slice reducer,添加你提供的任何Redux中间件,redux-thunk默认包含,并启用Redux DevTools Extension。
- createSlice
- 接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
- createAsyncThunk
- 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的thunk。
- configureStore
2. Redux Toolkit重构
- 对reducer进行重构:通过createSlice创建一个slice
- createSlice的参数
- name:用来标记slice的名词
- initialState:初始化值
- reducers:两个参数(state/调用这个action时传递的action参数)
- createSlice返回值是一个对象,包含所有的actions
- createSlice的参数
- store的创建:configureStore
- configureStore的参数
- reducer
- middleware
- devTools:是否配置devTools工具,默认为true
- configureStore的参数
// --- features/counter.js ---
import { createSlice } from "@reduxjs/toolkit"
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 888
},
reducers: {
addNumber(state, { payload }) {
state.counter = state.counter + payload
},
subNumber(state, { payload }) {
state.counter = state.counter - payload
}
}
})
export const { addNumber, subNumber } = counterSlice.actions
export default counterSlice.reducer
// --- index.js ---
import { configureStore } from "@reduxjs/toolkit"
import counterReducer from "./features/counter"
const store = configureStore({
reducer: {
counter: counterReducer
}
})
export default store
3. Redux Toolkit的异步操作
- createAsyncThunk创建出来的action被dispatch时,会存在三种状态
- pending:action被发出,但是还没有最终的结果
- fulfilled:获取到最终的结果
- rejected:执行过程中有错误或者抛出了异常
- 我们可以在createSlice的entraReducer中监听这些结果
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from "axios"
export const fetchHomeMultidataAction = createAsyncThunk(
"xxxxxx",
async (extraInfo, { dispatch, getState }) => {
const res = await axios.get("xxxxxx")
const banners = res.data.data.banner.list
dispatch(changeBanners(banners))
return res.data
})
const homeSlice = createSlice({
name: "home",
initialState: {
banners: []
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload
}
},
// 异步修改数据:监听异步函数的状态
// extraReducers: {
// [fetchHomeMultidataAction.pending](state, action) {
// console.log("fetchHomeMultidataAction pending");
// },
// [fetchHomeMultidataAction.fulfilled](state, { payload }) {
// console.log("fetchHomeMultidataAction fulfilled");
// state.banners = payload.data.banner.list
// },
// [fetchHomeMultidataAction.rejected](state, action) {
// console.log("fetchHomeMultidataAction rejected");
// }
// }
// extraReducers的另一种写法:像builder中添加case来监听异步操作的结果
extraReducers: (builder) => {
builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {
console.log("fetchHomeMultidataAction pending");
}).addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
console.log("fetchHomeMultidataAction fulfilled");
state.banners = payload.data.banner.list
state.recommends = payload.data.recommend.list
}).addCase(fetchHomeMultidataAction.rejected, (state, action) => {
console.log("fetchHomeMultidataAction rejected");
})
}
})
export const { changeBanners, changeRecommends} = homeSlice.actions
export default homeSlice.reducer
4. 自定义connect函数
// --- utils/index.js ---
export { StoreContext } from "./StoreContext"
export { connect } from "./connect"
// --- utils/StoreContext.js ---
import { createContext } from "react";
export const StoreContext = createContext()
// --- utils/connect.js ---
// connect的参数
// 参数一:函数
// 参数二:函数
// 返回值:函数 => 高阶组件(接收一个组件,返回一个组件)
import { PureComponent } from "react";
import { StoreContext } from "./StoreContext";
export function connect(mapStateToProps, mapDispatchToProps) {
return function(WrapperComponent) {
class NewComponent extends PureComponent {
constructor(props, context) {
super(props)
this.state = mapStateToProps(context.getState())
}
componentDidMount() {
this.context.subscribe(() => {
this.setState(mapStateToProps(this.context.getState()))
})
}
render() {
const stateObj = mapStateToProps(this.context.getState())
const dispatchObj = mapDispatchToProps(this.context.dispatch)
return <WrapperComponent {...this.props} {...stateObj} {...dispatchObj} />
}
}
NewComponent.contextType = StoreContext
return NewComponent
}
}
// --- index.js ---
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from "react-redux"
import { StoreContext } from './hoc';
import App from './App';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>
</Provider>
);
5. 状态管理方式
- 方式一:组件自己的state管理
- 方式二:Context数据的共享状态
- 方式三:Redux管理应用状态