官方文档:redux
官方文档:redux toolkit英文建议翻译
今天是个摸鱼的好日子,但是当我看向周围同事时,发现他们不是在刻苦铭心的继续改bug,就是在因为改需求后疯狂的改代码,而我看着群里估计就是不想改代码的关联方和就是一定要统一配置化的领导在需求的提示语和关联方奇奇怪怪的完全没有语意化属性名吵的不可开交,我就知道今天这鱼我摸定了,但是要显的我正在工作,于是我决定先敲个 typescript版的todos练练手,当我使用createStore创建store时发现他寄了,上了官网才发现人家已经推荐使用redux toolkit这个库,我估计肯定是redux的作者终于发现这玩意是真的繁琐且枯燥。
[^注]: 文中redux toolkit 都是在ts 项目中的示例,博客的代码tsx 好像有问题,只能使用jsx了
1、安装 Redux Toolkit 和 React-Redux
npm install @reduxjs/toolkit react-redux
yarn add @reduxjs/toolkit react-redux
从 React Redux v7.2.3 开始,react-redux
包依赖于@types/react-redux
,因此类型定义将与库一起自动安装。如果你版本低于那个数,您需要自己手动安装它们(npm install @types/react-redux
)。
看对项目的需要,首先必须要store文件夹,文件夹中有index.ts和hooks.ts文件与createSlice文件夹,名称可以直接定义。
2、 使用Redux Toolkit
首先是store中的index.ts
// index.ts
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {}
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
从reduxjs/toolkit
获取configureStore
并他创建store,注意的是reducer
这个对象名称不可修改,所有实例必须在reducer
中,RootState
获取store
中属性的类型, AppDispatch
获取所有提交时的键。
虽然可以将RootState
andAppDispatch
类型导入到每个组件中, 但是使用配置后的自定义hooks的类型化肯定更加方便,使用官方推荐在store中添加hooks.ts文件,内容如下:
import { useDispatch, useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
剩下的就是如同你在createStore
中配置的active一样配置Slice.ts
// tokenSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// Define the initial state using that type
const initialState: requestToken = {} as requestToken
export const tokenSlice = createSlice({
name: 'login',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
getAxiosToken(state, actions: PayloadAction<requestToken>) {
const { token, refresh_token } = actions.payload
state.token = token
state.refresh_token = refresh_token
}
}
})
export const {
getAxiosToken,
} = tokenSlice.actions
export default tokenSlice.reducer
在createSlice文件夹中创建tokenSlice.ts,你也可以用其他方法。
第一就是创建initialState
属性,这个名称是固定的,必须叫这个名称,因为是用ts的原因,可以指定类型给他。
在createSlice
中,name如同你创建active的type,reducers
名称也是固定的,其他的方法就是修改store,而且不用再return,PayloadAction
可以指定你返回的payload的类型。在你dispatch时,reduxjs/toolkit会根据name和reducers的方法名生成一个提交的active,如上图,我的tokenSlice的name为login,我dispatch方法getAxiosToken
时,他会生成type为 login/getAxiosToken
。最后默认导出tokenSlice的reducer,将actions中的方法解构并导出。
// store中的index.ts
import { configureStore } from '@reduxjs/toolkit'
import todosSlice from './createSlice/todosSlice'
import Login from './createSlice/tokenSlice'
export const store = configureStore({
reducer: {
todos: todosSlice,
Login
}
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
3、在组件中使用Redux Toolkit
import { useAppDispatch, useAppSelector } from './store/hooks';
import { getTodosList } from './store/todosSlice';
import { todosList } from './contents';
import { useEffect } from 'react';
import { axiosParams } from './type';
import { getMyToken } from './api';
function App() {
const dispatch = useAppDispatch()
const token = useAppSelector(state => state)
useEffect(() => {
dispatch(getTodosList(todosList))
}, [dispatch])
const handleClick = async () => {
const params: axiosParams = {
mobile: '13911111111',
code: '246810'
}
dispatch(getMyToken(params))
}
return (
<section>
// 发送请求按钮
<button onClick={() => handleClick()}>点我发送请求!</button>
// 获取参数按钮
<button onClick={() => console.log(token.Login)}>点我获取Token!</button>
</section>
);
}
export default App;
使用也是和以前一样,但是useDispatch
要改成你之前在store中的hooks.ts的useAppDispatch
。获取也同理,在useAppSelector获取所有的state,因为已在hooks.ts中配置过,所有你能很轻松的点出store中的属性。
注: 你应该发现我上面的请求dispatch时是从api中获取的,为什么呢?
// 旧api
import request from "../request";
import { axiosParams, axiosResquery, requestToken } from "../type";
export const getToken = async (value: axiosParams): Promise<axiosResquery<requestToken>> => {
try {
const { data } = await request.post<axiosResquery<requestToken>>('/authorizations', value)
return data
} catch (err: any) {
return err
}
}
当然是因为操作都是在api中完成的,类似于以前的在请求中dispatch出去,大家以前都是在active中异步获取数据再dispath出去的,现在useAppDispatch
不能在非函数组件中使用,那怎么dispatch出来呢?
4、Redux Toolkit中的异步操作createAsyncThunk
// 新api
import { createAsyncThunk } from "@reduxjs/toolkit";
import request from "../request";
import { axiosParams, axiosResquery, requestToken } from "../type";
export const getMyToken = createAsyncThunk(
'login/getTokenStatus/fulfilled',
async (value: axiosParams) => {
try {
const { data } = await request.post<axiosResquery<requestToken>>('/authorizations', value)
return data.data
}
}
)
现在原来在active的dispatch操作可以使用createAsyncThunk
代替,createAsyncThunk
函数可以就是3个参数,一个名为type
的动作类型值,一个payloadCreator
回调,和一个对象Options
。
像上图,我创建了getMyToken
,createAsyncThunk
的第一个参数为字符串login/getTokenStatus/fulfilled
,login就是因为他是属于tokenSlice的一部分,后面的名称就随便你,getTokenStatus是我上完wc后随便起的,其实叫getMyToken也可以,再后面就是他的操作类型,按官网的分类有 pending: 等待
、fulfilled: 完成
、rejected:失败这三个值
。
这里我只是展示了他成功时会返回数据,在这里catch 进行错误处理也是没问题的。
后面的函数类似于之前在active中的dispatch,他失败和成功都会返回,至于第三个参数我目前没使用到,也就没看下去,如果后面要用,我应该会补上提醒自己。
重点是tokenSlice.ts要改成这样:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { getMyToken } from '../api'
import { requestToken } from '../type'
// Define the initial state using that type
const initialState: requestToken = {} as requestToken
export const tokenSlice = createSlice({
name: 'login',
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
},
extraReducers: (builder) => {
builder.addCase(getMyToken.fulfilled, (state, active) => {
// 1. 点语法
// state.token = active.payload.token
// state.refresh_token = active.payload.refresh_token
// 2. 直接返回active.payload,而且return 的内容实际上是会被state的类型控制监视到的
// return active.payload
// 3. 返回一个新地址的对象
const { token,refresh_token } = active.payload
Object.assign(state, { token, refresh_token })
})
}
})
export default tokenSlice.reducer
当使用createAsyncThunk
后,reducers之前一些修改token的操作不需要就可以去掉了,在extraReducers
中接受builder
,该参数的addCase
接受引入的getMyToken
,之前我们说过createAsyncThunk
的type包含3个状态,这次我们使用fulfilled
,当然你也可以在createAsyncThunk
的type
不加fulfilled
,但是为了更好区分推荐还是加上,addCase的回调函数接受state
和active
,这里就已经可以操作和修改initialState
的值了,不需要再在reducers配置什么,当然如果有多个createAsyncThunk
,你也可以在extraReducers
中继续加,包括对getMyToken
失败的rejected
的处理,但是这里在dispatch时使用try和catch 处理会方便点。
// index.tsx
import { useAppDispatch, useAppSelector } from './store/hooks';
import { getTodosList } from './store/todosSlice';
import { todosList } from './contents';
import { useEffect } from 'react';
import { axiosParams } from './type';
import { getMyToken } from './api';
function App() {
const dispatch = useAppDispatch()
const token = useAppSelector(state => state)
useEffect(() => {
dispatch(getTodosList(todosList))
}, [dispatch])
const handleClick = async () => {
const params: axiosParams = {
mobile: '13911111111',
code: '246810'
}
try {
await dispatch(getMyToken(params)).unwrap()
} catch (_) {
return
}
}
return (
<section>
<button onClick={() => handleClick()}>点我发送请求!</button>
<button onClick={() => console.log(token.Login)}>点我获取Token!</button>
</section>
);
}
export default App;
最后记得将这里加上await dispatch(getMyToken(params)).unwrap()
,并使用try 和catch 将成功和失败即将要进行的操作分开,因为你看见了,现在无法在非函数组件中操作,只能在发送请求时判断是否成功,大家也放心,只要请求失败肯定会走catch!
以是只是我个人对Redux Toolkit
的初步了解,不可否认Redux Toolkit还有不少缺点,比如你不能直接修改initialState,上面的initialState包含 token和refresh_token两个值,你直接state = active.payload 这个操作他是不认的,我在上图用了三种方法,当然你也可以一个一个点语法赋值,当然也可以给initialState中创建一个login,login中包含userToken,userToken中包含token和refresh_token,这时你可以获取数据直接覆盖userToken,该操作也会让你明白使用useAppSelector获取多层对象中的某个属性值是多痛苦的事,说不定以后都会变的更好。
最后还是觉得,Redux Toolkit 比之前的react-redux 使用的方法好数倍!