TypeScript
TypeScript 是一种强类型的编程语言,具有强大的类型系统。了解和掌握 TypeScript 的类型系统是成为高效 TypeScript 开发者的关键。本文将深入探讨 TypeScript 类型系统的核心概念和用法。
学习路线
- 类型注解和类型推断
- 基本类型和字面量类型
- 接口和类型别名
- 泛型和泛型约束
- 类型保护和类型推断
- 类型兼容性与类型断言
- 高级类型:交叉类型、联合类型和条件类型
- 映射类型和索引签名
- 类型声明和模块导入
安装
执行命令:
npm install typescript -g
检查是否安装成功:
tsc
看到这个,说明成功了👇🏻
Hello World
本地新建文件hello.ts
,开始写代码
function sayHelloWorld(person: string) {
return name + ' hello world'
}
console.log(sayHelloWorld("Alice"))
执行tsc hello.ts
进行编译,会出现一个新的文件hello.js
,此时我们执行如下命令:
node hello.js
正常输出 Alice hello world
,成功!
上述的Ts
栗子中有一个点,就是:
指定类型。
注意: Ts
只会在编译与书写代码的过程中给你相关的警告,在Js
的执行中并不会有这样的警告,编译完的Js
代码也不会插入相关的校验代码。
如果我们在调用sayHelloWorld
的时候,传入一个123
,会出现以下情况:
情况一:编辑器会有提示,这时候看到提示,我们就可以修改代码。
情况二:如果没有修改代码,强制编译,过程中会抛出这个错误
Argument of type 'number' is not assignable to parameter of type 'string'.
类型“number”不能分配给类型"string"的参数.
但是js
文件依旧会编译成功,如果想要报错终止编译,那么需要配置tsconfig.json
中的noEmitOnError
tsconfig.json
这个文件是Ts
的编译选项配置文件,具体配置可以参考这里
不带任何输入文件的情况下调用
tsc
,编译器会从当前目录开始去查找tsconfig.json
文件,逐级向上搜索父目录。
生成tsconfig.json
在项目根目录执行
tsc --init
即可生成一个tsconfig.json
文件,里面有好多配置,我们来测试一个
我在这打开了删除注释的配置,然后在hello.ts
中新增一行注释
function sayHelloWorld(name: string) {
return name + "hello world111";
}
console.log(sayHelloWorld(1231212));
// 我是一行注释
执行tsc
编译,hello.js
文件如下👇🏻
同样的,我们刚刚说的那个noEmitOnError
也是可以生效的,成功!
这里放一个配置表:
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": {
// 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
基础
数据类型
布尔值
ts
let isOk: boolean = true
数值
let age: number = 18
let notANumber: number = NaN
字符串
let name: string = "Alice"
let age: number = 18
let sentence: string = `I from China Beijing, my name is ${name}, age ${age}`
空值
Js中没有Void
的概念,Ts中可以用void
表示没有任何返回值的函数:
function alertHello(): void {
alert('hello')
}
null & undefiled
let u: undefined = undefined
let n: null = null
注意
有一个点需要注意一下
let isOk: boolean = new Boolean('')
这样也会抛出错误不能将类型“Boolean”分配给类型“boolean”。
,因为new T
返回的是一个T
对象,👇🏻
任意值
Any
表示你的变量可以是任何值,Ts
-> Js
。。。
let anyVar: any = '121'
anyVar = true
完全ok,如果是👆🏻上面说的声明定式的类型,那么是不行的:
let name: string = 'alice'
name = true
抛出错误不能将类型“number”分配给类型“string”。
类型推论
如果你的变量没有赋值,那么TypeScript
会看你后面的值是啥类型,那你这个变量就是啥类型
let age = 12 // === let age: number = 12
age = '12'
抛出错误不能将类型“string”分配给类型“number”。
需要注意📢的是,如果你没有初始化变量,那就是any
类型
let age;
age = 1
age = true
这样完全Ok,并不会抛出错误,这样写类似于:
let age: any;
类型兼容性与类型断言
类型兼容性是指在赋值操作或函数调用中,一个类型是否可以被另一个类型替代的规则。在 TypeScript 中,类型兼容性遵循结构类型系统,即只要两个类型的内部结构兼容,它们就是兼容的。以下是类型兼容性的一些重要原则:
- 如果源类型包含目标类型的所有必需属性,则源类型是目标类型的子类型,它们是兼容的。
- TypeScript 中的类型兼容性是双向的,即源类型和目标类型互相兼容,那么它们是可互换的。
- 函数兼容性遵循参数列表的协变性,即目标函数的每个参数类型都必须兼容源函数的对应参数类型。
interface Animal {
name: string;
}
interface Cat {
name: string;
age: number;
}
let animal: Animal = {
name: "Animal" };
let cat: Cat = {
name: "Cat", age: 2 };
animal = cat; // 可以将 Cat 类型赋值给 Animal 类型,因为 Cat 具有 Animal 的所有必需属性
// cat = animal; // 无法将 Animal 类型赋值给 Cat 类型,因为 Cat 需要额外的 age 属性
类型断言允许开发者手动指定一个值的类型,即告诉编译器“我知道这个值的实际类型比它声明的类型更准确”。类型断言有两种形式:使用尖括号(<类型>值
)和使用as
关键字(值 as 类型
)。
let someValue: any = "This is a string";
let strLength: number = (<string>someValue).length; // 使用尖括号进行类型断言
let strLength2: number = (someValue as string).length; // 使用 as 关键字进行类型断言
console.log(strLength); // 输出:16
console.log(strLength2); // 输出:16
高级类型:交叉类型、联合类型和条件类型
- 交叉类型(Intersection Types):交叉类型表示同时具有多个类型的值,通过使用
&
运算符实现。交叉类型的结果是两个类型的并集。
示例代码:
interface Car {
brand: string;
color: string;
}
interface Flyable {
fly(): void;
}
type FlyingCar = Car & Flyable;
const myCar: FlyingCar = {
brand: "Tesla",
color: "red",
fly() {
console.log("Flying...");
},
};
- 联合类型(Union Types):联合类型表示一个值可以是多个类型之一,通过使用
|
运算符实现。联合类型的结果是多个类型的并集。
示例代码:
type Status = "success" | "error" | "loading";
function handleStatus(status: Status) {
if (status === "success") {
console.log("Operation succeeded");
} else if (status === "error") {
console.log("An error occurred");
} else {
console.log("Loading...");
}
}
handleStatus("success"); // 输出:Operation succeeded
handleStatus("error"); // 输出:An error occurred
handleStatus("loading"); // 输出:Loading...
- 条件类型(Conditional Types):条件类型是 TypeScript 中的一种高级类型,它根据一个条件来选择两种可能的类型中的一种进行映射。
示例代码:
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // A 的类型为 true
type B = IsString<number>; // B 的类型为 false
在上述示例中,使用条件类型 IsString<T>
来判断泛型 T
是否是字符串类型,如果是,则结果类型为 true
,否则为 false
。
联合类型
联合类型就是一个变量可以有多个类型,举个🌰
ts
let isWhat: string | number | boolean;
isWhat = '1'
isWhat = 1
isWhat = true
完全Ok,但是如果这样:
isWhat = [1,2,3]
就会抛出错误不能将类型“number[]”分配给类型“string | number | boolean”
再举一个例子:
function getLength(something: string | number): number {
return something.length
}
这样写就会抛出错误类型“number”上不存在属性“length”。
,length
不是他们的共有属性,所以会报错,改成这样:
function getString(something: string | number): string {
return something.toString()
}
完全Ok!
接口
基本定义
interface
是对行为的抽象,举个🌰:
interface Person {
name: string;
age: number;
}
let alice: Person = {
name: 'Alice',
age: 18
}
上面的栗子,变量alice
的结构必须与接口Person
相一致,如果我们不写age
,那么就会抛出错误类型 "{ name: string; }" 中缺少属性 "age",但类型 "Person" 中需要该属性。
所以定义的变量的属性,比接口定义的少是不被允许的,当然!多了也是不允许的,必须一致!
比如我们在变量alice
添加属性address
,那么就会抛出错误不能将类型“{ name: string; age: number; address: string; }”分配给类型“Person”。\ 对象文字可以只指定已知属性,并且“address”不在类型“Person”中。
可选属性
如果我们需要某个属性不是必须一致,那么可以这么做,继续用上面的栗子,假设age
为不必要属性:
interface Person {
name: string;
age?: number;
}
let alice: Person = {
name: 'Alice'
}
完全Ok
任意属性
如果我们需要在变量中定义一些我们将来可能会添加的属性,有极大的不确定性的话,比如我们想要新增一个address
属性,那么我们可以这样:
interface Person {
name: string;
age?: number;
[propName: string]: any
}
let alice: Person = {
name: 'Alice',
address: 'China Beijing',
gender: 0
}
完全OK!
只读属性
有时候我们需要一个属性不能再被修改,需要用到readonly
定义属性,举个🌰
interface Person {
readonly id: number;
name: string;
[propName: string]: any;
}
let alice: Person = {
id: 1,
name: 'Alice',
gender: 0
}
alice.id = 2
// error: 无法分配到 "id" ,因为它是只读属性。
总结一下:
- TypeScript的类型兼容性是基于结构类型系统的,只要两个类型的内部结构兼容,它们就是兼容的。如果一个类型包含目标类型的所有必需属性,则源类型是目标类型的子类型。
- 类型断言允许开发者手动指定一个值的类型,使用尖括号或者as关键字进行类型断言。
- TypeScript提供了交叉类型(Intersection Types)和联合类型(Union Types)来组合多个类型的特性。
- 条件类型(Conditional Types)是一种高级类型,根据条件来选择映射到不同类型的类型。