刚刚学完Rudex真的就很无语,这玩意对初学者在怎么这么不友好.😣
一 什么是Redux、
(1)相较于传统方法的特点
Redux就是一个状态集中管理的工具(用起来很麻烦),特点是可以独立于框架运行。也就是说原生的js开发也可以使用Redux来集中管理。来看看概念图
可以看见 ,没有Redux的话app中有一个组件的状态发生改变,与之相关的组件会依次发生改变,是类似于链式反应的传递关系。这就造成了一个后果就是反应速度会很慢,效率也会降低。但是对于Redux就没有这种情况,但一个组件发生改变,会向store提交一个action,然后通过store里面的reducer来反应这个action返回改变新状态,然后从store统一的同步的去更新这些状态 极大的提升了效率。这也是Redux的特点同步性
(2)实现流程
我们一起来看看Redux的实现过程 视图组件的状态发生改变 会提交一个Action 这个action里有一个Type属性 根据这个Type属性Reducer会响应一个响应的状态更新函数来更新这些状态 然后通过store这个统一规定管理工具来分发这些store更新视图
这里借用一下学姐对Acton reuder 和store的总结
1. state - 一个对象 存放着我们管理的数据状态
2. action - 一个对象 用来描述你想怎么改数据(传入什么参数)
3. reducer - 一个函数 根据action的描述生成一个新的state(怎样修改数据)
二 Redux搭配React开发实例
这里我们将用一个实例来简单看看Redux在React开发中是怎么使用的 先来看看开发效果
就这么简单的东西要写几百行就很恶心 所以小项目的开发就不需要使用Redux来管理状态了
(1)环境准备
这里Meta官方推荐使用Redux Toolkit进行React开发 并且需要搭配react-redux食用 这里就不多介绍了如果大家想了解这个两个插件的特点可以异步去这篇博客(点击即可跳转)。
1.使用CRA创建React项目
npx create-react-app react-redux
2.安装配套的插件:
npm i @reduxjs/toolkit react-redux
3.启动项目
npm start
4.在scr中创建store目录
这个目录主要是用来写状态管理工具的
目录结构:
其中counterStore是状态更新逻辑文件(这里的counter是状态名 可以换成其他名字),index.js是入口文件名(下面那个index.js不是哈)
1. 通常集中状态管理的部分都会单独创建一个单独的 `store` 目录
2. 应用通常会有很多个子store模块,所以创建一个 `modules` 目录,在内部编写业务分类的子store 比如counterStore 或者showStore等等 一般一个状态写一个js文件
3. store中的入口文件 index.js 的作用是整合modules中所有的子模块,并导出store
(2)编写环节
(1)设计思路
设计思路就是先配置Redux组件 编写counterStore模块 然后通过store整合一下 在使用store暴露的api来编写React组件
(2)counterStore模块
我们需要引入一下createSlice方法
import{createSlice, createStore} from '@reduxjs/toolkit'
这个方法是一个工具函数 就像一个店长一样,你需要提公共店名(name:''),库存(initalState),点单操作(reducer)是什么,然后这个店长会给你分配配方(自动实现reducer),遥控器(acton),工作证(acton types)等等
const counterStore=createSlice({
//详解 name就表示模块的名称 后续会根据name来自动生产action
name:'counter',
//初始化state(用来初始化自定义属性的)
initialState:{
//count属性 因为我们要做的就是一个count计数器
count:0
},
//修改数据的方法 支持直接修改的同步方法
reducers:{
//reducer里面是对应动作所执行的方法 用来修改状态的
//定义方法
increment(state){
state.count++
},
decrement(state){
state.count--
}
}
})
下面是对属性的详解:
name:‘counter’ 这个属性是用来表示该状态模块的名称的 后续createSlice会根据这个名册还给你自动生成对应action 比如counterAction.type
initalState:{ count:0 } 这个属性是用来初始化状态的 这里直接将state属性初始化为0
reducers:{ ... } 这个属性里面用来记录对应counterAction.type需要执行的方法 比如counterAction.type=‘increment’ 就执行increment()方法 来对应修改状态 而且这里还有一个特点就是reducer里面的方法可以直接来修改状态本身 不需要返回一个新的状态
下面就是导出action对象 和 reducer响应方法
const {increment, decrement} = counterStore.actions
const counterReducer=counterStore.reducer;
export{increment, decrement}
export default counterReducer
1.其中counterStore.actions这个actions是一个自动创建的隐藏属性 里面包含了reducer属性里面方法的对应acton方法 结构出来的这俩都是函数,调用它们会创建对应的action对象 比如调用increment()会返回{type: 'counter/increment'}这样的对象 作用是用来相应reducer里面的方法
/* 简单理解:
- reducers里的方法是**"收到什么动作,怎么改数据"**(配方表)
- actions里的方法是**"我要发出什么动作"**(遥控器 */
2.里面的constReducer就是正常的拿取counterStore里面的响应函数reudecer了
(3)Store/index.js设计
在这个接口里面 我们首先需要引入之前在从counterStore里面导出的counterReducer还需要使用configureStore来整合小模块
//2,在入口文件导入
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './modules/counterStore'
然后使用configureStore方法来创建store
//configureStore就是一个整合所有小模块的工具
const store=configureStore({
reducer:{
// counter是模块的名称 counterReducer是模块的reducer
counter:counterReducer
}
})
// store.getState().counter(模块名).count(具体值) 可以访问状态的值
export default store
其中 configureStore里的参数是一个对象:这个对量里面的reducer是将原本分散在几个小模块的reducer集合成一个大的reducer 里面的counter:counterReducer就是对应的模块
(4)React组件编写
在React组件编写时需要使用两个钩子 分别是useDIspatch和useSelector 这两个钩子都从属于react-redux。其中dispatch的作用是向reducer提交action,然后reducer会根据action type来执行对应的方法来修改状态。然后useSeletor的作用是请求编写在store里面的状态。
import { useDispatch, useSelector } from "react-redux"
//导入两个对应动作所执行的方法 这两个函数会返回一个action type对象 比如{type: 'counter/increment'}
//然后dispatch会提交这个action给reducer reducer会根据action type来执行对应的修改状态的方法
import{increment,decrement} from './store/modules/counterStore'
function App(){
const {count}=useSelector(state=>state.counter)//注意 这里的counter是模块名称 count是状态名称
//如果想修改数据需要重新引入新的钩子useDispatch 通过dispatch函数提交action来
const dispatch=useDispatch();
return(
<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{count}
<button onClick={()=>dispatch(increment())}>+</button>
</div>
)
}
export default App
最后在button组件中设置点击事件 执行dispatch函数 参数就是我们导出的两个方法(这两个方法会返回一个action对象前面讲过
到此我们的编写就完结了。
三 提交action传参
这里我们要在上面实例的基础上新增两个按钮 一个是➕10 一个➕20
实现上述的效果只需要在原有代码的基础上稍作修改 我们先说在哪修改 再探究原理
(1) 修改
首先在counterStore里面的reducer响应函数里面添加一个新的带参数方法addToNum(state,action)
reducers:{
//reducer里面是对应动作所执行的方法 用来修改状态的
//定义方法
increment(state){
state.count++
},
decrement(state){
state.count--
},
//03action传参
addToNun(state,action){
state.count+=action.payload
}
}
然后在导出和导入的地方也做相同的修改
const {increment, decrement,addToNun} = counterStore.actions
export{increment, decrement,addToNun}
在组件React里面也做出修改
import{increment,decrement,addToNun} from './store/modules/counterStore'
再向React组件里面添加两个按钮
<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{count}
<button onClick={()=>dispatch(increment())}>+</button>
<button onClick={()=>dispatch(addToNun(10))}>dec to 10</button>
<button onClick={()=>dispatch(addToNun(20))}>add to 20</button>
</div>
)
(2)原理
原理很简单 我们之前说过在counterStrore.js里面最后在导出的时候有一段对象解构的代码
const {increment, decrement,addToNun} = counterStore.actions
这段代码从counterStore的actons属性中将三个函数方法结构,这三个方法调用之后都会返回一个对象 比如调用increment()会返回{type: 'counter/increment'}这样的对象,如果调用的时候添加参数,就会在对象中多一个payload属性 这个属性值就是我们传递的参数 如{type:'counter/increment',payload:10}
然后在组件里面我们使用dispatch这个函数提交了这个action对象 那么reducer就会做出对应的操作,总体来看还是很简单的。
四 Redux管理异步操作
(1)设计效果与模式
我们先看看需要实现的效果
来看看设计模式
会发现 除了异步修改这一块不同 其他的设计方法和同步开发是一样的
设计流程:
1. 创建store的写法保持不变,配置好同步修改状态的方法
2. 单独封装一个函数,在函数内部return一个新函数,在新函数中
2.1 封装异步请求获取数据
2.2 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
3. 组件中dispatch的写法保持不变
(2)代码实现
(1)channelStore模块
第一步还是和之前是一样的 使用createSlice设计channelStore
import { createSlice } from "@reduxjs/toolkit";
import axios from 'axios'
const channelStore=createSlice({
name:'channel',
initialState:{
channelList:[]
},
reducers:{
setChannel(state,action){
state.channelList=action.payload
}
}
}
)
不同的就是下面的异步函数代码了
先写一个fetchChannel函数套皮方便导出和导入 然后return一个异步函数 通过axios来获取后端数据 在使用dispatch提交action
const {setChannel}=channelStore.actions
const url='http://geek.itheima.net/v1_0/channels'
//拉取异步请求
const fetchChanelList=()=>{
return async (dispatch)=>{
const res=await axios.get(url)
dispatch(setChannel(res.data.data.channels))
}
}
最后导出异步函数和reucer
export {fetchChanelList}
const channelReducer=channelStore.reducer
export default channelReducer
(2)store/index.js
在store里面的index.js里面 我们需要添加store里面的reducer了
const store=configureStore({
reducer:{
// counter是模块的名称 counterReducer是模块的reducer
counter:counterReducer,
channel:channelReducer
}
})
其他的操作不变
(3)React组件编写
在编写React时需要的修改不多 需要先导入fetch函数用来异步操作 然后使用useEffect来处理异步请求这个副作用 最后将使用useSelectoe钩子将channellist从状态里面导出来 再使用map在jsx中渲染
import{increment,decrement,addToNum } from './store/modules/counterStore'
import { useEffect } from "react";
import { fetchChanelList } from "./store/modules/channelStore";
function App(){
const {count}=useSelector(state=>state.counter)
const dispatch=useDispatch();
const {channelList}=useSelector(state=>state.channel)
//使用useEffect触发异步
useEffect(()=>{
dispatch(fetchChanelList())
},[dispatch])//把dispath作为依赖项 其实这里有没有都可以因为这是死数据 不会变化
return(
<div>
<button onClick={()=>dispatch(decrement())}>-</button>
{count}
<button onClick={()=>dispatch(increment())}>+</button>
<button onClick={()=>dispatch(addToNum(10))}>dec to 10</button>
<button onClick={()=>dispatch(addToNum(20))}>add to 20</button>
<ul>
{channelList.map(item=>(
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}