关于TypeScript应该尽量避免的语法总结

目录
  • 前言
  • 避免枚举
  • 避免名字空间
  • 避免装饰器(对于现在而言)
  • 避免 Private 关键字
  • 总结

前言

这个文章列举了我们建议尽量避免的 TypeScript 语法。但是因为你的项目的情况,有可能使用这些特性也是合理的,但是我们仍然建议,在默认情况下,尽量避免使用这些特性。

随着时间的发展,TypeScript 已经是一门复杂的语言。在早期的时候,TypeScript 研发团队增加了一些不兼容 JavaScript 的语法。但是随着发展,新的版本已经不会这么做了,会非常保守地和严格地遵循 JavaScript 的语法特性。(译者注,使用和 JavaScript 严格兼容的语法带来的好处不计其数。)

就像其他成熟的语言,我们在考虑 TypeScript 的语法使用哪些,避免哪些,并不是一个容易的决定。我们经验主要来自于Execute Program 的后端和前端的建设经验,以及创建我们的 TypeScript 课程时的经验。

避免枚举

枚举提供了一组常量。在下面的例子里,HttpMethod.Get 是字符串 ‘Get’ 的名字。HttpMethod 类型和一个联合类型是一样的,如 'GET' | 'POST'。

enum HttpMethod {
  Get = 'GET',
  Post = 'POST',
}
const method: HttpMethod = HttpMethod.Post;
method; // Evaluates to 'POST'

下面是支持使用枚举的原因:

假设,我们最终要替换 ‘POST’ 为 ‘post’。我们只要替换枚举的值就能达成这一目的。我们其他的代码因为引用的是 HttpMethod.Post ,所以完全不用改。

现在假设,如果我们用联合类型来实现这个场景。我们定义了联合类型 'GET' | 'POST',然后我们决定把它们改为小写的 'get' | 'post'。现在如果使用 'GET' 或者 'POST' 作为 HttpMethod 的代码就会报类型错误。我们需要把所有的代码手动改一遍。从这个例子来说,使用枚举能简单一些。

这个支持使用枚举的例子可能不是那么有说服力。当我们增加了一个枚举和联合类型的时候,实际上在创建以后是很少更改的。使用联合类型,确实会带来更多的更改成本,但是其实不是一个问题,因为实际上是很少更改的。即便要更改,因为有类型错误,我们并不害怕少改了。

使用枚举的坏处是:

我们需要适应 TypeScript 的语法。TypeScript 应该是 JavaScript,但是增加了静态类型。如果我们去掉 TypeScript 的类型,我们就应该得到一份完整有效的 JavaScript 的代码。(译者注:这个原因是整篇文章的核心,核心好处之一就是,你可以通过 esbuild 而不是 tsc 完成你的 ts 代码到 js 代码的转换,这个速度差距可能是 10-1000倍。。并且不引入 tsc,代表着少了一个可能出问题的地方。)在 TypeScript 的官方文档中,之前描述 TypeScript 的文档是 “类型级别的扩展”:即 TypeScript 是 JavaScript 类型级别的扩展,所有 TypeScript 的特性不改变运行时的行为。

下面是一个类型级别扩展的例子, TypeScript 的例子:

function add(x: number, y: number): number {
  return x + y;
}
add(1, 2); // Evaluates to 3

TypeScript 的编译器检查了代码的类型。然后生成了 JavaScript 的代码。很幸运,这个过程很简单:编译器只要把类型标注去掉就好了。在这个例子里,只要把 :number 去掉,下面就是完美的 JavaScript 代码:

function add(x, y) {
  return x + y;
}
add(1, 2); // Evaluates to 3

绝大部分 TypeScript 的特性都有这个特性,遵循了类型级别扩展的法则。要得到 JavaScript 代码,只需要去掉类型标准即可以。

然而,枚举打破了这个法则。HttpMethod 和 HttpMethod.Post是一部分的类型。他们应该被去除。然而,如果编译器去除这些代码,就会有问题,因为我们实际上在把 HttpMethod.Post 当成值类型在使用。如果编译器简单删除这些代码,这些代码就不能跑了。

