React 官方并没有提供对应的状态机插件,因此,我们需要下载第三方的状态机插件 —— Redux。
在终端中定位到项目根目录,然后执行以下命令下载 Redux
npm i redux
在 React 中,不会自动生成状态机的相关配置代码,因此,需要我们自己手动去创建目录以及配置文件。
我们可以在 src 的目录中创建一个 redux 或 store 的目录,用来存放所有关于状态机的配置文件。然后,在该目录中创建一个 store.js 文件,作为整个状态机的入口文件。
- import {legacy_createStore as createStore} from 'redux'
- const store=createStore();
createStore 方法接收一个函数作为参数,该函数的返回值,会保存到 store 仓库中
- const store = createStore((state = 数据初始值) => {
- return state;
- });
通过函数参数的默认值的方式,来设置仓库数据的初始值
在 store 仓库对象身上,提供了一个 getState() 方法,用来查看仓库中的所有数据:
console.log(store.getState());
由于目前没有任何文件中在引入状态机的配置文件,如果我们需要查看仓库中的数据,暂时需要在 index.js 中引入 /redux/store.js 文件来让其运行。

state:状态,就是我们传递的数据
action是一个通知对象,里面必须有一个type属性,表示当前通知的类型,至于其他属性,你可以任意添加
可以通过store.dispatch(action对象)来更新仓库中的数据
注意:
在实际开发中,更多人喜欢用action创建函数
在实际开发中,大多数情况下,type会被定义成字符串常量
reducer本质是一个函数,它用来响应发送过来的actions,经过处理把state发送给store
在reducer函数中,需要return返回值,这样store才能接收到数据
reducer函数接收两个参数,第一个参数是初始化store,第二个参数是action

