TypeScript 进阶
约 1029 字大约 3 分钟
TypeScript进阶
2024-08-13
泛型
基础泛型
function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>("hello");
const num = identity(42); // 类型推断泛型约束
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
logLength({ length: 10 }); // OK
logLength(123); // Error: number 没有 length泛型类
class GenericStack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
const stack = new GenericStack<number>();
stack.push(1);
stack.push(2);泛型工具类型
// 内置工具类型
type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Record<K extends string, T> = { [P in K]: T };
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T extends null | undefined ? never : T;自定义工具类型
// 将可选属性变为必须
type Required<T> = { [P in keyof T]-?: T[P] };
// 深度 Partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 深只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 函数类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;装饰器
启用装饰器
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 装饰器工厂
function logger(prefix: string) {
return function(constructor: Function) {
console.log(`${prefix}: ${constructor.name}`);
};
}
@logger("Application started")
class App {}方法装饰器
function readonly(target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
return descriptor;
}
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${key} with`, args);
return original.apply(this, args);
};
return descriptor;
}
class Calculator {
@readonly
static PI = 3.14159;
@log
add(a: number, b: number): number {
return a + b;
}
}属性装饰器
function minLength(min: number) {
return function(target: any, key: string) {
let value: string;
Object.defineProperty(target, key, {
get: () => value,
set: (v: string) => {
if (v.length < min) {
throw new Error(`${key} must be at least ${min} characters`);
}
value = v;
},
});
};
}
class User {
@minLength(3)
name: string;
constructor(name: string) {
this.name = name;
}
}模块系统
ES Module
//导出
export const PI = 3.14;
export class MathHelper { }
//默认导出
export default class App { }
//导入
import App, { PI, MathHelper } from './module';
//类型导入
import type { User } from './types';命名空间
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string): boolean {
return lettersRegexp.test(s);
}
}
}声明文件
.d.ts 文件
// my-library.d.ts
declare module 'my-library' {
export interface Config {
debug?: boolean;
timeout?: number;
}
export function initialize(config: Config): void;
export class Client {
constructor(config: Config);
request(): Promise<Response>;
}
}全局声明
// global.d.ts
declare global {
interface Window {
myGlobal: any;
}
interface Array<T> {
groupBy<K extends keyof T>(key: K): Record<T[K], T[]>;
}
}
export {};类型映射
映射类型
type OptionsFlags<T> = {
[K in keyof T]: boolean;
};
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};条件类型
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type ExtractPromise<T> = T extends Promise<infer U> ? U : T;
type Flatten<T> = T extends Array<infer U> ? U : T;infer 关键字
// 推断函数返回值
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// 推断构造函数
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;
// 推断数组元素
type ElementType<T> = T extends (infer E)[] ? E : T;类型安全
难度类型
// Exclude 和 Extract
type T = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
// NonNullable
type T3 = NonNullable<string | number | null | undefined>; // string | number
// Required 和 Partial
interface User {
name?: string;
age?: number;
}
type FullUser = Required<User>;
type OptionalUser = Partial<FullUser>;类型守卫
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function sound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow();
} else {
animal.bark();
}
}断言函数
function assertIsString(val: unknown): asserts val is string {
if (typeof val !== 'string') {
throw new Error('Not a string!');
}
}
function process(value: unknown) {
assertIsString(value);
// value 在这里被推断为 string
console.log(value.toUpperCase());
}编译配置
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}项目配置
项目引用
// tsconfig.json (主项目)
{
"files": [],
"references": [
{ "path": "./shared" },
{ "path": "./utils" }
]
}
// shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declarationMap": true,
"outDir": "./dist"
}
}