Redux/RTKQuery 碎碎念
redux 还活着🏊,且还有了 @reduxjs/toolkit 这个库值得探索。redux 推荐直接使用 @reduxjs/toolkit 工具。
本来想写一篇“高质量”文章,介绍 redux 方方面面,后面还是回归“简-易”,看技术文章主要还是轻松愉快。
为什么需要状态管理?目前社区有哪些状态管理工具?React 生态中为什么有如此之多的状态管理?
React 本身关注 UI 曾,所提给供组件的管理状态能力有限,适用的场景也有限。
状态 | 说明 | 渲染特点 |
---|---|---|
props | 适用于函数组件间传递状态 | 子组件重新渲染 |
useState | 适用于函数组件内部状态 | 本组件/子组件重新渲染 |
this.state | 适用于类组件内部状态 | 本组件/子组件重新渲染 |
context api | 适用于全局状态 | 全局重新渲染,性能差 |
考虑到适用场景,性能等因素,React 的状态管理工具,本着不同的理念被开发出来:
方向 | 示例 |
---|---|
单向数据流 | flux/redux/zustand |
双向数据绑定 | mobx/valtio |
状态原子化 | jotai/recoil |
有限状态机 | xstate |
除此之外,类似于 rxjs 库,实现自己的功能同时,也具备状态管理功能,但是并非专业做状态管理。
Redux 脱胎于 flux 与 React 一脉相承,下面文章中更多探索 @Reduxjs/toolkit。
函数式编程基础
函数 | 说明 |
---|---|
简单函数 | 完成一些简单的函数体封装 |
高级函数 HOF | 函数中返回函数(难度开始增加,至少两层函数) |
闭包函数 | 能够捕获所在函数的环境中的变量 |
函数柯里化 | 闭包与高阶函数的结合 |
函数串联 | 函数之间进行串联执行技巧 |
函数中间件 | 串行函数之间组合成串联链,形成中间件 |
安装 @reduxjs/toolkit 以及简单使用
pnpm install @reduxjs/toolkit
@reduxjs/toolkit 依赖与 redux/redux-thunk 所以直接从 @reduxjs/tooljs 中引入即可。@reduxjs/toolkit 分为两个部分:
功能增强 | 说明 |
---|---|
以 slice 为首的 api | 方便使用 reducer/action/thunk |
rtk-query | 方便使用 提供了数据访问能力 |
快速尝试
使用官方的create-react-app 来初始化一个项目,来快速尝试:
npx create-react-app my-app --template redux-typescript
创建的结果如下:
Redux 设计示意图

