frontend-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frontend Development Patterns

前端开发模式

This skill provides comprehensive guidance for modern frontend development using React, Next.js, TypeScript, and related technologies.
本技能为使用React、Next.js、TypeScript及相关技术进行现代前端开发提供全面指导。

Component Architecture

组件架构

Component Composition Patterns

组件组合模式

Container/Presentational Pattern:
typescript
// Presentational component (pure, reusable)
interface UserCardProps {
  name: string;
  email: string;
  avatar: string;
  onEdit: () => void;
}

function UserCard({ name, email, avatar, onEdit }: UserCardProps) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

// Container component (handles logic, state, data fetching)
function UserCardContainer({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId);
  const { mutate: updateUser } = useUpdateUser();

  if (isLoading) return <Skeleton />;
  if (!user) return <NotFound />;

  return <UserCard {...user} onEdit={() => updateUser(user.id)} />;
}
Compound Components Pattern:
typescript
// Flexible, composable API
<Tabs defaultValue="profile">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">
    <ProfileForm />
  </TabsContent>
  <TabsContent value="settings">
    <SettingsForm />
  </TabsContent>
</Tabs>
容器/展示组件模式:
typescript
// Presentational component (pure, reusable)
interface UserCardProps {
  name: string;
  email: string;
  avatar: string;
  onEdit: () => void;
}

function UserCard({ name, email, avatar, onEdit }: UserCardProps) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <h3>{name}</h3>
      <p>{email}</p>
      <button onClick={onEdit}>Edit</button>
    </div>
  );
}

// Container component (handles logic, state, data fetching)
function UserCardContainer({ userId }: { userId: string }) {
  const { data: user, isLoading } = useUser(userId);
  const { mutate: updateUser } = useUpdateUser();

  if (isLoading) return <Skeleton />;
  if (!user) return <NotFound />;

  return <UserCard {...user} onEdit={() => updateUser(user.id)} />;
}
复合组件模式:
typescript
// Flexible, composable API
<Tabs defaultValue="profile">
  <TabsList>
    <TabsTrigger value="profile">Profile</TabsTrigger>
    <TabsTrigger value="settings">Settings</TabsTrigger>
  </TabsList>
  <TabsContent value="profile">
    <ProfileForm />
  </TabsContent>
  <TabsContent value="settings">
    <SettingsForm />
  </TabsContent>
</Tabs>

Component Organization

组件组织

components/
├── ui/                    # Primitive components (buttons, inputs)
│   ├── button.tsx
│   ├── input.tsx
│   └── card.tsx
├── forms/                 # Form components
│   ├── login-form.tsx
│   └── register-form.tsx
├── features/              # Feature-specific components
│   ├── user-profile/
│   │   ├── profile-header.tsx
│   │   ├── profile-stats.tsx
│   │   └── index.ts
│   └── dashboard/
│       ├── dashboard-grid.tsx
│       └── dashboard-card.tsx
└── layouts/               # Layout components
    ├── main-layout.tsx
    └── auth-layout.tsx
components/
├── ui/                    # 基础组件(按钮、输入框)
│   ├── button.tsx
│   ├── input.tsx
│   └── card.tsx
├── forms/                 # 表单组件
│   ├── login-form.tsx
│   └── register-form.tsx
├── features/              # 功能专属组件
│   ├── user-profile/
│   │   ├── profile-header.tsx
│   │   ├── profile-stats.tsx
│   │   └── index.ts
│   └── dashboard/
│       ├── dashboard-grid.tsx
│       └── dashboard-card.tsx
└── layouts/               # 布局组件
    ├── main-layout.tsx
    └── auth-layout.tsx

State Management

状态管理

Local State (useState)

局部状态(useState)

Use for:
  • Component-specific UI state
  • Form inputs
  • Toggles, modals
typescript
function SearchBar() {
  const [query, setQuery] = useState('');
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {isOpen && <SearchResults query={query} />}
    </div>
  );
}
适用场景:
  • 组件专属UI状态
  • 表单输入
  • 开关、模态框
typescript
function SearchBar() {
  const [query, setQuery] = useState('');
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {isOpen && <SearchResults query={query} />}
    </div>
  );
}

Global State (Zustand)

