jtwang7 / JavaScript-Note

JavaScript学习笔记
10 stars 2 forks source link

TypeScript 从数组元素的值中定义 Union Type #65

Open jtwang7 opened 2 years ago

jtwang7 commented 2 years ago

TypeScript 从数组元素的值中定义 Union Type

参考文章:

场景

本文处理下面这个 TypeScript 开发场景: 有一个数组,数组中每一项都是一个对象,我希望定义一个 Type,是由数组中的每个对象中的某个属性值组成的联合类型 (union type)

// 数组
const configs = [
  {
    name: 'width',
    value: '60px',
  },
  {
    name: 'height',
    value: '40px',
  }
];

我期望将 configs 中的每一项中的 name 的实际字符串值,提炼成一个 union type。

// 期望得到的 type
type ConfigName = 'width' | 'height';

最直接的做法是直接像上面一样手动定义一下。这样写的缺点是,如果 configs 中的元素又增加了,例如 name 的取值增加一个 'max-width' ,那我们需要更新 ConfigName 的的定义。无疑增加了维护成本。 使用 TypeScript 编程的原则之一:更少的类型维护,更安全的类型推断。

解决思路

首先,我们尝试用 typeof 来进行类型定义。

type ConfigName = typeof configs[number]['name'];

// 推断结果为
// type ConfigName = string;

// 期望:
// type ConfigName = "width" | "height";

这与我们实际期望不符,我们发现,被推断为了 string 类型。并不是我们期望的 union type

const 断言

使用 TypeScriptconst 断言 const 断言有以下两个作用:

根据第 1 条规则,看上去好像可以恰好解决我们的问题,试一试:

// 对象断言为 const
const configs = [
  {
    name: 'width',
    value: '60px',
  },
  {
    name: 'height',
    value: '40px',
  }
] as const;

type ConfigName = typeof configs[number]['name'];

// 推断为:
// type ConfigName = "width" | "height";

// 期望:
// type ConfigName = "width" | "height";

缺点:推断的属性会被赋予 readonly 不可变属性

泛型函数 Generic Functions

在 TypeScript 中,我们想要描述两个值之间的对应关系时,会使用泛型。

type ConfigItem<T> = {
  name: T;
  value: string;
}
function defineConfigs<T extends string>(configs: Array<ConfigItem<T>>) {
  return configs;
}

const configs = defineConfigs([
  {
    name: 'width',
    value: '60px',
  },
  {
    name: 'height',
    value: '40px',
  }
]);

type ConfigName = typeof configs[number]['name'];

// 推断为:
// type ConfigName = "width" | "height";

// 期望:
// type ConfigName = "width" | "height";

原理:我们使用泛型函数在函数的输入和输出之间创建了一个链接,当调用它时,会根据其输入的具体值,得到一个具体的类型。

总结

至此,我们达到了最初的目的: