0%

Webpack 本质上是一个静态模块打包器:从入口出发,构建依赖图,经过 Loader 转换与 Plugin 扩展,最终输出可部署资源。本文以 webpack5 为主。

阅读全文 »

在团队协作中,为避免低级 Bug、产出风格统一的代码,会预先制定编码规范。使用 Lint 工具和代码风格检测工具,则可以辅助编码规范执行,有效控制代码质量。

阅读全文 »

一、package.json 是什么

package.json 是 Node.js 项目的清单文件,描述了项目的基本信息、依赖、脚本、配置等。它是所有 Node.js/npm 项目的核心入口。

阅读全文 »

理解 ES6 Module(ESM)和 CommonJS(CJS)这两种模块系统的区别,是掌握前端工程化的基础。本文将从多个维度深入剖析两者的差异,帮助你彻底理解它们。

阅读全文 »

一、什么是 SaaS

SaaS(Software as a Service)是一种软件交付模式:软件由服务商托管,用户通过浏览器访问,按需付费,无需自行部署和维护服务器。

阅读全文 »

一、ts 类型

1. 基本类型:string, number, boolean

Note: The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.

阅读全文 »

一、操作对象类型

Partial

将 T 的所有属性变为可选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface User {
name: string;
age: number;
email: string;
}

// 实现
type MyPartial<T> = { [P in keyof T]?: T[P] };

type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }

// 应用:更新操作
function updateUser(id: number, fields: Partial<User>) {
// 只传需要修改的字段
}
updateUser(1, { name: 'Alice' }); // ✅ 只需传 name

Required

将 T 的所有属性变为必选(去掉 ?)。

1
2
3
4
5
6
7
8
9
10
// 实现
type MyRequired<T> = { [P in keyof T]-?: T[P] };

interface Config {
debug?: boolean;
logLevel?: string;
}

type StrictConfig = Required<Config>;
// { debug: boolean; logLevel: string }

Readonly

将 T 的所有属性变为只读。

1
2
3
4
5
// 实现
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; readonly email: string }

Pick<T, K>

从 T 中选取一组属性 K 组成新类型。

1
2
3
4
5
6
7
8
// 实现
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };

type UserName = Pick<User, 'name' | 'email'>;
// { name: string; email: string }

// 应用:接口返回的敏感字段过滤
type PublicUser = Pick<User, 'name'>;

Omit<T, K>

从 T 中排除一组属性 K,保留剩余属性。

1
2
3
4
5
// 实现
type MyOmit<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P] };

type UserWithoutPassword = Omit<User, 'password' | 'secret'>;
// { name: string; age: number; email: string }

Record<K, T>

以 K 为键、T 为值构造一个对象类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 实现
type MyRecord<K extends keyof any, T> = { [P in K]: T };

type Page = 'home' | 'about' | 'contact';

type PageInfo = Record<Page, { title: string; url: string }>;
// {
// home: { title: string; url: string };
// about: { title: string; url: string };
// contact: { title: string; url: string };
// }

const pages: PageInfo = {
home: { title: '首页', url: '/' },
about: { title: '关于', url: '/about' },
contact: { title: '联系', url: '/contact' },
};

// 应用:枚举映射
const StatusMap: Record<number, string> = {
0: 'pending',
1: 'active',
2: 'disabled',
};

二、操作联合类型

Exclude<T, U>

从 T 中排除可赋值给 U 的类型。

1
2
3
4
5
// 实现
type MyExclude<T, U> = T extends U ? never : T;

type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T1 = Exclude<string | number | (() => void), Function>; // string | number

Extract<T, U>

从 T 中提取可赋值给 U 的类型。

1
2
3
4
5
// 实现
type MyExtract<T, U> = T extends U ? T : never;

type T2 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'
type T3 = Extract<string | number, number>; // number

NonNullable

从 T 中排除 nullundefined

1
2
3
4
// 实现
type MyNonNullable<T> = T extends null | undefined ? never : T;

type T4 = NonNullable<string | number | null | undefined>; // string | number

三、操作函数类型

ReturnType

获取函数类型的返回值类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实现
type MyReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : never;

type Fn = () => { a: number; b: string };
type T5 = ReturnType<Fn>; // { a: number; b: string }

// 实际应用:获取 API 函数的返回值类型
function fetchUser() {
return Promise.resolve({ id: 1, name: 'Alice' });
}

type FetchUserReturn = ReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string }>

// 结合 Awaited 获取真正的返回值
type UserData = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string }

Parameters

获取函数类型的参数类型(元组)。

1
2
3
4
5
6
7
8
9
// 实现
type MyParameters<T extends (...args: any) => any>
= T extends (...args: infer P) => any ? P : never;

type Fn2 = (name: string, age: number) => void;
type T6 = Parameters<Fn2>; // [name: string, age: number]

type FirstParam<T> = T extends (arg: infer P, ...rest: any) => any ? P : never;
type T7 = FirstParam<Fn2>; // string

