06-React状态管理 Redux(工作流程, 核心概念, 求和案例, 异步Action, React-Redux, 多状态管理, 纯函数, 高阶函数, Redux开发者工具)
Redux
简介
其实就是一个集中的状态管理技术, 类似于VueX, 以及后端的分布式配置中心, 在前端的文章里提后端,是不是不太好~, 但是能学习这个技术的人, 从简短的一句话中应该就已经简单的了解了这个技术,以及它的使用情况, 我就不过多写概念了, 主要写使用方式
Redux工作流程
三个核心概念
Action
- 动作对象
- 包含两个属性
字段 |
作用 |
数据类型 |
是否唯一 |
是否必填 |
type |
标识属性 |
字符串 |
是 |
是 |
data |
数据属性 |
任意 |
否 |
否 |
{ "type":"add", "data:":{ "name":"flower", "age":18 } }
Reducer
- 用于初始化状态, 加工状态
- 加工时, 依据旧的state和action,产生新的state的纯函数
Store
- 将state, action, reducer 联系在一起的对象
- 如何获取:
import {createStore} from 'redux' import reducer from './reducers' const store = createStore(reducer)
- 此对象的功能
函数 |
参数 |
作用 |
getState() |
无 |
获取state |
dispatch(action) |
action对象 |
分发action,处罚reducer调用,产生新的state |
subscribe(listener) |
listener对象 |
注册监听,当产生新的state时,自动调用 |
添加依赖
yarn add redux
求和案例
React版本
import React, {Component} from 'react'; class Count extends Component { state = { count: 0 } render() { return ( <div style={{textAlign: 'center'}}> <h1>当前求和为:{this.state.count}</h1> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) let {count} = this.state if (type === 'add') { count = count + value this.setState({count}) return } if (type === 're') { count = count - value this.setState({count}) return } if (type === 'j') { if (count % 2 !== 0) { count = count + value this.setState({count}) } return; } if (type === 'async') { count = count + value setTimeout(() => { this.setState({count}) }, 2000) } } } } export default Count;
Redux精简版
创建store.js
/** * 1: 引入createStore * 2: 引入为自定义组件服务的reducer * 3: 对外暴露store */ import {legacy_createStore as createStore} from 'redux' import countReducer from './count_reducer' export default createStore(countReducer)
创建count_reducer.js
/** * 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数 * 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action) */ const initValue = 0 export default function countReducer(preState = initValue, action) { const {type, data} = action if (type === 'add') return preState + data if (type === 're') return preState - data return preState }
使用redux改造原有Count组件
import React, {Component} from 'react'; import store from "../../redux/count/store"; class Count extends Component { componentDidMount() { // 检测Redux中状态的变化, 只要变化, 就调用Render store.subscribe(()=>{ // 调用刷新页面函数, 啥也不干就 重新渲染一下render this.forceUpdate() }) } render() { return ( <div style={{textAlign: 'center'}}> <h1>当前求和为:{store.getState()}</h1> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) if (type === 'add') { store.dispatch({type:'add',data:value}) } if (type === 're') { store.dispatch({type:'re',data:value}) } if (type === 'j') { if (store.getState() % 2 !== 0) { store.dispatch({type:'add',data:value}) } } if (type === 'async') { setTimeout(() => { store.dispatch({type:'add',data:value}) }, 2000) } } } } export default Count;
函数小结
函数 |
参数 |
作用 |
getState() |
无 |
获取state |
dispatch(action) |
action对象 |
分发action,处罚reducer调用,产生新的state |
subscribe(listener) |
listener对象 |
注册监听,当产生新的state时,自动调用 |
Redux完整版
对面上的案例进行改造
新增常量constant.js
/** * 该模块用于定义action对象的type类型, 统一管理常量值 */ export const ADD = 'add'; export const RE = 're';
改造reducer, 引入常量
/** * 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数 * 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action) */ import {ADD, RE} from "./constant"; const initValue = 0 export default function countReducer(preState = initValue, action) { const {type, data} = action console.log(preState, action) if (type === ADD) return preState + data if (type === RE) return preState - data return preState }
新增count_action.js
/** * 该文件专门为Count组件生成Action对象 */ import {ADD, RE} from './constant' export const creatAddAction = data => ({type: ADD, data}) export const creatReAction = data => ({type: RE, data})
使用action函数替换传入的对象
import React, {Component} from 'react'; import store from "../../redux/count/store"; import {creatAddAction, creatReAction} from "../../redux/count/count_action"; class Count extends Component { componentDidMount() { // 检测Redux中状态的变化, 只要变化, 就调用Render store.subscribe(()=>{ // 调用刷新页面函数, 啥也不干就 重新渲染一下render this.forceUpdate() }) } render() { return ( <div style={{textAlign: 'center'}}> <h1>当前求和为:{store.getState()}</h1> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) if (type === 'add') { store.dispatch(creatAddAction(value)) } if (type === 're') { store.dispatch(creatReAction(value)) } if (type === 'j') { if (store.getState() % 2 !== 0) { store.dispatch(creatAddAction(value)) } } if (type === 'async') { setTimeout(() => { store.dispatch(creatAddAction(value)) }, 2000) } } } } export default Count;
异步Action
在调用dispatch的时候传入的action对象, 如果对象是Object, 那么就是同步的action, 如果是函数, 那么就是异步的action

添加依赖
yarn add redux-thunk
编写异步函数
/** * 该文件专门为Count组件生成Action对象 */ import {ADD, RE} from './constant' // 同步Action, 返回值为Object export const creatAddAction = data => ({type: ADD, data}) export const creatReAction = data => ({type: RE, data}) // 所谓的异步Action,就是指所谓的返回值是函数 export const creatAsyncAddAction = (data,timeout) => dispatch => setTimeout(()=> dispatch(creatAddAction(data)) ,timeout)
设置Store支持函数
/** * 1: 引入createStore * 2: 引入为自定义组件服务的reducer * 3: 对外暴露store */ import {legacy_createStore as createStore, applyMiddleware} from 'redux' import countReducer from './count_reducer' // 用于支持异步Action import thunk from "redux-thunk"; export default createStore(countReducer,applyMiddleware(thunk))
修改Count组件
if (type === 'async') { // setTimeout(() => { store.dispatch(creatAsyncAddAction(value, 500)) // }, 2000) }
去除setTimeout, 交由Store支持
React-Redux
简介
一看名称就是react自己写的, 应该是封装了redux,方便使用集成
工作流程
其实就是在Count组件外面包了一层用于和Redux做交互的容器, 用于获取数据和交互
添加依赖
yarn add react-redux
使用react-redux实现求和案例
修改Count组件
import React, {Component} from 'react'; class Count extends Component { render() { const {count} = this.props return ( <div style={{textAlign: 'center'}}> <h1>当前求和为:{count}</h1> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) const {count, add, re, addAsync} = this.props if (type === 'add') { add(value) } if (type === 're') { re(value) } if (type === 'j') { if (count % 2 !== 0) { add(value) } } if (type === 'async') { addAsync(value, 500) } } } } export default Count;
定义容器组件
// 引入Count组件 import CountUI from "../../components/Count"; // 引入connect 用于连接UI和store import {connect} from 'react-redux' import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action"; // 函数的返回值作为状态传递给了UI组件 const mapStateToProps = (state) => { return { count: state } } // 函数的返回值作为函数操作传递给了UI组件 const mapDispatchToProps = (dispatch) => { return { add: (data) => { dispatch(creatAddAction(data)) }, addAsync: (data,timeout) => { dispatch(creatAsyncAddAction(data,timeout)) }, re: (data) => { dispatch(creatReAction(data)) } } } // 创建并暴露一个Count的容器组件 export default connect(mapStateToProps, mapDispatchToProps)(CountUI);
修改APP组件
import React, {Component} from 'react'; import Count from "./containers/Count"; import store from "./redux/count/store"; class App extends Component { render() { return ( <div> {/* 传入store对象 */} <Count store={store}/> </div> ); } } export default App;
修改index组件
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import store from "./redux/count/store"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> ); // 订阅store状态更新 store.subscribe(()=>{ root.render(<App/>) }) // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
优化求和案例
优化容器组件
// 引入Count组件 import CountUI from "../../components/Count"; // 引入connect 用于连接UI和store import {connect} from 'react-redux' import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action"; // 创建并暴露一个Count的容器组件 export default connect( state => ({ count: state }), { add: creatAddAction, addAsync: creatAsyncAddAction, re: creatReAction } )(CountUI);
原理
原来的是一个函数, 优化为一个对象, 直接返回一个action, 然后react-redux会自动调用dispatch进行action分发
优化Index组件
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <App /> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
可以将原来添加的监听删除了, 因为react-redux会自动监听redux的状态变化, 并重新渲染render
优化Store传入
将原有的APP组件中传入的store删除
import React, {Component} from 'react'; import Count from "./containers/Count"; class App extends Component { render() { return ( <div> {/* 传入store对象 */} <Count/> </div> ); } } export default App;
修改Index组件
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // 引入store + 提供者 import store from "./redux/count/store"; import {Provider} from "react-redux"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
这样就可以将store传递给所有需要store的容器组件了
将UI组件和容器组件整合
import React, {Component} from 'react'; // 引入connect 用于连接UI和store import {connect} from 'react-redux' import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action"; class Count extends Component { render() { const {count} = this.props return ( <div style={{textAlign: 'center'}}> <h1>当前求和为:{count}</h1> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) const {count, add, re, addAsync} = this.props if (type === 'add') { add(value) } if (type === 're') { re(value) } if (type === 'j') { if (count % 2 !== 0) { add(value) } } if (type === 'async') { addAsync(value, 500) } } } } // 创建并暴露一个Count的容器组件 export default connect( state => ({ count: state }), { add: creatAddAction, addAsync: creatAsyncAddAction, re: creatReAction } )(Count);
这样就不用增加工作量了, 每次写容器都得写两个文件了
多组件状态管理+数据交互
上面一直在用一个Count组件玩, 并没有涉及到组件交互和多组件状态存储, 下面就来玩一下
完整案例
Index组件
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // 引入store + 提供者 import store from "./redux/count/store"; import {Provider} from "react-redux"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); reportWebVitals();
App组件
import React, {Component} from 'react'; import Count from "./containers/Count"; import Person from "./containers/Person"; class App extends Component { render() { return ( <div> {/* 传入store对象 */} <Count/> <hr/> <Person /> </div> ); } } export default App;
Count容器组件+UI组件
import React, {Component} from 'react'; // 引入connect 用于连接UI和store import {connect} from 'react-redux' import {creatAddAction, creatAsyncAddAction, creatReAction} from "../../redux/count/count_action"; class Count extends Component { render() { const {count,persons} = this.props return ( <div style={{textAlign: 'center'}}> <h2>Count组件</h2> <h3>下方Person组件的值为</h3> <ul> <li>姓名-年龄</li> { persons.map(person => { return <li key={person.id}>{person.name+"-"+person.age}</li> }) } </ul> <h4>当前求和为:{count}</h4> <select ref={c => this.count = c} style={{width: '50px'}}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button style={{marginLeft: '10px'}} onClick={this.sum('add')}>+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('re')}>-</button> <button style={{marginLeft: '10px'}} onClick={this.sum('j')}>当前求和为奇数+</button> <button style={{marginLeft: '10px'}} onClick={this.sum('async')}>异步+</button> </div> ); } sum = type => { return event => { let {value} = this.count value = parseInt(value) const {count, add, re, addAsync} = this.props if (type === 'add') { add(value) } if (type === 're') { re(value) } if (type === 'j') { if (count % 2 !== 0) { add(value) } } if (type === 'async') { addAsync(value, 500) } } } } // 创建并暴露一个Count的容器组件 export default connect( state => ({ count: state.count, persons: state.persons }), { add: creatAddAction, addAsync: creatAsyncAddAction, re: creatReAction } )(Count);
Person容器组件+UI组件
import React, {Component} from 'react'; import {connect} from "react-redux"; import {createAddPersonAction} from "../../redux/count/person_action"; import {nanoid} from "nanoid"; class Person extends Component { render() { const {persons,count} = this.props console.log(persons); return ( <div style={{textAlign: 'center'}}> <h2>Person组件</h2> <h3>Count组件求和为:{count}</h3> <input ref={c => this.name = c} type="text" placeholder={"请输入姓名"}/> <input ref={c => this.age = c} type="text" placeholder={"请输入年龄"}/> <button onClick={this.addPerson}>添加</button> <ul> <li>姓名-年龄</li> { persons.map(person => { return <li key={person.id}>{person.name+"-"+person.age}</li> }) } </ul> </div> ); } addPerson = () => { const name = this.name.value const age = this.age.value const {addPerson} = this.props const person = { id:nanoid(), name, age } console.log(person); addPerson(person) } } export default connect(state=>({ persons:state.persons, count:state.count }),{ addPerson: createAddPersonAction })(Person)
常量JS
/** * 该模块用于定义action对象的type类型, 统一管理常量值 */ export const ADD = 'add'; export const RE = 're'; export const ADD_PERSON = 'add_person';
StoreJS
/** * 1: 引入createStore * 2: 引入为自定义组件服务的reducer * 3: 对外暴露store */ import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux' import countReducer from './count_reducer' import personReducer from './person_reducer' // 用于支持异步Action import thunk from "redux-thunk"; // 使用combineReducers合并多个Reducer // 字段:reducer const allReducers = combineReducers({ count:countReducer, persons:personReducer }) export default createStore(allReducers,applyMiddleware(thunk))
Count Create Action
/** * 该文件专门为Count组件生成Action对象 */ import {ADD, RE} from './constant' // 同步Action, 返回值为Object export const creatAddAction = data => ({type: ADD, data}) export const creatReAction = data => ({type: RE, data}) // 所谓的异步Action,就是指所谓的返回值是函数 export const creatAsyncAddAction = (data,timeout) => dispatch => setTimeout(()=> dispatch(creatAddAction(data)) ,timeout)
Person Create Action
import {ADD_PERSON} from "./constant";
export const createAddPersonAction = person => ({type:ADD_PERSON,data:person})
Count Reducer
/** * 1: 该文件是用于创建一个为Count组件服务的reducer, reducer的本质就是一个函数 * 2: reducer函数会接收到两个参数, 分别为: 之前的状态(preState), 动作对象(action) */ import {ADD, RE} from "./constant"; const initValue = 0 export default function countReducer(preState = initValue, action) { const {type, data} = action console.log(preState, action) if (type === ADD) return preState + data if (type === RE) return preState - data return preState }
Person Reducer
import {ADD_PERSON} from "./constant"; const initValue = [] export default function personHandler(preState = initValue, action) { const {type, data} = action if (type === ADD_PERSON) { return [data, ...preState] } return preState }
效果
实现了多组件Store存储,并交互数据
纯函数
- 一些特别的函数,只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下的约束
- 不得改写参数数据
- 不会产生任何副作用, 例如网络请求, 输入和输出设备
- 不能调用Date.now()或者Math,random等不纯的方法
- redux的reducer函数必须是一个纯函数
高阶函数
- 理解: 一类特别的函数
- 情况1: 参数是函数
- 情况2: 返回是函数
- 常见的高阶函数:
- 定时器设置函数
- 数组的forEach()/map()/filter()/reduce()/find()/bind()
- promise
- react-redux中的connect函数
- 作用: 能实现更加动态, 更加可扩展的功能
Redux开发者工具
应为我也不能上Google只能粘贴一个文件夹了
添加依赖
yarn add redux-devtools-extension
修改StoreJs
/** * 1: 引入createStore * 2: 引入为自定义组件服务的reducer * 3: 对外暴露store */ import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux' import countReducer from './count_reducer' import personReducer from './person_reducer' // 用于支持异步Action import thunk from "redux-thunk"; // 引入Redux开发者工具 import {composeWithDevTools} from 'redux-devtools-extension' // 使用combineReducers合并多个Reducer // 字段:reducer const allReducers = combineReducers({ count:countReducer, persons:personReducer }) // 调用 export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)))
效果
可以记录组件的action执行和值的变化
React项目打包部署
yarn build
E:\js\react_redux>yarn build yarn run v1.22.19 $ react-scripts build Creating an optimized production build... Compiled successfully. File sizes after gzip: 53.7 kB build\static\js\main.555f55e7.js 1.79 kB build\static\js\787.a95b438b.chunk.js 264 B build\static\css\main.e6c13ad2.css The project was built assuming it is hosted at /. You can control this with the homepage field in your package.json. The build folder is ready to be deployed. You may serve it with a static server: yarn global add serve serve -s build Find out more about deployment here: https://cra.link/deployment Done in 11.82s. E:\js\react_redux>
打包完成后会生成一个build文件夹, 我记得Vue应该是dist
npm -i serve -g
全局安装serve 当然, 真的上线也不是这么玩的, 一般前端上线都是挂在Nginx下的, 这里这个就是为了本地启动一个服务
E:\js\react_redux>npm i serve -g npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead. npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead. added 89 packages, and audited 90 packages in 21s 23 packages are looking for funding run `npm fund` for details found 0 vulnerabilities E:\js\react_redux>
进入到项目文件夹执行 serve build(文件名) 就可以启动一个服务
这样就可以访问了
并且React的图标也变为线上模式了,而不是debug模式了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
2021-08-23 11-SpringCloud Hystrix
2021-08-23 10-SpringCloud OpenFeign