TypeScript的学习

108 阅读16分钟

定义:

  • TypeScript具有类型系统,且是JavaScript的超集。它可以编译成普通的JavaScript代码。

  • TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。

  • TypeScript提供了一些检查来保证安全以及帮助分析你的程序。

基础知识:

1、基础类型

image2023-2-15_20-0-4.png

【类型断言】:手动指定一个值的类型

  • 值 as 类型   
image2023-2-15_20-4-20.png
  • <类型>值
image2023-2-15_20-5-37.png

2、接口interface

【定义/作用】

是一系列抽象方法的声明,是一些方法特征的集合。常用来定义对象的类型,并规定这种类型中包含什么属性以及属性的类型

【定义对象的写法】

image2023-2-15_20-31-42.png image2023-2-16_9-22-23.png
  • 其中?放在属性名后表示的是该属性为可选属性,可选属性:1、预定义可能存在的属性;2、捕获引用了不存在的属性时的错误
image2023-2-16_9-9-25.png
  • 通过在属性名前添加readonly表示的是该属性为只读属性,只能在对象刚创建时修改其值;且在定义数组类型时可以使用只读属性:ReadonlyArray<number>

【定义函数的写法】规定了参数列表和函数返回值的类型

image2023-2-16_9-20-45.png image2023-2-16_9-22-55.png

【定义可索引类型的写法】规定了可索引的类型,具有索引签名、索引类型、索引返回类型

image2023-2-16_9-29-13.png

  • 索引签名为数字类型

image2023-2-16_9-31-39.png

  • 索引签名为字符串类型 (因为当使用number去索引时,JS会将其转换为string去索引,所以 obj[2]=obj['2'] )
image2023-2-16_9-32-16.png

【类实现接口的写法】接口描述了类的公共部分,只能规定公开属性、方法和属性值的类型;类必须包含接口规定的所有,但可以拥有自己的属性和方法

image2023-2-16_9-58-42.png

【接口继承接口】可以继承接口里的成员

image2023-2-16_10-35-39.png

【接口继承类的写法】当接口继承类时,包含了类中的所有成员,包括私有成员。但不包括其实现。 而私有成员只有类的子类们、实例们才能实现该接口(因为只有它的子类们才能拥有它的私有成员)

image2023-2-17_9-15-0.png

3、类class

【定义】

定义了一件事物的抽象特点,包含它的属性和方法

image2023-2-17_9-21-24.png

【继承】可以通过继承来扩展现有的类。继承的类(派生类)称为子类,被继承的类(基类)称为超类

image2023-2-17_9-37-19.png

继承了基类的方法,又可以重写基类的方法

image2023-2-17_9-47-10.png

【修饰符public、private、protected】

  • 公有的public  默认修饰符,在任何地方都可以访问
  • 私有的private  意味着不能在声明它的类的外部访问(比如在实例、子类)
  • 受保护的protected 和private类似,区别在于在子类是允许被访问的;修饰构造函数时只允许被继承,不允许被实例化

image2023-2-17_10-22-13.png

  • 只读readonly 将属性设置为只读的   必须在声明时或构造函数里被初始化
  • 静态static 表明属性存在于类本身上而不是类的实例,访问这个属性可以通过在该属性前面加上类名,类名.属性名

image2023-2-17_10-51-1.png

  • 抽象abstract 用于定义抽象类和在抽象类内部定义抽象方法,不能直接被实例化;抽象类中的抽象方法必须在派生类中实现

4、函数

【定义】用来定义行为,函数类型包含:参数类型和返回值类型

image2023-2-17_11-12-2.png

【可选参数、默认参数、剩余参数】

  • 可选参数:在参数名旁加上?可实现可选参数的功能;可选参数必须在必须参数后边
  • 默认参数:为参数提供一个默认值,用户没传递或者传递过来的值为undefined时便使用这个默认值;若默认参数在必须参数前面时,须明确传入该参数无论是有值还是undefined
  • 剩余参数:会被当做个数不限的可选参数

