文章目录
前言
React的状态管理库一开始是个第三方库,叫做Redux。
后来Facebook发现这个库的使用量很大,于是官方就也出了个功能一样的库叫做react-redux。
这就是原本Redux和react-redux的关系。
我之前使用过类组件时期的Redux、函数组件初期的Redux,发现都特别难用,哪些简化Redux理解和操作的第三方库也不是那么好用。
对应的调试工具还是类似vsconsole那种入侵代码的库。
整体和当时的VueX对比,使用体验还是差很多的。
如今的react-redux(不知道是从哪一代更新的)变得异常好用,而且使用方式简化好多,也好理解,并且有浏览器插件的调试工具。已经完全不需要用第三方的了。
store、actions、reducers概念
以下是我对这三者的理解,但还停留在类组件时期,不知道现在的意思有没有变化(有空再去官网瞅瞅)。
-
store:是redux的指挥中心,接收actions发过来的任务,安排给reducers去执行。
-
actions:可以理解为这个人就是一个传话筒,负责接收到组件派发的任务后通知store,每个任务的通知是一个对象的形式,内部有type字段用来标记要做的是什么事,data字段用来存放需要的数据。
-
reducers:他负责执行store给的任务,并把最新的state给返回给store,store再把数据传递给组件。
看完我的大白话描述是不是对这些概念就清晰很多了。
使用
使用方式直接看 官方网址 ,建议直接从教材那一栏开始看起。非常简单:
一些简化思路
state获取简化
获取store中某个值其实封装成一个hook更好,利于复用和阅读,直接在redux的文件夹创建一个hooks文件,然后写入hook,官方推荐了一个写法:
import { TypedUseSelectorHook, useSelector } from "react-redux";
import type { RootState } from "@/redux/index";
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export default useAppSelector;
使用:
const value = useAppSelector((state) => state.global.value);
感觉好处就是不需要每次获取state的时候传入TS类型。
我也来推荐一个自己发现的更好方法,例如获取store里的pageInfo对象数据:
import { useSelector } from 'react-redux'
import type { StateType } from '../store'
import type { PageInfoType } from '../store/pageInfoReducer' // 某个redux模块
function useGetPageInfo() {
const pageInfo = useSelector<StateType>(state => state.pageInfo) as PageInfoType
return pageInfo
}
export default useGetPageInfo
获取直接这样:
const { title } = useGetPageInfo()
reducer调用简化
同样官方也推荐了个写法:
import { useDispatch } from "react-redux";
import type { AppDispatch } from "@/redux/index";
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
const useAppDispatch: () => AppDispatch = useDispatch;
export default useAppDispatch;
但使用起来和原来没太大区别,所以可以这样写,先在Redux的主入口写:
import { configureStore } from "@reduxjs/toolkit";
import global from "./global";
const store = configureStore({
reducer: {
global,
},
});
export default store;
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>;
// 最主要的是这个
export type AppDispatch = Pick<
ReturnType<typeof store>,
"dispatch"
>["dispatch"];
然后写个hooks:
import { useDispatch } from "react-redux";
import { Slice } from "@reduxjs/toolkit";
import { AppDispatch } from "../redux/index";
type GetArrFirst<T> = T extends [infer Res] ? Res : unknown;
const useAppDispatch = () => useDispatch<AppDispatch>();
const useCreateReduxFunction = <S extends Slice>(slice: S) => {
return <T extends keyof S["actions"]>(name: T) => {
const dispatch = useAppDispatch();
const actionCreator = (slice.actions as S["actions"])[name];
return (payload?: GetArrFirst<Parameters<typeof actionCreator>>) => {
dispatch(actionCreator(payload));
};
};
};
export default useCreateReduxFunction;
然后在模块中包裹下Slice:
export const useGlobalReduxFunction = createReduxFunction(GlobalSlice);
然后我们就能轻松调用这个模块的reducer了:
const setCurrentAssistant = useGlobalReduxFunction('setCurrentAssistant');
一个模块的reducer被执行后同时修改另一个模块的state
目前发现个不错的写法,例如我有个叫agentSlice的模块里面有个aaa方法要修改globalSlice模块里的某个state变量bbb,可以做如下步骤。
首先在agentSlice导出agentSlice的reducer:
export default agentSlice.reducer;
然后在globalSlice中引入:
import { agentSlice } from '../agent/index';
并且在reducer同级后加个:
extraReducers(builder) {
builder.addCase(
agentSlice.actions.aaa,
(state, action) => {
state.bbb= action.payload;
}
);
},
react-redux调试工具(推荐)
安装浏览器插件Redux DevTools,控制台就能看到了,可以自行百度下~
用immer改善编码体验(必装)
我们知道reducer执行里如果要修改store,需要返回一个新的store,就和写setState一样,有时候挺麻烦的。可以借助immer第三方工具库改成和Vue一样直接改写的编码风格。
例如:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import produce from 'immer'
export type UserType = {
id?: string
user?: string
}
// 假设初始值
const UserTypeState: UserType = {
id: '1',
user: 'admin'
}
export const userInfoSlice= createSlice({
name: 'userInfo', // 模块名称
initialState: UserTypeState, // 设置初始state数据
reducers: { // 设置reducer
changeUserName : produce((draft: UserTypeState, action: PayloadAction<{ userName: string }>) { // produce包一层
draft.user = action.payload // 直接修改即可
}),
}
})
export const { changeUserName } = userInfoSlice.actions // 把要用到的actions都导出去
export default userInfoSlice.reducer // 导出的是reducer
这个draft是个引用类型,从它那里解构出来的变量直接修改了也是生效的。
借助redux-undo实现撤销重做(推荐)
如果以后产品和你说要实现撤销重做功能,一定要第一时间想到redux-undo这个库。
在汇合文件index.js中,假设有个模块edit,负责某个编辑功能:
import { configureStore } from '@reduxjs/toolkit'
import userInfo, { UserType } from './userInfo.js'
import EditInfo, { EditType } from './edit.js'
// 所有state汇合起来
export type StateType = {
userInfo: UserType
editInfo: EditType
}
export default configureStore({
reducer: {
userInfo,
// 扩展其他模块
editInfo
}
})
edit模块的很多操作都写在了redux对应的模块中,咱们需要对这个edit模块的操作添加上撤销重做的特性。加入redux-undo可以改写成这样:
import { configureStore } from '@reduxjs/toolkit'
import userInfo, { UserType } from './userInfo.js'
import editInfo, { EditType } from './edit.js'
import undoable, { excludeAction, StateWithHistory } from 'redux-undo'
// 所有state汇合起来
export type StateType = {
userInfo: UserType
editInfo: StateWithHistory<EditType> // 1 泛型包裹一下
}
export default configureStore({
reducer: {
userInfo,
// 包裹
editInfo:undoable(editInfo, {
limit: 20, // 限制 undo 20 步
filter: excludeAction([ // 这里是过滤哪些reducer操作不需要加撤销重做功能,每项格式为: 文件地址+ /actions名称
'edit/resetComponents',
'edit/changeSelectedId',
'edit/selectPrevComponent',
'edit/selectNextComponent',
]),
})
}
})
在别的地方只需要这样调用即可撤销重做:
import { ActionCreators as UndoActionCreators } from 'redux-undo'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
// 撤销
function undo() {
dispatch(UndoActionCreators.undo())
}
// 重做
function redo() {
dispatch(UndoActionCreators.redo())
}
获取store要多拿一个值present
const pageInfo = useSelector<StateType>(state => state.pageInfo.present) as PageInfoType
尾巴
建议看完后再去官网上学习下,更全面的了解Redux。