TypeScript 核心概念梳理

Easter79
• 阅读 687

TypeScript 核心概念梳理

8月20日,TypeScript 4.0 正式发布了(  Announcing TypeScript 4.0  ),虽然没有重大的变 更和特性,可以看做是 3.9 版本正常迭代,不过 Daniel 也在公告中说了:对于初学者而言,现在是最好的上手时机。

In fact, if you’re new to the language, now is the best time to start using it.

确实 TS 在经过了几年的发展后,使用 TS 的团队也越来越多,更重要的是 TS 的生态越来越完备,非常多的库、框架等都支持了类型系统甚至直接用 TS 重写,现在开始使用 TS 就能够直接享受整个技术生态带来的开发效率提升。回归到业务,我们团队最近也确实在开始用 TS 来进行类库的开发,所以结合官方文档和社区文档并从新人学习的角度梳理了一份包含大部分 TS 核心概念的学习手册。 本文主要是做减法,梳理出核心的点,能够先用起来,然后按工作需要找一些点逐个进行深入学习。

背景


因为日常工作会使用页面搭建系统来生成很多前端页面,所以会开发很多的楼层模块配合搭建系统使用,最近模块是基于 Rax 开发的,后续会支持越来越多的投放渠道: web、weex、淘宝小程序、支付宝小程序等等,为了兼容约来越多的渠道,很多功能被抽象成了一个个小的类库, 在类库中去兼容各个渠道,从而让模块中的业务代码保持尽量的只有清晰的业务逻辑。

但是随着支持渠道的增多,不可避免的导致类库在不同的渠道支持的特性不一致,比如 web 中,类库A 支持三个参数甲、乙、丙,而在小程序中类库A 仅支持两个参数甲、乙,所以丙这个参数要设计成可选参数,在类似的场景变多之后,这些类库的文档说明成了很重要的工作,同时在 IDE 中写代码时如果有类型系统能自动告诉开发人员这个函数支持哪些参数就更好了,所以我们准备对类库进行 TS 的重写来提高我们的生产效率,也为后续 TS 在团队内的落地打一个好基础。

什么是 TypeScript


  • 简单的说 TypeScript 是 JavaScript 一个超集,能够编译成 JavaScript 代码

  • 其核心能力是在代码编写过程中提供了类型支持,以及在编译过程中进行类型校验

先说一下 JS 的现状:

  1. 在 JS 中的变量本身是没有类型,变量可以接受任意不同类型的值,同时可以访问任意属性,属性不存在无非是返回 undefined

  2. JS 也是有类型的,但是 JS 的类型是和值绑定的,是值的类型,用 typeof 判断变量类型其实是判断当前值的类型

    // JavaScript

TS 做的事情就是给变量加上类型限制

  1. 限制在变量赋值的时候必须提供类型匹配的值

  2. 限制变量只能访问所绑定的类型中存在的属性和方法

举个简单的例子,如下是一段能够正常执行的 JS 代码:

let a = 100

直接用 TS 来重写上面的代码,把变量 a 的类型设置为 number

在 TS 中给变量设置类型的语法是 【 : Type 】 类型注解

let a: number = 100

但是如果直接对这个 TS 代码进行编译会报错,因为当变量被限制了类型之后,就无法访问该类型中不存在的属性或方法。

那再来写一段能正常执行的 TS

let a: string = 'hello'

编译成 JS 后的代码为

var a = 'hello'

可以发现 : string 这个类型限制编译之后是不存在的,只在编译时进行类型校验。

当 TS 源码最终被编译成 JS 后,是不会产生任何类型代码的,所以在运行时自然也不存在类型校验。

也就是说,假设一个项目,用 TS 来写,哼哧哼哧加上各种类型检验,项目测试通过部署到线上之后

最后运行在客户端的代码和我直接用 JS 来写的代码是一样的,写了很多额外的类型代码,竟然是为了保证能顺利编译成原来的代码



TypeScript 的作用


