mcpField
R
react-patterns

react-patterns

github.com/manutej开发62473 次安装40 周前更新
代码仓库

关于 react-patterns

react-patterns 是面向 React 开发者的技能指南,涵盖现代 Hooks、组件组合、状态管理与性能实践。

主要功能

  1. 01React Hooks 与状态模式
  2. 02自定义 Hook 设计
  3. 03组件组合方式
  4. 04性能优化
  5. 05服务端与客户端组件

适用场景

  1. 01为复杂界面设计状态
  2. 02提取可复用的组件逻辑
  3. 03减少不必要的重复渲染
  4. 04重构 class 组件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill react-patterns

安装后在 Claude Code 中输入/react-patterns确认已加载。

附带文件EXAMPLES.mdREADME.md

详细介绍

React 模式 - 现代开发指南

全面掌握现代 React 开发模式的技能,涵盖 React 18+ 功能、Hooks、组件组合、状态管理策略,以及用于构建可扩展 Web 应用的性能优化技巧。

何时使用此技能

在以下情况下使用此技能:

  • 使用函数组件和 Hooks 构建现代 React 应用
  • 使用 useReducer 和 Context API 实现复杂的状态管理
  • 使用记忆化技术优化 React 应用性能
  • 创建可复用的自定义 Hooks,以共享逻辑
  • 使用 Server Components 和 React Server Components (RSC)
  • 管理表单、副作用和异步操作
  • 将类组件重构为现代函数式模式
  • 使用恰当的模式构建类型安全的 React 应用
  • 实现高级组件组合模式
  • 调试 React 性能问题和不必要的重新渲染

核心概念

React 理念

现代 React 强调:

  • 函数组件:返回 JSX 的纯函数
  • Hooks:可组合的状态和副作用管理
  • 声明式 UI:描述 UI 应呈现的样子,由 React 处理更新
  • 组件组合:使用简单、可复用的组件构建复杂 UI
  • 单向数据流:Props 向下传递,事件向上传递
  • 不可变性:绝不直接修改状态,始终创建新对象/数组
组件类型
  1. 展示组件:专注于 UI,通过 props 接收数据
  2. 容器组件:处理逻辑、状态和副作用
  3. 服务端组件:在服务端渲染,不包含客户端 JavaScript
  4. 客户端组件:使用 "use client" 标记的交互式组件
  5. 异步组件:可以等待数据的服务端组件

React Hooks 参考

内置状态 Hooks
useState

管理简单值的本地组件状态。

语法:

const [state, setState] = useState(initialValue);

基本示例:

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(prev => prev + 1)}>Increment (functional)</button>
    </div>
  );
}

最佳实践:

  • 当新状态取决于先前状态时,使用函数式更新
  • 尽可能将状态保留在本地
  • 不要将派生值存储在状态中
  • 对于开销较大的计算,使用函数进行初始化

多个状态变量:

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);

  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
    </form>
  );
}

惰性初始化:

function ExpensiveComponent() {
  // Function only runs once on initial render
  const [state, setState] = useState(() => {
    const initialValue = expensiveComputation();
    return initialValue;
  });

  return <div>{state}</div>;
}
useReducer

使用 reducer 模式管理复杂状态逻辑(类似于 Redux)。

语法:

const [state, dispatch] = useReducer(reducer, initialState, init?);

任务管理器示例:

import { useReducer } from 'react';

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added':
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    case 'changed':
      return tasks.map(t =>
        t.id === action.task.id ? action.task : t
      );
    case 'deleted':
      return tasks.filter(t => t.id !== action.id);
    default:
      throw Error('Unknown action: ' + action.type);
  }
}

