TS01 为什么要使用TypeScript

本文由 简悦 SimpRead 转码(有删减), 原文地址 https://github.com/ProtoTeam/blog/blob/master/201709/2.md

TypeScript 体系调研报告

作者简介:aoto 蚂蚁金服 · 数据体验技术团队

Q:为什么要写这边文章?这篇文章要表达什么?

A:我们考虑在 SPA 应用中使用 TS 作为开发语言,我们需要一篇系统性介绍 TS 本身及周边的文章来论证在项目中使用 TS 作为开发语言是科学合理的,而且是顺势而为的。

导引

  • TS 是什么
  • 为什么要用 TS
  • TS 能干点什么
  • 使用 TS 的成本
  • 社区发展
  • 周边生态
  • 深入解读 TS
  • 接受 TS
  • 权衡

TS 是什么

TypeScript = Type + Script(标准 JS)。我们从 TS 的官方网站上就能看到定义:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript。TypeScript 是一个编译到纯 JS 的有类型定义的 JS 超集。

为什么要用 TS

目标:生命周期较长(常常持续几年)的复杂 SPA 应用,保障开发效率的同时提升代码的可维护性和线上运行时质量。

  • 从开发效率上看,虽然需要多写一些类型定义代码,但 TS 在 VSCode、WebStorm 等 IDE 下可以做到智能提示,智能感知 bug,同时我们项目常用的一些第三方类库框架都有 TS 类型声明,我们也可以给那些没有 TS 类型声明的稳定模块写声明文件,如我们的前端 KOP 框架 (目前还是蚂蚁内部框架,类比 dva),这在团队协作项目中可以提升整体的开发效率。
  • 从可维护性上看,长期迭代维护的项目开发和维护的成员会有很多,团队成员水平会有差异,而软件具有的特质,长期迭代维护的项目总会遇到可维护性逐渐降低的问题,有了强类型约束和静态检查,以及智能 IDE 的帮助下,可以降低软件腐化的速度,提升可维护性,且在重构时,强类型和静态类型检查会帮上大忙,甚至有了类型定义,会不经意间增加重构的频率(更安全、放心)。
  • 从线上运行时质量上看,我们现在的 SPA 项目的很多 bug 都是由于一些调用方和被调用方(如组件模块间的协作、接口或函数的调用)的数据格式不匹配引起的,由于 TS 有编译期的静态检查,让我们的 bug 尽可能消灭在编译器,加上 IDE 有智能纠错,编码时就能提前感知 bug 的存在,我们的线上运行时质量会更为稳定可控。

一个复杂软件的常规研发流程,大致分为定义问题、需求分析、规划构建、软件架构、详细设计、编码调试、单元测试、集成测试、集成、系统测试、保障维护。构建活动(主要是编码调试)在中大型项目中的工作量占比大于 50%。同时,一个中大型项目,bug 由构建阶段引起的比例占到 50%~75%,对于一个成功的项目来说,构建活动是必须要做的,而且是工程师更为可控的。【代码大全】

TS 适合大规模 JavaScript 应用,正如他的官方宣传语JavaScript that scales。从以下几点可以看到 TS 在团队协作、可维护性、易读性、稳定性(编译期提前暴露 bug)等方面上有着明显的好处:

  • 加上了类型系统,对于阅读代码的人和编译器都是友好的。对阅读者来说,类型定义加上 IDE 的智能提示,增强了代码的易读型;对于编译器来说,类型定义可以让编译器揪出隐藏的 bug。
  • 类型系统 + 静态分析检查 + 智能感知 / 提示,使大规模的应用代码质量更高,运行时 bug 更少,更方便维护。
  • 有类似 VSCode 这样配套的 IDE 支持,方便的查看类型推断和引用关系,可以更方便和安全的进行重构,再也不用全局搜索,一个个修改了。
  • 给应用配置、应用状态、前后端接口及各种模块定义类型,整个应用都是一个个的类型定义,使协作更为方便、高效和安全。

TS 能干点什么

静态检查

这类问题是 ESLint 等工具检测不出来的。

低级错误

const peoples = [{
  name: 'tim',
  age: 20
}, {
  name: 'alex',
  age: 22
}];

const sortedPeoples = peoples.sort((a, b) => a.name.localCompare(b.name));

