AgentSkillsCN

Modern React

React 的最佳实践与常用模式

SKILL.md
--- frontmatter
name: Modern React
description: React best practices and patterns

Skill: Modern React

Write clean, performant React components following modern best practices.

Component Structure

Rules

  • ✅ DO: Use functional components with hooks
  • ✅ DO: Keep components small and focused
  • ✅ DO: Co-locate related code (styles, tests, types)
  • ❌ DON'T: Use class components for new code
  • ❌ DON'T: Create god components that do everything

File Structure

code
components/
  Button/
    Button.tsx        # Component
    Button.test.tsx   # Tests
    Button.module.css # Styles (if not using Tailwind)
    index.ts          # Re-export

Example

typescript
// ✅ Good - clean component structure
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

export function Button({
  variant = 'primary',
  size = 'md',
  isLoading = false,
  children,
  onClick,
}: ButtonProps) {
  return (
    <button
      className={cn(styles.button, styles[variant], styles[size])}
      onClick={onClick}
      disabled={isLoading}
    >
      {isLoading ? <Spinner /> : children}
    </button>
  );
}

Hooks Best Practices

Rules

  • ✅ DO: Follow Rules of Hooks (top level, React functions only)
  • ✅ DO: Extract custom hooks for reusable logic
  • ✅ DO: Use the right hook for the job
  • ❌ DON'T: Call hooks conditionally
  • ❌ DON'T: Overuse useEffect

When to Use Each Hook

HookUse Case
useStateLocal component state
useReducerComplex state logic
useContextAccess context values
useRefMutable values, DOM refs
useMemoExpensive calculations
useCallbackStable function references
useEffectSynchronize with external systems

Avoid useEffect Abuse

Rules

  • ✅ DO: Use for external system synchronization
  • ✅ DO: Use for subscriptions (and clean up!)
  • ❌ DON'T: Use for derived state (use useMemo)
  • ❌ DON'T: Use for event handlers
  • ❌ DON'T: Use for data that can be calculated during render

Examples

typescript
// ❌ Bad - derived state in useEffect
function SearchResults({ items, query }: Props) {
  const [filtered, setFiltered] = useState<Item[]>([]);

  useEffect(() => {
    setFiltered(items.filter(i => i.name.includes(query)));
  }, [items, query]);

  return <List items={filtered} />;
}

// ✅ Good - calculate during render
function SearchResults({ items, query }: Props) {
  const filtered = useMemo(
    () => items.filter(i => i.name.includes(query)),
    [items, query]
  );

  return <List items={filtered} />;
}

// ❌ Bad - resetting state on prop change
function Profile({ userId }: Props) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    setUser(null); // Reset
    fetchUser(userId).then(setUser);
  }, [userId]);
}

// ✅ Good - use key to reset component
function ProfilePage({ userId }: Props) {
  return <Profile key={userId} userId={userId} />;
}

// ✅ Good - legitimate useEffect for subscription
function ChatRoom({ roomId }: Props) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    return () => connection.disconnect(); // Cleanup!
  }, [roomId]);
}

State Management

Rules

  • ✅ DO: Keep state as local as possible
  • ✅ DO: Lift state only when needed
  • ✅ DO: Use context for truly global state
  • ✅ DO: Consider external stores for complex state (Zustand, Jotai)
  • ❌ DON'T: Put everything in global state
  • ❌ DON'T: Duplicate state (derive instead)

State Colocation

typescript
// ❌ Bad - state too high
function App() {
  const [searchQuery, setSearchQuery] = useState('');
  return <SearchPage query={searchQuery} setQuery={setSearchQuery} />;
}

// ✅ Good - state colocated
function SearchPage() {
  const [searchQuery, setSearchQuery] = useState('');
  return (
    <>
      <SearchInput value={searchQuery} onChange={setSearchQuery} />
      <SearchResults query={searchQuery} />
    </>
  );
}

Composition Over Configuration

Rules

  • ✅ DO: Use composition (children, render props)
  • ✅ DO: Create compound components for complex UI
  • ❌ DON'T: Pass dozens of props for configuration

Examples

typescript
// ❌ Bad - configuration via props
<Card
  title="Hello"
  subtitle="World"
  showHeader={true}
  showFooter={true}
  footerContent={<Button>Click</Button>}
  headerAction={<IconButton icon="close" />}
/>

// ✅ Good - composition
<Card>
  <Card.Header>
    <Card.Title>Hello</Card.Title>
    <IconButton icon="close" />
  </Card.Header>
  <Card.Body>
    Content here
  </Card.Body>
  <Card.Footer>
    <Button>Click</Button>
  </Card.Footer>
</Card>

Event Handling

Rules

  • ✅ DO: Handle events in event handlers, not useEffect
  • ✅ DO: Use useCallback for handlers passed to memoized children
  • ❌ DON'T: Create arrow functions in JSX for frequently re-rendered components

Examples

typescript
// ✅ Good - event in handler
function Form() {
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    await saveData(formData);
    showToast('Saved!');
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

// ❌ Bad - event response in useEffect
function Form() {
  const [submitted, setSubmitted] = useState(false);

  useEffect(() => {
    if (submitted) {
      saveData(formData);
      showToast('Saved!');
    }
  }, [submitted]);
}

TypeScript with React

Rules

  • ✅ DO: Define prop interfaces
  • ✅ DO: Use React.ReactNode for children
  • ✅ DO: Use discriminated unions for component variants
  • ❌ DON'T: Use React.FC (prefer explicit prop types)

Examples

typescript
// ✅ Good - explicit types
interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
}

function Button({ children, onClick }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}

// ✅ Good - discriminated union for variants
type AlertProps =
  | { variant: 'success'; onDismiss: () => void }
  | { variant: 'error'; onRetry: () => void }
  | { variant: 'info' };

function Alert(props: AlertProps) {
  switch (props.variant) {
    case 'success':
      return <div><button onClick={props.onDismiss}>Dismiss</button></div>;
    case 'error':
      return <div><button onClick={props.onRetry}>Retry</button></div>;
    case 'info':
      return <div>Info message</div>;
  }
}

Custom Hooks

Rules

  • ✅ DO: Extract reusable logic into custom hooks
  • ✅ DO: Prefix with "use"
  • ✅ DO: Return consistent shapes (object for multiple values)
  • ❌ DON'T: Extract too early (wait for duplication)

Examples

typescript
// ✅ Good - reusable data fetching hook
function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    setIsLoading(true);
    fetchUser(userId)
      .then(setUser)
      .catch(setError)
      .finally(() => setIsLoading(false));
  }, [userId]);

  return { user, isLoading, error };
}

// Usage
function Profile({ userId }: { userId: string }) {
  const { user, isLoading, error } = useUser(userId);

  if (isLoading) return <Spinner />;
  if (error) return <Error error={error} />;
  return <UserCard user={user} />;
}