TypeScript 高级用法全解

TypeScript 是一种类型安全的 JavaScript 超集,除了基本类型和对象类型之外,TypeScript 还提供了一些高级类型系统,使得我们可以更好地处理复杂的数据结构和业务逻辑。本文将深入探讨 TypeScript 的高级类型系统,以更好地理解和使用这些高级类型,提高代码的可读性、可维护性和健壮性。

1. 字面量类型

在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即字面量类型。TypeScript 支持以下字面量类型:

  • 字符串字面量类型;
  • 数字字面量类型;
  • 布尔字面量类型;
  • 模板字面量类型。

(1)字符串字面量类型

字符串字面量类型其实就是字符串常量,与字符串类型不同的是它是具体的值:

type Name = "TS";
const name1: Name = "test"; // ❌ 不能将类型“"test"”分配给类型“"TS"”。ts(2322)
const name2: Name = "TS";

实际上,定义单个字面量类型在实际应用中并没有太大的用处。它的应用场景就是将多个字面量类型组合成一个联合类型,用来描述拥有明确成员的实用的集合:

type Direction = "north" | "east" | "south" | "west";

function getDirectionFirstLetter(direction: Direction) {
  return direction.substr(0, 1);
}

getDirectionFirstLetter("test"); // ❌ 类型“"test"”的参数不能赋给类型“Direction”的参数。
getDirectionFirstLetter("east");

这个例子中使用四个字符串字面量类型组成了一个联合类型。这样在调用函数时,编译器就会检查传入的参数是否是指定的字面量类型集合中的成员。通过这种方式,可以将函数的参数限定为更具体的类型。这不仅提升了代码的可读性,还保证了函数的参数类型。

除此之外,使用字面量类型还可以为我们提供智能的代码提示:

(2)数字字面量类型

数字字面量类型就是指定类型为具体的数值:

type Age = 18;

interface Info {
  name: string;
  age: Age;
}

const info: Info = {
  name: "TS",
  age: 28 // ❌ 不能将类型“28”分配给类型“18”
};

可以将数字字面量类型分配给一个数字,但反之是不行的:

let val1: 10|11|12|13|14|15 = 10;
let val2 = 10;

val2 = val1;
val1 = val2; // ❌ 不能将类型“number”分配给类型“10 | 11 | 12 | 13 | 14 | 15”。

(3)布尔字面量类型

布尔字面量类型就是指定类型为具体的布尔值(truefalse):

let success: true;
let fail: false;
let value: true | false;

success = true;
success = false;  // ❌ 不能将类型“false”分配给类型“true”

由于布尔字面量类型只有true或false两种,所以下面 value 变量的类型是一样的:

let value: true | false;
let value: boolean;

(4)模板字面量类型

在 TypeScript 4.1 版本中新增了模板字面量类型。什么是模板字面量类型呢?它一字符串字面量类型为基础,可以通过联合类型扩展成多个字符串。它与 JavaScript 的模板字符串语法相同,但是只能用在类型定义中使用。

① 基本语法

当使用模板字面量类型时,它会替换模板中的变量,返回一个新的字符串字面量。

type attrs = "Phone" | "Name";
type target = `get${attrs}`;

// type target = "getPhone" | "getName";

可以看到,模板字面量类型的语法简单,并且易读且功能强大。

假如有一个CSS内边距规则的类型,定义如下:

type CssPadding = 'padding-left' | 'padding-right' | 'padding-top' | 'padding-bottom';

上面的类型是没有问题的,但是有点冗长。margin 和 padding 的规则相同,但是这样写我们无法重用任何内容,最终就会得到很多重复的代码。

下面来使用模版字面量类型来解决上面的问题:

type Direction = 'left' | 'right' | 'top' | 'bottom';

type CssPadding = `padding-${Direction}`

// type CssPadding = 'padding-left' | 'padding-right' | 'padding-top' | 'padding-bottom'

这样代码就变得更加简洁。如果想创建margin类型,就可以重用Direction类型:

type CssMargin = `margin-${Direction}`

如果在 JavaScript 中定义了变量,就可以使用 typeof 运算符来提取它:

const direction = 'left';

type CssPadding = `padding-${typeof direction}`;

// type CssPadding = "padding-left"
② 变量限制

模版字面量中的变量可以是任意的类型吗?可以使用对象或自定义类型吗?来看下面的例子:

