目录
redux
redux是一个专门用于做状态管理的JS库(不是react插件库)。它可以用在react、angular、vue等项目中,但基本与react配合使用。其作用是:集中式管理react应用中多个组件共享的状态。其中文官方文档为:redux中文官方文档 。
redux的应用场景:
某个组件的状态,需要让其他组件可以随时拿到(共享)
一个组件需要改变另一个组件的状态(通信)
总之:redux能不用就不用,当你项目中不用比较吃力的时候,才考虑使用。
其原理图如下所示:
由上图的原理图可以看出redux有三个核心概念:
actionCreators:动作的对象,包含两个属性,type:标识属性,值为字符串,是唯一必要属性;data:数据属性,值类型任意,可选属性。
Reducers:用于初始化状态、加工状态,加工时,根据旧的state和action,产生新的state的纯函数。
Store:将state、action、reducer联系在一起的对象。
redux的安装与使用
如果你是刚创建的项目而且想使用redux,推荐使用官方给出的方法,在创建react脚手架的同时把redux也下载好,也可以同时搭配TS来使用:
如果是已经成型的项目,直接终端运行如下命令即可:
# NPM
npm install redux
# Yarn
yarn add redux
可以看到当我们安装完成后,redux已经来到了4.2版本,然而该版本将原始 createStore API 标记为 @deprecated(废弃),为了兼容低版本代码,官方不会真正的删除createStoreAPI,只是将该 API 标记为已弃用(@deprecated)。并且添加了一个全新的 configureStore API,此外该版本鼓励用户迁移到 Redux Toolkit。
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。它围绕 Redux 核心,并包含对于构建 Redux 应用必不可少的软件包和功能。Redux Toolkit 建立在最佳实践中,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序更加容易。但是许多老教程仍然使用过时的Redux手写模式,如果你使用老版本并且想消除删除线,可以切换 legacy_createStore 模式导出的 API,此种模式下没有@deprecation标签。
本文会先讲解一下老版本的使用,然后再简单提及一下 Redux Toolkit 的使用,如下:
在src目录下新建 redux 文件夹,里面存放着加工数据的reducer和接收加工数据的store文件夹:
// store.js文件,该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 切换 legacy_createStore 模式导出的 API,此种模式下没有(警告)@deprecation标签。
import { legacy_createStore as createStore } from "redux";
// 引入处理count的reducer
import countReducer from './countReducer.js'
// 暴露store
export default createStore(countReducer)
// countReducer.js文件,该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
// reducer函数会接到两个参数,分别是:之前的状态(preState),动作对象(action)
export default function countReducer(preState,action){
// 从action中获取:type、data
const {type,data} = action
// 根据type决定如何加工数据
switch (type) {
case 'increment': // 如果是加
return preState + data
case 'decrement': // 如果是减
return preState - data
default:
return 0 // 页面的初始数据
}
}
创建好redux相关文件之后,我们直接要在使用store状态的组件中引入即可,因为redux只处理数据状态,并不会重新渲染,所以需要我们进行手动渲染设置,如下:
import React, { Component } from 'react'
// 引入store,用于获取redux中保存状态
import store from '../../redux/store.js'
export default class Count extends Component {
componentDidMount(){
// 检测redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
// this.setState({}) // 什么也不传值,只调用也能重新调用render渲染页面
this.forceUpdate() // 强制刷新
})
}
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
store.dispatch({type:'increment',data:value*1})
}
decrement = () =>{
const {value} = this.selectNumber
store.dispatch({type:'decrement',data:value*1})
}
incrementIfOdd = () =>{
const {value} = this.selectNumber
const count = store.getState()
if(count%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 style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<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>
)
}
}
如果要使用redux状态的组件有很多,每个组件都写componentDidMount来重新渲染页面会很繁琐,推荐将监听store变化的函数写在入口文件,如下:
import React from 'react';
import ReactDOM from "react-dom";
import App from './App'
import store from './redux/store.js'
ReactDOM.render(<App />,document.getElementById('root'))
// 检测redux中状态的变化,只要变化就调用render
store.subscribe(()=>{
ReactDOM.render(<App />,document.getElementById('root'))
})
接下来使用actionCreators来告知store要传入的动作的对象,在redux文件夹下新建count_action文件,如下:
// count_action.js文件,该文件专门为Count组件生成action对象
// 完整书写
export const createIncrementAction = (data)=>{
return {type:'increment',data}
}
// 精简书写
export const createDecrementAction = data => ({type:'decrement',data})
将创建好的actionCreators引入要使用store的Count组件,将原本的dispatch要写的对象,替换掉
在日常开发中,如果文件的数量太多,要书写很多常量,可以在redux文件夹下新建一个文件来专门处理常量问题,放置容易写错的type值,防止程序员因为粗心而将单词写错,也便于以后的管理:
处理好常量之后,在使用type和data的对象的actionCreators和Reducer文件引入即可:
处理异步action
同步action就是我们平常在调用actionCreators时,dispatch传入的type和data对象。
什么是异步action呢?举个例子:component组件相当于顾客,actionCreators相当于服务员,store相当于老板,reducers相当于厨师,顾客去饭店点餐,告诉服务员,5分钟后再上菜,掐时间的是服务员,这个就属于异步action,当然顾客也能去饭店等五分钟再告诉服务员上菜,而这需要顾客自己掐时间,也能达到相同的效果,但是顾客为啥要掐时间呢?服务员是服务我们的,应该需要他去劳作,哈哈,大致就是这个意思,但是怎么做呢?如下:将action文件设置一个处理异步任务的函数。
// count_action.js文件,该文件专门为Count组件生成action对象
import { INCREMENT,DECREMENT } from "./constant"
import store from '../redux/store'
// 完整书写
export const createIncrementAction = (data)=>{
return {type:INCREMENT,data}
}
// 精简书写
export const createDecrementAction = data => ({type:DECREMENT,data})
// 异步的action
export const createIncrementAsyncAction = (data,time) =>{
// 函数柯里化,将函数的返回值暴露出来
return () =>{
setTimeout(()=>{
store.dispatch(createIncrementAction(data))
},time)
}
}
我们在调用函数的时候,直接传入具体实参即可:
而我们在调用这个异步任务后,控制台爆出如下错误: 其告诉我们action只能接收对象不能接收函数,如果要接收函数需要使用第三方中间件,并且名字在控制台也告诉了我们:redux-thunk
npm安装完 redux-thunk之后,在store文件中引入并使用即可,如下:
// store.js文件,该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 切换 legacy_createStore 模式导出的 API,此种模式下没有(警告)@deprecation标签。
import { legacy_createStore as createStore,applyMiddleware } from "redux";
// 引入处理count的reducer
import countReducer from './countReducer.js'
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk))
注意:异步action不是必须要写的,完全可以自己等待异步任务的结果然后再去分发同步action。当我们想要对状态进行操作,但是具体的数据靠异步任务返回的时候可以采用异步action。
react-redux的使用
facebook公司发现,许多的react开发人员都倾向于使用redux进行集中式状态管理,所以其公司自行推出了react-redux插件库,便于react和redux进行协调开发,react-redux模型图如下:
根据原理图,我们还需要创建一个容器组件,将以前直接在UI组件库里面调用redux的API进行抽离出来,所有与redux相关的API全部放置在容器组件中进行,如下:
在书写容器组件代码之前,需要npm安装一下react-redux这个包,安装完成之后,我直接给出以下代码,用容器组件来连接UI组件,如下:
// 引入Count的UI组件
import CountUI from '../../components/Count'
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
/*
1.mapstateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UIT组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
return {
jia:(number)=>{dispatch(createIncrementAction(number))},
jian:(number)=>{dispatch(createDecrementAction(number))},
jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
当然这个容器组件也可以采用简写方式,因为react-redux这个第三方库会自动帮助我们分发任务所以我们不需要在写dispatch,因为是函数传参,函数也能直接用箭头函数写在参数上:
// 引入Count的UI组件
import CountUI from '../../components/Count'
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
/*
1.mapstateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UIT组件props的value
3.mapStateToProps用于传递状态
*/
// function mapStateToProps(state){
// return {count:state}
// }
// 简写
// const mapStateToProps = state => ({count:state})
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key, value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
// function mapDispatchToProps(dispatch){
// return {
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// }
// }
// 简写
// const mapDispatchToProps = dispatch => ({
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// })
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
// dispatch => ({
// jia:(number)=>{dispatch(createIncrementAction(number))},
// jian:(number)=>{dispatch(createDecrementAction(number))},
// jiaAsync:(number,time)=>{dispatch(createIncrementAsyncAction(number,time))}
// })
// 因为react-redux会帮助我们自动分发dispatch
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction
}
)(CountUI)
因为UI组件是被容器组件给包裹住的,所有需要我们在App.jsx中引入容器组件而不是UI组件,并且需要我们在容器组件中以props的形式传入store对象,以此来在容器组件中能使用store的API:
那如果容器组件如果有很多的情况下,每个容器组件都需要props传入store未免有些太过繁琐了,这里我们可以借助 Provider 组件来一次性处理所有问题,如下:
根据以上的操作,我们在UI组件中就不需要来进行redux中store相关API的书写了,直接书写容器组件中定义的操作状态的方法即可,如下:
import React, { Component } from 'react'
export default class Count extends Component {
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
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(this.props);
return (
<div style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<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>
)
}
}
有了容器组件的出现,传统的需要在强制去调用render刷新页面已经不在需要了,如下:
写到这大家可能会有点疑惑,如果我们要是有20UI组件的话,是不是还需要写20个容器组件,这样不是导致我们的文件数过大吗,没错是这样,那么有什么办法来解决呢,其实我们完全可以将容器组件和UI组件整合在一起,因为react-redux并没有禁止我们将这两个组件写成一个啊,所有我们将之前的UI组件代码粘贴到容器组件中,如下:(完全没问题)
// 引入Count的UI组件
import { createIncrementAction,createDecrementAction,createIncrementAsyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件于redux
import { connect } from 'react-redux'
import React, { Component } from 'react'
class Count extends Component {
increment = () =>{
const {value} = this.selectNumber
// 通过dispatch来分配任务
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(this.props);
return (
<div style={{padding:'15px'}}>
{/* 通过getState这个API获取数据 */}
<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>
)
}
}
// 使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction
}
)(Count)
当我们要进行多组件的redux集中状态管理怎么办,要知道我们之前书写store的时候,只传入一个组件的,如果要进行多组件的redux需要借助redux的一个API,combineReducers用于管理多个组件并将其封装成一个对象,如下:
// store.js文件,该文件专门用于暴露一个store对象,整个应用只有一个store对象
// 切换 legacy_createStore 模式导出的 API,此种模式下没有(警告)@deprecation标签。
import { legacy_createStore as createStore,applyMiddleware,combineReducers } from "redux";
// 引入处理count的reducer
import countReducer from './reducers/count.js'
// 引入处理person的reducer
import personReducer from "./reducers/person.js";
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 将所有的容器组件合并成一个对象
const allReducer = combineReducers({
count:countReducer,
person:personReducer
})
// 暴露store
export default createStore(allReducer,applyMiddleware(thunk))
当我们书写多个组件进行redux的时候,还需要书写其actions和reducers文件,已经其容器组件和UI组件合并的文件:
actionCreators文件:
Reducers文件:
import {ADD_PERSON} from '../constant'
// 初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){
const {type,data} = action
switch (type){
case ADD_PERSON:
return [data,...preState]
default:
return preState
}
}
Person组件:
import { nanoid } from 'nanoid'
import React, { Component } from 'react'
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}
if(this.nameNode.value.trim() === '' || this.ageNode.value.trim() === '') return alert('输入不能为空!')
this.props.addperson(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
render() {
return (
<div>
<h2>我是Person组件</h2>
<h2>上方组件求和为:{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.person.map((p)=>{
return <li key={p.id}>姓名:--{p.name},年龄:--{p.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
// 通过store文件,拿到其任何组件的状态
state => ({person:state.person,count:state.count}),// 映射状态
{
addperson:createAddPersonAction// 映射状态的方法
}
)(Person)
通过redux的状态是共享的,我们在Count组件中拿到Person组件中的状态,并将其状态的数组长度打印到页面:
总结
明确两个概念:
1)UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。2)容器组件:负责和redux通信,将结果交给UI组件。
如何创建一个容器组件:靠react-redux 的connect函数,connect(mapStateToProps,mapDispatchToProps)(UI组件)
mapstateToProps:映射状态,返回值是一个对象
mapDispatchToProps:映射操作状态的方法,返回值是一个对象
备注:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入mapDispatchToProps也可以是一个对象
容器组件的UI组件可以整合成一个文件
redux开发者工具的安装与使用
对于有条件的朋友,可以直接访问谷歌浏览器的网上商店,搜索redux找到其开发工具进行下载即可,而没条件的朋友可以访问:极简插件 。搜索redux,如下:
下载并解压,将后缀名为CRX的文件,直接拖到谷歌浏览器的扩展程序页面,如下:
当然安装之后是并不能直接使用的,还需要进行一些操作,如下:
编辑器新建终端执行如下命令,安装第三方库:
npm install redux-devtools-extension
安装完成之后,需要在store文件中导入并引用,如下:
安装如上配置好之后,就可以使用redux开发者工具了,如下:
当然还有一些其他的功能,就不再一一讲解了,感兴趣的朋友可以去试一下。
Redux Toolkit的使用讲解
Redux Toolkit 是我们官方推荐的编写 Redux 逻辑的方法。它围绕 Redux 核心,并包含我们认为对于构建 Redux 应用必不可少的软件包和功能。Redux Toolkit 建立在我们建议的最佳实践中,简化了大多数 Redux 任务,防止了常见错误,并使编写 Redux 应用程序更加容易。
Redux Toolkit如何使用以及其存在的优势,官方已经讲解的很清楚了,其中文官方网址:网址 。现在博主就简单做一个小demo给大家示例一下,如果是已经存在的项目想要使用redux toolkit可以安装第三方包:
# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
如果是新建一个项目且要使用redux toolkit的话,安装脚手架的时候在后面添加如下命令即可:
创建store文件与之间的API不同,如下:
import { configureStore } from "@reduxjs/toolkit";
import counter from './reducers/count'
export default configureStore({
// 需要在store文件中对reducer文件进行一个挂载
reducer:{
counter
}
})
创建处理Count组件的reducer文件:
import { createSlice } from "@reduxjs/toolkit";
const counter = createSlice({
// 命名空间,name值会作为action中type值的前缀
name:'counter',
// 初始化状态数据
initialState: {
count: 0
},
// 定义reducer更新状态函数,组件中dispatch使用的action函数
reducers:{
add(state,action){
console.log(state,action)
state.count++
}
}
})
// 导出action函数
export const {add} = counter.actions
export default counter.reducer
在Count组件中通过调用相关API,直接获取reducer组件中的状态和操作状态的方法:
import React from 'react'
import { useDispatch,useSelector } from 'react-redux'
import { add } from '../../redux/reducers/count'
export default function Count(){
const dispatch = useDispatch()
const { count } = useSelector(state => state.counter)
return (
<div style={{marginLeft:'50%'}}>
{count}
<button onClick={()=>{
dispatch(add())
}}>+1</button>
</div>
)
}
App根组件渲染子组件到页面上,入口文件仍然使用Provider这个API对App后代组件中进行props传参store。
如果想在redux toolkit处理异步请求也是非常方便的,我们可以看到官网已经提供给我们处理异步请求的API了createAsyncThunk,可以使用测试一下,其他相关API如下请看:
我们之前处理异步请求还需要导入一个包,而现在使用redux toolkit是直接帮助我们封装好的,直接使用即可,如下:
总结:学习是没有止境的,在官方不断迭代出新的技术的同时,我们要去学会拥抱变化,顺应未来的发展趋势,只有在瀑布的激流中不断逆流向上,才能看到远方更美的风景。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/139987.html