React组件模式Skill component-patterns

这个技能涵盖React中各种组件模式,如组合模式、自定义钩子、渲染属性、高阶组件和复合组件,用于构建高效、可维护的前端UI组件。关键词:React组件、组合、自定义钩子、渲染属性、高阶组件、复合组件、性能优化、前端开发、代码分割、虚拟滚动。

前端开发 0 次安装 0 次浏览 更新于 3/9/2026

name: component-patterns description: 实现React组件模式,包括组合、自定义钩子、渲染属性、高阶组件和复合组件。适用于构建可重用的React组件、实现设计模式或重构组件架构。

组件模式

实现React组件模式以构建可重用、可维护的组件。

快速开始

多数情况下使用组合模式,共享逻辑使用自定义钩子,灵活API使用复合组件。

指令

组合模式

构建灵活组件的默认模式。

基本组合:

function Card({ children }) {
  return <div className="card">{children}</div>;
}

function CardHeader({ children }) {
  return <div className="card-header">{children}</div>;
}

function CardBody({ children }) {
  return <div className="card-body">{children}</div>;
}

// 用法
<Card>
  <CardHeader>标题</CardHeader>
  <CardBody>内容</CardBody>
</Card>

带属性:

interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
}

function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
  return (
    <button className={`btn btn-${variant} btn-${size}`}>
      {children}
    </button>
  );
}

自定义钩子模式

跨组件提取和重用有状态逻辑。

基本自定义钩子:

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  
  const toggle = useCallback(() => {
    setValue(v => !v);
  }, []);
  
  return [value, toggle] as const;
}

// 用法
function Component() {
  const [isOpen, toggleOpen] = useToggle();
  return <button onClick={toggleOpen}>{isOpen ? '关闭' : '打开'}</button>;
}

数据获取钩子:

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);
  
  return { data, loading, error };
}

表单钩子:

function useForm<T>(initialValues: T) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValues(prev => ({
      ...prev,
      [e.target.name]: e.target.value
    }));
  };
  
  const reset = () => setValues(initialValues);
  
  return { values, handleChange, reset };
}

复合组件模式

通过隐式状态共享创建灵活组件API。

基本复合组件:

interface TabsContextValue {
  activeTab: string;
  setActiveTab: (tab: string) => void;
}

const TabsContext = createContext<TabsContextValue | null>(null);

function Tabs({ children, defaultTab }: { children: React.ReactNode; defaultTab: string }) {
  const [activeTab, setActiveTab] = useState(defaultTab);
  
  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }: { children: React.ReactNode }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ id, children }: { id: string; children: React.ReactNode }) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('Tab must be used within Tabs');
  
  const { activeTab, setActiveTab } = context;
  
  return (
    <button
      className={activeTab === id ? 'active' : ''}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
}

function TabPanel({ id, children }: { id: string; children: React.ReactNode }) {
  const context = useContext(TabsContext);
  if (!context) throw new Error('TabPanel must be used within Tabs');
  
  if (context.activeTab !== id) return null;
  return <div className="tab-panel">{children}</div>;
}

// 附加子组件
Tabs.List = TabList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;

// 用法
<Tabs defaultTab="home">
  <Tabs.List>
    <Tabs.Tab id="home">首页</Tabs.Tab>
    <Tabs.Tab id="profile">个人资料</Tabs.Tab>
  </Tabs.List>
  <Tabs.Panel id="home">首页内容</Tabs.Panel>
  <Tabs.Panel id="profile">个人资料内容</Tabs.Panel>
</Tabs>

渲染属性模式

通过函数属性提供渲染灵活性。

基本渲染属性:

interface MouseTrackerProps {
  render: (position: { x: number; y: number }) => React.ReactNode;
}

function MouseTracker({ render }: MouseTrackerProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  useEffect(() => {
    const handleMove = (e: MouseEvent) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    
    window.addEventListener('mousemove', handleMove);
    return () => window.removeEventListener('mousemove', handleMove);
  }, []);
  
  return <>{render(position)}</>;
}

// 用法
<MouseTracker
  render={({ x, y }) => (
    <div>鼠标位置 {x}, {y}</div>
  )}
/>

