中文文档:https://cn.redux.js.org/
Github: https://github.com/reduxjs/redux
学习的视频是尚硅谷react教程
1、redux理解
redux是专门做状态管理的JS库,可以在react、angular、vue等项目中使用,集中管理 react应用中多个组件共享的状态。Redux 提供的模式和工具使你更容易理解应用程序中的状态何时、何地、为什么、state 如何被更新,以及当这些更改发生时应用程序逻辑将如何表现
1.1使用情况
- 某个组件的状态需要让其他组件随时可以拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用,如果不适用比较吃力才考虑使用
1.2 redux工作流程和核心概念
React Components: react组件
Action Creators: 行为创建器。
action就是动作对象,包括本次动作类型
type和本次操作的数据data。比如在计数器中,x2这个动作,“X”就是动作类型。“2”就是操作的数据。 dispatch(action):分发action对象
Store: 状态仓库(指挥者)
dispatch函数把action交给Store,再将(previousState, action)传给Reducers
将state、action、reducer联系在一起的对象
如何得到此对象??
import {createStore} from ‘redux’
import reducer from ‘./reducers’
const store = createStore(reducer)
此对象的功能??
getState(): 得到state
dispacth(action): 分发action,触发reducer,调用新的state
subcribe(listener):注册监听,当产生了新的state时自动调用
Reducers:处理状态(执行者)
执行完返回 newState给store,Store使用getState()获取最新状态交给React Components组件
用于初始化状态、加工状态,加工时,根据旧的state和action,产生新的state的纯函数
2、使用redux编写应用
2.1 准备
首先创建新的一个基于脚手架的React应用,可以删除public和src文件夹下的文件,自己按需创建,整个代码目录就会比较简单。在public文件夹下新建index.html文件,写好基本的结构;在src文件夹下新建app.jsx文件和index.js文件,在app.jsx中编写组件,在index.js渲染。
// app.js文件
import React, {Component} from "react";
export default class App extends Component {
render() {
return (
<div>
这是APP
</div>
)
}
}
// index.js文件
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./app";
ReactDOM.render(<App/>, document.getElementById('root'))
最后在项目所在的目录下执行npm start命令,确定正常运行就可以往里面编写需求代码了。
2.2 纯React编写求和案例
在src下面新建文件夹components,在此文件下新建组件并编写需求代码。
下面是components下面Count文件夹下index.jsx的代码,还没有使用redux,只是普通的react程序
import React, {Component} from "react";
export default class Count extends Component {
state ={
count : 0
}
// 加法
increment = () =>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({
count : count + value*1
})
}
//减法
decrement= () =>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({
count : count - value*1
})
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
const {count} = this.state
if(count % 2 !== 0){
this.setState({
count : count + value*1
})
}
}
//异步
incrementAsync = () =>{
const {value} = this.selectNumber
const {count} = this.state
setTimeout(()=>{
this.setState({
count : count + value*1
})
},500)
}
render() {
return (
<div>
<h1> 当前求和为:{this.state.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}> +</button>
<button onClick={this.decrement}> -</button>
<button onClick={this.incrementIfOdd}> 当前和为奇数再加</button>
<button onClick={this.incrementAsync}> 异步加</button>
</div>
)
}
}
2.3 mini版redux程序
下载安装redux,在终端下载npm add redux
首先,新建一个文件夹redux,存放所有与redux相关的代码。在redux文件夹新建store.js和count_reducer.js文件。在上面redux的工作流程中提到了这些关键概念。
// store.js文件
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer)
在count_reducer.js文件:
/*
该文件是用于创建一个为Count组件服务的reducer,本质就是一个函数
reducer函数的两个参数: 之前的状态(preState)和动作对象action
*/
const initState = 0
// 形参默认值
export default function countReducer(preState=initState, action) {
// 从action动作中获取type和data
//if (preState === undefined) preState = 0
const { type, data } = action
switch (type) {
case 'increment':
return preState + data;
case 'decrement':
return preState - data;
default:
//初始化
return preState
}
}
对应的,组件Count的程序更改为:
import React, {Component} from "react";
//引入store 用于获取redux中的状态
import store from "../../redux/store";
export default class Count extends Component {
//检测redux中状态的变化,只要变化就调用render
componentDidMount(){
store.subscribe(()=>{
// 重要redux任何状态就会调用该回调函数
this.setState({})
})
}
// 加法
increment = () =>{
const {value} = this.selectNumber
store.dispatch({type:'increment', data:value*1})
}
//减法
decrement= () =>{
const {value} = this.selectNumber
// 通知redux
store.dispatch({type:'decrement', data:value*1})
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
if(store.getState() % 2 !== 0){
store.dispatch({type:'increment', data:value*1})
}
}
//异步
incrementAsync = () =>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch({type:'increment', data:value*1})
},500)
}
render() {
return (
<div>
{/* 获取store的状态 */}
<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>
</select>
<button onClick={this.increment}> +</button>
<button onClick={this.decrement}> -</button>
<button onClick={this.incrementIfOdd}> 当前和为奇数再加</button>
<button onClick={this.incrementAsync}> 异步加</button>
</div>
)
}
}
总结一下: 先写好store.js(暴露store对象)和count_reducer.js文件(对action传递的动作类型和数据进行操作,初始化或者修改状态),再在组件中通过store.getState()获取状态,store.dispatch()执行动作, store.subscribe()订阅redux状态更改。
redux的Mini版
(1)去除count组件自身的状态
(2).src下建立:
——redux
————store.js
————count_reducer.js
(3) .store.js
引入redux中的legacy_createStore,创建store
.legacy_createStore调用时要传入一个为其服务的reducer
记得要暴露对象
(4).count_reducer.js
reducer本质是一个函数:接收preState,action,返回加工后的状态
reducer第一次被调用时,是store自动触发的,传递的preState是Undefined
(5) 在index.jx中检测store中状态的改变,一旦发生改变重新渲染
redux只负责管理状态,至于状态的改变驱动页面的展示,要自己写。
完整版只需要再新增count_action.js文件,专门创建action对象。另外可以将常用的变量统一写在constant.js中,便于后期管理和维护。
3、 异步action
一般对象类型的action称为同步action,值为函数的action称为异步action。
需要安装一个中间件:npm add redux-thunk
store.js文件
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk))
4、react-redux
!!! 这部分会很绕,是在redux版本上进行更改对比介绍的,最后面会给大家整理一个最终版本。个人认为中间优化的思路还是可以学习一下的。
由react团队出品的插件库,与react更适配
- 所有的UI组件都应该包裹在一个容器组件,是父子关系
- 容器组件是真正和UI组件打交道的,可以随意的使用redux的API
- UI组件中不能使用任何redux的API
- 容器组件会传给UI组件:(1)redux中所保存的状态 (2)用于操作状态的方法
- 容器传给UI状态和操作状态的方法均通过props传递
4.1 UI组件和容器组件
UI组件:一般放在components文件夹,不能使用任何的redux的api,只负责页面的呈现、交互
容器组件:一般放在container文件夹,负责和redux通信,将结果交给UI组件
(1)容器组件—connect
看上面那种图可知,容器组件起的作用就是连接UI组件和redux的,因此在容器组件在需要引入UI组件和redux,但是redux不需要自己直接引入,而是在App.jsx文件中通过props向容器组件中传入。
connect(mapStateToProps, mapDispatchToProps)(CountUI):靠rect-redux的connect函数,返回值也是一个函数 使用connect()()创建并暴露count容器组件。
mapStateToProps:
- mapStateToProps函数返回的是一个对象
- 返回的对象中key作为传递给UI组件props的Key,value就作为传递给UI组件的value
- mapStateToProps用于传递状态
mapDispatchToProps:
- mapDispatchToProps函数返回的一个对象
- 返回的对象中key作为传递给UI组件props的Key,Value就作为传递给UI组件的props的方法
- mapStateToProps用于传递操作状态的方法
注意:容器组件的store是通过props传进去的,而不是再容器组件中直接引入,如下面代码所示
mapDispatchToProps也可以是一个对象,下面精简版的代码中可以看到对象的写法
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store}/>
</div>
)
}
下面是src/containers/Count/index.jsx文件的内容:
/**
* Count的容器组件
*/
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入action
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
/* 映射状态
1.mapStateToProps函数返回的是一个对象
2.返回的对象中key作为传递给UI组件props的Key,value就作为传递给UI组件的value
3.mapStateToProps用于传递状态
*/
const mapStateToProps = (state)=>{
return {count: state}
}
/*映射操作状态的方法
1.mapDispatchToProps函数返回的一个对象
2.返回的对象中key作为传递给UI组件props的Key,Value就作为传递给UI组件的props的方法
3.mapStateToProps用于传递操作状态的方法
*/
// mapDispatchToProps函数传递操作方法key作为传递给UI组件props的key,Value就作为传递给UI组件的props的方法
const mapDispatchToProps = (dispatch)=>{
return {
//通知redux执行加法
increment:number=> dispatch(createIncrementAction(number)),
//通知redux执行减法
decrement:number=> dispatch(createDecrementAction(number)),
//通知redux执行异步
asynIncrement:(number,time)=> dispatch(createIncrementActionAsyn(number,time)),
}
}
// connect的返回值也是一个函数 使用connect()()创建并暴露count容器组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
精简版的代码:
import CountUI from '../../components/Count'
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
import {connect} from 'react-redux'
export default connect(
state=> ({count: state}),
{
increment: createIncrementAction
decrement: createDecrementAction
asynIncrement: createIncrementActionAsyn
},
))(CountUI)
(2)react-redux版的代码优化
src下的index.js
react-redux版本中,可以删去。容器组件已经具有检测状态变化的能力。
store.subscribe(()=>{
ReactDOM.render(<App/>, document.getElementById('root'))
})
index.js和App.jsx文件
上面说到,容器组件的store是由App.jsx传入的。当存在多个容器组件时,则需要对每一个容器组件进行传值。根据这点也可以进行优化。
需要在入口文件index.js中引入Provider组件
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./redux/store";
//引入Provider组件
import {Provider} from 'react-redux'
ReactDOM.render(
//App里所有组件都能收到store,只需要写入一次
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
整合UI组件和容器组件
当组件数目增多时,使用redux的组件数量会成倍增长。根据这点,将UI组件和容器组件整合来优化。
直接讲UI组件的代码复制,通过connect函数连接即可。
/**
* Count的容器组件
*/
// 整合UI和容器组件,不引入UI组件,直接在这个文件中写UI组件
import {createIncrementAction, createDecrementAction, createIncrementActionAsyn} from '../../redux/count_action'
import {connect} from 'react-redux'
import React, {Component} from "react"
//定义UI组件
class Count extends Component {
increment = () =>{
const {value} = this.selectNumber
// store.dispatch(createIncrementAction(value*1))
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.asynIncrement(value*1, 500)
// store.dispatch(createIncrementActionAsyn(value*1, 500))
}
render() {
return (
<div>
{/* 获取store的状态 */}
<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>
</select>
<button onClick={this.increment}> +</button>
<button onClick={this.decrement}> -</button>
<button onClick={this.incrementIfOdd}> 当前和为奇数再加</button>
<button onClick={this.incrementAsync}> 异步加</button>
</div>
)
}
}
const mapStateToProps = state=> ({count: state})
const mapDispatchToProps = (dispatch)=>{
return {
//通知redux执行加法
increment:number=> dispatch(createIncrementAction(number)),
//通知redux执行减法
decrement:number=> dispatch(createDecrementAction(number)),
//通知redux执行异步
asynIncrement:(number,time)=> dispatch(createIncrementActionAsyn(number,time)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Count)
(3) 总结:react-redux优化
(1)容器组件和UI组件整合一个文件
(2)无需自己给容器组件传递store,给包裹
(3)使用react-redux后也不用自己检测redux中状态的改变,容器组件可以自动完成这个工作
(4)mapDispatchToProps可以写成对象
(4)组件与redux交互的步骤
首先定义好一个UI组件,引入connet生成一个容器组件(并暴露),写法如下:
export default connect(
state=> ({count: state}),
{
increment: createIncrementAction
decrement: createDecrementAction
asynIncrement: createIncrementActionAsyn
},
))(CountUI)
接着在UI组件中,通过this.props.XX读取和操作状态
decrement= () =>{
const {value} = this.selectNumber
this.props.decrement( value *1 )
}
4.2 数据共享
redux的使用场景就是多个组件需要数据共享,接下来介绍的就是多组件使用的情况。
- 首先定义一个Person组件,和Count组件通过redux共享数据
- 为Person组件编写:reducer、action,配置constant常量
- 重点:Person的reducer和Count的reducer要使用combineReducers合并,合并后的总状态是一个对象
/*
redux文件下的store.js
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './reducers/count_reducer'
// 引入为Person组件服务的reducer
import personReducer from './reducers/person'
import {thunk} from 'redux-thunk'
//合并reducers 传入的就是redux保存的总状态对象
const allReducer = combineReducers({
he: countReducer,
rens: personReducer
})
// 暴露store
export default createStore(allReducer,applyMiddleware(thunk))
- 交给store的总是reducer,最后注意在组件中取出状态的
redux的reducer函数必须是一个纯函数。纯函数就是只要是同样的输入(实参),必定得到同样的输出(返回),不得改写参数数据,不会产生任何副作用(比如网络请求),不能调用Date.Now()或者Math.random()等不纯的方法。
index.js代码
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./redux/store";
import {Provider} from 'react-redux'
ReactDOM.render(
//此处需要Provider包裹App,目的是让App所有的后代容器组件都能接收到store
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
App.jsx代码
import React, {Component} from "react";
import Count from "./containers/Count"; //引入Count的容器组件
import store from "./redux/store";
import Person from "./containers/Person";//引入Person的容器组件
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store}/>
<hr/>
<Person/>
</div>
)
}
}
redux文件夹下的store.js代码
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 创建redux的核心store对象
import {legacy_createStore as createStore, applyMiddleware, combineReducers} from 'redux'
// 引入汇总后的reducer
import allReducer from './reducers'
import {thunk} from 'redux-thunk'
import {composeWithDevTools} from 'redux-devtools-extension'
// 暴露store
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
redux文件夹下reducers文件夹下的index.js代码
这个文件由 redux文件夹下的store.js代码所引入,当组件数量多时,store.js文件也不会太大
/**
* 该文件用于汇总所有的reducer
*/
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 引入为Person组件服务的reducer
import personReducer from './person'
import {combineReducers} from 'redux'
//合并reducers 传入的就是redux保存的总状态对象
const allReducer = combineReducers({
count: countReducer,
persons: personReducer
})
export default allReducer
redux文件夹下actions文件夹下的person.js和count.js代码
import {ADDPERSON} from '../constant'
// 创建增加一个人的动作action对象
export const createAddPerson = personObj => ({type: ADDPERSON, data:personObj})
/*
该文件专门为count组件生成action对象
*/
// 同步action 返回值为object
import {INCREMENT, DECREMENT} from '../constant'
export const createIncrementAction = data =>({type: INCREMENT, data})
export const createDecrementAction = data =>({type: DECREMENT, data})
// 异步action 就是值为函数 异步action中一般会调用同步action
export const createIncrementActionAsyn = (data, time) =>{
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction)
},time)
}
}
containers文件夹下Person的index.jsx代码
import React, { Component } from "react";
import {nanoid} from 'nanoid'
import { connect } from "react-redux";
import { createAddPerson } 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}
this.props.increPerson(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
render(){
return(
<div>
<h2>我是Person组件,上方求和为{this.props.count}</h2>
<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.personArr.map((p)=>{
return <li>{p.name}--{p.age}</li>
})}
</ul>
</div>
)
}
}
export default connect(
state => ({
personArr: state.persons,
count: state.count
}),
{
increPerson: createAddPerson
}
)(Person)
containers文件夹下Count的index.jsx代码
import React, {Component} from "react";
import { connect } from "react-redux";
import {createDecrementAction,createIncrementAction} from '../../redux/actions/count_action'
class count extends React.Component {
add =()=>{
this.props.jiafa(1)
}
render() {
return (
<div>
<h1> 求和为:{this.props.count},下面总人数和为:{this.props.personCount}</h1>
<button onClick={this.add}>+1</button>
</div>
)
}
}
export default connect(
state => ({count: state.count, personCount: state.persons.length}),
{
jiafa: createIncrementAction,
jianfa: createDecrementAction
}
)(count)
5 redux开发工具的使用
使用redux的扩展的开发工具步骤。在浏览器的扩展中搜索redux下载Redux DevTools。接着在项目终端安装下面这个包
npm add redux-devtools-extension
在store.js中导入
import {composeWithDevTools} from 'redux-devtools-extension'
.省略
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
重新启动项目即可,下图是我点击了几次按钮和添加新的一位人员信息的记录。可以很快的查看redux的Action、State等等