全局状态(Zustand)

Use for:
  • User authentication state
  • Theme preferences
  • Shopping cart
  • Cross-component shared state
typescript
import create from 'zustand';

interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
  logout: () => void;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// Usage
function Header() {
  const user = useUserStore((state) => state.user);
  const logout = useUserStore((state) => state.logout);

  return <div>{user ? user.name : 'Guest'}</div>;
}
适用场景:
  • 用户认证状态
  • 主题偏好
  • 购物车
  • 跨组件共享状态
typescript
import create from 'zustand';

interface UserStore {
  user: User | null;
  setUser: (user: User) => void;
  logout: () => void;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// 用法
function Header() {
  const user = useUserStore((state) => state.user);
  const logout = useUserStore((state) => state.logout);

  return <div>{user ? user.name : 'Guest'}</div>;
}

Server State (React Query / TanStack Query)

服务端状态(React Query / TanStack Query)

Use for:
  • API data fetching
  • Caching API responses
  • Optimistic updates
  • Background refetching
typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetch data
function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });

  if (isLoading) return <Skeleton />;
  if (error) return <Error error={error} />;

  return <div>{data.name}</div>;
}

// Mutations with optimistic updates
function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (user: User) => api.updateUser(user),
    onMutate: async (newUser) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });

      // Snapshot previous value
      const previous = queryClient.getQueryData(['user', newUser.id]);

      // Optimistically update
      queryClient.setQueryData(['user', newUser.id], newUser);

      return { previous };
    },
    onError: (err, newUser, context) => {
      // Rollback on error
      queryClient.setQueryData(['user', newUser.id], context?.previous);
    },
    onSettled: (newUser) => {
      // Refetch after mutation
      queryClient.invalidateQueries({ queryKey: ['user', newUser.id] });
    },
  });
}
适用场景:
  • API数据获取
  • API响应缓存
  • 乐观更新
  • 后台重新获取
typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 获取数据
function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
    staleTime: 5 * 60 * 1000, // 5分钟
  });

  if (isLoading) return <Skeleton />;
  if (error) return <Error error={error} />;

  return <div>{data.name}</div>;
}

// 带乐观更新的突变操作
function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (user: User) => api.updateUser(user),
    onMutate: async (newUser) => {
      // 取消正在进行的重新获取
      await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });

      // 快照之前的值
      const previous = queryClient.getQueryData(['user', newUser.id]);

      // 乐观更新
      queryClient.setQueryData(['user', newUser.id], newUser);

      return { previous };
    },
    onError: (err, newUser, context) => {
      // 出错时回滚
      queryClient.setQueryData(['user', newUser.id], context?.previous);
    },
    onSettled: (newUser) => {
      // 突变后重新获取
      queryClient.invalidateQueries({ queryKey: ['user', newUser.id] });
    },
  });
}

Performance Optimization

性能优化

1. Memoization

1. 记忆化

useMemo (expensive calculations):
typescript
function ProductList({ products }: { products: Product[] }) {
  const sortedProducts = useMemo(
    () => products.sort((a, b) => b.price - a.price),
    [products]
  );

  return <div>{sortedProducts.map(...)}</div>;
}
useCallback (prevent re-renders):
typescript
function Parent() {
  const [count, setCount] = useState(0);

  // ✅ Memoized - Child won't re-render unless count changes
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

const Child = memo(function Child({ onClick }: { onClick: () => void }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});
React.memo (prevent component re-renders):
typescript
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  // Only re-renders if data changes
  return <div>{/* expensive rendering */}</div>;
});
useMemo(用于昂贵计算):
typescript
function ProductList({ products }: { products: Product[] }) {
  const sortedProducts = useMemo(
    () => products.sort((a, b) => b.price - a.price),
    [products]
  );

  return <div>{sortedProducts.map(...)}</div>;
}
useCallback(防止重复渲染):
typescript
function Parent() {
  const [count, setCount] = useState(0);

  // ✅ 已记忆化 - 除非count变化,否则子组件不会重新渲染
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <Child onClick={handleClick} />;
}