/* This is compiled JavaScript code referencing a TypeScript enum. But if the
 * TypeScript compiler simply removes the enum, then there's nothing to
 * reference!
 *
 * This code fails at runtime:
 *   Uncaught ReferenceError: HttpMethod is not defined */
const method = HttpMethod.Post;

TypeScript 的解决方案,就是打破自己的规则。当编译一个枚举的时候,编译器会自己生成一些 JavaScript 代码。其实很少 TypeScript 特性会这样做,这个其实让 TypeScript 的编译模型变得复杂了。因为这些原因,我们建议避免使用枚举,而用联合类型来取代它。

为什么类型级别扩展这个规则这么重要呢?

让我们来看,这个法则在和 JavaScript 和 TypeScript 的工具链生态互动时,会发生什么。TypeScript 的项目都是从 JavaScript 项目继承而来的,所以使用打包工具和编译工具,例如 webpack 和 babel 是很正常的。这些工具都是为了 JavaScript 设计的,即便在今天,依然是关注在 JavaScript 上。每一个工具都有自己的生态。这里有无数的 Babel 和 Webpack 自有的生态的插件。

有可能让所有 Babel 和 Webpack 以及他们的生态插件支持 TypeScript 么?对于大部分 TypeScript 语言来说,实际上类型扩展规则让这些内容支持 TypeScript 很简单。工具只要去掉类型标准,然后对其余的 JavaScript 做剩下的工具就好了。

当对于像枚举这样的特性(包括名字空间 namespaces),这个事儿要复杂一些。不能简单移除枚举。工具需要把 enum HttpMethod { ... } 转译 成合适的 JavaScript 代码,因为 JavaScript 并没有 enum 关键字。

这会带来一些实际的工作量,来处理 TypeScript 自己打破自己的类型扩展法则的问题。像 Babel、webpack 以及他们的生态插件,都是先对 JavaScript 作为设计对象,TypeScript 一般来说只是他们支持的一个功能。很多时候,TypeScript 的支持并不能收到像 JavaScript 一样的支持,就会有很多 Bug。(译者注:考虑 JavaScript 实际上让这些工具和插件的难度小很多,考虑 TypeScript,很多问题其实变复杂了,而且这个复杂度的提升不一定是有价值的。时至今日,依然是 JavaScript 的代码和需求远远大于 TypeScript。即便出于降低这些工具的复杂度的目的,也不应该为了解决 TypeScript 的问题而引入 这些问题。最核心的运行时,依然,以及必然是 JavaScript。)

很多工具的工作主要是在处理变量声明和函数声明,这些事情其实相对都是比较容易做的。但是牵扯枚举和名字空间,就不能仅仅去掉类型标注开始做逻辑了。你当然可以信赖 TypeScript 的编译器,但是很多不常用的工具可不一定考虑这个问题了。

当你的编译器、打包器、压缩器、linter、代码格式器(译者注:其实代码格式器很容易造成 bug,尤其对于 TypeScript)只要发生了一个对于上述的事儿处理有问题,是非常难进行 debug 的。编译器的 Bug 是非常非常难找的(译者注:当出现一个 bug,你会第几直觉认为是编译器的错误呢?其实不使用这些特性,你的代码是不依赖 TypeScript 编译器的,这一点至关重要。)。主要这篇文章的这些文字:经历了几周以后,在我的同事的帮助下,我们对于这个bug牵扯的范畴有了更深的认识。(注意加粗字体)(译者注:我本来花了大约两个月的时间去研究 TypeScript 的装饰器以及装饰器元数据,然后计划把他们加入到我自己的框架里。但是最后沮丧的发现,如果我引入他们,我就没有办法用 esbuild 了,原因是 esbuild 不计划支持 TypeScript 的装饰器元数据,但是支持了装饰器,但是这个支持其实也很新,而我的整个框架其实是以 esbuild 为基石的。我很沮丧,放弃了 TypeScript 的装饰器)(译者注:引入 tsc 是不明智的,因为 tsc 非常非常复杂。实际上,你只用类型的话,在代码编写阶段基本也就完成了绝大部分 tsc 的事情。在最后用 esbuild 一去类型,就可以继续了。)

避免名字空间