执行 TS 编译命令tsc,检测到错误:

error TS2339: Property 'localCompare' does not exist on type 'string'.

如果是在支持 TS 的 IDE 中(VS Code、WebStorm 等),则不需等到编译,在 IDE 中就可以非常明显在localCompare位置提示出错误信息。

localCompare这种输入手误(或者手滑不小心删除或添加了字符)时有发生,如果没有编译器静态检查,那有可能就是一个字符引发的血案:埋下了一个隐藏的运行时 bug。如果在 SPA 应用中,这个问题需要较长的操作路径才能被发现,一旦用户触发这个地雷,那它就会爆炸:应用直接 crash(在没有页面刷新的 SPA 中问题尤为凸显)。

非空判断

let data = {
  list: null,
  success: true
};
const value = data.list.length;

执行tsc编译:

error TS2532: Object is possibly 'null'.

data.list.length这行直接引用了data.list的属性,但data.list的数据格式有不是数组的可能性,这种场景在前端处理后端接口返回时经常出现,接口返回的数据层级可能非常深,如果在某一级缺少了非空判断逻辑,那就意味着埋下了一个不知道什么时候就会引爆的炸弹。

类型推断

const arr = [];
arr.toUpperCase();

class Cat {
  miao() {}
}

class Dog {
  wang() {}
}
const cat = new Cat();
cat.wang();

执行tsc编译:

error TS2339: Property 'toUpperCase' does not exist on type 'any[]'.
error TS2339: Property 'wang' does not exist on type 'Cat'.

TS 有类型推断,给不同类型的执行对象调用错误的方法都将被检查出来。

面向对象编程增强

访问权限控制

class Person {
  protected name: string;
  public age: number;
  constructor(name: string) { this.name = name; }
}

class Employee extends Person {
  static someAttr = 1;
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }
}
let howard = new Employee("Howard", "Sales");
console.log(howard.name);

执行tsc编译:

error TS2445: Property 'name' is protected and only accessible within class 'Person' and its subclasses.

Personname属性是protected类型,只能在自己类中或者子类中使用。访问权限控制在面向对象编程中很有用,他能帮忙我们做到信息隐藏,JS 面向对象编程的一个大问题就是没有提供原生支持信息隐藏的方案(很多时候都是通过约定编码方式来做)。信息隐藏有助于更好的管理系统的复杂度,这在软件工程中显得尤为重要。

接口

interface Machine {
  move(): void
}

interface Human {
  run(): void
}

class Base {
}

class Robot extends Base implements Machine, Human {
  run() {
    console.log('run');
  }
  move() {
    console.log('move');
  }
}

Robot类可以继承Base类,并实现MachineHuman接口,这种可以组合继承类和实现接口的方式使面向对象编程更为灵活、可扩展性更好。

泛型

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

定义了一个模板类型T,实例化GenericNumber类时可以传入内置类型或者自定义类型。泛型(模板)在传统面向对象编程语言中是很常见的概念了,在代码逻辑是通用模式化的,参数可以是动态类型的场景下比较有用。

类型系统

interface SystemConfig {
  attr1: number;
  attr2: string;
  func1(): string;
}

interface ModuleType {
  data: {
    attr1?: string,
    attr2?: number
  },
  visible: boolean
}

const config: SystemConfig = {
  attr1: 1,
  attr2: 'str',
  func1: () => ''
};

const mod: ModuleType = {
  data: {
    attr1: '1'
  },
  visible: true
};

我们定义了一个系统配置类型SystemConfig和一个模块类型ModuleType,我们在使用这些类型时就不能随便修改configmod的数据了。每个被调用方负责自己的对外类型展现,调用者只需关心被调用方的类型,不需关心内部细节,这就是类型约束的好处,这对于多人协作的团队项目非常有帮助。

模块系统增强

namespace N {
  export namespace NN {
    export function a() {
      console.log('N.a');
    }
  }
}

N.NN.a();

TS 除了支持 ES6 的模块系统之外,还支持命名空间。这在管理复杂模块的内部时比较有用。

使用 TS 的成本

学习成本

理论上学习并应用一门新语言是需要很高成本的,但好在 TS 本身是 JS 的超集,这也意味着他本身是可以支持现有 JS 代码的,至少理论上是这样。学习一下类型系统的相关知识和面向对象的基础知识,应该可以 hold 住 TS,成本不会很高。官方文档是最好的学习材料。

