How to add TypeScript type safety to my redux action?

◇◆丶佛笑我妖孽 提交于 2020-01-14 05:46:07

问题


I'm writing my application in TypeScript and I'm using Redux to keep track of my app state. My redux store state looks something like this:

interface AppState {
  readonly grid : IGridSettings;
  readonly selected : ISelectedSettings;
  [key : string] : IGridSettings | ISelectedSettings;
}

interface IGridSettings {
  readonly extents : number;
  readonly isXY : boolean;
  readonly isXZ : boolean;
  readonly isYZ : boolean;
  readonly spacing : number;
  [key : string] : number | boolean;
}

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

I created the following action to let me update the store however the setPropertyValue function is not ideal since it has no type safety!

//How to add type safety to this function?
const setPropertyValue = ( property : string[], value : any ) : SetPropertyValueAction => ( {
  type: 'SET_PROPERTY_VALUE',
  payload: {
    property,
    value,
  }
} );

interface SetPropertyValueAction extends Action {
  type : string;
  payload : {
    property : string[];
    value : any;
  };
}

I can use this action in my code this way:

setPropertyValue( ['grid', 'extents'], 100 );

This works, but since there is no type safety in the setPropertyValue function there is nothing restricting me from feeding invalid values into the function like this:

setPropertyValue( ['grid', 'size'], 100 ); //invalid since IGridSettings doesn't have a 'size' property
setPropertyValue( ['grid', 'isXY'], -1 ); //value should be a boolean not a number
setPropertyValue( ['selected', 'bodyColor'], '255' ); //value should be a number not a string

Is there a way to rewrite the setPropertyValue function so that it only accepts arguments that are valid for the property name found in AppState. Also the value passed in must correspond to the correct type for the selected property.

Perhaps using a string array for the property to change is not ideal but I don't know of a better way to target only the properties that are available in AppState.


回答1:


If you need the index signature then there is no way to validate the keys of the type (since after all the index signature would mean the class in indexable by any key, so any key would be valid)

To validate taht a string is a key of a type we can use the keyof operator, in conjuction with some generic type parameters (to capture the actual key passed in)

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;
}

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

interface SetPropertyValueAction {
  type : string;
  payload : {
    property : PropertyKey[];
    value : any;
  };
}

setPropertyValue( ['grid', 'extents'], 100 );
setPropertyValue( ['grid', 'size'], 100 ); //err
setPropertyValue( ['grid', 'isXY'], -1 ); //err
setPropertyValue( ['selected', 'bodyColor'], '255' );// err 


来源:https://stackoverflow.com/questions/54041309/how-to-add-typescript-type-safety-to-my-redux-action

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!