React系统学习笔记--超基础--超详细--超简洁--Redux(七)
PartⅦ:Redux 1 redux理解
学习文档
英文文档:
中文文档:
:
redux是什么
redux是一个专门用于做状态管理的JS库(不是react插件库)
它可以用在react,,vue等项目中,但基本与react配合使用
作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux
某个组件的状态,需要让其他组件可以随时拿到(共享)
一个组件需要改变另一个组件的状态(通信)
总体原则:能不用就不用, 如果不用比较吃力才考虑使用
redux工作流程
2 redux的三个核心概念
动作的对象
包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性
例子:{ type: ‘’,data:{name: ‘tom’,age:18} }
用于初始化状态、加工状态,初始化时传入的是加工时,根据旧的state和, 产生新的state的纯函数(以下为纯函数概念) redux的函数必须是一个纯函数
store
将state、、联系在一起的对象
如何得到此对象?
此对象的功能?
3 redux的核心API
()与()
()作用:创建包含指定的store对象,目前已被弃用(2022.11)
()作用:应用上基于redux的中间件(插件库)
store对象
作用: redux库最核心的管理对象
它内部维护着:
核心方法:
具体编码:
()
作用:合并多个函数
//代码示例
------------------ redux/reducers/index.js ------------------------------------
/*** 该文件用于汇总所有的reducer为一个总的reducer*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
import persons from './person'//汇总所有的reducer变为一个总的reducer
export default combineReducers({count,persons
})
4 redux基础求和案例
redux精简版案例
src下准备好redux文件夹,文件下有store.js和.js
store.js
//新写法,创建redux最为核心的store对象
import { legacy_createStore as createStore } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'export default createStore(countReducer)
.js
//初始化状态---注意现在状态不放在组件里
const initState = 99
export default function countReducer(preState = initState, action) {//从action对象中获取:type,dataconst { type, data } = action//根据type决定如何加工数据switch (type) {case 'increment':return preState + datacase 'decrement':return preState - datadefault:return preState}}
Count.jsx组件
//获取redux中的状态getState
<h1>当前求和为:{store.getState()}</h1>//引入store
import store from '../../redux/store'//允许组件还有自己的状态
state = { car: '保时捷' }//redux更新完状态需要重新渲染,这样写不是最佳方案,因为如果组件多的话每个都要写
//可以在入口文件中引入store,将App挂载的时候包裹在store.subscribe里面,状态一变化,App组件都将重新渲染
componentDidMount() {//监测redux中状态的变化,只要变化,就调用renderstore.subscribe(() => {this.setState({})})
}increment = () => {const { value } = this.selectNumberstore.dispatch({ type: 'increment', data: value * 1 })
}
decrement = () => {const { value } = this.selectNumberstore.dispatch({ type: 'decrement', data: value * 1 })
}
入口文件index.js
// 状态改变重新渲染 App 组件
store.subscribe(() => {ReactDOM.render(<App />, document.getElementById('root'))
})
备注:redux只负责管理状态,状态改变驱动页面显示需要我们自己来
redux完整版案例
多了两个文件.js和.js
.js
//该模块用于定义action对象中type类型的常量值,以防单词写错
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
.js
//该文件专门为Count组件生成action对象
import { INCREMENT, DECREMENT } from "./constant"export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
Count.js组件
//不用自己写action对象,直接调用
increment = () => {const { value } = this.selectNumberstore.dispatch(createIncrementAction(value * 1))
}
decrement = () => {const { value } = this.selectNumberstore.dispatch(createDecrementAction(value * 1))
}
异步的redux版本–不在Count组件中设置定时
有两种形式:{}同步,异步
store默认不允许给的值不是一般对象(简单来说就是store最终接收的必须是一个合法的包含type和data的一般对象),给函数会报错滴,这时需要一个中间件来解决redux-thunk
npm i redux-thunk
store.js
//redux中的执行中间件函数applyMiddleware
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk中间件,用来支持异步action
import thunk from 'redux-thunk'
//暴露store,第二个参数是执行异步的中间件
export default createStore(countReducer, applyMiddleware(thunk))
有了中间件store才会帮你调异步的函数,异步中一般都会调用同步
.js
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的值是函数--因为函数可以开启异步任务
export const createIncrementAsyncAction = (data, time) => {return () => {setTimeout(() => {store.dispatch(createIncrementAction(data))}, time)}
}//其实还可以这样写,因为是store来调这异步函数,所以不用引入store
//因为store帮我们调的异步函数,所以知道等下要用到dispatch,store帮你传过来了
import { INCREMENT, DECREMENT } from "./constant"
//同步action,action的值是object一般对象
export const createIncrementAction = data => ({ type: INCREMENT, data })
export const createDecrementAction = data => ({ type: DECREMENT, data })
//异步action,action的值是函数--因为函数可以开启异步任务
export const createIncrementAsyncAction = (data, time) => {return (dispatch) => {setTimeout(() => {dispatch(createIncrementAction(data))}, time)}
}
Count.js组件
incrementAsync = () => {const { value } = this.selectNumber// setTimeout(() => {store.dispatch(createIncrementAsyncAction(value * 1, 500))// }, 500)
}
5 react-redux
理解
一个react插件库
专门用来简化react应用中使用redux
react-Redux将所有组件分成两大类
UI组件
只负责 UI 的呈现,不带有任何业务逻辑
通过props接收数据(一般数据和函数)
不使用任何 Redux 的 API
一般保存在文件夹下,也可以直接写在容器组件中直接加工成容器组件
容器组件
负责管理数据和业务逻辑,不负责UI的呈现
使用 Redux 的 API
一般保存在文件夹下
容器组件需要实现很多功能,需要借助东西去生成
注意:store不是在容器组件中直接引入,而是在App组件中通过props传过来的
UI组件是看不见任何redux的AIPI
求和案例react-redux
首先准备好UI组件(一般放在src下的)和容器组件(一般放在src下的)
容器组件Count下的index.js,注意store不在这里引入
//引入Count的UI组件
import CountUI from "../../components/CountUI";
//引入action
import { createIncrementAction, createDecrementAction, createIncrementAsyncAction }
from "../../redux/count_action";
//引入connect用于链接UI组件和redux
import { connect } from 'react-redux'
/* mapStateToProps函数返回的是一个对象
对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value---容器组件把从redux中获取的状态传给UI组件
mapStateToProps用于传递状态
注意这边不需要亲自引入store,react-redux调用mapStateToProps函数时帮我们传入state了*/
function mapStateToProps(state) {return { count: state }
}
/* mapDispatchToProps函数返回的是一个对象
对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
mapDispatchToProps用于传递操作状态的方法*/
function mapDispatchToProps(dispatch) {return {jia: (number) => {//通知redux执行加法dispatch(createIncrementAction(number))},jian: (number) => {//通知redux执行减法dispatch(createDecrementAction(number))},jiaAsync: (number, time) => {dispatch(createIncrementAsyncAction(number, time))}}
}//创建Count容器组件
const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI)export default CountContainer
UI组件
//从props中读取
<h1>当前求和为:{this.props.count}</h1>//UI组件中不会有任何redux的APIincrement = () => {const { value } = this.selectNumberthis.props.jia(value * 1)}decrement = () => {const { value } = this.selectNumberthis.props.jian(value * 1)}incrementIfOdd = () => {const { value } = this.selectNumberif (this.props.count % 2 !== 0) {this.props.jia(value * 1)}}incrementAsync = () => {const { value } = this.selectNumberthis.props.jiaAsync(value * 1, 500)}
App.js
import store from './redux/store'
//把store传递给容器组件
<Count store={store} />
优化一:简写
//创建Count容器组件---第二个函数可以写为对象,react-redux会帮我们自动分发dispatch
export default connect(state => ({ count: state }),{jia: createIncrementAction,jian: createDecrementAction,jiaAsync: createIncrementAsyncAction}
)(CountUI)
优化二:组件使用
使用了react-redux之后可以不写监测redux中状态变化的代码,因为创建容器组件的时候就有了监测redux状态变化的能力
所以之前为什么不是自己创建容器组件,因为创建的可以实现自动监测redux中状态的变化
//入口文件index.js---可以不用写了
store.subscribe(() => {root.render(<App />)
})
如果容器组件太多,我们需要在App.jsx中一个一个去传store,太繁琐,有没有什么方法可以优化
在入口文件中用组件,可以只写一次,用到store的容器组件都会被提供store
//入口文件index.js
import { Provider } from 'react-redux';const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>
);
优化三:整合UI组件和容器组件
一个文件可以定义n个组件
定义了UI组件之后直接定义容器组件并使用,最终暴露容器组件
知识点小结
()()
作用: 用于包装 UI 组件生成容器组件
使用(,)(UI组件)
注意点:
作用:将外部的数据(即state对象)转换为UI组件的标签属性
函数返回的是一个对象;
返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
用于传递状态
作用:将分发的函数转换为UI组件的标签属性
函数返回的是一个对象;
返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
用于传递操作状态的方法
可以省略,直接传入,api将会自动调用
数据共享—重点
多个组件的数据都放在redux中,redux用对象来存储,需要借助redux提供的先进行合并,合并之后的总状态是一个对象
容器组件在中取状态,交给子组件UI展示
现在有和Count组件,进行数据共享
为组件创建、、
export const ADD_PERSON = 'add_person'
import { ADD_PERSON } from '../constant'//创建增加一个人的action动作对象
export const createAddPersonAction = personObj => ({ type: ADD_PERSON, data: personObj })
//对人状态的初始化以及加工
//引入常量
import { ADD_PERSON } from '../constant'
//初始化状态
const initState = [{ id: '001', name: 'tome', age: 18 }]
//加工状态
export default function personReducer(preState = initState, action) {const { type, data } = actionswitch (type) {case ADD_PERSON:return [data, ...preState]default:return preState}
}
需要在store.js中进行合并
//新写法,创建redux最为核心的store对象
import { legacy_createStore as createStore, applyMiddleware, combineReducers } from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
//引入为Person服务的reducer
import personReducer from './reducers/person'
//引入redux-thunk中间件,用来支持异步action
import thunk from 'redux-thunk'//借助personReducer汇总所有reducer
const allReducer = combineReducers({//谁以后可以初始化和加工这个hehe: countReducer,rens: personReducer
})//暴露store
export default createStore(allReducer, applyMiddleware(thunk))
组件—创建容器链接UI组件和redux
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.valueconst age = this.ageNode.valueconst personObj = { id: nanoid(), name, age }this.props.jiaRen(personObj)this.nameNode.value = ''this.ageNode.value = ''}render() {return (<div><h2>我是Person组件</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.yiduiren.map((p) => {return <li key={p.id}>名字{p.name}--年龄{p.age}</li>})}</ul></div>)}
}export default connect(//state就是redux帮我们保存的总状态state => ({ yiduiren: state.rens }),{jiaRen: createAddPersonAction})(Person)
同样的App.js中需要引入组件并展示
<Count />
<Person />
纯函数
在中如果是一个数组,不可以用push、等方法进行修改,如此修改并不会修改其引用,所以diff并不会判定其发生改变,导致页面无法自动重新渲染
//浅比较,redux不认为是新数组--而且这样子写reducer就不是一个纯函数了--这边就是改写了参数preState的数据
preState.unshift(data)
return preState
//返回新数组可以
return [data, ...preState]
纯函数定义:只要是同样的输入(实参),必定得到同样的输出(返回)
遵守以下一些约束:
redux的函数必须是一个纯函数
6 使用redux调试工具
安装浏览器插件
Redux
下载工具依赖包
npm i redux--
修改store.js
{} from 'redux--'
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
7 求和案例的最终代码
src文件目录
src
–
--Count
--index.jsx
--
--index.jsx
–redux
--
--count.js
--.js
--
--count.js
--index.js
--.js
--.js
--store.js
–App.jsx
–index.js
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 Person from './containers/Person' //引入的Person的容器组件export default class App extends Component {render() {return (<div><Count/><hr/><Person/></div>)}
}
redux文件
文件夹
--------------------------------count.js------------------------------------------
/**
* 该文件专门未Count组件生成对象
*/
import {INCREMENT,DECREMENT} from '../constant'//声明同步action,就是指action的值为Object类型的一般对象
export const increment=data=>({type:INCREMENT,data})
export const decrement=data=>({type:DECREMENT,data})//声明异步action,就是指action的值为函数,异步action中一般都会调用同步action
//在外部调用该action方法时需要引入redux-thunk,用于支持异步action
//该方法会自动传入dispatchexport const incrementAsync=(data,time)=>{return (dispatch)=>{setTimeout(()=>{dispatch(increment(data))},time)}}
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const addPerson=personObj=>({type:ADD_PERSON,data:personObj
})
文件夹
--------------------------------count.js------------------------------------------
/**
* 1. 该文件时用于创建一个为Count组件服务的reducer.reducer的本质就是一个函数
* 2. reducer函数会接到两个参数,分别为:之前状态(preState),动作对象(action)
*/
import {INCREMENT,DECREMENT
} from '../constant'
const initState = 0 //初始化状态
export default function countReducer(preState = initState, action) {//从action对象中获取:type:dataconst {type,data} = action//根据type决定如何加工数据switch (type) {case INCREMENT:return preState + datacase DECREMENT:return preState - datadefault:return preState}
}
--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){// console.log('personReducer@#@#@#');const {type,data} = actionswitch (type) {case ADD_PERSON: //若是添加一个人//preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。return [data,...preState]default:return preState}
}
--------------------------------------index.js-------------------------------
/**
* 该文件用于汇总所有的reducer为一个总的reducer
*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
import persons from './person'//汇总所有的reducer变为一个总的reducer
export default combineReducers({count,persons
})
store.js
/**
* 该文件撰文用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
4..js
/**
* 该模块是用于定义action对象中的type类型的常量值,目的只有一个:
* 便于管理的同事防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
export const ADD_PERSON = 'add_person'
Count文件夹的index.jsx
import React, { Component } from 'react'//引入actionimport {increment,decrement,incrementAsync} from "../../redux/actions/count"//引入connect用于链接UI组件与reduximport { connect } from 'react-redux'//定义UI组件,这个将再connect()()中加工成容器组件,就可以调用到其传入的redux状态与actionsclass Count extends Component {increment = () => {//获取出入内容const { value } = this.selectNumberthis.props.increment(value * 1)}//减法decrement = () => {const { value } = this.selectNumberthis.props.decrement(value * 1)}//奇数再加incrementIfOdd = () => {const { value } = this.selectNumberif (this.props.count % 2 !== 0) {this.props.increment(value * 1)}}//异步加incrementAsync = () => {const { value } = this.selectNumberthis.props.incrementAsync(value * 1, 500)}render() {return (<div><h2>我是Count组件,下方组件总人数为:{this.props.personCount}</h2><h4>当前求和为:{this.props.count}</h4><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>)}}//使用connect()()创建并暴露一个Count的容器组件//使用connect(传入状态,操作状态方法)(UI组件)export default connect(state => ({count: state.count,personCount: state.persons.length}),{increment, decrement, incrementAsync})(Count)
文件夹下的jsx
import React, { Component } from 'react'import { connect } from 'react-redux'import { addPerson } from '../../redux/actions/person'import { nanoid } from 'nanoid'//创建UI组件class Person extends Component {addPerson = () => {const name = this.nameNode.valueconst age = this.ageNode.value * 1const personObj = { id: nanoid(), name, age }this.props.addPerson(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.persons.map((p) => {return <li key={p.id}>{p.name}--{p.age}</li>})}</ul></div>)}}export default connect(state => ({persons: state.persons,count: state.count}), { addPerson })(Person)