image2023-2-17_11-21-50.png

【重载】函数重载的意思:根据传入不同数量或类型的参数,而作出不同的处理;TS的函数重载:为同一个函数提供多个函数类型定义来进行

以下的重载就表明了输入为number,返回值必须为number;输入为string,返回值必须为string;

若没有前两条类型定义会出现缺点:没有明确输入与返回值的类型是统一的;且输入与返回值类型不同时也不会报错

image2023-2-17_11-53-21.png

5、泛型Generics

【定义】指在定义函数、接口或者类的时候,不预先指定具体的类型,而是在使用的时候再指定类型

【用变量表示类型】用定义类型变量T,变量T会捕获到传入的类型,就可以在之后使用这个类型

-单个泛型参数

image2023-2-17_14-12-43.png

-多个泛型参数

image2023-2-17_14-42-13.png

【创建泛型函数】

-定义变量,并赋值一个泛型函数

-先定义变量并指定为泛型函数类型,再赋值一个泛型函数

-使用带有调用签名的对象字面量来定义泛型函数

image2023-2-17_14-44-18.png

【泛型接口】用泛型接口规定变量的类型,值必须满足泛型接口中规定的函数类型

image2023-2-17_14-48-35.png

泛型约束:只要传入的参数包含约束的属性即可  接口约束:当传入的参数为字面量对象时,要求属性不能多也不能少

image2023-2-17_14-55-43.png

多个类型参数之间也可以相互约束

image2023-2-17_15-0-12.png

【泛型类】确认类的所有属性都在用相同的类型,而泛型类指的是实例部分的类型,静态属性不使用泛型

image2023-2-17_14-50-36.png

6、枚举enum

【定义】可用于定义一些有名字的数字常量

一个枚举类型可以包含零个或多个枚举成员,其中枚举成员具有一个数字值

在正常的枚举中,没有初始化方法的成员被当成常数成员

常数枚举是在enum前使用const修饰符

image2023-2-17_15-27-43.png

外部枚举是用来描述已经存在的枚举类型的,没有初始化方法时被当做需要经过计算的

image2023-2-17_15-41-37.png

7、类型推论、类型兼容性

【最佳通用类型】 需要从几个表达式中推断类型时候,会从这些表达式类型中推断出一个最合适的通用类型,当候选类型不能使用时需明确指出类型

image2023-2-17_15-56-25.png

 【上下文类型】发生在表达式类型与所处位置相关时,通常包含函数的参数、赋值表达式的右边、类型断言、对象成员、数组字面量、返回值语句

【类型兼容性】基于结构子类型的,结构类型是一种只使用其成员来描述类型的方式

结构化类型系统的基本规则:如果x要兼容y,那么y至少具有与x相同的属性

image2023-2-17_16-21-38.png

两个函数的参数兼容:x的每个参数必须在y中找到对应类型的参数,才允许赋值

image2023-2-17_16-28-23.png

两个函数的返回值兼容:x要兼容y,那么y至少具有与x相同的属性

image2023-2-17_16-31-36.png

枚举类型与数字类型互相兼容,但是不同枚举类型之间是不兼容的

image2023-2-17_16-36-33.png

类之间的比较时,只有实例成员会被比较,静态与构造函数则不会被比较

image2023-2-17_16-38-46.png

泛型之间的兼容:没指定泛型类型的泛型参数时,会把所有的泛型参数当成any比较

image2023-2-17_16-41-10.png

8、高级类型

image2023-2-23_11-53-12.png

9、Symbol

image2023-2-24_10-34-28.png

10、模块和命名空间

【模块】

定义:在其自身的作用域里执行,而不是在全局作用域里,通过export导出给模块外部使用,通过import导入给模块内部使用

模块时使用模块加载器去导入其它的模块,模块加载器的作用:在运行时,执行此模块代码前去查找并执行这个模块的所有依赖,熟知的JS模块加载器是:服务于Node.js的CommonJS、服务于Web应用的Require.js