const Child = memo(function Child({ onClick }: { onClick: () => void }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});
React.memo(防止组件重复渲染):
typescript
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  // 仅当data变化时才重新渲染
  return <div>{/* 开销较大的渲染逻辑 */}</div>;
});

2. Code Splitting

2. 代码拆分

Route-based splitting (Next.js automatic):
typescript
// app/dashboard/page.tsx - automatically code split
export default function DashboardPage() {
  return <Dashboard />;
}
Component-level splitting:
typescript
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/heavy-chart'), {
  loading: () => <Skeleton />,
  ssr: false, // Don't render on server
});

function Analytics() {
  return <HeavyChart data={chartData} />;
}
基于路由的拆分(Next.js自动实现):
typescript
// app/dashboard/page.tsx - 自动进行代码拆分
export default function DashboardPage() {
  return <Dashboard />;
}
基于组件的拆分:
typescript
import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('@/components/heavy-chart'), {
  loading: () => <Skeleton />,
  ssr: false, // 不在服务端渲染
});

function Analytics() {
  return <HeavyChart data={chartData} />;
}

3. Image Optimization

3. 图片优化

typescript
import Image from 'next/image';

// ✅ Optimized - Next.js Image component
<Image
  src="/hero.jpg"
  alt="Hero image"
  width={800}
  height={600}
  priority // Load immediately for LCP
  placeholder="blur"
  blurDataURL="data:image/..."
/>

// ❌ Not optimized
<img src="/hero.jpg" alt="Hero" />
typescript
import Image from 'next/image';

// ✅ 已优化 - Next.js Image组件
<Image
  src="/hero.jpg"
  alt="Hero image"
  width={800}
  height={600}
  priority // 作为LCP资源立即加载
  placeholder="blur"
  blurDataURL="data:image/..."
/>

// ❌ 未优化
<img src="/hero.jpg" alt="Hero" />

4. Lazy Loading

4. 懒加载

typescript
import { lazy, Suspense } from 'react';

const Comments = lazy(() => import('./comments'));

function Post() {
  return (
    <div>
      <PostContent />
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={postId} />
      </Suspense>
    </div>
  );
}
typescript
import { lazy, Suspense } from 'react';

const Comments = lazy(() => import('./comments'));

function Post() {
  return (
    <div>
      <PostContent />
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments postId={postId} />
      </Suspense>
    </div>
  );
}

5. Virtual Scrolling

5. 虚拟滚动

