TypeScript: How to cast a generic object?

2019-08-03 17:50发布

I have the following interfaces:

interface AppState {
  readonly grid : IGridSettings;
  readonly selected : ISelectedSettings;
}

interface IGridSettings {
  readonly extents : number;
  readonly isXY : boolean;
  readonly isXZ : boolean;
  readonly isYZ : boolean;
  readonly spacing : number;
}

interface ISelectedSettings {
  readonly bodyColor : number;
  readonly colorGlow : number;
  readonly lineColor : number;
  readonly selection : number[] | undefined;
}

interface Action<T = any> {
  type: T
}

interface SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]> extends Action {
  type : string;
  payload : {
    property : [KAction, KActionProp];
    value : IUserActions[KAction][KActionProp];
  };
}

Eventually my redux reducer gets called when the setPropertyValue function is emitted:

const setPropertyValue = <KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>( property : [KAction, KActionProp], value : AppState[KAction][KActionProp] ) : SetPropertyValueAction<KAction, KActionProp> => ( {
  type: 'SET_PROPERTY_VALUE',
  payload: {
    property,
    value,
  }
} );

function myReducer( state : AppState = INITIAL_STATE, a : Action ) : AppState {
  switch( a.type ) {
    case 'SET_PROPERTY_VALUE': {
      const action = a as SetPropertyValueAction; //  <- error here
      return createNewState( state, action.payload.property, action.payload.value );
    }
  }
  return states;
}

The problem that I'm having is I don't know how to fix the cast from Action to SetPropertyValueAction. TypeScript tells me the error is:

Generic type 'SetPropertyValueAction<KAction, KActionProp>' requires 2 type argument(s).

I tried changing the line of code that has the error to the following:

const action = a as SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>;

...but this gives me two errors saying 'Cannot find name KAction and KActionProp'

How can I cast an Action object to the generic SetPropertyValueAction ??

This is a follow up question to my other question that has been answered here: How to add TypeScript type safety to my redux action?

1条回答
再贱就再见
2楼-- · 2019-08-03 18:06

We need to specify the type parameters for the type. Since we don't really know them, we need something that is compatible with keyof T but represent is not an actual key of T. unknown will not do since it does not satisfy the constraint, the one type we can use is any or never (which is a subtype of any type)

interface AppState {
    readonly grid: IGridSettings;
    readonly selected: ISelectedSettings;
}

interface IGridSettings {
    readonly extents: number;
    readonly isXY: boolean;
    readonly isXZ: boolean;
    readonly isYZ: boolean;
    readonly spacing: number;
}

interface ISelectedSettings {
    readonly bodyColor: number;
    readonly colorGlow: number;
    readonly lineColor: number;
    readonly selection: number[] | undefined;
}

interface Action<T = any> {
    type: T
}

interface SetPropertyValueAction<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]> extends Action {
    type: string;
    payload: {
        property: [KAction, KActionProp];
        value: AppState[KAction][KActionProp];
    };
}

const setPropertyValue = <KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>(property: [KAction, KActionProp], value: AppState[KAction][KActionProp]): SetPropertyValueAction<KAction, KActionProp> => ({
    type: 'SET_PROPERTY_VALUE',
    payload: {
        property,
        value,
    }
});
declare const INITIAL_STATE: AppState;
// implementation if necessary     
function createNewState<KAction extends keyof AppState, KActionProp extends keyof AppState[KAction]>(state: AppState, property: [KAction, KActionProp], value: AppState[KAction][KActionProp]): AppState {
    return Object.assign({ ...state }, {
        [property[0]]: Object.assign({ ...state[property[0]] }, {
            [property[1]]: value,
        })
    });
}
function myReducer(state: AppState = INITIAL_STATE, a: Action): AppState {
    switch (a.type) {
        case 'SET_PROPERTY_VALUE': {
            const action = a as SetPropertyValueAction<never, never>; //  we don't know the actual type, never will have to do

            return createNewState(state, action.payload.property, action.payload.value);
        }
    }
    return state;
}
查看更多
登录 后发表回答