前言
类型注解
- 概念:
- 给 变量 添加 类型约束,使变量 只能被赋值为约定好的类型,同时可以有相关的类型提示;
- 作用:
- 限制变量能赋值的数据类型并给出提示;
- 解释:
- 约定了什么类型,就只能给变量赋该类型的值(赋别的类型的值会报错);
- 语法:
变量: 类型 - 代码展示:
// : number - 就是类型注解 // 约束变量的类型 - 约束的是什么类型,就只能给这个变量赋该类型的值,否则会报错 let age: number = 23 age = 24 // age = '24' - 报错:不能将类型"string",分配给类型"number" - 说明:
: number就是类型注解,约束变量age只能被赋值为number类型,同时可以有number类型相关的提示; -
类型分类
- 可以将TS中的常用类型细分为 两类:
- JS已有类型;
- TS新增类型;
JS已有类型
- 基本数据类型:
number、string、boolean、null、undefined、symbol;- 特点:简单,完全按照JS中类型的名称来书写;
symbol:- 表示独一无二的值,最大的用法是用来定义对象的唯一属性名;
Symbol函数栈不能用new命令,因为Symbol是原始数据类型,不是函数。可以接受一个字符串作为参数,为新创建的Symbol提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分;const a: number = 1; const b: string = '1'; const c: boolean = true; const d: undefined = undefined; const e: null = null; const f: symbol = Symbol('symbol');
- 引用数据类型:
object(数组、对象、函数、……);- 特点:对象类型,在TS中更加细化,每个具体的对象都有自己的类型语法;
// 数组类型的两种写法:(推荐第一种写法) // ✅ 数值类型数组 - 因为已经约束为number类型,所以在该数组中只能出现number类型,不能出现其他类型 const arr: number[] = [1, 2]; // ❌ 字符串类型数组 - 不推荐 const arr1: Array<number> = [1, 2]; const obj: { name: string age: number sayHi(): void // 参数可传可不传 greet: (type?: string) => void } = { name: '奥特曼', age: 12, sayHi: () => {}, greet: (type) => type }; obj.greet(); const timeObj: { startTime: number endTime: Date } = { startTime: new Date().getTime(), endTime: new Date() };
TS新增类型
- 联合类型(
|); - 自定义类型(类型别名)(
type); - 接口(
interface); - 元组(
特殊的数组); - 字面量类型;
- 枚举(
enum); void(空);- 泛型;
any(任何类型)等;
一、基本类型,类型名大小写区别?
- 类型写法可看【前言-JS已有类型】;
- 简单类型 的 类型名 只能是 小写;
- 有时候发现,类型名首字母大写,也不会报错,但是所表达的意思就不一样了;
- 小写:
- 表示 该变量 的 值 是
string类型;
- 表示 该变量 的 值 是
- 大写:
- 表示 该变量 是一个 实例对象;
- 为什么大写的
String也可以?- 像数字、字符串等,在
JS内部有个 内部包装类 ,当我们把 字符串当对象使用的时候,在 js 内部会帮我们自动进行包装;
- 像数字、字符串等,在
// 类型名小写 // 约束变量的str的类型是 string 类型 let str: string = '13'; // 类型名大写 // 此处的意思是 str1 表示是个 实例对象,是 String 的 实例对象 let str1: String = '123'; // 这种才是 大写 String 的正确赋值方法 let str2: String = new String('123'); - 小写:
二、值类型
TS规定,单个值也可以是一种类型,称为值类型;
let x: 'hello';
x = 'hello'; // 正确
x = 'world'; // 报错
// 变量 x 的类型是字符串 'hello',导致它只能赋值为这个字符串,赋值其他的字符串为报错;
- TS推断类型时,遇到
const命令声明的变量,如果没有显示注明类型,就会推断为该变量是值类型;// x 的类型是 'api' const x = 'api'; // y 的类型是 string const y: string = 'api'; - 注意:
const命令声明的变量,如果赋值为对象,并不会推断为值类型;
三、联合类型
联合类型(
|):由 两个 或 多个 其他类型 组成的 类型,表示可以是 这些类型 中的 任意一种;
- 代码展示:
// 联合类型 - | // 添加小括号:首先是数组,其次,这个数组中元素的类型 let arr: (number | string | boolean | object)[] = [1, true, 'a', {name: '哈哈'}] // ❗ 注意区分 // 不添加小括号:test既可以是数字 也可以是 字符串型的数组 let test: number | string[] = 1 let test1: number | string[] = ['a'] - 🔺 注意:
- 添加小括号:
- 可以是 许多类型 中的 任意一种;
- 也可是 多种类型 同时出现;
- 不添加小括号:
const a = string | number[] = 1;const a = string | number[] = ['1', '2'];- 既可以是
string类型,也可以是number类型的数组;
- 添加小括号:
- 联合类型可以与值类型相结合,表示一个变量的值有若干种可能;
四、交叉类型
交叉类型:指的多个类型组成的一个新类型,使用符号
&表示;
- 交叉类型的主要用途是表示对象的合成;
// 对象 obj,必须同时具有 a、b 两个属性,并且对应的属性值要满足约定的类型
let obj: { a: number } & { b: string };
obj = {
a: 1,
b: '1',
};
- 交叉类型 可以结合 类型别名 来实现 继承;
五、数组类型
5.1 添加类型
- 数组 的 类型注解 有 两种方式:
5.1.1 ✅ 方括号
- 在数组成员的类型后面,加上一对方括号;
// 字符串类型的数组 const arr: string[] = ['1']; // 数字类型的数组 const arr1: number[] = [1]; // 对象类型的数组 const objArr: object[] = []
5.1.2 ❌ TS内置 Array 接口
- 使用 TypeScript 内置的
Array接口;// 数字类型的数组 const arr; Array<number> = [1]; // 字符串类型的数组 const str: Array<string> = ['1'];
5.2 只读数组
- js规定,使用 const 声明的数组变量是可以改变成员的;
- 但是很多时候,确实有声明为只读数组的需求,即不允许变动数组成员;
- TypeScript 允许声明只读数组,有两种方式:
- 数组类型前面加上
readonly关键字; - 使用
ReadonlyArray接口;
- 数组类型前面加上
// 只读数组
const arr: readonly number[] = [1, 2, 3];
const arr1: ReadonlyArray<number> = [1, 2, 3];
arr[0] = 2; // 报错
arr1[0] = 2; // 报错
5.3 多维数组
- TypeScript 使用
T[][]的形式,表示二维数组,T是最底层数组成员的类型;
// 多维数组
const arr11: number[][] = [[1, 2], [3, 4]];
const arr2: (number | string)[][] = [[1, 2], [2, '3']];
// 当然,也可以使用 内置接口去实现,这里就不展示了,大家感情兴趣的话,可以自行尝试哈
六、元组类型
元组是TS特有的数据类型,JS没有单独区分这种类型; 它表示:成员类型可以自由设置的数组,即数组的各个成员的类型可以不同; 元组类型是另一种类型的数组,它确切的知道 包含多少个元素,以及 每个元素的类型;
- 元组必须明确声明每个成员的类型:
const arr: [number, string, null, boolean] = [1, '1', null, true]; - 元组类型的写法,与数组有一个差异,数组的成员类型是写在方括号外面的
(number[]),元组的成员类型是写在方括号里面的([number]);
七、函数类型
- 函数类型:
- 实际上指的是 函数 参数 和 返回值 的类型;
7.1 添加类型
- 为函数指定类型的有两种方式:
- 单独指定参数、返回值的类型:
// 单独指定参数和返回值类型 - 声明式函数 + 函数表达式 function add0(a: number, b: number): number { return a + b; } // 设置参数默认值 function add(a: number = 0, b: number = 0): number { return a + b; } const add1 = (a: number, b: number): number => a + b; // 设置参数默认值 const add2 = (a: number = 1, b: number = 1): number => a + b; - 同时指定参数、返回值的类型:(❗只适用于函数表达式)
// 同时给 参数 和 返回值 指定类型 // 同时指定参数和返回值类型 - 只适用于 函数表达式 const sub: (a: number, b: number) => number = (a, b) => a - b; // 设置参数默认值 const sub1: (a: number, b: number) => number = (a = 0, b = 0) => a - b;
- 单独指定参数、返回值的类型:
- 🔺注意:
- 如果 函数 没有 返回值,那么,函数 返回值 类型为:
void;
// 如果函数没有返回值,那么,函数返回值的类型为void function add(name: string): void { console.log(name); } add('小Q'); - 如果 函数 没有 返回值,那么,函数 返回值 类型为:
7.2 可选参数
- 如果函数的某个参数可以省略,则在 参数名称 后面 添加
?;
const fn: (name?: string) => void = (name) => {
console.log(name ?? '嘻嘻哈哈')
};
- 注意:
- 可选参数 只能出现 在 参数列表 的 最后,也就是说可选参数后面不能再出现必选参数;
7.3 参数默认值
- TS函数的参数默认值写法与JS一致;
- 设置了默认值的刹那好苏,就是可选的,如果不传入该参数,它就会等于默认值;
const add: (a: number, b: number) => number = (a = 0, b = 0) => (a + b);
七、类型别名
7.1 基本使用
- 类型别名:
- 自定义类型
- 为任意类型起别名;
- 使用场景:
- 当同一类型(比较复杂的类型)被多次使用时,可以通过类型别名,简化该类型的使用;
解释:
- 使用
type关键字类创建类型别名; - 类型别名可以是任意合法的变量名称;
- 创建类型别名后,直接 使用该类型别名作为变量的类型注解 即可;
- 语法:
type 类型别名 = { 定义类型 }; - ❗ 注意:
- 类型别名的命名采用规范的 大驼峰格式;
- 类型别名 不能 重复定义;
- 代码展示:
/**
* 使用 type 关键字来创建类型别名
* 类型别名可以是任意合法的变量名称
* 创建类型别名后,直接 使用该类型别名作为变量的类型注解 即可
*/
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 2, 'a']
let arr2: CustomArray = ['x', 'y', 'z', 6, 7]
7.2 type + 交叉类型模拟继承
- 类型别名 配合 交叉类型(
&)可以 模拟 继承,同样可以实现类型复用; - 代码展示:
// 父接口 type GoodsType = { id: string; price: number; } // 子接口继承 type DIsGoodsType = GoodsType & { disPrice: number; }
八、接口
- 当一个 对象类型 被 多次使用 的时候,一般会使用 接口(interface) 来描述 对象类型,达到复用的目的;
8.1 基本使用
- 作用:
- 在
TS中使用interface接口来描述对象的数据类型(常用于给对象的属性和方法添加类型约束);
- 在
- 解决了什么问题:
- 类型的复用问题;
- ❗ 注意:
- 使用
interface关键字来声明接口; - 接口名称,可以是任意合法的变量名称;
- 声明接口后,直接使用 接口名称 作为变量的类型;
- 因为每一行只有一个属性类型,因此,属性类型后面没有
;; - 一般很少使用
object,基本都是使用interface去定义对象,更加准确; interface接口类型实际限制的是 字段名 和 字段的类型;interfacee是可以重名的;- 第一个接口后面的接口就相当于给第一个接口增加类型;
interface A { msg: string; status: number } interface A { code: number } // 现在的 A 就等于下面这样 interafce A { msg: string; status: number; code: number }
- 使用
- 代码展示:
// 定义接口 interface IPerson { name: string age: number work: string city: string sayHi(): void greet: (str: string) => void } // 使用接口 const obj: IPerson = { name: '邵秋华', age: 24, work: '中级Web前端开发工程师', city: '武汉', sayHi() {}, greet(str) {} } console.log(obj); - 接口练习:
- 下面
interface中的 分号 可以 省略;
// 后端返回到数据 /* { message: [ { image_src: "", open_type: "", goods_id: 122, navigator_url: "" } ], meta: { msg: "", status: 200 } } */ // 方式一 ✅:使用多个 interface interface BannerInfo { image_src: string; open_type: string; goods_id: number; navigator_url: string; } interface ResInfo { msg: string; status: number; } interface BannerRes { message: BannerInfo[]; meta: ResInfo } // 方式二 ❌:纯手写 interface BannerRes { message: Array<{ image_src: string; open_type: string; goods_id: number; navigator_url: string; }>; meta: { msg: string; status: number } } interface BannerRes { message: { image_src: string; open_type: string; goods_id: number; navigator_url: string; }[]; meta: { msg: string; status: number } } - 下面
8.2 接口的继承
- 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽出来,通过继承来实现复用;
- 接口的继承使用过关键字
extends; - 代码展示:
interface Meta { msg: string; status: number; } // 接口 Res1 继承 Meta interface Res1 extends Meta { success: boolean } const xiHa: Res1 = { success: true, msg: "", status: 200 }
解释:
- 使用
extends(继承) 关键字实现了接口 Res1 继承 Meta;- 继承后,Res1 就有了 Meta 的所有属性和方法;
8.3 接口的可选字段
- 概念:
- 通过
?对属性进行可选标注,赋值的时候 该属性可以缺失,如果有值必须保证类型满足要求;
- 通过
- 代码展示:
interface Res { msg: string; status: number; code: number; success?: boolean } const resInfo: Res = { msg: "", status: 200, code: 200 }
8.4 接口和类型别名的对比
- 相同点:
- 都可以 给 对象 指定类型;
- 都能实现继承的效果:
interface使用extends;type配合 交叉类型 模拟 继承;
- 不同点:
- 接口:
- 只能 为 对象 指定类型;
- 同名的接口会合并(属性取并集,不能出现类型冲突);
- 类型别名:
- 可以为 任意类型 指定 别名;
- 不能声明同名的
type(会报错);
- 接口:
-
九、对象类型
9.1 基本使用
- JS 中的 对象 是由 属性 和 方法 构成 的;
- TS 中 对象 的 类型 就是在 描述对象 结构(由什么类型的属性和方法);
- ❗ 注意:
- 直接使用
{}来描述对象结构;- 属性 采用 属性名:类型 的形式;
- 方法 采用 方法名(): 返回值类型 的形式;
- 如果 方法 有 参数,就在 方法名 后面的 小括号 中 指定参数类型(比如:
greet(name: string): void); - 在 一行代码 中 指定对象的多个属性类型 时,使用
;来分隔- 如果 一行代码 中 只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉
;; - 方法的类型 也可以使用 箭头函数形式;
- 如果 一行代码 中 只指定一个属性类型(通过换行来分隔多个属性类型),可以去掉
- 直接使用
- 对象类型的写法:
// ❌
let person: {name: string; age: number; sayHi(): void; greet?:(str: string) => void} = {
name: '邵秋华',
age: 23,
sayHi() {},
greet(str) {}
}
// 和上面的写法意义是一样的,更推荐下面这种,写起来方便很多
// ✅
let person: {
name: string
age: number
sayHi(): void
greet?:(str: string) => void
} = {
name: '邵秋华',
age: 23,
sayHi() {},
greet(str) {}
}
9.2 可选属性
- 可选属性的语法与函数可选参数的语法一致,都是用
?来表示; - 代码展示:
function myAxios(config: {url: string; method?: string}): void { console.log(config); } const myAxios1: (config: {url: string; method?: string}) => void = (config) => { console.log(config); } myAxios({ url: '哈哈嘻嘻', method: 'POST' }) myAxios1({ url: '嘻嘻哈哈' })
9.3 只读属性
- 属性名前面加上
readonly关键字即可; - 注意:
- 只读属性只能在对象初始化期间赋值,此后就不能修改该属性了(修改就会报错);
- 属性值是个基本数据类型,修改(替换)就会报错;
- 属性值是个复杂数据类型,readonly 修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该属性值;
interface TestObjType {
readonly name: string;
age: number;
readonly likeArr: string[];
readonly textOptions: {
city: string;
text: string;
}
};
// 对象初始化
const testObj: TestObjType = {
name: '嘻哈少将',
age: 25,
likeArr: ['打游戏', '看电影'],
textOptions: {
city: '北京',
text: '哈哈哈'
}
}
testObj.name = '张三'; // 报错
testObj.likeArr[0] = '躺尸';
testObj.textOptions.city = '深圳';
testObj.textOptions = {}; // 报错
十、枚举类型
- 枚举的功能类似于 字面量类型 + 联合类型 组合的功能,也可以 表示一组明确的可选值。
10.1 简介
- 枚举:
- 定义一组 命名常量;
- 使用
enum关键字定义枚举; - 他描述一个值,该值可以是这些命名常量中的一个;
enum Direction { Up, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction);
}
- 🔺 注意:
- 使用
enum关键字定义枚举; - 约定枚举名称、枚举中的值以 大写字母开头;
- 枚举中的多个值之间通过
,(逗号)分隔; - 定义好枚举后,直接使用 枚举名称 作为 类型注解;
- 使用
- 访问枚举成员:
- 类似于JS中的对象,可以通过
. / []语法访问枚举成员;
- 类似于JS中的对象,可以通过
10.2 数字枚举
- 枚举成员是有值的,默认为:从0开始递增的数值;
- 数字枚举:枚举成员 为 数字 的 枚举;
- 当然,也可以给枚举中的成员初始化值;
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 } function changeDirection(direction: Direction) { console.log(direction); } // 访问 枚举成员 changeDirection(Direction.Up); changeDirection(Direction.Down); changeDirection(Direction.Left); changeDirection(Direction.Right); - 枚举值也可以是不连续的;
enum TestEnum { a = 1, b, // 2 c, // 3 d = 10, e, // 11 }
10.3 字符串枚举
- 字符串枚举:枚举成员 的 值 是 字符串;
- 🔺 注意:
- 字符串枚举没有自增长行为,因此,字符串枚举 的 每个成员 必须有 初始值;
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
10.4 同名的枚举
- 多个同名的Enum结构会自动合并;
enum Test { A, };
enum Test { B = 1, };
enum Test { C = 3, };
enum Test { D = 5, };
// 等同于
enum Test {
A,
B,
C = 3,
D = 5,
}
- 注意:
- Enum结构合并时,只允许第一个声明 的成员省略初始值,否则会报错;
- Enum结构合并时,不能有同名成员,否则会报错;
- 同名Enum合并的另一个限制是,所有定义必须同为 const 枚举 或者 非const 枚举,不允许混合使用;
enum Test { A, };
enum Test { B, }; // 报错
// -------------
enum Test { A, B, };
enum test { B = 1, C = 2, }; // 报错:标识B重复
// -------------
// 正确
enum Test { A, };
enum Test { B = 1,};
// 正确
const enum Xi { A, };
const enum Xi { B = 1, };
// 报错
enum Ha { A, };
const enum Ha { B = 1, };
10.5 枚举的特点及原理
- 枚举是TS为数不多的非JavaScript类型级扩展(不仅仅是类型)的特征之一
- 其他类型仅仅被当作类型,而枚举不仅用做类型,还提供值(枚举成员都是有值的)
- 也就是说,其他的类型都会在编译为JS代码时自动移除,但是,枚举类型会被编译为JS代码
-
- 说明:
- 枚举与前面讲到的字面量类型+联合类型组合的功能类似,都用来表示一组明确的可选值列表
- 一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效;
十一、泛型
- 概念:
- 是指在定义接口、函数等类型的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性,使用泛型可以复用类型并且让类型更加灵活;
11.1 泛型接口、泛型别名
- 语法:
- 泛型接口:
- 在接口类型的名称后面使用
<T>既声明一个泛型参数,接口里面的其他成员都能使用该参数的类型;
- 在接口类型的名称后面使用
- 泛型别名:
- 在类型别名
type的后面使用<T>即可声明一个泛型参数,接口里的其他成员都能使用该参数的类型;
- 在类型别名
// 泛型接口 ✅ interface 类型名称<T> {} // 泛型别名 ❌ type 类型名称<T> = xxx- 一般泛型名,是一个字母,大写;
- 泛型接口:
- 通用思路:
- 找到 可变的类型部分 通过泛型
<T>抽象为泛型参数(定义参数); - 再使用泛型的时候,把 具体类型传入到泛型参数位置(传参);
- 找到 可变的类型部分 通过泛型
- 注意:
- 泛型参数可以有多个;
- 代码展示:
// 定义泛型接口 interface ResData<T> { code: number; msg: string; data: T; } // 定义具体类型 interface User { name: string; age: number; gender: string; } type UserInfo = ResData<User[]>; // 使用泛型并传入具体类型 // let userData: UserInfo = { let userData: ResData<User[]> = { code: 200, msg: "success", data: [ { name: "小Q", age: 24, gender: "女" } ] }; // 定义具体类型 interface Goods { id: number; goods_name: string; price: number; url: string; } type GoodInfo = ResData<Goods>; // 使用泛型并传入具体类型 // let goodsData: GoodsInfo = { let goodsData: ResData<Goods> = { code: 200, msg: "success", data: { id: 123, goods_name: "shuiGuo", price: 12, url: "www.baidu.com" } };
11.2 泛型函数
- 语法:
- 在 函数名称 后面使用
<T>即可声明一个泛型参数,整个函数中(参数、返回值、函数体、变量)都可以使用该参数的泛型;
function fn<T>() {} - 在 函数名称 后面使用
- 代码展示:
// 需求:设置一个函数 creatArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值(多种类型) function createArray<T>(length: number, defVal: T): T[] { const arr = []; for (let i = 0; i < length; i++) { arr[i] = defVal; } return arr; } console.log(createArray<number>(5, 100));
11.3 泛型约束
- 作用:
- 泛型的特点就是灵活不确定,有些时候泛型函数的内部需要 访问一些特定类型的数据才有的属性,此时会有类型错误,需要通过泛型约束解决;
- 使用关键字
extends实现泛型约束; - 代码展示:
// 泛型约束 interface lengthObj { length: number; } function logLen<T extends lengthObj>(obj: T) { console.log(obj.length); } // 传参的时候,必须满足 lengthObj 类型 logLen<lengthObj>({ length: 8 }); logLen<lengthObj>([1]); logLen<lengthObj>("hello ts"); logLen<lengthObj>(100); // 报错:类型 number 的参数不能赋值给 lengthObj 的参数
十二、any 类型
- 不推荐使用
any类型; - 作用:
- 变量被注解为
any类型之后,TS会忽略类型检查,错误的类型赋值不会报错,也不会有任何提示;
- 变量被注解为
- 隐式具有any类型的情况:
- 声明变量不提供类型也不提供默认值;
- 函数参数不加类型;
- 注意:
any使用的越多,程序可能出现的漏洞越多,因此不推荐使用any类型,尽量避免;
十三、类型断言
- 作用:
- 有些时候开发者比TS本身更清楚当前的类型是什么,可以使用断言(
as)让类型更加精确和具体;
- 有些时候开发者比TS本身更清楚当前的类型是什么,可以使用断言(
- 使用
as关键字实现 类型断言; - 关键字
as后面的类型是一个 更加具体的类型(HTMLAnchorElement是HTMLElement的子类型); - 通过类型断言,aLink的类型变得更加具体,这样就可以访问a标签特有的属性或方法了
const aLink = document.getElementById('link') as HTMLAnchorElement - ❌ 另一种语法
<>const aLink = <HTMLAnchorElement>document.getElemetById('link') - 代码展示:
function fix(a: number | string, b: number | string): number | string | null { if (typeof(a) === "number" && typeof(b) === "number") return a + b; if (typeof(a) === "string" && typeof(b) === "string") return a + b; return null; }- 不进行类型断言:
- 提示的是 string 和 number 共有的方法或属性;

- 将
res断言为 number:- 提示的都是和 number 相关的方法或属性;

- 将
res断言为 string:- 提示的都是和 string 相关的方法或属性;

- 将
res断言为 null:- null没有属性和方法,所以什么都不提示

- 不进行类型断言:
- 拓展:
- 查看类型
console.dir(xxx)最后面就有类型([[Prototype]]: 类型);
- 查看类型
