TypeScript类型系统进阶与实战

TypeScript类型系统进阶与实战

前言

TypeScript 作为 JavaScript 的超集,其强大的类型系统是其最核心的特性之一。掌握 TypeScript 的类型系统不仅可以帮助我们在编译时发现错误,还能提高代码的可读性和可维护性。本文将深入探讨 TypeScript 类型系统的高级特性,并通过实战案例展示其在实际开发中的应用。

一、类型系统基础回顾

1. 基本类型与高级类型

TypeScript 提供了丰富的类型系统,包括基本类型和高级类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 基本类型
let num: number = 42;
let str: string = "Hello TypeScript";
let bool: boolean = true;
let obj: object = { name: "TypeScript" };
let arr: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 42];
let undef: undefined = undefined;
let nullVal: null = null;
let anyVal: any = "可以是任何类型";
let unknownVal: unknown = "未知类型";
let neverVal: never = (() => { throw new Error(); })();

// 高级类型 - 联合类型
let union: string | number = 42;
union = "now I'm a string";

// 高级类型 - 交叉类型
interface A { a: number; }
interface B { b: string; }
let intersection: A & B = { a: 1, b: "hello" };

2. 类型推断与类型断言

TypeScript 具有强大的类型推断能力,同时也允许开发者进行类型断言:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 类型推断
let inferredNumber = 42; // 推断为 number 类型
let inferredString = "hello"; // 推断为 string 类型
let inferredArray = [1, 2, 3]; // 推断为 number[] 类型

// 类型断言 - 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// 类型断言 - as 语法(推荐,尤其是在 JSX 中)
let someValue2: any = "this is also a string";
let strLength2: number = (someValue2 as string).length;

// 双重断言(谨慎使用)
let value: string = "hello";
let numValue: number = (value as unknown as number);

3. 类型守卫

类型守卫允许我们在特定作用域内缩小变量的类型范围:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 使用 typeof 进行类型守卫
function processValue(value: string | number) {
  if (typeof value === "string") {
    // 在这个作用域内,TypeScript 知道 value 是 string 类型
    return value.toUpperCase();
  } else {
    // 在这个作用域内,TypeScript 知道 value 是 number 类型
    return value.toFixed(2);
  }
}

// 使用 instanceof 进行类型守卫
class Dog {
  bark() { return "Woof!"; }
}

class Cat {
  meow() { return "Meow!"; }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    return animal.bark();
  } else {
    return animal.meow();
  }
}

// 自定义类型守卫
interface Bird {
  fly: () => void;
  layEggs: () => void;
}

interface Fish {
  swim: () => void;
  layEggs: () => void;
}

function isBird(pet: Bird | Fish): pet is Bird {
  return (pet as Bird).fly !== undefined;
}

function getPetAction(pet: Bird | Fish) {
  pet.layEggs(); // 共同方法
  
  if (isBird(pet)) {
    pet.fly(); // Bird 特有方法
  } else {
    pet.swim(); // Fish 特有方法
  }
}

二、高级类型详解

1. 泛型

泛型是 TypeScript 中最强大的特性之一,它允许我们在定义函数、接口或类时使用类型参数:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用泛型函数
let output1 = identity<string>("myString"); // 明确指定类型
let output2 = identity(42); // 类型推断

