redux生态选择标准与范例,介绍redux原理、工具集、中间件、增强器,提供一套可靠的reudx生态选择标准
说明:我们通过redux将所有状态集中管理,从使用react去存储和维护自身相关的状态的复杂关系中解脱。redux官方推荐了很多工具集,中间件,增强器来简化redux写法和增加redux功能,项目开发中可能出现同一种作用的工具集或中间件被同时引入,影响代码阅读和开发效率。所以需要有一套可靠的redux生态选择标准,说明选择标准,并提供使用范例
一、生态选择
Redux + Redux Toolkit
选择依据:
1、redux风格指南,工具包推荐
官方网址:redux.js.org/style-guide…,redux.js.org/redux-toolk…
2、redux、react-redux、redux-thunk、redux-saga、immer分析
3、Redux Toolkit分析
二、redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
核心概念:
源码分析:
redux官方地址:www.redux.org.cn/docs/introd…
1、 入口:index.ts
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
导出对外提供的API
2、createStore.ts
创建一个store来以存放应用中所有的 state
//只看核心代码,忽略函数重载和异常处理,和一些简单的util
/**
* @param reducer 给定当前状态树和要处理的动作
* @param preloadedState 初始状态
* @param enhancer 中间件等第三方能力增强器
*
* @returns store,支持读取状态、调度操作并订阅更改
*/
export default function createStore(reducer, preloadedState, enhancer) {
/**
* 通过传入参数的类型,判断第二个参数是preloadedState,还是enhancer
*/
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
/**
* 使用了其他中间件和增强器时直接返回enhancer(createStore)(reducer,preloadedState)
*/
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
}
// 利用闭包存储变量
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false
/**
* 读取store 管理的state状态树
*/
function getState(): S {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState as S
}
/**
* 添加侦听器。每次dispatch时都被调用
* 核心代码就一行:nextListeners.push(listener),将传入的函数存在nextListeners中,dispatch时再进行调用,一般不会直接用原生的subscribe,
* @param 每次调度都会调用的回调
* @returns 删除此更改侦听器的函数
*/
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
/**
* 触发state更改,仅支持普通对象操作,如果想发送 Promise、Observable、thunk 或其他东西需要将store包装到相应的中间件
* 这里的dispatch方法核心还是只有一行:currentState = currentReducer(currentState, action),通过传入的action去匹配reduncer中操作,然后返回修改后的值
* @param action 一个表示“改变了什么”的普通对象,必须有一个 `type` 属性
* @returns action。
*/
function dispatch(action: A) {
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* 替换 state 的 reducer
* 只有在需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。在实现 Redux 热加载机制的时候也可能会用到
* 目前项目中还没有用到
* @param nextReducer The reducer for the store to use instead.
* @returns The same store instance with a new reducer in place.
*/
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
)
}
;(currentReducer as unknown as Reducer<NewState, NewActions>) = nextReducer
dispatch({ type: ActionTypes.REPLACE } as A)
return store as unknown as Store<
ExtendState<NewState, StateExt>,
NewActions,
StateExt,
Ext
> &
Ext
}
/**
* 目前没有用过
* @returns A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer: unknown) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError(
`Expected the observer to be an object. Instead, received: '${kindOf(
observer
)}'`
)
}
function observeState() {
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// 初始化 store 里的 state tree
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
} as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
return store
}
3、combineReducers.ts
把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法
//只看核心代码,忽略函数重载和异常处理,和一些简单的util
/**
* @param reducers 一个对象,其值对应不同的reducer
* @returns 一个reducer 函数,它调用传递的每个reducer对象,并构建一个形状相同的状态对象。
*/
export default function combineReducers(reducers) {
/**
* 过滤reducers,只保留typeof reducers[key] === 'function'的reducer
*/
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
/**
* 合并后的reducer
*/
return function combination(
state: StateFromReducersMapObject<typeof reducers> = {},
action: AnyAction
) {
let hasChanged = false
/**
* 核心代码,将对象处理为以下格式返回:
* {
* reducer1: {
* // reducer1管理的 state 对象 ...
* },
* reducer2: {
* // reducer2管理的 state 对象 ...
* }
*}
*/
const nextState: StateFromReducersMapObject<typeof reducers> = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
4、applyMiddleware.ts
Middleware 包装 store 的 dispatch 方法来达到想要的目的,一般就是传入一个支持异步处理redux的中间件,例如:redux-thunk。
Middleware 只是包装了 store 的 dispatch 方法,middleware 的函数签名是 ({ getState, dispatch }) => next => action
//只看核心代码,忽略函数重载和异常处理,和一些简单的util
/**
* @param middlewares 要应用的中间件
* @returns 应用中间件后的store。
*/
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)
}
//核心就下面两行
// 调用middleware(middlewareAPI),并将其返回函数合并为一个数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 通过compose从右到左把接收到的函数合成成新的后再调用函数,返回新的dispatch
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
5、compose.ts
从右到左来组合多个函数
/**
* @param ...funcs 需要合成的多个函数
* @returns 从右到左把接收到的函数合成后的最终函数
*/
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))
)
}
6、bindActionCreators.ts
一个简化dispatch调用的函数,将多个action存为在一个对象中支持直接通过对象进行调用,可以直接看官方demo
/**
* @param actionCreators 一个对象,对象中为创建 action 的函数
* @param dispatch
*
* @returns actionCreators
*/
export default function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, but instead received: '${kindOf(
actionCreators
)}'. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
const boundActionCreators: ActionCreatorsMapObject = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
7、一个最简单的使用示例
app.redux.ts
// state
const demo = {
loading: true,
name: 'wangWu',
};
// action
export const demoActionSetName = (name: string) => {
return { type: 'CHANGE_NAME', payload: { name } };
};
// reducer
export const demoReducer = (
state = demo,
action: {
type: string;
payload: {
loading?: boolean;
name: string;
};
},
) => {
switch (action.type) {
case `CHANGE_NAME`:
return {
...state,
...action.payload,
};
default:
return state;
}
};
store.ts
import { combineReducers, createStore } from 'redux';
import { demoReducer } from './app.redux';
const reducer = combineReducers({ demo: demoReducer });
const store = createStore(reducer);
export default store;
demo仓库地址:github.com/3wind/react…
二、react-redux
Redux 官方提供的 React 绑定库。 具有高效且灵活的特性。
1、 使 Redux store可用于任何需要访问 Redux 存储的嵌套组件
<Provider store={store}>
<App />
</Provider>
2、钩子 API:
1)、useSelector() 使用选择器函数从 Redux 存储状态中提取数据
const result: any = useSelector(selector: Function, equalityFn?: Function)
2)、useDispatch() 从 Redux 存储中返回对函数的引用,可以根据需要使用它来调度操作
const dispatch = useDispatch()
3、connect() 将 React 组件连接到 Redux 存储
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
/**
*connect接受四个不同的参数,都是可选的。按照惯例,它们被称为:
*mapStateToProps?: Function
*mapDispatchToProps?: Function | Object
*mergeProps?: Function
*options?: Object
*/
4、batch() 将setTimeout/setInterval/Promise.then(fn)/fetch回调/xhr网络回调种的多次更新合并为单个渲染更新
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}
三、redux-thunk
redux-thunk对redux的dispatch进行增强,使dispatch支持传入一个函数,而不只是普通的 Object。
源码分析:
index.tx
/**
* redux-thunk代码除去类型声明外,一共不超过10行
*/
function createThunkMiddleware<
State = any,
BasicAction extends Action = AnyAction,
ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
/**
*判断action:如果是function类型,就调用这个function,不是函数则调用next,
设计原因参考redux的applyMiddleware说明,applyMiddleware会调用middleware并传入dispatch, getState
*/
const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
({ dispatch, getState }) =>
next =>
action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument)
}
return next(action)
}
return middleware
}
redux官方调用示例
function makeASandwichWithSecretSauce(forPerson) {
// 控制反转!
// 返回一个接收 `dispatch` 的函数。
// Thunk middleware 知道如何把异步的 thunk action 转为普通 action。
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
// Thunk middleware 可以让我们像 dispatch 普通 action
// 一样 dispatch 异步的 thunk action。
store.dispatch(
makeASandwichWithSecretSauce('Me')
)
缺点:嵌套逻辑较多时会形成回调地狱
四、redux-saga
redux-saga-in-chinese.js.org/index.html
redux-saga 使用了 Generator 功能,让redux的异步的流程更易于读取,写入和测试
Generator 介绍:developer.mozilla.org/en-US/docs/…
redux-saga常用API说明:redux-saga.js.org/docs/api
一个最简单的使用示例
app.redux.ts
function* updateName({ payload }: any): Generator {
yield delay(2000);
yield put({
type: 'CHANGE_NAME',
payload: { name: payload.name },
});
}
function* updateLoadStatus({ payload }: any) {
yield delay(2000);
yield put({
type: 'CHANGE_LOADING_STATUS',
payload: { loading: payload.loading },
});
}
export function* watchUpdateName() {
// 监听类型为UPDATE_NAME的action,监听到调用updateName
yield takeEvery('UPDATE_NAME_SAGA', updateName);
yield takeEvery('UPDATE_LOADING_STATUS_SAGA', updateLoadStatus);
}
store.ts
import { applyMiddleware, combineReducers, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { all } from 'redux-saga/effects';
import { demoReducer, watchUpdateName } from './app.redux';
const rootReducer = combineReducers({ demo: demoReducer });
// 启动Effects
function* rootSaga() {
yield all([watchUpdateName()]);
}
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// 声明state类型
export type RootState = ReturnType<typeof rootReducer>;
export default function configureStore() {
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
return store;
}
demo仓库地址:github.com/3wind/react…
五、immer
简化不可变状态的处理,可用于任何需要使用不可变数据结构的上下文
/**
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @returns {any} a new state, or the initial state if nothing was modified
*/
produce(currentState, recipe: (draftState) => void): nextState
使用示例
import produce from "immer"
const baseState = [
{
title: "Learn TypeScript",
done: true
},
{
title: "Try Immer",
done: false
}
]
const nextState = produce(baseState, draftState => {
draftState.push({title: "Tweet about it"})
draftState[1].done = true
})
React & Immer
immerjs.github.io/immer/examp…
六、Redux Toolkit
Redux Toolkit包旨在成为编写Redux逻辑的标准方式。它最初是为了帮助解决Redux的三个常见问题:
1、配置Redux存储太复杂了
2、为了让Redux做任何有用的事情,我不得不添加很多包。
3、Redux需要太多的样板代码
快速应用:
redux-toolkit.js.org/tutorials/q…
store setup
1、configureStore
redux-toolkit.js.org/api/configu…
type ConfigureEnhancersCallback = (
defaultEnhancers: StoreEnhancer[]
) => StoreEnhancer[]
interface ConfigureStoreOptions<
S = any,
A extends Action = AnyAction,
M extends Middlewares<S> = Middlewares<S>
> {
/**
* A single reducer function that will be used as the root reducer, or an
* object of slice reducers that will be passed to `combineReducers()`.
*/
reducer: Reducer<S, A> | ReducersMapObject<S, A>
/**
* An array of Redux middleware to install. If not supplied, defaults to
* the set of middleware returned by `getDefaultMiddleware()`.
*/
middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
/**
* Whether to enable Redux DevTools integration. Defaults to `true`.
*
* Additional configuration can be done by passing Redux DevTools options
*/
devTools?: boolean | DevToolsOptions
/**
* The initial state, same as Redux's createStore.
* You may optionally specify it to hydrate the state
* from the server in universal apps, or to restore a previously serialized
* user session. If you use `combineReducers()` to produce the root reducer
* function (either directly or indirectly by passing an object as `reducer`),
* this must be an object with the same shape as the reducer map keys.
*/
preloadedState?: DeepPartial<S extends any ? S : S>
/**
* The store enhancers to apply. See Redux's `createStore()`.
* All enhancers will be included before the DevTools Extension enhancer.
* If you need to customize the order of enhancers, supply a callback
* function that will receive the original array (ie, `[applyMiddleware]`),
* and should return a new array (such as `[applyMiddleware, offline]`).
* If you only need to add middleware, you can use the `middleware` parameter instead.
*/
enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback
}
function configureStore<S = any, A extends Action = AnyAction>(
options: ConfigureStoreOptions<S, A>
): EnhancedStore<S, A>
2、getDefaultMiddleware
redux-toolkit.js.org/api/getDefa…
getDefaultMiddleware
import { configureStore } from '@reduxjs/toolkit'
import logger from 'redux-logger'
import rootReducer from './reducer'
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
})
// Store has all of the default middleware added, _plus_ the logger middleware
reducers and actions
1、createReducer
redux-toolkit.js.org/api/createR…
简化创建 Redux reducer 函数的实用程序。它在内部使用 Immer 通过在 reducer 中编写“可变”代码来极大地简化不可变的更新逻辑,并支持将特定的 action 类型直接映射到 case reducer 函数,这些函数将在调度该 action 时更新状态。
2、createAction
redux-toolkit.js.org/api/createA…
用于定义Redux操作类型和创建者的辅助函数。
import { createAction } from '@reduxjs/toolkit'
const increment = createAction<number | undefined>('counter/increment')
let action = increment()
// { type: 'counter/increment' }
action = increment(3)
// returns { type: 'counter/increment', payload: 3 }
console.log(increment.toString())
// 'counter/increment'
console.log(`The action type is: ${increment}`)
// 'The action type is: counter/increment'
3、createSlice
redux-toolkit.js.org/api/createS…
它接受初始状态、reducer 函数的对象和“切片名称”,并自动生成与 reducer 和状态相对应的动作创建者和动作类型
在内部,它使用createActionand createReducer,因此也可以使用Immer编写“变异”不可变更新:
import { createSlice, 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
4、createAsyncThunk
redux-toolkit.js.org/api/createA…
抽象了处理异步请求生命周期的标准推荐方法
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
})
},
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
对createAsyncThunk扩展使用
export function createServiceAsyncThunk<ThunkArg = null, Returned = null>(
typePrefix: string,
payloadCreator: (params: ThunkArg) => Promise<IResponseData<Returned>>,
callBack?: any,
preCallBack?: any
) {
return createAsyncThunk(typePrefix, async (params: ThunkArg, { rejectWithValue, dispatch }) => {
let res;
try {
preCallBack && preCallBack(params);
res = await payloadCreator(params);
callBack && callBack({ res, dispatch, params });
} catch (error) {
notification.error({
message: '网络错误,请稍后再试',
});
}
if (res.errno) {
notification.error({
message: res.errmsg || '请求错误,请稍后再试',
});
return rejectWithValue(res);
}
return res;
});
}
export function createDownloadServiceAsyncThunk<ThunkArg = null, Returned = null>(
typePrefix: string,
payloadCreator: (params: ThunkArg) => Promise<IResponseData<Returned>>
) {
return createAsyncThunk(typePrefix, async (params: ThunkArg, { rejectWithValue, dispatch }) => {
let res: any;
try {
res = await payloadCreator(params);
} catch (error) {
notification.error({
message: '网络错误,请稍后再试',
});
}
if (res.errno) {
notification.error({
message: res.errmsg || '请求错误,请稍后再试',
});
return rejectWithValue(res);
}
if (res.data === -1) {
notification.warn({
message: '已存在下载任务,请下载完成后操作',
});
} else {
notification.success({
message: '下载任务添加成功',
});
}
// 延时弹出下载框,保证 数据导出 的请求先于 下载列表 的请求到达后端服务器
setTimeout(() => {
// dispatch(showDownloadListModal())
setMainAppState({ type: 'showDownloadListModal' });
}, 100);
return res;
});
}
5、createEntityAdapter
import {
createEntityAdapter,
createSlice,
configureStore,
} from '@reduxjs/toolkit'
type Book = { bookId: string; title: string }
const booksAdapter = createEntityAdapter<Book>({
// Assume IDs are stored in a field other than `book.id`
selectId: (book) => book.bookId,
// Keep the "all IDs" array sorted based on book titles
sortComparer: (a, b) => a.title.localeCompare(b.title),
})
const booksSlice = createSlice({
name: 'books',
initialState: booksAdapter.getInitialState(),
reducers: {
// Can pass adapter functions directly as case reducers. Because we're passing this
// as a value, `createSlice` will auto-generate the `bookAdded` action type / creator
bookAdded: booksAdapter.addOne,
booksReceived(state, action) {
// Or, call them as "mutating" helpers in a case reducer
booksAdapter.setAll(state, action.payload.books)
},
},
})
const store = configureStore({
reducer: {
books: booksSlice.reducer,
},
})
type RootState = ReturnType<typeof store.getState>
console.log(store.getState().books)
// { ids: [], entities: {} }
// Can create a set of memoized selectors based on the location of this entity state
const booksSelectors = booksAdapter.getSelectors<RootState>(
(state) => state.books
)
// And then use the selectors to retrieve values
const allBooks = booksSelectors.selectAll(store.getState())
其他
1、createSelector/createDraftSafeSelector
redux-toolkit.js.org/api/createS…
创建可以在内部安全使用的选择器和具有 Immer 驱动的可变逻辑的化简器
2、General Purpose
redux-toolkit.js.org/api/matchin…
isAllOf - 当所有条件都满足时返回true
isAnyOf - 当至少满足一个条件时返回true
3、createAsyncThunk-specific matchers
redux-toolkit.js.org/api/matchin…
接受一个或多个动作创建者,当全部匹配时返回true
isAsyncThunkAction
isPending
isFulfilled
isRejected
isRejectedWithValue
4、Other Exports
redux-toolkit.js.org/api/other-e…
nanoid: 生成一个非加密安全的随机ID字符串。createAsyncThunk默认使用这个请求id
import { nanoid } from '@reduxjs/toolkit'
console.log(nanoid())
// 'dgPXxUz_6fWIQBD8XmiSy'
miniSerializeError: createAsyncThunk使用的默认错误序列化函数
export interface SerializedError {
name?: string
message?: string
stack?: string
code?: string
}
export function miniSerializeError(value: any): SerializedError {}
copyWithStructuralSharing: 递归地将两个相似的对象合并在一起
export function copyWithStructuralSharing<T>(oldObj: any, newObj: T): T
export function copyWithStructuralSharing(oldObj: any, newObj: any): any {}
createNextState
immer库中默认的不可变更新函数,这里重新导出为createNextState
current
immer库中的函数,获取草稿当前状态的快照
original
immer库中的函数,它返回原始对象
import { createReducer, createAction, current } from '@reduxjs/toolkit'
interface Todo {
//...
}
const addTodo = createAction<Todo>('addTodo')
const initialState = [] as Todo[]
const todosReducer = createReducer(initialState, (builder) => {
builder.addCase(addTodo, (state, action) => {
state.push(action.payload)
console.log(current(state))
})
})
isDraft
immer库中的函数,它检查给定值是否是代理包装的“草稿”状态。
freeze
immer库中的函数,它冻结可绘制对象。
combineReducers
Redux 的combineReducers,为方便起见重新导出。虽然在configureStore内部调用它,但您可能希望自己调用它来组合多个级别的切片缩减器。
compose
Redux 的compose. 它从右到左组成功能。这是一个函数式编程实用程序。您可能希望使用它连续应用多个商店自定义增强器/功能。
bindActionCreators
Redux 的bindActionCreators. 它包装了动作创建者,dispatch()以便它们在调用时立即分派。
createStore
Redux 的createStore. 您不需要直接使用它。
applyMiddleware
Redux 的applyMiddleware. 您不需要直接使用它。