typescript 类型定义-TypeScript核心概念梳理

2023-08-29 0 3,967 百度已收录

8 月 20 日,TypeScript 4.0 正式发布(Announcing TypeScript 4.0)。 虽然没有大的改动和功能,但也算是3.9版本的正常迭代。 不过,大牛在公告中也表示:对于初学者来说,现在是最好的入门时机。

事实上,如果您不熟悉该语言,现在是开始使用它的最佳时机。

确实,TS经过几年的发展,越来越多的团队使用TS。 更重要的是,TS的生态越来越完善。 很多库、框架等都支持类型系统甚至直接使用TS来复用Write,如果现在开始使用TS,就可以直接享受到整个技术生态带来的开发效率的提升。 回归到业务上,我们团队最近确实开始使用TS来开发解释器,所以我们结合官方文档和社区文档,从新人的角度整理了一份包含TS大部分核心概念的学习指南。 这篇文章主要是加法,整理出核心点,可以先用一下,然后再根据工作的需要,找到一些点来一一深入研究。

背景

因为日常工作会使用页面构建系统生成很多后端页面,所以会开发很多楼层模块来配合构建系统。 最近基于Rax开发该模块,未来将支持越来越多的投放渠道:web、weex、淘宝小程序、支付宝小程序等,为了兼容越来越多的渠道,很多功能都做了体现为小型解释器,解释器兼容各种渠道,使得模块中的业务代码能够尽可能保持清晰的业务逻辑。

但随着支持渠道的增多typescript 类型定义,难免出现不同渠道的解释器支持的功能不一致的情况。 例如,在web中,类库A支持A、B、C三个参数,而在小程序中,通用的A只支持A、B两个参数,所以参数C应该设计为可选参数。 当类似的场景增加之后,这些泛型的文档化就成为一项非常重要的任务。 同时,如果在IDE中编写代码时有类型系统的话,最好手动告诉开发者这个函数支持哪些参数,所以我们打算为泛型重画TS,以提高我们的生产效率,为以后的开发打下良好的基础。后续在团队中实施TS。

什么是 TypeScript

先说一下目前JS的情况:

JS中的变量本身没有类型,变量可以接受任何不同类型的值,并且可以访问任何属性。 如果属性不存在,则返回undefined

JS也有类型,但是JS的类型是和值绑定的,也就是值的类型。 使用typeof判断变量类型好像是当前值的类型

<pre class="code-snippet__js" data-lang="javascript">// JavaScript
var a = 123typeof a // "number"a = 'sdf'typeof a // "string"a = { name: 'Tom' }a = function () { return true}a.xxx // undefined

TS所做的就是给变量添加类型约束

当限制变量参数时,必须提供与类型匹配的值

限制变量只能访问绑定类型中存在的属性和技巧

举个简单的例子,下面是一段可以正常执行的JS代码:

let a = 100if (a.length !== undefined) {  console.log(a.length)} else {  console.log('no length')}

直接用TS重画里面的代码,将变量a的类型设置为number

TS中设置变量类型的句型是[ : Type ]类型注释