应用成本

老项目

对于老项目,由于 TS 兼容 ES 规范,所以可以比较方便的升级现有的 JS(这里指 ES6 及以上)代码,逐渐的加类型注解,渐进式增强代码健壮性。迁移过程:

  1. npm 全局安装 typescript 包,并在工程根目录运行tsc --init,自动产生tsconfig.json文件。 默认的 3 个配置项:更多配置项说明

    • "target":"es5": 编译后代码的 ES 版本,还有 es3,es2105 等选项。
    • "module":"commonjs": 编译后代码的模块化组织方式,还有 amd,umd,es2015 等选项。
    • "strict":true: 严格校验,包含不能有没意义的 any,null 校验等选项。
  2. 初始化得到的tsconfig.json无需修改,增加"allowJs": true选项。

  3. 配置 webpack 配置,增加 ts 的 loader,如 awesome-typescript-loader。(如果是基于 atool-build 来构建的项目,则它内置了 ts 编译,这步省略)

    loaders: [
    	// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
    	{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }
    ]
    
  4. 此时你可以写文件名为 ts 和 tsx(React)后缀的代码了,它可以和现有的 ES6 代码共存,VSCode 会自动校验这部分代码,webpack 打包也没问题了。

  5. 逐渐的,开始打算重构以前的 ES6 代码为 TS 代码,只需将文件后缀改成 ts(x) 就行,就可以享受 TS 及 IDE 智能感知 / 纠错带来的好处。

更多迁移教程:官方迁移教程官方 React 项目迁移教程社区教程 1社区教程 2

新项目

对于新项目,微软提供了非常棒的一些 Starter 项目,详细介绍了如何用 TS 和其他框架、库配合使用。如果是 React 项目,可以参考这个 Starter:TypeScript-React-Starter

成本对比

星多表示占优

成本点 ES TS 说明
学习和踩坑成本 ※※※※※ ※※※ 虽然是 JS 超集,但还是要学习 TS 本身及面向对象基础知识,开发环境搭建、使用中的问题和坑也需要自己趟,好在 TS 社区比较成熟,网上沉淀的资料很多
整体代码量 ※※※※※ ※※※※ TS 代码增加比较完善的类型定义的话整体代码量比原生 ES 多 5%~10% 左右
原生 JS(标准 ES、浏览器端、服务器端) ※※※ ※※※※※ IDE 内置了详尽的类型声明,可以智能提示方法和参数说明,提升了效率
依赖外部库(React、Lodash、Antd) ※※※ ※※※※※ 有 TS 类型声明库,IDE 智能提示和分析,效率提升
内部公共库、模块 ※※※ ※※※※ 团队内部自行编写类型定义文件,有一定工作量,但开发效率可以有一些提升,逐步完善类型定义后,效率进一步提升
团队协作效率 ※※ ※※※※※ 对系统配置、外部接口、内部模块做类型定义后,实例对象属性就不能随意改了,每个被调用方负责自己的对外类型展现(可以理解为形状),调用者只需关心被调用方的类型,不需关心内部细节
代码可维护性 ※※ ※※※※ 由于团队成员水平差异,和软件的的特质,长期迭代维护的项目总会遇到可维护性的问题,有了强类型约束和静态检查,以及智能 IDE 的帮助下,可以降低软件腐化的速度,提升可维护性,且在重构时,强类型和静态类型检查会帮上大忙,甚至有了类型定义,会不经意间增加重构的频率
运行时稳定性 ※※ ※※※※ 由于 TS 有静态类型检查,很多 bug 都会被消灭在上线前

小结

从上面的对比中可以看到,使用大家都熟悉的 ES 作为开发语言只在学习和踩坑成本以及整体代码量上占优,如果只是短期项目,那用 ES 无可厚非,但我们的项目生命周期持续好几年,是持续迭代升级的,目前 TS 社区已经比较成熟,学习资料也很多,而且 TS 带来的是内部协作开发效率、可维护性、稳定性的提升,所以从长远来看这个代价是值得付出的。而且各种类型声明定义文件的存在,是可以提升开发效率的;而且静态类型检查可以减少 bug 数量和排查 bug 的难度,变相也提升了效率,而且使整个项目相对变得更为稳定可控。