名字空间类似 module,但是一个文件里可以有多个名字空间。例如,我们在一个文件里引入了不同名字空间的导出代码,以及它们对应的测试。(我们不建议这样使用名字空间,这里只是作为一个探讨的例子。)

namespace Util {
  export function wordCount(s: string) {
    return s.split(/\b\w+\b/g).length - 1;
  }
}

namespace Tests {
  export function testWordCount() {
    if (Util.wordCount('hello there') !== 2) {
      throw new Error("Expected word count for 'hello there' to be 2");
    }
  }
}

Tests.testWordCount();

名字空间在实践上会造成一些问题。在上面的枚举的例子里,我们看到了 TypeScript 的类型扩展法则。通常,TypeScript 去除类型标注,留下的就是 JavaScript 的代码。

名字空间自然也打破了这一设定。在 namespace Util { export function wordCount ... } 代码里,我们不能仅仅靠去除类型标注就获得 JavaScript 的代码。整个名字空间就是一个 TypeScript 的类型定义!在其他的代码里使用 Util.wordCount(...) 会发生什么呢?如果我们删除 Util 名字空间,然后生成 JavaScript 代码,Util 就没有了。所以 Util.wordCount(...) 也不能工作。

就和枚举一样,TypeScript 也不能仅仅删除名字空间定义,而要生成一些 JavaScript 的代码。

对于枚举,我们建议用联合类型来取代。对于名字空间,我们建议就用 ESM 取代就好了。虽然创建很多文件很麻烦。但是两者能达成的效果是完全一样的。

避免装饰器(对于现在而言)

装饰器是一个可以修改和取代其他函数或者类的方法。这里是一个从 TypeScript 官方文档里找到的装饰器的例子:

// This is the decorator.
@sealed
class BugReport {
  type = "report";
  title: string;

  constructor(t: string) {
    this.title = t;
  }
}

@sealed 装饰器暗示了C# 的 sealed 装饰器。这个装饰器可以防止其他类继承这个类。我们可以实现一个 sealed 的函数,然后接受一个类,修改它,让它不能继承这个类。

装饰器一开始是首先在 TypeScript 添加的,然后 JavaScript(ECMAScript)才开始了标准化进程。在 2022 年1月,装饰器依然是一个 ECMAScript 提案阶段 2 的提案。阶段 2 代表在 “draft”(起草)阶段。装饰器提案似乎一直停滞在委员会中:实际上,这个提案是在 2019 年 2 月到达阶段 2 的。

我们建议在 stage 3 前,避免使用装饰器。stage 3 指 “candidate” 阶段,或者 stage 4 “finished” 阶段。

有这样的可能,即 ECMAScript 永远不完成装饰器提案。如果这个提案不完成,装饰器的处境就和枚举、名字空间一样。使用装饰器,就代表着,打破了 TypeScript 的类型扩展规则,并且使用这个特性,很多打包工具,可能都是有问题的。我们不知道多会能让装饰器通过,但是装饰器带来的好处并没有那么大,所以我们选择等待。

一些开源的库,例如有名的 TypeORM,非常重的使用了装饰器。我们承认,如果遵守我们的建议,就不能使用 TypeORM。当然使用 TypeORM 和装饰器有时候是好的选择,但是你应该明白这么做带来的问题,你要知道,目前装饰器的提案的标准化过程可能永远不会结束。(译者注:如果你想享受 esbuild 带来的好处,装饰器用的深就可能是个问题。当然,如果你的业务可以自闭在一套装饰器写的框架里,可能也不是非常大的问题。但是,如果 JS 的装饰器出现,现有的装饰器框架可能就有问题了。)

避免 Private 关键字

TypeScript 有两种方式让一个类型属性私有。老的方法是 private 关键字,这个是 TypeScript 独有的。目前还有一个新的方式:#somePrivateField,这个是 JavaScript 的方式。下面是一个例子:

class MyClass {
  private field1: string;
  #field2: string;
  ...
}

我们建议 #somePrivateField 字段。但是这两个方法基本是等同的。但是我们建议更多使用 JavaScript 的特性。

来总结一些我们的四个建议:

  • 避免 enum 枚举
  • 避免名字空间 namespace
  • 避免装饰器,尽量等到这个语法标准化完成。如果你需要一个库用装饰器,要考虑它的标准化状态。
  • 尽量用 #somePrivateField而不是private somePrivateField.