TS与ES5一样。📢 模块里不要使用命名空间,因为模块具有自己的作用域

【导出】

任何声明都能够通过添加export关键字来导出

1、直接导出

image2023-2-24_10-48-2.png

2、导出重命名

image2023-2-24_10-48-41.png

3、导出多个模块,所有模块

image2023-2-24_10-49-17.png

【导入】

1、导入一个模块中的某个导出内容

image2023-2-24_10-50-23.png

2、对导入内容重命名

image2023-2-24_10-51-4.png

3、将整个模块导入到一个变量,通过它来访问模块的导出部分

image2023-2-24_10-52-4.png

4、默认导出

每个模块都可以有一个default导出,并且一个模块只能够有一个default导出

image2023-2-24_10-55-33.png

image2023-2-24_10-55-47.png

【TS模块支持export=语法】

TypeScript模块支持export=语法以支持传统的CommonJS和AMD的工作流模型

export=语法定义一个模块的导出对象,若要导入一个使用了export=的模块时,必须使用TS提供的特定语法import module=require("module")

image2023-2-24_11-0-37.png

image2023-2-24_11-0-22.png

【如果仅导出单个class或function】

如果一个模块就为了导出特定的内容,就使用export default

image2023-2-24_11-13-36.png image2023-2-24_11-13-52.png

【如果要导出多个对象】

把他们放在顶层里导出,导入时须明确列出导入的名字

image2023-2-24_11-15-4.png image2023-2-24_11-15-28.png

【命名空间】

外部模块称为模块,内部模块称为命名空间

解决重名问题,命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性中,如果需要导出,则可以添加export

image2023-2-24_11-25-46.png

命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中

【模块解析】

定义:编译器所要依据的一个流程,用它来找出某个导入操作所引用的具体值;编译器会尝试定位表示导入模块的文件,编译会遵循Classic或Node策略

相对导入是以 / ,./ ,../ 开头的,

image2023-2-24_11-39-4.png

其余导入的形式则为非相对导入

截屏2024-01-04 09.46.31.png

【模块解析策略】

1、Classic 

以前是TS默认的解析策略,现在是为了向后兼容

-相对导入的模块是相对于导入它的文件进行解析的

-对于非相对模块的导入,则会从包含导入文件的目录开始依次向上级目录遍历,尝试匹配

2、Node

试图在运行时模仿Node.js模块解析机制

在Node.js里导入是通过require函数调用进行的,Node.js会根据require的相对路径还是非相对路径做出不同行为

-相对路径:

流程图-导出 (5).png

-非相对路径:

Node会在node_modules里查找模块,先查找文件,然后是合适的文件夹

流程图-导出 (6).png

3、TS如何解析

TS是模仿node.js运行时的策略来在编译阶段定位模块定义文件,所以增加了TS源文件的扩展名(.ts、.tsx、.d.ts)、以及在package.json里使用types来表示main的意义

-相对路径:

流程图-导出 (7).png

-非相对路径:

流程图-导出 (8).png

【路径映射】

TS编译器通过使用tsconfig.json文件里的paths来支持这样的声明映射

image2023-2-24_15-38-20.png

image2023-2-24_15-37-52.png

【利用rootDirs指定虚拟目录】

多个目录下的工程源文件在编译时会进行合并放在某个输出目录下,每当编译器在某个rootDirs的子目录下发现了相对模块导入,就会尝试从每一个rootDirs中导入

比如底下:构建的时候会将/src/views 和 /generated/templates/views的输出拷贝到同一个目录下

image2023-2-24_15-43-39.png image2023-2-24_15-45-7.png

11、合并声明

【定义】

指编译器将针对同一个名字的两个独立声明合并为单一声明,合并后的声明同时拥有原先两个声明的特性,任何数量的声明都可以被合并

【合并接口】

截屏2024-01-03 16.44.36.png

 对于函数成员,每个同名函数声明都会被当成这个函数的一个重载,后面的接口具有更高的优先级

