官方文档
1. 基本类型 1.1 普通类型包括 boolean、number、string、undefined、null、any、unknown、void、never、object。
// 几种特殊属性说明
// any:可以表示任何类型,并且变量可以正常使用(不建议);
// unknown:可以表示任何类型,但是不论用来做什么都是不合法的;
// void:表示没有任何类型,值只能为 undefined 和 null;当函数没有返回值时 其返回值类型是 void。
function fn(): void {}
// never:表示一个不存在的类型;never与其他类型的联合后,为其他类型;
type Test = string | number | never // 相当于 type Test = string | number
// object:表示非原始类型,也就是除 number,string,boolean 之外的类型;
1.2 数组类型
// 2.1 基本使用 类型[];如 number[]、string[]、any[]
const arr1: number[] = [1, 2, 3]
// 2.2 元组 [类型1, 类型2, ...];如 [string, number]
const arr2: [string, number] = ['str', 3]
// 2.3 数组泛型 Array<类型>;如 Array、Array
const arr3: Array = [1, 2, 3]
1.3 联合类型、交叉类型
1 联合类型
类型1 | 类型2;表示取值可以为多种类型中的一种;
如 number | string、(number | string)[]、Array
。
2 交叉类型
接口1 & 接口2;表示把几种类型合并起来。
// 应用场景: 比如 Student 继承 Person 上的属性
interface Person {
name: string;
}
type Student = Person & { age: number }
// 必须同时存在对应类型的 name 和 age 才符合 Student 类型
const obj: Student = {
name: "lk";
age: 18
}
1.4 类型保护、类型断言
1 类型保护
通过
typeof
方法判断返回值的类型,通过返回值类型对返回值做进一步处理;最终返回 符合函数返回值类型的最终值。
function getLength(arg: number | string): number {
if(typeof arg === 'string') {
return arg.length
} else {
return arg.toString().length
}
}
2 类型断言
可以用来手动指定一个值的类型;使用类型断言来告诉 TS,我(开发者)比你(编译器)更清楚这个参数是什么类型,你就别给我报错了。
// 方式一: <类型>值
// 方式二: 值 as 类型 tsx中只能用这种方式
function getLength(arg: number | string): number {
const str = arg
if (str.length) {
return str.length
} else {
const number = arg as number
return number.toString().length
}
}
1.5 类型推断、字面量类型
1 类型推断
TS会在没有明确的指定类型的时候推测出一个类型;
情况一: 定义变量时赋值了, 推断为对应的类型;
情况二: 定义变量时没有赋值, 推断为any类型。
2 字面量类型
2. 接口(Interface)有时候,我们需要定义一些常量,就需要用到字面量类型;
比如: type Sex = ‘男’ | ‘女’;
这样就只能从这些定义的常量中取值,乱取值会报错。
接口是对象的状态(属性)和行为(方法)的抽象(描述)。
1 使用接口来定义对象的类型
// 可选属性(age): 在可选属性名字定义的后面加一个 ? 符号
// 只读属性(id): 在属性名前用 readonly 来指定只读属性
// 拓展: 判断该用 readonly 还是 const 是要作为变量还是作为属性使用,
// 作为变量使用的话用 const,若作为属性则使用 readonly。
interface IPerson {
readonly id: number;
name: string;
age?: number;
}
2 用接口表示函数
接口可以描述函数(参数的类型与返回的类型)
interface SearchFunc {
(source: string, subString: string): boolean
}
3 一个类可以实现多个接口(implements)
interface Foo1 { foo1(): string }
interface Foo2 { foo2(): number }
class Car2 implements Foo1, Foo2 {
foo0() { alert('foo0') }
foo1() { return 'string' }
foo2() { return 123 }
}
4 一个接口可以继承多个接口(extends)
interface Foo1 { foo1(): string }
interface Foo2 { foo2(): number }
interface NewFoo extends Foo1, Foo2 {}
3. 类型别名(type)
3.1 类型别名
类型别名会给一个类型起个新名字,有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
type Name = string // 基本类型
type arrItem = number | string // 联合类型
const arr: arrItem[] = [1, '2', 3]
type Person = { name: Name }
type Student = Person & { grade: number } // 交叉类型
type Teacher = Person & { major: string }
type StudentAndTeacherList = [Student, Teacher] // 元组类型
const list:StudentAndTeacherList = [
{ name: 'lin', grade: 100 },
{ name: 'liu', major: 'Chinese' }
]
3.2 type 和 interface
1 相同点
// 1 都可以定义一个对象或函数;
interface Person1 { name: string }
const person1: Person1 = { name: 'lin' }
type Person2 = { name: string }
const person2: Person2 = { name: 'lin' }
// 2 都允许继承:
interface PersonI { name: string }
type PersonT = { name: string }
// (1) interface 继承 interfacetype 使用 extends
interface StudentI1 extends PersonI { grade: number }
interface StudentI2 extends PersonT { grade: number }
// (2) type 继承 typeinterface 使用交叉类型 &
type StudentT1 = PersonI & { grade: number }
type StudentT2 = PersonT & { grade: number }
// const person: Student = { name: 'lin', grade: 100 }
2 不同点
// 1.interface(接口) 是 TS 设计出来用于定义对象类型的,可以对对象的形状进行描述;
// 2.type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰;
// 3.type 可以声明基本类型、联合类型、交叉类型、元组,interface 不行;
// 4.interface可以合并重复声明,type 不行。
// interface 重复声明不报错
interface PersonI { name: string }
interface PersonI { age: number }
const person: PersonI = { name: 'lin', age: 18 }
// type 重复声明会报错
type PersonT = { name: string }
type PersonT = { age: number } // err
4. 类
5. 函数访问修饰符: 用来描述类内部的属性方法的可访问性;
public: 默认值, 公开的外部也可以访问;
private: 只能类内部可以访问;
protected: 类内部和其子类内部可以访问;
static: 类的静态属性,只能被类本身访问,而不能被实例化的对象访问。
5.1 为参数添加参数类型和返回值类型
```tsx
function foo1(x: number, y: number): number {
return x + y
}
```
5.2 函数的完整类型
```tsx
let foo2: (x: number, y?: number) = number
= function(x: number, y: number): number {
return x + y
}
```
5.3 TS中的可选参数
JavaScript 里,每个参数都是可传可不传的;没传参的时候,它的值就是 undefined,但是在
TypeScript
我们需要在参数名后面使用 ? 实现可选参数的功能(比如上面的参数 y )
5.4 函数重载
6. 泛型 6.1 普通类型和函数泛型函数名相同, 而形参不同的多个函数
// 普通类型
function foo1 (a: string, b: number): [string, number] {
return [a, b]
}
console.log(foo1('abc', 123))
// 函数泛型
function foo2 (a: K, b: number): [K, number] {
return [a, b]
}
console.log(foo2 ('abc', 123))
// 多个泛型参数
function foo3 (a: K, b: V): [K, V] {
return [a, b]
}
console.log(foo3 ('abc', 123))
6.2 泛型接口
在定义接口时, 为接口中的属性或方法定义泛型类型;在使用接口时, 再指定具体的泛型类型
// 泛型接口
interface IbaseCRUD {
data: T[];
add: (value: T) => void;
}
// 定义引用类
class User {
id?: number
name: string
age: number
constructor (name: any, age: any) {
this.name = name
this.age = age
}
}
// 定义使用类
class UserCRUD implements IbaseCRUD {
data: User[] = []
add(user: User): void {
user = {...user, id: Date.now()}
this.data.push(user)
}
}
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom1', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD)
6.3 泛型类
在定义类时, 为类中的属性或方法定义泛型类型;在创建类的实例时, 再指定特定的泛型类型
class Foo { // 泛型类
value: T
constructor(value: T) {
this.value = value
}
}
console.log(new Foo (123)) // number类
console.log(new Foo ('abc')) // string类
7. 工具类型
7.1 索引类型
1 keyof
( *** 作符)
用于获取某种类型的所有键,返回一个联合类型;
interface IPerson {
name: string;
age: number;
}
type Test = keyof IPerson // 'name' | 'age'
2 T[K](索引访问)
T[K] 表示接口 T 的属性 K 所代表的类型;
interface IPerson {
name: string;
age: number;
}
let type1: IPerson['name'] // string
let type2: IPerson['age'] // number
3 extends (泛型约束)
T extends U,表示泛型变量可以通过继承某个类型,获得某些属性。
interface ILength {
length: number
}
function printLength(arg: T): T {
console.log(arg.length)
return arg
}
7.2 映射类型
7.2.1 in *** 作符
用来对联合类型实现遍历。
```tsx
// 相当于
// type Obj = {
// name: string;
// school: string;
// }
type Person = "name" | "school"
type Obj = {
[p in Person]: string
}
const obj: Obj = {
name: "lk",
school: "jgs"
}
```
7.2.2 Partial < T >
将 T 的所有属性映射为可选属性。
```tsx
// 原理:1 [P in keyof T]遍历T上的所有属性
// 2 ? 设置属性为可选
// 3 T[P]设置类型为原来的类型
type Partial = {
[P in keyof T]?: T[P]
}
// 示例
interface IPerson {
name: string;
age: number;
}
let p1: IPerson = {
name: 'lin',
age: 18
}
type IPartial = Partial // Partial 改造
let p2: IPartial = { age: 18 } // 内部属性均为可选 ?
let p3: IPartial = {}
```
3 Readonly
< T >
将 T 的所有属性映射为只读属性。
// 原理
type Readonly = {
readonly [P in keyof T]: T[P]
}
// 示例
interface IPerson {
name: string
age: number
}
type IReadOnly = Readonly
let p1: IReadOnly = {
name: 'lin',
age: 18
}
p1.name = 'line' // 报错
4 Pick
用于抽取对象子集,挑选一组属性并组成一个新的类型。
```tsx
// 原理:参数1 T 表示要抽取的目标类型对象
// 参数2 存在一个约束, K 一定要来自 T类型 内部属性字面量的联合类型
type Pick = {
[P in K]: T[P]
}
// 示例
interface IPerson {
name: string,
age: number,
sex: string
}
type IPick = Pick
let p1: IPick = {
name: 'lin',
age: 18
}
```
5 Record
创建新的属性,Record 是一种非同态映射类型。
```tsx
// 原理: 参数1 传入继承于 any 的任何值,作为新创建对象的属性字面量的类型(键)
// 参数2 作为新创建对象的值
type Record = {
[P in K]: T
}
// 示例
interface IPerson {
name: string,
age: number
}
type IRecord = Record
let personMap: IRecord = {
person1: { name: 'lin', age: 18 },
person2: { name: 'liu', age: 25 }
}
```
6 拓展
7.3 条件类型同态映射类型:只作用于 obj 属性而不会引入新的属性,如 Partial、
Readonly
、Pick;
非同态映射类型: 会创建新的属性,如 Record
T extends U ? X : Y
若类型 T 可被赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型Exclude 和 Extract 的实现就用到了条件类型
1 Exclude
Exclude 意思是不包含,
Exclude
会返回联合类型 T
中不包含联合类型 U
的联合类型。
// 原理:never表示一个不存在的类型;never与其他类型的联合后,为其他类型。
type Exclude = T extends U ? never : T
// 示例
// 相当于 type Test = "b" | "c"
type Test = Exclude<'a' | 'b' | 'c', 'a'>
2 Extract
Extract
提取联合类型 T 和联合类型 U 的交集;并作为一个联合类型进行返回。
// 原理:
type Extract = T extends U ? T : never
// 示例
// 相当于 type Test = 'key1' | 'key2'
type Test = Extract<'key1' | 'key2' | 'key3', 'key1' | 'key2'>
7.4 其他工具类型
1 Omit
Omit
从类型 T 中剔除 U 中的所有属性(对象类型)。
// 原理1:使用 Pick 实现
// Pick 用于挑选一组属性并组成一个新的类型,Omit 是剔除一些属性,留下剩余的。
type Omit = Pick>
// 原理2:不使用 Pick 实现
type Omit2 = {
[P in Exclude]: T[P]
}
// 示例
// 相当于
// type IOmit = {
// name: string;
// }
interface IPerson {
name: string,
age: number
}
type IOmit = Omit
2 NonNullable
NonNullable
用来过滤类型中的 null 及 undefined 类型。
// 原理:never表示一个不存在的类型;never与其他类型的联合后,为其他类型。
type NonNullable
= T extends null | undefined ? never : T
// 示例
type T0 = NonNullable; // string | number
type T1 = NonNullable; // string[]
3 Parameters
Parameters 获取函数的参数类型,将每个参数类型放在一个元组中。
// 原理:在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
// 1 Parameters 首先约束参数T必须是个函数类型;
// 2 判断T是否是函数类型,如果是则使用 infer P 暂时存一下函数的参数类型,
// 后面的语句直接用 P 即可得到这个类型并返回,否则就返回never
type Parameters any>
= T extends (...args: infer P) => any ? P : never
// 示例
type T1 = Parameters<() => string> // []
type T2 = Parameters<(arg: string) => void> // [string]
type T3 = Parameters<(arg1: string, arg2: number) => void>
// [arg1: string, arg2: number]
4 ReturnType
ReturnType
获取函数的返回值类型。
// 原理:1 ReturnType首先约束参数T必须是个函数类型
// 2 判断T是否是函数类型,如果是则使用infer R暂时存一下函数的返回值类型,
// 后面的语句直接用 R 即可得到这个类型并返回,否则就返回any
type ReturnType any>
= T extends (...args: any) => infer R ? R : any
// 示例
type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
7.5 类型体 *** 的概念
8. TS 声名文件 8.1 declare
TypeScript
高级类型会根据类型参数求出新的类型,这个过程会涉及一系列的类型计算逻辑,这些类型计算逻辑就叫做类型体 ***
。当然,这并不是一个正式的概念,只是社区的戏称,因为有的类型计算逻辑是比较复杂的。
当使用第三方库时,很多三方库不是用 TS 写的,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
1 在 TS 中直接使用 Vue
(错误演示)
// err:找不到名称“Vue”
const app = new Vue({ // 报错
el: '#app',
data: {
message: 'Hello Vue!'
}
})
2 使用 declare 关键字,相当于告诉 TS 编译器,这个变量(Vue
)的类型已经在其他地方定义了,你直接拿去用,别报错;
// 定义类型
interface VueOption {
el: string,
data: any
}
// 使用 declare 关键字进行声名
declare class Vue {
options: VueOption
constructor(options: VueOption)
}
// 不报错
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
// 注意:declare class Vue 并没有真的定义一个类,只是定义了类 Vue 的类型,
// 仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
// const app = new Vue({
// el: '#app',
// data: {
// message: 'Hello Vue!'
// }
// })
8.2 .d.ts
通常我们会把声明语句放到一个单独的文件(
Vue.d.ts
)中,这就是声明文件,以.d.ts
为后缀。一般来说,ts 会解析项目中所有的
*.ts
文件,当然也包含以.d.ts
结尾的文件。所以当我们将Vue.d.ts
放到项目中时,其他所有*.ts
文件就都可以获得Vue
的类型定义了。
// ├── src
// | ├── index.ts
// | └── Vue.d.ts
// └── tsconfig.json
// --------- src/Vue.d.ts
// 定义类型
interface VueOption {
el: string,
data: any
}
// 使用 declare 关键字进行声名
declare class Vue {
options: VueOption
constructor(options: VueOption)
}
// --------- src/index.ts
// 使用
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
8.3 使用三方库
8.4 自己写声明文件当我们使用三方库的时候,是不是所有的三方库都要写一大堆
decare
的文件呢?不一定,要看社区里有没有这个三方库的 TS 类型包(一般都有)。社区使用
@types
统一管理第三方库的声明文件,是由DefinitelyTyped
这个组织统一管理的;比如通过npm
安装lodash
的类型包,只要安装了,就可以在 TS 里正常使用lodash
。当然,如果一个库本来就是 TS 写的,就不用担心类型文件的问题,比如
Vue3
。
比如你以前写了一个请求模块
myFetch
现在新项目用了 TS 了,要在新项目中继续用这个
用 TS 重写myFetch
,你有两种选择:myFetch
,新项目引重写的myFetch
直接引myFetch
,给它写声明文件(本文对 自己写声明文件 只做了解不详细描述,具体详见顶部参考链接)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)