let a: number = 100if (a.length !== undefined) { // error TS2339: Property 'length' does not exist on type 'number'.  console.log(a.length)} else {  console.log('no length')}

但是,如果直接编译这段TS代码,就会报错,因为当变量被限制为某个类型时,很难访问该类型中不存在的属性或技能。

然后写一个可以正常执行的TS

let a: string = 'hello'console.log(a.length)

编译成JS的代码是

var a = 'hello'console.log(a.length)

可以发现类型字符串被限制编译后就不存在了,编译时只进行了类型校准。

TS源码最终编译成JS时,不会形成类型代码,自然也就没有运行时的类型校准。

也就是说假设一个项目是用TS写的,加上各类测试,项目测试部署上线后

typescript 类型定义-TypeScript核心概念梳理

最终客户端运行的代码和我直接用JS写的代码是一样的。 我额外写了很多类型代码,以确保它能成功编译成原始代码。

TypeScript 的作用

TS有哪些作用,主要有以下三点:

把类型系统看成一个文档,更适用于代码结构相对复杂的场景,本质上是一个很好的注释。

有了IDE,就有了更好的代码手动补全功能。

通过IDE,可以在代码编译过程中进行一些代码校准。 比如一些if内部类型错误,JS需要执行相应的代码来检测错误,而TS可以在编写代码的过程中检测到一些错误,并且代码交付的质量比较高,但是当然TS也是如此难以识别的逻辑错误。

TypeScript 类型梳理

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

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

TS中扩展的类型,这些类型也只存在于编译时,虽然编译时和运行时赋值的也是JS已有的值类型

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

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

布尔值

let isDone: boolean = false

价值

let age: number = 18

细绳

let name: string = 'jiangmo'

空值

function alertName(): void { // 用 : void 来表示函数没有返回值  alert('My name is Tom')}

空和未定义

let u: undefined = undefinedlet n: null = null// 注意:和所有静态类型的语言一样,TS 中不同类型的变量也无法相互赋值age = isDone // error TS2322: Type 'false' is not assignable to type 'number'.// 但是因为 undefined 和 null 是所有类型的子类型,所以可以赋值给任意类型的变量age = n // ok

[类型推断]

例如:定义变量的同时指定形参时,TS会手动推导变量类型,无需类型注解

let age = 18// 等价于let age: number = 18// 所以上面代码中的类型声明其实都可以省略// 但是如果定义的时候没有赋值,不管之后有没有赋值,则这个变量完全不会被类型检查(被推断成了 any 类型)let xx = 'seven'x = 7// 所以这个时候应该显示的声明类型let x: numberx = 7

继续列出类型

数组的类型

let nameList: string[] = ['Tom', 'Jerry']let ageList: number[] = [5, 6, 20]

对象类型

interface Person { // 自定义的类型名称,一般首字母大写  name: string  age: number}let tom: Person = {  name: 'Tom',  age: 25,}

函数类型

// JavaScript
const sum = function (x, y) { return x + y}

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

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

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

这里,如果直接将函数类型提取出来作为自定义类型名,代码会更加美观,并且易于复用。

可以使用类型别名重命名 TS 类型

[输入别名]

type MySum = (x: number, y: number) => numberconst sum: MySum = function (x, y) {  return x + y}

返回函数类型

使用套接字定义的函数类型

interface MySum {  (a: number, b: number): number}const sum: MySum = function (x, y) {  return x + y}

既然函数类型介绍完了,我补充一下函数类型如何定义其余参数的类型以及如何设置默认参数。

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

班级类型

class Animal {  name: string // 这一行表示声明实例属性 name  constructor(name: string) {    this.name = name  }  sayHi(): string {    return `My name is ${this.name}`  }}let a: Animal = new Animal('Jack') // : Animal 约束了变量 a 必须是 Animal 类的实例console.log(a.sayHi()) // My name is Jack

对了,值得一提的是,除了类型支持之外,TS 还扩展了类的句型特征

添加了三个新的访问修饰符public、private、protected和只读属性关键字readonly和abstract抽象类

这里就不展开了,有需要的话查看官方文档即可

内置对象和外部方法

JavaScript中有很多外部对象和工具函数,TS自带了对应的类型定义

let e: Error = new Error('Error occurred')let d: Date = new Date()let r: RegExp = /[a-z]/let body: HTMLElement = document.body

typescript 类型定义-TypeScript核心概念梳理

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

TS 中的扩展类型

任何值 任何

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

任意值any的特征:

任何类型的变量都可以参数化为任何其他类型,这与 null 和 undefined 相同。 任何类型都可以参数化为任何类型的变量

允许访问任何值的任何属性

let a: any = 123a = '123' // oklet n: number[] = a // oka.foo && a.foo() // ok

所以any是万能的,也违背了TS的类型约束的目的。 尽量避免使用任何。

联合型

let x: string | number = 1x = '1'

但联合类型有一个额外的限制:

当 TypeScript 不确定联合类型的变量是什么类型时,我们只能访问联合类型的所有类型所共有的属性或技巧。

let x: string | number = 1x = '1'x.length // 这里能访问到 length ,因为 TS 能确定此时 x 是 string 类型// 下面这个例子就会报错function getLength(something: string | number): number {  return something.length // error TS2339: Property 'length' does not exist on type 'string | number'.}

两种解决方案

function getLength(something: string | number): number {  if (typeof something === 'string') { // TS 能识别 typeof 语句    return something.length // 所以在这个 if 分支里, something 的类型被推断为 string  } else {    return 0  }}

使用类型断言手动强制更改现有类型

function getLength(something: string | number): number {  return (something as string).length // 不过这样做实际上代码是有问题的,所以用断言的时候要小心}

[类型断言]

使用类型断言更改类型时的限制:

联合类型可以被谓词为其中一种类型

父类可以被谓词为泛型

任何类型都可以被谓词为any

any 可以被谓词为任何类型

可以概括为一个规律:要促使A被判定为B,只有A与B兼容或者B与A兼容。

双重断言

let a = 3(a as any) as string).split // ok

如果说判断有风险,那么双重判断就是重复跳跃

字符串文字类型

type EventNames = 'click' | 'scroll' | 'mousemove'function handleEvent(ele: Element, event: EventNames) {  // do something}

请注意,只有一个字符串也是字符串文字类型

type MyType = 'hello'

尽管此类类型通常不会自动设置,但它们通常是通过类型推断来推断的。

例如,编译错误消息为:“Argument of type '"foo"' is not assignable to argument of type 'number'”。

提示中的类型“foo”通常是从字符串“foo”推断出的字符串文字类型。

元组

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

枚举

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

这里你可以看到Directions.Left直​​接使用类型作为值。

是不是说用[ : Type ]类型注解语法来约束变量,编译后类型代码会被删除?

为了解释这一点,我们首先看看普通类型代码被编译成什么。

type MyType = string | number | boolean

编译结果:

// 不会产生任何 JS 代码

enum Directions {  Up,  Down,  Left,  Right,}console.log(Directions)

编译结果:

var Directions;(function (Directions) {  Directions[(Directions['Up'] = 0)] = 'Up'  Directions[(Directions['Down'] = 1)] = 'Down'  Directions[(Directions['Left'] = 2)] = 'Left'  Directions[(Directions['Right'] = 3)] = 'Right'})(Directions || (Directions = {}))console.log(Directions)/*  运行时 log 出来的 Directions 变量如下  {     '0': 'Up',    '1': 'Down',    '2': 'Left',    '3': 'Right',    Up: 0,    Down: 1,    Left: 2,    Right: 3   }*/

这怎么理解呢?

let d: Directions = Directions.Left

事实上,在这行代码中,前面的 Directions 代表类型typescript 类型定义,后面的 Directions 代表值。

也就是说,Directions是值和类型的“复合体”,抽象成不同句型的值或类型。

事实上,有多种方法可以从 Directions 中提取类型部分。

enum Directions {  Up,  Down,  Left,  Right,}type MyDirections = Directionsconsole.log(MyDirections) // error TS2693: 'MyDirections' only refers to a type, but is being used as a value here.

此时 MyDirections 是纯类型,不能用作值。

事实上,在前面介绍的函数类型和类类型的声明中,也存在这样的值和类型的“复合体”。

const sum = function (x: number, y: number = 5): number {  return x + y}console.log(sum) // [Function: sum]type MySum = typeof sum // 注意,剥离出来的函数类型是不会带有默认参数的,因为默认参数其实是函数的特性,和类型系统无关const f: MySum = (a, b) => 3 // okconsole.log(MySum) // error TS2693: 'MySum' only refers to a type, but is being used as a value here.

然后回到枚举。

字符串枚举

enum Directions {  Up = 'UP',  Down = 'DOWN',  Left = 'LEFT',  Right = 'RIGHT',}console.log(Directions.Up === 'UP') // true

常量枚举

const enum Directions {  Up = 'UP',  Down = 'DOWN',  Left = 'LEFT',  Right = 'RIGHT',}let d = Directions.Left
// 如果取消注释下面这行代码,编译会报错// console.log(Directions) // error TS2475: 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment or type query.

编译结果:

var d = 'LEFT' /* Left */

通用的

假设一个场景,函数的输入参数类型为number | string,输出参数类型与输入参数相同

首先尝试使用union类型来约束输入输出参数

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

但MyFunc无法表明输出参数的类型与输入参数相同,即当输入参数为数字时,输出参数也是数字。

在这种情况下,您可以使用类库来定义多个相似的函数类型。

通用函数

function GenericFunc<T>(arg: T): T {  return arg}// 这里的 GenericFunc 是表示的是一个函数值,同时将类型参数 T 赋值为 numberlet n = GenericFunc<number>(1) // n 可以通过类型推论得出类型为 :number// 进一步,利用 泛型约束 ,限制出入参为 number | stringtype MyType = number | stringfunction GenericFunc<T extends MyType>(arg: T): T { // extends MyType 表示类型参数 T 符合 MyType 类型定义的形状  return arg}let s = GenericFunc<string>('qq')let b = GenericFunc<boolean>(false) // error TS2344: Type 'boolean' does not satisfy the constraint 'string | number'.

通用插座

interface GenericFn {  (arg: T): T}// 定义一个泛型函数作为函数实现function identity<T>(arg: T): T {  return arg}// 使用泛型时传入一个类型来使 类型参数 变成具体的类型//  表示 T 此时就是 number 类型,GenericFn 类似是 “函数调用” 并返回了一个具体的类型 (这里是一个函数类型)const myNumberFn: GenericFn<number> = identityconst myStringFn: GenericFn<string> = identitylet n = myNumberFn(1) // n 可以通过类型推论得出类型为 :numberlet s = myStringFn('string') // s 可以通过类型推论得出类型为 :string

对比上面的泛型函数和泛型socket,有一个区别:

// GenericFunc 是上面定义的泛型函数type G = GenericFunc<string> // error TS2749: 'GenericFunc' refers to a value, but is being used as a type here.

// GenericFn 是上面定义的泛型接口type G = GenericFn<number> // okGenericFn<number>() // error TS2693: 'GenericFn' only refers to a type, but is being used as a value here.

泛型类

class GenericClass {  zeroValue: T  constructor(a: T) {    this.zeroValue = a  }}let instance = new GenericClass<number>(1)// 等价于let instance: GenericClass<number> = new GenericClass(1)// 因为有类型推论,所以可以简写成let instance = new GenericClass(1)

内置字段子类

// 数组的类型之前是用 【 Type[] 】 语法来表示的let list: number[] = [1, 2, 3]// 现在也可以这么表示let list: Array<number> = [1, 2, 3]

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 typescript typescript 类型定义-TypeScript核心概念梳理 https://www.wkzy.net/game/174721.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务