image2023-2-28_10-25-9.png image2023-2-28_10-24-54.png

【合并命名空间】

非导出成员仅在原有的(合并前的)命名空间内可见,合并之后,从其他命名空间合并进来的成员无法访问非导出成员

image2023-2-28_10-29-4.png

等同于:

image2023-2-28_10-29-40.png

【合并命名空间和类】

image2023-2-28_10-33-40.png

12、JSX

【定义】

JSX是一种嵌入式的类似XML的语法,可以被转换为合法的JS

文件的拓展名为.tsx,启用jsx选项

image2023-2-28_10-40-55.png

.tsx文件禁用了使用尖括号的类型断言,所以新加入了as的类型断言符号

固有元素使用特殊的接口JSX.IntrinsicElememts接口的属性来查找

JSX.ElementAttributesProperty决定props名

JSX.ElementChildrenAttribute决定children名

默认JSX表达式结果类型为any,可以通过JSX.Element接口自定义这个类型

JSX可以使用{}标签内嵌表达式

image2023-2-28_10-57-13.png

13、装饰器(decorators)

启用:在命令行/tsconfig.json里启用experimentalDecorators编译器选项

tsc --target ES5 --experimentalDecorators

image2023-2-28_11-0-41.png

【定义】 是一种特殊类型的声明,能够被附加到类声明、方法、访问符、属性或参数上,写法: @expression,expression求值后必须为一个函数,在运行时被调用

【装饰器工厂】 装饰器工厂就是一个简单的函数,返回一个表达式,以供装饰器在运行时被调用

image2023-2-28_11-6-57.png

【装饰器组合】 多个装饰器可以同时应用到一个声明上,由上到下依次对装饰器表达式求值,求值的结果会被当做函数,由下到上依次调用

image2023-2-28_11-14-49.png

14、三斜线指令

仅可放在包含它的文件最顶端,注释的内容会做为编译器指令使用,三斜线引用告诉编译器在编译过程中药引入的额外文件

如果指定了–noResolve编译选项,三斜线引用会被忽略,不会增加新文件

/// <reference path="..." />用来声明依赖

声明文件里包含 /// <reference types="node" />,表明这个文件使用了@types/node/index.d.ts 里面声明的名字

15、如何书写声明文件

结构image2023-2-28_16-4-53.png
规范image2023-2-28_16-6-59.png
举例image2023-2-28_16-7-22.png
深入image2023-2-28_16-7-48.png
发布和使用image2023-2-28_16-8-34.png

16、工程配置

【tsconfig.json】

可以参考

该文件意味着这个目录是TS项目的目录,并指定了用来编译这个项目根文件和编译选项

使用tsc [ts file]命令可以编译ts文件,将其编译为js文件,但不可能将所有要编译的文件路径都添加在tsc命令后边,所以需要在tsconfig.json文件中配置tsc编译器

执行tsc命令时,ts编译器会首先在当前目录寻找这个json文件,找不到将向上级目录寻找

-编译选项(compilerOptions字段下的配置项)

1、outDir

指定编译后的文件所在目录,默认情况下,编译后的文件与源文件都在同一个目录下

2、rootDir

更改项目的根目录位置,默认情况下根目录为tsconfig.json文件所在的目录,所有的相对路径都是相对这个根目录的

3、removeComments

用于删除编译后的js文件中的注释代码

5、module

假设你正在开发项目,需要在Node.js环境中运行,nodejs使用的是CommonJS模块,而项目中使用了import引入模块,

配置 ”module“:”CommonJS"  可以将编译后的js文件import语句转换为require语句

”module“:”esnext"  表示最新的ES语法 

6、outFile

指定编译后的结果文件被打包成一个bundle

7、sourceMap

表示编译的源文件与输出的结果的一种映射关系。这样调试的时候才知道源文件的代码。以.map后缀结尾

参考某个例子tsconfig.json

image2023-2-28_17-17-42.png

-非编译选项(compilerOptions字段外的根级选项,控制的是ts编译器要编译的项目文件信息)