社区发展

从 Stackoverflow 的 2017 年开发者调查报告Google 趋势npm 下载量趋势上可以到看,TypeScript 社区发展很快,特别是最近几年。特别是伴随着 VS Code 的诞生(TS 写的,对 TS 支持非常友好),VS Code + TypeScript 的组合让前端圈产生了一股清流,生产力和规范性得到了快速提升。从 Google 对 TS 的支持(Angular 高于 2 的版本是 TS 写的)看到,国际大厂也是支持的。

从蚂蚁集团内部看,Ant Design、Basement 等产品也是基于 TS 写的(至少是在大量使用),虽然有一些反对的声音,但总体还是看好的,有合适的土壤就会快速发展,如 Ant Design。

周边生态

类型声明包

React、及其他各种著名框架、库都有 TS 类型声明,我们可以在项目中通过npm install @types/react方式安装,可以在这个网站搜索你想要安装的库声明包。安装后,写和那些框架、库相关的代码将会是一种非常爽的体验,函数的定义和注释将会自动提示出来,开发效率将会得到提升。

IDE

VS Code、WebStorm 等前端圈流行的 IDE 都对 TS 有着非常友好的支持,VS Code 甚至自身就是 TS 写成的。

深入解读 TS

TS 语言设计目标

目标

编译期可以做静态检查,为大规模代码提供结构化装置,编译出符合习惯、易读的 JS 代码,和 ECMAScript 标准对齐,使用一贯的、可删除的、结构化的类型系统,保护编译后的 JS 代码的运行时行为等等。

非目标

模仿现有语言,优化编译后代码的性能,应用 “正确” 的类型系统,增加运行时类型信息等等。

TS 设计目标原文

TS 简史

在最近几年,随着 V8 平台、各大现代浏览器的起来,JS 的运行平台再不断完善,但,JS 对于大型应用的开发是非常困难的,JS 语言设计出来的目的不是为了大型应用,他是一门脚本语言,他没有静态类型校验,但更重要的是,他没有提供大型应用必须的 classes、modules/namespaces、interfaces 等结构化的装置,中间也出来过 GWT 等为了其他语言开发者开发大型 JS 应用的项目,这些项目可以让你利用 Java 等面向对象语言开发大型 Web 应用,也可以利用到 Eclipse 等好用的 IDE,但这些项目不是用 JS 写代码。

所以如果你想用 JS 里的一些东西,你可能要比较费劲的在其他语言里把它给实现出来,所以我们考虑如何增强 JS 语言,提供如静态类型检查、classes、modules/namespaces、interfaces 等大型应用装置,这就是 TS 语言:TS 是一种开发大型 JS 应用的语言,更详细一点来说,TS 是有类型的编译到纯 JS 的 JS 超集。 所以一般来说,JS 代码也是 TS 代码。本身 TS 编译器也是 TS 写的,运行 Node.js 环境。【Anders Hejlsberg: Introducing TypeScript 2012

TS 作者在最近微软 Build 大会给出的一个图:

如图,Web 和 Node 平台的 JS 始终与 JS 最新规范有一段距离,Web 平台的距离更远,TS 可以填充这个间隙,让使用者在 Web 和 Node 平台都能用上最新的 Feature,用上优雅的 JS,提高生产力。【Anders Hejlsberg: What’s new in TypeScript? 2017

和 Flow 、Babel 的对比

vs Flow

这篇文章可以看出,基础的类型检查功能发展到现在已经差别不大了,但在周边生态、文档完整性、社区资源方面 TS 胜过 Flow。

vs Babel

Babel 也是很不错的 ES6 to 5 编译工具,有不错的插件机制,社区发展也不错,但在同样一段代码编译出的 JS 代码里可以看到,TS 编译后的代码是更符合习惯、简洁易读一些(都用的是官方网站的 Playground 工具)。我曾经维护过 TS 编译后的 JS 代码(TS 源码丢失),感觉还 OK。

Babel 编译后:

TS 编译后:

Type 和 Type System

TypeScript = Type + Script,那么编程语言中的 Type 是怎么定义的呢?