// 泛型接口
interface GenericIdentityFn<T> {
  (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

// 泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

// 泛型约束
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // 现在我们知道它有一个 .length 属性
  return arg;
}

// 泛型参数约束另一个泛型参数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // 正确
getProperty(x, "m"); // 错误,"m" 不在 "a", "b", 或 "c" 中

2. 映射类型

映射类型允许我们基于旧类型创建新类型:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 只读映射类型
interface Person {
  name: string;
  age: number;
}

// 使所有属性只读
type ReadonlyPerson = Readonly<Person>;

// 部分映射类型
// 使所有属性可选
type PartialPerson = Partial<Person>;

// 必需映射类型
// 使所有属性必需
type RequiredPerson = Required<Person>;

// 选择映射类型
// 从类型中选择一组属性
type PickPerson = Pick<Person, "name">;

// 记录映射类型
// 构造一个属性键为 K,属性值为 T 的类型
type RecordPerson = Record<string, Person>;

// 排除映射类型
// 从联合类型中排除某些类型
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

// 提取映射类型
// 从联合类型中提取某些类型
type T1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"

// 非空映射类型
// 从类型中排除 null 和 undefined
type T2 = NonNullable<string | number | null | undefined>; // string | number

// 自定义映射类型
// 使属性值变为Promise
type PromiseProps<T> = {
  [K in keyof T]: Promise<T[K]>;
};

type AsyncPerson = PromiseProps<Person>;

3. 条件类型

条件类型允许我们基于类型关系创建新类型:

 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
30
31
32
33
34
// 基本条件类型
// T extends U ? X : Y
// 如果 T 可分配给 U,则类型为 X,否则为 Y
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 T3 = TypeName<string | number>; // "string" | "number"

// 类型推断
// 使用 infer 关键字在条件类型中推断类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type T4 = ReturnType<() => string>; // string

// 递归条件类型
// 提取数组元素类型
type Flatten<T> = T extends Array<infer U> ? U : T;

type T5 = Flatten<string[]>; // string

type T6 = Flatten<number>; // number

// 递归扁平化数组
type DeepFlatten<T> = 
  T extends Array<infer U> ? DeepFlatten<U> : T;

type T7 = DeepFlatten<number[][]>; // number

三、类型体操与高级技巧

1. 字符串字面量类型与模板字面量类型

字符串字面量类型和模板字面量类型允许我们创建更精确的字符串类型:

 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
30
31
32
33
34
35
36
37
38
39
40
// 字符串字面量类型
type Direction = "north" | "south" | "east" | "west";

function move(direction: Direction) {
  console.log(`Moving ${direction}`);
}

move("north"); // 正确
move("up"); // 错误

// 模板字面量类型(TypeScript 4.1+)
type Greeting = `Hello, ${string}!`;

const validGreeting: Greeting = "Hello, World!"; // 正确
const invalidGreeting: Greeting = "Hi, World!"; // 错误

// 高级模板字面量类型
type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;

type T8 = EventName<"value">; // "valueChanged"
type T9 = Concat<"Hello", " World">; // "Hello World"

// 字符串联合类型映射
const Colors = {
  red: 'red',
  green: 'green',
  blue: 'blue'
} as const;

type Color = typeof Colors[keyof typeof Colors]; // "red" | "green" | "blue"

// 字符串转换类型
type Capitalize<T extends string> = intrinsic;
type Uncapitalize<T extends string> = intrinsic;
type Uppercase<T extends string> = intrinsic;
type Lowercase<T extends string> = intrinsic;

type T10 = Capitalize<"hello">; // "Hello"
type T11 = Uppercase<"hello">; // "HELLO"

2. 函数重载与参数推断

函数重载允许我们为同一个函数提供多个类型定义:

 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
30
31
32
33
// 函数重载
function reverse(x: string): string;
function reverse(x: number): number;
function reverse(x: string | number): string | number {
  if (typeof x === 'string') {
    return x.split('').reverse().join('');
  } else {
    return Number(x.toString().split('').reverse().join(''));
  }
}

// 高级参数推断
type Func1 = (a: number, b: string) => string;
type Func2 = (a: boolean, b: number, c: string) => number;

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

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

type T12 = ArgsType<Func1>; // [number, string]
type T13 = ReturnType<Func1>; // string

// 函数组合类型
type Compose<A, B, C> = (b: B) => C) => (a: A) => B) => (a: A) => C;

// 剩余参数推断
function createTuple<T extends any[]>(...args: T): T {
  return args;
}

const tuple = createTuple(1, "hello", true); // [number, string, boolean]

3. 高级类型体操示例

一些高级类型体操示例,展示 TypeScript 类型系统的强大能力:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 1. 获取对象的所有属性路径
type Paths<T, D extends number = 10> = [D] extends [never]
  ? never
  : T extends object
  ? { 
      [K in keyof T]-?: K extends string
        ? `${K}` | (Paths<T[K], Decrement<D>> extends infer R
            ? R extends string
              ? `${K}.${R}`
              : never
            : never)
        : never
    }[keyof T]
  : never;

// 辅助类型:减1
type Decrement<N extends number> = 
  [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][N];

interface User {
  name: string;
  address: {
    street: string;
    city: string;
    zip: number;
  };
  contacts: {
    email: string;
    phone: string;
  }[];
}

type UserPaths = Paths<User>;
// "name" | "address" | "address.street" | "address.city" | "address.zip" | "contacts"

// 2. 深度部分类型
type DeepPartial<T> = T extends Function
  ? T
  : T extends Array<infer U>
  ? DeepPartialArray<U>
  : T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

interface DeepPartialArray<T> extends Array<DeepPartial<T>> {}

// 3. 类型差异
type Diff<T, U> = T extends U ? never : T;

type T14 = Diff<string | number | boolean, string>; // number | boolean

// 4. 排除空属性
type NonEmpty<T> = {
  [K in keyof T]: T[K] extends null | undefined ? never : K;
}[keyof T];

