Loading...
Loading...
Compare original and translation side by side
useEffectuseEffectuseEffectuseEffectuseEffect// 🔴 BAD: Redundant state + unnecessary Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}firstNamelastNamefullName// ✅ GOOD: Calculate during render
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// Derived — no state, no Effect, one render
const fullName = firstName + ' ' + lastName;
return <span>{fullName}</span>;
}useMemo// ✅ GOOD: Memoize expensive derivations
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// Only recomputes when todos or filter change, not when newTodo changes
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}useEffect// 🔴 错误:冗余状态 + 不必要的Effect
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <span>{fullName}</span>;
}firstNamelastNamefullName// ✅ 正确:在渲染时直接计算
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 派生值——无需状态,无需Effect,仅一次渲染
const fullName = firstName + ' ' + lastName;
return <span>{fullName}</span>;
}useMemo// ✅ 正确:对开销大的派生值进行记忆化
function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState('');
// 仅在todos或filter变化时重新计算,newTodo变化时不会触发
const visibleTodos = useMemo(
() => getFilteredTodos(todos, filter),
[todos, filter]
);
return <ul>{visibleTodos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>;
}// 🔴 BAD: Event-specific logic triggered by state change in an Effect
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
// ...
}// ✅ GOOD: Side effects belong in the event handler that caused them
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}// 🔴 错误:通过Effect监听state变化触发事件专属逻辑
function ProductPage({ product, addToCart }) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to the cart!`);
}
}, [product]);
function handleBuyClick() {
addToCart(product);
}
// ...
}// ✅ 正确:副作用应放在触发它的事件处理函数中
function ProductPage({ product, addToCart }) {
function buyProduct() {
addToCart(product);
showNotification(`Added ${product.name} to the cart!`);
}
function handleBuyClick() {
buyProduct();
}
function handleCheckoutClick() {
buyProduct();
navigateTo('/checkout');
}
// ...
}// 🔴 BAD: Chain of Effects triggering each other (4 render passes!)
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
// ...
}// ✅ GOOD: Derive what you can, compute the rest in the event handler
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// Derived — no state needed
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game already ended.');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) alert('Good game!');
}
}
}
// ...
}// 🔴 错误:Effects互相触发的链式调用(多达4次渲染!)
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => {
if (card !== null && card.gold) {
setGoldCardCount(c => c + 1);
}
}, [card]);
useEffect(() => {
if (goldCardCount > 3) {
setRound(r => r + 1);
setGoldCardCount(0);
}
}, [goldCardCount]);
useEffect(() => {
if (round > 5) setIsGameOver(true);
}, [round]);
// ...
}// ✅ 正确:尽可能派生值,其余逻辑合并到事件处理函数中
function Game() {
const [card, setCard] = useState(null);
const [goldCardCount, setGoldCardCount] = useState(0);
const [round, setRound] = useState(1);
// 派生值——无需状态
const isGameOver = round > 5;
function handlePlaceCard(nextCard) {
if (isGameOver) throw Error('Game already ended.');
setCard(nextCard);
if (nextCard.gold) {
if (goldCardCount < 3) {
setGoldCardCount(goldCardCount + 1);
} else {
setGoldCardCount(0);
setRound(round + 1);
if (round === 5) alert('Good game!');
}
}
}
// ...
}useEffectuseEffect// 🔴 BAD: Manual fetch in useEffect — race conditions, no caching, no SSR
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let ignore = false;
setIsLoading(true);
fetchResults(query).then(json => {
if (!ignore) {
setResults(json);
setIsLoading(false);
}
});
return () => { ignore = true; };
}, [query]);
// ...
}// ✅ GOOD: Use TanStack Query (or your framework's data primitive)
import { useQuery } from '@tanstack/react-query';
function SearchResults({ query }) {
const { data: results = [], isLoading } = useQuery({
queryKey: ['search', query],
queryFn: () => fetchResults(query),
enabled: !!query,
});
// Automatic caching, deduplication, race-condition handling,
// background refetching, and SSR support — for free.
}loaderuse()useSWR(key, fetcher)useEffectignoreuseData(url)useEffectuseEffect// 🔴 错误:在useEffect中手动获取数据——存在竞态条件、无缓存、不支持SSR
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
let ignore = false;
setIsLoading(true);
fetchResults(query).then(json => {
if (!ignore) {
setResults(json);
setIsLoading(false);
}
});
return () => { ignore = true; };
}, [query]);
// ...
}// ✅ 正确:使用TanStack Query(或框架自带的数据原语)
import { useQuery } from '@tanstack/react-query';
function SearchResults({ query }) {
const { data: results = [], isLoading } = useQuery({
queryKey: ['search', query],
queryFn: () => fetchResults(query),
enabled: !!query,
});
// 自动缓存、请求去重、竞态条件处理、
// 后台重新获取、SSR支持——全部免费获得。
}loaderuse()useSWR(key, fetcher)useEffectignoreuseData(url)useSyncExternalStoreuseSyncExternalStoreuseEffectsetState// 🔴 BAD: Manual subscription with useEffect
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}// ✅ GOOD: useSyncExternalStore — concurrent-safe, less code
import { useSyncExternalStore } from 'react';
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}useEffectsetState// 🔴 错误:使用useEffect手动订阅
function OnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;
}// ✅ 正确:useSyncExternalStore——并发安全、代码更少
import { useSyncExternalStore } from 'react';
function subscribe(callback: () => void) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
function getSnapshot() {
return navigator.onLine;
}
function OnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return <span>{isOnline ? '✅ 在线' : '❌ 离线'}</span>;
}keykeyuseEffectkey// 🔴 BAD: Resetting state in an Effect
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}// ✅ GOOD: Use key to reset component state
function ProfilePage({ userId }) {
// When userId changes, React unmounts the old EditProfile and mounts a new one
return <EditProfile userId={userId} key={userId} />;
}
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}useEffectkey// 🔴 错误:在Effect中重置状态
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
useEffect(() => {
setComment('');
}, [userId]);
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}// ✅ 正确:使用key重置组件状态
function ProfilePage({ userId }) {
// 当userId变化时,React会卸载旧的EditProfile并挂载新的实例
return <EditProfile userId={userId} key={userId} />;
}
function EditProfile({ userId }) {
const [comment, setComment] = useState('');
return <textarea value={comment} onChange={e => setComment(e.target.value)} />;
}useEffectuseEffect| Use Case | Why It's Correct |
|---|---|
| Integrating a non-React widget (map, chart, video player) | Syncing React state → imperative DOM API |
| Setting up a WebSocket or EventSource connection | External system lifecycle tied to component |
Measuring DOM layout ( | Reading post-render DOM information |
| Managing focus or scroll position imperatively | Imperative DOM interaction |
| Connecting to hardware APIs (camera, geolocation) | External system with subscribe/cleanup |
| Syncing React state → browser API |
react-intersection-observerframer-motion| 使用场景 | 合理性说明 |
|---|---|
| 集成非React组件(地图、图表、视频播放器) | 同步React状态 → 命令式DOM API |
| 建立WebSocket或EventSource连接 | 外部系统生命周期与组件绑定 |
测量DOM布局( | 读取渲染后的DOM信息 |
| 命令式管理焦点或滚动位置 | 命令式DOM交互 |
| 连接硬件API(摄像头、地理位置) | 带有订阅/清理逻辑的外部系统 |
| 同步React状态 → 浏览器API |
react-intersection-observerframer-motionuseEffectuseMemouseSyncExternalStorekeyuseEffectuseEffectuseMemouseSyncExternalStorekeyuseEffectuseEffectuseEffectuseEffectuseEffectuseEffectsetStateuseSyncExternalStoreuseEffectkeyuseEffectuseEffectuseEffectuseEffectuseEffectsetStateuseSyncExternalStoreuseEffectkeyuseEffect