redux-toolkit使用和源码浅析

832 阅读5分钟

如果对redux还不熟悉的话,建议 可以去看我之前写过的一篇文章redux和react-redux从实现到理解

前言

当我们使用redux作为状态管理工具时,通常要书写很多模板代码,并且为了满足业务需求,还需要自己引入一些middleware,整个过程是比较麻烦的。比如下面计数器管理的一个例子

import { createStore, combineReducers } from 'redux'

// actionTypes
const INCREMENT = 'counter/incremented';
const DECREMENT = 'counter/decremented';

// actions
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: INCREMENT });

// reducer
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case INCREMENT:
      return { value: state.value + 1 };
    case DECREMENT:
      return { value: state.value - 1 };
    default:
      return state;
  }
}

const store = createStore(
  combineReducers({
    counter: counterReducer,
    // 其他reducer
  }),
  // 配置中间件一大推
  applyMiddleware([a, b, c])
);

// 更改状态
store.dispatch(increment());
store.dispatch(decrement());

当我们业务变得复杂时,我们就需要将上述代码拆分成多个reduceractionTypesactions,这样的模板代码书写不仅消耗着我们精力,而且分散多个文件,也不直观。

因此,redux-toolkit尝试提供一些在配置过程中所需要的工具,并处理最常见的一些用例。它提供的工具使我们更方便的配置store,以及管理状态。下面来一起看看是如何使用。

基本使用

还是以计数器为例,通过redux-toolkit提供的createSliceconfigureStore来配置。

// 把actionType、actions、reducer放在一起管理
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    // 内置了immer,可以直接修改原state
    incremented: state => {
      state.value += 1;
    },
    decremented: state => {
      state.value -= 1;
    },
  },
});

// 定义store,其中内置了thunk中间件
const store = configureStore({
  reducer: {
    [counterSlice.name]: counterSlice.reducer,
  },
});

const { incremented, decremented } = counterSlice.actions;

// 更改状态
store.dispatch(increment());
store.dispatch(decrement());

上述代码相对于使用reduxAPI整体更简洁,并且我们可以用一个slice.js文件来维护一个页面的状态管理,不需要多个文件去修改。

下面我们一起学习下它几个API的原理。😆

源码浅析

createAction

createAction源码比较简单,就是根据我们传入的type。去生成{ type, payload: xxx }形式。如果传入了第二个参数,就需要进一步生成payload。

function createAction(type, prepareAction) {
  // 返回的action
  function actionCreator(...args) {
    // 如果存在第二个参数,需要进一步生成payload。
    if (prepareAction) {
      let prepared = prepareAction(...args)
      if (!prepared) {
        throw new Error('prepareAction did not return an object')
      }

      return {
        type,
        payload: prepared.payload,
      }
    }
    // 不存在第二个参数直接构造层action的格式
    return { type, payload: args[0] }
  }
  // String()、console.log 时,可以返回type
  actionCreator.toString = () => `${type}`
  actionCreator.type = type
  // 调用match函数是否匹配,type相等
  actionCreator.match = (action) => action.type === type
 
  return actionCreator
}

createReducer

先来看一下createReducer的基本使用,第一个参数传入initState初始值,第二个参数如果是函数,会接受builder参数,调用builderaddCaseaddMatcher方法可以添加case,如果是对象,就像reducer正常定义type以及对应的函数。

const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')

const initialState = { value: 0 }

const counterReducer1 = createReducer(initialState, (builder) => {
  builder
    .addCase(increment, (state) => {
      state.value++
    })
    .addCase(decrement, (state) => {
      state.value--
    })
    .addMatcher(() => true, (state, action) => {}) // 通过函数决定是否执行
    .addDefaultCase((state, action) => {
      // 默认case noting
    })
})

const counterReducer2 = createReducer(initialState, {
  [increment]: (state) => state.value++,
  [decrement]: (state) => state.value--,
}, [
    // 通过函数决定是否执行
    {
      matcher: () => true,
      reducer(state, action) {},
    },
],
(state) => {
  // 默认case noting
})

// counterReducer2 和 counterReducer1 等价

可以看到第2个参数选择传入函数,利用builder等同于2、3、4参数传入对象。

export function createReducer(initialState, mapOrBuilderCallback, actionMatchers = [], defaultCaseReducer) {

  // 规范化参数,将builder函数形式,转变成3个参数
  let [actionsMap, finalActionMatchers, finalDefaultCaseReducer] =
    typeof mapOrBuilderCallback === 'function'
      ? executeReducerBuilderCallback(mapOrBuilderCallback)
      : [mapOrBuilderCallback, actionMatchers, defaultCaseReducer]

  // 确保获取初始状态是冻结的,是被immer包裹的
  let getInitialState;
  if (isStateFunction(initialState)) {
    getInitialState = () => createNextState(initialState(), () => {})
  } else {
    const frozenInitialState = createNextState(initialState, () => {})
    getInitialState = () => frozenInitialState
  }
  
  // 生成最后的reducer
  function reducer(state = getInitialState(), action) {
    // 所有符合条件的reducer
    let caseReducers = [
      actionsMap[action.type],
      ...finalActionMatchers
        .filter(({ matcher }) => matcher(action))
        .map(({ reducer }) => reducer),
    ]
    // 如果没有符合的,就是用默认的case
    if (caseReducers.filter((cr) => !!cr).length === 0) {
      caseReducers = [finalDefaultCaseReducer]
    }
    // 执行所有符合条件的reducer
    return caseReducers.reduce((previousState, caseReducer) => {
      if (caseReducer) {
        // 如果当前值是已经被immer代理过,就直接执行函数
        if (isDraft(previousState)) {
          const draft = previousState; 
          const result = caseReducer(draft, action)

          // 如果结果是undefined,还是使用源数据就可以
          if (typeof result === 'undefined') {
            return previousState
          }
          // 如果有返回值,使用新值
          return result
        } else if (!isDraftable(previousState)) {
          // 如果当前值无法被immer代理,需要判断是否会返回空值
          const result = caseReducer(previousState, action)
          // 如果之前就是空值,再返回空值是允许的
          if (typeof result === 'undefined') {
            if (previousState === null) {
              return previousState
            }
            throw Error(
              'A case reducer on a non-draftable value must not return undefined'
            )
          }

          return result
        } else {
          // 使用immer再次代理,并传入reducer,获得下次结果
          return createNextState(previousState, (draft) => {
            return caseReducer(draft, action)
          })
        }
      }
      return previousState
    }, state)
  }

  reducer.getInitialState = getInitialState

  return reducer;
}