type T15 = NonEmpty<{ a: string; b: null; c: undefined; d: number }>; // "a" | "d"

// 5. 可辨识联合类型
enum ShapeKind {
  Circle,
  Square,
}

interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}

interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case ShapeKind.Circle:
      return Math.PI * shape.radius ** 2;
    case ShapeKind.Square:
      return shape.sideLength ** 2;
  }
}

四、实战案例

1. 类型安全的 HTTP 客户端

使用 TypeScript 创建一个类型安全的 HTTP 客户端:

  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
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// 定义 API 响应类型
interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: { code: string; message: string };
}

// 定义 API 端点类型
interface ApiEndpoints {
  '/users': {
    GET: { query: { page?: number; limit?: number }; response: { users: User[]; total: number } };
    POST: { body: Omit<User, 'id'>; response: User };
  };
  '/users/:id': {
    GET: { params: { id: string }; response: User };
    PUT: { params: { id: string }; body: Partial<User>; response: User };
    DELETE: { params: { id: string }; response: { success: boolean } };
  };
}

// 用户类型
interface User {
  id: string;
  name: string;
  email: string;
  age?: number;
}

// 类型安全的 HTTP 客户端
class HttpClient<Endpoints extends Record<string, any>> {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  // GET 请求
  async get<
    Endpoint extends keyof Endpoints & string,
    Method extends keyof Endpoints[Endpoint] & 'GET',
    Response = Endpoints[Endpoint][Method]['response']
  >(
    endpoint: Endpoint,
    options?: {
      params?: Endpoints[Endpoint][Method]['params'];
      query?: Endpoints[Endpoint][Method]['query'];
    }
  ): Promise<ApiResponse<Response>> {
    let url = `${this.baseUrl}${endpoint}`;

    // 处理路径参数
    if (options?.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        url = url.replace(`:${key}`, String(value));
      });
    }

    // 处理查询参数
    if (options?.query) {
      const queryString = new URLSearchParams(
        options.query as Record<string, string>
      ).toString();
      if (queryString) {
        url += `?${queryString}`;
      }
    }

    try {
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const data = await response.json();
      return {
        success: response.ok,
        data: response.ok ? data : undefined,
        error: !response.ok ? { code: String(response.status), message: data.message || '请求失败' } : undefined,
      };
    } catch (error) {
      return {
        success: false,
        error: { code: 'NETWORK_ERROR', message: error instanceof Error ? error.message : '网络错误' },
      };
    }
  }

  // POST 请求
  async post<
    Endpoint extends keyof Endpoints & string,
    Method extends keyof Endpoints[Endpoint] & 'POST',
    Response = Endpoints[Endpoint][Method]['response']
  >(
    endpoint: Endpoint,
    options: {
      params?: Endpoints[Endpoint][Method]['params'];
      body: Endpoints[Endpoint][Method]['body'];
    }
  ): Promise<ApiResponse<Response>> {
    let url = `${this.baseUrl}${endpoint}`;

    // 处理路径参数
    if (options?.params) {
      Object.entries(options.params).forEach(([key, value]) => {
        url = url.replace(`:${key}`, String(value));
      });
    }

    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(options.body),
      });

      const data = await response.json();
      return {
        success: response.ok,
        data: response.ok ? data : undefined,
        error: !response.ok ? { code: String(response.status), message: data.message || '请求失败' } : undefined,
      };
    } catch (error) {
      return {
        success: false,
        error: { code: 'NETWORK_ERROR', message: error instanceof Error ? error.message : '网络错误' },
      };
    }
  }

  // PUT 和 DELETE 方法类似...
}

// 使用示例
const api = new HttpClient<ApiEndpoints>('https://api.example.com');

// 类型安全的 API 调用
async function fetchUsers() {
  // 自动完成和类型检查
  const response = await api.get('/users', {
    query: { page: 1, limit: 10 },
  });

  if (response.success && response.data) {
    // response.data 类型为 { users: User[], total: number }
    console.log(response.data.users);
  }
}

async function createUser() {
  const response = await api.post('/users', {
    body: { name: 'John Doe', email: 'john@example.com' },
  });

  if (response.success && response.data) {
    // response.data 类型为 User
    console.log(response.data.id);
  }
}

2. 类型安全的状态管理

使用 TypeScript 创建一个类型安全的状态管理库:

  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
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 定义状态管理库接口
interface Store<State> {
  getState: () => Readonly<State>;
  setState: (newState: Partial<State> | ((state: State) => Partial<State>)) => void;
  subscribe: (listener: (state: Readonly<State>) => void) => () => void;
  get: <K extends keyof State>(key: K) => State[K];
  set: <K extends keyof State>(key: K, value: State[K] | ((prev: State[K]) => State[K])) => void;
}