type CustomObject = {
  foo: string
}

type target = `get${CustomObject}`
// ❌ 不能将类型“CustomObject”分配给类型“string | number | bigint | boolean | null | undefined”。

type complexUnion = string | number | bigint | boolean | null | undefined;

type target2 = `get${complexUnion}`  // ✅

可以看到,当在模板字面量类型中使用对象类型时,就报错了,因为编译器不知道如何将它序列化为字符串。实际上,模板字面量类型中的变量只允许是string、number、bigint、boolean、null、undefined或这些类型的联合类型。

③ 实用程序

Typescript 提供了一组实用程序来帮助处理字符串。它们不是模板字面量类型独有的,但与它们结合使用时很方便。完整列表如下:

  • Uppercase<StringType>:将类型转换为大写。
  • Lowercase<StringType>:将类型转换为小写。
  • Capitalize<StringType>:将类型第一个字母大写。
  • Uncapitalize<StringType>:将类型第一个字母小写。

这些实用程序只接受一个字符串字面量类型作为参数,否则就会在编译时抛出错误:

复制

type nameProperty = Uncapitalize<'Name'>;
// type nameProperty = 'name';

type upercaseDigit = Uppercase<10>;
// ❌ 类型“number”不满足约束“string”。

type property = 'phone';
type UppercaseProperty = Uppercase<property>;
// type UppercaseProperty = 'Property';

下面来看一个更复杂的场景,将字符串字面量类型与这些实用程序结合使用。将两种类型进行组合,并将第二种类型的首字母大小,这样组合之后的类型符合驼峰命名法:

type actions = 'add' | 'remove';

type property = 'name' | 'phone';

type result = `${actions}${Capitalize<property>}`;
// type result = addName | addPhone | removeName | removePhone
④ 类型推断

在上面的例子中,我们使用使用模版字面量类型将现有的类型组合成新类型。下面来看看如何使用模板字面量类型从组合的类型中提取类型。这里就需要用到infer关键字,它允许我们从条件类型中的另一个类型推断出一个类型。

下面来尝试提取字符串字面量 marginRight 的根节点:

type Direction = 'left' | 'right' | 'top' | 'bottom';

type InferRoot<T> = T extends `${infer K}${Capitalize<Direction>}` ? K : T;

type Result1 = InferRoot<'marginRight'>;
// type Result1 = 'margin';

type Result2 = InferRoot<'paddingLeft'>;
// type Result2 = 'padding';

可以看到, 模版字面量还是很强大的,不仅可以创建类型,还可以解构它们。

⑤ 作为判别式

在TypeScript 4.5 版本中,支持了将模板字面量串类型作为判别式,用于类型推导。来看下面的例子:

interface Message {
    type: string;
    url: string;
}

interface SuccessMessage extends Message {
    type: `${string}Success`;
    body: string;
}

interface ErrorMessage extends Message {
    type: `${string}Error`;
    message: string;
}

function handler(r: SuccessMessage | ErrorMessage) {
    if (r.type === "HttpSuccess") { 
        let token = r.body;
    }
}

在这个例子中,handler 函数中的 r 的类型就被推断为 SuccessMessage。因为根据 SuccessMessage 和 ErrorMessage 类型中的type字段的模板字面量类型推断出 HttpSucces 是根据SuccessMessage中的type创建的。

2. 联合类型

(1)基本使用

联合类型是一种互斥的类型,该类型同时表示所有可能的类型。联合类型可以理解为多个类型的并集。 联合类型用来表示变量、参数的类型不是某个单一的类型,而可能是多种不同的类型的组合,它通过 | 来分隔不同的类型:

type Union = "A" | "B" | "C";

在编写一个函数时,该函数的期望参数是数字或者字符串,并根据传递的参数类型来执行不同的逻辑。这时就用到了联合类型:

function direction(param: string | number) {
  if (typeof param === "string") {
    ...
  }
  if (typeof param === "number") {
    ...
  }
  ...
}

这样在调用 direction 函数时,就可以传入string或number类型的参数。当联合类型比较长或者想要复用这个联合类型的时候,就可以使用类型别名来定义联合类型:

type Params = string | number | boolean;

再来看一个字符串字面量联合类型的例子,setStatus 函数只能接受某些特定的字符串值,就可以将这些字符串字面量组合成一个联合类型:

type Status = 'not_started' | 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔向理想的星辰大海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值