这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
本节课所学
- TypeScript 及其发展历史
- TypeScript 基本语法
TypeScript
发展历史
- 2012-10 :微软发布了 TypeScript 第一个版本(0.8)
- 2014-10 :Angular 发布了基于 TypeScript 的2.0版本
- 2015-04 :微软发布了 VS Code
- 2016-05 :@types/react 发布,TypeScript 可开发 react
- 2020-09 :Vue 发布了 3.0 版本,官方支持 TypeScript
- 2021-11 :v4.5版本发布
为什么是 TypeScript
TypeScript 是 Javascript 的语法 超集,它添加了静态类型,优点在于可读性增强(基于语法解析TSDoc,ide 增强),以及可维护性增强(在编译阶段暴露大部分错误,在多人合作的大项目中,获得更好的稳定性和开发效率)。
所谓 超集 就是包含于兼容所有 JS 特性,支持共存,支持渐进式引入与升级。
基本语法
TypeScript 区别于其他静态类型语言,它使用 类型后置 声明变量的类型。
一、基础数据类型
- 字符串
const str: string = 'string'; - 数字
const num: number = 1; - 布尔
const bol: boolean = true; - null
const n: null = null; - undefined
const un: undefined = undefined;
二、引用类型
2.1 array
- 数组元素只有一种类型
- type[]
let arr: number[] = [1,2,3,4,5];- Array<>
let arr: Array<number> = [1,2,3,4,5]; - 若数组有多种类型的元素:
- 使用 | 联合类型
let arr: (number | string)[] = [1,2,3,4,'a','b']; - 二维数组
let arr: number[][] = [[10],[20]] let arr2: Array<Array<number>> = [[10],[20]]
2.2 object
指定对象中属性和属性值的类型
interface objI {
name: string,
age: number,
fn: (a: string) => string
}
let person: objI = {
name: 'aaa',
age: 20,
fn(a){
return a
}
}
2.2.1 可选属性
某个对象中的某个属性可有可无,此时定义类型时可以在属性名后面使用 ? 表示该属性为可选属性。
interface IPerson {
name: string
age: number
hobby?: string
}
let p1: IPerson = {
name: 'aa',
age: 20,
hobby: 'sing'
}
let p2: IPerson = {
name: 'bb',
age: 19,
}
2.2.2 索引签名
有时候对象中的某些属性不会 提前 知道,但是却知道值的类型,此时可以使用 索引签名 描述可能值的类型
interface IReq {
id: string
src: string
author: string
[propName: string] : any
}
let r: IReq = {
id: '1',
src: './*/',
author: 'jack',
imgSrc: './img1.png',
fn: () => {}
}
2.3 function
对函数的
输入和输出声明类型
-
function 关键字定义函数 :
function test(str1: string, str2: string): string { return str1 + str2 } -
const 关键字定义函数
let fn1: (str1: string, str2: string) => string = (str1,str2) => { return str1 + str2 }
2.3.1 可选参数、默认参数、剩余参数
-
可选参数
在声明函数时,可以通过
?来定义可选参数,比如age?: number这种形式。可选参数必须要放在普通参数的后面,不然会导致编译错误。function userInfo(name: string, id: number, age?: number): string { return name + id; } -
默认参数
同
Js一样可以对函数的参数设置默认值,在类型后直接复制即可// 为 name 设置默认值 function userInfo(name: string = "jack", id: number, age?: number): string { return name + id; } -
剩余参数
在参数最后一位使用
...,并且声明类型function add(x: number, ...items: number[]): number { let num = x; items.forEach((item) => { num += item; }); return num; }
2.3.2 函数重载
函数有不同类型的
参数,根据不同的参数返回不同类型的结果。为解决这个问题就要为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据函数的调用去列表中进行匹配并处理。
-
重载签名
函数调用时根据所传参数的类型匹配不同的参数类型的函数
-
实现签名
函数调用类型匹配后的具体逻辑实现
// 重载签名 function test(x: number): number function test(x: string): string // 实现签名 function test(x: number | string): number| string | void{ ... return ... } // 函数调用 test(10); test('aa');
三、TS 额外提供的类型
3.1 Enum 枚举类型
使用枚举类型可以定义一些
带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举
// 定义枚举时,如果常量不赋初值,默认从 0 开始递增,即 NORTH = 0,SOUTH = 1...
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
// 一般使用枚举都会赋初值
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
3.2 any
表示任何类型,当一个变量声明为
any类型,任何类型的值都可以赋给它,它也可以赋值给任何类型的变量。并且any类型的值可以执行任何操作而无需事先执行任何形式的检查。使用any就会关闭类型检查,因此尽量不使用。
// a 为 any 表示任何类型的值都可传入,但会关闭类型检查,编译期间不会判断出类型错误
fucntion fn(a: any): void {}
3.3 unknown
同
any一样表示任何类型,任何类型的值都可以赋给它。但比any更安全,因为unknown类型的值不能赋给其他类型的变量。
3.3.1 any 和 unknown 的区别
- 任何类型的值都可以赋值给
any类型,any类型也可以赋值给任何其他类型(除never等个别类型)。 - 任何类型的值同样可以赋值给
unknown类型,但unknown类型的值能被赋值给any类型和unknown类型本身 any类型的值在编译期间可以执行任何操作而不做检查,unknown类型禁止做任何操作,因为不知道会赋什么样的值,对未知的变量做某一具体的操作是错误的。
3.4 Tuple 元组类型
另一种类型的数组,可以指定数组中元素的个数以及每个元素的类型
type CustomTuple = [string, number]
let t1: CustomTuple = ['1' ,1]
t1[2] = 1; // 报错,因为 CustomTuple 只有两个索引,0 和 1,但是可以通过 push 添加元素,且必须是元组的某个类型
let t2: CustomTuple = ['1' ,1, 2] // 报错,因为 CustomTuple 指定了数组只有两个元素
3.5 void
在
JS中没有显式的使用return声明返回值的函数,默认返回值undefined,TS中不应该是undefined,应该显式的声明返回值的类型为void
// 函数 fn 没有任何返回值
type Fn = () => void
let fn: Fn = () => {}
3.6 字面量类型
当变量是某几个具体的字符串或数字中的一个时,可以对变量声明字面量类型。常搭配联合类型使用,限定
变量(变量限定为某一个值用处不大,一般不会对变量使用字面量类型) 或函数参数的值
// 对变量使用字面量类型,用的比较少
type strType = 'a' | 'b' | 'c'
let str1: strType = 'a'
let str2: strType = 'b'
let str3: strType = 'c'
let str4: strType = 'd' // 报错,'d' 不能分配给类型 strType,因为 strType 已经限定为 'a' 'b' 'c' 中的某一个
// 对函数参数使用字面量类型
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre"); // 报错,centre 不能分配给类型 "left" | "right" | "center"
3.7 联合类型
某个变量可以有多个类型时,可以使用联合类型,使用
|进行类型拼接。
// x 既可以是 number 类型,也可以是 string 类型
function fn(x: number | string, y: boolean): void{}
四、类型别名 & 接口
4.1 类型别名
当某个变量的类型声明过长或多个变量使用相同的类型,这时可以使用
类型别名为类型起一个名字
type Message = string | string[];
let greet = (message: Message) => {
// ...
}
4.2 接口
既可用于对象的描述,也可用于对类的的行为抽象
interface IPoint {
x: number;
y: number
}
let p: IPoint = {
x: 10,
y: 10
}
4.3 interface 与 type 的异同
- 都可以用来描述对象 / 函数类型
interface IPoint { x: number y: number } interface IFn { (x: number, y: number) : number } let p1: IPoint = { x: 10, y: 10 } let fn1: IFn = (x, y) => x type Point = { x: number y: number } type Fn = (x: number, y: number) => number let p2: Point = { x: 10, y: 10 } let fn2: Fn = (x, y) => x interface用来定义对象/函数,type可以用来定义原始类型、联合类型、元组类型等等type CustomNum = number | string type CunstomTuple = [string, number] let n: CustomNum = 10; let t: CunstomTuple = ['a', 10]interface允许定义多个同名接口,且默认合并为一个,type 不允许定义多个同名类型别名interface IPerson { name: string } interface IPerson { age: number } interface IPerson { sex: string } // 默认合并为一个 // interface IPerson { // name: string // age: number // sex: string // } // 如果声明多个同名函数接口,合并后会当做同一函数的重载 interface IPoint { (x:number, y:number, z?:number) : number | void } interface IPoint { (x:string, y:string) : string | void } interface IPoint { (x:boolean, y:boolean) : boolean | void } // 合并 // interface IPoint { // // 同名的函数会当做同一函数的重载 // (x:boolean, y:boolean, z?:number) : boolean | void // (x:string, y:string) : string | void // (x:number, y:number) : number | void // }- 继承 / 拓展方式不同
// interface 通过 extends 关键字继承/拓展,且只能使用一次 extends interface IPonitX { x: number } interface IPonitY extends IPonitX { y: number } let p1: IPonitY = { x: 10, y: 10 } // type 通过 & 拓展,且可以使用多次 type PointX = { x: number } type PointY = PointX & { y: number } let p2: PointY = { x: 10, y: 10 }
五、泛型
声明时不指定类型,只在调用时指定类型,允许同一个函数接收不同类型参数的一种模板,相比较于
any,泛型的可复用性更好。
5.1 泛型的使用
泛型可以说是提供
可扩展性,变量不会只拘泥于一种类型
- 函数使用泛型
// 函数调用时使用 <> 传递 类型参数,会自动的向参数以及返回值传递类型 function fn1 <T>(param: T): T{} function fn2 <T, U>(param: T, mass: U): T{} fn1<string>(); fn2<string, number>(); - 泛型接口
interface IInfo<T> { data: T } let info: IInfo<string> = { data: 'aa' } - 泛型类型别名
type Point<T> = { x?: T y?: T } let p: Point<number> = { x: 10 } - 泛型类
class - 泛型默认参数 : 同默认值一样,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。
function fn1 <T = string>(param: T): T{}
5.2 泛型变量
任何的
大写字母都可以作为泛型变量,为了规范会使用一些特定的字母:T、K...
- T(Type):表示一个 TypeScript 类型
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素类型
- ......
5.3 泛型会用到的操作符
5.3.1 keyof 操作符
keyof某个对象类型的声明,并结合声明中的键,返回联合类型,如果类型中有索引签名,会返回索引签名的类型
type Point = { x: number; y: number };
type P = keyof Point // 此时 type P = 'x' | 'y'
let imp1: P = 'x';
let imp2: P = 'y';
type CustomPoint = {
x: number;
y: number;
[key: string]: any
}
type P2 = keyof CustomPoint // 此时 type P2 = 'x' | 'y' | string
let imp3: P2 = 'x'
let imp4: P2 = 'y'
let imp5: P2 = 'a'
5.3.2 typeof 操作符
引用某个变量或属性的类型,类似于使用
typeof取出某个变量或属性的类型,转而赋给另一个变量或属性,可以理解为一个取值赋值(取的值是类型)操作
type Point = { x: number; y: number };
let o: Point = {
x: 10,
y: 10
}
type CustomPoint = typeof o & {
[key: string]: any
}
5.3.3 in 操作符
用于遍历联合类型,相当于一个
循环赋值操作
type CustomKeys = 'data' | 'methods' | 'computed' | 'watch'
type CustomOptions = {
[p in CustomKeys] : any
}
// CustomOptions = {
// data: any;
// methods: any;
// computed: any;
// watch: any;
// }
5.3.4 [ ] 索引访问
索引的方式查找属性的类型。类似于数组索引取值,TS取的是值的类型。
type Test = {name: string;age: number;}
type TestItem1 = Test['name'] // 取 Test 中 name 的类型,也就是 string
let t1: TestItem = 'aa'
// 也可以联合 keyof 或其他类型使用
type TestItem2 = Test[keyof Test]; // 取 Test 中 name 和 age 的属性,也就是 string | number
let t2: TestItem2 = "n";
let t3: TestItem2 = 10;
5.4 泛型约束
限制类型变量所接受的类型数量,比如
T只接受string和数组类型
function identity <T> (param: T): T{
console.log(param.length); // 报错,T 不存在 length 属性,此时就需要为 T 做约束
return param;
}
// 使用 extends 关键字对 T 进行约束
type Length = {
length: number
}
// T extends Length 表示传入给 T 的类型必须要有 Length 中的属性,否则报错
function identity <T extends Length> (param: T): T{
console.log(param.length);
return param;
}
总结
通过本节课学习,学到了 TypeScript 在开发中的各种类型的声明和使用方式,了解了在大项目的开发中 TypeScript 所体现的优势(对变量进行约束,在代码编译期间就能暴露一些声明或语法错误)。