基础数据类型
JS的八种内置类型
let str: string = 'abc';
let num: number = 123;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
let sym: symbol = Symbol('me');
注:默认情况下,null和undefined是所有类型的子类型,可以赋值给其他类型。
如果tsconfig.json中指定 “strictNullChecks”: true,则null和undefined只能赋值给void。
Array
两种定义定义(声明字符串数组)
let arr: string[] = ['a', 'b'];
let arr2: Array<string> = ['a', 'b']
定义联合类型数组
let arr:(number | string)[]; // 既可以存储数字,也可以存储字符串
定义指定对象成员的数组
interface Arrobj{
name: string,
age: number
}
let arr: Arrobj[] = [{name: 'John', age: 22}]
Tuple(元祖)
数组一般值类型相同,当需要数组存储不同类型值时,可以通过元祖指定元素个数和类型。这是TS特有的类型,工作方式类似于数组。
定义元祖
let x: [string, number] = ['abc', 123]
元祖类型的结构赋值
let [str, id] = x
console.log(str, id) // ’abc', 1
元祖类型的可选元素
let optionTuple: [string, boolean?]; // 以 ? 结尾的元素为可选元素
optionTuple = ['xxx'] // 赋值时可选元素可以不赋值
元祖类型的剩余元素
// 接受第一个元素number类型外,其余参数为string类型
let restTuple: [number, ...string[]];
restTuple = [666, 'xxx', 'aaa', 'ccc']
只读类型元祖
let readOnlyTuple: readonly[number, string] = [10, 'xxx'] // 进行修改操作会抛出异常
void
void表示没有任何类型,和其它类型是平等关系,不能直接赋值
let a: void;
let b: number = a; // Error
never
never表示永不存在的值的类型。
两种值永不存在的情况:
- 如果一个函数执行时抛出了异常,那这个函数永远不存在返回值。
- 函数中无限循环的代码,永远不存在返回。
注:never类型同null 和 undefined 一样,也是任何类型的子类型,可以赋值给任何类型。但是没有类型是never子类型,没有值可以赋值给never,never只能是作为类型使用。
any
在TS中,任何类型都可以被归为any类型,这让any类型成为了类型系统的顶级类型。
普通类型赋值有类型限定,any类型赋值可以被赋值为任意类型。any类型的数据访问任何属性,调用任何方法都是被允许的。
声明变量时未指定类型时,会被识别为any类型。
注:为了开发规范,尽量不要用any。
unknown
为了解决any问题,TypeScript3.0引入了unknown类型。它与any一样,所有类型都可以分配给unknown。
unknown与any的最大区别:任何类型的值都可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown则任何类型值都可以赋值给它,但unknown只能赋值给unknown和any。
function getDog() {
retrun 'bobi';
}
const dog: unknown = {hello: getDog};
dog.hello(); // Error unknown不缩小类型的情况下,无法执行任何操作
这种机制起到了预防性,要求我么必须缩小类型,可以使用typeof、类型断言等方式来缩小未知范围。
function getDogName() {
let x: unknown;
return x;
}
const dogName = getDogName()
// 直接使用
const upName = dogName.toLowerCase(); //Error 无法通过编译
// typeof
if(typeof dogName === 'string') {
const cuName = dogName.toLowerCase();
}
// 类型断言
const crName = (dogName as string).toLowerCase();
Number、String、Boolean、Symbol
小写字母开头的 number、string、boolean、symbol 是原始类型;
首字母大写的类型是相应原始类型的包装对象,即对象类型。
注:原始类型兼容对应的对象类型,对象类型不兼容对应的原始类型。铭记不要使用对象类型来注解值的类型。
Function
函数声明
function sum(x: number, y: number): number {
return x + y;
}
函数表达式
let mySum: (x: number, y: number) => number = function(x: number, y: number): number {
return x + y;
}
使用接口定义函数
interface SearchFunc {
(source: string, subString: string): boolean;
}
let searchStr: SearchFunc = (source, str) {
return source.indexOf(str) > -1
}
可选参数、参数默认值、剩余参数
function buildInfo(firstName: string, lastName: string = 'zhang', sex?: number, ...otherInfo: any[]) {
console.log(lastName + '' + firstName, sex ? '男同学': '女同学', '是' + otherInfo.join(''))
}
buildInfo('san', undefined, 1, '高三', '理科生') // zhangsan男同学是高三理科生
函数重载
通常函数会根据不同类型参数返回不同类型结果,当TS开启 noImplicitAny 配置项时,会提示参数具有隐式any类型,需要对不同类型做不同处理。
function add(x: number, y: number): number;
function add(x: string, y: string): string;
type Types = number | string
function add(x: Types, y: Types) {
if(typeof x === 'string') {
return x.toString() + y.toString()
}
return x + y;
}
类型推断
为了方便开发中不需要对基础类型进行类型注解,TS在很多情况下,会根据上下文环境自动推断出变量的类型。const类型推断会被推断为[字面量类型](# 字面量类型)
let str = 'this is string'; // 等价于 let string: string = 'this is string'
// 需要注意const定义的变量
const str = 'this is string'; // 此时推断为字面量类型'this is string',不等价于 const str: string = 'this is string'
在TS中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断为any类型而完全不被类型检查:
let num;
num = 'seven';
num = 7;
类型断言
有时会有部分情况,有一个实体具有比它现有类型更确切的类型,我们需要直接以指定类型处理这个实体,来通过编译。类似于其他语言的类型转换,只在编译阶段起作用。
const arrayNumber: number[] = [1,2,3,4];
// 直接返回大于2的结果,有可能是undefined,会造成编译错误
const findThan2: number = arrayNumber.find(num => num > 2);
// 使用类型断言,此时可以肯定结果是有大于3的值
const findThan3: number = arrayNumber.find(num => num > 3) as number;
类型断言语法
// 尖括号
let someValue: any = 'this is string';
let strLength: number = (<string>someValue).length;
// as语法
let strLength: number = (someValue as string).length;
注:推荐使用as语法,尖括号语法格式会与react中JSX语法冲突。
非空断言
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 “!” 可以用于断言操作对象是非null和非undefined类型。即 x! 将从 x 值域中排除null和undefined。
let str: null | undefined || string;
str!.toString(); // str需要排除null和undefined类型后剩余string类型才可以调用toString方法
确定赋值断言
允许在实例属性和变量声明后放置一个“!”(let x!: number),从而告诉TS该属性会被明确赋值。编译阶段不会报“赋值前被调用“的错误。
let x: number;
initialize();
console.log(2 * x); // Error 赋值前就被调用
// 使用确定赋值断言
let x!: number;
initialize();
console.log(2 * x); // 20
function initialize() {
x = 10
}
字面量类型
在TS中,字面量不仅可以表示值,还可以表示类型(字符串字面量类型、数字字面量类型、布尔字面量类型对应的字符串字面量、数字字面量、布尔字面量拥有与其值一样的字面量类型)。
let str: 'this is string' = 'this is string';
let num: 1 = 1;
let bool: true = true;
注:字面量类型时,'this is string' 类型是string类型的子类型,而string类型不一定是 'this is string'类型。其他字面量类型一样。
使用:字面量类型可以用于定义指定值的变量中。由字面量类型组成的联合类型可以用于值的范围限制。如 type status: 'open' | 'close'。
let 和 const 分析
const定义变量缺类型注解的情况下,TS会推断为字面量类型。
let 定义变量缺类型注解的情况下,TS会推断为字面量类型的父类型。
类型拓宽(Type Widening)
所有通过let或var定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显示添加类型注解的条件,那它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。
除了字面量类型拓宽,TS也对某些特定类型值也有类似“类型拓宽”的设计:
对null和undefined的类型进行拓宽,通过let、var定义的变量如果满足未显示声明类型注解且被赋予了 null 或undefined 值,则推断出这些变量的类型是any。
let x = null; // 类型拓宽成any
let y = undefined; // 类型拓宽成any
let anyFun = (param = null) => param; // 形参类型是null | undefined
对于默认的类型拓宽,可以通过添加显示类型注释、使用const断言等方式来推断出最窄的类型,没有拓宽。
const arr1 = [1,2,3]; // Type is number[]
const arr2 = [1,2,3] as const; // Type is readonly [1, 2, 3]
类型缩小(Type Narrowing)
有类型拓宽,就有类型缩小。通过某些操作符将变量的类型有一个较为宽泛的集合缩小到相对较小、较明确的集合。
简单示例:
// 使用类型守卫将函数参数的类型从any缩小到明确的类型
let func = (val: any) => {
if(typeof val === 'string') {
return val; // 类型是string
}else if(typeof val === 'number') {
return val; // 类型是number
}
return null;
}
// 使用类型守卫将联合类型缩小到明确的子类型
let func = (val: string | number) => {
if(typeof val === 'string') {
return val; // 类型是string
}else{
return val; // 类型是number
}
}
注:分支排除法,如联合类型排除null(typeof val === 'object') 排除null是错误的,需要注意。布尔值取反判断等特殊值雷同。
联合类型
联合类型表示取值可以为多种类型中的一种,使用 | 分隔每个类型。
联合类型通常与null、undefined一起使用,如函数参数str: string | undefined,意味着该形参可以接受undefined。
类型别名
用于给一个类型起个新名字,常用于联合类型。
type Message = string | string[]
交叉类型
将多个类型合并为一个类型。常用于将多个接口合并为一个类型,限制指定数据满足多个接口类型。
type IntersectionType = {id: number, name: string } & {age: number};
const mixed: IntersectionType = {
id: 1,
name: 'name',
age: 18
}
注:合并时可能出现同名属性,会出现两种情况:
- 属性类型不兼容,合并后该属性的类型就是多个同名属性类型的交叉类型。如都有name属性,一个接口为string类型,一个接口为number类型,合并后name属性就是string & number,也就是never类型,此时对name赋值会报错,不定义name属性,则会提示缺少属性name。
- 属性类型兼容,合并后该属性的类型就是多个同名属性类型的交叉类型的子类型。如都有age属性,一个为number类型,一个为字面量类型2,合并后age属性为字面量类型2,此时age只能赋值为2,。
接口(Interfaces)
用于定义对象的类型,一般首字母大写,且定义的变量跟接口的属性要保持一致,即赋值的时候,变量的形状必须和接口的形状保持一致。
可选 | 只读属性
可选属性使用 ? 标识符结尾,如 name?: string。
只读属性只能在对象刚刚创建时修改其值,readonly name: string。
任意属性
除必选属性外,还可以支持其他属性,如接受一些带限制的扩展属性,key为string类型,值为any类型的属性。
interface Person {
name: string,
age: number,
[propName: string]: any; // 索引签名的形式实现属性扩展
}
注:定义任意属性后,确定属性和可选属性的类型都必须是它的类型的子集。如果任意属性的类型不包含其他定义属性(包括可选属性)的类型,则会报错。
绕开额外属性检查的方式
当对变量赋值,或是传递参数时,可能数据不仅满足类型的数据,还有多余数据时,可能会触发类型检查。
-
鸭式辨型法
对变量直接赋值时,会进行类型检查,而通过变量给变量赋值的形式,则是会进行类型兼容判断,而鸭式辨型法的判断,只要具有相同属性,就会被认为两个类型相同,可以进行赋值操作,避开类型检查。
interface LabeledValue { label: string } function printLabel(labelObj: labeledValue) { console.log(labelObj.label); } let myObj = {size: 10, label: 'Size 10 Object'}; printLabel(myObj); // OK printLabel({size: 10, label: 'Size 10 Object'}); // Error,形参类型检查不匹配
-
类型断言
类型断言的意义等同于告诉程序,以指定类型对待数据,不会进行额外的属性检查。
interface Props{ name: string } let myProps: Props = { name: 'xxx', age:18 } as Props;
-
索引签名
可以直接接收多余的属性。(接口任意属性)
接口与类型别名的区别
大多数情况下效果等价,某些特定场景下会有很大区别。
interface、type两者都可以用来描述对象或函数的类型,但是语法不通。
Interface
interface Point {
x: number,
y: number
}
interface setPoint{
(x: number, y: number): void
}
Type
type point = {
x: number,
y: number
}
type setPoint = (x: number, y: number) => void
与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元祖。
// 原始值
type Name = String;
// object
type PartialPointX = {x: number};
type PartialPointY = {y: number};
// 联合类型
type PartialPoint = PartialPointX | PartialPointY;
// 元祖
type Data = [number, string];
// dom
let div = document.createElement('div');
type B = typeof div;
注:接口可以定义多次,类型别名不可以。多次定义同名接口,会自动合并为单个接口。
扩展
接口扩展接口,可以通过extends继承,InterfaceA extends InterfaceB
类型别名拓展类型别名,可以通过 &,TypeA & TypeB
接口扩展类型别名,可以通过 extends,InterfaceA extends TypeB
类型别名拓展接口,可以通过 &, TypeA & InterfaceB
泛型
当一个变量可以是任意类型,可以定义为any类型,但是any类型弊端较大,列举所有类型情况也不现实,就可以通过泛型来实现。
实现一个函数,接受任意类型,返回接收的参数
function identity<T>(arg: T): T { // T为抽象类型,只有调用时才能确定值
return arg;
}
T代表Type,在定义泛型时通常用作第一个类型变量名称,实际上可以用任意名称代替,通常用T表示而已。
常见泛型变量代表的意思:
- K(key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element): 表示元素类型;
定义泛型时<>中可以放置多个类型,用于扩展灵活性
function identity<T, U>(value: T, message: U): T{ // 调用时传入了两个类型,可以一一对应
return value;
}
identity<number, string>(68, 'message');
泛型约束
当我们使用泛型而不对其进行任何约束时TS是会报错的,因为此时的泛型理论上是任何类型,不同于any,直接访问属性或调用方法会报错,除非访问的都是所有集合公有的。
对此,需要对泛型进行约束,可以用extends关键字实现。
interface Sizeable{
size: number
}
function trace<T extends Sizeable>(arg: T): T {
console.log(arg.size);
return arg;
}
使用类型继承,而不直接定义形参的类型,是为了可以接收更多的类型,如果给形参定好了类型,传入别的类型时会丢失类型。
泛型工具类型
TS内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record和ReturnType等。
Partial:将类型的属性变成可选;
Required:将类型的属性变成必选;
Readonly:将类型的属性变成只读;
Record:将指定类型的属性的值转换为另一个指定类型;
ReturnType:用来得到一个函数的返回值类型;
Exclude:将某个类型中属于另一个类型移除掉;
Extract:从指定类型中提取出另一个类型拥有的属性类型;
Omit:从一个类型中提取出指定类型不存在的属性,构造一个新的类型;
Pick:从某个类型中挑选一些属性出来;
NonNullable:过滤类型中的null及undefined类型;
Parameters:拥有获取函数的参数类型组成的元祖类型;
操作符
typeof:获取变量或者属性的类型;
keyof:用于获取某种类型的所有键,其返回类型是联合类型;
in:用来遍历枚举类型;
infer:声明一个类型变量,存储自动推断的结果,infer只能用于extends语句中;