最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to make a subtype of string in typescript - Stack Overflow

programmeradmin0浏览0评论

I making a game called risk using typescript and react hooks. The game is played on some kind of a map. So first of all I have design I MapEditor. The state of Map Editor is like

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: string]: ICountry};
   continents: { [k: string]: IContinent };
}

countries and continents are objects. The interface for country looks like

//The "name" property and above key will be same in `{[k: string]: ICountry};` will be same
export interface ICountry {
   name: string;
   border: IDot[];
   neighbours: string[];
   pleted: boolean;
}

Now I make a reducer function. For all the types of action I used two props name and data. name will always be a string and data will a type depending on name

type ActionTypes = {name: "removeCountry", data: string} | {name: "addCountry", data: ICountry};
const reducer = (state: IMapEditorState, action: ActionTypes) => {
   ...
}

Now see the first type in ActionTypes which is {name: "removeCountry", data: string}. In the dispatch method a I will use {name: "removeCountry"} the piler will force to pass data as string but it couldn't be any string which I don't want. I want that I should only be able to pass string which is key of {[k: string]: ICountry} in IMapEditorState or the name in ICountry.

Is there any way that I could create a subtype of a string called CountryName and use it

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: CountryName]: ICountry};
   continents: { [k: string]: IContinent };
}
export interface ICountry {
   name: CountryName;
   border: IDot[];
   neighbours: string[];
   pleted: boolean;
}
type ActionTypes = {name: "removeCountry", data: CountryName} | {name: "addCountry", data: ICountry};

I will very thankful if you help me and kindly give your view on my data structure if got idea what is the game.

I making a game called risk using typescript and react hooks. The game is played on some kind of a map. So first of all I have design I MapEditor. The state of Map Editor is like

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: string]: ICountry};
   continents: { [k: string]: IContinent };
}

countries and continents are objects. The interface for country looks like

//The "name" property and above key will be same in `{[k: string]: ICountry};` will be same
export interface ICountry {
   name: string;
   border: IDot[];
   neighbours: string[];
   pleted: boolean;
}

Now I make a reducer function. For all the types of action I used two props name and data. name will always be a string and data will a type depending on name

type ActionTypes = {name: "removeCountry", data: string} | {name: "addCountry", data: ICountry};
const reducer = (state: IMapEditorState, action: ActionTypes) => {
   ...
}

Now see the first type in ActionTypes which is {name: "removeCountry", data: string}. In the dispatch method a I will use {name: "removeCountry"} the piler will force to pass data as string but it couldn't be any string which I don't want. I want that I should only be able to pass string which is key of {[k: string]: ICountry} in IMapEditorState or the name in ICountry.

Is there any way that I could create a subtype of a string called CountryName and use it

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: CountryName]: ICountry};
   continents: { [k: string]: IContinent };
}
export interface ICountry {
   name: CountryName;
   border: IDot[];
   neighbours: string[];
   pleted: boolean;
}
type ActionTypes = {name: "removeCountry", data: CountryName} | {name: "addCountry", data: ICountry};

I will very thankful if you help me and kindly give your view on my data structure if got idea what is the game.

Share Improve this question asked Jan 25, 2020 at 7:38 Maheer AliMaheer Ali 36.7k7 gold badges49 silver badges82 bronze badges 4
  • @CertainPerformance I have created anything further. Just wanted to get a better way to further. – Maheer Ali Commented Jan 25, 2020 at 7:47
  • @CertainPerformance Consider a function test(x : CountryName){...}. Now here I want could only pass the name property of ICountry(For the time ignore the key of the object) – Maheer Ali Commented Jan 25, 2020 at 7:51
  • Given that the interface is name: CountryName;, typing function test(x : CountryName) should already require that the parameter is of the permitted type, a CountryName and nothing else, right? – CertainPerformance Commented Jan 25, 2020 at 7:59
  • @CertainPerformance I also want the keys of the countries to be of type CountryName. But if its not possible so please explain why not. – Maheer Ali Commented Jan 25, 2020 at 8:05
Add a ment  | 

2 Answers 2

Reset to default 5

If you want to be able to do these checks at pile time, you'll have to make a list of all possible country names:

type CountryName = 'cName1' | 'cName2' | 'cName3';

Or, if you can define an initial object of all possible countries, you can declare it as const (so that TS doesn't generalize its strings), and then take its keys via keyof:

const initialCountries = {
    cName1: {
        name: 'cName1',
        pleted: false
        // ...
    },
    cName2: {
        name: 'cName2',
        pleted: false
    },
    cName3: {
        name: 'cName3',
        pleted: false
    },
} as const;
type CountryName = keyof typeof initialCountries;

Result for CountryName is "cName1" | "cName2" | "cName3".

Then you can define IMapEditorState using the above CountryName:

export interface ICountry {
    name: CountryName;
    border: IDot[];
    neighbours: string[];
    pleted: boolean;
}
export interface IMapEditorState {
    mousePos: IPoint;
    countries: { [k: CountryName]: ICountry };
    continents: { [k: string]: IContinent };
}

And then the following will pile:

const initalIMapEditorState: IMapEditorState = {
    countries: initialCountries,
    // ...
};

and then you can use CountryName wherever else you need to:

type ActionTypes = {name: "removeCountry", data: CountryName} | {name: "addCountry", data: ICountry};

Typescript 4.1.5:

In my case I needed to mark a stringified date in this format "YYYY-MM-DD" as an ISODate rather than just a string. I used this approach, I think it's more effective than this answer if the number of valid strings is more than just a few:

interface ISODateDifferentiator extends String {
  [key: string]: unknown;
}
export type ISODate = ISODateDifferentiator & string; 

export const ValidateIsoDate = (date: string): date is ISODate => {
  return date.match(/^\d{4}-\d{2}-\d{2}$/) !== null;
} 

You get the desired Type 'string' is not assignable to type 'ISODate'. when doing an unsafe assignment but can convert using the typeguard function. You also still get the string method auto-pletion.

发布评论

评论列表(0)

  1. 暂无评论