In computer science and computer programming, a data type or simply type is a classification of data which tells the compiler or interpreter how the programmer intends to use the data.【维基百科

在计算机科学中,数据类型或者简单说类型,是数据的类别,用来告诉编译器 / 解释器程序员想怎么使用数据。基本的数据类型如整数、布尔值、字符等,组合数据类型如数组、对象等,也有抽象数据类型如队列、栈、集合、字典等等。数据类型用在类型系统中,类型系统提供了类型定义、实现和使用的方式,每种编程语言都有各自的类型系统实现(如果有的话)。

我们来看看 Type System(类型系统)的定义:

In programming languages, a type system is a set of rules that assigns a property called type to the various constructs of a computer program, such as variables, expressions, functions or modules.[1] These types formalize and enforce the (otherwise implicit) categories the programmer uses for data structures and components (ex: “string”, “array of float”, “function returning boolean”). The main purpose of a type system is to reduce possibilities for bugs in computer programs[2] by defining interfaces between different parts of a computer program, and then checking that the parts have been connected in a consistent way. 【维基百科

在编程语言中,类型系统是一个规则集合,给程序中的变量、表达式、函数、模块等程序构建元素分配叫做类型的属性。这些类型明确并强制(也可能是含蓄的)程序员如何使用数据结构。** 类型系统的主要目的是通过定义程序不同部分间协作的接口,并检查不同部分以始终如一的方式协作,来减少程序可能产生的 bug。** 这种检查可能是静态的(编译期)或动态的(运行时),或者既有静态的也有动态的。

类型系统的好处

检测错误

The most obvious benefit of static typechecking is that it allows early detection of some programming errors. Errors that are detected early can be fixed immediately, rather than lurking in the code to be discovered much later,when the programmer is in the middle of something else—or even after the program has been deployed. Moreover, errors can often be pinpointed more accurately during typechecking than at run time, when their effects may not become visible until some time after things begin to go wrong.【Types and Programming Languages

As your app grows, you can catch a lot of bugs with typechecking. 【React typechecking

静态类型检查最明显的好处是可以尽早的检查出程序中的错误。错误被尽早的检查出来可以使它得到快速的修复,而不是潜伏在代码中,在中期甚至部署上线后才被发现。而且,错误在编译期可以被更精确的定位出来,而在运行时,错误产生的影响在程序出现问题前可能是不容易被发现的。

程序会有各种各样的数据结构,如果改了一个数据类型,前端很多时候都是通过全局查找来处理这种重构问题的。而静态类型检查则可以使再次编译后就能探知所有可能的错误,加上 IDE 的智能错误提示,重构起来更放心、更方便。

抽象化

Another important way in which type systems support the programming process is by enforcing disciplined programming. In particular, in the context of large-scale software composition, type systems form the backbone of the module languages used to package and tie together the components of large systems. Types show up in the interfaces of modules (and related structures such as classes); indeed, an interface itself can be viewed as “the type of a module,” providing a summary of the facilities provided by the module—a kind of partial contract between implementors and users.

Structuring large systems in terms of modules with clear interfaces leads to a more abstract style of design, where interfaces are designed and discussed independently from their eventual implementations. More abstract thinking about interfaces generally leads to better design.【Types and Programming Languages

类型系统支持编程阶段的另外一个重要方式是强制让编程遵守纪律。在大规模软件系统中,类型系统组成了组件协作系统的脊梁。类型展现在模块(或者相关的结构如类)的接口中。接口可以看做 “模块的类型”,展示了模块所能提供的功能,是一种实现者和用户间的合约。

在大量模块协作组成的大规模结构化软件系统中清晰的接口可以使设计更为抽象,接口的设计和讨论独立于它们的实现。一般来说,对接口更为抽象的思考可以使得做出更好的设计。

Types enable programmers to think at a higher level than the bit or byte, not bothering with low-level implementation. For example, programmers can begin to think of a string as a set of character values instead of as a mere array of bytes. Higher still, types enable programmers to think about and express interfaces between two of any-sized subsystems. This enables more levels of localization so that the definitions required for interoperability of the subsystems remain consistent when those two subsystems communicate.【维基百科