function TaskApp() {
  const [tasks, dispatch] = useReducer(tasksReducer, []);

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: Date.now(),
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <AddTask onAddTask={handleAddTask} />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

何时使用 useReducer:

  • 存在多个相关状态值
  • 状态更新逻辑复杂
  • 状态转换遵循可预测的模式
  • 需要优化包含大量更新的性能
  • 希望将状态逻辑与组件分离
useContext

访问上下文值,避免逐层传递 props。

语法:

const value = useContext(SomeContext);

主题上下文示例:

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Usage
function Button() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      className={theme === 'dark' ? 'btn-dark' : 'btn-light'}
    >
      Toggle Theme
    </button>
  );
}

组合使用 useReducer + useContext:

import { createContext, useContext, useReducer } from 'react';

const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);

export function TasksProvider({ children }) {
  const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        {children}
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

export function useTasks() {
  return useContext(TasksContext);
}

export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

// Usage in components
function TaskList() {
  const tasks = useTasks();
  const dispatch = useTasksDispatch();

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          {task.text}
          <button onClick={() => dispatch({ type: 'deleted', id: task.id })}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
}
Effect Hooks
useEffect

将组件与外部系统(API、DOM、订阅)同步。

语法:

useEffect(() => {
  // Effect logic
  return () => {
    // Cleanup logic
  };
}, [dependencies]);

数据获取示例:

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false; // Prevent race conditions

    async function fetchUser() {
      try {
        setLoading(true);
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();

        if (!ignore) {
          setUser(data);
        }
      } catch (err) {
        if (!ignore) {
          setError(err.message);
        }
      } finally {
        if (!ignore) {
          setLoading(false);
        }
      }
    }

    fetchUser();

    return () => {
      ignore = true; // Cleanup
    };
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>User: {user?.name}</div>;
}

订阅示例:

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const connection = createConnection(roomId);

    connection.on('message', (msg) => {
      setMessages(prev => [...prev, msg]);
    });

    connection.connect();

    return () => {
      connection.disconnect(); // Cleanup on unmount or roomId change
    };
  }, [roomId]);

  return (
    <div>
      {messages.map((msg, i) => (
        <div key={i}>{msg}</div>
      ))}
    </div>
  );
}

常见 Effect 模式:

// Run once on mount
useEffect(() => {
  console.log('Component mounted');
}, []); // Empty dependency array

// Run on every render
useEffect(() => {
  console.log('Component rendered');
}); // No dependency array

// Run when specific values change
useEffect(() => {
  console.log('userId changed:', userId);
}, [userId]); // Dependency array with values

// Cleanup on unmount
useEffect(() => {
  const timer = setTimeout(() => {
    console.log('Delayed action');
  }, 1000);

  return () => clearTimeout(timer);
}, []);
useLayoutEffect

useEffect 的同步版本,在浏览器绘制前运行。

使用场景:

  • 测量 DOM 元素
  • 同步 DOM 修改
  • 防止视觉闪烁

示例:

import { useLayoutEffect, useRef, useState } from 'react';

function Tooltip() {
  const ref = useRef(null);
  const [tooltipHeight, setTooltipHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = ref.current.getBoundingClientRect();
    setTooltipHeight(height); // Synchronous update before paint
  }, []);

  return (
    <div ref={ref}>
      Tooltip content (height: {tooltipHeight}px)
    </div>
  );
}
性能 Hooks
useMemo

在多次渲染之间缓存开销较大的计算结果。

语法:

const cachedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

示例:

import { useMemo } from 'react';

