【TS 演化史 -- 13】字符串枚举 和 弱类型(Weak Type)探测

2022-06-15 14:06:29 浏览数 (1)

作者:Marius Schulz 译者:前端小智 来源:https://mariusschulz.com/

点赞再看,养成习惯本文 GitHub https://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

字符串枚举

TypeScript 2.4 实现了最受欢迎的特性之一:字符串枚举,或者更精确地说,带有字符串值成员的枚举

现在可以将字符串值分配给枚举成员了:

代码语言:javascript复制
enum MediaTypes {
  JSON = 'application/json',
  XML = 'application/xml'
}

字符串枚举可以像 TypeScript 中的任何其他枚举一样使用:

代码语言:javascript复制
enum MediaTypes {
  JSON = "application/json",
  XML = "application/xml"
}

fetch("https://example.com/api/endpoint", {
  headers: {
    Accept: MediaTypes.JSON
  }
}).then(response => {
  // ...
});

下面编译目标为 es3/es5 生成的 JS 的代码:

代码语言:javascript复制
var MediaTypes;
(function (MediaTypes) {
    MediaTypes["JSON"] = "application/json";
    MediaTypes["XML"] = "application/xml";
})(MediaTypes || (MediaTypes = {}));
fetch("https://example.com/api/endpoint", {
    headers: {
        Accept: MediaTypes.JSON
    }
}).then(function (response) {
    // ...
});

这个输出几乎与编译器为带有数字成员的枚举生成的输出类似,只是字符串值成员没有反向映射。

字符串值枚举成员没有反向映射

TypeScript 为每个构造映射对象的枚举发出一些映射代码。对于字符串值枚举成员,此映射对象定义从键到值的映射,反之则不是:

代码语言:javascript复制
var MediaTypes;
(function (MediaTypes) {
    MediaTypes["JSON"] = "application/json";
    MediaTypes["XML"] = "application/xml";
})(MediaTypes || (MediaTypes = {}));

这意味着咱们可以通过键解析一个值,但不能通过键的值解析一个键

代码语言:javascript复制
MediaTypes["JSON"]; // "application/json"
MediaTypes["application/json"]; // undefined

MediaTypes["XML"]; // "application/xml"
MediaTypes["application/xml"]; // undefined

与具有数字值成员的枚举进行比较:

代码语言:javascript复制
enum DefaultPorts {
  HTTP = 80,
  HTTPS = 443
}

在这种情况下,编译器还会生成从值到键的反向映射

代码语言:javascript复制
var DefaultPorts;
(function(DefaultPorts) {
  DefaultPorts[(DefaultPorts["HTTP"] = 80)] = "HTTP";
  DefaultPorts[(DefaultPorts["HTTPS"] = 443)] = "HTTPS";
})(DefaultPorts || (DefaultPorts = {}));

这种反向映射允许通过键值解析键和通过键解析值

代码语言:javascript复制
DefaultPorts["HTTP"]; // 80
DefaultPorts[80]; // "HTTP"

DefaultPorts["HTTPS"]; // 443
DefaultPorts[443]; // "HTTPS"

用常量枚举内联枚举成员

为了避免生成的枚举映射代码的开销,咱们可以通过将const修饰符添加到声明中,将MediaTypes枚举转换为const枚举:

代码语言:javascript复制
const enum MediaTypes {
  JSON = "application/json",
  XML = "application/xml"
}

fetch("https://example.com/api/endpoint", {
  headers: {
    Accept: MediaTypes.JSON
  }
}).then(response => {
  // ...
});

使用const修饰符后,编译器将不会为MediaTypes枚举生成任何映射代码。相反,它将内联所有使用站点上每个枚举成员的值,从而可能节省一些字节和属性访问间接性的开销:

代码语言:javascript复制
fetch("https://example.com/api/endpoint", {
    headers: {
        Accept: "application/json" /* JSON */
    }
}).then(function (response) {
    // ...
});

但是,如果由于某种原因,咱们需要在运行时访问映射对象,该怎么办呢

使用preserveConstEnums生成一个常量枚举

有时,可能有必要发出一个const枚举的映射代码,例如,当某些 JS 代码需要访问它时,在这种情况下,可以在tsconfig.json文件中打开prepareConstEnums编译器选项:

代码语言:javascript复制
{
  "compilerOptions": {
    "target": "es5",
    "preserveConstEnums": true
  }
}

如果咱们使用设置的preserveConstEnums选项再次编译代码,编译器仍然会内联MediaTypes,同时它也会发出映射代码:

代码语言:javascript复制
var MediaTypes;
(function (MediaTypes) {
    MediaTypes["JSON"] = "application/json";
    MediaTypes["XML"] = "application/xml";
})(MediaTypes || (MediaTypes = {}));
fetch("https://example.com/api/endpoint", {
    headers: {
        Accept: "application/json" /* JSON */
    }
}).then(function (response) {
    // ...
});