ConstructorParameters

获取构造函数类型的参数类型。

1
2
3
4
5
6
7
type T8 = ConstructorParameters<typeof Date>;
// [value: string | number | Date](取决于重载)

// 应用:工厂函数类型安全
function factory<T>(ctor: new (...args: any[]) => T, ...args: ConstructorParameters<typeof ctor>) {
return new ctor(...args);
}

InstanceType

获取构造函数类型的实例类型。

1
2
3
4
5
class MyClass {
constructor(public name: string) {}
}

type T9 = InstanceType<typeof MyClass>; // MyClass

四、操作字符串类型

Uppercase / Lowercase / Capitalize / Uncapitalize

1
2
3
4
5
6
7
8
9
10
11
type T10 = Uppercase<'hello'>;        // "HELLO"
type T11 = Lowercase<'HELLO'>; // "hello"
type T12 = Capitalize<'hello'>; // "Hello"
type T13 = Uncapitalize<'Hello'>; // "hello"

// 应用:统一事件名格式
type EventName = 'click' | 'mouseenter' | 'scroll';
type Handlers = `on${Capitalize<EventName>}`; // "onClick" | "onMouseenter" | "OnScroll"
// 修正:Uncapitalize 包裹
type CorrectHandler = `on${Capitalize<EventName>}`;
// 实际上用 Uncapitalize 保持首字母大写后的结果

五、高级实用类型

Awaited<T

递归解包 Promise 类型(TS 4.5+)。

1
2
3
type T14 = Awaited<Promise<string>>;              // string
type T15 = Awaited<Promise<Promise<number>>>; // number
type T16 = Awaited<Promise<string | number>>; // string | number

ThisParameterType / OmitThisParameter

1
2
3
4
5
6
function toHex(this: Number) {
return this.toString(16);
}

type T17 = ThisParameterType<typeof toHex>; // Number
type T18 = OmitThisParameter<typeof toHex>; // () => string

六、面试题

Q1: 如何让 Pick 支持嵌套路径?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type NestedPick<T, K extends string> =
K extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? { [P in Key]: NestedPick<T[Key], Rest> }
: never
: K extends keyof T
? { [P in K]: T[K] }
: never;

type UserDeep = {
profile: { name: string; age: number };
settings: { theme: string };
};

type PickedUser = NestedPick<UserDeep, 'profile.name'>;
// { profile: { name: string } }

Q2: 实现 DeepReadonly

1
2
3
4
5
6
7
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};

Q3: 从联合类型中排除函数类型

1
2
3
4
type ExcludeFunction<T> = T extends (...args: any) => any ? never : T;

type Mixed = string | number | (() => void) | (() => string);
type WithoutFn = ExcludeFunction<Mixed>; // string | number

Q4: PickOmit 的区别在面试中如何回答

1
2
3
4
5
6
7
8
Pick<T, K>:从 T 中选取 K 属性集合组成新类型,K 必须为 T 的 key。
Omit<T, K>:从 T 中排除 K 属性集合,保留剩余属性。

两者互补:Omit<T, K> = Pick<T, Exclude<keyof T, K>>。

适用场景:
Pick → 接口响应中只暴露部分字段给前端
Omit → 排除敏感字段(密码、token)

Q5: 实现一个类型安全的状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type StateMachine<S extends string, E extends string> = {
initial: S;
states: Record<S, {
on: Partial<Record<E, S>>;
}>;
};

const machine: StateMachine<'idle' | 'loading' | 'success', 'FETCH' | 'RETRY'> = {
initial: 'idle',
states: {
idle: { on: { FETCH: 'loading' } },
loading: { on: { FETCH: 'loading', RETRY: 'loading' } },
success: { on: { FETCH: 'loading' } },
},
};

一、为什么需要泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不用泛型:只能针对单一类型
function identity(arg: number): number { return arg; }

// 或用 any 丢失类型信息
function identity(arg: any): any { return arg; }

// 用泛型:类型被保留,传入什么类型就返回什么类型
function identity<T>(arg: T): T {
return arg;
}

const num = identity(42); // type: 42(字面量类型)
const str = identity('hello'); // type: "hello"

泛型的核心价值:在保证类型安全的前提下,编写可复用的组件。不指定具体类型,而是由调用者传入类型参数。

二、泛型函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 多个类型参数
function swap<T, U>(pair: [T, U]): [U, T] {
return [pair[1], pair[0]];
}

const swapped = swap([1, 'hello']); // type: [string, number]

// 数组泛型
function first<T>(arr: T[]): T | undefined {
return arr[0];
}

// 箭头函数泛型(JSX 中需注意语法)
const last = <T,>(arr: T[]): T | undefined => arr[arr.length - 1];

三、泛型接口与泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 泛型接口
interface ResponseData<T> {
code: number;
message: string;
data: T;
}

type User = { id: number; name: string };

const res: ResponseData<User> = {
code: 200,
message: 'ok',
data: { id: 1, name: 'Alice' },
};