function TodoList({ todos, filter }) {
  // Only recompute when todos or filter changes
  const visibleTodos = useMemo(() => {
    console.log('Filtering todos...');
    return todos.filter(todo => {
      if (filter === 'all') return true;
      if (filter === 'active') return !todo.done;
      if (filter === 'completed') return todo.done;
      return true;
    });
  }, [todos, filter]);

  return (
    <ul>
      {visibleTodos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

何时使用 useMemo:

  • 开销较大的计算(筛选、排序大型数组)
  • 防止子组件不必要的重新渲染
  • 稳定作为依赖项传递的对象/数组引用

反模式(不必要的用法):

// ❌ Don't use for simple calculations
const sum = useMemo(() => a + b, [a, b]); // Overkill

// ✅ Just compute directly
const sum = a + b;
useCallback

在多次渲染之间记忆化函数引用。

语法:

const cachedFn = useCallback(() => {
  // Function logic
}, [dependencies]);

示例:

import { useState, useCallback, memo } from 'react';

const TodoItem = memo(function TodoItem({ todo, onChange, onDelete }) {
  console.log('TodoItem rendered:', todo.id);

  return (
    <li>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => onChange(todo)}
      />
      {todo.text}
      <button onClick={() => onDelete(todo.id)}>Delete</button>
    </li>
  );
});

function TodoList() {
  const [todos, setTodos] = useState([]);

  // Memoize handlers to prevent TodoItem re-renders
  const handleChange = useCallback((todo) => {
    setTodos(prev => prev.map(t =>
      t.id === todo.id ? { ...t, done: !t.done } : t
    ));
  }, []);

  const handleDelete = useCallback((id) => {
    setTodos(prev => prev.filter(t => t.id !== id));
  }, []);

  return (
    <ul>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          todo={todo}
          onChange={handleChange}
          onDelete={handleDelete}
        />
      ))}
    </ul>
  );
}

何时使用 useCallback:

  • 将回调传递给经过记忆化的子组件
  • 函数是 useEffect 或其他 Hooks 的依赖项
  • 优化开销较大的事件处理器
React.memo

对整个组件进行记忆化,避免不必要的重新渲染。

语法:

const MemoizedComponent = memo(function Component(props) {
  // Component logic
}, arePropsEqual?);

示例:

import { memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
  console.log('ExpensiveComponent rendered');

  return (
    <div>
      {data.map(item => (
        <div key={item.id} onClick={() => onClick(item)}>
          {item.name}
        </div>
      ))}
    </div>
  );
});

// With custom comparison
const CustomMemoComponent = memo(
  function CustomMemoComponent({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // Return true if props are equal (skip re-render)
    return prevProps.user.id === nextProps.user.id;
  }
);
Ref Hook
useRef

创建可变引用,并使其在多次渲染之间保持不变。

语法:

const ref = useRef(initialValue);

DOM 引用示例:

import { useRef } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const videoRef = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      videoRef.current.play();
    } else {
      videoRef.current.pause();
    }
  }, [isPlaying]);

  return <video ref={videoRef} src={src} />;
}

存储可变值:

function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);

  function start() {
    if (intervalRef.current) return; // Already running

    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  }

  function stop() {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

上一个值模式:

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

// Usage
function Counter({ count }) {
  const prevCount = usePrevious(count);

  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCount}</p>
    </div>
  );
}
Transition Hook(React 18+)
useTransition

将状态更新标记为非紧急更新,让 UI 保持响应。

语法:

const [isPending, startTransition] = useTransition();

示例:

import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value); // Urgent update (input value)

    startTransition(() => {
      // Non-urgent update (search results)
      const filtered = performExpensiveSearch(value);
      setResults(filtered);
    });
  }

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <div>Searching...</div>}
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </div>
  );
}
useDeferredValue

延迟更新部分 UI。

语法:

const deferredValue = useDeferredValue(value);

示例:

import { useState, useDeferredValue, memo } from 'react';

const SlowList = memo(function SlowList({ items }) {
  // Intentionally slow rendering
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);

  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList items={filterItems(deferredText)} />
    </>
  );
}
Server Action Hook(RSC)
useActionState

管理 server action 的状态和待处理状态。

语法:

const [state, formAction, isPending] = useActionState(serverAction, initialState);

示例:

"use client";

import { useActionState } from 'react';
import { updateName } from './actions';

function UpdateNameForm() {
  const [state, submitAction, isPending] = useActionState(
    updateName,
    { error: null }
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" disabled={isPending} />
      {state.error && <span className="error">{state.error}</span>}
      <button type="submit" disabled={isPending}>
        {isPending ? 'Updating...' : 'Update'}
      </button>
    </form>
  );
}

Server Action(actions.js):

