React + TypeScript 笔试题库

内容分享2天前发布
0 0 0

核心技术栈: UmiMax + React + Ant Design Pro + TypeScript

其他技术: Dva/Redux、ProComponents、Ahooks、Ant Design Charts


目录

TypeScript 基础TypeScript 高级类型React + TypeScriptUmiMax 相关Ant Design Pro 相关泛型与工具类型类型体操实战编程题架构设计题


TypeScript 基础

题目 1:类型推断


// 问:以下代码中,变量的类型是什么?

const a = 123;
const b = 'hello';
const c = [1, 2, 3];
const d = { name: 'Alice', age: 20 };
const e = [1, 'hello', true];

// 答案
// a: number
// b: string
// c: number[]
// d: { name: string; age: number; }
// e: (string | number | boolean)[]

题目 2:interface vs type

问:以下代码有什么问题?如何修复?


// 问题代码
type User = {
  name: string;
  age: number;
};

type User = {
  email: string;
};

// 答案:type 不能重复声明
// 修复方案 1:使用 interface
interface User {
  name: string;
  age: number;
}

interface User {
  email: string;
}
// 结果:User { name: string; age: number; email: string; }

// 修复方案 2:使用交叉类型
type User = {
  name: string;
  age: number;
} & {
  email: string;
};

题目 3:函数重载

实现一个函数,根据参数类型返回不同的结果


// 要求实现
function getValue(key: string): string;
function getValue(key: number): number;
function getValue(key: boolean): boolean;

// 答案
function getValue(key: string): string;
function getValue(key: number): number;
function getValue(key: boolean): boolean;
function getValue(key: string | number | boolean): string | number | boolean {
  if (typeof key === 'string') {
    return `String: ${key}`;
  } else if (typeof key === 'number') {
    return key * 2;
  } else {
    return !key;
  }
}

// 测试
console.log(getValue('hello')); // "String: hello"
console.log(getValue(10));      // 20
console.log(getValue(true));    // false

题目 4:枚举类型

问:以下两种枚举有什么区别?


// 数字枚举
enum Status1 {
  Pending,
  Success,
  Failed,
}

// 字符串枚举
enum Status2 {
  Pending = 'PENDING',
  Success = 'SUCCESS',
  Failed = 'FAILED',
}

// 答案:
// 1. 数字枚举支持反向映射,字符串枚举不支持
console.log(Status1[0]); // "Pending"
console.log(Status2['PENDING']); // undefined

// 2. 数字枚举会自增,字符串枚举需要手动赋值
// 3. 字符串枚举更易调试(查看网络请求时更清晰)
// 4. 推荐使用字符串枚举或 const enum

题目 5:类型守卫

实现一个类型守卫函数判断是否为字符串数组


// 实现 isStringArray
function processData(data: unknown) {
  if (isStringArray(data)) {
    // 这里 data 应该被推断为 string[]
    data.forEach((item) => console.log(item.toUpperCase()));
  }
}

// 答案
function isStringArray(value: unknown): value is string[] {
  return (
    Array.isArray(value) && 
    value.every((item) => typeof item === 'string')
  );
}

// 测试
processData(['a', 'b', 'c']); // 正常执行
processData([1, 2, 3]);       // 不会执行

TypeScript 高级类型

题目 6:联合类型与交叉类型

问:以下代码的输出类型是什么?


type A = { name: string; age: number };
type B = { name: string; email: string };

type C = A & B;
type D = A | B;

const c: C = {
  name: 'Alice',
  age: 20,
  email: 'alice@example.com',
};

const d1: D = { name: 'Bob', age: 25 };
const d2: D = { name: 'Charlie', email: 'charlie@example.com' };

// 答案:
// C = { name: string; age: number; email: string } (交叉类型,包含所有属性)
// D = A | B (联合类型,可以是 A 或 B)

题目 7:映射类型

实现一个将所有属性变为可选的工具类型


// 要求实现 MyPartial
type MyPartial<T> = ?;

// 测试
interface User {
  id: number;
  name: string;
  email: string;
}

type PartialUser = MyPartial<User>;
// 应该等价于
// { id?: number; name?: string; email?: string; }

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

// 进阶:实现深度可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

题目 8:条件类型

实现一个提取 Promise 返回值类型的工具


// 要求实现 UnwrapPromise
type UnwrapPromise<T> = ?;

