redux武装

25 阅读7分钟

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 状态容器,提供可预测化的状态管理

核心概念:

www.redux.org.cn/docs/introd…

源码分析:

redux官方地址:www.redux.org.cn/docs/introd…

1、 入口:index.ts

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

导出对外提供的API

2、createStore.ts

www.redux.org.cn/docs/api/cr…

创建一个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

www.redux.org.cn/docs/api/co…

把一个由多个不同 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

www.redux.org.cn/docs/api/ap…

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

www.redux.org.cn/docs/api/co…

从右到左来组合多个函数

/**
 * @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

www.redux.org.cn/docs/api/bi…

一个简化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

react-redux.js.org/api/hooks

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。

源码分析:

源码地址:github.com/reduxjs/red…

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

immerjs.github.io/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. 您不需要直接使用它。