TypeScript系列:第六篇 - 编写高质量的TS类型
掌握这些,ts类型声明事半功倍 💪🏻
不要做
- 永远不要使用类型
Number
、String
、Boolean
、Symbol
或Object
这些类型指的是非原始装箱对象,使用number
、string
、boolean
和symbol
类型 - 不要使用
any
作为类型,除非正在将 JavaScript 项目迁移到 TypeScript - 不要将返回类型
any
用于其值将被忽略的回调,使用返回类型void
,其可以防止意外地以未经检查的方式使用函数的返回值const fun = (fn: () => void) => {};
declare
作用?
declare
关键字在 TypeScript 中主要用于声明类型信息,而不是实际的实现代码。其帮助 TypeScript 理解代码中的类型结构,从而提供更好的类型检查和代码提示等功能。
声明全局变量类型
当在项目中使用了某些全局变量,而这些变量没有明确的类型定义时,可以使用declare
来声明它们的类型。
declare const globalVar: string;
如,在 UmiJS 框架 中,针对全局变量声明类型:
// typings.d.ts
declare function log(info: string): void;
declare global {type AnalysisFunction = (params: { pageId: string; eventId: string; ext?: object }) => void;interface Window: {// 基座挂载window判断权限方法PermissionAuth?: (params: {// 页面唯一标识pk: string;// 按钮唯一标识fk?: string;}) => boolean;}
}
声明模块
如果要使用一个外部模块,但这个模块没有提供 TypeScript 的类型定义文件(.d.ts
),可以通过declare module
来声明它的类型。
declare module 'my-custom-module' {export function doSomething(): void;
}
.d.ts
同 .ts
区别?
-
.d.ts
:第三方库提供类型声明、声明全局变量或全局类型、扩展现有模块的类型声明// jquery.d.ts declare var jQuery: {(selector: string): any;ajax: {(url: string, settings?: any): void;}; };
-
.ts
:定义实际的类型和接口,并且这些类型和接口将被项目中的其他代码使用时,或者在项目内部进行模块化开发时// user.ts export interface User {id: number;name: string;email: string; }
import type
同 import
区别?
-
import type
:用于导入类型信息,但不会将导入的模块包含在运行时代码中。它主要用于类型声明,不会影响最终的 JavaScript 输出// main.ts import type { User } from './types';
-
import
:用于导入模块中的值(如变量、函数、类等),这些值在运行时会被加载和执行。它不仅用于类型声明,还用于实际的代码运行
extends
扩展类型
interface
上的 extends
关键字允许从其他命名类型复制成员,并添加任何新成员。
这对于减少类型声明的数量以及表明同一属性的几个不同的意图很有用。
interface Person {name: string;age: number;
}
// 类型扩展
interface Man extends Person {readonly sex: '男';
}interface SpecialSkill {fly: () => void;
}
// 也可以从多种类型扩展
interface Superman extends Man, SpecialSkill {}
typeof
同 keyof
区别?
-
typeof
操作符用于获取一个变量或属性的类型 -
keyof
操作符用于获取一个对象类型的所有键的联合类型
interface IPerson {name: string;age: number;
}type PersonKey = keyof IPerson; // 类型是 "name" | "age"
const p: PersonKey = 'age';
const pType = typeof p; // string
更多内容,可详见:TypeScript系列:第四篇 - typeof 与 keyof
extends keyof
类型约束
在动态访问和操作对象属性时保持类型安全,避免运行时错误。
<TData, TLabelKey extends keyof TData>
定义两个泛型参数TData
和TLabelKey
。
TData
表示传入的对象类型TLabelKey
表示对象中的某个键,该键必须是TData
对象的一个键(通过extends keyof TData
约束)
动态访问对象属性
动态地访问对象的某个属性时,可以使用这种约束来确保访问的属性是有效的。
interface TData {city: string;adcode: number;
}// labelKey必须是data对象的键之一
function getValue<TData, TLabelKey extends keyof TData>(data: TData, labelKey: TLabelKey): TData[TLabelKey] {return data[labelKey];
}
动态生成对象属性
interface TData {city: string;adcode: number;
}// labelKey必须是data对象的键之一,value的类型必须与data[labelKey]的类型一致
function setProperty<TData, TLabelKey extends keyof TData>(data: TData, labelKey: TLabelKey, value: TData[TLabelKey]): TData {data[labelKey] = value;return data;
}
!
非空断言
在不进行任何显式检查的情况下从类型中删除 null
和 undefined
function liveDangerously(x?: number) {return x.toFixed(); // x可能为“未定义”return x!.toFixed(); // ✔️
}
- 绕过类型检查:当确定一个变量不会是
null
或undefined
,但 TypeScript 编译器无法确定时,使用!
来绕过类型检查 - 避免编译错误:在某些情况下,TypeScript会报错,因为其认为一个变量可能是
null
或undefined
。使用!
可以避免编译错误
模板字符串类型的排列组合
获得一组规律固定,可由排列组合得到的联合类型
type A = 'a1' | 'a2';
type B = 'b1' | 'b2';type Products = `${A}-${B}`; // "a1-b1" | "a1-b2" | "a2-b1" | "a2-b2"
实用工具类型
interface A {a: number;b: number;c: number;
}
interface B {c: number;d: number;
}
方法 | 作用 | 示例 | 结果 |
---|---|---|---|
Partial<Type> | 构造一个将 Type 的所有属性设置为可选的类型 | Partial<A & B> | {a?: number, b?: number, c?: number} |
Pick<Type, Keys> | 从 Type 中选取一组属性 Keys 来构造一个类型 | Pick<A, 'a'|'c'> | {a: number, c: number} |
Omit<Type, Keys> | 从 Type 中选择所有属性然后删除 Keys 来构造一个类型 | Omit<A, 'a'|'c'> | {b: number} |
Extract<Type, Union> | 从 Type 中提取所有可分配给 Union 的联合成员来构造一个类型 | Extract<A|B, B> | B |