Skip to content

React Hooks最佳实践指南

引言

React Hooks是React 16.8引入的新特性,它让我们可以在函数组件中使用状态和其他React特性。本文将介绍React Hooks的最佳实践。

基础Hooks

useState

typescript
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState<number>(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

useEffect

typescript
import { useEffect, useState } from 'react';

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  
  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    }
    
    fetchUser();
  }, [userId]);
  
  if (!user) return <div>Loading...</div>;
  
  return <div>{user.name}</div>;
}

useContext

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

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

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

自定义Hooks

useLocalStorage

typescript
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 (error) {
      return initialValue;
    }
  });
  
  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue] as const;
}

useFetch

typescript
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(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
        setError(null);
      } catch (err) {
        setError(err as Error);
        setData(null);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

性能优化

useMemo

typescript
import { useMemo } from 'react';

function ExpensiveComponent({ data }: { data: number[] }) {
  const processedData = useMemo(() => {
    return data.filter(n => n > 0).map(n => n * 2);
  }, [data]);
  
  return <div>{processedData.join(', ')}</div>;
}

useCallback

typescript
import { useCallback } from 'react';

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

状态管理

useReducer

typescript
import { useReducer } from 'react';

interface State {
  count: number;
  step: number;
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'setStep'; payload: number };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1
  });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>
        +
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>
        -
      </button>
    </div>
  );
}

副作用处理

清理副作用

typescript
function ChatRoom({ roomId }: { roomId: string }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();
    
    return () => {
      connection.disconnect();
    };
  }, [roomId]);
  
  return <div>Chat Room: {roomId}</div>;
}

异步处理

typescript
function AsyncComponent() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    let mounted = true;
    
    async function fetchData() {
      const result = await fetch('/api/data');
      const json = await result.json();
      
      if (mounted) {
        setData(json);
      }
    }
    
    fetchData();
    
    return () => {
      mounted = false;
    };
  }, []);
  
  return <div>{data}</div>;
}

测试

测试自定义Hook

typescript
import { renderHook, act } from '@testing-library/react-hooks';

test('useCounter', () => {
  const { result } = renderHook(() => useCounter());
  
  expect(result.current.count).toBe(0);
  
  act(() => {
    result.current.increment();
  });
  
  expect(result.current.count).toBe(1);
});

最佳实践

  1. Hook命名规范

    • 使用use前缀
    • 驼峰命名
    • 语义化命名
  2. 依赖项处理

    • 正确设置依赖数组
    • 避免过多依赖
    • 使用useCallback和useMemo优化
  3. 状态管理

    • 合理拆分状态
    • 使用useReducer处理复杂状态
    • 避免重复状态
  4. 性能优化

    • 避免不必要的重渲染
    • 合理使用缓存
    • 及时清理副作用

常见问题

  1. Hook规则

    • 只在顶层调用Hook
    • 只在React函数中调用Hook
    • 保持调用顺序一致
  2. 闭包陷阱

    • 使用useRef保存最新值
    • 正确设置依赖项
    • 使用函数式更新
  3. 性能问题

    • 避免过度优化
    • 合理使用缓存Hook
    • 控制重渲染次数

参考资料

  1. React官方文档
  2. React Hooks FAQ
  3. React性能优化指南
  4. Testing Library文档
  5. TypeScript与React最佳实践

幸运的人用童年治愈一生,不幸的人用一生治愈童年 —— 强爸