尽管我们建议大家避免使用这些特性,但是学习这些特性的知识还是大有裨益的。因为在遗产代码里,你还是会大量看到这些东西,甚至一些新的代码。我们相信,肯定不是所有人都认同这些建议。

总结

到此这篇关于TypeScript应该尽量避免的语法的文章就介绍到这了,更多相关TS尽量避免的语法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 分享Typescript的13个基础语法

    目录 一.Ts是什么 二. 基本语法 1.声明原始数据类型 2.声明Object类型 2.1声明数组类型 2.2声明元组类型 3.声明枚举类型 4.函数参数与返回类型 5.任意类型 6.类型断言 7.接口基本使用 8.类基本使用 9.类的访问修饰符 10.类只读属性 11.类与接口 12.抽象类 13.泛型 一.Ts是什么 首先,强类型不允许随意的隐式类型转换,而弱类型是允许的.JavaScript就是经典的弱类型语言.而Typescript可以说是JavaScript的超集,在JS的基础上新增

  • TypeScript中的类型断言[as语法|<>语法]的使用

    Typescript中类型断言官方解释 要理解好类型断言,其实就深刻理解一句话:你会比TypeScript更了解某个值的详细信息 . 类型断言,断言 断言,顾名思义,我断定怎么怎么样,代入这句话里就是,我断定这个类型是什么.当然这是我们主观上的思维逻辑,程序并不认可,所以我们需要告诉程序:“相信我,我知道自己在干什么” . 这么干说,大家可能还是理解的不够透彻,我用两个函数举一个例子: /** * @param d 日期 * @param f 想要格式化的字符串 */ function date

  • vue语法自动转typescript(解放双手)

    代码的复用是一件很常见的事情,如果是公共代码的复用那还好说,直接做成一个内部私有库,想用的话安装一下 npm包就行了,但是业务代码的复用就不好做成包了,一般都是复制粘贴 我一般写代码的时候,如果觉得某段业务代码以前见过其他人写过,那么考虑到业务优先性,只要别人的代码不是写得太烂,我一般会优先抄别人的代码,省得自己再写一遍 然后我就遇到了一个问题,公司目前前端项目大部分都是 vue,早期没有 ts这个说法,后来新项目才逐渐引入 ts,所以新项目用的是 vue-ts,而一般想抄的老代码都是没有引入

  • TypeScript声明文件的语法与场景详解

    目录 简介 语法 内容 模块化 模块语法 三斜线指令 reference amd-module 场景 1. 在内部项目中给内部项目写声明文件 2. 给第三方包写声明文件 全局变量的第三方库 修改全局变量的模块的第三方库的声明 修改window ESM和CommonJS UMD 模块插件 总结 简介 声明文件是以.d.ts为后缀的文件,开发者在声明文件中编写类型声明,TypeScript根据声明文件的内容进行类型检查.(注意同目录下最好不要有同名的.ts文件和.d.ts,例如lib.ts和lib.

  • TypeScript新语法之infer extends示例详解

    目录 正文 模式匹配 提取枚举的值的类型 总结 正文 我们知道,TypeScript 支持 infer 来提取类型的一部分,通过模式匹配的方式. 模式匹配 比如元组类型提取最后一个元素的类型: type Last<Arr extends unknown[]> = Arr extends [...infer rest,infer Ele] ? Ele : never; 比如函数提取返回值类型: type GetReturnType<Func extends Function> = F

  • 关于TypeScript应该尽量避免的语法总结

    目录 前言 避免枚举 避免名字空间 避免装饰器(对于现在而言) 避免 Private 关键字 总结 前言 这个文章列举了我们建议尽量避免的 TypeScript 语法.但是因为你的项目的情况,有可能使用这些特性也是合理的,但是我们仍然建议,在默认情况下,尽量避免使用这些特性. 随着时间的发展,TypeScript 已经是一门复杂的语言.在早期的时候,TypeScript 研发团队增加了一些不兼容 JavaScript 的语法.但是随着发展,新的版本已经不会这么做了,会非常保守地和严格地遵循 Ja

  • TypeScript Type Innference(类型判断)

    TypeScript 是微软开发的 JavaScript 的超集,TypeScript兼容JavaScript,可以载入JavaScript代码然后运行.TypeScript与JavaScript相比进步的地方 包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销:增加一个完整的类结构,使之更新是传统的面向对象语言. 为什么会有 TypeScript? JavaScript 只是一个脚本语言,并非设计用于开发大型 Web 应用,JavaScript 没有提供类和模块的概

  • 使用TypeScript开发微信小程序的方法

    TypeScript简介: TypeScript是一种由微软开发的自由和开源的编程语言.它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程.安德斯·海尔斯伯格,C#的首席架构师,已工作于TypeScript的开发. TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以不加改变的在TypeScript下工作.TypeScript是为大型应用之开发而设计,而编译时它产生 JavaScript 以确保兼容性.

  • 用VsCode编辑TypeScript的实现方法

    TypeScript是Javascript的超集,它提供了类.模块.接口来帮助你构建强大的组件. VS Code下TypeScript支持在两种不同的模式下运行: 文件范围:在这个模式下,VsCode打开的TypeScript文件被视为独立单元.只要A.ts没有明确引用b.ts(使用///引用目录或额外模块),那么两个文件就没有通用项目上下文. 显式项目:一个typescript项目通过一个tsconfig.json被定义.该文件指定ts项目根目录,列出了属于项目的文件还有编译选项.更多有关ts

  • 一文搞懂TypeScript的安装、使用、自动编译的教程

    1. 初识 TypeScript 上篇文章给大家介绍过TypeScript的安装.使用.自动编译的实现  需要的朋友点击查看. TypeScript 的介绍 TypeScript 是一种由微软开发的开源.跨平台的编程语言.它是 JavaScript 的超集,最终会被编译为 JavaScript 代码. 2012 年 10 月,微软发布了首个公开版本的 TypeScript,2013 年 6 月 19 日,在经历了一个预览版之后微软正式发布了正式版 TypeScript TypeScript 的作

  • typescript基本数据类型HTMLElement与Element区别

    目录 TypeScript是什么? TypeScript的安装和编译 上手实践 typescript中HTMLElement 和 Element的区别 探讨 TypeScript是什么? 涉及代码仓库 github.com/Dartgm/dart… TypeScript是由微软开发的一款开源的编程工具 TypeScript是JavaScript的超集,遵循最新的ES5/ES6规范,TypeScript扩展了JavaScript的语法 TypeScript更像后端Java,C#这样的面向对象的语言

  • TypeScript实现类型安全的EventEmitter

    目录 正文 EventEmitter 实现 类型安全的 EventEmitter 临时扩展自定义事件 结尾 正文 最近个人项目用 EventEmitter 模块越来越多了,因为类型不够安全,写起来要很小心.所以打算改良一下,实现 TypeScript 类型安全的 EventEmitter,解决事件名和函数类型不能做检验的问题. Nodejs 的 EventEmitter 是一个发布订阅模块. 利用该类,我们可以实现事件的监听,被监听对象会在合适的时机触发事件,调用监听对象提供的方法,是模块间解耦

  • MySQL无GROUP BY直接HAVING返回空的问题分析

    有一张表,id是主键,这样的写法可以返回一条记录: 复制代码 代码如下: "SELECT * FROM t HAVING id=MIN(id);" 但是只是把MIN换成MAX,这样返回就是空了: 复制代码 代码如下: "SELECT * FROM t HAVING id=MAX(id);" 这是为什么呢?我们先来做个试验,验证这种情况.这是表结构,初始化两条记录,然后试验: 复制代码 代码如下: root@localhost : plx 10:25:10> s

  • Vue单文件组件基础模板小结

    背景 相信大家在使用Vue开发项目时,基本都是以单文件组件的形式开发组件的,这种方式好处多多: 1.代码集中,便于开发.管理和维护 2.可复用性高,直接将vue文件拷贝到新项目中 我暂时就想到这两点,童鞋们可以在评论里帮我补充:因为有这么多优点,所以决定有必要将vue组件的常用配置项提炼出来,形成一个组件模板,方便日后项目开发复用 <template> <div> <h1>{{title}}</h1> <ChildComponents></

随机推荐