什么是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.list
const 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>
)
参考