store 创建方式的不同
// 从 redux 创建
export default function createStore<
S,
A extends Action,
Ext = {},
StateExt = never
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {}
// 从 @reduxjs/toolkit 创建(官方推荐)
export function configureStore<
S = any,
A extends Action = AnyAction,
M extends Middlewares<S> = [ThunkMiddlewareFor<S>]
>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M> {}
store 对象及其属性
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
创建 store 内部实现分支
必须传递参数: reducer 函数
没有 reducer 直接抛出错误
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
reducer 必须是一个函数,或者组合多个reducer 之后的函数
enhancer参数: 决定了 redux 是否要实现中间件系统
- 有 enhancer (实现中间件逻辑,创建 store)
if (typeof enhancer !== 'undefined') {
// ...
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
- 没有 enhander (无需处理中间件逻辑,功能相对简单)
// ...
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
function getState(): S {
// ...
return currentState as S // 当根state
}
// 订阅-取消订阅
function subscribe(listener: () => void) {
// ...
nextListeners.push(listener)
return function unsubscribe() {
// ...
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
function dispatch(action: A) {
// ...
currentState = currentReducer(currentState, action)
// ...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
派发一个默认的 action
dispatch({ type: ActionTypes.INIT } as A)
reducer 是分片的,组合 reducer 时需要重新分配映射对象。
Redux 中的函数串行 compose
函数串行 compose 执行是 Redux 实现中间件系统的基础:
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce(
(a, b) =>
(...args: any) =>
a(b(...args))
)
}
Redux 中间件的支持 applyMiddleware
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return (createStore: StoreEnhancerStoreCreator) =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
middlewares 中间件函数 传递 middlewareAPI 组合成中间件数组,配合 compose 函数串联执行得到一个函数,然后传入 store.dispatch 函数得到中间件中的 dispatch 函数用于派发 action.(此处是 Redux 函数理解最为困难的地方),单数如果熟悉 express 等其实这个也很容易理解了。
重要 reducer
reducer 要理解为 redux 版本的 reduce 累加器函数,不同的是 reducer 的state 是之前的值,dipatch 是下一个值的调度器,是一个没有副作用的(给定输入,有固定输出)的纯函数。
定义一个 reducer 函数
// 接受 state, action 对象的纯函数(固定输入-固定输出)
const reducer = (state, action) => {
switch (action.type) {
case "ADD":
return [ ...state, action.id ]
case "REMOVE":
return state.filter(id => id !== action.id)
default:
return state
}
}
特点:固定输入,有固定的输出,没有副作用
combineReducers 组合 reducer 函数
- 返回 combination reducer 函数
combineReducers 组合之后,reducers: ReducersMapObject 对象与原来 reducer 具有了对象的映射关系,reducers 的 key 可以自己定义。
export default function combineReducers(reducers: ReducersMapObject) {
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers);
return function combination(
state: StateFromReducersMapObject<typeof reducers> = {},
action: AnyAction
) {
const nextState: StateFromReducersMapObject<typeof reducers> = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
}
return hasChanged ? nextState : state
}
}
后面要体积的 createSlice options 的 name, 在组合reducer 就可以命名 reducer。
createReducer 工厂函数
- builder 添加案例/匹配案例,更加趋近与函数式编程
export function createReducer<S extends NotFunction<any>>(
initialState: S | (() => S),
mapOrBuilderCallback:
| CaseReducers<S, any>
| ((builder: ActionReducerMapBuilder<S>) => void),
actionMatchers: ReadonlyActionMatcherDescriptionCollection<S> = [],
defaultCaseReducer?: CaseReducer<S>
): ReducerWithInitialState<S> {}
官网示例:
使用时推荐与 createAction 一起使用, createAction 会创建一个 actionCreator 函数, 方便 addCase 传参
import { createAction, createReducer } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction<number>('counter/incrementByAmount')
const initialState = { value: 0 } as CounterState
const counterReducer = createReducer(initialState, (builder) => {
builder
.addCase(increment, (state, action) => {
state.value++
})
.addCase(decrement, (state, action) => {
state.value--
})
.addCase(incrementByAmount, (state, action) => {
state.value += action.payload
})
})
slice 切片管理:reducer/actions/state
由于 reducer 是具有切片能力,@reduxjs/toolkit 将
export function executeReducerBuilderCallback<S>(
builderCallback: (builder: ActionReducerMapBuilder<S>) => void
): [
CaseReducers<S, any>,
ActionMatcherDescriptionCollection<S>,
CaseReducer<S, AnyAction> | undefined
] {
const actionsMap: CaseReducers<S, any> = {}
const actionMatchers: ActionMatcherDescriptionCollection<S> = []
let defaultCaseReducer: CaseReducer<S, AnyAction> | undefined
const builder = {
addCase(
typeOrActionCreator: string | TypedActionCreator<any>,
reducer: CaseReducer<S>
) {
if (process.env.NODE_ENV !== 'production') {
if (actionMatchers.length > 0) {
throw new Error(
'`builder.addCase` should only be called before calling `builder.addMatcher`'
)
}
if (defaultCaseReducer) {
throw new Error(
'`builder.addCase` should only be called before calling `builder.addDefaultCase`'
)
}
}
const type =
typeof typeOrActionCreator === 'string'
? typeOrActionCreator
: typeOrActionCreator.type
if (type in actionsMap) {
throw new Error(
'addCase cannot be called with two reducers for the same action type'
)
}
actionsMap[type] = reducer
return builder
},
addMatcher<A>(
matcher: TypeGuard<A>,
reducer: CaseReducer<S, A extends AnyAction ? A : A & AnyAction>
) {
actionMatchers.push({ matcher, reducer })
return builder
},
addDefaultCase(reducer: CaseReducer<S, AnyAction>) {
defaultCaseReducer = reducer
return builder
},
}
builderCallback(builder)
return [actionsMap, actionMatchers, defaultCaseReducer]
}
createSlice 切片管理 reducer/action
createSlice 相对 reducer 使用更加简单,但是它融合了 action/reducer/immer, 不在需要手动调用 createReducer 和 createAction。
option | 说明 |
---|---|
name | 切片名可以用于 reducer 名 |
initialState | 初始化 state |
reducers | 计算函数 |
extraReducers | 额外计算函数 builder 函数田间 |
import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState = { value: 0 } as CounterState
const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment(state) {
state.value++
},
decrement(state) {
state.value--
},
incrementByAmount(state, action: PayloadAction<number>) {
state.value += action.payload
},
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
reducer 从一个简单的函数,到 createSlice 来用切片管理 reducer 和 action 手动,极大的减少了 redux 的样板代码。
相濡以沫的 dispatch/action
dispatch 属于 store 外部,一般与 React UI 结合派发 action 对象
Action TS 基础类型
export interface Action<T = any> {
type: T
}
export interface AnyAction extends Action {
[extraProps: string]: any
}
export interface ActionCreator<A, P extends any[] = any[]> {
(...args: P): A
}
export interface ActionCreatorsMapObject<A = any, P extends any[] = any[]> {
[key: string]: ActionCreator<A, P>
}
基础 action 对象 {type: 'xxx', payload: any}
const action = {type: "add", payload: 1},
action 工厂函数:actionCreator
进阶为了方便管理,redux 将 这个对象函数化,需要的时候就是调用,来处理不同的 payload, 函数时又进了一步
const actionCreator = (payload) => {
return {
type: "add",
payload
}
}
多 action 与 dispatch 绑定
bindActionCreators时 redux 提供的 多 actionCreator 与 dispatch 的管理(钩子函数组件中使用场景很少)
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function (this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
export default function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
const boundActionCreators: ActionCreatorsMapObject = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
这个功能在 class 组件时候很有用,但是在 function hooks 时代用的比较少了,因为 reducer 和 action 使用 slice 切片来管理,更加简单方便。
工厂函数 createAtion, 返回时一个 actionCreator 函数, 并非一个 action 对象,actionCreator 具备进行方法和属性:
- toString 方法 返回 type
- type 属性
- match 与当前 action.type 进行比较
import { createAction } from '@reduxjs/toolkit'
const increment = createAction<number | undefined>('counter/increment')
let action = increment()
// { type: 'counter/increment' }
action = increment(3)
// { type: 'counter/increment', payload: 3 }
切片 slice 中解构 action
createSlice 返回的对象中包含 actions(就是 reducers 中函数名),action 在切片中更好管理(直接不用写 action 相关的样板代码)
export const { increment, decrement, incrementByAmount } = counterSlice.actions
createSlice 的实现
接收 options 作为参数, 返回一个对象,包含:
option | 说明 |
---|---|
name | slice name 可作为 reducer name map |
reducer | 计算函数 |
actions | 所有的 action 对象 |
caseReducers | -- |
getInitialState | 获取初始值 |
export function createSlice<
State,
CaseReducers extends SliceCaseReducers<State>,
Name extends string = string
>(
options: CreateSliceOptions<State, CaseReducers, Name>
): Slice<State, CaseReducers, Name> {}
return {
name,
reducer(state, action) {
if (!_reducer) _reducer = buildReducer()
return _reducer(state, action)
},
actions: actionCreators as any,
caseReducers: sliceCaseReducersByName as any,
getInitialState() {
if (!_reducer) _reducer = buildReducer()
return _reducer.getInitialState()
},
}
处理 reducers
const reducerNames = Object.keys(reducers)
reducerNames.forEach((reducerName) => {
const maybeReducerWithPrepare = reducers[reducerName]
const type = getType(name, reducerName)
let caseReducer: CaseReducer<State, any>
let prepareCallback: PrepareAction<any> | undefined
if ('reducer' in maybeReducerWithPrepare) {
caseReducer = maybeReducerWithPrepare.reducer
prepareCallback = maybeReducerWithPrepare.prepare
} else {
caseReducer = maybeReducerWithPrepare
}
sliceCaseReducersByName[reducerName] = caseReducer
sliceCaseReducersByType[type] = caseReducer
actionCreators[reducerName] = prepareCallback
? createAction(type, prepareCallback)
: createAction(type)
})
dispatch 同步异步
同步 dipatch 一个对象
普通 dispatch(action) 是同步的, dipatch 在使用了 thunk 中间见之后,可以派发一个函数作为 action。
dispatch({type: "add", payload: {}})
异步 dispatch 一个effect函数
与 thunk 获取数据方式不同的时,redux-saga 在 redux 中间,捕获 action (同步)来处理异步任务。到那时 saga 使用 Generator 函数,与 react hooks 结合不是很紧密。如果使用 hooks, 可以考虑使用 rtk query 中 自动包含了 thunk 函数,开箱即用。
createAsyncThunk: dispatch 与 thunk
createAsyncThunk 是 thunk 的工厂函数,快速定义一个异步 thunk action
参数 | 说明 |
---|---|
typePrefix | 请求前缀 |
payloadCreator | 请求函数,返回数据 |
options | 数据 |
- actionCreator 函数 (Object.assign 合并下面的数据)
payloadCreator | 说明 |
---|---|
pedding | actionCreator promise pendding 状态 |
rejected | actionCreator promise rejected 状态 |
fulfilled | actionCreator promise fulfilled 状态 |
actionCreator 函数 返回一个匿名函数,匿名函数返回一个 promise:
- promise 函数实例
- abort thunk 函数请求取消
- requestId 请求 id
- arg 参数
- unwrap 获取结果
示例:派发一个异步 thunk 函数 action
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
dispatch(fetchUserById(123))
派发之后,可以通过 fetchUserById 与 extrReducers builder 函数中捕获并设置数据。因为是异步 thunk 所以dispatch 也可以使用then方法来获取异步数据。
创建异步 thunk 源码
const fulfilled = createAction(typePrefix + '/fulfilled', () => ({}));
const pending = createAction(typePrefix + '/pending', () => ({}));
const rejected = createAction(typePrefix + '/rejected', () => ({}));
function actionCreator(
arg: ThunkArg
): AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> {
//...
return Object.assign(promise as Promise<any>, {
abort,
requestId,
arg,
unwrap() {
return promise.then<any>(unwrapResult)
},
})
}
return Object.assign(
actionCreator as AsyncThunkActionCreator<
Returned,
ThunkArg,
ThunkApiConfig
>,
{
pending,
rejected,
fulfilled,
typePrefix,
}
)
本质就是 actionCreator 与 promise 的结合,可以方便的使用 promise 的各种状态与reducer一起结合。
中间件
中间件系统, 是 redux 增强 redux 能力的, 熟悉 ndoejs express 框架的对中间应该不陌生。中间件主要来实现: 日志,监控数据,路由等细粒度。
使用中间件
import { createStore, applyMiddleware } from 'redux'
import reducer from './reducers'
const middleware = [ thunk ]
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger())
}
const store = createStore(
reducer,
applyMiddleware(...middleware)
)
applyMiddleware 是重要的函数,用于将中间件增强可以称为 enhancer middleware function,本质就是返回一个函数。
真正的开始从 applyMiddleware 调用开始
函数式: 从一个函数到另外一个函数,高阶函数,函数 curry 化, 函数串联,应用的就是十分广泛。
applyMiddleware(...middleware) 调用会得到一个 enhancer 函数用来增强 store
enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
enhancer 其实就是一个柯里化的函数,让 createStore 函数复用, enhancer(createStore) 返回一个匿名函数, 匿名函数调用之后返回一个 store 对象,下面是匿名函数的实现:
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
map 一个数组,注入 中间件需要 api, 然后中间会都会调用此一次,得到一个函数,下面式 thunk 函数
const thunk = ({ dispatch, getState }) =>
next =>
action => {
// The thunk middleware looks for any functions that were passed to `store.dispatch`.
// If this "action" is really a function, call it and return the result.
if (typeof action === 'function') {
// Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
return action(dispatch, getState, extraArgument)
}
// Otherwise, pass the action down the middleware chain as usual
return next(action)
}
全部都是函数,就好像都《百年孤独》,都是同样类型的名字。脑子的堆栈好像不够用了。《思考, 快与慢》中系统1 提供给系统2,但是系统2 算力不足,解决办法,就是多尝试,所角度的尝试写,思考。
中间件函数遍历,调用完毕之后,产生一个数组,redux 命令为 chain, 本质就是一个 Function[]
集合,目的式方便 compose 函数,compose 的作用就是串行的执行 chain 中的函数(也就是串联执行中间件函数中的函数体)。注意返回不是真正的执行而是得到一个函数,这个函数,需要传入 store.dispatch 得到一个新的 dispatch, 这是一个增强的 dispatch 函数。
redux 中常用中间件
名称 | 说明 |
---|---|
redux-logger | state变化中间件,方便调试 |
redux-promise | promise 化 |
redux-thunk | 异步thunk函数 |
redux-saga | 使用生成器的状态管和副作用管理 |
redux-persist | 数据持久化 |
实体
实体主要式解决结构性数据 + 一组完整的增删改查 api。实体就留给读者自行探索适用场景。
RTK query 简介
RTK Query 从其他开创数据获取解决方案的工具中汲取灵感,例如 Apollo Client、React Query、Urql 和 SWR,但在其 API 设计中添加了独特的方法:
核心 api | 说明 |
---|---|
createApi | RTK Query 功能的核心。它允许您定义一组端点来描述如何从一系列端点检索数据,包括如何获取和转换该数据的配置。在大多数情况下,您应该在每个应用程序中使用一次,根据经验,“每个基本 URL 一个 API 切片”。 |
fetchBaseQuery()) | 请求包装器,可自定义,使用 axios 等 |
<ApiProvider /> | 一个 Provider 上下文的组件, |
setupListeners() | 用于启用refetchOnMount 和refetchOnReconnect 行为的实用程序。 |
创建 baseQuery
- 基础使用
- 基于 axios
- 基于 graphql
- ...
创建一个基本的 baseApi
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query/react';
import { fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { message } from 'antd';
// actions
import { logout } from '@/store/reducers/globalReducer';
export const _baseQuery = fetchBaseQuery({
baseUrl: '/api',
prepareHeaders: (headers, { getState }) => {
const { userInfo } = (getState() as any).globalState;
if (userInfo.access_token) {
headers.set('authorization', `Bearer ${userInfo.access_token}`);
}
headers.set(
'Content-Type',
'application/x-www-form-urlencoded;charset=UTF-8',
);
return headers;
},
});
export const baseQuery: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
let result: any = await _baseQuery(args, api, extraOptions);
if (result && result.data && result.data.code !== 0) {
message.error(result.data.message);
if (result.data.code === 3000) {
api.dispatch(logout);
window.location.href = '/user/login';
}
}
return result;
};
对于这个基本的请求,增加了 token 请求头的添加,对请求的结果进行了初步处理,返回 data.code 不是 0 时,toast 一个错误,如果是 3000 表明是授权失败,下面是一个简单的使用示例:
import type { IData } from '@/pages/dashboard/types';
import { baseQuery } from './customBaseQuery';
import { createApi } from '@reduxjs/toolkit/query/react';
export const dashboardApi = createApi({
baseQuery,
reducerPath: 'dashboard',
tagTypes: ['Dashboard'],
endpoints: (builder) => ({
getDashboardData: builder.query({
query: () => ({ url: '/dashboard' }),
transformResponse: (response: any) => response.data as IData,
}),
}),
});
export const { useGetDashboardDataQuery } = dashboardApi;
createApi 基本用法
参数: options
参数 options | 说明 |
---|---|
baseQuery | 基础请求工具函数(可以自定义) |
endpoints | 端点对应着请求接口 |
extractRehydrationInfo | - |
tagTypes | 缓存生效失效标签 |
reducerPath | api注册reducer |
serializeQueryArgs | 自定义序列化参数 |
keepUnusedDataFor | 默认60的缓存时间 |
refetchOnMountOrArgChange | 重新获取数据在挂在或者参数变化(false) |
refetchOnFocus | 在窗口重新获取聚焦时(false) |
refetchOnReconnect | 重新链接网络时 |
端点: 常用参数
参数 options | 说明 |
---|---|
query | 查询地址函数 |
transformResponse | 转换响应数据 |
providesTags | 依据 options.tagTypes 提供 tag |
invalidatesTags | 一般用于突变 |
keepUnusedDataFor | 适用于端点的缓存时间 |
其他参数不做说明,需要可自行探索...
示例:使用 rtk query 对于文章的增删改查的 Restful API
- GET 请求放在 query 端点中
- POST/PUT/PATCH/DELETE 改变数据放入突变中
import qs from 'qs';
import { baseQuery } from './customBaseQuery';
import { transformResponse, transformResponseNoData } from '@/utils/index';
import { createApi } from '@reduxjs/toolkit/query/react';
export const articleApi = createApi({
baseQuery,
reducerPath: 'article',
tagTypes: ['Article', 'MyArticle'],
endpoints: (build) => ({
getArticles: build.query({
query: () => ({ url: '/articles' }),
transformResponse,
providesTags: ['Article', 'MyArticle'],
}),
getArticleDetail: build.query({
query: (id) => ({ url: `/articles/${id}` }),
transformResponse,
providesTags: ['Article', 'MyArticle'],
}),
addArticle: build.mutation({
query: (data) => ({
url: '/articles',
method: 'POST',
body: qs.stringify(data),
}),
invalidatesTags: () => [{ type: 'Article' }],
transformResponse: transformResponseNoData,
}),
delArticleById: build.mutation({
query: ({ id }) => ({
url: `/articles/${id}`,
method: 'DELETE',
}),
invalidatesTags: () => [{ type: 'Article' }],
transformResponse: transformResponseNoData,
}),
updateArticle: build.mutation({
query: ({ id, ...data }) => ({
url: `/articles/${id}`,
method: 'PUT',
body: qs.stringify(data),
}),
invalidatesTags: () => [{ type: 'Article' }],
transformResponse: transformResponseNoData,
}),
}),
});
export const {
useGetArticlesQuery,
useGetArticleDetailQuery,
useAddArticleMutation,
useDelArticleByIdMutation,
useUpdateArticleMutation,
} = articleApi;
在非 redux store 中使用
import * as React from 'react';
import { ApiProvider } from '@reduxjs/toolkit/query/react';
import { MainApp } from './MainApp';
imoprt api from './articleApi'
function App() {
return (
<ApiProvider api={api}>
<Pokemon />
</ApiProvider>
);
}
看源码知道,其实 ApiProvider 中实现了一个 store,只是没有显示的创建。实现代码如下:
export function ApiProvider<A extends Api<any, {}, any, any>>(props: {
children: any
api: A
setupListeners?: Parameters<typeof setupListeners>[1] | false
context?: Context<ReactReduxContextValue>
}) {
const [store] = React.useState(() =>
configureStore({
reducer: {
[props.api.reducerPath]: props.api.reducer,
},
middleware: (gDM) => gDM().concat(props.api.middleware),
})
)
// Adds the event listeners for online/offline/focus/etc
useEffect(
(): undefined | (() => void) =>
props.setupListeners === false
? undefined
: setupListeners(store.dispatch, props.setupListeners),
[props.setupListeners]
)
return (
<Provider store={store} context={props.context}>
{props.children}
</Provider>
)
}
createApi 的构建函数源码
createApi 主要是注入端点,此时并不会发送请求
const createApi = /* @__PURE__ */ buildCreateApi(
coreModule(),
reactHooksModule()
)
高阶函数:buildCreateApi
主要工作:
- 接受 modules 作为参数(闭包参数)
- 返回 baseCreateAPi 函数
- 初始化 optionsWithDefault/context/api/initializedModules
export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
...modules: Modules
): CreateApi<Modules[number]['name']> {
return function baseCreateApi(options) {}}
baseCreateAp 函数调用时,返回 api.injectEndpoints 也即是 api 对象
api 对象 | |
---|---|
injectEndpoints | 注入端点 |
enhanceEndpoints | 注入端点增强 |
return api.injectEndpoints({ endpoints: options.endpoints as any })
初始化 modules 中完成了,适用于 UI 中请求钩子的生成
onst initializedModules = modules.map((m) =>
m.init(api as any, optionsWithDefaults, context)
)
export const reactHooksModule = ({
batch = rrBatch,
useDispatch = rrUseDispatch,
useSelector = rrUseSelector,
useStore = rrUseStore,
unstable__sideEffectsInRender = false,
}: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => ({
name: reactHooksModuleName,
init(api, { serializeQueryArgs }, context) {
const anyApi = api as any as Api<
any,
Record<string, any>,
string,
string,
ReactHooksModule
>
const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
api,
moduleOptions: {
batch,
useDispatch,
useSelector,
useStore,
unstable__sideEffectsInRender,
},
serializeQueryArgs,
context,
})
safeAssign(anyApi, { usePrefetch })
safeAssign(context, { batch })
return {
injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
} = buildQueryHooks(endpointName)
safeAssign(anyApi.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
})
;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery
;(api as any)[`useLazy${capitalize(endpointName)}Query`] =
useLazyQuery
} else if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName)
safeAssign(anyApi.endpoints[endpointName], {
useMutation,
})
;(api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation
}
},
}
},
})
createApi 构建 hooks 流程
上图时 createApi 的构建 api 的大致流程,构建分为三个部分:
- query 查询
- mutation 突变
- prefetch 预取
rtk query 的请求是如何保存到 redux 里面的
缓存时 rtk-query 重要的功能。在请求发送出去之后,或默认的存储 60, 不会在重新发送http请求,而是从缓存redux缓存中获取。
- 优点:同一个 React 组件,不会发送相同的请求,但是如果参数不一样,还是会发送不同的请求。
- 缺点: 要重新理解缓存行为
缓存机制行为与破除强制缓存方法
-keepUnusedDataFor
控制缓存的时间(默认值:60s),可以放在全局,局部的,和钩子函数中,三个位置。
- 钩子函数调用之后 提供 refetch 强制发送请求,覆盖现有数据。
refetchOnFocus
在窗口重新获取焦点的时候发送请求(配合:#setupListeners
), 实现核心时间 'focus','visibilitychange','online', 'offline' 时间- refetchOnMountOrArgChange 实时发送请求
使用 tag 缓存
数据请求,数据时列表的场景非常常见。将整个列表的 添加 id 的内容,
const providesTags = [{type: 'Tag', id: 'id1'}, {type: 'Tag', id: 'id2'}, {type: 'Tag', id: 'id3'}, 'Tag']
让 Tag 失效: 编辑列表的中某一条('id1')
invalidatesTags: (result, error, arg) => [{ type: 'Tag', id: arg.id }],
下面时失效的图表: Automated Re-fetching | Redux Toolkit (redux-toolkit.js.org)
向错误提供缓存,例如登录失效的后,获取到的数据是没有授权。此时将没有授权缓存起来。重新调用登录接口之后,让没有授权的缓存接口失效,(这里感觉就很话就很麻了。缓存让使用请求多了一个层级面)
其他常用功能
分页功能
- 使用 useState 状态进行控制
示例:使用 @ant-design/pro-components ProTable 获取数据以及分页
import type { FC } from 'react';
import React, { useMemo, useState } from 'react';
// components
import { ReloadOutlined } from '@ant-design/icons';
import { ProTable, PageContainer } from '@ant-design/pro-components'
// hooks
import { useNavigate } from 'react-router-dom';
// hooks:api
import { useGetArticlesQuery, useDelArticleByIdMutation } from '@/api/article';
// columns
import { createIndexcolumns } from './IndexColumns';
const ArticleList: FC = () => {
const navigate = useNavigate();
const proTableRef = React.useRef<any>();
const [page, setPage] = useState(1);
const {
data: dataSource,
refetch,
isLoading
} = useGetArticlesQuery({
pageNum: page,
pageSize: 20,
});
const [delArticleById] = useDelArticleByIdMutation()
const col = useMemo(() => {
return createIndexcolumns(navigate, delArticleById);
}, []);
return (
<PageContainer
ghost
header={{
title: '文章列表',
breadcrumb: {},
}}
style={{ backgroundColor: "#fff", margin: "20px 20px" }}
>
<ProTable
search={false}
loading={isLoading}
actionRef={proTableRef}
dataSource={dataSource?.list || []}
toolbar={{
title: '分类列表',
settings: [
{
icon: <ReloadOutlined />,
tooltip: '刷新',
key: 'reload',
onClick: () => {
refetch();
},
},
],
}}
pagination={{
total: dataSource?.total,
onChange(page) {
setPage(page);
},
}}
columns={col}
rowKey="_id"
/>
</PageContainer>
);
};
export default ArticleList;
其他功能
功能 | 说明 |
---|---|
提取预取数据 prefetch | 需要提前获取数据 |
处理错误 | 请求错误 |
条件获取 | skip控制 |
轮询接口 | pollingInterval |
... |
参考
- Redux 官方 redux.js.org/
- @reduxjs/toolkit 官方 redux-toolkit.js.org/
- @reduxjs/toolkit 仓库 github.com/reduxjs/red…
- @reduxjs/toolkit 仓库示例 github.com/reduxjs/red…
- flux 官方 facebook.github.io/flux/
- ProComponents ProTable procomponents.ant.design/components/…
- 图解 Flux zhuanlan.zhihu.com/p/20263396
- Flux 架构入门教程 ruanyifeng.com/blog/2016/0…
- MapReduce核心思想与工作过程 zhuanlan.zhihu.com/p/377204048
- 前端技术栈(二):从Flux到Redux blog.csdn.net/TurkeyCock/…
转载自:https://juejin.cn/post/7127632794309623838