"use server";

export async function updateName(prevState, formData) {
  const name = formData.get('name');

  if (!name) {
    return { error: 'Name is required' };
  }

  try {
    await db.users.updateName(name);
    return { error: null };
  } catch (err) {
    return { error: 'Failed to update name' };
  }
}

自定义 Hook 模式

自定义 Hook 可让你提取有状态逻辑,并在多个组件之间复用。

命名约定

自定义 Hook 的名称始终以 use 为前缀:

useCustomHook // ✅ Correct
customHook    // ❌ Wrong
数据获取 Hook
import { useState, useEffect } from 'react';

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false;

    async function fetchData() {
      try {
        setLoading(true);
        setError(null);

        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const result = await response.json();

        if (!ignore) {
          setData(result);
        }
      } catch (err) {
        if (!ignore) {
          setError(err.message);
        }
      } finally {
        if (!ignore) {
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      ignore = true;
    };
  }, [url]);

  return { data, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{user.name}</div>;
}
表单输入 Hook
import { useState } from 'react';

export function useFormInput(initialValue = '') {
  const [value, setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  function reset() {
    setValue(initialValue);
  }

  return {
    value,
    onChange: handleChange,
    reset
  };
}

// Usage
function LoginForm() {
  const email = useFormInput('');
  const password = useFormInput('');

  function handleSubmit(e) {
    e.preventDefault();
    console.log('Email:', email.value);
    console.log('Password:', password.value);
    email.reset();
    password.reset();
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" {...email} placeholder="Email" />
      <input type="password" {...password} placeholder="Password" />
      <button type="submit">Login</button>
    </form>
  );
}
本地存储 Hook
import { useState, useEffect } from 'react';

export function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  }, [key, value]);

  return [value, setValue];
}

// Usage
function Settings() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');
  const [language, setLanguage] = useLocalStorage('language', 'en');

  return (
    <div>
      <select value={theme} onChange={e => setTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
      <select value={language} onChange={e => setLanguage(e.target.value)}>
        <option value="en">English</option>
        <option value="es">Spanish</option>
      </select>
    </div>
  );
}
防抖 Hook
import { useState, useEffect } from 'react';

export function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// Usage
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // Perform search
      console.log('Searching for:', debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      type="text"
      value={searchTerm}
      onChange={e => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
}
窗口大小 Hook
import { useState, useEffect } from 'react';

export function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    handleResize(); // Set initial size
    window.addEventListener('resize', handleResize);

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

// Usage
function ResponsiveComponent() {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Window width: {width}px</p>
      <p>Window height: {height}px</p>
      {width < 768 ? <MobileView /> : <DesktopView />}
    </div>
  );
}
在线状态 Hook
import { useState, useEffect } from 'react';

export function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }

    function handleOffline() {
      setIsOnline(false);
    }

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);

    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);

  return isOnline;
}

// Usage
function StatusBar() {
  const isOnline = useOnlineStatus();

  return (
    <div className={isOnline ? 'online' : 'offline'}>
      {isOnline ? '✅ Online' : '❌ Offline'}
    </div>
  );
}

组件模式

复合组件

构建能够协同工作并共享隐式状态的组件。

import { createContext, useContext, useState } from 'react';

const TabsContext = createContext();

export function Tabs({ children, defaultValue }) {
  const [activeTab, setActiveTab] = useState(defaultValue);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

export function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

export function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  const isActive = activeTab === value;

  return (
    <button
      className={`tab ${isActive ? 'active' : ''}`}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

export function TabPanels({ children }) {
  return <div className="tab-panels">{children}</div>;
}

export function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);

  if (activeTab !== value) return null;

  return <div className="tab-panel">{children}</div>;
}