// 创建状态管理库
function createStore<State>(initialState: State): Store<State> {
  let state = { ...initialState };
  const listeners: ((state: Readonly<State>) => void)[] = [];

  // 获取完整状态
  const getState = (): Readonly<State> => {
    return state;
  };

  // 设置部分或全部状态
  const setState = (newState: Partial<State> | ((state: State) => Partial<State>)) => {
    const nextState = {
      ...state,
      ...(typeof newState === 'function' ? newState(state) : newState),
    };
    
    state = nextState;
    
    // 通知所有监听器
    listeners.forEach((listener) => listener(state));
  };

  // 订阅状态变化
  const subscribe = (listener: (state: Readonly<State>) => void): (() => void) => {
    listeners.push(listener);
    
    // 返回取消订阅的函数
    return () => {
      const index = listeners.indexOf(listener);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    };
  };

  // 获取单个状态值
  const get = <K extends keyof State>(key: K): State[K] => {
    return state[key];
  };

  // 设置单个状态值
  const set = <K extends keyof State>(key: K, value: State[K] | ((prev: State[K]) => State[K])) => {
    setState({
      [key]: typeof value === 'function' ? (value as Function)(state[key]) : value,
    });
  };

  return {
    getState,
    setState,
    subscribe,
    get,
    set,
  };
}

// 定义应用状态类型
interface AppState {
  counter: {
    count: number;
    step: number;
  };
  user: {
    isLoggedIn: boolean;
    name: string | null;
    permissions: string[];
  };
  ui: {
    theme: 'light' | 'dark';
    sidebarOpen: boolean;
  };
}

// 初始状态
const initialState: AppState = {
  counter: {
    count: 0,
    step: 1,
  },
  user: {
    isLoggedIn: false,
    name: null,
    permissions: [],
  },
  ui: {
    theme: 'light',
    sidebarOpen: true,
  },
};

// 创建应用状态管理
const store = createStore<AppState>(initialState);

// 使用示例

// 订阅状态变化
const unsubscribe = store.subscribe((state) => {
  console.log('State changed:', state);
});

// 更新状态
store.setState({
  counter: {
    ...store.getState().counter,
    count: 10,
  },
});

// 使用函数更新状态
store.setState((prevState) => ({
  counter: {
    ...prevState.counter,
    count: prevState.counter.count + prevState.counter.step,
  },
}));

// 获取和设置单个状态
const count = store.get('counter');
console.log(count); // { count: 11, step: 1 }

store.set('ui', {
  ...store.get('ui'),
  theme: 'dark',
});

// 使用函数设置单个状态
store.set('counter', (prev) => ({
  ...prev,
  count: prev.count + 1,
}));

// 取消订阅
// unsubscribe();

// 创建状态选择器(类似 Redux 的 selector)
const selectCount = (state: AppState) => state.counter.count;
const selectUserName = (state: AppState) => state.user.name;

// 使用选择器
const currentCount = selectCount(store.getState());
const currentUserName = selectUserName(store.getState());

3. 类型安全的表单验证

使用 TypeScript 创建一个类型安全的表单验证库:

  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
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// 验证规则类型
interface ValidationRule<T = any> {
  message: string;
  validate: (value: T) => boolean;
}

// 验证结果类型
interface ValidationResult {
  isValid: boolean;
  errors: Record<string, string[]>;
}

// 表单验证器类型
type Validator<T extends Record<string, any>> = {
  [K in keyof T]: ValidationRule<T[K]>[];
};

// 创建表单验证函数
function createValidator<T extends Record<string, any>>(validator: Validator<T>) {
  // 验证单个字段
  function validateField<K extends keyof T>(fieldName: K, value: T[K]): string[] {
    const rules = validator[fieldName] || [];
    const errors: string[] = [];

    for (const rule of rules) {
      if (!rule.validate(value)) {
        errors.push(rule.message);
      }
    }

    return errors;
  }

  // 验证整个表单
  function validateForm(formData: T): ValidationResult {
    const errors: Record<string, string[]> = {};
    let isValid = true;

    Object.keys(validator).forEach((fieldName) => {
      const fieldErrors = validateField(fieldName as keyof T, formData[fieldName as keyof T]);
      
      if (fieldErrors.length > 0) {
        errors[fieldName] = fieldErrors;
        isValid = false;
      }
    });

    return {
      isValid,
      errors,
    };
  }

  // 获取单个规则函数
  function getRule<K extends keyof T>(fieldName: K, index: number): ValidationRule<T[K]> | undefined {
    return validator[fieldName]?.[index];
  }

  return {
    validateField,
    validateForm,
    getRule,
  };
}