作为函数的子元素:

interface DataProviderProps<T> {
  url: string;
  children: (data: T | null, loading: boolean) => React.ReactNode;
}

function DataProvider<T>({ url, children }: DataProviderProps<T>) {
  const { data, loading } = useFetch<T>(url);
  return <>{children(data, loading)}</>;
}

// 用法
<DataProvider url="/api/users">
  {(users, loading) => (
    loading ? <Spinner /> : <UserList users={users} />
  )}
</DataProvider>

高阶组件(HOC)模式

包装组件以添加功能(传统模式,建议使用钩子)。

基本HOC:

function withLoading<P extends object>(
  Component: React.ComponentType<P>
) {
  return function WithLoadingComponent(props: P & { loading: boolean }) {
    const { loading, ...rest } = props;
    
    if (loading) return <Spinner />;
    return <Component {...(rest as P)} />;
  };
}

// 用法
const UserListWithLoading = withLoading(UserList);
<UserListWithLoading users={users} loading={loading} />

带额外属性的HOC:

function withAuth<P extends object>(
  Component: React.ComponentType<P & { user: User }>
) {
  return function WithAuthComponent(props: P) {
    const { user, loading } = useAuth();
    
    if (loading) return <Spinner />;
    if (!user) return <Navigate to="/login" />;
    
    return <Component {...props} user={user} />;
  };
}

性能优化模式

React.memo

防止昂贵组件的不必要重新渲染。

const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  // 昂贵渲染逻辑
  return <div>{/* 复杂UI */}</div>;
});

// 自定义比较
const MemoizedComponent = React.memo(
  function Component({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);

useMemo

记忆化昂贵计算。

function Component({ items }) {
  const sortedItems = useMemo(() => {
    return items.sort((a, b) => a.value - b.value);
  }, [items]);
  
  const total = useMemo(() => {
    return items.reduce((sum, item) => sum + item.price, 0);
  }, [items]);
  
  return <div>{/* 使用 sortedItems 和 total */}</div>;
}

useCallback

记忆化函数以防止子组件重新渲染。

function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  return <MemoizedChild onClick={handleClick} />;
}

const MemoizedChild = React.memo(function Child({ onClick }) {
  return <button onClick={onClick}>点击</button>;
});

代码分割

拆分代码以减少初始包大小。

const LazyComponent = React.lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <LazyComponent />
    </Suspense>
  );
}

// 基于路由的分割
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));

<Routes>
  <Route path="/dashboard" element={
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  } />
</Routes>

虚拟滚动

高效处理大型列表。

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

function VirtualList({ items }) {
  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`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

常见模式

容器/展示组件模式

分离逻辑和展示。

// 展示组件
interface UserListProps {
  users: User[];
  onDelete: (id: string) => void;
}

function UserList({ users, onDelete }: UserListProps) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => onDelete(user.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

// 容器组件
function UserListContainer() {
  const { data: users, isLoading } = useQuery(['users'], fetchUsers);
  const deleteMutation = useMutation(deleteUser);
  
  if (isLoading) return <Spinner />;
  
  return <UserList users={users} onDelete={deleteMutation.mutate} />;
}

提供者模式

在组件树中共享数据。

interface ThemeContextValue {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  const toggleTheme = useCallback(() => {
    setTheme(t => t === '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;
}

受控与非受控组件

受控(推荐):

function ControlledInput() {
  const [value, setValue] = useState('');
  
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

非受控:

function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);
  
  const handleSubmit = () => {
    console.log(inputRef.current?.value);
  };
  
  return <input ref={inputRef} />;
}

故障排除

不必要的重新渲染:

  • 使用React DevTools性能分析器识别
  • 用React.memo包装
  • 适当使用useMemo/useCallback
  • 检查状态是否提升过高

属性传递问题:

  • 使用Context API处理深层嵌套属性
  • 考虑组件组合
  • 提取中间组件

钩子中的闭包问题:

  • 为useEffect/useCallback添加依赖
  • 使用函数式更新:setState(prev => prev + 1)
  • 使用useRef处理可变值

内存泄漏:

  • 在useEffect中清理订阅
  • 卸载时取消待处理请求
  • 移除事件监听器