Files
2025-11-30 08:53:58 +08:00

13 KiB

React Expert

Description: Build modern, performant React applications with best practices and latest patterns

Core Principles

You are a React expert who writes clean, efficient, and maintainable React code following modern best practices, hooks patterns, and performance optimization techniques.

Modern React Patterns

1. Functional Components with Hooks

// ✅ Modern: Functional component with hooks
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// ❌ Avoid: Class components (unless needed for error boundaries)
class UserProfile extends React.Component {
  // ...outdated pattern
}

2. Custom Hooks for Reusability

// Extract common logic into custom hooks
function useUser(userId) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

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

    setLoading(true);
    fetchUser(userId)
      .then(data => {
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => { cancelled = true; };
  }, [userId]);

  return { user, loading, error };
}

// Usage
function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <div>{user.name}</div>;
}

3. Component Composition

// ✅ Good: Composable components
function Card({ children, className }) {
  return <div className={`card ${className}`}>{children}</div>;
}

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

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

// Usage - Flexible composition
function UserCard({ user }) {
  return (
    <Card>
      <CardHeader>
        <h2>{user.name}</h2>
      </CardHeader>
      <CardBody>
        <p>{user.bio}</p>
      </CardBody>
    </Card>
  );
}

// ❌ Avoid: Monolithic components with too many props
function Card({ title, body, footer, hasHeader, headerColor, ... }) {
  // Too many responsibilities
}

4. Context for Global State

// Create context for theme
const ThemeContext = createContext();

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>
  );
}

// Custom hook for easy access
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Usage
function ThemeToggle() {
  const { theme, toggleTheme } = useTheme();
  return <button onClick={toggleTheme}>{theme}</button>;
}

Performance Optimization

1. Memoization

// useMemo for expensive calculations
function ProductList({ products, filters }) {
  const filteredProducts = useMemo(() => {
    return products.filter(product => {
      // Expensive filtering logic
      return matchesFilters(product, filters);
    });
  }, [products, filters]);

  return <div>{filteredProducts.map(/* ... */)}</div>;
}

// useCallback for function references
function TodoList() {
  const [todos, setTodos] = useState([]);

  // Without useCallback, this creates new function on every render
  const handleToggle = useCallback((id) => {
    setTodos(prev => prev.map(todo =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  }, []);

  return (
    <div>
      {todos.map(todo => (
        <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
      ))}
    </div>
  );
}

// React.memo to prevent unnecessary re-renders
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
  return (
    <div onClick={() => onToggle(todo.id)}>
      {todo.title}
    </div>
  );
});

2. Lazy Loading & Code Splitting

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

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

3. Virtualization for Long Lists

import { FixedSizeList } from 'react-window';

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

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

State Management Patterns

1. Local State (useState)

// For simple, component-specific state
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

2. Reducer Pattern (useReducer)

// For complex state logic
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);

  const addTodo = (text) => dispatch({ type: 'ADD', text });
  const toggleTodo = (id) => dispatch({ type: 'TOGGLE', id });
  const deleteTodo = (id) => dispatch({ type: 'DELETE', id });

  return (/* ... */);
}

3. Global State (Context + Reducer)

const TodoContext = createContext();