// Usage
function App() {
  return (
    <Tabs defaultValue="tab1">
      <TabList>
        <Tab value="tab1">Tab 1</Tab>
        <Tab value="tab2">Tab 2</Tab>
        <Tab value="tab3">Tab 3</Tab>
      </TabList>

      <TabPanels>
        <TabPanel value="tab1">Content for Tab 1</TabPanel>
        <TabPanel value="tab2">Content for Tab 2</TabPanel>
        <TabPanel value="tab3">Content for Tab 3</TabPanel>
      </TabPanels>
    </Tabs>
  );
}
Render Props

使用值为函数的 prop 在组件之间共享代码。

function DataFetcher({ url, render }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return render({ data, loading });
}

// Usage
function App() {
  return (
    <DataFetcher
      url="/api/users"
      render={({ data, loading }) => (
        loading ? <div>Loading...</div> : (
          <ul>
            {data?.map(user => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        )
      )}
    />
  );
}
高阶组件(HOC)

包装组件以增强功能。

import { useEffect, useState } from 'react';

// HOC for data fetching
function withData(Component, url) {
  return function WithDataComponent(props) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      fetch(url)
        .then(res => res.json())
        .then(data => {
          setData(data);
          setLoading(false);
        });
    }, []);

    return <Component {...props} data={data} loading={loading} />;
  };
}

// Original component
function UserList({ data, loading }) {
  if (loading) return <div>Loading...</div>;

  return (
    <ul>
      {data?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// Enhanced component
const UserListWithData = withData(UserList, '/api/users');

// Usage
function App() {
  return <UserListWithData />;
}
容器/展示模式

将逻辑与展示分离。

// Container (logic)
function UserListContainer() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    fetchUsers().then(data => {
      setUsers(data);
      setLoading(false);
    });
  }, []);

  const filteredUsers = users.filter(user =>
    user.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <UserListPresenter
      users={filteredUsers}
      loading={loading}
      searchTerm={searchTerm}
      onSearchChange={setSearchTerm}
    />
  );
}

// Presenter (UI)
function UserListPresenter({ users, loading, searchTerm, onSearchChange }) {
  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={e => onSearchChange(e.target.value)}
        placeholder="Search users..."
      />
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

性能优化

识别性能问题

使用 React DevTools Profiler 识别:

  • 不必要地重新渲染的组件
  • 开销较大的渲染操作
  • 状态更新级联
优化策略
1. 记忆化
import { memo, useMemo, useCallback } from 'react';

const ExpensiveList = memo(function ExpensiveList({ items, onItemClick }) {
  console.log('Rendering ExpensiveList');

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

function App() {
  const [items, setItems] = useState([]);
  const [count, setCount] = useState(0);

  // Memoize expensive computation
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return [...items].sort((a, b) => a.name.localeCompare(b.name));
  }, [items]);

  // Memoize callback
  const handleItemClick = useCallback((id) => {
    console.log('Item clicked:', id);
  }, []);

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <ExpensiveList items={sortedItems} onItemClick={handleItemClick} />
    </div>
  );
}
2. 代码拆分
import { lazy, Suspense } from 'react';

// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// Route-based splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}
3. 虚拟化
import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={500}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}
4. 避免匿名函数
// ❌ Bad - creates new function on every render
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => console.log(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

// ✅ Good - use useCallback
function List({ items }) {
  const handleClick = useCallback((id) => {
    console.log(id);
  }, []);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

Server Components(RSC)

React Server Components 在服务端渲染,可减小客户端 bundle 的体积。

Server Component
// app/users/page.js (Server Component by default)
async function UsersPage() {
  // Fetch data directly on server
  const users = await db.users.findAll();

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default UsersPage;
Client Component
// components/Counter.js
"use client"; // Mark as client component

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}
组合模式
// app/page.js (Server Component)
import Counter from '@/components/Counter'; // Client Component

async function HomePage() {
  const data = await fetchData(); // Server-side data fetching

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.description}</p>
      <Counter /> {/* Interactive client component */}
    </div>
  );
}

export default HomePage;

状态管理策略

本地状态(useState)

适用于:组件特定状态

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
提升状态

适用于:兄弟组件之间共享状态

