mcpField
R
react-patterns

react-patterns

github.com/claude-dev-suite开发4641 次安装4 天前更新
代码仓库

关于 react-patterns

面向 React 开发者的组件模式技能,汇总复合组件、Render Props、高阶组件、自定义 Hooks 和组合等模式,并说明适用场景与注意事项。

主要功能

  1. 01复合组件模式
  2. 02Render Props 与高阶组件
  3. 03自定义 Hooks
  4. 04组件组合实践
  5. 05模式选择与问题排查

适用场景

  1. 01构建灵活的相关组件
  2. 02复用有状态逻辑
  3. 03分离组件逻辑与界面
  4. 04排查 Context 或渲染问题

安装

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

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill react-patterns

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

附带文件advanced.md

详细介绍

React 模式

完整参考:有关容器/展示、状态机、插槽、多态和 Provider 模式,请参阅 advanced.md。

复合组件

协同工作以构成连贯 UI 的组件:

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

interface AccordionContextValue {
  openItems: Set<string>;
  toggle: (id: string) => void;
}

const AccordionContext = createContext<AccordionContextValue | null>(null);

function useAccordion() {
  const context = useContext(AccordionContext);
  if (!context) {
    throw new Error('Accordion components must be used within Accordion');
  }
  return context;
}

function Accordion({ children, multiple = false, defaultOpen = [] }: AccordionProps) {
  const [openItems, setOpenItems] = useState<Set<string>>(new Set(defaultOpen));

  const toggle = (id: string) => {
    setOpenItems((prev) => {
      const next = new Set(prev);
      if (next.has(id)) {
        next.delete(id);
      } else {
        if (!multiple) next.clear();
        next.add(id);
      }
      return next;
    });
  };

  return (
    <AccordionContext.Provider value={{ openItems, toggle }}>
      <div className="accordion">{children}</div>
    </AccordionContext.Provider>
  );
}

// Attach sub-components
Accordion.Item = Item;
Accordion.Trigger = Trigger;
Accordion.Content = Content;

Render Props

将函数作为子项或属性传递,以控制渲染:

interface DataFetcherProps<T> {
  url: string;
  children: (state: {
    data: T | null;
    loading: boolean;
    error: Error | null;
    refetch: () => void;
  }) => ReactNode;
}

function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch(url);
      if (!res.ok) throw new Error('Failed to fetch');
      setData(await res.json());
    } catch (e) {
      setError(e as Error);
    } finally {
      setLoading(false);
    }
  }, [url]);

  useEffect(() => { fetchData(); }, [fetchData]);

  return <>{children({ data, loading, error, refetch: fetchData })}</>;
}

// Usage
<DataFetcher<User[]> url="/api/users">
  {({ data, loading, error, refetch }) => {
    if (loading) return <Spinner />;
    if (error) return <ErrorMessage error={error} onRetry={refetch} />;
    return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
  }}
</DataFetcher>

高阶组件(HOC)

包装组件以添加功能:

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

// HOC with Refs
function withLogger<P extends object>(WrappedComponent: React.ComponentType<P>) {
  const WithLogger = React.forwardRef<unknown, P>((props, ref) => {
    useEffect(() => { console.log('Props changed:', props); }, [props]);
    return <WrappedComponent {...props} ref={ref} />;
  });
  WithLogger.displayName = `WithLogger(${WrappedComponent.displayName || WrappedComponent.name})`;
  return WithLogger;
}

自定义 Hook 模式

将可复用逻辑提取到 Hook 中:

// useLocalStorage
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch { return initialValue; }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    const valueToStore = value instanceof Function ? value(storedValue) : value;
    setStoredValue(valueToStore);
    window.localStorage.setItem(key, JSON.stringify(valueToStore));
  }, [key, storedValue]);

  return [storedValue, setValue] as const;
}

// useDebounce
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debouncedValue;
}

// useOnClickOutside
function useOnClickOutside(
  ref: RefObject<HTMLElement>,
  handler: (event: MouseEvent | TouchEvent) => void
) {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
      if (!ref.current || ref.current.contains(event.target as Node)) return;
      handler(event);
    };
    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);
    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

组合模式

优先使用组合,而非继承:

function Card({ children, className }: CardProps) {
  return <div className={`card ${className || ''}`}>{children}</div>;
}

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

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

// Specialized cards through composition
function UserCard({ user }: { user: User }) {
  return (
    <Card>
      <CardHeader>
        <Avatar src={user.avatar} />
        <h3>{user.name}</h3>
      </CardHeader>
      <CardBody><p>{user.bio}</p></CardBody>
    </Card>
  );
}

最佳实践总结

模式适用场景
复合组件灵活的相关组件
Render Props共享行为、控制渲染
HOC横切关注点、遗留代码
自定义 Hook可复用的有状态逻辑
组合由简单构建复杂结构
容器/展示将逻辑与 UI 分离
状态机复杂的状态转换
多态灵活的元素类型
  • 使用自定义 Hook 实现可复用逻辑
  • 保持组件小巧且专注
  • 使用 TypeScript 改善开发体验
  • 不要过度使用模式——保持简单(KISS)
  • 不要嵌套过多 HOC

反模式

反模式问题解决方案
对简单组件过度设计不必要的复杂性使用简单的属性
嵌套过多 HOC属性冲突,难以调试使用组合或 Hook
未进行记忆化的 Render Props性能问题对渲染函数进行记忆化
属性层层传递过深难以维护使用 Context 或复合组件
使用 Context 管理频繁变化的值性能问题使用状态库

快速故障排除

问题可能原因修复方法
Context 不可用缺少 Provider使用 Provider 组件进行包装
HOC 丢失 ref未转发 ref使用 React.forwardRef
Render Props 导致重新渲染函数标识发生变化对渲染函数进行记忆化

参考文档

评论

支持 Markdown