// 测试
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<number>>; // number
type C = UnwrapPromise<string>;          // string

// 答案
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;

// 进阶:支持嵌套 Promise
type DeepUnwrapPromise<T> = T extends Promise<infer R>
  ? DeepUnwrapPromise<R>
  : T;

type D = DeepUnwrapPromise<Promise<Promise<string>>>; // string

题目 9:infer 关键字

实现一个获取函数返回值类型的工具


// 要求实现 MyReturnType
type MyReturnType<T> = ?;

// 测试
function getUser() {
  return { name: 'Alice', age: 20 };
}

type User = MyReturnType<typeof getUser>;
// 应该是 { name: string; age: number }

// 答案
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

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

function add(a: number, b: number): number {
  return a + b;
}

type Params = MyParameters<typeof add>; // [number, number]

题目 10:模板字面量类型

实现一个将字符串首字母大写的类型


// 要求实现 Capitalize
type Capitalize<S extends string> = ?;

// 测试
type A = Capitalize<'hello'>; // 'Hello'
type B = Capitalize<'world'>; // 'World'

// 答案
type Capitalize<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${Uppercase<First>}${Rest}`
  : S;

// 进阶:实现驼峰命名转换
type CamelCase<S extends string> = S extends `${infer Head}_${infer Tail}`
  ? `${Head}${Capitalize<CamelCase<Tail>>}`
  : S;

type C = CamelCase<'user_name'>; // 'userName'
type D = CamelCase<'get_user_by_id'>; // 'getUserById'

React + TypeScript

题目 11:React 组件类型

定义一个 Button 组件的 Props 类型


// 要求:
// 1. 支持所有原生 button 属性
// 2. 添加自定义 loading 属性
// 3. children 必填

// 答案
import React from 'react';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  loading?: boolean;
  children: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({ 
  loading, 
  children, 
  disabled,
  ...rest 
}) => {
  return (
    <button disabled={disabled || loading} {...rest}>
      {loading ? 'Loading...' : children}
    </button>
  );
};

// 使用
<Button onClick={() => {}} loading={true}>
  Click me
</Button>

题目 12:useState 类型推断

问:以下代码有什么问题?如何修复?


// 问题代码
function UserProfile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then((data) => {
      setUser(data); // data 可能是 User 类型
    });
  }, []);

  return <div>{user.name}</div>; // 错误:user 可能为 null
}

// 答案 1:使用类型断言
interface User {
  name: string;
  age: number;
}

const [user, setUser] = useState<User | null>(null);

return <div>{user?.name}</div>; // 使用可选链

// 答案 2:使用默认值
const [user, setUser] = useState<User>({
  name: '',
  age: 0,
});

// 答案 3:使用类型守卫
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;

题目 13:useRef 类型

实现一个自动聚焦的输入框组件


// 答案
import React, { useRef, useEffect } from 'react';

const AutoFocusInput: React.FC = () => {
  // 方法 1:使用泛型
  const inputRef = useRef<HTMLInputElement>(null);

  // 方法 2:使用类型断言
  // const inputRef = useRef<HTMLInputElement>(null!);

  useEffect(() => {
    // null 检查
    if (inputRef.current) {
      inputRef.current.focus();
    }

    // 或使用可选链
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} type="text" />;
};

// 进阶:转发 ref
interface InputProps {
  placeholder?: string;
}

const ForwardedInput = React.forwardRef<HTMLInputElement, InputProps>(
  ({ placeholder }, ref) => {
    return <input ref={ref} placeholder={placeholder} />;
  }
);

题目 14:事件处理类型

定义正确的事件处理函数类型


// 答案
import React from 'react';

interface FormProps {
  onSubmit: (data: FormData) => void;
}

const Form: React.FC<FormProps> = ({ onSubmit }) => {
  // 表单提交事件
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    onSubmit(formData);
  };

  // 输入框变化事件
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  // 按钮点击事件
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    console.log('Button clicked');
  };

  // 键盘事件
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onChange={handleChange} onKeyDown={handleKeyDown} />
      <button onClick={handleClick}>Submit</button>
    </form>
  );
};

题目 15:自定义 Hook 类型

实现一个 useLocalStorage Hook


// 答案
import { useState, useEffect } from 'react';

function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
  // 从 localStorage 读取初始值
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  // 保存到 localStorage
  const setValue = (value: T | ((prev: T) => T)) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

// 使用
interface User {
  name: string;
  email: string;
}

function App() {
  const [user, setUser] = useLocalStorage<User>('user', {
    name: '',
    email: '',
  });

  return (
    <div>
      <input
        value={user.name}
        onChange={(e) => setUser({ ...user, name: e.target.value })}
      />
    </div>
  );
}

UmiMax 相关

题目 16:路由配置类型

定义 UmiMax 路由配置的类型


// 答案
import { IRoute } from '@umijs/max';

const routes: IRoute[] = [
  {
    path: '/',
    component: '@/layouts/index',
    routes: [
      {
        path: '/home',
        component: '@/pages/Home',
        name: '首页',
        icon: 'home',
      },
      {
        path: '/user',
        name: '用户管理',
        routes: [
          {
            path: '/user/list',
            component: '@/pages/User/List',
            name: '用户列表',
          },
          {
            path: '/user/:id',
            component: '@/pages/User/Detail',
            name: '用户详情',
          },
        ],
      },
    ],
  },
];

export default routes;

题目 17:UmiMax useModel 类型

实现一个带类型的全局状态管理


// models/useUserModel.ts
import { useState, useCallback } from 'react';

export interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

export default function useUserModel() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(false);

  const login = useCallback(async (email: string, password: string) => {
    setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
      });
      const data: User = await response.json();
      setUser(data);
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    } finally {
      setLoading(false);
    }
  }, []);

  const logout = useCallback(() => {
    setUser(null);
  }, []);

  return {
    user,
    loading,
    login,
    logout,
  };
}

// 使用
import { useModel } from '@umijs/max';

function UserProfile() {
  const { user, logout } = useModel('useUserModel');

  if (!user) {
    return <div>Please login</div>;
  }

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

题目 18:Request 配置类型

定义 UmiMax Request 拦截器类型


// app.tsx
import type { RequestConfig } from '@umijs/max';

export const request: RequestConfig = {
  timeout: 10000,
  
  // 请求拦截器
  requestInterceptors: [
    (url, options) => {
      const token = localStorage.getItem('token');
      
      return {
        url,
        options: {
          ...options,
          headers: {
            ...options.headers,
            Authorization: `Bearer ${token}`,
          },
        },
      };
    },
  ],

  // 响应拦截器
  responseInterceptors: [
    async (response) => {
      const data = await response.clone().json();
      
      if (data.code !== 0) {
        throw new Error(data.message);
      }
      
      return response;
    },
  ],

  // 错误处理
  errorConfig: {
    errorHandler: (error: any) => {
      console.error('Request error:', error);
    },
  },
};

题目 19:Access 权限类型

实现基于角色的权限控制


// access.ts
export default function access(initialState: { currentUser?: API.CurrentUser }) {
  const { currentUser } = initialState || {};

  return {
    canAdmin: currentUser?.role === 'admin',
    canEdit: currentUser?.role === 'admin' || currentUser?.role === 'editor',
    canView: !!currentUser,
  };
}

// 使用 Access 组件
import { Access, useAccess } from '@umijs/max';

function AdminPanel() {
  const access = useAccess();

  return (
    <Access accessible={access.canAdmin} fallback={<div>No permission</div>}>
      <div>Admin Panel</div>
    </Access>
  );
}

// 在路由中使用
const routes = [
  {
    path: '/admin',
    component: '@/pages/Admin',
    access: 'canAdmin',
  },
];

Ant Design Pro 相关

题目 20:ProTable 类型定义

定义 ProTable 的数据类型和列配置


import { ProTable } from '@ant-design/pro-components';
import type { ProColumns } from '@ant-design/pro-components';

// 数据类型
interface UserItem {
  id: number;
  name: string;
  email: string;
  age: number;
  status: 'active' | 'inactive';
  createdAt: string;
}

// 列配置
const columns: ProColumns<UserItem>[] = [
  {
    title: 'ID',
    dataIndex: 'id',
    key: 'id',
    width: 80,
    search: false,
  },
  {
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
    copyable: true,
  },
  {
    title: '邮箱',
    dataIndex: 'email',
    key: 'email',
  },
  {
    title: '年龄',
    dataIndex: 'age',
    key: 'age',
    valueType: 'digit',
    sorter: true,
  },
  {
    title: '状态',
    dataIndex: 'status',
    key: 'status',
    valueType: 'select',
    valueEnum: {
      active: { text: '激活', status: 'Success' },
      inactive: { text: '禁用', status: 'Error' },
    },
  },
  {
    title: '创建时间',
    dataIndex: 'createdAt',
    key: 'createdAt',
    valueType: 'dateTime',
    search: false,
  },
  {
    title: '操作',
    valueType: 'option',
    key: 'option',
    render: (text, record, _, action) => [
      <a key="edit" onClick={() => handleEdit(record)}>
        编辑
      </a>,
      <a key="delete" onClick={() => handleDelete(record.id)}>
        删除
      </a>,
    ],
  },
];

// 组件
const UserTable = () => {
  const actionRef = useRef<ActionType>();

  return (
    <ProTable<UserItem>
      columns={columns}
      actionRef={actionRef}
      request={async (params, sort, filter) => {
        const response = await fetchUserList(params);
        return {
          data: response.data,
          success: true,
          total: response.total,
        };
      }}
      rowKey="id"
      search={{
        labelWidth: 'auto',
      }}
      pagination={{
        pageSize: 10,
      }}
    />
  );
};

题目 21:ProForm 类型定义

实现一个用户表单组件


import { ProForm, ProFormText, ProFormSelect } from '@ant-design/pro-components';
import type { ProFormInstance } from '@ant-design/pro-components';

interface UserFormValues {
  name: string;
  email: string;
  role: 'admin' | 'user' | 'editor';
  phone?: string;
}

const UserForm: React.FC = () => {
  const formRef = useRef<ProFormInstance<UserFormValues>>();

  const handleSubmit = async (values: UserFormValues) => {
    try {
      await createUser(values);
      message.success('创建成功');
      return true;
    } catch (error) {
      message.error('创建失败');
      return false;
    }
  };

  return (
    <ProForm<UserFormValues>
      formRef={formRef}
      onFinish={handleSubmit}
      initialValues={{
        role: 'user',
      }}
    >
      <ProFormText
        name="name"
        label="姓名"
        placeholder="请输入姓名"
        rules={[{ required: true, message: '请输入姓名' }]}
      />
      
      <ProFormText
        name="email"
        label="邮箱"
        placeholder="请输入邮箱"
        rules={[
          { required: true, message: '请输入邮箱' },
          { type: 'email', message: '邮箱格式不正确' },
        ]}
      />

      <ProFormSelect
        name="role"
        label="角色"
        options={[
          { label: '管理员', value: 'admin' },
          { label: '编辑', value: 'editor' },
          { label: '用户', value: 'user' },
        ]}
        rules={[{ required: true, message: '请选择角色' }]}
      />

      <ProFormText
        name="phone"
        label="手机号"
        placeholder="请输入手机号"
      />
    </ProForm>
  );
};

泛型与工具类型

题目 22:实现 Pick 工具类型


// 实现 MyPick,从对象类型中选取指定的键
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// 测试
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type UserBasic = MyPick<User, 'id' | 'name'>;
// 结果:{ id: number; name: string; }

题目 23:实现 Omit 工具类型


// 实现 MyOmit,从对象类型中排除指定的键
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// 或者
type MyOmit2<T, K extends keyof T> = {
  [P in Exclude<keyof T, K>]: T[P];
};

// 测试
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

type UserWithoutPassword = MyOmit<User, 'password'>;
// 结果:{ id: number; name: string; email: string; }

题目 24:实现 Record 工具类型


// 实现 MyRecord
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

// 测试
type Roles = 'admin' | 'user' | 'editor';

type RolePermissions = MyRecord<Roles, string[]>;
// 结果:
// {
//   admin: string[];
//   user: string[];
//   editor: string[];
// }

const permissions: RolePermissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read'],
  editor: ['read', 'write'],
};

题目 25:实现 Readonly 工具类型


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

// 深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

// 测试
interface User {
  name: string;
  settings: {
    theme: string;
    language: string;
  };
}

type ReadonlyUser = DeepReadonly<User>;

const user: ReadonlyUser = {
  name: 'Alice',
  settings: {
    theme: 'dark',
    language: 'zh-CN',
  },
};

// user.name = 'Bob'; // 错误
// user.settings.theme = 'light'; // 错误

类型体操

题目 26:获取数组元素类型


// 实现 ArrayElement
type ArrayElement<T> = T extends (infer E)[] ? E : never;

// 测试
type A = ArrayElement<string[]>; // string
type B = ArrayElement<number[]>; // number
type C = ArrayElement<Array<{ name: string }>>; // { name: string }

题目 27:元组转联合类型


// 实现 TupleToUnion
type TupleToUnion<T extends any[]> = T[number];

// 测试
type A = TupleToUnion<[string, number, boolean]>; // string | number | boolean
type B = TupleToUnion<['a', 'b', 'c']>; // 'a' | 'b' | 'c'

题目 28:实现字符串长度计算


// 实现 StringLength (使用递归)
type StringLength<S extends string, Acc extends any[] = []> = 
  S extends `${infer First}${infer Rest}`
    ? StringLength<Rest, [...Acc, First]>
    : Acc['length'];

// 测试
type A = StringLength<'hello'>; // 5
type B = StringLength<''>; // 0
type C = StringLength<'TypeScript'>; // 10

题目 29:实现对象键路径


// 获取对象的所有键路径
type PathKeys<T, K extends keyof T = keyof T> = K extends string
  ? T[K] extends Record<string, any>
    ? `${K}` | `${K}.${PathKeys<T[K]>}`
    : `${K}`
  : never;

// 测试
interface User {
  name: string;
  age: number;
  address: {
    city: string;
    street: {
      name: string;
      number: number;
    };
  };
}

type UserPaths = PathKeys<User>;
// 'name' | 'age' | 'address' | 'address.city' | 'address.street' | 'address.street.name' | 'address.street.number'

题目 30:实现 Promise.all 类型


// 实现 PromiseAll
declare function PromiseAll<T extends any[]>(
  values: readonly [...T]
): Promise<{
  [K in keyof T]: T[K] extends Promise<infer R> ? R : T[K];
}>;

// 测试
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = Promise.resolve('foo');

const result = PromiseAll([promise1, promise2, promise3]);
// result 的类型是 Promise<[number, number, string]>

实战编程题

题目 31:实现防抖函数(带类型)


function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null;

  return function (this: any, ...args: Parameters<T>) {
    const context = this;

    if (timeout) clearTimeout(timeout);

    timeout = setTimeout(() => {
      func.apply(context, args);
    }, wait);
  };
}

// 使用
const handleSearch = debounce((keyword: string) => {
  console.log('Searching for:', keyword);
}, 500);

handleSearch('TypeScript');

题目 32:实现节流函数(带类型)


function throttle<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let previous = 0;

  return function (this: any, ...args: Parameters<T>) {
    const now = Date.now();
    const context = this;

    if (now - previous > wait) {
      func.apply(context, args);
      previous = now;
    }
  };
}

// 使用
const handleScroll = throttle(() => {
  console.log('Scrolling...');
}, 200);

window.addEventListener('scroll', handleScroll);

题目 33:实现深拷贝(带类型)


function deepClone<T>(obj: T): T {
  // 处理基本类型和 null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj.getTime()) as any;
  }

  // 处理数组
  if (Array.isArray(obj)) {
    const cloneArr: any[] = [];
    obj.forEach((item, index) => {
      cloneArr[index] = deepClone(item);
    });
    return cloneArr as any;
  }

  // 处理对象
  const cloneObj: any = {};
  Object.keys(obj).forEach((key) => {
    cloneObj[key] = deepClone((obj as any)[key]);
  });

  return cloneObj;
}

// 使用
interface User {
  name: string;
  age: number;
  friends: string[];
  settings: {
    theme: string;
  };
}

const user: User = {
  name: 'Alice',
  age: 20,
  friends: ['Bob', 'Charlie'],
  settings: { theme: 'dark' },
};

const clonedUser = deepClone(user);

题目 34:实现 EventEmitter(带类型)


type EventHandler<T = any> = (data: T) => void;

class EventEmitter<Events extends Record<string, any>> {
  private events: Map<keyof Events, EventHandler<any>[]> = new Map();

  on<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): void {
    const handlers = this.events.get(event) || [];
    handlers.push(handler);
    this.events.set(event, handlers);
  }

  off<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): void {
    const handlers = this.events.get(event);
    if (handlers) {
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    }
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    const handlers = this.events.get(event);
    if (handlers) {
      handlers.forEach((handler) => handler(data));
    }
  }

  once<K extends keyof Events>(event: K, handler: EventHandler<Events[K]>): void {
    const onceHandler: EventHandler<Events[K]> = (data) => {
      handler(data);
      this.off(event, onceHandler);
    };
    this.on(event, onceHandler);
  }
}

// 使用
interface AppEvents {
  userLogin: { userId: number; username: string };
  userLogout: { userId: number };
  messageReceived: { from: string; content: string };
}

const emitter = new EventEmitter<AppEvents>();

emitter.on('userLogin', (data) => {
  console.log(`User ${data.username} logged in`);
});

emitter.emit('userLogin', { userId: 1, username: 'Alice' });

题目 35:实现 LRU 缓存(带类型)


class LRUCache<K, V> {
  private capacity: number;
  private cache: Map<K, V>;

  constructor(capacity: number) {
    this.capacity = capacity;
    this.cache = new Map();
  }

  get(key: K): V | undefined {
    if (!this.cache.has(key)) {
      return undefined;
    }

    // 移到最后(最近使用)
    const value = this.cache.get(key)!;
    this.cache.delete(key);
    this.cache.set(key, value);

    return value;
  }

  put(key: K, value: V): void {
    // 如果已存在,先删除
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }

    // 如果超过容量,删除最久未使用的(第一个)
    if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    // 添加到最后
    this.cache.set(key, value);
  }

  has(key: K): boolean {
    return this.cache.has(key);
  }

  size(): number {
    return this.cache.size;
  }

  clear(): void {
    this.cache.clear();
  }
}

// 使用
const cache = new LRUCache<string, number>(3);

cache.put('a', 1);
cache.put('b', 2);
cache.put('c', 3);
console.log(cache.get('a')); // 1

cache.put('d', 4); // 'b' 被删除
console.log(cache.has('b')); // false

架构设计题

题目 36:设计一个 API 请求封装

要求:

支持 GET、POST、PUT、DELETE支持请求拦截器支持响应拦截器支持错误处理完整的 TypeScript 类型


// types.ts
export interface RequestConfig {
  url: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  params?: Record<string, any>;
  data?: any;
  headers?: Record<string, string>;
}

export interface Response<T = any> {
  code: number;
  data: T;
  message: string;
}

export type RequestInterceptor = (
  config: RequestConfig
) => RequestConfig | Promise<RequestConfig>;

export type ResponseInterceptor<T = any> = (
  response: Response<T>
) => Response<T> | Promise<Response<T>>;

export type ErrorHandler = (error: any) => void;

// request.ts
class HttpClient {
  private baseURL: string;
  private requestInterceptors: RequestInterceptor[] = [];
  private responseInterceptors: ResponseInterceptor[] = [];
  private errorHandler?: ErrorHandler;

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

  // 添加请求拦截器
  useRequestInterceptor(interceptor: RequestInterceptor): void {
    this.requestInterceptors.push(interceptor);
  }

  // 添加响应拦截器
  useResponseInterceptor(interceptor: ResponseInterceptor): void {
    this.responseInterceptors.push(interceptor);
  }

  // 设置错误处理
  setErrorHandler(handler: ErrorHandler): void {
    this.errorHandler = handler;
  }

  // 执行请求拦截器
  private async runRequestInterceptors(
    config: RequestConfig
  ): Promise<RequestConfig> {
    let finalConfig = config;
    
    for (const interceptor of this.requestInterceptors) {
      finalConfig = await interceptor(finalConfig);
    }

    return finalConfig;
  }

  // 执行响应拦截器
  private async runResponseInterceptors<T>(
    response: Response<T>
  ): Promise<Response<T>> {
    let finalResponse = response;

    for (const interceptor of this.responseInterceptors) {
      finalResponse = await interceptor(finalResponse);
    }

    return finalResponse;
  }

  // 核心请求方法
  async request<T = any>(config: RequestConfig): Promise<Response<T>> {
    try {
      // 执行请求拦截器
      const finalConfig = await this.runRequestInterceptors(config);

      // 构建 URL
      const url = new URL(finalConfig.url, this.baseURL);
      if (finalConfig.params) {
        Object.keys(finalConfig.params).forEach((key) => {
          url.searchParams.append(key, finalConfig.params![key]);
        });
      }

      // 发送请求
      const response = await fetch(url.toString(), {
        method: finalConfig.method,
        headers: {
          'Content-Type': 'application/json',
          ...finalConfig.headers,
        },
        body: finalConfig.data ? JSON.stringify(finalConfig.data) : undefined,
      });

      const data: Response<T> = await response.json();

      // 执行响应拦截器
      const finalResponse = await this.runResponseInterceptors(data);

      return finalResponse;
    } catch (error) {
      if (this.errorHandler) {
        this.errorHandler(error);
      }
      throw error;
    }
  }

  // 便捷方法
  get<T = any>(url: string, params?: Record<string, any>): Promise<Response<T>> {
    return this.request<T>({ url, method: 'GET', params });
  }

  post<T = any>(url: string, data?: any): Promise<Response<T>> {
    return this.request<T>({ url, method: 'POST', data });
  }

  put<T = any>(url: string, data?: any): Promise<Response<T>> {
    return this.request<T>({ url, method: 'PUT', data });
  }

  delete<T = any>(url: string, params?: Record<string, any>): Promise<Response<T>> {
    return this.request<T>({ url, method: 'DELETE', params });
  }
}

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

// 添加请求拦截器(添加 token)
httpClient.useRequestInterceptor((config) => {
  const token = localStorage.getItem('token');
  
  return {
    ...config,
    headers: {
      ...config.headers,
      Authorization: `Bearer ${token}`,
    },
  };
});

// 添加响应拦截器(统一处理错误码)
httpClient.useResponseInterceptor((response) => {
  if (response.code !== 0) {
    throw new Error(response.message);
  }
  return response;
});

// 设置错误处理
httpClient.setErrorHandler((error) => {
  console.error('Request error:', error);
  // 可以在这里显示错误提示
});

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

async function getUser(id: number) {
  const response = await httpClient.get<User>(`/users/${id}`);
  return response.data;
}

async function createUser(user: Omit<User, 'id'>) {
  const response = await httpClient.post<User>('/users', user);
  return response.data;
}

题目 37:设计一个表单验证器


type ValidationRule<T> = {
  required?: boolean;
  min?: number;
  max?: number;
  pattern?: RegExp;
  validator?: (value: T) => boolean | Promise<boolean>;
  message?: string;
};

type FormRules<T> = {
  [K in keyof T]?: ValidationRule<T[K]>[];
};

type ValidationError = {
  field: string;
  message: string;
};

class FormValidator<T extends Record<string, any>> {
  private rules: FormRules<T>;

  constructor(rules: FormRules<T>) {
    this.rules = rules;
  }

  async validate(data: T): Promise<{ valid: boolean; errors: ValidationError[] }> {
    const errors: ValidationError[] = [];

    for (const field in this.rules) {
      const fieldRules = this.rules[field];
      const value = data[field];

      if (fieldRules) {
        for (const rule of fieldRules) {
          const error = await this.validateField(field, value, rule);
          if (error) {
            errors.push(error);
          }
        }
      }
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }

  private async validateField<K extends keyof T>(
    field: K,
    value: T[K],
    rule: ValidationRule<T[K]>
  ): Promise<ValidationError | null> {
    // 必填验证
    if (rule.required && !value) {
      return {
        field: field as string,
        message: rule.message || `${String(field)} is required`,
      };
    }

    // 最小值/长度验证
    if (rule.min !== undefined) {
      const length = typeof value === 'string' ? value.length : value;
      if (length < rule.min) {
        return {
          field: field as string,
          message: rule.message || `${String(field)} must be at least ${rule.min}`,
        };
      }
    }

    // 最大值/长度验证
    if (rule.max !== undefined) {
      const length = typeof value === 'string' ? value.length : value;
      if (length > rule.max) {
        return {
          field: field as string,
          message: rule.message || `${String(field)} must be at most ${rule.max}`,
        };
      }
    }

    // 正则验证
    if (rule.pattern && typeof value === 'string') {
      if (!rule.pattern.test(value)) {
        return {
          field: field as string,
          message: rule.message || `${String(field)} format is invalid`,
        };
      }
    }

    // 自定义验证
    if (rule.validator) {
      const valid = await rule.validator(value);
      if (!valid) {
        return {
          field: field as string,
          message: rule.message || `${String(field)} is invalid`,
        };
      }
    }

    return null;
  }
}

// 使用示例
interface UserForm {
  username: string;
  email: string;
  password: string;
  age: number;
}

const validator = new FormValidator<UserForm>({
  username: [
    { required: true, message: '用户名不能为空' },
    { min: 3, message: '用户名至少3个字符' },
    { max: 20, message: '用户名最多20个字符' },
  ],
  email: [
    { required: true, message: '邮箱不能为空' },
    {
      pattern: /^[^s@]+@[^s@]+.[^s@]+$/,
      message: '邮箱格式不正确',
    },
  ],
  password: [
    { required: true, message: '密码不能为空' },
    { min: 6, message: '密码至少6个字符' },
    {
      validator: (value) => /[A-Z]/.test(value),
      message: '密码必须包含大写字母',
    },
  ],
  age: [
    { required: true, message: '年龄不能为空' },
    { min: 18, message: '年龄必须大于18岁' },
  ],
});

// 验证表单
async function submitForm(data: UserForm) {
  const result = await validator.validate(data);

  if (result.valid) {
    console.log('验证通过');
    // 提交表单
  } else {
    console.error('验证失败:', result.errors);
    // 显示错误信息
  }
}

常见面试问题

问题 1:any、unknown、never 的区别


// any: 关闭类型检查,可以赋值给任何类型
let a: any = 123;
a = 'hello';
a = true;
let b: string = a; // 不会报错

// unknown: 类型安全的 any,需要类型检查才能使用
let c: unknown = 123;
// let d: string = c; // 错误:需要类型断言或类型守卫
if (typeof c === 'string') {
  let d: string = c; // 正确
}

// never: 永远不会有值的类型
function throwError(): never {
  throw new Error('Error');
}

function infiniteLoop(): never {
  while (true) {}
}

// never 是所有类型的子类型
let e: never;
let f: string = e; // 正确

问题 2:type 和 interface 的区别


// 1. 扩展方式不同
// interface 使用 extends
interface Animal {
  name: string;
}
interface Dog extends Animal {
  bark(): void;
}

// type 使用交叉类型
type Animal2 = {
  name: string;
};
type Dog2 = Animal2 & {
  bark(): void;
};

// 2. interface 可以声明合并
interface User {
  name: string;
}
interface User {
  age: number;
}
// 合并后:{ name: string; age: number; }

// type 不能重复声明
// type User2 = { name: string; };
// type User2 = { age: number; }; // 错误

// 3. type 可以定义联合类型、元组等
type ID = string | number;
type Point = [number, number];

// 4. interface 可以被类实现
interface Clickable {
  click(): void;
}

class Button implements Clickable {
  click() {
    console.log('Clicked');
  }
}

问题 3:TypeScript 中的协变和逆变


// 协变(Covariance):子类型可以赋值给父类型
interface Animal {
  name: string;
}

interface Dog extends Animal {
  bark(): void;
}

let animals: Animal[] = [];
let dogs: Dog[] = [];

animals = dogs; // 正确:数组是协变的

// 逆变(Contravariance):函数参数是逆变的
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;

let handleAnimal: AnimalHandler = (animal) => {
  console.log(animal.name);
};

let handleDog: DogHandler = (dog) => {
  console.log(dog.name);
  dog.bark();
};

// handleDog = handleAnimal; // 错误
handleAnimal = handleDog; // 正确(在 strictFunctionTypes: false 时)

// 双向协变(Bivariance):默认情况下,TypeScript 函数参数是双向协变的
// 可以通过 strictFunctionTypes 开启严格模式

总结

TypeScript 学习路径

基础阶段

基本类型(string, number, boolean, array, tuple, enum, any, void, null, undefined)接口与类型别名函数类型类与继承

进阶阶段

泛型高级类型(联合、交叉、映射、条件)工具类型(Partial, Required, Pick, Omit, Record, etc.)类型守卫与类型断言

实战阶段

React + TypeScriptUmiMax 项目实践Ant Design Pro 组件封装复杂类型推导

专家阶段

类型体操高级类型编程性能优化架构设计

推荐资源

📚 TypeScript 官方文档📚 TypeScript Deep Dive📚 Type Challenges📚 React TypeScript Cheatsheet📚 Ant Design Pro 官方文档📚 UmiMax 官方文档


© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...