弱类型(Weak Type)探测

TypeScript 2.4 引入了弱类型的概念。如果类型的所有属性都是可选的,则认为类型是弱类型。更具体地说,弱类型定义一个或多个可选属性,没有必需属性,也没有索引签名。

例如,下面的类型被认为是弱类型:

代码语言:javascript复制
interface PrettierConfig {
  printWidth ?: number;
  tabWidth?: number;
  semi?: boolean;
}

弱类型检测的主要目标是发现代码中可能的错误,否则这些错误将是无声的错误。考虑一下这个例子:

代码语言:javascript复制
interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true
};

const formatter = createFormatter(prettierConfig); // 错误

在 TypeScript 2.4 之前,这段代码是类型正确的。PrettierConfig的所有属性都是可选的,所以完全可以不指定它们。相反,咱们的prettierConfig对象有一个semicolons 属性,它在prettierConfig类型中不存在。

从 TypeScript 2.4 开始,当属性没有重叠时,给弱类型赋值是一个错误,带有以下消息的类型检查器错误

代码语言:javascript复制
类型“{ semicolons: boolean; }”与类型“PrettierConfig”不具有相同的属性

虽然咱们的代码并非严格错误,但它可能包含一个静默错误。createFormatter函数可能会忽略它不知道的config的任何属性(例如semicolons),并退回到每个属性的默认值。在这种情况下,无论将其semicolons 设置为true还是false,咱们的semicolons 属性都不会起作用。

TypeScript 的弱类型检测帮助咱们解决了这个问题,并在函数调用中为prettierConfig参数提出了一个类型错误。这样,咱们很快就会意识到有些事情看起来不对劲。

显式类型注解

无需依赖弱类型检测,咱们可以向prettierConfig对象显式添加类型注释:

代码语言:javascript复制
const prettierConfig: PrettierConfig = {
  semicolons: true // Error
};

const formatter = createFormatter(prettierConfig);

使用了这个类型注释,咱们会得到以下类型错误:

代码语言:javascript复制
不能将类型“{ semicolons: boolean; }”分配给类型“PrettierConfig”。
  对象文字可以只指定已知属性,并且“semicolons”不在类型“PrettierConfig”中。

这样,类型错误就出现在咱们(错误地)定义semicolons 属性的地方,而不是将prettierConfig参数传递给createFormatter函数的行中。

另一个好处是 TypeScript 语言可以给咱们自动完成建议,因为类型注释告诉它咱创建的对象的类型。

弱类型的解决方法

如果出于某种原因,咱们就是不想从特定弱类型的弱类型检测中获得错误,该怎么办?一种解决方法是使用unknown 类型添加索引签名到PrettierConfig类型:

代码语言:javascript复制
interface PrettierConfig {
  [prop: string]: unknown;
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true
};

const formatter = createFormatter(prettierConfig);

现在,这段代码是类型正确的,因为咱们在PrettierConfig类型中明确允许使用unknown名称的属性。

或者,咱们可以使用类型断言来告诉类型检查器将prettierConfig对象视为类型为PrettierConfig

代码语言:javascript复制
interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  semicolons: true
};

const formatter = createFormatter(
  prettierConfig as PrettierConfig
);

建议不要使用类型断言来绕过弱类型检。也许在一个用例中,这种方法是有意义的,但是通常,咱们应该更喜欢其他解决方案之一。

弱类型检测的限制

请注意,弱类型检测仅在属性中完全没有重叠时才会产生类型错误。一旦指定了弱类型中定义的一个或多个属性,编译器将不再引发类型错误

代码语言:javascript复制
interface PrettierConfig {
  printWidth?: number;
  tabWidth?: number;
  semi?: boolean;
}

function createFormatter(config: PrettierConfig) {
  // ...
}

const prettierConfig = {
  printWidth: 100,
  semicolons: true
};

const formatter = createFormatter(prettierConfig);

在上面的例子中,同时指定了printWidthsemicolons。因为printWidth存在于PrettierConfig中,现在咱们的对象和PrettierConfig类型之间有一个属性重叠,弱类型检测不再为函数调用引发类型错误。

这里的结论是,弱类型检测目的设计是为了最小化误报(正确的使用被视为不正确)的数量,这是以牺牲更少的真报(不正确的使用被视为不正确)为代价的。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:

https://mariusschulz.com/blog...

https://mariusschulz.com/blog...

https://www.tslang.cn/docs/re...


交流

干货系列文章汇总如下,觉得不错点个Star

https://github.com/qq44924588...

我是小智,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

0 人点赞