数据仓库,存放组件数据的地方。一个项目一般只有一个数据仓库,store可以把action和reducer联系在一起。
主要的职责
维护应用的state
提供getState()方法获取state
提供dispatch()方法发送action
通过subscribe()来注册监听
通过subscribe()返回值来注销监听
在src目录下创建一个actions文件夹
在该目录下创建一个index.js文件
action是一个通知对象,里面必须有一个type属性,这里的num属性是自定义的表示计数器每次增加的个数
- const incrementAction={
- type:'increment',
- num:1
- }
- export {incrementAction}
在src目录下创建reducers目录
在该目录下创建index.js文件,用来构建reducer,注意reducer要接收两个参数
第一个参数是默认状态,我们可以定义一个初始化的state,然后进行赋值
在函数里面判断第二个参数action的type值是否是我们发送过的,如果是,我们可以通过return返回新的state
- const counterReducer=(state=0,action)=>{
- switch(action.type){
- case 'increment':
- return state+action.num
- default:
- return state
- }
- }
- export {counterReducer}
在src目录下创建一个文件夹store
在该目录下创建index.js文件,用来构建store,注意createStore函数第一个参数接收的是reducer
- import {legacy_createStore as createStore} from 'redux'
- import {counterReducer} from '../reducers'
- const store=createStore(counterReducer)
- export default store
创建components文件夹,该目录下存放自定义组件
在该目录下新建Counter.jsx文件
将Counter.jsx引入到App.js文件中
- import Counter from "./components/Counter";
- function App() {
- return (
- <div>
- <Counter>Counter>
- div>
- );
- }
- export default App;
调用dispatch函数,更新仓库的数据
- import React,{useEffect,useState} from 'react'
- import store from '../redux/store'
- import {incrementAction} from '../redux/actions'
-
- export default function Counter() {
- const increment=()=>{
- store.dispatch(incrementAction)
- }
- return (
- <div>
- <h1>计数器:{store.getState()}h1>
- <button onClick={()=>{increment()}}>+button>
- div>
- )
- }
当组件加载完毕后,调用 store. subscribe注册监听,监听state数据的变化
- import React,{useEffect,useState} from 'react'
- import store from '../redux/store'
- import {incrementAction} from '../redux/actions'
-
- export default function Counter() {
- const [count, setCount] = useState(0);
- const increment=()=>{
- store.dispatch(incrementAction)
- }
- useEffect(()=>{
- store.subscribe(()=>{
-
- console.log('正在监控'+store.getState());
- setCount({})
- })
- })
-
- return (
- <div>
- <h1>计数器:{store.getState()}h1>
- <button onClick={()=>{increment()}}>+button>
- div>
- )
- }
我们一般会在页面里面修改数据,即,在页面里面调用store的dispatch方法。那就意味着action对象中的num字段很大可能是动态的,即不同的页面num字段可能是不同的,这样我们就不能把action写成一个死的对象,最好是封装成一个函数,执行过后返回一个action通知对象,然后num的值通过函数的参数来决定。这样的函数我们称之为action创建函数(action creator)
- const incrementAction=(num)=>{
- return{
- type:'increment',
- num:num
- }
- }
- export {incrementAction}
在实际开发中,type在多数情况下会被定义成常量,如下所示
在src/actions目录下,新建actionTypes.jsx文件,将所有type类型定义成常量
export const INCREMENT ="increment"
在action creator中引入
- import {INCREMENT} from './constant'
- const incrementAction=(num)=>{
- return{
- type:INCREMENT,
- num:num
- }
- }
- export {incrementAction}
项目中肯定不止一个数据,所以state的默认值应该是一个对象,而不是其他。而且除了默认值,每当case到一个条件,返回一个新的对象时,应该返回一个全新的对象,然后才是你要修改的数据(当然这些数据,如果是引用类型的话,应该修改其引用本身,否则界面可能不会更新,尽管数据发生变化了
- import { INCREMENT } from '../actions/constant'
- const counterReducer = (state = { num: 0 }, action) => {
- switch (action.type) {
- case INCREMENT:
- return {
- ...state,
- num: state.num + action.num
- }
- default:
- return state
- }
- }
- export { counterReducer }
优化后,在组件中调用的代码如下
- import React,{useEffect,useState} from 'react'
- import store from '../redux/store'
- import {incrementAction} from '../redux/actions'
-
- export default function Counter() {
- const [count, setCount] = useState(0);
- const increment=()=>{
- store.dispatch(incrementAction(2))
- }
- useEffect(()=>{
- store.subscribe(()=>{
-
- console.log('正在监控'+store.getState());
- setCount({})
- })
- })
-
- return (
- <div>
- <h1>计数器:{store.getState()}h1>
- <button onClick={()=>{increment()}}>+button>
- div>
- )
- }
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,本文主要介绍它。
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征。
只负责 UI 的呈现,不带有任何业务逻辑
没有状态(即不使用this.state这个变量)
所有数据都由参数(this.props)提供
不使用任何 Redux 的 API
容器组件有以下几个特征。
负责管理数据和业务逻辑,不负责 UI 的呈现
带有内部状态
使用 Redux 的 API
总之,只要记住一句话就可以了:UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑。
React-Redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
- npm i react-redux
- npm i redux
在constant.jsx中添加INCREMENT
export const INCREMENT="increment";
在src/actions/index.jsx中添加incrementAction
- import { INCREMENT } from "./constant";
- export const incrementAction=num=>({type:INCREMENT,num})
在src/reducers/index.jsx中添加counterReducer
- import {INCREMENT} from '../actions/constant'
- const counterReducer=(state={num:0},action)=>{
- switch(action.type){
- case INCREMENT:
- return{
- ...state,
- num:state.num+action.num
- }
- default:
- return state
- }
- }
- export {counterReducer}
在src/store/index.jsx中编写代码如下
- import {legacy_createStore as createStore} from 'redux'
- import {counterReducer} from '../reducers'
- const store=createStore(counterReducer)
- export default store
在index.js中导入Provider组件
import {Provider} from 'react-redux'
利用provider组件将整个接口进行包裹
给Provider组件设置store的属性,该属性的值就是通过createStore构建出来的store实例对象
- 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(
- <Provider store={store}>
- <App />
- Provider>
- );
Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
由于UI组件不能使用Redux的API所以,如果在组件中如果要使用Redux就必须将UI组件变成容器类组件
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来
- import React from 'react'
- import { connect } from 'react-redux'
-
- function Counter() {
- render() {
- return (
- <div>
-
- div>
- )
- }
- }
- export default connect()(Counter)
connect 方法接收一个回调函数作为参数:
- const mapStateToProps = () => {
-
- }
- export default connect(mapStateToProps)(Counter);
该回调函数本身,又可以通过第一个参数接收到仓库中的所有数据
- const mapStateToProps = (state) => {
- console.log(state); // 仓库中所有的数据
- }
- export default connect(mapStateToProps)(Counter);
在该回调函数中,返回一个对象,该对象会和组件的 props 进行合并。换句话说,该函数的返回值会添加到组件的 props 中:
- const mapStateToProps = (state) => {
- return {
- 数据名: 从 state 中获取的数据值
- }
- }
- export default connect(mapStateToProps)(Counter);
处理完成后,我们就可以在组件的 props 中访问到对应的仓库数据了:
- function Counter(props)
- render() {
- return (
- <div>
- <h1>计数器h1>
- <h2>{props.数据名}h2>
- div>
- )
- }
- }
修改仓库数据,依然是通过 dispatch() 方法来触发修改操作。
在组件中,只要和仓库关联过,就能在 props 上直接获取到 dispatch 方法。因此,在组件中可以直接通过 props.dispatch() 方法来触发修改数据的操作。
- import React,{useEffect,useState} from 'react'
- import store from '../redux/store'
- import {incrementAction} from '../redux/actions'
- import { connect } from 'react-redux';
-
- function Counter(props) {
- const increment=()=>{
- props.dispatch(incrementAction(3))
- }
- return (
- <div>
- <h1>计数器:{props.num}h1>
- <button onClick={()=>{increment()}}>+button>
- div>
- )
- }
- const mapStateToProps = (state) => {
- console.log(state); // 仓库中所有的数据
- return{
- num:state.num
- }
- }
- export default connect(mapStateToProps)(Counter)
针对 React 中的函数组件,React-Redux 中也提供了第三方的 Hook。
通过调用 useSelector 方法,并传递一个回调函数作为参数,我们可以在这个回调函数中获取到仓库中的 state。
- import { useSelector } from 'react-redux'
- useSelector((state) => {
- console.log(state); // 仓库中所有的数据
- })
然后我们可以在回调函数中,将我们需要使用的数据 return 出来,然后用一个变量来接收:
- const data = useSelector((state) => {
- return 数据;
- })
后续组件要使用数据,就可以直接通过变量进行数据的访问了。
调用 useDispatch() 方法,可以直接获取到 dispatch() 方法。
- import { useDispatch } from 'react-redux'
- export default fucntion Test() {
- const dispatch = useDispatch();
- return (
- )
- }
如果组件中使用 Hook 来获取 dispatch 方法的话,就不再需要使用 connect 来对组件和仓库进行关联了。
获取到 dispatch 方法后,后续的使用就和 props.dispatch 的使用一致。
关键代码
- import React from 'react'
- import {useDispatch,useSelector} from 'react-redux'
- import {incrementAction} from '../../redux/action'
-
-
- export default function Counter(props) {
- const num=useSelector((state)=>{
- return state.num
- })
- const dispatch=useDispatch()
- const increment=()=>{
- dispatch(incrementAction(3))
- }
- return (
- <div>
- <h1>计数器:{num}h1>
- <button onClick={()=>{increment()}}>+button>
- div>
- )
- }
当项目越来越大的时候,需要管理的数据也会越来越多,如果所有的数据都由一个reducer管理的话,则这个reducer肯定会变得非常的臃肿,且难以维护。所以有必要对reducer做一个拆分,不同功能模块的数据切片,由不同的reducer来管理。假设现在有两个模块,账户管理模块和商品管理模块,每个模块都有数据需要管理
- import {combineReducers} from 'redux'
- import counterReducer from './counterReducer'
- export default combineReducers({
- counter:counterReducer
- })
注意:调用时候需要使用使用到切片的名字才能访问到,比如
- import React from 'react'
- import {useSelector} from 'react-redux'
- export default function Counter(props) {
- const count=useSelector((state)=>{
- console.log(state);
- return state.counter.num //state.切片名的key.num
- })
- return (
- <div>
- <h1>计数器:{count}h1>
- div>
- )
- }