状态管理
React中,没有专门的状态管理库,都是js通用的状态管理库来进行全局的数据存储和管理。
所以在React中要实现状态管理就比较麻烦,首先需要创建一个全局的数据存储和数据管理的工具,然后通过其他工具让全局数据的修改能触发React页面更新。
Redux是最老的全局数据管理库,@reduxjs/toolkit是Redux的进化版本。
Redux、@reduxjs/toolkit和MobX这三个库所创建的全局数据管理对象就是单纯的js对象,和React无关并不能够触发React的更新,所以需要将它们所创建的全局数据管理对象通过另外一个库来和React的组件进行连接。
Redux介绍
Redux是一个流行的JavaScript框架,通常与 React 框架一起使用,但它可以与任何 JavaScript 框架或库一起工作。Redux 用于在应用中处理全局状态管理,确保状态的变化是可预测的。

Redux基本原则:
- 单一数据源
state是只读的- 使用纯函数来执行修改
Redux核心API:
legacy_createStore(reducer):用于创建store数据仓库,需要引入import {legacy_createStore} from "redux";store.getState():用于获取store里面的数据store.dispatch(action):用于派发action,触发reducer修改store里面的数据,它接受一个对象。store.subscribe(methods):订阅store的修改,它接受一个函数。只要store发生改变,传入的回调函数就会被执行