typescript
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualRow) => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualRow.size}px`,
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            {items[virtualRow.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}
typescript
import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map((virtualRow) => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualRow.size}px`,
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            {items[virtualRow.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

Accessibility (a11y)

无障碍访问(a11y)

Semantic HTML

语义化HTML

typescript
// ✅ Semantic
<nav>
  <ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

// ❌ Non-semantic
<div>
  <div>
    <div onClick={goHome}>Home</div>
    <div onClick={goAbout}>About</div>
  </div>
</div>
typescript
// ✅ 语义化
<nav>
  <ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

// ❌ 非语义化
<div>
  <div>
    <div onClick={goHome}>Home</div>
    <div onClick={goAbout}>About</div>
  </div>
</div>

ARIA Attributes

ARIA属性

typescript
<button
  aria-label="Close dialog"
  aria-expanded={isOpen}
  aria-controls="dialog-content"
  onClick={toggle}
>
  <X aria-hidden="true" />
</button>

<div
  id="dialog-content"
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
>
  <h2 id="dialog-title">Dialog Title</h2>
  {content}
</div>
typescript
<button
  aria-label="关闭对话框"
  aria-expanded={isOpen}
  aria-controls="dialog-content"
  onClick={toggle}
>
  <X aria-hidden="true" />
</button>

<div
  id="dialog-content"
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
>
  <h2 id="dialog-title">对话框标题</h2>
  {content}
</div>

Keyboard Navigation

键盘导航

typescript
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(0);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setFocusedIndex((i) => Math.min(i + 1, items.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setFocusedIndex((i) => Math.max(i - 1, 0));
        break;
      case 'Enter':
        selectItem(items[focusedIndex]);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div onKeyDown={handleKeyDown} role="combobox">
      {/* dropdown content */}
    </div>
  );
}
typescript
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  const [focusedIndex, setFocusedIndex] = useState(0);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setFocusedIndex((i) => Math.min(i + 1, items.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setFocusedIndex((i) => Math.max(i - 1, 0));
        break;
      case 'Enter':
        selectItem(items[focusedIndex]);
        break;
      case 'Escape':
        setIsOpen(false);
        break;
    }
  };

  return (
    <div onKeyDown={handleKeyDown} role="combobox">
      {/* 下拉框内容 */}
    </div>
  );
}

Focus Management

焦点管理

typescript
import { useRef, useEffect } from 'react';

function Modal({ isOpen, onClose }: ModalProps) {
  const closeButtonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (isOpen) {
      // Focus close button when modal opens
      closeButtonRef.current?.focus();

      // Trap focus within modal
      const handleTab = (e: KeyboardEvent) => {
        // Implement focus trap logic
      };

      document.addEventListener('keydown', handleTab);
      return () => document.removeEventListener('keydown', handleTab);
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div role="dialog" aria-modal="true">
      <button ref={closeButtonRef} onClick={onClose}>
        Close
      </button>
      {content}
    </div>
  );
}
typescript
import { useRef, useEffect } from 'react';

function Modal({ isOpen, onClose }: ModalProps) {
  const closeButtonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
    if (isOpen) {
      // 模态框打开时聚焦关闭按钮
      closeButtonRef.current?.focus();

      // 将焦点限制在模态框内
      const handleTab = (e: KeyboardEvent) => {
        // 实现焦点捕获逻辑
      };

      document.addEventListener('keydown', handleTab);
      return () => document.removeEventListener('keydown', handleTab);
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div role="dialog" aria-modal="true">
      <button ref={closeButtonRef} onClick={onClose}>
        关闭
      </button>
      {content}
    </div>
  );
}

Form Patterns

表单模式

Controlled Forms with Validation

带验证的受控表单

typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  age: z.number().min(18, 'Must be 18 or older'),
});

type FormData = z.infer<typeof schema>;

function RegistrationForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await api.register(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input {...register('email')} type="email" />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <input {...register('password')} type="password" />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}
typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

const schema = z.object({
  email: z.string().email('邮箱地址无效'),
  password: z.string().min(8, '密码长度至少为8位'),
  age: z.number().min(18, '必须年满18岁'),
});

type FormData = z.infer<typeof schema>;

function RegistrationForm() {
  const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = async (data: FormData) => {
    await api.register(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input {...register('email')} type="email" />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <input {...register('password')} type="password" />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '提交中...' : '提交'}
      </button>
    </form>
  );
}

Form State Management

表单状态管理

typescript
// Optimistic updates
const { mutate } = useMutation({
  mutationFn: updateUser,
  onMutate: async (newData) => {
    // Cancel outgoing queries
    await queryClient.cancelQueries({ queryKey: ['user', userId] });

    // Snapshot previous
    const previous = queryClient.getQueryData(['user', userId]);

    // Optimistically update UI
    queryClient.setQueryData(['user', userId], newData);

    return { previous };
  },
  onError: (err, newData, context) => {
    // Rollback on error
    queryClient.setQueryData(['user', userId], context?.previous);
    toast.error('Update failed');
  },
  onSuccess: () => {
    toast.success('Updated successfully');
  },
});
typescript
// 乐观更新
const { mutate } = useMutation({
  mutationFn: updateUser,
  onMutate: async (newData) => {
    // 取消正在进行的查询
    await queryClient.cancelQueries({ queryKey: ['user', userId] });

    // 快照之前的数据
    const previous = queryClient.getQueryData(['user', userId]);

    // 乐观更新UI
    queryClient.setQueryData(['user', userId], newData);

    return { previous };
  },
  onError: (err, newData, context) => {
    // 出错时回滚
    queryClient.setQueryData(['user', userId], context?.previous);
    toast.error('更新失败');
  },
  onSuccess: () => {
    toast.success('更新成功');
  },
});

Error Handling

错误处理

Error Boundaries

错误边界

