TypeScript—字面量类型

Literal-Types.png

为什么会有字面量类型

先来做个小小的测试题,你能准确猜出来,下面 TS 代码中变量的类型吗?

const companyName = "Shanshu";
let companyArea = "beijing";
var companyPeopleCount = 200;

等待十秒中……


答案揭晓:

const companyName = "Shanshu"; // Shanshu
let companyArea = "beijing";    // name
var companyPeopleCount = 200;   // number

解释下,因为 TS 的类型推断,TS 会把能改变(var 和 let 声明的变量)的变量 companyArea 和 companyPeopleCount 自动推断为合适的类型,const 声明的常量如果赋值为普通类型,因为其永远不会再改变了,则推断为字面量类型。

字面量类型的用处

在我们编程中,除了 const 声明的变量,一个变量只有一个值的意义并不是很大,字面量真正发挥威力是作为联合类型使用。

那使用字面量类型的好处有啥呢,我认为好处有两个:

  • 更加直观,程序不易出错
  • 细化 TS 类型系统

第一个好处,举例:

function printText(s: string, alignment: "left" | "right" | "center") {
  // ... do something
}
printText("Hello, world", "center");

想象一个函数的第二个参数,如果没有使用字面量类型,那只能用 string 类型,一旦使用 string 类型,意味着我们能传入任何字符串,万一第二个参数拼写错误,printText("Hello, world", "centre"); 程序的鲁棒性就不复存在了。

第二点好处,举例:

玩过 UI 框架都知道,table column 的 width 可以指定为 number 和 string。

interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  // ...
}
configure({ width: 100 }); // YES
configure("auto"); // YES
configure("automatic"); // NO

想象一下如果没有字面量类型,我们只能这么写 x: Options | string 把 auto 变成 string,这样的话 configure("automatic"); 就是对的了,容错性增强了,但程序的鲁棒性降低了。

字面量类型推论

当我们使用 const 变量初始化一个对象字面量类型的时候,例如 :

const obj = { counter: 0 };
if (someCondition) {
  obj.counter = 1;
}

因为对象的 counter 属性可能会被更改,所以 obj.counter 的类型会默认被推论为 number,而不是字面量类型 0,(PS:如果你想让 counter 属性就是字面类型 0,也是可以的,下面会介绍)。

知道这个特性,我们接下里在看一个例子。

相信大家在项目中都发送过 Ajax/Fetch 请求,而且为了参数便于管理,我们一般会把发送的参数放入一个对象里面,就像下面这样。

function handleRequest(url: string, method: "GET" | "POST") {
    // do something...
}
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'

在 TS 中 req.method 会报错,这是因为 method 参数指定的类型是字面量类型 "GET" | "POST",而 const 声明的字面量类型 req 对象的 method 会自动推论为 string,所以会报错。

那应该怎么解决呢?

使用断言,断言为字面量类型

  1. 类型断言
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
  1. const 类型断言(推荐这个)

这里的 const 不是声明变量的关键字,而是 const 类型。

const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

它会让 const 声明的变量变成真正的字面量类型,我们看下不加 const 断言 req 变量的类型推论:

不加 const 断言 req 变量的类型推论

下面是加上 const 断言的 req 变量的类型。

const 声明的变量变成真正的字面量类型

模版字面量类型

我们知道字面量类型,可以为数字、布尔值、字符串借助断言甚至可以是复杂数据类型的字面量。

不过接下里我们重点说说字符串字面量。提到字符串有一个难题就是字符串的拼接问题,JS 最优雅的字符串拼接,你一定很熟了,那就是模版字符串。

字符串字面量类型本质还是字符串,也会遇到拼接问题,那怎么搞呢?对,你没猜错 模版字面量类型(Template Literal Types,不过对 TS 版本要求是大于等于 4.1 。

我们看看模版字面量类型怎么使用,我现在需要一个类型变量 Greeting 是 hello world!,我可以这么做:

type Hello = "hello";
type World = "world";

type Greeting = `${Hello} ${World}!`;

演示结果:

type Greeting = "hello world!"

TS 的模版字面量类型的用法很简单和 JS 的模版字符串使用基本一致。

字符串字面量类型四个方法

JS 字符串有两个很实用的方法 toLowerCase 和 toUpperCase,分别负责把字符串全部变小写和全部变大写。字符串字面量类型本质作为字符串也有这两种方法,而且还多了两个,一种四个,不过在 TS 中应该叫泛型,而不是方法。

Uppercase

把每个字符串都转换成大写

type NickName = "CondorHero-2021";

// "CONDORHERO-2021"
type UppercaseNickName = Uppercase<NickName>;

Lowercase

把每个字符串都转换成小写

type NickName = "CONDORHERO-2021";

// "condorhero-2021"
type LowercaseNickName = Lowercase<NickName>;

Capitalize

首字符变成大写

type NickName = "condorhero-2021";

// "Condorhero-2021"
type CapitalizeNickName = Capitalize<NickName>;

Uncapitalize

首字符变成小写

type NickName = "CONDORHERO-2021";

// "cONDORHERO-2021"
type UncapitalizeNickName = Uncapitalize<NickName>;

这时候有个问题,这四个泛型的源码怎么实现呢?

来来,我们来看看,见证时刻:

type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

intrinsic 是什么呢?说好的源码呢。原来 intrinsic 的意思代表这四个泛型是通过 TS 编译器来实现的。

不满足这个答案的你肯定会问底层到底怎么实现的呢?其实也很简单通过 JS 来实现的,我们看下代码:

function applyStringMapping(symbol: Symbol, str: string) {
    switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
        case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
        case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
        case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
        case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
    }
    return str;
}

好了,到此关于字面量类型的内容就结束了。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容