类型会让程序员在一个更高的维度思考,而不是在底层的计算机实现细节纠缠。例如,我们可以把字符串想成字符集,而不仅仅是比特数组。更高维度,类型系统可以让我们用接口来思考和表达任意子系统 / 子程序之间的协作,接口定义可以让子系统 / 子程序之间的通信方式始终如一。

文档化

Types are also useful when reading programs. The type declarations in procedure headers and module interfaces constitute a form of documentation,giving useful hints about behavior. Moreover, unlike descriptions embedded in comments, this form of documentation cannot become outdated, since it is checked during every run of the compiler. This role of types is particularly important in module signatures.【Types and Programming Languages

类型对于阅读程序也是有用的。在程序头部的类型声明和模块接口形成了文档的形状,提供程序的行为提示。此外,不同于在注释中的描述,这种形式的文档不会过期,因为每次编译都会校验,这在模块签名里特别重要。

In more expressive type systems, types can serve as a form of documentation clarifying the intent of the programmer. For example, if a programmer declares a function as returning a timestamp type, this documents the function when the timestamp type can be explicitly declared deeper in the code to be an integer type.【维基百科

在复用表现力的类型系统中,类型可以看做是一种描述程序员意图的表述方式。例如,我们声明一个函数返回一个时间戳,这样就相当于明确说明了这个函数在更深层次的代码调用中会返回整数类型。

语言安全

The term “safe language” is, unfortunately, even more contentious than “type system.” Although people generally feel they know one when they see it, their notions of exactly what constitutes language safety are strongly influenced by the language community to which they belong. Informally, though, safe languages can be defined as ones that make it impossible to shoot yourself in the foot while programming.【Types and Programming Languages

安全语言这个说法是有争议的。受到该语言社区的严重影响。不正式的来说,安全语言可以被定义为在编程时不可能从底层把自己杀死。

A type system enables the compiler to detect meaningless or probably invalid code. For example, we can identify an expression 3 / "Hello, World" as invalid, when the rules do not specify how to divide an integer by a string. Strong typing offers more safety, but cannot guarantee complete type safety.【维基百科

类型系统会允许编译器检查无意义或者可能不合法的代码。例如,我们知道3/'hello world'不合法,强类型提供了更多的安全性,但也不能完全做到类型安全。

效率

Static type-checking may provide useful compile-time information. For example, if a type requires that a value must align in memory at a multiple of four bytes, the compiler may be able to use more efficient machine instructions.【维基百科

静态类型检查会提供有用的编译期信息。例如,如果一个类型需要在内存中占四个字节,编译器可能会使用更有效率的机器指令。

静态类型、动态类型和弱类型、强类型

  • 静态类型:编译期就知道每一个变量的类型。类型错误编译失败是语法问题。如 Java、C++。
  • 动态类型:编译期不知道类型,运行时才知道。类型错误抛出异常发生在运行时。如 JS、Python。
  • 弱类型:容忍隐式类型转换。如 JS,1+'1'='11',数字型转成了字符型。
  • 强类型:不容忍隐式类型转换。如 Python,1+'1'会抛出TypeError

接受 TS

TS 刚出来时我是有点抵触的,或者对她的感觉就跟和CoffeeScriptDart等编译到 JS 语言差不多,感觉就是其他语言往 JS 渗透的产物,近一两年,社区中 TS 的声音越来越强,而我也开始做大型 JavaScript 应用,随之逐渐重新认识 TS,逐渐认识到 TS 的类型系统、TSC 的静态检查、VS Code 等 IDE 的强力支持对于开发出可维护性好、稳定性高的大型 JavaScript 应用的重要性。

权衡

如何更好的利用 JS 的动态性和 TS 的静态特质,我们需要结合项目的实际情况来进行综合判断。一些建议:

  • 如果是中小型项目,且生命周期不是很长,那就直接用 JS 吧,不要被 TS 束缚住了手脚。
  • 如果是大型应用,且生命周期比较长,那建议试试 TS。开源项目如 VS CodeGitHub 桌面端,不开源的如 Slack 桌面端、Asana、Palantir
  • 如果是框架、库之类的公共模块,那更建议用 TS 了。如 Ant DesignAngularIonic

至于到底用不用 TS,还是要看实际项目规模、项目生命周期、团队规模、团队成员情况等实际情况综合考虑。

发布了382 篇原创文章 · 获赞 91 · 访问量 33万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览