typescript
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: any) {
    console.error('Error boundary caught:', error, errorInfo);
    // Log to error tracking service
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
<ErrorBoundary fallback={<ErrorFallback />}>
  <App />
</ErrorBoundary>
typescript
import { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: any) {
    console.error('Error boundary caught:', error, errorInfo);
    // 记录到错误追踪服务
    logErrorToService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div>
          <h2>出现了一些问题</h2>
          <button onClick={() => this.setState({ hasError: false })}>
            重试
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// 用法
<ErrorBoundary fallback={<ErrorFallback />}>
  <App />
</ErrorBoundary>

Async Error Handling

异步错误处理

typescript
function DataComponent() {
  const { data, error, isError, isLoading } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  });

  if (isLoading) return <Skeleton />;
  if (isError) return <ErrorDisplay error={error} />;

  return <DisplayData data={data} />;
}
typescript
function DataComponent() {
  const { data, error, isError, isLoading } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    retry: 3,
    retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
  });

  if (isLoading) return <Skeleton />;
  if (isError) return <ErrorDisplay error={error} />;

  return <DisplayData data={data} />;
}

Responsive Design

响应式设计

Mobile-First Approach

移动端优先方案

typescript
// Tailwind CSS (mobile-first)
<div className="
  w-full              /* Full width on mobile */
  md:w-1/2           /* Half width on tablets */
  lg:w-1/3           /* Third width on desktop */
  p-4                /* Padding 16px */
  md:p-6             /* Padding 24px on tablets+ */
">
  Content
</div>
typescript
// Tailwind CSS(移动端优先)
<div className="
  w-full              /* 移动端占满宽度 */
  md:w-1/2           /* 平板端占一半宽度 */
  lg:w-1/3           /* 桌面端占三分之一宽度 */
  p-4                /* 内边距16px */
  md:p-6             /* 平板及以上设备内边距24px */
">
  内容
</div>

Responsive Hooks

响应式钩子

typescript
import { useMediaQuery } from '@/hooks/use-media-query';

function ResponsiveLayout() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
  const isDesktop = useMediaQuery('(min-width: 1025px)');

  if (isMobile) return <MobileLayout />;
  if (isTablet) return <TabletLayout />;
  return <DesktopLayout />;
}
typescript
import { useMediaQuery } from '@/hooks/use-media-query';

function ResponsiveLayout() {
  const isMobile = useMediaQuery('(max-width: 768px)');
  const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)');
  const isDesktop = useMediaQuery('(min-width: 1025px)');

  if (isMobile) return <MobileLayout />;
  if (isTablet) return <TabletLayout />;
  return <DesktopLayout />;
}

Data Fetching Strategies

数据获取策略

Server Components (Next.js 14+)

服务端组件(Next.js 14+)

typescript
// app/users/page.tsx - Server Component
async function UsersPage() {
  // Fetched on server
  const users = await db.user.findMany();

  return <UserList users={users} />;
}
typescript
// app/users/page.tsx - 服务端组件
async function UsersPage() {
  // 在服务端获取数据
  const users = await db.user.findMany();

  return <UserList users={users} />;
}

Client Components with React Query

使用React Query的客户端组件

typescript
'use client';

function UserList() {
  const { data: users, isLoading } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });

  if (isLoading) return <UsersLoading />;

  return <div>{users.map(user => <UserCard key={user.id} {...user} />)}</div>;
}
typescript
'use client';

function UserList() {
  const { data: users, isLoading } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });

  if (isLoading) return <UsersLoading />;

  return <div>{users.map(user => <UserCard key={user.id} {...user} />)}</div>;
}

Parallel Data Fetching

并行数据获取

typescript
function Dashboard() {
  const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser });
  const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: fetchStats });
  const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

  // All three queries run in parallel
  return <div>...</div>;
}
typescript
function Dashboard() {
  const { data: user } = useQuery({ queryKey: ['user'], queryFn: fetchUser });
  const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: fetchStats });
  const { data: posts } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts });

  // 三个查询并行执行
  return <div>...</div>;
}

Dependent Queries

依赖式查询

typescript
function UserPosts({ userId }: { userId: string }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  const { data: posts } = useQuery({
    queryKey: ['posts', user?.id],
    queryFn: () => fetchUserPosts(user!.id),
    enabled: !!user, // Only fetch after user is loaded
  });

  return <div>...</div>;
}
typescript
function UserPosts({ userId }: { userId: string }) {
  const { data: user } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  const { data: posts } = useQuery({
    queryKey: ['posts', user?.id],
    queryFn: () => fetchUserPosts(user!.id),
    enabled: !!user, // 仅在user加载完成后才获取
  });

  return <div>...</div>;
}

