react中的状态管理-redux
- 英文文档: https://redux.js.org/
- 中文文档: http://www.redux.org.cn/
- Github: https://github.com/reactjs/redux
一、redux
1、redux是什么?
1. redux是一个专门用于做状态管理
的JS库;
2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用;
3. 作用: 集中式管理react应用中多个组件共享的状态。
2、什么时候需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
3、redux工作流程
4、redux的三个核心概念
4.1、action
- 动作的对象
- 包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性 - 例子:{ type: ‘ADD_STUDENT’,data:{name: ‘tom’,age:18} }
4.2、reducer
- 用于初始化状态、加工状态。
- 加工时,根据旧的state和action, 产生新的state的
纯函数
。
4.3、store
- 将state、action、reducer联系在一起的对象
- 如何得到此对象?
- import {createStore} from ‘redux’
- import reducer from ‘./reducers’
- const store = createStore(reducer)
- 此对象的功能?
- getState(): 得到state
- dispatch(action): 分发action, 触发reducer调用, 产生新的state
- subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
5、redux的核心API
5.1、createstore()
作用:创建包含指定reducer的store对象
5.2、store对象
- 作用: redux库最核心的管理对象
- 它内部维护着:
- state
- reducer
- 核心方法:
- getState(),获取状态
- dispatch(action),派发行为
- subscribe(listener),监测redux的状态变化
- 具体编码:
- store.getState()
- store.dispatch({type:‘INCREMENT’, number})
- store.subscribe(render)
5.3、applyMiddleware()
作用:应用上基于redux的中间件(插件库)
5.4、combineReducers()
作用:合并多个reducer函数
6、异步action
- redux默认是不能进行异步处理的,
- 某些时候应用中需要在redux中执行异步任务(ajax, 定时器)
- 使用异步中间件:npm install --save redux-thunk
7、redux的应用(求和案例)
运行结果
文件目录结构
1、创建store,store.js
/*该文件专门用于暴露一个store对象,整个应用只有一个store对象*/
//1、引入createStore,专门用于创建redux中最为核心的store对象,applyMiddleware:执行中间件
import {createStore, applyMiddleware} from "redux";
//2、引入为count组件服务的reducer
import countReducer from "./count_reducer";
//3、引入redux-thunk,用于支持异步action
import thunk from "redux-thunk";
//4、暴露store
export default createStore(countReducer, applyMiddleware(thunk));
2、count_reducer
/*
* 1、初始化状态
* 2、加工状态
* 该文件用于创建一个为count组件服务的reducer,reducer的本质就是一个函数;
* reducer会接收到两个参数,分别为:之前的状态(preState),行为(action)
* */
import {INCREMENT,DECREMENT} from "./constant";
const initState = 7;
function countReducer(preState = initState, action) {
console.log(preState, action);
const {type, data} = action;
switch (type) {
case INCREMENT://加
return preState + data;
case DECREMENT://减
return preState - data;
default:
return preState
}
}
export default countReducer
3、count_action.js
/*该文件专门为count组件生成action对象*/
import {INCREMENT, DECREMENT} from "./constant";
import store from "./store";
//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
//所谓的异步action,就是指action的值为函数,需要使用一个中间插件,
// 异步action中一般都会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction = (data, time) => {
//返回的是一个函数
return () => {
setTimeout(() => {
store.dispatch(createIncrementAction(data))
}, time)
}
}
4、constant.js
/*
* 该模块是用于定义action对象中type类型的常量值,
* 便于管理的同时,当值程序员单词写错,降低单词拼写出错率*/
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
5、count/index.js
import React, {Component} from 'react';
//1、引入store,用于获取redux中保存的状态
import store from "../../redux/store";
//2、引入actionCreator,专门用于创建对象
import {createDecrementAction, createIncrementAction, createIncrementAsyncAction} from "../../redux/count_action";
export default class Count extends Component {
/* componentDidMount() {
//5、组件挂载完成后监测redux中的状态变化,只要变化就调用render渲染页面
store.subscribe(() => {
this.setState({})
})
}*/
increment = () => {
const {value} = this.selectNumber;
// 4、通知redux加value(store.dispatch进行行为派发)
store.dispatch(createIncrementAction(value * 1))
}
decrement = () => {
const {value} = this.selectNumber;
store.dispatch(createDecrementAction(value * 1))
}
//奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber;
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch(createIncrementAction(value * 1))
}
}
//异步加
incrementAsync = () => {
const {value} = this.selectNumber;
store.dispatch(createIncrementAsyncAction(value * 1, 500))
}
render() {
return (
<div>
{/*3、store.getState()获取状态*/}
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select><br/><br/>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
监测redux的状态变化也可以在index.js入口文件中完成
import App from './App';
import store from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);
//监测redux状态变化的api写在这里,就不用每个组件都需要调用store.subscribe
// 检测redux 状态 发生变化 整个app 都调用 render() 作用是不用重复写 componentDidMount
store.subscribe(() => {
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);
})
6、App.js
在App.js文件中引入组件count/index.js
二、react-redux
1、react-Redux将所有组件分成两大类
- UI组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
- 容器组件
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
2、react-redux相关的API
2.1、Provider
让所有组件都可以得到state数据
root.render(
/*Provider让所有组件都可以得到state数据*/
<Provider store={store}>
<App/>
</Provider>
);
2.2、connect
用于包装 UI 组件生成容器组件
export const CountContainer = connect(
mapStateToProps,
mapDispatchToProps)(CountUI)
2.3、 mapStateToprops
将外部的数据(即state对象)转换为UI组件的标签属性
const mapStateToProps = (state) => {
//返回值是一个对象
return {count: state}
}
2.4、mapDispatchToProps
将分发action的函数转换为UI组件的标签属性
3、react-redux的应用(求和案例)
-
运行效果
-
文件目录结构
3.components/count/index.js
import React, {Component} from 'react';
export default class Count extends Component {
increment = () => {
const {value} = this.selectNumber;
this.props.jia(value * 1)
}
decrement = () => {
const {value} = this.selectNumber;
this.props.jian(value * 1)
}
//奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber;
if (this.props.count % 2 !== 0) {
this.props.jia(value * 1)
}
}
//异步加
incrementAsync = () => {
const {value} = this.selectNumber;
this.props.jiaAsync(value * 1,500)
}
render() {
console.log('UI组件', this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select><br/><br/>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
- containers/count/index.jsx
//这是一个容器组件
//1、引入connect用于连接UI组件与redux
import {connect} from "react-redux";
//2、引入Count的UI组件
import CountUI from "../../components/count";
//3、引入action
import {createIncrementAction, createDecrementAction, createIncrementAsyncAction} from "../../redux/count_action";
/*
* 1、mapStateToProps函数的返回是一个对象
* 2、返回的对象中key就作为状态传递给UI组件props的key,value就作为传递给UI组件props的值value
* 3、mapStateToProps用于传递状态*/
const mapStateToProps = (state) => {
//返回值是一个对象
return {count: state}
}
/*
* 1、mapDispatchToProps函数的返回是一个对象
* 2、返回的对象中key就作为状态传递给UI组件props的key,value就作为传递给UI组件props的值value
* 3、mapStateToProps用于传递操作状态的方法*/
const mapDispatchToProps = (dispatch) => {
return {
jia: (data) => {
dispatch(createIncrementAction(data))
},
jian: (data) => {
dispatch(createDecrementAction(data))
},
jiaAsync: (data, time) => {
dispatch(createIncrementAsyncAction(data, time))
}
}
}
//4、connect()()创建并暴露Count的容器组件
//connect()第一次调用的时候必须传入两个参数,两个参数必须是函数
export const CountContainer = connect(
mapStateToProps,
mapDispatchToProps)(CountUI)
/*简写
1. */
/*export const CountContainer = connect(
state => ({count: state}),
/!* //mapDispatchToProps的一般写法
dispatch => ({
jia: data => dispatch(createIncrementAction(data)),
jian: data => dispatch(createDecrementAction(data)),
jiaAsync: (data, time) => dispatch(createIncrementAsyncAction(data, time))
})*!/
//mapDispatchToProps的简写
{
//reaact-redux会自动进行派发
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction
}
)(CountUI)*/
- store.js
/*该文件专门用于暴露一个store对象,整个应用只有一个store对象*/
//1、引入createStore,专门用于创建redux中最为核心的store对象,applyMiddleware:执行中间件
import {createStore, applyMiddleware} from "redux";
//2、引入为count组件服务的reducer
import countReducer from "./count_reducer";
//3、引入redux-thunk,用于支持异步action
import thunk from "redux-thunk";
//4、暴露store
export default createStore(countReducer, applyMiddleware(thunk));
- count_reducer
/*
* 1、初始化状态
* 2、加工状态
* 该文件用于创建一个为count组件服务的reducer,reducer的本质就是一个函数;
* reducer会接收到两个参数,分别为:之前的状态(preState),行为(action)
* */
import {INCREMENT,DECREMENT} from "./constant";
const initState = 7;
function countReducer(preState = initState, action) {
console.log(preState, action);
const {type, data} = action;
switch (type) {
case INCREMENT://加
return preState + data;
case DECREMENT://减
return preState - data;
default:
return preState
}
}
export default countReducer
- count_action.js
/*该文件专门为count组件生成action对象*/
import {INCREMENT, DECREMENT} from "./constant";
import store from "./store";
//同步action,就是指action的值为object类型的一般对象
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
//所谓的异步action,就是指action的值为函数,需要使用一个中间插件,
// 异步action中一般都会调用同步action,异步action不是必须要用的
export const createIncrementAsyncAction = (data, time) => {
//返回的是一个函数
return () => {
setTimeout(() => {
store.dispatch(createIncrementAction(data))
}, time)
}
}
- constant.js
/*
* 该模块是用于定义action对象中type类型的常量值,
* 便于管理的同时,当值程序员单词写错,降低单词拼写出错率*/
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
- App.js
//1、引入容器里面的CountContainer组件
import {CountContainer} from "./containers/count";
import './App.css';
function App() {
return (
<div className="App">
<CountContainer/>
</div>
);
}
export default App;
- index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import store from "./redux/store";
import {Provider} from "react-redux"
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
/*Provider让所有组件都可以得到state数据*/
<Provider store={store}>
<App/>
</Provider>
);
将UI组件和容器组件结合到一起
import {connect} from "react-redux";
import React, {Component} from 'react';
import {createIncrementAction, createDecrementAction, createIncrementAsyncAction} from "../../redux/count_action";
//定义UI组件
class Count extends Component {
increment = () => {
const {value} = this.selectNumber;
this.props.jia(value * 1)
}
decrement = () => {
const {value} = this.selectNumber;
this.props.jian(value * 1)
}
//奇数再加
incrementIfOdd = () => {
const {value} = this.selectNumber;
if (this.props.count % 2 !== 0) {
this.props.jia(value * 1)
}
}
//异步加
incrementAsync = () => {
const {value} = this.selectNumber;
this.props.jiaAsync(value * 1, 500)
}
render() {
console.log('UI组件', this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select><br/><br/>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
export const CountContainer = connect(
state => ({count: state}),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction
}
)(Count)
4、react-redux数据共享案例
效果:
文件目录:
containers/count/index.jsx
import {connect} from "react-redux";
import React, {Component} from 'react';
import {createIncrementAction, createDecrementAction, createIncrementAsyncAction} from "../../redux/actions/count";
class Count extends Component {
increment = () => {
const {value} = this.selectNumber;
this.props.increment(value * 1)
}
decrement = () => {
const {value} = this.selectNumber;
this.props.decrement(value * 1)
}
incrementIfOdd = () => {
const {value} = this.selectNumber;
if (this.props.count % 2 !== 0) {
this.props.increment(value * 1)
}
}
incrementAsync = () => {
const {value} = this.selectNumber;
this.props.incrementAsync(value * 1, 500)
}
render() {
console.log('UI组件', this.props);
return (
<div>
<h1>我是count组件</h1>
<h1>当前求和为:{this.props.count}</h1>
<h1>下方组件总人数为:{this.props.personTotal}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select><br/><br/>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
export const CountContainer = connect(
state => ({
count: state.countReducer,
personTotal: state.personReducer.length
}),
{
increment: createIncrementAction,
decrement: createDecrementAction,
incrementAsync: createIncrementAsyncAction
}
)(Count)
containers/person/index.jsx
import React, {Component} from 'react';
import {nanoid} from "nanoid"
import {connect} from "react-redux";
import {createAddPersonAction} from "../../redux/actions/person"
class Person extends Component {
addPerson = () => {
const name = this.nameNode.value;
const age = this.ageNode.value;
const personObj = {id: nanoid(), name, age};
console.log(personObj);
this.props.addOnePerson(personObj);
this.nameNode.value = "";
this.ageNode.value = "";
}
render() {
return (
<div>
<h1>我是person组件</h1>
<h1>上方组件的和为:{this.props.sum}</h1>
<input ref={c => this.nameNode = c} type="text" placeholder="输入名字"/>
<input ref={c => this.ageNode = c} type="text" placeholder="输入年龄"/>
<button onClick={this.addPerson}>添加</button>
<ul>
{this.props.person.map((item) => {
return (<li key={item.id}>名字:{item.name}----年龄:{item.age}</li>)
})}
</ul>
</div>
)
}
}
export const PersonContainer = connect(
state => ({
person: state.personReducer,
sum: state.countReducer
}),//映射状态
{addOnePerson: createAddPersonAction}
)(Person)
redux/actions/count.js
import {INCREMENT, DECREMENT} from "../constant";
import store from "../store";
export const createIncrementAction = data => ({type: INCREMENT, data})
export const createDecrementAction = data => ({type: DECREMENT, data})
export const createIncrementAsyncAction = (data, time) => {
return () => {
setTimeout(() => {
store.dispatch(createIncrementAction(data))
}, time)
}
}
redux/actions/person.js
import {ADD_PERSON} from "../constant";
//创建增加一个人的动作对象
export const createAddPersonAction=personObj=>({type:ADD_PERSON,data:personObj})
redux/reducers/count.js
import {INCREMENT,DECREMENT} from "../constant";
function countReducer(preState = 7, action) {
console.log(preState, action);
const {type, data} = action;
switch (type) {
case INCREMENT://加
return preState + data;
case DECREMENT://减
return preState - data;
default:
return preState
}
}
export default countReducer
redux/reducers/person.js
import {ADD_PERSON} from "../constant"
export default function personReducer(prevState = [{id: "001", name: "tom", age: 12}], action) {
const {type, data} = action;
switch (type) {
case ADD_PERSON:
return [data, ...prevState];
default:
return [...prevState]
}
}
redux/reducers/index.js:用于汇总多个reducer
//该文件用于汇总所有reducer
//引入combineReducers,用于汇总对个reducer
import {combineReducers} from "redux";
import countReducer from "./count";
import personReducer from "./person";
//合并reducer,保存的总状态是一个对象
const allReducer = combineReducers({
countReducer, personReducer
})
export default allReducer;
redux/costant.js
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
export const ADD_PERSON="addPerson"
redux/store.js
/*该文件专门用于暴露一个store对象,整个应用只有一个store对象*/
//1、引入createStore,专门用于创建redux中最为核心的store对象,applyMiddleware:执行中间件
import {createStore, applyMiddleware} from "redux";
//可以在浏览器中使用插件观察redux的状态变化
import {composeWithDevTools} from "redux-devtools-extension";
//2、引入汇总后的reducer
import allReducer from "./reducers";
//3、引入redux-thunk,用于支持异步action
import thunk from "redux-thunk";
//4、暴露store
export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)));
App.js
import {CountContainer} from "./containers/count";
import {PersonContainer} from "./containers/person";
import './App.css';
function App() {
return (
<div className="App">
<CountContainer/>
<hr/>
<PersonContainer/>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import store from "./redux/store";
import {Provider} from "react-redux"
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
/*Provider包裹App,Provider让所有组件都可以得到state数据*/
<Provider store={store}>
<App/>
</Provider>
);