TypeScript
概述
JavaScript的超集,在JavaScript基础上对了一些特性
- TypeScript=JavaScript+类型系统+ES6
- TypeScript=>编译=>JavaScript
- 最低可以编译到ES3的版本,不需要通过babel处理兼容问题
- 因为最终会被编译成js运行,所以任何一种js运行环境都能支持,例如浏览器环境和Node环境
TypeScript快速上手
- Typescript本身是一个npm的模块,我们需要安装这个模块 npm i typescript -D
- Typescript文件的后缀为.ts
-
执行npx tsc ts文件 则会在ts文件的同级目录下生成一个同名(后缀为.js)文件,将ts语法转换成了js语法
-vscode默认对Typescript的语法做类型检查,所以我们不用等到编译,可以在编辑器中直接看到类型错误提示
TypeScript配置文件
- tsc命令不仅能编译单个ts文件,也可以编译整个项目(工程),当编译整个项目的之前,我们需要给这个项目创建一个ts的配置文件(tsconfig.json)
- npx tsc –init =>在根目录下生成配置文件(tsconfig.json)
{
"compilerOptions": {
"target": "es5", /* 设置编译后的JavaScript的ECMAScript标准: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* 输出的代码采用什么样的方式进行模块化: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["es2015","DOM"], /* ts引用的哪些标准库,如果注释掉,则ts引用默认的标准库,如果填写了具体的标准库,则ts默认引用的标注库全部取消,ts只会引用写入的标准库 */
"sourceMap": true, /* 开启源代码映射,方便调试ts源代码 */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* 设置编译结果输出的文件夹 */
"rootDir": "./src", /*设置源代码(待编译代码)所在的文件夹,我们一般把源代码放在src的目录下 */
"strict": true, /* 开启严格模式,不能隐式推断 */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
- 严格模式的开启:需要为每个成员指定一个明确的类型注解
function foo(name) {
// 当我们把name的类型注解去掉,那么这个name被隐式推断为any类型
// 这种情况,在严格模式下不允许的
// 严格模式下,需要我们为每个成员指定一个明确的类型注解,即使这个成员是any类型,不能隐式推断为any
console.log(`hello,${name}`)
}
// 改进
function foo(name:any) {
// 当我们把name的类型注解去掉,那么这个name被隐式推断为any类型
// 这种情况,在严格模式下不允许的
// 严格模式下,需要我们为每个成员指定一个明确的类型注解,即使这个成员是any类型,不能隐式推断为any
console.log(`hello,${name}`)
}
- 在命令行中输入npx tsc ,会自动根据ts的配置文件编译整个项目
原始类型
- 使用全局的Symbol函数创建一个symbol类型的值会报错
/*
*原始数据类型 string|number|boolean|undefined|null|symbol
*/
const a: string = "12"
const b: number = 12
const c: boolean = true
const d: void = undefined
const e: null = null
// 使用全局的Symbol函数创建一个symbol类型的值会报错
const f: symbol = Symbol()
标准库
概念
标准库是内置对象所对应的声明文件
,我们在代码中使用内置对象就必须引用对应的标准库,否则TypeScript就找不到对应的类型,系统就会报错
背景
- Symbol是js的一个内置标准对象,和Array,Object性质是一样的,只不过Symbol是ES6新增的,对于这些内置的对象,它们也是有类型的,而且它们的类型在TypeScript引用的标准库(有不同的版本)已经帮我们定义好了
-
当target:”es5″时
,ts引用的标准库声明文件为lib.es5.d.ts,很明显,这个文件实际上是es5标准库,而我们的Symbol是ECMAScript2015中定义的,所以自然不会在这个文件中定义Symbol所对应的类型,相同的道理Promise也会报相同的错误
解决方案
-
方案一
1.1 方法:
直接修改targer为ES2015=> “target”: “ES2015”, 这样ts就会默认引用ES6标准库 lib.es2015.core.d.ts
1.2 注意点:
这种方式,在编译的过程中就不会将ES6语法转换成ES5语法(例如箭头函数不会转换成普通函数)
-
方案二
2.1 方法:
当我们想要将ES6代码转换成ES5代码,但ts又要引用ES2015(ES6)标准库,可以在ts的配置文件中的lib选项指定ts引用的ES2015标准库=> “lib”: [“ES2015”]
2.2 注意点:
发现dom和bom相关的api都报错,例如console对象在浏览器环境中是bom对象所提供的,而我们刚刚在lib选项中只设置了es2015,所有ts引用的默认标准库都被覆盖了,我们需要手动添加标准库,ts中将dom个bom都声明到一个标准库中,就叫做DOM标准库 => “lib”: [“ES2015”,“DOM”]
TypeScript中文错误消息
vecode会根据插件的语言环境来显示相应语言的错误消息提示,如果强制显示某种语言显示消息,例如我们在vscode中经常安装中文语言包,
TypeScript报错,就是中文错误消息,我们想要强制显示引文错误消息时(方便百度查找资料)
- 首选项=>设置=>搜索 TypeScript: Locale=>en
TypeScript作用域的问题
-
在不同文件中当变量名相同时,就会报错,例如在index.ts和index1.ts文件中都定义const a=1,
-
原因:
我们在ts文件中的变量是定义在全局作用域上的,TypeScript在编译整个项目的时候就会出现变量重复定义的错误(const命令),如果我们想这个ts文件是单独 -
解决方法
- 在ts文件中添加export {}代码,那么这个ts文件就会作为一个模块,通过commonjs规范我们知道,模块有自己的单独作用域,注意:export {}的花括华并不是导出一个空对象,而是export 的一个语法
- 此时ts文件中的变量就是这个模块中的局部成员
const a = 1
export { }
Object类型
-
TypeScript中的Object并不单指普通的对象的类型,而是
泛指所有的非原始类型
,也就是
对象,数组,函数
,接收原始类型的值就会报错
export { }
// object类型可以接收非原始类型 普通对象/数组/函数
const obj: object = {}
const arr: object = []
const fn: object = function () { }
// 当接收原始类型的时候就会报错
const num: object = 12
- 如果我们需要普通的对象类型,那么我们可以以对象字面量的形式来声明这个普通对象类型
export { }
const num: { foo: number } = { foo: 1 }
- 定义类型 type
export { }
// 可选属性
type s = string | number
interface Foo {
name?: s,
age: number
}
const foo: Foo = {
name: "1",
age: 20
}
- 更专业的形式是通过接口的方式来声明上面的普通对象类型,在下面接口内容中详细介绍
数组类型
- 与flow中完全一致
export { }
const arr1: Array<number> = [1, 2]
const arr2: number[] = [1, 2]
const arr3: [string, number] = ["1", 2]
-
强类型使用的好处:不需要加大量的if判断条件
实例,定义一个累加函数
export { }
function sun(...args: number[]) {
/*
*在js中我们可能要对数组中每个元素是不是number类型加一个if判断条件
*在ts中我们可以给参数args进行类型声明,在编译阶段检查是否有类型错误
*/
return args.reduce((prev, current) => {
return prev + current
})
}
sun(1, 2) //正常
sun(1, "2")// 报错
元祖类型–特殊的数组类型
- 元祖类型是一种数组类型,明确元素数量和每个元素类型
- 使用类似数组字面量的形式来定义元祖类型
- 不要求每个元素的类型相同
export { }
// 明确了元素数量和元素类型的数组,每个元素的类型可以不同
const a: [number, string] = [1, " 2"]
枚举类型
-
在开发过程中,我们经常会用到某几个
数值
来代表某种状态
export { }
const obj = {
status: 0 // 0代表草稿 1代表未发布 2代表已发布
// 这个状态的取值只可能是0/1/2这三个值
// 如果在代码中直接使用0/1/2这三个值来表示状态的话,时间久了,我们就弄不清楚这三个数字到底对应的是哪个状态,也可能混入其他的值(0/1/2以外的值)
}
-
枚举类型有两个特点
1.给一组数值分别起上一个更好理解的名字
2.一个枚举中只会存在几个固定的值,并不会出现超出范围的可能性 -
语法
// 1:enum关键字
// 2:对象名和花括号之间没有等号
// 3:键值之间是等号,而不是普通对象中的冒号
// 4:值只能是number类型或者string类型,strin类型枚举情况很少见
enum 对象名 {
键1 = 值1,
键2 = 值2,
键3 = 值3,
}
- 实例
export { }
// 键值之间是等号,而不是普通对象中的冒号
enum objStatus {
Draft = 0,
Unpublished = 1,
published = 2
}
const obj = {
status: objStatus.Draft
}
枚举类型注意点
- 枚举类型会入侵到我们运行时的代码中,即会影响编译后的结果,我们在TypeScript中使用的大多数类型再经过编译装换后都会被移除掉,因为它只是为了帮助我们在编译过程中做类型检查,而枚举最终会编译成一个双向的键值对对象=>可以通过键获取值,也可以通过值获取键
- ts文件中的代码
export { }
enum objStatus {
Draft = 0,
Unpublished = 1,
published = 2
}
const obj = {
status: objStatus.Draft
}
- 编译后js中的代码
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var objStatus;
(function (objStatus) {
objStatus[objStatus["Draft"] = 0] = "Draft";
objStatus[objStatus["Unpublished"] = 1] = "Unpublished";
objStatus[objStatus["published"] = 2] = "published";
})(objStatus || (objStatus = {}));
var obj = {
status: objStatus.Draft
};
//# sourceMappingURL=index.js.map
- 这个双向的键值对对象的好处就是可以通过过索引值获取到键名称,即使这个键在我们开发过程中没有使用到,例如上面的Unpublished 和published 的键名
常量枚举
- 语法:在enum关键字前面添加const关键字
-
作用:不会生成双向的键值对对象,没用到的键名不会出现在编译后的js代码中=>
常量枚举类型不会入侵到我们运行时的代码中
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var obj = {
status: 0 /* Draft */
};
//# sourceMappingURL=index.js.map
函数类型
- 函数参数和返回值的限制
函数声明的类型约束
1.参数固定
export { }
function func1(a: number, b: number): string {
return "func"
}
func1(1, 2)
- 参数可选,在参数后面加上?,和对象可选属性一样
export { }
function func1(a: number, b?: number): string {
return "func"
}
func1(1, 2)
- 可选参数出现在参数的最后面,因为参数还债位置进行传递,如果可选参数出现在必选参数前面,我们是拿不到这个必选参数的, 即如果a是可选参数,b是必选参数,当函数调用时传入一个参数,那这个参数到底是赋值给a还是b呢,会造成混乱
函数表达式的参数类型(箭头函数形式)
- 函数表达式也可以采用函数声明的方式对参数和返回值进行限制,但表达式最终会通过一个变量接收,这个变量也是有类型的,ts会根据函数表达式推断出这个变量的类型,即
// ts自动推断func变量的类型
export { }
const func = function (a: number, b: number): string {
return "func"
}
// 显示对变量func进行类型注解
const func: (a: number, b: number) => string = function (a: number, b: number): string {
return "func"
}
// 以上两种方式等价
当函数作为另一个函数的参数(回调函数)
function fn(callback: (a: string, b: number) => void) {
}
fn(function (a: string, b: number) {
console.log(a, b)
})
任意类型 any
export { }
function stringify(value: any) {
return JSON.stringify(value)
}
stringify("12")
stringify(12)
stringify(true)
- any类型不会有任何类型的检查,所以他依然会存在类型安全的问题,我们轻易不要使用这种类型,只是在兼容老代码的时候用到该类型
隐式类型推断
- 在ts中如果我们没有很明确的通过类型注解来标记一个变量的类型,ts会根据这个变量的使用情况来推断这个变量的类型–隐式类型推断
- ts根据变量的使用情况能推断出变量的类型
export { }
let age = 10 // ts 会推断这个变量是number类型 相当于let age:number=10
age = "12" // 当给number类型的变量赋字符串的值的时候回报错
- ts无法推断一个变量的具体类型时,就会将这个类型标记为any
export { }
let foo // 声明这个变量的时候并没有给它赋值,foo就是any类型
foo = 10
类型断言
- 在有些特殊的情况下.ts无法推断出一个变量的具体类型,而我们作为开发者可以根据代码的使用情况是可以知道变量到底是什么类型的
export { }
// 假设这个数组nums来自一个明确的接口
const nums = [100, 120, 119, 112]
const res = nums.find(item => item > 0)
// 因为上面的res既可能是number类型,也可能为undefined类型,因为在代码执行阶段,才会获取到res真实的值,在编译阶段nums.find函数是不执行,所以res的结果我们是不知道的,类型也就无法确定
// 类型无法确定的情况下,我们是不能调用变量的特有方法,res*res只可能res是number类型情况下成立
// const square = res * res 报错
-
断言 按时关键字
1.语法 const 接收变量=断言变量 as 类型
export { }
// 假设这个数组nums来自一个明确的接口
const nums = [100, 120, 119, 112]
const res = nums.find(item => item > 0)
// 因为上面的res既可能是number类型,也可能为undefined类型,因为在代码执行阶段,才会获取到res真实的值,在编译阶段nums.find函数是不执行,所以res的结果我们是不知道的,类型也就无法确定
// 类型无法确定的情况下,我们是不能调用变量的特有方法,res*res只可能res是number类型情况下成立
const num = res as number
const square = num * num
- 类型断言并不是类型转换(类型断言是编译阶段概念,类型转换是运行时的概念),所以需要一个变量来接收断言的变量,而不是直接使用断言变量进行操作
接口 interface
- 我们可以理解为一种规范,一种契约
-
约定对象的结构,对象有哪些成员,成员的类型是什么(包含键类型和值类型)
- 关键字 interface
export { }
function printPost(post) {
// 对post对象有一定的要求:必须要有title和content属性
// 打印文章的标题和内容
console.log(post.title)
console.log(post.content)
}
- 语法
export { }
// interface关键字定义一个对象的结构:对象有哪些成员,成员的类型是什么
interface Post {
title: string,
content: string
}
function printPost(post: Post) {
// 对post对象有一定的要求:必须要有title和content属性
// 打印文章的标题和内容
console.log(post.title)
console.log(post.content)
}
可选属性
- 约定的对象某些属性可选
export { }
interface Post {
title: string,
content: string,
subtitle?: string,//可选属性,在属性后面加?
}
const post: Post = {
title: "title",
content: "content",
}
只读属性
- 约定对象属性前面 readonly修饰符
- 只读属性初始化赋值后不能修改
export { }
interface Post {
title: string,
content: string,
subtitle?: string,
readonly sumary: string
}
const post: Post = {
title: "title",
content: "content",
sumary: 'sumary' // 当sumary这个属性初始化赋值后不能修改
}
post.sumary = "sumary1" //修改报错
接口动态成员用法
- 适用于动态成员对象,在定义的时候我们不能确定有哪些具体的成员,例如缓存对象,
export { }
interface Cache {
[key: string]: string //这里的key并不是固定的,可以是任意的名称,只是代表了属性的名称,只是一种格式,后面的string代表了这个键的类型
}
const cache: Cache = {}
// cache这个对象可以动态添加任何成员,不过这些成员必须是string类型的的键和值
cache.foo = "value1"
cache.bar = "value2"
类
- TypeScript增强了class的相关用法 =>访问修饰符和抽象类
类的基本使用
export { }
class Person {
name: string //也可以通过等号的方式赋初始值,不过我们一般还是在会构造函数中动态的为这个name属性赋值
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
// 直接使用this访问当前类的属性会报错,说的是当前实例上并不存在对应name和age属性
// 在TypeScript中我们需要明确在类(class)中它所拥有的属性,而不是在构造函数中动态的通过this添加属性
// 在类(class)中声明属性的方式就是直接在类中定义=>ES2016中定义的语法
}
}
- 注意点:
- 在TypeScript中类的实例属性必须要有一个初始值,可以在声明属性的等号后面赋值或者在构造函数中初始化,两者必须取其一,不然会报错
-
类的实例属性在使用之前必须要在类中声明,目的是为了给实例属性做类型标注
- 方法不需要在类中声明(定义在原型对象上,并不是实例的成员)
export { }
class Person {
name: string;
age: number
constructor(name: string, age: number) {
// 构造函数参数的类型和类中对应属性的类型必须保持一致
this.name = name
this.age = age
}
say(msg: string): void {
console.log(`${msg}`)
}
}
类的访问修饰符(访问级别的控制 public/private/protected)
- 公有属性:关键字public 默认不加修饰符的属性都是公有属性
- 私有属性: 关键字private=>在构造函数内部能访问,在实例对象中不能访问该属性
// 私有属性private,只能在类的内部使用,创建的实例this,是不能访问该属性的
class Person {
name: string
private age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
say() {
// 在类的内部可以访问私有属性
console.log(this.age)
}
}
const p = new Person("tom", 12)
console.log(p.name) // 实例对象能访问到name属性=>公有属性
console.log(p.age) // 实例对象能访问到age属性=>私有属性
- 受保护属性;关键字protected 在子类和自身属性中都能访问得到,和private修饰符一样,在实例中也是访问不到该属性的
class Person {
name: string
private age: number
protected gender: boolean // 只允许在子类中访问相应的属性
constructor(name: string, age: number) {
this.name = name;
this.age = age;
this.gender = true
}
say() {
// 在类的内部可以访问私有属性
console.log(this.gender)
}
}
const p = new Person("tom", 12)
// console.log(p.gender) // 报错
class Student extends Person {
constructor(name: string, age: number) {
super(name, age)
console.log(this.gender) // 可以访问到父类的gender属性
}
}
修饰符小结
- public定义的成员是公共成员,在类的内部,外部,子类中都可以访问
- private定义的成员是私有成员,只能在类的内部访问
- protected定义的是受保护的成员,在自身属性和子类中都可以访问
类的只读属性
- 属性被标注为只读,则实例化的对象的这个属性不能被修改
- 当该属性存在修饰符,则readonly 跟在修饰符的后面
export { }
class Person {
public readonly name: string // 在实例中该属性只读
private age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const p = new Person("tom", 12)
p.name = "123" //试图修改实例的只读属性的值时,报错
类与接口
- 不同的类之间有相同的方法(能力),我们会使用接口(协议)去抽象
export { }
// 类与接口
// 两个不同的类Person和Animal,相同的特征是都会吃和跑(方法)
// 定义一个接口约定类中公共的方法,不包含具体的实现(没有函数体)
interface EatAndRun {
eat(food: string): void
run(distance: number): void
}
class Person implements EatAndRun {
eat(food: string): void {
console.log(`优雅的进餐:${food}`)
}
run(distance: number): void {
console.log(`直立行走:${distance}`)
}
}
class Animal implements EatAndRun {
eat(food: string): void {
console.log(`呼噜呼噜的吃:${food}`)
}
run(distance: number): void {
console.log(`爬行:${distance}`)
}
}
- 改进:
- 面的一个接口抽象了两个方法,也就是抽象了两个能力,但是这两个能力必须在类中同时存在吗?不一定,比如有的类只有其中一个方法(能力),
- 更优的方案是:一个接口约束一个能力,让一个类型实现多个接口
export { }
// 一个接口约束一个能力
interface Eat {
eat(food: string): void
run(distance: number): void
}
interface Run {
eat(food: string): void
run(distance: number): void
}
class Person implements Eat, Run { //使用逗号引用两个接口
eat(food: string): void {
console.log(`优雅的进餐:${food}`)
}
run(distance: number): void {
console.log(`直立行走:${distance}`)
}
}
class Animal implements Eat, Run {
eat(food: string): void {
console.log(`呼噜呼噜的吃:${food}`)
}
run(distance: number): void {
console.log(`爬行:${distance}`)
}
}
小结
- 类的实例属性在使用之前必须要在类中声明
- 不同的类之间有相同的方法(能力),我们会使用接口(协议)去抽象
- interface定义接口,implements引用接口
- 一个接口约束一种能力,一个类型可以引用多个接口
抽象类
- 抽象类和接口在某种程度上有点类似,它可以约束子类中有哪些成员
- 不同于接口的是,抽象类可以包含一些具体的实现(函数体),而接口只能是一个成员的抽象,不包含具体的实现
-
关键字 abstract 写在class之前,
当类抽象后,这个类就不能通过new实例化对象,只能被子类继承
abstract class Animal {
eat(food: string): void {
console.log(`呼噜呼噜的吃:${food}`)
}
}
class Dog extends Animal {
}
-
当我们创建这个抽象类的实例时,就会报错
-
在抽象类中还可以定义抽象方法,在方法名前加abstract ,抽象方法不需要方法体,它的实现是在子类中(当父类中有抽象方法时,子类必须要去实现这个方法)
-
子类实例化的对象就会同时拥有父类中的实例方法和自身实现方法
export { }
//
abstract class Animal {
eat(food: string): void {
console.log(`呼噜呼噜的吃:${food}`)
} //eat方法是在父类中定义,子类继承
abstract run(distance: number): void
// run是告诉子类,在子类中必须实现这个run 方法
}
class Dog extends Animal {
run(distance: number): void {
console.log(distance)
}
}
//子类实例化的对象就会同时拥有父类中的实例方法和自身实现方法
const d = new Dog()
d.eat("牛肉")
d.run(100)
泛型 -实际上函数
概念
-
作用:
泛型就是把我们定义是不能明确的类型变成一个参数,在我们使用的时候再去传递一个这样的类型参数
,实际上就是类型占位 - 关键词:尖括号 <泛型类型> 在函数签名后面,函数参数不确定类型用T占位,调用函数时,在函数签名后<具体类型>
- 函数为例:我们在声明函数的时候不去指定函数具体的类型,等到调用的时候再去指定它的类型,好处是极大程度复用代码
export { }
// 创建一个数字类型的数组
function createNumberArr(length: number, value: number): number[] {
// 这里的Array就是一个泛型类,我们并不知道这个Array中元素的类型,所以我们使用一个泛型参数,等到调用的时候再传递一个具体的类型
return Array<number>(length).fill(value)
}
createNumberArr(3, 100)
但这个函数存在一个问题,只能创建数字类型的数组,不能创建字符串类型数组,我么可以继续定义一个创建字符串的函数
export { }
// 创建一个字符串类型的数组
function createNumberArr(length: number, value: string): string[] {
// 这里的Array就是一个泛型类,我们并不知道这个Array中元素的类型,所以我们使用一个泛型参数,等到调用的时候再传递一个具体的类型
return Array<string>(length).fill(value)
}
createNumberArr(3, "100")
缺点:但这样,定义的这两个函数,其实有些代码是重复的
方案:将类型作为一个参数,调用的时候再去传递这个类型
export { }
// 1:一般泛型参数都会以大写的T作为名称,然后把函数中不明确的类型用T来代表
function createArr<T>(length: number, value: T): T[] {
return Array<T>(length).fill(value)
}
// 2:调用函数的时候使用尖括号的方式传递这个泛型参数
// 这样这个函数就可以支持创建任意类型的数组
createArr<string>(3, "100")
//小结:泛型就是把我们定义是不能明确的类型变成一个参数,在我们使用的时候再去传递一个这样的类型参数
类型声明
背景
- 在实际的项目开发过程中,我们难免会用到第三方的npm 模块,而这些npm模块并不都是TypeScript编写的,所以它提供的成员不会有强类型的体验,例如lodash
export { }
// lodash模块中提供了很多工具函数
//cameCase将字符串转换成驼峰形式
import { cameCase } from "lodash"
// 但我们发现cameCase没有任何类型提示,在这种情况下我们需要单独类型声明
declare function cameCase(input: string): string
// 类型声明后这个函数就会有相应的类型限制
const res = cameCase("hello type")
// 类型声一个成员在定义的时候由于种种原因,它没有声明一个明确的类型,我们在使用的时候可以为它单独做出一个明确的声明=>考虑兼容一些铺的js模块
- 目前绝大多数常用的npm模块都已经提供了对应的声明,我们只需要安装下该模块对应的类型声明模块就可以了
-
npm install @types/lodash=>lodash的类型声明模块
-
lodash类型声明模块
知识点
TypeScript和Polyfill
-
TS 可以通过配置文件把代码编译至 ES5,但该编译过程只针对语法层面的编译,比如 const/let 关键字,针对 ES6 新的 api 还得结合 babel 进行 polyfill
- 一般先通过TS把ts文件编译成js文件,然后通过babel处理编译后的js文件
第三方模块没有类型声明
express模块-没有类型声明文件
- 找不到express模块的类型声明文件
caz模块-有类型声明文件
小结
为什么有的第三方包没有类型补充声明
-
为什么有的模块报错,有的模块不报错呢,并不是说该模块是通过ts写的,而是该模块有没有
类型声明文件
,在模块的package.json文件中有types字段,指向该模块的类型声明文件 - express模块没有types字段,也就是说express模块没有类型声明文件
-
caz模块有
types
字段,也就是说caz模块有类型声明文件
-
模块的类型声明文件补充声明文件(除标准库外),我们导入的express实际上是一个对象,如果没有对这个对象进行类型注解,那么我们不知道该对象有哪些成员,成员的类型是什么,所以要补充该模块express对象所有的成员的类型声明,使用declare语句
- 标准库中也是这样进行类型声明的
安装第三方包的类型补充声明文件
- 安装第三方包的类型补充声明文件
npm install @types/express
-
安装的第三方包的类型补充声明文件放置到TypeScript自带的类型声明文集夹中
如何将写好的模块(不管是js写的还是ts写的)生成该模块的类型补充声明文件
-
tds工具
-
js模块的package.json文件中的deprecated:true/deprecatedMap:true