【问题标题】:How to create a type from an array of objects in TypeScript?如何从 TypeScript 中的对象数组创建类型?
【发布时间】:2021-02-10 09:50:28
【问题描述】:

给定一个像这样的countries 数组:

const countries = [
  { name: "Australia", code: "AU" },
  { name: "Japan", code: "JP" },
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
];

生成以下类型的最简单方法是什么?

type CountryCodes = "" | "AU" | "JP" | "NO" | "RU";

注意:有一个额外的空字符串。

【问题讨论】:

  • 你不能从你所拥有的东西中得到那个,因为国家对象被扩大到code: string
  • @jonrsharpe code: string 不需要在那里。我只是想确保CountryCodes 类型只有来自countries 的代码,没有其他代码。
  • @MishaMoroshko 参数是函数的一部分吗?如果是的话,它可以通过泛型来完成,但它本身并不能做很多事情。
  • @Akxe 你指的是什么参数/函数?
  • @MishaMoroshko 如果您查看我的答案,您会看到包装函数如何影响结果。例如,很多泛型可以在类中使用。

标签: typescript union-types


【解决方案1】:

虽然从 JS 的角度来看这个函数是没用的。它能够将代码统一为一种类型。

function createCountries<T extends string>(
  contries: { name: string; code: T }[],
): { name: string; code: T }[] {
  return contries;
}

const countries = createCountries([
  { name: "Australia", code: "AU" },
  { name: "Japan", code: "JP" },
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
]);

type CountryCodes = "" | (typeof countries)[number]["code"]; // "" | "AU" | "JP" | "NO" | "RU";

// Example - How to use the type
function getCountryByCode(code: CountryCode): Country | undefined {
  return countries.find(country => country.code == code);
}

countries 的类型:

{
    name: string;
    code: "AU" | "JP" | "NO" | "RU";
}[]

没有更多信息,这是你能做的最好的......

type Countries = { name: string; code: string }[];

const countriesAbc = [
  { name: "Australia", code: "AU" },
  { name: "Japan", code: "JP" },
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
] as const;

const countries: Countries = [...countriesAbc];

type CountryCodes = "" | (typeof countriesAbc)[number]["code"]; // "" | "AU" | "JP" | "NO" | "RU";

【讨论】:

  • 是否可以定义一个codesOf 函数来获取:type CountryCodes = codesOf(countriesAbc) ?我想避免 "" | (typeof countriesAbc)[number]["code"] 重复。
  • @MishaMoroshko 不,构建时的打字稿不提供任何生成类型的功能。它有助手,但在这里用处不大。但是,您可以导出类型并在任何您想要的地方使用它。只需将其导入另一个文件或使用它(在同一个文件中)。为您添加示例
【解决方案2】:

首先,如果不对输入数据集类型稍加修改,就无法实现您想要的。正如jonsharpe's comment 所说,这里的数组成员类型已扩大到{ name: string; code: string; }。这可以通过as const 断言轻松解决:

const countries = [
  { name: "Australia", code: "AU" },
  { name: "Japan", code: "JP" },
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
] as const;

现在数组本身被认为是一个元组,并且每个成员的属性也是readonly。之后,您只需要一个映射类型来提取元组的值(通常使用T[number] 完成),获取code 成员的类型并从中构建一个联合:

type CountryCodes<T> = { 
  [ P in keyof T ] : T[P] extends { code: string } ? T[P]["code"] : never  
}[keyof T & number] | "";

T[P] extends { code: string } 约束确保我们可以用"code" 索引T[P]。结果正是你想要的(请注意,一切都是在类型系统中完成的):

type cc = CountryCodes<typeof countries>; //type cc = "" | "AU" | "JP" | "NO" | "RU"

Playground


利用 4.1 的 key remapping 功能的更简洁的版本:

type CountryCodes<T> = keyof { 
  [ P in keyof T as T[P] extends { code: string } ? T[P]["code"] : never ] : T[P]
} | "";

【讨论】:

  • 谢谢!这几乎就是我想要的!不过有一个问题,as PP in keyof T as P 中的重要性是什么?
  • @MishaMoroshko 很好 - 实际上,它没有任何意义,我删除了它 - 最初,我正在考虑密钥重映射实现,这里是:type CountryCodes&lt;T&gt; = keyof { [ P in keyof T as T[P] extends { code: string } ? T[P]["code"] : never ] : T[P] } | "";,结果是一样的,但它需要TS 4.1
【解决方案3】:

你可以用其他方式来做

type CountryCodes = "" | "AU" | "JP" | "NO" | "RU";

interface Country {name: string; code: CountryCodes} 
// Alternatively
// type Country = {name: string; code: CountryCodes}


const countries: Array<Country> = [ 
  { name: "Australia", code: "DE" }, // COMPILE ERROR
  { name: "Japan", code: "JP" },
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
];

甚至更好

type NotEmptyCountryCodes = "AU" | "JP" | "NO" | "RU";

type CountryCodes = "" | NotEmptyCountryCodes;

interface Country {name: string; code: NotEmptyCountryCodes}
// Alternatively
// type Country = {name: string; code: NotEmptyCountryCodes}

const countries: Array<Country> = [ 
  { name: "Australia", code: "DE" }, // COMPILE ERROR
  { name: "Japan", code: "" }, // COMPILE ERROR
  { name: "Norway", code: "NO" },
  { name: "Russian Federation", code: "RU" }
];

【讨论】:

    【解决方案4】:

    我认为从域的角度来看,它可能期望国家代码的变化更少,国家名称更灵活。如果您的问题只是从地图列表中创建联合字符串类型,另一种方法更好。

    从 const 列表中创建联合类型 CountryCode(这在之后很有用):

    const countryCodeList = ["AU", "JP", "NO", "RU"] as const;
    type CountryCode = "" | (typeof countryCodeList)[number];
    

    用 CountryCode 定义国家:

    type Country = { name: string; code: CountryCode};
    

    与in搭配使用,安全灵活:

    // mock of date access.
    const fetchByCode = (code: CountryCode, lang: string) : string => {
      return "name of " + code;
    };
    
    // create county data from code list.
    const createCountries = () : Country[] => {
      return countryCodeList.map((code) => {
        const name = fetchByCode(code,'en');
        return {name, code}
      });
    };
    
    // use country data.
    const countriesEn = createCountries()
    console.log(countriesEn);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 2013-09-02
      • 2023-01-20
      • 1970-01-01
      • 2019-01-02
      • 2021-08-02
      • 2022-08-20
      相关资源
      最近更新 更多