typescript类型基础
枚举类型
enum Season {
Spring,
Summer,
Fall,
Winter
}
数值型枚举
enum Direction {
Up,
Down,
Left,
Right
}
const direction:Direction = Direction.up
每个数值型枚举成员都表示一个具体的数字,如果在定义一个枚举的时候没有设置枚举成员的值,Typescript会自动计算每个枚举成员的值。第一个枚举成员的值为0,后续++
enum Direction {
Up = 1, // 1
Down, // 2
Left = 10, // 10
Right // 11
}
数值型枚举是number类型的子类型,允许将数值型枚举赋值给number类型。
const direction: number = Direction.up
Number类型也能够赋值给枚举类型,即使number类型的值不在枚举成员值的列表中也不会产生错误
enum Direction {
Up,
Down,
Left,
Right
}
const d1: Direction = 0
const d2: Direction = 10 // 不会产生错误
字符串枚举
字符串枚举成员必须使用字符串字面量或者另一个字符串枚举成隐患来初始化。
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
U = Up,
D = Down
L = Left
R = Right
}
字符串枚举是string类型的子类型,因此允许将字符串枚举类型赋值给string类型。下例中常量direction为string类型
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
const direction: string = Direction.Up
不允许将string类型赋值给字符串枚举类型
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
const direction: Direction = 'UP' // 编译错误,不能将类型up赋值给direction
异构型枚举
异构型枚举: TypeScript允许在一个枚举中同时定义数值类型枚举成员和字符串枚举成员。
enum Color {
Black = 0,
White = 'White'
}
不允许使用计算的值作为枚举成员的初始值
enum Color {
Black = 0 + 0 // 不允许使用计算的值作为枚举成员的初始值
}
在异构型枚举中,必须为在字符串枚举成员之后的数值型枚举成员指定一个初始值。
enum ColorA {
Black,
White = 'white'
}
enum ColorB {
White = 'white'
Black // 枚举成员必须有一个初始值
}
枚举成员映射
任何类型的枚举,都可以通过枚举成员名来访问枚举成员。
enum Bool {
False = 0,
True = 1
}
Bool.False ;
Bool.True
Bool[Bool.False] // 'False'
Bool[Bool.True] // 'True'
常量枚举成员和计算枚举成员
常量枚举成员
若枚举类型的第一个枚举成员没有定义初始值。枚举成员是常量枚举成员并且初始值为0
若枚举成员没有定义初始值并且和他相邻的成员是常量枚举成员并且初始值为紧邻的前一个枚举成员值加一,如果紧邻的前一个枚举成员的值不是数值型常量,就会产生错误
enum Foo {
A,
B
}
enum Bar {
c = 'c',
d // 编译错误
}
- 常量枚举表达式可以是数字字面量,字符串字面量和不包含替换值的模板字面量
- 常量枚举表达式可以是对前面定义的常量枚举成员的引用
- 常量枚举表达式可以是分组运算符包围起来的常量枚举表达式
- 常量枚举表达式中可以使用一元运算符,操作数必须为常量枚举表达式
- 常量枚举表达式可以使用二元运算符,两个操作数必须是常量枚举表达式
enum Foo {
A = 0,
B = `B`, // 无替换的模板字面量
C = 'C' // 字符串字面量
D = A // 引用前面定义的常量枚举成员
}
enum Bar {
A = -1 // 一元运算符
B = 1 + 2// 二元运算符
C = (4 / 2) * 3 // 分组运算符
}
字面量枚举成员[常量枚举成员的子集]
- 枚举成员没有定义初始值
- 枚举成员的初始值为数字字面量、字符串字面量和不包含替换值
的模板字面量 - 枚举成员的初始值为对其他字面量枚举成员的引用。
联合枚举类型
当枚举类型中的所有成员都是字面量枚举成员的时候,枚举类型为联合枚举类型
联合枚举成员类型
联合枚举类型中的枚举成员除了能够表示一个常量值外,还能表示一种类型,即联合枚举成员类型
enum Direction {
Up,
Down,
Left,
Right
}
const up: Direction.Up = Direction.Up
联合枚举成员类型是联合枚举类型的子类型,因此可以将联合枚举类型成员赋值给联合枚举类型
enum Direcition {
Up,
Down,
Left,
Right
}
const up: Direction.Up = Direction.Up
const direction: Direction = up
联合枚举类型
联合枚举类型是所有联合枚举成员构成的联合类型
enum Direction {
Up,
Down,
Left,
Right
}
type UnionDirectionType =
| Direction.Up
| Direction.Down
| Direction.Left
| Direction.Right
Direction枚举是联合枚举类型,等同于联合类型UnionDirectionType,其中|符号是定义联合类型的语法。由于联合枚举类型是由固定数量的联合枚举成员类型构成的联合类型
联合枚举类型是由固定数量的联合枚举成员类型构成的联合类型,编辑器可以通过该性质对代码进行检查。
const枚举类型
枚举类型是typescript对JavaScript的拓展
enum Direction {
Up,
Down,
Left,
Right
}
const d: Direction = Direction.Up
// javascript 这种情况下有枚举成员值映射到枚举成员名的反向映射
"use strict"
var Direction
(funciton (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}))
const d = Direction.Up
// JavaScript没有映射的情况
"use strict"
var Direction
(function (Direction) {
Direction["Up"] = 0
Direction["Down"] = 1
Direction["Left"] = 2
Direction["Right"] = 3
})(Direction || (Direction = {}))
完全不需要生成Direction对象相关的代码,只需要将"Direction.Up"替换成为它表示的常量0就可以。
"use strict"
const d = 0
const 枚举类型具有相同的效果,const枚举类型将在编译阶段被完全删除,并且使用了const枚举类型的地方会直接将const枚举成员的值内联到代码中
const enum Direction {
Up,
Down,
Left,
Right
}
const directions = {
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
}
代码经过typescript编译器编译后生成的JavaScript代码如下
"use strict"
const diretions = [
0 /*Up */,
1 /*Down */,
2 /*Left */,
3 /*Right */
]
为了便于代码调试和保持代码的可读性,typescript编辑器内链了注释,注释为枚举成员的名字
字面量类型
Typescript支持将字面量作为类型使用,称为字面量类型。
boolean字面量类型
true or false
string字面量类型
字符串字面量和模板字面量都能创建字符串,字符串字面量和不带参数的模板字面量都可以作为string字面量的类型使用
const a : 'hello' = 'hello'
const b : `world` = `world`
string字面量类型是string类型的子类型,可以将string字面量类型赋值给string类型
const a: 'hello' = 'hello'
const b: `world` = `world`
let c: string
c = a
c = b
数字字面量类型
数字字面量类型包括number字面量类型和bigint字面量类型
const a0: 0b1 = 1;
const b0: 0o1 = 1;
const c0: 1 = 1;
const d0: 0x1 = 1;
const a1: 0b1n = 1n;
const b1: 0o1n = 1n;
const c1: 1n = 1n;
const d1: 0x1n = 1n;
除了正数外,负数也可以作为数字字面量类型
const a0: -10 = -10;
const b0: 10 = 10;
const a1: -10n = -10n;
const b1: 10n = 10n;
number字面量类型和bigint字面量类型分别是number类型和bigint类型的子类型
const one: 1 = 1;
const num: number = one;
const oneN: 1n = 1n;
const numN: bigint = oneN;
枚举成员字面量类型
联合枚举成员类型使用枚举成员字面量形式表示
enum Direction {
Up,
Down,
Left,
Right
}
const up:Direction.Up =Direction.Up;
const down:Direction.Down=Direction.Down;
const left:Direction.Left = Direction.Left;
const right:Direction.Right=Direction.Right;
单元类型
单元类型又称为单例类型。指的是仅包含一个可能值的类型,由于这个特殊的类型,编译器在处理单元类型的时候不需要关注单元类型表示的具体值
const a: undefined = undefined
const b: null = null
const c: void = undefined
const d: unique symbol = Symbol()
const e: `hello` = 'hello'
enum Foo {A, B}
const f: Foo.A = Foo.A
顶端类型
顶端类型是一种通用类型,被称为通用超类型,在类型系统中,所有的类型都是顶端类型的子类型。
any
any类型是Typescript支持的顶端类型
let x: any
x = true;
x = 'hi';
x = 323;
x = 999n;
x = Symbol();
x = undefiend;
x = null;
x = {};
x = [];
x = function() {}
需要注意的是,虽然any类型是所有类型的父类型,但是TypeScript允许将any类型赋值给任何其他类型。
let x: any;
let a: boolean = x;
let b: string = x;
let c: number = x;
let d: bigint = x;
let e: symbol = x;
let f: void = x;
let g: undefined = x;
let h: null = x;
在any类型上允许执行任意的操作而不会产生编译错误。
–noImplicitAny
typescript中的类型注释是可以选择的,如果一个值没有明确的类型注解,编译器无法自动推断他的类型,那么这个值为any类型。
在大多数情况下,我们想要避免上述情况的发生。因此,TypeScript提供了一个“–noImplicitAny”编译选项来控制该行为。当启用了该编译选项时,如果发生了隐式的any类型转换,那么会产生编译错误。
{
"compilerOptions": {
"noImpliciAny": true
}
}
unknown
unknown类型使用unknow关键字做标识。let x: unknown
根据顶端类型的性质,任何其他的类型都能够赋值给unknown类型。unknown类型是比any类型更加安全的顶端类型。unknown类型只允许赋值给any类型和unknown类型,不允许赋值给任何其他类型。
let x: unknown;
const a1: any = x;
const b1: unknown = x;
// 错误
const a2: boolean = x;
const b2: string = x;
const c2: number = x;
同时在unknown类型上不允许执行绝大部分的操作。
let x: unknown;
// error
x + 1;
x.foo;
x()
在程序中使用unknown类型,需要将其细化为某些具体的类型,否则产生编译错误
function f1 (message: any) {
return message.length
}
f1(undefined)
function f2(message: unknown) {
return message.length // 编译错误,属性length不存在
}
此例子中,函数f1的参数message为any类型,在函数体重直接读取参数message的length属性不会产生编译错误,编译器不会对any类型进行任何类型检查,在f2重,参数的类型定义为unknown类型,这样的话,在函数体重不能直接读取参数message的length属性,否则将产生编译错误。
小结
- typescript仅有any和unknown两种顶端类型
- typescript中的所有类型都能赋值给any类型和unknown类型,两者没有写入的限制
- any类型能够赋值给任何其他的类型,不包含马上要介绍的never类型
- unknown类型仅仅能赋值给any类型和unknown类型
- 使用unknown类型,需要将其细分为某种具体类型
尾端类型
在类型系统中,尾端类型是所有其他类型的子类型,由于一个值不能同时属于所有类型,一个值不能同时为数字类型和字符串类型,因此尾端类型不包含任何值,尾端类型称为空类型或者0类型
never
typescript只有一个尾端类型:never类型,
function f() {
throw new Error()
}
根据尾端类型的定义,never类型是所有其他类型的子类型,所以,never类型允许赋值给任何类型,尽管不存在never类型的值
let x: never
let a: boolean = x;
let b: string = x;
let c: number = x;
let d: bigint = x;
let e: symbol = x;
let f: void = x;
let g: undefined = x;
let h: null = x;
没有类型是never类型的子类型,所有其他类型都不能赋值给never类型。
let x: never;
let y: never;
x = y;
// 错误
x = true;
x = 'hi'
x = 3.14
never的应用场景
- never类型可以作为函数的返回值类型,表示函数无法返回一个值。如果函数体中没有使用return语句,那么在正常执行完函数代码后会返回一个undefined值。在这种情况下,函数的返回值类型是void类型而不是never类型。只有在函数根本无法返回一个值的时候,函数的返回值类型才是never类型。、
- 函数中抛出了异常,这会到种子函数终止执行,从而不会返回任何值,这种情况下,函数的返回值为never类型。
function throwError():never { throw new Error() // 函数永远无法执行到末尾,返回类型为'never' }
- 函数的代码不是直接抛出异常而是间接的抛出异常,那么函数的返回值类型也是never类型。
fail函数包含了一条return语句,return语句中表达式的类型为never类型,因此fail函数的返回值类型也为never类型。function throwError:never () { throww new Error() } function fail():never { ruturn throwError() }
- 函数体中存在无限循环从而导致函数的执行永远也不会结束。在这种情况下函数的返回值也为never类型
function infiniteLoop(): never { while(true) { console.log('endless') } } // infiniteLoop函数的执行永远也不会结束,这意味着它无法正常返回一个值。因此,该函数的返回值类型为never类型
- 在条件类型中,使用never类型来完成一些类型运算。
数组类型
数组是十分常用的数据结构,它表示一组有序元素的集合。在TypeScript中,数组值的数据类型为数组类型。
数组类型的定义
简便数组类型表示法
简便数组类型表示法借用了数组字面量的语法,通过在数组元素后添加一对方括号"[]"来定义数组。
TElement []
const digits: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果数组中的元素的类型为复合类型,则需要在数组元素类型上使用分组运算符。
const red: (string | number)[] = ['f',f,0,0,0,0]
泛型数组类型表示法
泛型数组表示法是另一种表示数组类型的方法
Array<TElement>
该语法中 ,Array表示数组类型,
const digits: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const red: Array<string | number> = ['f',f,0,0,0,0]
比较
简便数组类型表示法和泛型类型表示法在功能上没有任何差别,只是在编码风格上有所差。’
在定义简单数组类型的时候,如果数组元素为单一原始类型或者类型引用,使用简便数组表示法会更加清晰和简洁
let a: string[];
let b: HTMLButtonElement[]
如果数组元素是复杂类型,如果是对象类型和联合类型等,可以选择使用泛型数组类型表示法。
let a: Array<string | number> ;
let b: Array<{x: number; y: number}>
数组元素类型
定义了数组类型之后,当访问数组元素的时候能够获得正确的元素的类型信息
const digits: number[] = [0, 1, 2, 3, 4, 5]
const zero = digits[0]
//虽然没有给常量zero添加类型注解,但是TypeScript编译器能够从数组类型中推断出zero的类型为number类型。
const out: number = digits[100]
当访问数组中不存在的元素,将会返回undefined值。typescript的类型系统无法推断出是否存在数组访问过界的情况
自读数组
只读数组和常规数组的区别在于,只读数组只允许程序读取数组,不允许修改和写入数组元素
ReadonlyArray
const red: ReadonlyArray<number> = [255, 0, 0]
readonly
readonly修饰符不允许与泛型数组类型表示法一起使用
const red: readonly number[] = [255, 0, 0]
Readonly
Readonly是typescript提供的一个内置工具类型,用于定义只读对象类型。将类型参数T的所有属性转换为只读
属性
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}