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);
});
最佳实践
Hook命名规范
- 使用use前缀
- 驼峰命名
- 语义化命名
依赖项处理
- 正确设置依赖数组
- 避免过多依赖
- 使用useCallback和useMemo优化
状态管理
- 合理拆分状态
- 使用useReducer处理复杂状态
- 避免重复状态
性能优化
- 避免不必要的重渲染
- 合理使用缓存
- 及时清理副作用
常见问题
Hook规则
- 只在顶层调用Hook
- 只在React函数中调用Hook
- 保持调用顺序一致
闭包陷阱
- 使用useRef保存最新值
- 正确设置依赖项
- 使用函数式更新
性能问题
- 避免过度优化
- 合理使用缓存Hook
- 控制重渲染次数
参考资料
- React官方文档
- React Hooks FAQ
- React性能优化指南
- Testing Library文档
- TypeScript与React最佳实践