1、files

用于指示哪些文件需要编译,可以添加一组文件路径

2、include & exclude

项目文件较少时,可以使用files选项设置,但是当文件特别多或者项目文件更新频繁时,使用include

image2023-2-28_16-32-52.png

exclude与include相反,用于排除某些文件

image2023-2-28_16-34-36.png

17、如何为Vue3标注TS类型(使用的是

【为props标注类型】

1、defineProps()宏函数支持从它的参数中推导类型

const props = defineProps({
    foo: { type: String, required: true },
    bar: Number
})

2、通过泛型参数来定义props的类型

const props = defineProps<{
    foo: string,
    bar?: number
}>();

3、但是上述方式失去了定义props默认值的能力,使用withDefaults编译器宏;

const props=withDefults(
    defineProps<{
       foo: Record<string,any>;
       bar: boolean;
       belta: string;
    }>(),
    {
       foo: ()=>({}),
       bar: false,
       belta: ''
    }
);

【为emits标注类型】

1、使用运行时声明

const emit = defineEmits(['update:modelValue','show']);

2、基于类型的声明

const emit = defineEmits<{
  (e:'update:modelValue',id:number):void;
  (e:'show',value:string):void
}>();                   

【为ref()标注类型】

1、默认推导类型

ref会根据初始化时的值自动推导其类型

const pageIndex = ref(1);

2、通过接口指定类型

const year: Ref<string | number> = ref('2024');

3、通过泛型指定类型

const currentAge = ref<string | number>(18);

如果指定了一个泛型参数但是没有给出初始值,那么将得到一个包含undefined的联合类型

const n =ref<number>();

【为reactive()标注类型】

1、默认推导类型

reactive()会隐私地从它的参数中推导类型

const formData = reactive({
    name:'juejin',
    age: 18
});

2、通过接口指定类型

interface Book{
    title: string;
    yaer?: number;
};
    
const book:Book = reactive({title:'TypeScript的学习'});

【为computed()标注类型】

1、默认推导类型

computed()会自动从其计算函数的返回值上推导出类型

const formData = computed(()=>{
    return { name:'juejin', age: 18 }
});

2、通过泛型指定类型

通过泛型参数显式指定类型

如果返回的类型不对将会报错

const sumCount = computed<number | unknown>(()=>{
    return 18;
});

【为事件处理函数标注类型】

在处理原生DOM事件时,应该给事件处理函数的参数正确地标注类型

没有类型的标注时,参数会隐式的标注为any类型,如果在tsconfig.json中配置了strict:true会报错

const changeMethod = (params:string) =>{
    console.log(params);
};

【为provide/inject标注类型】

provide和inject通常在不同组件中,Vue提供了一个injectionKey接口,这是一个继承自Symbol的泛型类型,可以用来在提供者和消费者之间同步注入值的类型

import {provide, inject} from 'vue';
import type { InjectionKey } from 'vue';

const data = Symbol() as InjectionKey<string>;
provide(data,'foo');  // 若提供的是非字符串值会导致错误

const foo = inject(data);  // foo的类型: string | undefined

当使用字符串注入key时,注入值的类型是unkown,需要通过泛型参数显式声明:

const foo = inject<string>('key')

【为dom模板引用标注类型】

模板ref需要通过一个显式指定的泛型参数和一个初始值null来创建

直到组件被挂载前,这个ref的值都是初始的null,所以访问el.value时要使用可选链

const el = ref<HTMLInputElement | null>(null);
    
<el-dialog ref="el"></el-dialog>

【为组件模板引用标注类型】

场景:需要为一个子组件添加一个模板ref,以便调用它公开的方法

// 子组件
defineExpose({
 open
})

为了获取子组件类型,首先通过typeof得到其类型,再使用TS内置的InstanceType工具类型来获取其实例类型:

// 父组件

import Children from 'xxxx';

 
const modal = ref<InstanceType<typeof Children> | null >(null);

model.value?.open();  // 调用子组件公开的方法