定义:
-
TypeScript具有类型系统,且是JavaScript的超集。它可以编译成普通的JavaScript代码。
-
TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。
-
TypeScript提供了一些检查来保证安全以及帮助分析你的程序。
基础知识:
1、基础类型
【类型断言】:手动指定一个值的类型
- 值 as 类型
- <类型>值
2、接口interface
【定义/作用】
是一系列抽象方法的声明,是一些方法特征的集合。常用来定义对象的类型,并规定这种类型中包含什么属性以及属性的类型
【定义对象的写法】
- 其中?放在属性名后表示的是该属性为可选属性,可选属性:1、预定义可能存在的属性;2、捕获引用了不存在的属性时的错误
-
通过在属性名前添加readonly表示的是该属性为只读属性,只能在对象刚创建时修改其值;且在定义数组类型时可以使用只读属性:
ReadonlyArray<number>
【定义函数的写法】规定了参数列表和函数返回值的类型
【定义可索引类型的写法】规定了可索引的类型,具有索引签名、索引类型、索引返回类型
- 索引签名为数字类型
- 索引签名为字符串类型 (因为当使用number去索引时,JS会将其转换为string去索引,所以 obj[2]=obj['2'] )
【类实现接口的写法】接口描述了类的公共部分,只能规定公开属性、方法和属性值的类型;类必须包含接口规定的所有,但可以拥有自己的属性和方法
【接口继承接口】可以继承接口里的成员
【接口继承类的写法】当接口继承类时,包含了类中的所有成员,包括私有成员。但不包括其实现。 而私有成员只有类的子类们、实例们才能实现该接口(因为只有它的子类们才能拥有它的私有成员)
3、类class
【定义】
定义了一件事物的抽象特点,包含它的属性和方法
【继承】可以通过继承来扩展现有的类。继承的类(派生类)称为子类,被继承的类(基类)称为超类
继承了基类的方法,又可以重写基类的方法
【修饰符public、private、protected】
- 公有的public 默认修饰符,在任何地方都可以访问
- 私有的private 意味着不能在声明它的类的外部访问(比如在实例、子类)
- 受保护的protected 和private类似,区别在于在子类是允许被访问的;修饰构造函数时只允许被继承,不允许被实例化
- 只读readonly 将属性设置为只读的 必须在声明时或构造函数里被初始化
- 静态static 表明属性存在于类本身上而不是类的实例,访问这个属性可以通过在该属性前面加上类名,类名.属性名
-
抽象abstract 用于定义抽象类和在抽象类内部定义抽象方法,不能直接被实例化;抽象类中的抽象方法必须在派生类中实现
4、函数
【定义】用来定义行为,函数类型包含:参数类型和返回值类型
【可选参数、默认参数、剩余参数】
- 可选参数:在参数名旁加上?可实现可选参数的功能;可选参数必须在必须参数后边
- 默认参数:为参数提供一个默认值,用户没传递或者传递过来的值为undefined时便使用这个默认值;若默认参数在必须参数前面时,须明确传入该参数无论是有值还是undefined
- 剩余参数:会被当做个数不限的可选参数
【重载】函数重载的意思:根据传入不同数量或类型的参数,而作出不同的处理;TS的函数重载:为同一个函数提供多个函数类型定义来进行
以下的重载就表明了输入为number,返回值必须为number;输入为string,返回值必须为string;
若没有前两条类型定义会出现缺点:没有明确输入与返回值的类型是统一的;且输入与返回值类型不同时也不会报错
5、泛型Generics
【定义】指在定义函数、接口或者类的时候,不预先指定具体的类型,而是在使用的时候再指定类型
【用变量表示类型】用定义类型变量T,变量T会捕获到传入的类型,就可以在之后使用这个类型
-单个泛型参数
-多个泛型参数
【创建泛型函数】
-定义变量,并赋值一个泛型函数
-先定义变量并指定为泛型函数类型,再赋值一个泛型函数
-使用带有调用签名的对象字面量来定义泛型函数
【泛型接口】用泛型接口规定变量的类型,值必须满足泛型接口中规定的函数类型
泛型约束:只要传入的参数包含约束的属性即可 接口约束:当传入的参数为字面量对象时,要求属性不能多也不能少
多个类型参数之间也可以相互约束
【泛型类】确认类的所有属性都在用相同的类型,而泛型类指的是实例部分的类型,静态属性不使用泛型
6、枚举enum
【定义】可用于定义一些有名字的数字常量
一个枚举类型可以包含零个或多个枚举成员,其中枚举成员具有一个数字值
在正常的枚举中,没有初始化方法的成员被当成常数成员
常数枚举是在enum前使用const修饰符
外部枚举是用来描述已经存在的枚举类型的,没有初始化方法时被当做需要经过计算的
7、类型推论、类型兼容性
【最佳通用类型】 需要从几个表达式中推断类型时候,会从这些表达式类型中推断出一个最合适的通用类型,当候选类型不能使用时需明确指出类型
【上下文类型】发生在表达式类型与所处位置相关时,通常包含函数的参数、赋值表达式的右边、类型断言、对象成员、数组字面量、返回值语句
【类型兼容性】基于结构子类型的,结构类型是一种只使用其成员来描述类型的方式
结构化类型系统的基本规则:如果x要兼容y,那么y至少具有与x相同的属性
两个函数的参数兼容:x的每个参数必须在y中找到对应类型的参数,才允许赋值
两个函数的返回值兼容:x要兼容y,那么y至少具有与x相同的属性
枚举类型与数字类型互相兼容,但是不同枚举类型之间是不兼容的
类之间的比较时,只有实例成员会被比较,静态与构造函数则不会被比较
泛型之间的兼容:没指定泛型类型的泛型参数时,会把所有的泛型参数当成any比较
8、高级类型
9、Symbol
10、模块和命名空间
【模块】
定义:在其自身的作用域里执行,而不是在全局作用域里,通过export导出给模块外部使用,通过import导入给模块内部使用
模块时使用模块加载器去导入其它的模块,模块加载器的作用:在运行时,执行此模块代码前去查找并执行这个模块的所有依赖,熟知的JS模块加载器是:服务于Node.js的CommonJS、服务于Web应用的Require.js
TS与ES5一样。📢 模块里不要使用命名空间,因为模块具有自己的作用域
【导出】
任何声明都能够通过添加export关键字来导出
1、直接导出
2、导出重命名
3、导出多个模块,所有模块
【导入】
1、导入一个模块中的某个导出内容
2、对导入内容重命名
3、将整个模块导入到一个变量,通过它来访问模块的导出部分
4、默认导出
每个模块都可以有一个default导出,并且一个模块只能够有一个default导出
【TS模块支持export=语法】
TypeScript模块支持export=语法以支持传统的CommonJS和AMD的工作流模型
export=语法定义一个模块的导出对象,若要导入一个使用了export=的模块时,必须使用TS提供的特定语法import module=require("module")
【如果仅导出单个class或function】
如果一个模块就为了导出特定的内容,就使用export default
【如果要导出多个对象】
把他们放在顶层里导出,导入时须明确列出导入的名字
【命名空间】
外部模块称为模块,内部模块称为命名空间
解决重名问题,命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性中,如果需要导出,则可以添加export
命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
【模块解析】
定义:编译器所要依据的一个流程,用它来找出某个导入操作所引用的具体值;编译器会尝试定位表示导入模块的文件,编译会遵循Classic或Node策略
相对导入是以 / ,./ ,../ 开头的,
其余导入的形式则为非相对导入
【模块解析策略】
1、Classic
以前是TS默认的解析策略,现在是为了向后兼容
-相对导入的模块是相对于导入它的文件进行解析的
-对于非相对模块的导入,则会从包含导入文件的目录开始依次向上级目录遍历,尝试匹配
2、Node
试图在运行时模仿Node.js模块解析机制
在Node.js里导入是通过require函数调用进行的,Node.js会根据require的相对路径还是非相对路径做出不同行为
-相对路径:
-非相对路径:
Node会在node_modules里查找模块,先查找文件,然后是合适的文件夹
3、TS如何解析
TS是模仿node.js运行时的策略来在编译阶段定位模块定义文件,所以增加了TS源文件的扩展名(.ts、.tsx、.d.ts)、以及在package.json里使用types来表示main的意义
-相对路径:
-非相对路径:
【路径映射】
TS编译器通过使用tsconfig.json文件里的paths来支持这样的声明映射
【利用rootDirs指定虚拟目录】
多个目录下的工程源文件在编译时会进行合并放在某个输出目录下,每当编译器在某个rootDirs的子目录下发现了相对模块导入,就会尝试从每一个rootDirs中导入
比如底下:构建的时候会将/src/views 和 /generated/templates/views的输出拷贝到同一个目录下
11、合并声明
【定义】
指编译器将针对同一个名字的两个独立声明合并为单一声明,合并后的声明同时拥有原先两个声明的特性,任何数量的声明都可以被合并
【合并接口】
对于函数成员,每个同名函数声明都会被当成这个函数的一个重载,后面的接口具有更高的优先级
【合并命名空间】
非导出成员仅在原有的(合并前的)命名空间内可见,合并之后,从其他命名空间合并进来的成员无法访问非导出成员
等同于:
【合并命名空间和类】
12、JSX
【定义】
JSX是一种嵌入式的类似XML的语法,可以被转换为合法的JS
文件的拓展名为.tsx,启用jsx选项
.tsx文件禁用了使用尖括号的类型断言,所以新加入了as的类型断言符号
固有元素使用特殊的接口JSX.IntrinsicElememts接口的属性来查找
JSX.ElementAttributesProperty决定props名
JSX.ElementChildrenAttribute决定children名
默认JSX表达式结果类型为any,可以通过JSX.Element接口自定义这个类型
JSX可以使用{}标签内嵌表达式
13、装饰器(decorators)
启用:在命令行/tsconfig.json里启用experimentalDecorators编译器选项
tsc --target ES5 --experimentalDecorators
【定义】 是一种特殊类型的声明,能够被附加到类声明、方法、访问符、属性或参数上,写法: @expression,expression求值后必须为一个函数,在运行时被调用
【装饰器工厂】 装饰器工厂就是一个简单的函数,返回一个表达式,以供装饰器在运行时被调用
【装饰器组合】 多个装饰器可以同时应用到一个声明上,由上到下依次对装饰器表达式求值,求值的结果会被当做函数,由下到上依次调用
14、三斜线指令
仅可放在包含它的文件最顶端,注释的内容会做为编译器指令使用,三斜线引用告诉编译器在编译过程中药引入的额外文件
如果指定了–noResolve编译选项,三斜线引用会被忽略,不会增加新文件
/// <reference path="..." />用来声明依赖
声明文件里包含 /// <reference types="node" />,表明这个文件使用了@types/node/index.d.ts 里面声明的名字
15、如何书写声明文件
| 结构 | |
| 规范 | |
| 举例 | |
| 深入 | |
| 发布和使用 |
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
-非编译选项(compilerOptions字段外的根级选项,控制的是ts编译器要编译的项目文件信息)
1、files
用于指示哪些文件需要编译,可以添加一组文件路径
2、include & exclude
项目文件较少时,可以使用files选项设置,但是当文件特别多或者项目文件更新频繁时,使用include
exclude与include相反,用于排除某些文件
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(); // 调用子组件公开的方法