核心技术栈: 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 官方文档