那 TS 的作用究竟是什么呢,主要是以下三点:

  1. 将类型系统看作为文档,在代码结构相对复杂的场景中比较适用,本质上就是良好的注释。

  2. 配合 IDE,有更好的代码自动补全功能。

  3. 配合 IDE,在代码编写的过程中就能进行一些代码校验。例如在一些 if 内部的类型错误,JS 需要执行到了对应代码才能发现错误,而 TS 在写代码的过程中就能发现部分错误,代码交付质量相对高一些,不过对于逻辑错误,TS 当然也是无法识别的。

===

===

TypeScript 类型梳理


分两类来介绍 TS 的类型系统:

  1. JS 中现有的值类型在 TS 中对应如何去限制变量

  2. TS 中拓展的类型,这些类型同样只在编译时存在,编译之后运行时所赋的值其实也是 JS 现有的值类型

下文中会穿插一些类似 [ xx ] 这样的标题,这是在列举介绍 TS 类型的过程中插入介绍的 TS 概念


JS 中现有的值类型如何绑定到变量

  • 使用语法:类型注解【 : Type 】

布尔值

let isDone: boolean = false

数值

let age: number = 18

字符串

let name: string = 'jiangmo'

空值

function alertName(): void { // 用 : void 来表示函数没有返回值

Null 和 Undefined

let u: undefined = undefined

[ 类型推论 ]

  • 如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型

例如:定义变量的时候同时进行赋值,那么 TS 会自动推断出变量类型,无需类型注解

let age = 18

继续列举类型

数组的类型

  • 语法是 【 Type[] 】

    let nameList: string[] = ['Tom', 'Jerry']

对象的类型

  • 接口 (interface) 用于描述对象的类型

    interface Person { // 自定义的类型名称,一般首字母大写

函数的类型

  • 以函数表达式为例 ( 函数声明定义的函数也是用类似的 参数注解 语法来进行类型约束 )

    // JavaScript

TS 中有多种语法来定义函数类型

  • 直接约束出入参类型

    const sum = function (x: number, y: number): number {

  • 单独给 sum 变量设置类型

    const sum: (x: number, y: number) => number = function (x, y) {

这里如果把函数类型直接提取出来用并起一个自定义的类型名,代码会更美观,也易复用。

利用 类型别名 可以给 TS 类型重命名

[ 类型别名 ]

  • 类型别名的语法是 【 type 自定义的类型名称 = Type 】

    type MySum = (x: number, y: number) => number

回到函数类型

  1. 用接口定义函数的类型

    interface MySum {

函数类型介绍完了,最后额外补充一下函数类型怎么 定义剩余参数的类型 以及 如何设置默认参数。

const sum = function (x: number = 1, y: number = 2, ...args: number[]): number {

类的类型

  • 和函数类型的语法相似,直接在 ES6 语法中用【 : Type 】类型注解 和 参数注解 语法给类的属性和方法设置类型

    class Animal {

顺便值得一提的是,除了类型支持以外,TS 也拓展了 class 的语法特性

新增了三种访问修饰符 public、 private、 protected 和只读属性关键字 readonly 以及 abstract 抽象类

这里就不展开了,有需要的再去查阅一下官方文档即可

内置对象和内置方法

JavaScript 中有很多内置对象和工具函数,TS 自带其对应的类型定义

  • 很多内置对象可以直接在 TypeScript 中当做定义好了的类型来使用

    let e: Error = new Error('Error occurred')

  • 一些内置的方法,TS 也补充了类型定义,配合 IDE 在编写代码的时候也能得到 TS 的参数提示。

    Math.pow(2, '3') // error TS2345: Argument of type '"3"' is not assignable to parameter of type 'number'.

** TS 中拓展的类型**

任意值 any

与其说 any 是 JS 中不存在的类型,不如说原本 JS 中的变量只有一个类型就是 any

任意值 any 的特点:

any 类型的变量可以赋值给任何别的类型,这一点和 null 与 undefined 相同任何类型都可以赋值给 any 类型的变量

在任意值上访问任何属性都是允许的

let a: any = 123

所以 any 是万金油,也是和 TS 进行类型约束的目的是相违背的,要尽量避免使用 any。

联合类型

  • 类型中的或操作,在列出的类型里满足其中一个即可

    let x: string | number = 1

不过联合类型有一个额外约束:

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法。

let x: string | number = 1

两种解决思路

  • 让 TS 能够自行推断出具体类型

    function getLength(something: string | number): number {

  1. 利用 类型断言,手动强制修改现有类型

    function getLength(something: string | number): number {

[ 类型断言 ]

  • 用来手动指定一个值的类型,语法 【 value as Type 】

用类型断言修改类型时的限制:

  1. 联合类型可以被断言为其中一个类型

  2. 父类可以被断言为子类

  3. 任何类型都可以被断言为 any

  4. any 可以被断言为任何类型

总结成一条规律就是:要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可。

✎ 双重断言

  • 利用利用上述 3 和 4 两条规则,可以强制把一个值改为任意其他类型

    let a = 3

如果说断言有风险,那双重断言就是在反复横跳了

字符串字面量类型

  • 用来约束取值只能是某几个字符串中的一个

    type EventNames = 'click' | 'scroll' | 'mousemove'

注意,只有一个字符串也是字符串字面量类型

type MyType = 'hello'

虽然一般不会手动设置这样的类型,不过类型推论经常会推断出这种类型。

比如某次编译报错提示为:Argument of type '"foo"' is not assignable to parameter of type 'number'.

提示中的 type '"foo"' 一般就是根据字符串 'foo' 推断出来的字符串字面量类型。

元组

  • 类似 Python 中的元组,可以看做是固定长度和元素类型的数组

let man: [string, number] = ['Tom', 25]// 不过 TS 中的元组支持越界``// 当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型``man.push('male')

枚举

  • 用于取值被限定在一定范围内的场景,可以替代 JS 中用字面量来定义一个对象作为字典的场景

enum Directions { Up, Down, Left, Right,``}``let d: Directions = Directions.Left

这里看到 Directions.Left 直接把类型当做一个值来用了。

不是说类型是用于【 : Type 】类型注解 语法来约束变量,编译之后类型代码都会被删除吗?

为了解释这个问题,我们先来来看看单纯的类型代码会被编译成什么。

  • 首先以一个联合类型举例

    type MyType = string | number | boolean

编译结果:

// 不会产生任何 JS 代码
  • 再来看看枚举类型会被编译成什么

    enum Directions {

编译结果:

var Directions

这怎么理解呢?

let d: Directions = Directions.Left

其实这一行代码中,前一个 Directions 表示类型,后一个 Directions 表示值。

即 Directions 是一个值和类型的“复合体”,在不同的语法中具象化为值或者类型。

其实有办法可以把类型部分从 Directions 中抽离出来。

enum Directions {

此时 MyDirections 就是一个纯粹的类型,不能当做一个值来使用。

其实之前介绍的函数类型、类类型等声明中,也存在这样的值与类型的“复合体”

const sum = function (x: number, y: number = 5): number {

然后再回到枚举。

字符串枚举

  • 用字符串字面量初始化枚举成员,在实际使用过程中很常见

    enum Directions {

常数枚举

  • 用 const enum 定义的枚举类型

  • 和普通枚举的区别就是对应的值也会在编译阶段被删除,只会留下枚举成员的值

    const enum Directions {

编译结果:

var d = 'LEFT' /* Left */

泛型

  • 其实泛型并不是一种具体的类型,而是在定义函数、接口或类的类型时的的拓展特性。

  • 泛型是类型系统里的 “函数” ,通过传入具体的 类型参数 来得到一个具体的类型,从而达到复用类型代码的目的

假设一个场景,某个函数的入参类型为 number | string ,并且出参类型和入参相同

先尝试用联合类型来约束出入参

type MyFunc = (x: number | string) => number | string

但是 MyFunc 无法表示出参类型和入参相同,即入参是 number 的时候出参也是 number。

在这个场景下,可以利用泛型来定义出多个类似的函数类型。

泛型函数

  • 表示声明了一个 类型参数,在定义类型的时候 T 就可以作为一个类型来使用

  • 类型参数也可以定义多个,比如 <A, B, C>

    function GenericFunc(arg: T): T {

泛型接口

  • 用 泛型接口 来定义函数类型

    interface GenericFn {

对比上述的 泛型函数 和 泛型接口,有一个区别:

  • 给泛型函数传参之后得到的是一个函数值,而不是类型

    // GenericFunc 是上面定义的泛型函数

  • 而泛型接口传参之后得到的是一个类型,而不是函数值

    // GenericFn 是上面定义的泛型接口

泛型类

  • 用 泛型类 来定义类的类型

    class GenericClass {

✎ 内置的数组泛型

  • TS 中内置类一个数组泛型 Array,传入类型参数后会返回对应的数组类型

    // 数组的类型之前是用 【 Type[] 】 语法来表示的

[ 声明合并 ]

上面那个场景,某个函数的入参类型为 number | string ,并且出参类型和入参相同,其实不用泛型也可以用函数重载来实现

✎ 函数的合并

  • 即函数声明的合并,即函数重载

  • TS 中的重载并不是真正意义上的重载,只是在根据不同的实参类型,从上而下挑选出一个具体的函数类型来使用

    function func(x: number): number

✎ 接口的合并

  • 接口中方法的合并和函数的合并相同,但是 属性的合并要求类型必须唯一

    interface Alarm {

===

声明文件


  • 以 .d.ts 结尾的文件

  • 声明文件里面 100% 全部都是纯类型的声明,不会编译出任何 JS 代码

一般来说,TS 会解析项目中所有的 *.ts 以及 .d.ts 结尾的文件,从而获取其中这些类型声明。

使用场景:作为一个第三方类库的开发者

假如你的库是用 TypeScript 写的,但是最终你的库分发出去的时候要编译成 JS。

否则这个类库就只能给 TS 项目来使用了,因为没有使用 TS 的项目没法直接引用 .ts 文件。

但是编译成 JS 有一个问题就是类型代码都被删除了之后,对于 TS 项目的使用方来说就没法继承 TS 的三大优势 (文档、自动补全、类型校验)

所以需要在类库的编译产出文件中保留一些 .d.ts 类型声明文件,和编译出来的 JS 文件中导出的函数、类等进行一一匹配。

这样 JS 文件和类型文件分离之后,你的类库就可以同时被 JS 项目和 TS 项目引用了。

假如你的库是并没有使用 TypeScript 来编写,那就需要额外有人给这个库写单独的声明文件。

比如 jQuery 并不是用 TS 来写的,但是你可以安装单独的 TS 类型包来实现补全这个包的类型系统。

npm install @types/jquery --save-dev

感谢&全文引用:

TypeScript 入门教程

TypeScript 官方手册

https://www.typescriptlang.org/docs/handbook/basic-types.html

TypeScript Handbook

https://zhongsp.gitbooks.io/typescript-handbook/content

淘系技术天猫奢品团队

我们是一群服务天猫奢侈品,奢品折扣,品牌客户,淘宝心选,淘宝虾选等大店数据化经营解决方案的技术团队,依托阿里大中台推动品牌经营解决方案升级,不断提升客户经营的效率,持续提升技术方案赋能业务提升价值。

如果你感兴趣我们的业务或者转岗可以发送简历至 jiangmo.ljt@alibaba-inc.com  24小时在线服务。(点击文末 ”阅 读原文“ 查看职位详情)

✿   拓展阅读

TypeScript 核心概念梳理 TypeScript 核心概念梳理

TypeScript 核心概念梳理

作者| 刘江涛(江墨)

编辑| 橙子君

出品| 阿里巴巴新零售淘系技术

TypeScript 核心概念梳理

TypeScript 核心概念梳理

本文分享自微信公众号 - 淘系技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k