本文章将讲解Redux并使用Redux Toolkit工具集简化Redux的使用;
Redux 是什么?
Redux是一个第三方的管理全局状态的工具库,所有的全局状态存储在一个可全局访问的Store当中,它可以使用action(动作)容易的去更新状态,让你更容易的理解状态如何被更新,跟Context API + useReducer的概念类似;
什么时候使用 Redux
许多应用都不需要使用Redux,除非你的应用当中:有大量的全局状态、状态被频繁更新、复杂的更新逻辑
Redux的 工作流程
事件处理通过dispatch(分发-触发一个事件)一个action(动作-发生了什么事)到Store当中找到相应的Reducer(视为一个事件监听器),Reducer根据收到的action类型更新State,每个使用Store的组件将会重新渲染
Redux Toolkit 是什么?
Redux Toolkit是一个现代、高效使用Redux的开发工具集,简化了Redux的操作,它包括Immer、Redux-Thunk、Reselect三个核心包
Immer(处理不可变性),可以在reducer里直接改变状态Redux-Thunk(中间件),处理异步操作Reselect(Redux的简单'选择器'库),用来简化Reducer函数
还自带了一个DevTools工具方便调试代码,需在浏览器插件搜索Redux DevTools配合使用
使用 Redux Toolkit
安装工具包
npm install @reduxjs/toolkit react-redux
创建 Redux Store
src/store.js代码
configureStoreAPI 创建一个Redux储存实例,需要传入一个reducer参数(此时可以先忽略)
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter/counterSlice'
const store = configureStore({
reducer: {
counter: counterReducer
},
})
export default store
传入了一个{counter: counterReducer}对象时,表示在Redux状态对象状态中存在一个state.counter属性
为 React 提供 Redux Store
通过react-redux中的Provider组件将App组件包裹并将store作为prop传递
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)
创建 Redux State Slice
目录规范
通常Redux会有一个规范的目录结构,/src/features/单个功能/功能对应的slice.js
创建一个 Slice 文件
Redux Toolkit中使用createSlice函数来创建一个slice(切片,管理特定的状态域),该函数需传递一个对象包含三个属性name、initialState、reducers
name:一个字符串标识切片,将作用生成的action types的前缀initialState:reducer的初始值state对象reducers:一个对象,包含多个处理action(动作)的reducer函数
导入createSlice函数,并创建初始值对象
import { createSlice } from '@reduxjs/toolkit'
// 初始值
const initialState = {
value: 0,
}
创建slice,在Redux Toolkit中可以编写直接改变State的逻辑,因为内部使用了Immer,Redux的状态更新都是以不可变的更新的方式不可变:复制原来的object/array,然后更新它的复制体
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value += 1
},
decrement(state) {
state.value -= 1
},
incrementByAmount(state, action) {
state.value += action.payload
},
},
})
导出对应的reducer以及action creater
// 每个 case reducers函数会生成对应的 action creater(返回action对象的函数)
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// 导出相应的reducer
export default counterSlice.reducer
术语
slice文件是单个功能的reducer和action的合集
reducer是一个函数,接收当前的state和一个action对象,决定着如何更新状态,并返回新状态action是一个对象{type:xx, payload: xx},用于描述动作(发生的事情)和传递的参数,action creator是一个创建并返回action对象的函数
在 React 组件中使用 Redux
React-Redux中有两个钩子,useSelect从store中获取数据,useDispatch分发对应的动作action
useSelector的参数是一个函数,函数的参数就是store存储的数据useDispatch返回一个函数,在这个函数中需要传递一个action对象,触发slice中对应的reducer函数
import { useDispatch, useSelector } from 'react-redux'
import {
increment,
decrement,
incrementByAmount,
incrementAsync,
} from './counterSlice'
import { useState } from 'react'
const Counter = () => {
const [number, setNumber] = useState(0)
const counter = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<>
<div>
<button onClick={() => dispatch(increment())}>+</button>
<span>{counter}</span>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
<div>
<input
type='number'
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<button onClick={() => dispatch(incrementByAmount(+number))}>
同步添加amount
</button>
<button onClick={() => dispatch(incrementAsync(+number))}>
异步添加amount
</button>
</div>
</>
)
}
export default Counter
开发者工具栏中的 Redux
此前安装了浏览器插件Redux DevTools,按F12在工具栏中选择Redux,当dispatch分发的每个action都会被显示
可以看见分发dispatch(increment()),会发出一个action的对象即{type:counter/increment},因为Redux Tookits根据slice自动生成了对应的action(动作),type由name + reducer组成;
payload 携带多个参数
reducer函数action是一个对象,其中action.payload是dispatch(action creater)携带的参数
dispatch(incrementByAmount(5))
// 这里的 action creator函数的参数 就是对应的action.payload
// 等价于 dispatch({type: 'counter/incrementByAmount', payload: 5}
使用action creator时只能传递一个参数,如果想传递多个参数
- 使用一个对象
dispatch(incrementByAmount({type: 'add', value: 5}))
incrementByAmount(state, action) {
// 结构 action.payload 对象
const {type, value} = action.payload
if (type === 'add') {
// state.value += value
} else {
// xxx
}
},
- 自定义
reducer函数,配置reducer和prepare属性,prepare就是执行相应的reducer前执行的函数
dispatch(incrementByAmount('add', 5))
incrementByAmount: {
reducer(state, action) {
const { type, value } = action.payload
if (type === 'add') {
state.value += value
} else {
state.value -= value
}
},
prepare(type, value) {
return {
payload: {
type,
value,
},
}
},
},
使用 thunk 处理异步逻辑
Redux store只知道同步操作,而Redux Toolkits自带了thunk中间件,允许在dispatch分发action之后,执行对应的reducer函数之间进行异步操作;
编写一个thunk action creator,返回一个thunk函数,该函数有两个参数dispatch和getState,在thunk函数里进行异步处理
传统的方法
dispatch(incrementAsync(5))
export const incrementAsync = function (amount) {
return function (dispatch, getState) {
// 模拟异步操作
setTimeout(() => dispatch(incrementByAmount(amount)), 1000)
}
}
使用createAsyncThunkAPI自动生成thunk函数,第一个参数action.type的字符串,第二个参数是一个payload creator回调函数,会返回一个包含数据的Promise或者带有错误的Promise
export const incrementAsync = createAsyncThunk(
'counter/incrementAsync',
async () => {
// 模拟异步请求
try {
const res = await new Promise((resolve, reject) => {
setTimeout(() => resolve(5), 1000)
// setTimeout(() => reject('error'), 1000)
})
return res
} catch (error) {
// 返回一个Promise.reject触发 reducer的rejected
return Promise.reject(error)
}
}
)
需要在slice中的extraReducers中对三种动作类型进行处理,该函数有一个builder的参数,builder对象提供了一些方法可以定义额外的case reducer,并支持链式调用,addCase增加reducer,这些reducer可以对createAsyncThunk的状态进行监听处理对应的状态pending、fulfilled、rejected;
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
// 省略
},
extraReducers(builder) {
builder
.addCase(incrementAsync.pending, (state, action) => {
console.log(1)
})
.addCase(incrementAsync.fulfilled, (state, action) => {
console.log(2, action)
// state.value += action.payload
})
.addCase(incrementAsync.rejected, (state, action) => {
console.log(3)
})
},
})
最后的想法
Redux是可以编写可预测、可测试的代码,Redux Toolkit极大的简化了Redux的操作,更容易的管理Redux的状态;
Redux适用于管理全局状态在大型应用中,当需要频繁更新和管理复杂状态时用它Redux可以让逻辑和UI进行分离,更容易的理解state在什么时候、什么地方,如何被更新Redux Toolkit是更推荐编写Redux的工具集,支持thunk中间件、immer以可变的方式编写不可变状态,拓展的DevTools很容易看到对应的dispatch