TypeScript Patterns

TypeScript模式

Prop Types

属性类型

typescript
// Basic props
interface ButtonProps {
  children: ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

// Props with generic
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => ReactNode;
  keyExtractor: (item: T) => string;
}

// Props extending HTML attributes
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  error?: string;
}
typescript
// 基础属性
interface ButtonProps {
  children: ReactNode;
  onClick: () => void;
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
}

// 泛型属性
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => ReactNode;
  keyExtractor: (item: T) => string;
}

// 继承HTML属性的属性
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  error?: string;
}

Type-Safe API Responses

类型安全的API响应

typescript
// API response types
interface ApiResponse<T> {
  data: T;
  error?: never;
}

interface ApiError {
  data?: never;
  error: {
    code: string;
    message: string;
  };
}

type ApiResult<T> = ApiResponse<T> | ApiError;

// Usage
async function fetchUser(id: string): Promise<ApiResult<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}
typescript
// API响应类型
interface ApiResponse<T> {
  data: T;
  error?: never;
}

interface ApiError {
  data?: never;
  error: {
    code: string;
    message: string;
  };
}

type ApiResult<T> = ApiResponse<T> | ApiError;

// 用法
async function fetchUser(id: string): Promise<ApiResult<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Testing Patterns

测试模式

Component Testing

组件测试

typescript
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './login-form';

test('submits form with email and password', async () => {
  const onSubmit = jest.fn();

  render(<LoginForm onSubmit={onSubmit} />);

  fireEvent.change(screen.getByLabelText('Email'), {
    target: { value: 'test@example.com' },
  });

  fireEvent.change(screen.getByLabelText('Password'), {
    target: { value: 'password123' },
  });

  fireEvent.click(screen.getByRole('button', { name: 'Login' }));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
  });
});
typescript
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './login-form';

test('提交包含邮箱和密码的表单', async () => {
  const onSubmit = jest.fn();

  render(<LoginForm onSubmit={onSubmit} />);

  fireEvent.change(screen.getByLabelText('Email'), {
    target: { value: 'test@example.com' },
  });

  fireEvent.change(screen.getByLabelText('Password'), {
    target: { value: 'password123' },
  });

  fireEvent.click(screen.getByRole('button', { name: 'Login' }));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'password123',
    });
  });
});

Mock API Calls

模拟API调用

