简体   繁体   English

如何在打字稿中创建字符串的子类型

[英]How to make a subtype of string in typescript

I making a game called risk using typescript and react hooks.我使用 typescript 和 react hooks 制作了一个名为 risk 的游戏。 The game is played on some kind of a map.游戏是在某种地图上进行的。 So first of all I have design I MapEditor .所以首先我有设计我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. countriescontinents是对象。 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[];
   completed: boolean;
}

Now I make a reducer function.现在我做了一个减速器功能。 For all the types of action I used two props name and data .对于所有类型的动作,我使用了两个道具namedata name will always be a string and data will a type depending on name name将始终是一个string ,数据将取决于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} .现在查看ActionTypes的第一个类型,它是{name: "removeCountry", data: string} In the dispatch method a I will use {name: "removeCountry"} the compiler will force to pass data as string but it couldn't be any string which I don't want.在调度方法 a 中,我将使用{name: "removeCountry"}编译器将强制将data作为string传递,但它不能是我不想要的任何字符串。 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 .我想,我应该只能够通过字符串这是关键的{[k: string]: ICountry}IMapEditorStatenameICountry

Is there any way that I could create a subtype of a string called CountryName and use it有什么方法可以创建一个名为CountryName的字符串的子类型并使用它

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: CountryName]: ICountry};
   continents: { [k: string]: IContinent };
}
export interface ICountry {
   name: CountryName;
   border: IDot[];
   neighbours: string[];
   completed: 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.如果你能帮助我,我将非常感谢,如果你知道什么是游戏,请对我的数据结构发表你的看法。

If you want to be able to do these checks at compile 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 (以便 TS 不会概括其字符串),然后通过keyof获取其键:

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

Result for CountryName is "cName1" | "cName2" | "cName3" CountryName结果是"cName1" | "cName2" | "cName3" "cName1" | "cName2" | "cName3" "cName1" | "cName2" | "cName3" . "cName1" | "cName2" | "cName3"

Then you can define IMapEditorState using the above CountryName :然后你可以使用上面的CountryName定义IMapEditorState

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

And then the following will compile:然后将编译以下内容:

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

and then you can use CountryName wherever else you need to:然后您可以在任何其他需要的地方使用CountryName

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

Typescript 4.1.5:打字稿 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.在我的情况下,我需要将这种格式“YYYY-MM-DD”的字符串化日期标记为 ISODate 而不仅仅是字符串。 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'.您得到所需的Type 'string' is not assignable to type 'ISODate'. when doing an unsafe assignment but can convert using the typeguard function.当进行不安全的赋值但可以使用 typeguard 函数进行转换时。 You also still get the string method auto-completion.您还可以获得字符串方法自动完成。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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