// 泛型类型别名
type Nullable<T> = T | null;
type Pair<T, U> = [T, U];

四、泛型约束(extends)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ❌ 编译错误:.length 不一定存在
function logLength<T>(arg: T): T {
console.log(arg.length);
return arg;
}

// ✅ 约束 T 必须具有 length 属性
interface HasLength {
length: number;
}

function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}

logLength('hello'); // 5(string 有 length)
logLength([1, 2, 3]); // 3(array 有 length)
logLength({ length: 10 });// 10(手动满足)

// keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const user = { name: 'Alice', age: 25 };
getProperty(user, 'name'); // type: string ✅
getProperty(user, 'age'); // type: number ✅
getProperty(user, 'email'); // ❌ 编译错误:email 不在 keyof 中

五、条件类型

1
2
3
4
5
6
7
8
9
10
11
12
// 基础条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<number>; // false

// 条件类型 + 泛型
type ExtractArrayType<T> = T extends (infer U)[] ? U : never;

type C = ExtractArrayType<string[]>; // string
type D = ExtractArrayType<number[]>; // number
type E = ExtractArrayType<number>; // never

infer 关键字

infer 用于在条件类型中声明一个待推断的类型变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 提取 Promise 返回值类型
type Unwrap<T> = T extends Promise<infer R> ? R : T;

type F = Unwrap<Promise<string>>; // string
type G = Unwrap<number>; // number(非 Promise,直接返回 T)

// 提取函数返回值类型(类似 ReturnType)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

// 递归提取嵌套 Promise
type DeepUnwrap<T> = T extends Promise<infer R> ? DeepUnwrap<R> : T;
type H = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string

分布式条件类型

当条件类型作用于裸类型参数时,会自动分发到联合类型的每个成员:

1
2
3
4
5
type ToArray<T> = T extends any ? T[] : never;

type I = ToArray<string | number>;
// 等价于 string[] | number[](分发给每个成员)
// 不是 (string | number)[]!

如何阻止分发?用元组包裹:

1
2
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type J = ToArrayNonDist<string | number>; // (string | number)[]

六、实用泛型模式

工厂函数

1
2
3
4
5
6
7
8
9
function createInstance<T>(ctor: new (...args: any[]) => T, ...args: any[]): T {
return new ctor(...args);
}

class User {
constructor(public name: string) {}
}

const u = createInstance(User, 'Alice'); // type: User

类型安全的 EventEmitter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface EventMap {
click: { x: number; y: number };
focus: { element: HTMLElement };
keydown: { key: string };
}

class TypedEmitter {
private handlers = new Map<keyof EventMap, Function[]>();

on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void) {
if (!this.handlers.has(event)) this.handlers.set(event, []);
this.handlers.get(event)!.push(handler);
}

emit<K extends keyof EventMap>(event: K, data: EventMap[K]) {
this.handlers.get(event)?.forEach(h => h(data));
}
}

const emitter = new TypedEmitter();
emitter.on('click', (data) => console.log(data.x, data.y)); // type-safe
emitter.emit('click', { x: 10, y: 20 });

链式调用类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class QueryBuilder<T extends Record<string, any>> {
private conditions: string[] = [];

where<K extends keyof T>(key: K, value: T[K]): this {
this.conditions.push(`${String(key)} = ${value}`);
return this;
}

build(): string {
return this.conditions.join(' AND ');
}
}

interface User {
name: string;
age: number;
}

const query = new QueryBuilder<User>()
.where('name', 'Alice') // ✅ type-safe
.where('age', 25) // ✅
.build();

七、面试题

Q1: ExtractExclude 的实现

1
2
3
4
5
6
7
// Exclude<T, U>:从 T 中排除可赋值给 U 的类型
type MyExclude<T, U> = T extends U ? never : T;
type K = MyExclude<'a' | 'b' | 'c', 'a' | 'b'>; // 'c'

// Extract<T, U>:从 T 中提取可赋值给 U 的类型
type MyExtract<T, U> = T extends U ? T : never;
type L = MyExtract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'

Q2: 实现 ReturnType

1
2
3
4
type MyReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : never;

type M = MyReturnType<() => string>; // string

Q3: 实现 PickOmit

1
2
3
4
5
6
7
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};

type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};

Q4: 如何实现深度 Partial?

1
2
3
4
5
6
7
8
9
10
11
12
13
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface Config {
nested: {
value: string;
flag: boolean;
};
}

type PartialConfig = DeepPartial<Config>;
// { nested?: { value?: string; flag?: boolean } }

Q5: infer 实现数组元素类型

1
2
3
4
5
6
7
8
type ArrayItem<T> = T extends (infer U)[] ? U : never;

type N = ArrayItem<string[]>; // string
type O = ArrayItem<number[][]>; // number[](只解一层)

// 深层解包
type DeepArrayItem<T> = T extends (infer U)[] ? DeepArrayItem<U> : T;
type P = DeepArrayItem<number[][][]>; // number