typescript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    return res(ctx.json({
      id: req.params.id,
      name: 'Test User',
      email: 'test@example.com',
    }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('displays user data', async () => {
  const queryClient = new QueryClient();

  render(
    <QueryClientProvider client={queryClient}>
      <UserProfile userId="123" />
    </QueryClientProvider>
  );

  expect(await screen.findByText('Test User')).toBeInTheDocument();
});
typescript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/users/:id', (req, res, ctx) => {
    return res(ctx.json({
      id: req.params.id,
      name: '测试用户',
      email: 'test@example.com',
    }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('显示用户数据', async () => {
  const queryClient = new QueryClient();

  render(
    <QueryClientProvider client={queryClient}>
      <UserProfile userId="123" />
    </QueryClientProvider>
  );

  expect(await screen.findByText('测试用户')).toBeInTheDocument();
});

Build Optimization

构建优化

Bundle Analysis

包分析

bash
undefined
bash
undefined

Next.js bundle analyzer

Next.js 包分析器

npm install @next/bundle-analyzer
npm install @next/bundle-analyzer

next.config.js

next.config.js

const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', });
module.exports = withBundleAnalyzer({ // config });
const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', });
module.exports = withBundleAnalyzer({ // 配置 });

Run analysis

运行分析

ANALYZE=true npm run build
undefined
ANALYZE=true npm run build
undefined

Tree Shaking

摇树优化

typescript
// ✅ Named imports (tree-shakeable)
import { Button } from '@/components/ui/button';

// ❌ Namespace import (includes everything)
import * as UI from '@/components/ui';
typescript
// ✅ 具名导入(支持摇树优化)
import { Button } from '@/components/ui/button';

// ❌ 命名空间导入(包含所有内容)
import * as UI from '@/components/ui';

Dynamic Imports

动态导入

typescript
// Import only when needed
async function handleExport() {
  const { exportToPDF } = await import('@/lib/pdf-export');
  await exportToPDF(data);
}
typescript
// 仅在需要时导入
async function handleExport() {
  const { exportToPDF } = await import('@/lib/pdf-export');
  await exportToPDF(data);
}

Common Frontend Mistakes to Avoid

需避免的常见前端错误

  1. Prop drilling: Use Context or state management library instead
  2. Unnecessary re-renders: Use memo, useMemo, useCallback appropriately
  3. Missing loading states: Always show loading indicators
  4. No error boundaries: Catch errors before they break the app
  5. Inline functions in JSX: Causes re-renders, use useCallback
  6. Large bundle sizes: Code split and lazy load
  7. Missing alt text: All images need descriptive alt text
  8. Inaccessible forms: Use proper labels and ARIA
  9. Console.log in production: Remove or use proper logging
  10. Mixing server and client code: Know Next.js boundaries
  1. 属性透传:改用Context或状态管理库
  2. 不必要的重复渲染:合理使用memo、useMemo、useCallback
  3. 缺少加载状态:始终显示加载指示器
  4. 未使用错误边界:在错误影响整个应用前捕获它们
  5. JSX中的内联函数:会导致重复渲染,使用useCallback
  6. 包体积过大:进行代码拆分和懒加载
  7. 缺少图片alt文本:所有图片都需要描述性alt文本
  8. 表单无障碍性不足:使用正确的标签和ARIA属性
  9. 生产环境中的console.log:移除或使用专业日志工具
  10. 混淆服务端和客户端代码:明确Next.js的边界

Performance Metrics (Core Web Vitals)

性能指标(核心Web指标)

LCP (Largest Contentful Paint)

LCP(最大内容绘制)

Target: < 2.5 seconds
Optimize:
  • Preload critical images
  • Use Next.js Image component
  • Minimize render-blocking resources
  • Use CDN for assets
目标:< 2.5秒
优化方向:
  • 预加载关键图片
  • 使用Next.js Image组件
  • 最小化阻塞渲染的资源
  • 使用CDN托管静态资源

FID (First Input Delay)

FID(首次输入延迟)

Target: < 100 milliseconds
Optimize:
  • Minimize JavaScript execution
  • Code split large bundles
  • Use web workers for heavy computation
  • Defer non-critical JavaScript
目标:< 100毫秒
优化方向:
  • 最小化JavaScript执行时间
  • 拆分大型代码包
  • 使用Web Worker处理繁重计算
  • 延迟加载非关键JavaScript

CLS (Cumulative Layout Shift)

CLS(累积布局偏移)

Target: < 0.1
Optimize:
  • Set explicit width/height on images
  • Reserve space for ads/embeds
  • Avoid inserting content above existing content
  • Use CSS transforms instead of layout properties
目标:< 0.1
优化方向:
  • 为图片设置明确的宽高
  • 为广告/嵌入内容预留空间
  • 避免在现有内容上方插入新内容
  • 使用CSS变换替代布局属性

When to Use This Skill

何时使用本技能

Use this skill when:
  • Building React or Next.js components
  • Implementing frontend features
  • Optimizing frontend performance
  • Debugging rendering issues
  • Setting up state management
  • Implementing forms
  • Ensuring accessibility
  • Working with responsive design
  • Fetching and caching data
  • Testing frontend code

Remember: Modern frontend development is about creating fast, accessible, and delightful user experiences. Follow these patterns to build UIs that users love.
在以下场景使用本技能:
  • 构建React或Next.js组件
  • 实现前端功能
  • 优化前端性能
  • 调试渲染问题
  • 搭建状态管理方案
  • 实现表单
  • 确保无障碍访问
  • 开发响应式设计
  • 获取和缓存数据
  • 测试前端代码

注意:现代前端开发的核心是创建快速、无障碍且愉悦的用户体验。遵循这些模式,构建用户喜爱的UI。