UNPKG

@reduxjs/toolkit

Version:

The official, opinionated, batteries-included toolset for efficient Redux development

110 lines (97 loc) • 3.08 kB
import type { Dispatch, Middleware, UnknownAction } from 'redux' import { compose } from '../reduxImports' import { createAction } from '../createAction' import { isAllOf } from '../matchers' import { nanoid } from '../nanoid' import { getOrInsertComputed } from '../utils' import type { AddMiddleware, DynamicMiddleware, DynamicMiddlewareInstance, MiddlewareEntry, WithMiddleware, } from './types' export type { DynamicMiddlewareInstance, GetDispatchType as GetDispatch, MiddlewareApiConfig, } from './types' const createMiddlewareEntry = < State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>, >( middleware: Middleware<any, State, DispatchType>, ): MiddlewareEntry<State, DispatchType> => ({ middleware, applied: new Map(), }) const matchInstance = (instanceId: string) => (action: any): action is { meta: { instanceId: string } } => action?.meta?.instanceId === instanceId export const createDynamicMiddleware = < State = any, DispatchType extends Dispatch<UnknownAction> = Dispatch<UnknownAction>, >(): DynamicMiddlewareInstance<State, DispatchType> => { const instanceId = nanoid() const middlewareMap = new Map< Middleware<any, State, DispatchType>, MiddlewareEntry<State, DispatchType> >() let middlewareVersion = 0 const withMiddleware = Object.assign( createAction( 'dynamicMiddleware/add', (...middlewares: Middleware<any, State, DispatchType>[]) => ({ payload: middlewares, meta: { instanceId, }, }), ), { withTypes: () => withMiddleware }, ) as WithMiddleware<State, DispatchType> const addMiddleware = Object.assign( function addMiddleware( ...middlewares: Middleware<any, State, DispatchType>[] ) { const previousSize = middlewareMap.size middlewares.forEach((middleware) => { getOrInsertComputed(middlewareMap, middleware, createMiddlewareEntry) }) if (middlewareMap.size !== previousSize) { middlewareVersion++ } }, { withTypes: () => addMiddleware }, ) as AddMiddleware<State, DispatchType> const getFinalMiddleware: Middleware<{}, State, DispatchType> = (api) => { const appliedMiddleware = Array.from(middlewareMap.values()).map((entry) => getOrInsertComputed(entry.applied, api, entry.middleware), ) return compose(...appliedMiddleware) } const isWithMiddleware = isAllOf(withMiddleware, matchInstance(instanceId)) const middleware: DynamicMiddleware<State, DispatchType> = (api) => (next) => { let appliedVersion = -1 let dispatch = next return (action) => { if (isWithMiddleware(action)) { addMiddleware(...action.payload) return api.dispatch } if (appliedVersion !== middlewareVersion) { dispatch = getFinalMiddleware(api)(next) appliedVersion = middlewareVersion } return dispatch(action) } } return { middleware, addMiddleware, withMiddleware, instanceId, } }