基础类型
原始数据类型
什么是原始数据类型?MDN中说,除Object类型外其它类型的值是不可变的,这些类型称为“原始数据类型”(原始值)。
// 1. string
let firstName: string = 'viking'
let message: string = `Hello, ${firstName}`
// 2. number
let age: number = 10
let binaryNumber: number = 0b1111
// 3. boolean
let isDone: boolean = false
// 4. undefined
let u: undefined = undefined
// 5. null
let n: null = null
注意: undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:let num: number = undefined
Array和Tuple
说完原始数据类型,来说Object 类型,第一个要接触的就是数组,应该是我们前端开发工程师最熟悉的一种数据结构,它有多种定义方法,这里我们先简介最简单的一种。好,来到代码:
let arrOfNumbers: number[] = [1, 2, 3, 4]
arrOfNumbers.push(5)
arrOfNumbers.push('abc') //数组的项中不允许出现其他的类型
有时候需要对数组的项进行限制,固定数量和类型,这时候就可以使用元组(Tuple)。
let tupleData: [number, string] = [20, 'zhangsan'];
函数
函数定义类型
函数分为命名函数和匿名函数(函数表达式)两种,可以通过参数类型和返回值类型定义函数类型。
function add(x: number, y?: number): number {
return x + y;
}
const add2 = function (x: number, y?: number): number {
return x + y;
};
// x和y表示参数类型
// y?: 表示可选参数
// ):number 表示返回值类型
函数声明类型
如果仅仅声明函数格式而不定义函数体,则可以使用函数声明类型。
const add3: (x: number, y: number) => number = add2;
补充类型
此外,ts提供了补充类型用于满足特定情况。
// 1. any: 任意类型
let notSure: any = 4
notSure = 'string'
// 2. void: 函数无返回值
function print(): void {
console.log('print message')
}
// 3. never: 函数抛出异常或永远无法结束
function error(): never {
throw new Error('error')
}
接口
接口(interface)用于定义对象类型,描述对象的形状。通过interface,我们可以自定义任何类型和数据结构。
基本使用
interface Person {
name: string;
age: number;
}
let viking: Person = {
name: 'viking',
age: 20,
};
可选属性、只读属性、任意属性
interface Person {
readonly id: number, // 只读属性
name: string;
age?: number; // 可选属性
[propName: string]: any; // 任意属性,可以自由添加自定义属性
}
let viking: Person = {
id: 10,
name: 'viking',
hobbies: ['basketball']
};
viking.id = 20 // 报错,只读属性不能修改
函数对象
interface可以描述函数,因为函数也是对象。interface描述函数的场景往往出现在函数上除了函数体本身还有其它属性。
interface ISum {
(x: number, y: number): number;
displayName: string;
}
const isum: ISum = function (x, y) {
return x + y;
};
isum.displayName = 'isum';
鸭子类型
interface只关注对象的形式,不关注具体赋值对象的实际类型,即所谓鸭子类型(duck typing)。如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。
interface Person {
name: string;
age: number;
}
interface Duck {
name: string;
age: number;
}
const duck: Duck = {
name: 'Peking Duck',
age: 10,
};
const person: Person = duck; // Duck和Person的形状一样,所以Duck类型可以赋值给Person类型
接口继承
接口之间可以通过继承的方式复用已经定义好的属性类型。一个接口可以继承多个接口,创建出多个接口的合成接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square: Square = {
color: 'blue',
penWidth: 5.0,
sideLength: 10,
};
泛型
为什么会有泛型?
考虑这样一种场景,函数中返回值的类型要和参数的类型相同,且参数的类型可能有多种,无法预先指定。
function echo(arg) {
return arg;
}
const result = echo(123); // 此时result的类型为any
在这种类型无法预先执行且存在类型依赖的场景下,就需要一个类型占位符来表示类型,在使用的时候进行类型赋值,这个占位符就是泛型。
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
上面的场景可以这样改造:
function echo<T>(arg: T): T {
return arg;
}
const result = echo<number>(123); // 此时result类型为number
多个值的泛型
泛型就是占位符,可以传一个就可以传多个。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const result = swap<string, number>(['str', 123]) // 此时result类型为[number, string]
泛型约束
泛型是个占位符,我们如果要求这个占位符必须包含某些属性,就可以对泛型进行约束。
interface IWithLength {
length: number;
}
// 使用extends关键字对泛型进行约束
function echo<T extends IWithLength>(arg: T): T {
console.log(arg.length);
return arg;
}
const res1 = echo<number[]>([1, 2, 3]);
const res2 = echo<string>('123');
const res3 = echo({ length: 10, msg: 'hello' });
extends中还有一个条件类型约束,格式为:T extends U ? X : Y
,意思是:如果T满足U的约束,则取X类型,不满足则取Y类型。条件类型类似三元表达式,可以基于泛型定义新的类型。
// 如果泛型参数 T 为 null 或 undefined,那么取 never,否则直接返回T。
type NonType<T> = T extends null | undefined ? never : T;
let demo1: NonType<number>; // => number
let demo2: NonType<string>; // => string
let demo3: NonType<null>; // => never
高级特性
字面量类型
如果我们使用const且不声明类型直接赋值,那么类型推断就会将变量推断为相应的字面量类型。
const firstName = 'viking'; // 'viking'类型
const age = 20; // 20类型
字面量类型通常用于设置一些可选值,类似枚举的功能。
type MethodType = 'GET' | 'POST' | 'PUT' | 'DELETE'
const method:MethodType = 'DELETE'
类型断言
当我们明确知道变量是什么类型时,就可以使用类型断言告诉TS不要在检查了,按我说的做。
function getLength(input: number | string) {
const str = input as string;
if (str.length) {
return str.length;
} else {
const number = input as number;
return number.toString().length;
}
}
联合类型和交叉类型
交叉类型表示需要同时满足多个类型,即”AND“关系;联合类型表示满足任意一个类型即可,即”OR“关系。
interface Person {
name: string;
age: number;
}
type Student = Person & { stuId: string }; // 交叉类型
const stu:Student = {
name: 'zhangsan',
age: 20,
stuId: '101001'
}
function getLength(input: number | string) {... // 联合类型
类型别名
对于函数类型,交叉类型,联合类型等,类型描述都比较长,此时就可以给一个类型别名,方便使用。
// type关键字定义类型别名
type Student = Person & { stuId: string };
type MethodType = 'GET' | 'POST' | 'PUT' | 'DELETE';
keyof和in
keyof可以获取类型成员键名组成的联合类型,而in可以对联合类型关键字进行遍历。
interface Person {
name: string;
age: number;
}
type Keys = keyof Person; // 'name'|'age'
type Student = {
[key in Keys]: Person[key]; // Person[key]用于获取类型的成员类型(lookup types)
}
TS提供了一个内置类型Partial
,可以将任意类型的属性转换为可选属性,基本原理就是使用了keyof和in。
interface Person {
name: string;
age: number;
}
type OptionalPerson = Partial<Person>
const p:OptionalPerson = {name: 'zhangsan'} // name和age都是可选属性
// Partial的实现原理
type Partial<T> = {
[P in keyof T]?: T[P];
};