Problemer med TypeScript-typingen for butikkforbedringer i Redux 4.0


Kan en TypeScript-ekspert hjelpe meg med å finne ut hvorfor denne koden for å lage store-forbedringer i redux 4.0 gir meg typefeil?

Det minimale repoet er her. Det lager en do-nothing store-økning. Etter å ha klonet, installer avhengigheter ved å kjøre yarn. For å se feilen, kjør yarn run compile.

Den relevante filen er

import {
  StoreEnhancer,
  StoreEnhancerStoreCreator,
  Reducer,
  DeepPartial,
  AnyAction,
} from 'redux';

interface RootState {
  someKey: string;
}

export const enhancer: StoreEnhancer =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>
    (reducer: Reducer<RootState, AnyAction>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: typeof store.dispatch = <A extends AnyAction>(action: A) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
  };
};

Feilen jeg får er

src/enhancer.ts(15,5): error TS2322: Type '(reducer: Reducer<RootState, AnyAction>, preloadedState?: DeepPartial<RootState> | undefined) => ...' is not assignable to type 'StoreEnhancerStoreCreator<{}, {}>'.
  Types of parameters 'reducer' and 'reducer' are incompatible.
    Types of parameters 'state' and 'state' are incompatible.
      Type 'RootState | undefined' is not assignable to type 'S | undefined'.
        Type 'RootState' is not assignable to type 'S | undefined'.
          Type 'RootState' is not assignable to type 'S'.

Jeg forstår ikke hvorfor RootState ikke kan tilordnes til S.

Svar #1

La oss dykke dypt inn i hvordan StoreEnhancerStoreCreator er definert

export type StoreEnhancerStoreCreator<Ext = {}, StateExt = {}> = <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<S>) => Store<S & StateExt, A> & Ext;

Dette er en generisk type med to typeargumenter, Ext og StateExt. Resultatet av denne typen er en annen generisk funksjon. For eksempel, hvis vi gjør

const genericStoreCreator: StoreEnhancerStoreCreator = <S = any, A extends Action = AnyAction>(r: Reducer<S, A>) => createStore(r)

genericStoreCreator vil fortsatt være en generisk funksjon. Vi har allerede brukt generisk type StoreEnhancerStoreCreator uten å oppgi typeargumenter til den (med standard typeverdier), men resultatet er fortsatt en generisk funksjon.

Da kan vi kalle genericStoreCreator og gi spesifikke typeargumenter for S og A.

const store = genericStoreCreator((state: RootState = { someKey: 'someKey' }, action: AnyAction) => state)

Og nå har vi en spesifikk store uten noen generiske.

Du kan vurdere StoreEnhancerStoreCreator som generisk inni generisk.

La oss nå gå tilbake til lagringsforsterkere. Fra kilde (fant det ikke i dokumentasjonen)

En butikkforsterker er en høyereordensfunksjon som kombinerer en butikkoppretter for å returnere en ny, forbedret butikkoppretter.

Så resultatet av store-forsterkeren bør fortsatt være en generell butikkoppretter. Inne i forsterkerfunksjonen bør vi ikke forholde oss til en spesifikk form på tilstanden (dvs. at den har someKey-egenskap) eller spesifikk form på handlingen (bortsett fra at den har type-egenskap). Når man implementerer forsterkeren vet vi ikke hvilken eksakt butikk den vil forsterke.

For å få koden din til å fungere, bare ta med generics inne i forsterkerkoden

export const enhancer: StoreEnhancer =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator =>
    <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: Dispatch<A> = (action) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
   };
};

Merk at den forbedrede butikkoppretteren fortsatt er generell og kan brukes med hvilken som helst tilstand og handlinger.

Men hva kan vi forbedre med forbedrer? Som kilden sier

@template Ext Store-utvidelse som er blandet inn i Store-typen.

@template StateExt State-utvidelse som blandes inn i statustypen.

La oss utvide butikken med print5-funksjonen som når den kalles vil logge 5 til konsollen.

export const enhancer: StoreEnhancer<{ print5: () => void }> =
  (createStore: StoreEnhancerStoreCreator): StoreEnhancerStoreCreator<{ print5: () => void }> =>
    <S = any, A extends Action = AnyAction>(reducer: Reducer<S, A>, preloadedState?: DeepPartial<RootState>) => {

  const store = createStore(reducer, preloadedState);
  const newDispatch: Dispatch<A> = (action) => {
    const result = store.dispatch(action);
    return result;
  }
  return {
    ...store,
    dispatch: newDispatch,
    print5 () { console.log(5) },
   };
  };

Merk igjen at den resulterende butikken fortsatt er generisk.

Og her er en enkel demo. Legg merke til hvordan print5-funksjonen blir videreført fra enhancer til den resulterende butikken.

Relaterte artikler