function Parent() {
  const [sharedValue, setSharedValue] = useState('');

  return (
    <>
      <ChildA value={sharedValue} onChange={setSharedValue} />
      <ChildB value={sharedValue} />
    </>
  );
}
Context API

适用于:整个应用范围的状态(主题、身份验证、语言)

const AuthContext = createContext();

function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = async (credentials) => {
    const user = await api.login(credentials);
    setUser(user);
  };

  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
useReducer + Context

适用于:包含多个操作的复杂状态逻辑

const StateContext = createContext();
const DispatchContext = createContext();

function reducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      return { ...state, items: [...state.items, action.payload] };
    case 'REMOVE_ITEM':
      return { ...state, items: state.items.filter(i => i.id !== action.payload) };
    default:
      return state;
  }
}

function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, { items: [] });

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

最佳实践

1. 组件设计
  • 保持组件小巧且专注(单一职责)
  • 将可复用逻辑提取到自定义 Hook 中
  • 优先使用组合,而非继承
  • 谨慎使用 prop 展开
  • 定义 PropTypes 或 TypeScript 类型
2. 状态管理
  • 尽可能让状态保持在本地
  • 仅在必要时提升状态
  • 避免不必要的状态(尽可能从 props 派生)
  • 对复杂状态逻辑使用 reducer
  • 尽可能批量更新状态
3. 性能
  • 不要过早优化——先进行测量
  • 使用 React DevTools Profiler
  • 对开销较大的计算进行记忆化
  • 避免在渲染期间创建对象/数组
  • 为列表正确使用 key prop
4. Effects
  • 让 effect 保持专注(每个 effect 处理一个关注点)
  • 始终指定依赖项
  • 清理 effect(返回清理函数)
  • 对 fetch 请求使用 AbortController
  • 使用 ignore 标志避免竞态条件
5. 代码组织
src/
├── components/
│   ├── common/         # Reusable UI components
│   ├── features/       # Feature-specific components
│   └── layout/         # Layout components
├── hooks/              # Custom hooks
├── context/            # Context providers
├── utils/              # Utility functions
├── services/           # API services
└── types/              # TypeScript types

应避免的常见反模式

1. 修改状态
// ❌ Wrong
const handleAdd = () => {
  items.push(newItem);
  setItems(items);
};

// ✅ Correct
const handleAdd = () => {
  setItems([...items, newItem]);
};
2. 缺少依赖项
// ❌ Wrong
useEffect(() => {
  console.log(userId);
}, []); // Missing userId dependency

// ✅ Correct
useEffect(() => {
  console.log(userId);
}, [userId]);
3. 条件调用 Hook
// ❌ Wrong
if (condition) {
  const [state, setState] = useState(0);
}

// ✅ Correct
const [state, setState] = useState(0);
if (condition) {
  // Use state
}
4. 在组件内部创建组件
// ❌ Wrong
function Parent() {
  function Child() { // Re-created on every render
    return <div>Child</div>;
  }
  return <Child />;
}

// ✅ Correct
function Child() {
  return <div>Child</div>;
}

function Parent() {
  return <Child />;
}

故障排除

常见问题

问题:无限重新渲染循环

  • 检查 useEffect 依赖项
  • 避免在渲染期间直接设置状态
  • 使用函数式更新:setState(prev => prev + 1)

问题:闭包中的值已过期

  • 在 setState 中使用函数式更新
  • 在 useEffect 中添加缺失的依赖项
  • 对可变值使用 useRef

问题:组件未重新渲染

  • 检查状态是否被修改(使用不可变更新)
  • 验证 React.memo 比较函数
  • 确保对象/数组具有新的引用

问题:内存泄漏警告

  • 清理副作用(返回清理函数)
  • 在卸载时取消正在进行的请求
  • 清除计时器/间隔

资源


技能版本:1.0.0 最后更新:2025 年 10 月 技能类别:前端开发、React、JavaScript 兼容版本:React 18+、Next.js 13+、TypeScript

评论

支持 Markdown