简体   繁体   中英

Typescript: generic type not correctly inferred (`unknown`)

I'm trying to create a small system similar to Redux/Redux-toolkit. What I would like is to have a createSlice function like this:

interface Lobby {
  players: string[];
}

const slice = createSlice<Lobby>({
  addPlayer: (state, name: string) => ({ ...state, players: [...players, name ] }),
});

It should return an object like this:

{
  actions: { addPlayer: (name: string) => ({ type: 'addPlayer', payload: name }) }
  reducer: (state: Lobby, action: { type: string, payload: any }) => Lobby
}

After experimenting I think createSlice cannot be implemented in this way, because it has multiple generic arguments, and you can't just partially apply only state: Lobby as a parameter.

The closest thing I have so far is this (actual implementation aside):

const createSlice = <S, T extends Record<string, (state: S, payload: any) => S>>(
  actions: T
) => {
  type ActionsType = {
    [K in keyof T]: (
      payload: Parameters<T[K]>[1]
    ) => { type: K; payload: Parameters<T[K]>[1] };
  };

  type ReducerType = (state: S, { type: string, payload: any }) => S;

  return {
    actions: ("implementation" as any) as ActionsType,
    reducer: ("implementation" as any) as ReducerType,
  };
};

which I can use like this:

const slice = createSlice({
  addPlayer: (state: Lobby, name: string) => ({ ...state, players: [...players, name ] }),
});

This works, but the parameter S is incorrectly (?) inferred as unknown , so the reducer type is wrong, and the following will not cause a type error:

const actions = slice({
  addPlayer: (state: Lobby, name: string) => 3, // 3 is not Lobby, so I want this to error
});

I'm not sure how to proceed / fix this now..

I honestly don't entirely understand what was causing the initial problem, but factoring out generic types allows createSlice to take a single generic argument & seems to get things erroring properly.

type ActionOptions<S> = Record<string, (state: S, payload: any) => S>;
type ActionsType<S, T extends ActionOptions<S>> = {
    [K in keyof T]: (
      payload: Parameters<T[K]>[1]
    ) => { type: K; payload: Parameters<T[K]>[1] };
};

type ReducerType<S> = (state: S, {type, payload}: { type: string, payload: any }) => S;

function createSlice<S> (
  actions: ActionOptions<S>
) {  
  return {
    actions: ("implementation" as any) as ActionsType<S, ActionOptions<S>>,
    reducer: ("implementation" as any) as ReducerType<S>,
  };
};

type Lobby = { __brand: 'lobby' };

const slice = createSlice({
  addPlayer: (state: Lobby, payload: string) => ({ ...state, players: [payload ] }),
});

const actions = createSlice({
  addPlayer: (state: Lobby, name: string) => 3, // Type 'number' is not assignable to type 'Lobby'
});

playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM