枚举
简单枚举
enum Tristate {
False,
True,
Unknown,
Never,
}
编译成JS:
var Tristate;
(function(Tristate) {
Tristate[(Tristate['False'] = 0)] = 'False';
Tristate[(Tristate['True'] = 1)] = 'True';
Tristate[(Tristate['Unknown'] = 2)] = 'Unknown';
})(Tristate || (Tristate = {}));
结果:
Tristate['False] // default: 0
Tristate['True] // 1
Tristate['Unknow] // 2
Tristate['Never] // 3
数字枚举和字符串枚举
enum Tristate {
False,
True = 'TRUE',
Unknown = 2,
Never
}
// 结果
Tristate['False] // default:0
Tristate['True] // 'TRUE'
Tristate['Unknow] // 2
Tristate['Never] // 3
开放枚举
enum Color {
Red,
Green,
Blue
}
enum Color {
DarkRed = 3,
DarkGreen,
DarkBlue
}
Color:
{
Red,
Green,
Blue
DarkRed = 3,
DarkGreen,
DarkBlue
}
辨析联合类型
TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
1.可辨识
可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
after:string;
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission;
after:string;
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
after:string;
}
在上述代码中,我们分别定义了 Motorcycle
、 Car
和 Truck
三个接口,在这些接口中都包含一个 vType
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
2.联合类型
基于前面定义了三个接口,我们可以创建一个 Vehicle
联合类型:
type Vehicle = Motorcycle | Car | Truck;
现在我们就可以开始使用 Vehicle
联合类型,对于 Vehicle
类型的变量,它可以表示不同类型的车辆。
3.不同表现
// 可辨析 公共字段vType值不同
const vehicle:Vehicle = {
vType:'motorcycle' // 推断为 Motorcycle
}
// 可辨析 独一无二的字段 make
const vehicle:Vehicle = {
make:123 // 推断为 Motorcycle
}
// 不可辨析,需要自己断言
const vehicle:Vehicle = {
after:'after'
}
4.类型守卫
下面我们来定义一个 evaluatePrice
方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:
const EVALUATION_FACTOR = Math.PI;
function evaluatePrice(vehicle: Vehicle) {
return vehicle.capacity * EVALUATION_FACTOR;
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);
对于以上代码,TypeScript 编译器将会提示以下错误信息:
Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
原因是在 Motorcycle 接口中,并不存在 capacity
属性,而对于 Car 接口来说,它也不存在 capacity
属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice
方法,重构后的代码如下:
function evaluatePrice(vehicle: Vehicle) {
switch(vehicle.vType) {
case "car":
return vehicle.transmission * EVALUATION_FACTOR;
case "truck":
return vehicle.capacity * EVALUATION_FACTOR;
case "motorcycle":
return vehicle.make * EVALUATION_FACTOR;
}
}
在以上代码中,我们使用 switch
和 case
运算符来实现类型守卫,从而确保在 evaluatePrice
方法中,我们可以安全地访问 vehicle
对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。
交叉类型
同名基础类型属性的合并
接口 X 和接口 Y 都含有一个相同的成员 c,但它们的类型不一致。对于这种情况,此时 XY类型中成员 c 的类型是不是可以是 string
或 number
类型呢?
interface X {
c: string;
d: string;
}
interface Y {
c: number;
e: string
}
type XY = X & Y; // Never
混入后成员 c 的类型为 string & number
,即成员 c 的类型既可以是 string
类型又可以是 number
类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为 never
。
同名非基础类型属性的合并
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }
interface A { x: D; }
interface B { x: E; }
interface C { x: F; }
type ABC = A & B & C;
let abc: ABC = {
x: {
d: true,
e: 'semlinker',
f: 666
}
};
console.log('abc:', abc);
在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。
函数重载
interface Overloaded {
(foo: string): string;
(foo: number): number;
}
// 实现接口的一个例子:
function stringOrNumber(foo: number): number;
function stringOrNumber(foo: string): string;
function stringOrNumber(foo: any): any {
if (typeof foo === 'number') {
return foo * foo;
} else if (typeof foo === 'string') {
return `hello ${foo}`;
}
}
const overloaded: Overloaded = stringOrNumber;
// 使用
const str = overloaded(''); // str 被推断为 'string'
const num = overloaded(123); // num 被推断为 'number'
接口扩展
简单接口
interface Square {
kind: 'square';
size: number;
}
通用接口
interface DictionaryString{
[index:string]:T
}
具有某些字段
interface DictionaryStringWithName{
name:string;
[index:string]:string
}
下面的会出错:
interface DictionaryString{
name:string;
[index:string]number; // 与name类型不一致,需要兼容name
}
需要兼容name:
interface DictionaryString{
name:string;
[index:string]number | string; // ok
}
高级用法
https://www.typescriptlang.or...
泛型约束
确保属性存在
有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。
一个很好的例子是在处理字符串或数组时,我们会假设 length
属性是可用的。让我们再次使用 identity
函数并尝试输出参数的长度:
function identity(arg: T): T {
console.log(arg.length); // Error
return arg;
}
复制代码
在这种情况下,编译器将不会知道 T
确实含有 length
属性,尤其是在可以将任何类型赋给类型变量 T
的情况下。我们需要做的就是让类型变量 extends
一个含有我们所需属性的接口,比如这样:
interface Length {
length: number;
}
function identity(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
检查对象上的键是否存在
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
复制代码
通过 keyof
操作符,我们就可以获取指定类型的所有键,之后我们就可以结合前面介绍的 extends
约束,即限制输入的属性名包含在 keyof
返回的联合类型中。具体的使用方式如下:
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
高级用法实现
前置学习
infer
当 infer
用于构造函数类型中,可用于参数位置 new (...args: infer P) => any;
和返回值位置 new (...args: any[]) => infer P;
。
type ParamType = T extends (param: infer P) => any ? P : T;
typeof
// Partial
type Partial = {
[P in keyof T]?: T[P];
};
// Record
type Record = {
[P in K]: T;
};
// Pick
type Pick = {
[P in K]: T[P];
};
// Exclude
type Exclude = T extends U ? never : T;
// RetureType
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
// Readonly
type Readonly = {
readonly K: T[K];
};
FQA
interface vs type
1.Objects/Functions
接口和类型别名都可以用来描述对象的形状或函数签名:
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
类型别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
2.Other Types
与接口类型不一样,类型别名可以用于一些其他类型,比如原始类型、联合类型和元组:
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
3.Extend
接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的。接口可以扩展类型别名,而反过来是不行的。
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
}
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4.Implements
类可以以相同的方式实现接口或类型别名,但类不能实现使用类型别名定义的联合类型:
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x = 1;
y = 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x = 1;
y = 2;
}
type PartialPoint = { x: number; } | { y: number; };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
x = 1;
y = 2;
}
5.Declaration merging
与类型别名不同,接口可以定义多次,会被自动合并为单个接口。
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
any vs unknown
共同点
就像所有类型都可以赋值给 any
,所有类型也都可以赋值给 unknown
。
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
不同点
unknown
类型只能被赋值给any
类型和unknown
类型本身,any
可以赋值给任何类型。- 将
value
变量类型设置为unknown
后,这些操作都不再被认为是类型正确的。
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
void vs never
void 表示没有任何类型,never 表示永远不存在的值的类型。
共同点
void 和 never 都表示函数没有返回值。
不同点
- void的语义是指返回了空值,never是指函数永不返回或者总是抛出错误
- void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。