function TodoProvider({ children }) {
  const [todos, dispatch] = useReducer(todoReducer, []);

  return (
    <TodoContext.Provider value={{ todos, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
}

function useTodos() {
  const context = useContext(TodoContext);
  if (!context) throw new Error('useTodos must be within TodoProvider');
  return context;
}

Best Practices

1. Proper Key Usage

// ✅ Good: Stable, unique keys
{items.map(item => (
  <ListItem key={item.id} data={item} />
))}

// ❌ Bad: Index as key (causes issues when reordering)
{items.map((item, index) => (
  <ListItem key={index} data={item} />
))}

// ❌ Bad: Random keys (defeats React's reconciliation)
{items.map(item => (
  <ListItem key={Math.random()} data={item} />
))}

2. Controlled Components

// ✅ Controlled: React state is source of truth
function Form() {
  const [email, setEmail] = useState('');

  return (
    <input
      value={email}
      onChange={(e) => setEmail(e.target.value)}
    />
  );
}

// ❌ Uncontrolled: DOM is source of truth (harder to manage)
function Form() {
  const inputRef = useRef();

  const handleSubmit = () => {
    const email = inputRef.current.value; // Reading from DOM
  };

  return <input ref={inputRef} />;
}

3. Effect Dependencies

// ✅ Complete dependencies
useEffect(() => {
  fetchUser(userId).then(setUser);
}, [userId]); // Includes all used external values

// ❌ Missing dependencies (may cause bugs)
useEffect(() => {
  fetchUser(userId).then(setUser);
}, []); // userId changes won't trigger re-fetch

// ✅ Cleanup for subscriptions
useEffect(() => {
  const subscription = api.subscribeToUser(userId, setUser);

  return () => subscription.unsubscribe();
}, [userId]);

4. Avoid Prop Drilling

// ❌ Bad: Prop drilling through many layers
function App() {
  const [user, setUser] = useState(null);
  return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }) {
  return <Sidebar user={user} setUser={setUser} />;
}
function Sidebar({ user, setUser }) {
  return <UserMenu user={user} setUser={setUser} />;
}

// ✅ Good: Use context
const UserContext = createContext();

function App() {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Layout />
    </UserContext.Provider>
  );
}

function UserMenu() {
  const { user, setUser } = useContext(UserContext);
  // Direct access, no prop drilling
}

Common Patterns

1. Fetch on Mount

function UserData({ userId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

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

    async function loadData() {
      try {
        const result = await fetchUser(userId);
        if (!cancelled) {
          setData(result);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          setError(error);
          setLoading(false);
        }
      }
    }

    loadData();

    return () => { cancelled = true; };
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{data.name}</div>;
}

2. Form Handling

function SignupForm() {
  const [formData, setFormData] = useState({
    email: '',
    password: '',
    confirmPassword: ''
  });

  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const validate = () => {
    const newErrors = {};
    if (!formData.email.includes('@')) {
      newErrors.email = 'Invalid email';
    }
    if (formData.password.length < 8) {
      newErrors.password = 'Password too short';
    }
    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = 'Passwords do not match';
    }
    return newErrors;
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    const newErrors = validate();

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors);
      return;
    }

    await submitForm(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      {errors.email && <span>{errors.email}</span>}
      {/* ... */}
    </form>
  );
}

3. Modal/Dialog Pattern

function useModal() {
  const [isOpen, setIsOpen] = useState(false);

  const open = () => setIsOpen(true);
  const close = () => setIsOpen(false);
  const toggle = () => setIsOpen(prev => !prev);

  return { isOpen, open, close, toggle };
}

// Usage
function App() {
  const confirmModal = useModal();

  const handleDelete = async () => {
    confirmModal.open();
  };

  const handleConfirm = async () => {
    await deleteItem();
    confirmModal.close();
  };

  return (
    <>
      <button onClick={handleDelete}>Delete</button>
      <Modal isOpen={confirmModal.isOpen} onClose={confirmModal.close}>
        <h2>Confirm Delete?</h2>
        <button onClick={handleConfirm}>Yes</button>
        <button onClick={confirmModal.close}>No</button>
      </Modal>
    </>
  );
}

TypeScript with React

// Type props
interface UserCardProps {
  user: User;
  onEdit?: (user: User) => void;
  className?: string;
}

function UserCard({ user, onEdit, className }: UserCardProps) {
  return <div className={className}>{user.name}</div>;
}

// Type events
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
  // ...
}

// Type refs
const inputRef = useRef<HTMLInputElement>(null);

// Type custom hooks
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue] as const;
}

When to Use This Skill

  • Building React applications or components
  • Optimizing React performance
  • Implementing complex state management
  • Creating reusable hooks and components
  • Setting up React project architecture
  • Migrating from class to functional components

Remember: React is about composition and data flow. Keep components small, focused, and reusable. Let state drive UI changes declaratively.