createReducer的过程就是先规范化参数,然后找到所有符合当前typereducer函数。依次执行Ta们。返回最终结果。在每次执行函数时,都会尝试用immer代理结果值,使结果变成不可变性。

createSlice

通过createSlice函数可以直接生成actionsreduceractions可以用于变更状态dispatch参数,reducer直接传入到configStore函数中生成store

其实createSlice就是使用createReducercreateAction生成一个对象。具体代码解析如下:

// options里面有 name、reducer、initialState
function createSlice(options) {
  const { name } = options

  if (!name) {
    throw new Error('`name` is a required option for createSlice')
  }

  // 初始值使用immer代理,不可变性
  const initialState =
    typeof options.initialState == 'function'
      ? options.initialState
      : createNextState(options.initialState, () => {})

  const reducers = options.reducers || {}

  const reducerNames = Object.keys(reducers)

  // name -> reducer
  const sliceCaseReducersByName = {}
  // type -> reducer
  const sliceCaseReducersByType = {}
  // 生成action的Map
  const actionCreators = {}

  reducerNames.forEach((reducerName) => {
    const maybeReducerWithPrepare = reducers[reducerName]
    // type 是通过 name和reducer的key拼接成的。类似于counter/increment
    const type = getType(name, reducerName)

    let caseReducer
    let prepareCallback
    
    // 如果是对象且包含reducer,那结构是 { reducer, prepare } 
    if ('reducer' in maybeReducerWithPrepare) {
      caseReducer = maybeReducerWithPrepare.reducer
      prepareCallback = maybeReducerWithPrepare.prepare
    } else {
      caseReducer = maybeReducerWithPrepare
    }

    sliceCaseReducersByName[reducerName] = caseReducer
    sliceCaseReducersByType[type] = caseReducer
    // 如果有prepareCallback,则传入,会封装payload。具体可以看ceateActions逻辑
    actionCreators[reducerName] = prepareCallback
      ? createAction(type, prepareCallback)
      : createAction(type)
  })

  // 生成reducer
  function buildReducer() {
    const [
      extraReducers = {},
      actionMatchers = [],
      defaultCaseReducer = undefined,
    ] =
      typeof options.extraReducers === 'function'
        ? executeReducerBuilderCallback(options.extraReducers)
        : [options.extraReducers]

    // 可以通过extraReducers参数,传入额外的reducer、actionMatcher、默认case
    const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType }
    // 具体逻辑可以看createReducer逻辑
    return createReducer(
      initialState,
      finalCaseReducers,
      actionMatchers,
      defaultCaseReducer
    )
  }

  let _reducer;

  return {
    name,
    // 可以传入configStore
    reducer(state, action) {
      if (!_reducer) _reducer = buildReducer()

      return _reducer(state, action)
    },
    // 用export导出,dispatch使用
    actions: actionCreators,
    caseReducers: sliceCaseReducersByName,
    // 获取初始值
    getInitialState() {
      if (!_reducer) _reducer = buildReducer()

      return _reducer.getInitialState()
    },
  }
}

configStore

configureStore(options) {
  // 默认内置的middleware
  const curriedGetDefaultMiddleware = curryGetDefaultMiddleware()

  const {
    reducer = undefined,
    middleware = curriedGetDefaultMiddleware(),
    devTools = true,
    preloadedState = undefined,
    enhancers = undefined,
  } = options || {}

  let rootReducer
  // 生成reducer
  if (typeof reducer === 'function') {
    rootReducer = reducer
  } else if (isPlainObject(reducer)) {
    rootReducer = combineReducers(reducer)
  }

  // 生成middleware,可以传入函数,来改变默认中间件
  let finalMiddleware = middleware
  if (typeof finalMiddleware === 'function') {
    finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
  }
  
  const middlewareEnhancer = applyMiddleware(...finalMiddleware)

  let finalCompose = compose

  // 有开发者工具,更换compose
  if (devTools) {
    finalCompose = composeWithDevTools({
      // Enable capture of stack traces for dispatched Redux actions
      trace: !IS_PRODUCTION,
      ...(typeof devTools === 'object' && devTools),
    })
  }

  // 增加store
  let storeEnhancers = [middlewareEnhancer]
  if (Array.isArray(enhancers)) {
    storeEnhancers = [middlewareEnhancer, ...enhancers]
  } else if (typeof enhancers === 'function') {
    storeEnhancers = enhancers(storeEnhancers)
  }

  const composedEnhancer = finalCompose(...storeEnhancers)

  // 调用redux的createStore把整理好的参数传入
  return createStore(rootReducer, preloadedState, composedEnhancer)
}

configStore也很简单,就是将内置的middlewareenhancer和用户传入的组合一起,调用createStore创建store