// 常用验证规则
const required = <T>(message: string): ValidationRule<T> => ({
  message,
  validate: (value: T) => value !== undefined && value !== null && value !== '',
});

const minLength = (message: string, min: number): ValidationRule<string> => ({
  message,
  validate: (value: string) => typeof value === 'string' && value.length >= min,
});

const maxLength = (message: string, max: number): ValidationRule<string> => ({
  message,
  validate: (value: string) => typeof value === 'string' && value.length <= max,
});

const pattern = (message: string, regex: RegExp): ValidationRule<string> => ({
  message,
  validate: (value: string) => typeof value === 'string' && regex.test(value),
});

const min = (message: string, min: number): ValidationRule<number> => ({
  message,
  validate: (value: number) => typeof value === 'number' && value >= min,
});

const max = (message: string, max: number): ValidationRule<number> => ({
  message,
  validate: (value: number) => typeof value === 'number' && value <= max,
});

const email = (message: string = '请输入有效的邮箱地址'): ValidationRule<string> => ({
  message,
  validate: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
});

// 表单数据类型
interface LoginForm {
  username: string;
  password: string;
  rememberMe: boolean;
}

interface RegisterForm {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  agreeTerms: boolean;
}

// 登录表单验证器
const loginFormValidator = createValidator<LoginForm>({
  username: [
    required('用户名不能为空'),
    minLength('用户名长度至少为3个字符', 3),
    maxLength('用户名长度不能超过20个字符', 20),
  ],
  password: [
    required('密码不能为空'),
    minLength('密码长度至少为6个字符', 6),
    maxLength('密码长度不能超过50个字符', 50),
  ],
  rememberMe: [],
});

// 注册表单验证器
const registerFormValidator = createValidator<RegisterForm>({
  username: [
    required('用户名不能为空'),
    minLength('用户名长度至少为3个字符', 3),
    maxLength('用户名长度不能超过20个字符', 20),
  ],
  email: [
    required('邮箱不能为空'),
    email(),
  ],
  password: [
    required('密码不能为空'),
    minLength('密码长度至少为6个字符', 6),
    pattern('密码必须包含至少一个数字和一个字母', /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/),
  ],
  confirmPassword: [], // 特殊规则,需要与密码比较
  agreeTerms: [
    {
      message: '请同意服务条款',
      validate: (value: boolean) => value === true,
    },
  ],
});

// 使用示例 - 登录表单验证
function validateLoginForm(formData: LoginForm): ValidationResult {
  return loginFormValidator.validateForm(formData);
}

// 使用示例 - 注册表单验证(包含跨字段验证)
function validateRegisterForm(formData: RegisterForm): ValidationResult {
  // 先执行基本验证
  const basicResult = registerFormValidator.validateForm(formData);
  
  // 添加跨字段验证(确认密码)
  if (formData.password !== formData.confirmPassword) {
    basicResult.isValid = false;
    basicResult.errors.confirmPassword = ['两次输入的密码不一致'];
  }
  
  return basicResult;
}

// 测试验证
const loginForm: LoginForm = {
  username: 'john',
  password: 'password123',
  rememberMe: true,
};

const registerForm: RegisterForm = {
  username: 'john_doe',
  email: 'john@example.com',
  password: 'Password123',
  confirmPassword: 'Password123',
  agreeTerms: true,
};

console.log('Login form validation:', validateLoginForm(loginForm));
console.log('Register form validation:', validateRegisterForm(registerForm));

总结

TypeScript 的类型系统是一个功能强大的工具,可以帮助我们在编译时发现错误,提高代码质量和开发效率。本文通过详细介绍 TypeScript 的高级类型特性,包括泛型、映射类型、条件类型等,并通过实战案例展示了如何在实际项目中应用这些特性,构建类型安全的应用程序。

主要内容回顾:

  1. 类型系统基础回顾:基本类型、类型推断、类型断言和类型守卫
  2. 高级类型详解:泛型、映射类型和条件类型
  3. 类型体操与高级技巧:字符串字面量类型、模板字面量类型、函数重载和参数推断
  4. 实战案例:类型安全的 HTTP 客户端、状态管理和表单验证

掌握 TypeScript 的类型系统需要不断的实践和学习。通过深入理解这些高级特性,我们可以充分利用 TypeScript 的优势,编写更安全、更可维护的代码。在实际项目中,我们应该根据项目的复杂度和团队的熟悉程度,合理地使用这些高级特性,避免过度设计和类型过于复杂。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计