【问题标题】:How to make a subtype of string in typescript如何在打字稿中制作字符串的子类型
【发布时间】:2020-01-25 07:38:45
【问题描述】:

我使用 typescript 和 react hooks 制作了一个名为 risk 的游戏。游戏在某种地图上进行。所以首先我有设计我MapEditor。 Map Editor 的状态是这样的

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

countries 和 continents 是对象。国家的界面看起来像

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

现在我做一个减速器功能。对于所有类型的动作,我使用了两个道具name 和data。 name 将始终是 string 并且数据将是取决于 name 的类型

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

现在查看ActionTypes 中的第一个类型,即{name: "removeCountry", data: string}。在调度方法中,我将使用{name: "removeCountry"},编译器将强制将data 作为string 传递,但它不能是我不想要的任何字符串。我希望我应该只能在IMapEditorState 中传递{[k: string]: ICountry} 的键或ICountry 中的name 的字符串。

有什么方法可以创建一个名为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};

如果你能帮助我,我将非常感激,如果你知道什么是游戏,请给出你对我的数据结构的看法。

【问题讨论】:

  • @CertainPerformance 我已经进一步创建了任何东西。只是想找到更好的方法。
  • @CertainPerformance 考虑function test(x : CountryName){...}。现在这里我想只能通过ICountry的name属性(暂时忽略对象的键)
  • 鉴于接口是name: CountryName;,输入function test(x : CountryName) 应该已经要求参数是允许的类型,即CountryName,仅此而已,对吧?
  • @CertainPerformance 我还希望countries 的键为CountryName 类型。但如果不可能,请解释为什么不能。

标签: javascript typescript react-hooks


【解决方案1】:

如果您希望能够在编译时进行这些检查,则必须列出所有可能的国家/地区名称:

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

或者,如果你可以定义一个所有可能国家的初始对象,你可以将它声明为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;

CountryName 的结果是 "cName1" | "cName2" | "cName3"。

然后你可以使用上面的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 };
}

然后会编译如下:

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

然后您可以在任何其他需要的地方使用CountryName:

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

【讨论】:

    【解决方案2】:

    打字稿 4.1.5:

    在我的情况下,我需要将这种格式“YYYY-MM-DD”的字符串化日期标记为 ISODate 而不仅仅是字符串。我使用了这种方法,如果有效字符串的数量不止几个,我认为它比this answer更有效:

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

    在执行不安全赋值时,您会得到所需的 Type 'string' is not assignable to type 'ISODate'.,但可以使用 typeguard 函数进行转换。您还可以获得字符串方法自动完成。

    【讨论】:

    • 在ISODateDifferentiator 中指定[key: string]: unknown; 有什么意义?我们可以只使用一个空接口或其他东西吗?
    猜你喜欢
    • 2017-08-21
    • 1970-01-01
    • 2020-12-22
    • 1970-01-01
    • 2019-06-05
    • 2018-04-28
    • 2016-07-24
    • 2020-05-08
    • 2020-04-14
    相关资源
    最近更新 更多