typescript 进阶(一)
前言
本文主要记录个人在使用 typescript 时的一些用法,介绍 typescript 。建议在阅读前先了解 typescript 的基础语法。
互斥键的类型
在 ts 官网的联合类型文档中有这样一种情况:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(s: Shape) {
if (s.kind === "circle") {
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}
在此种情况下 ts 可以正常判断这两种类型的联合, 但是我们这样判断:
function area(s: Shape) {
if (s.radius) { // if 中判断修改
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}
或者是这样, 没有 type 的情况:
type Shape =
| { radius: number, cal: ()=>number}
| { x: number }
function area(s: Shape) {
if(s.cal){
return s.cal()
}else{
return s.x * s.x;
}
}
这里就会报如下的错误:
Property 'cal' does not exist on type 'Shape'.
Property 'cal' does not exist on type '{ x: number; }'.
ts 在联合类型中, 我们直接通过 .
获取的属性, 是必须在所有子类型中共有的
这里我们有 2 种结局方案
- 使用
in
操作符
type Shape =
| { radius: number, cal: ()=>number}
| { x: number }
function area(s: Shape) {
if ('cal' in s) {
return s.cal()
} else {
return s.x * s.x;
}
}
- 使用特殊的类型库来包装
函数类型
有时候函数我们也会当做对象来使用:
interface IFN {
(): number;
name: string
}
const a: IFN = () => {
//...
return 1
}
a.name = 'test'
同样地, 会有一些特殊的函数, 如 Date
, 他有多套不同的函数:
new Date(1656953943886)
// Tue Jul 05 2022 00:59:03 GMT+0800 (中国标准时间)
// Date 对象
new Date('2022-12-1')
// Thu Dec 01 2022 00:00:00 GMT+0800 (中国标准时间)
// 也是 Date 对象
// ...
在 ts 的声明中他是这样被描述的
interface DateConstructor {
new(): Date;
new(value: number | string): Date;
new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date;
(): string;
readonly prototype: Date;
parse(s: string): number;
UTC(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): number;
now(): number;
}
字面量类型
// @errors: 2345
declare function handleRequest(url: string, method: "GET" | "POST"): void;
// ---cut---
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
解决方案
- 使用泛型:
// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// Change 2
handleRequest(req.url, req.method as "GET");
as const
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
此例子在官网文档中也有提到: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html
对象
重新映射
通过对象 as 重新映射出类型:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
映射中添加条件判断:
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// type ObjectsNeedingGDPRDeletion = {
// id: false;
// name: true;
// }
枚举
静态枚举
const enum ITypeEnums {
Input ,
Select
}
// 普通枚举:
enum ITypeEnums {
Input ,
Select
}
经过编译后:
var ITypeEnums2;
(function (ITypeEnums2) {
ITypeEnums2[ITypeEnums2["Input"] = 0] = "Input";
ITypeEnums2[ITypeEnums2["Select"] = 1] = "Select";
})(ITypeEnums2 || (ITypeEnums2 = {}));
很明显的是静态枚举消失了
这是 ts 为了避免在访问枚举值时额外的生成代码的代价
静态枚举的编译
const enum ITypeEnums {
Input ,
Select
}
let types = [
ITypeEnums.Input,
ITypeEnums.Select
];
const type: ITypeEnums.Input = 1
let types = [
0 /* ITypeEnums.Input */,
1 /* ITypeEnums.Select */
];
const type = 1;
枚举的选择
什么情况下选择枚举, 而什么情况下会选择对象
在一般情况下, 我们使用静态对象就够了:
const enum EDirection {
Up,
Down,
Left,
Right,
}
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const
let a = ODirection.Up
// 提示 Up: 0
对状态进行多种判断时, 我们用到枚举的情况会更多(尤其是静态枚举)
const enum EDirection {
Up,
Down,
Left,
Right,
}
const getStatus = (status: EDirection) => {
switch(status){
case EDirection.Up:
return 'success'
case EDirection.Down:
return 'fail'
default:
return null
}
}
编译之后的结果:
const getStatus = (status) => {
switch (status) {
case 0 /* EDirection.Up */:
return 'success';
case 1 /* EDirection.Down */:
return 'fail';
default:
return null;
}
};
比较下非静态枚举的编译:
var EDirection;
(function (EDirection) {
EDirection[EDirection["Up"] = 0] = "Up";
EDirection[EDirection["Down"] = 1] = "Down";
EDirection[EDirection["Left"] = 2] = "Left";
EDirection[EDirection["Right"] = 3] = "Right";
})(EDirection || (EDirection = {}));
const getStatus = (status) => {
switch (status) {
case EDirection.Up:
return 'success';
case EDirection.Down:
return 'fail';
default:
return null;
}
};
通过此种静态枚举的方案来判断类型, 比较 Object['name']
的方式和普通枚举的方式来说,
性能更好, 可维护性也更高
有静态方法的枚举
你可以使用 enum
+ namespace
的声明的方式向枚举类型添加静态方法。
这里的枚举只支持普通枚举
enum EDirection {
Up,
Down,
Left,
Right,
}
namespace EDirection {
export function go(type: EDirection) {
switch (type) {
case EDirection.Up:
case EDirection.Down:
return false;
default:
return true;
}
}
}
EDirection.go(EDirection.Down)
搭配使用可以在状态的基础上, 作出各种变化和判断方法