Redux使用
安装模块:
npm i redux
npm i react-redux
创建仓库
在项目中src目录下新建store目录,在store目录下新建index.js文件,在该文件中编写reducer方法。
Redux创建store数据仓库就是编写reducer方法,然后在方法中给state默认值作为初始数据,接着在方法中判断type来进行修改数据,最后在方法中调用legacy_createStore创建store仓库。
reducer是一个纯函数,接收两个参数:当前的状态(state)也就是修改前上一次的状态和动作对象(action),然后返回新的状态。
- 当前的状态
state,也就是仓库store的数据 - 动作对象
action表示发生了什么,是一个普通的 JavaScript 对象。必须有一个type属性来表示这个action的类型,还可以包含额外的信息作为payload属性,这个payload也就是需要修改的数据。type属性、payload属性这两个属性名是任意命名的,也就是调用store.dispatch(action)这个方法时需要传入一个对象,至于具体传入什么样的对象取决于开发者。但通常都是传入type属性表示修改store仓库的哪个数据,payload属性表示修改的数据的值。
Redux创建store数据仓库代码:
// src/store/index.js
import { legacy_createStore as createStore } from 'redux'
const defaultState = {
singer: "G.E.M.",
album: ['G.E.M.', '18', 'My Secret', 'Xposed', '新的心跳', '摩天动物园', '启示录']
}
const reducer = (state = defaultState, action) => {
/** reducer方法接受两个参数state,action
* state是当前的状态也就是仓库的数据,并给state仓库数据赋值一个默认值defaultState作为初始数据
* action是动作对象
**/
// 具体修改数据的行为
switch (action.type) {
case "CHANGESINGER":
state.singer = action.payload;
break;
case "RESETSINGER":
state.singer = "G.E.M.";
break;
default:
break;
}
// 返回的新的状态state需要深拷贝,这样的目的是解除对原来的state状态的引用
state = JSON.parse(JSON.stringify(state))
// 最后reducer方法必须返回处理后的新的状态state
return state;
}
// 调用redux的legacy_createStore来创建store数据仓库
let store = createStore(reducer);
export default store; // 将创建的仓库暴露出去
state进行深拷贝的原因是,当修改后通过react-redux触发React进行重新渲染时会将之前的store的状态和现在获取到的修改后的store的状态进行全等判断,如果store中只是修改了引用数据的某个属性,而引用地址没有改变,React是不会重新渲染页面的,这也保证了状态的唯一性。
App.jsx中引入仓库,并在控制台输出查看创建的store数据仓库
// App.jsx
import React from 'react';
// 引入仓库
import store from './store/index.js';
// 控制台输出仓库
console.log(store,'store');
function App() {
return (
<div>
<h1>My App</h1>
</div>
);
}
export default App;
其实
store数据仓库就是一个对象,里面有一些方法。最关键的两个方法就是dispatch方法,用于触发对某个数据的修改和getState方法,用于获取state状态的数据。
获取仓库数据
store.getState():用于获取store里面的数据
// App.jsx
import React from 'react';
// 引入仓库
import store from './store/index.js';
// 获取仓库的数据
let state = store.getState();
console.log(state, 'state');
function App() {
return (
<div>
<h1>My App</h1>
<p>歌手:{state.singer}</p>
<p>专辑:</p>
{
state.album.map((item,index) => {
return <p key={index}>{item}</p>
})
}
</div>
);
}
export default App;
修改仓库数据
store.dispatch(action):用于修改store里面的数据
// App.jsx
import React from 'react';
// 引入仓库
import store from './store/index.js';
// 获取仓库的数据
let state = store.getState();
function App() {
const changeStore = () => {
// 此处传入的对象就是reducer方法中接受的action参数
store.dispatch({type:'CHANGESINGER',payload:"G.E.M. 邓紫棋"});
}
const looStore = () => {
console.log(store.getState())
}
return (
<div>
<h1>My App</h1>
<p>歌手:{state.singer}</p>
<p>专辑:</p>
{
state.album.map((item,index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改store</button>
<button onClick={looStore}>查看store</button>
</div>
);
}
export default App;
页面上的数据并没有被修改,但是store里面的数据确实被修改了。因为Redux是一个单纯的JavaScript框架,它更新了store里面的数据并不会触发React更新页面的数据。
Redux更新了store里面的数据虽然不会触发React更新页面的数据,但是它会触发Redux自己的subscribe监听,store.subscribe(methods)就是通用于订阅store的修改。
使用react-redux将redux和React连接起来
首先引入react-redux的Provider组件,通过这个组件将redux创建的store仓库关联到项目中,然后利用react-redux的connect方法连接到某个要使用store仓库中数据的组件,最后编写一些映射关系。
注册react-redux插件,将react-redux和React进行关联来触发React更新页面数据
在React中注册插件就是引入一个组件,然后把要使用该插件的部分用引入的组件包裹起来。所以React中全局注册react-redux插件就是,将引入的react-redux的Provider组件把App组件包裹起来。
需要把redux创建的store仓库注入到react-redux的Provider组件,这样才能将 redux的 store 注入到 React 组件树中,使所有组件都能访问到 store。
// main.jsx
import { createRoot } from 'react-dom/client' // 用于渲染页面的
import React from 'react' // 用于创建组件的
import { Provider } from 'react-redux'; // 引入react-redux的Provider组件,然后用这个组件包裹App组件完成react-redux的全局注册
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App></App>
</Provider>
)
react-redux的connect方法连接要使用store仓库数据的组件
调用connect方法会返回一个高阶组件,然后把使用store仓库数据的组件传给返回的这个高阶组件。这样就使用了connect方法来连接 React 组件和 redux的 store。不过在最新的 react-redux 版本中,推荐使用 useSelector 和 useDispatch Hook,还有一个Hook是useStore。
useSelector: 用于从 redux的store中选择数据,并订阅数据变化,当所选的数据发生变化时,React的组件会自动重新渲染。useDispatch: 用于获取 redux的store的dispatch函数,以便在组件中修改数据。useStore没有参数,它直接返回当前的redux的store实例,可以通过这个实例调用 redux的getState、dispatch和subscribe等方法
connect方法可以在类组件和函数组件中使用,useSelector和useDispatch等Hook只能在函数组件中使用。
使用connect方法
因为将组件传入了connect方法返回的高阶组件,所以就可以通过组件的props属性来获取react-redux注入到React 组件树中的redux的 store。
//App.jsx
import React from 'react';
// 引入connect组件
import { connect } from 'react-redux'
function App(props) {
console.log(props,'props');
return (
<div>
<h1>My App</h1>
</div>
);
}
export default connect()(App);
在
props属性中添加了dispatch方法
connect方法参数
connect 方法接受四个参数,分别是 mapStateToProps、mapDispatchToProps、mergeProps 和 options,前面三个参数都是一个函数,第四个参数是一个配置对象。下面是每个参数作用和用法的详细介绍:
mapStateToProps(state, [ownProps]),作用是将 redux 的store中的状态映射到传入connect方法返回的高阶组件中的 React 组件的props。换而言之,它的作用是要给React组件的props加入redux 的store中的哪些数据。
参数:
state:redux 的store的当前状态,也就是修改前的上一次的状态。ownProps(可选):传入connect方法返回的高阶组件中的React 组件的props。
mapStateToProps(state, [ownProps])需要提供一个返回值:一个对象,其键值对会被合并到传入connect方法返回的高阶组件中的 React 组件的 props中。如果想映射redux 的store的所有状态可以直接返回redux 的整个store的状态,如果只想映射redux 的store的某些状态就把数据的属性名和属性值作为返回对象的键值对。
示例:
const mapStateToProps = (state, ownProps) => {
return {
todos: state.todos,
userId: ownProps.userId
}
};
通过这个参数和高阶组件注入到props属性中的dispatch方法就可以完成修改redux 的store的数据并触发React更新页面。
// App.jsx
import React from 'react';
// 引入connect组件
import { connect } from 'react-redux'
function App(props) {
console.log(props, 'props');
const changeStore = () => {
// 调用注入到props属性的dispatch来修改数据,此处传入的对象同样是reducer方法中接受的action参数
props.dispatch({ type: 'CHANGESINGER', payload: "G.E.M. 邓紫棋" });
}
return (
<div>
<h1>My App</h1>
<p>歌手:{props.singer}</p>
<p>专辑:</p>
{
props.album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改store</button>
</div>
);
}
let ReduxApp = connect((state) => state)(App);
export default ReduxApp;
react-redux是将redux 的
store状态注入到了React组件的props属性,所以当修改数据时就相当于修改了React组件的props属性,这也就触发了React更新页面的操作,重新运行了函数组件的函数体。所以react-redux和React进行关联的工作原理就是将redux 的store的数据映射到React组件的props属性。
在
rudecer方法需要返回新的状态,将state进行深拷贝的原因也是如果store状态注入到React组件的props属性中的引用数据只是修改了值,引用地址不变,修改前后的props属性是进行浅比较,会认为props属性没有改变,就不会React的更新重新渲染页面。
mapDispatchToProps(dispatch, [ownProps]),作用是将 redux 的dispatch函数映射到传入connect方法返回的高阶组件中的 React 组件的props。换而言之,它的作用是要给React组件的props加入哪些方法。
参数:
dispatch:redux 的store的dispatch函数。ownProps(可选):React 组件的 props。
mapDispatchToProps(dispatch, [ownProps])需要提供一个返回值:一个对象,其键值对会被合并到传入connect方法返回的高阶组件中的 React 组件的 props中。
示例:
const mapDispatchToProps = (dispatch, ownProps) => ({
addTodo: (text) => dispatch(addTodo(text)),
clearTodos: () => dispatch(clearTodos())
});
通过这个参数可以将封装的dispatch方法映射到props属性中,如果写了这个参数,在props属性中就不会在注入dispatch方法,而是将dispatch方法作为mapDispatchToProps(dispatch, [ownProps])这个参数的第一个参数,然后自己封装一些修改方法。
// App.jsx
import React from "react"
import { connect } from 'react-redux'
function App(props) {
console.log(props, 'props');
const changeStore = () => {
// 调用注入到props属性的方法来修改数据,是封装过的dispatch方法
props.changSiger("G.E.M. 邓紫棋");
}
const resetStore = () => {
// 调用注入到props属性的方法来修改数据,是封装过的dispatch方法
props.resetSinger();
}
return (
<div>
<h1>My App</h1>
<p>歌手:{props.singer}</p>
<p>专辑:</p>
{
props.album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改歌手名</button>
<button onClick={resetStore}>重置歌手名</button>
</div>
);
}
let ReduxApp = connect((state) => state, (dispatch) => {
return {
changSiger: (val) => {
// 调用传入的dispatch方法修改store仓库的数据,此处传入的对象同样是reducer方法中接受的action
dispatch({ type: 'CHANGESINGER', payload: val })
},
resetSinger: () => {
// 调用传入的dispatch方法修改store仓库的数据,此处传入的对象同样是reducer方法中接受的action
dispatch({ type: 'RESETSINGER' })
}
}
})(App);
export default ReduxApp;
React组件的
props属性中就不会在注入dispatch方法
mergeProps(stateProps, dispatchProps, ownProps),作用是将mapStateToProps和mapDispatchToProps返回的对象以及组件自身的props合并成一个最终的props对象。
参数:
stateProps:mapStateToProps返回的对象。dispatchProps:mapDispatchToProps返回的对象。ownProps:React 组件的props。
mergeProps(stateProps, dispatchProps, ownProps)需要提供一个返回值返回值:一个对象,最终会被传递给组件。
示例:
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
...dispatchProps,
customProp: 'custom value'
});
通过这个参数可以将所有要传入React组件的数据合并成一个对象映射到props属性
// main.jsx
import { createRoot } from 'react-dom/client' // 用于渲染页面的
import React from 'react' // 用于创建组件的
import { Provider } from 'react-redux'
import store from './store/index.js'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App msg='我是一条消息' />
</Provider>
)
// App.jsx
import React from "react"
import { connect } from 'react-redux'
function App(props) {
console.log(props, 'props');
const changeStore = () => {
// 调用注入到props属性的方法来修改数据,是封装过的dispatch方法
props.changSiger("G.E.M. 邓紫棋");
}
const resetStore = () => {
// 调用注入到props属性的方法来修改数据,是封装过的dispatch方法
props.resetSinger();
}
return (
<div>
<h1>My App</h1>
<p>歌手:{props.singer}</p>
<p>专辑:</p>
{
props.album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改歌手名</button>
<button onClick={resetStore}>重置歌手名</button>
</div>
);
}
let ReduxApp = connect((state) => state, (dispatch) => {
return {
changSiger: (val) => {
// 调用传入的dispatch方法修改store仓库的数据,此处传入的对象同样是reducer方法中接受的action
dispatch({ type: 'CHANGESINGER', payload: val })
},
resetSinger: () => {
// 调用传入的dispatch方法修改store仓库的数据,此处传入的对象同样是reducer方法中接受的action
dispatch({ type: 'RESETSINGER' })
}
}
}, (stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
...dispatchProps,
customProp: 'custom value'
}))(App);
export default ReduxApp;
options(可选),作用是提供一些配置选项,用于优化性能或自定义行为。
常用选项:
pure:布尔值,表示是否启用纯组件优化,默认为true。areStatesEqual:一个函数,用于比较两个状态对象(修改前的store状态和修改后的store状态)是否相等。areOwnPropsEqual:一个函数,用于比较组件自身的两个props(修改前的props属性和修改后的props属性) 对象是否相等。areStatePropsEqual:一个函数,用于比较mapStateToProps返回的两个props对象是否相等。areMergedPropsEqual:一个函数,用于比较mergeProps返回的两个props对象是否相等。
使用useStore获取 store 实例
// App.jsx
import React from "react"
import { useSelector, useDispatch, useStore } from 'react-redux'
function App(props) {
console.log(props,'props')
// 获取redux的store示例
const store = useStore();
console.log(store,'store');
// 获取当前的state
const state = store.getState();
console.log(state,'state')
// 用dispatch修改数据
const changeStore = () => {
// 调用store实例的dispatch方法
store.dispatch({
type:"CHANGESINGER",
payload:"G.E.M. 邓紫棋"
})
}
const lookStore = () => {
console.log(store.getState(),'look look');
}
return (
<div>
<h1>My App</h1>
<p>歌手:{state.singer}</p>
<p>专辑:</p>
{
state.album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改歌手名</button>
<button onClick={lookStore}>查看数据</button>
</div>
);
}
export default App;
从示例中能够看出通过
store实例获取的store数据在修改后不会更新
注意事项:
- 性能优化:直接使用
store.getState()获取状态不会触发组件的重新渲染。如果需要在状态变化时重新渲染组件,建议使用useSelector而不是useStore。 - 高级用法:
useStore主要用于那些需要直接操作store的场景,例如在自定义 Hook 中。
使用 useSelector获取store中的数据
useSelector Hook 用于从 store 中选择数据,并将其订阅到组件中。当所选的数据发生变化时,组件会自动重新渲染。它接收 redux的 store 的当前状态作为参数,返回需要的数据。
// App.jsx
import React from "react"
import { useSelector, useDispatch } from 'react-redux'
function App(props) {
console.log(props, 'props')
// 获取当前的state
let singer = useSelector((state) => state.singer);
console.log(singer,'singer');
let album = useSelector((state) => state.album);
console.log(album,'album');
return (
<div>
<h1>My App</h1>
<p>歌手:{singer}</p>
<p>专辑:</p>
{
album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
</div>
);
}
export default App;
使用 useDispatch获取store中的dispatch 函数
useDispatch Hook 用于获取 redux的 store 的 dispatch 函数,以便在组件中修改仓库数据。
// App.jsx
import React from "react"
import { useSelector, useDispatch } from 'react-redux'
function App(props) {
console.log(props, 'props')
// 获取当前的state
let singer = useSelector((state) => state.singer);
console.log(singer, 'singer');
let album = useSelector((state) => state.album);
console.log(album, 'album');
const dispatch = useDispatch();
// 用dispatch修改数据
const changeStore = () => {
// 调用s获取 redux的 store的dispatch函数修改数据
dispatch({
type: "CHANGESINGER",
payload: "G.E.M. 邓紫棋"
})
}
const resetStore = () => {
dispatch({type:"RESETSINGER"})
}
return (
<div>
<h1>My App</h1>
<p>歌手:{singer}</p>
<p>专辑:</p>
{
album.map((item, index) => {
return <p key={index}>{item}</p>
})
}
<button onClick={changeStore}>修改歌手名</button>
<button onClick={resetStore}>重置歌手名</button>
</div>
);
}
export default App;