状态管理
直接修改 State
React 的状态是不可变的(Immutable)。直接修改对象属性不会触发组件重新渲染。
- ❌ 错误写法
const [user, setUser] = useState({ name: 'Alice', age: 20 });
const updateName = () => {
// 错误:直接修改了引用,React 感知不到变化
user.name = 'Bob';
// 即使调用 setUser(user),由于引用没变,可能不会触发更新
setUser(user);
};
- ✅ 正确写法
const updateName = () => {
// 正确:创建一个新对象
setUser({ ...user, name: 'Bob' });
// 或者使用函数式更新
setUser(prev => ({ ...prev, name: 'Bob' }));
};
依赖旧状态进行更新
当新的 state 依赖于旧的 state 时(例如计数器),直接使用变量可能导致竟态条件或批处理导致的错误。
- ❌ 错误写法
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// 结果可能只加了 1,而不是 3
};
- ✅ 正确写法
const handleIncrement = () => {
// 使用回调函数,prev 保证是最新值
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
滥用 State 存储“计算属性”
如果某个值可以通过现有的 props 或 state 计算出来,就不应该把它单独存为 state。
- ❌ 错误写法
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');
// 错误:fullName 冗余,且容易导致数据不同步
const [fullName, setFullName] = useState('John Doe');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
- ✅ 正确写法
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');
// 正确:直接在渲染中计算(Derived State)
const fullName = `${firstName} ${lastName}`;
副作用与生命周期
useEffect 依赖项缺失
这是最常见的 Bug 来源。使用了外部变量却不放入依赖数组,导致闭包陷阱(Closure Trap),拿不到最新值。
- ❌ 错误写法
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远打印初始值 0
}, 1000);
return () => clearInterval(timer);
}, []); // 欺骗 React 说不依赖 count
- ✅ 正确写法
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(timer);
}, [count]); // 依赖变化时重启定时器
// 或者使用 ref 解决闭包问题而不重启定时器
在 useEffect 中不写清理函数
对于订阅、定时器或事件监听,如果不清理,会导致内存泄漏或奇怪的行为。
- ❌ 错误写法
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
// 缺少 return 清理函数
}, []);
- ✅ 正确写法
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
// 组件卸载时移除监听
return () => window.removeEventListener('resize', handleResize);
}, []);
渲染逻辑
列表渲染使用 Index 作为 Key
如果列表项会发生排序、插入或删除,使用 index 作为 key 会导致渲染错误或性能问题。
- ❌ 错误写法
{items.map((item, index) => (
<ListItem key={index} name={item.name} />
))}
- ✅ 正确写法
// 使用数据中唯一的 ID
{items.map((item) => (
<ListItem key={item.id} name={item.name} />
))}
短路运算渲染数字 0
JavaScript 中 0 是假值,但 React 会渲染数字 0。
- ❌ 错误写法
// 如果 list.length 是 0,页面会显示 "0"
{list.length && <List data={list} />}
- ✅ 正确写法
// 方式1:确保前面是布尔值
{list.length > 0 && <List data={list} />}
// 方式2:双重取反
{!!list.length && <List data={list} />}
// 方式3:三元运算符
{list.length ? <List data={list} /> : null}
在组件内部定义组件
这会导致每次父组件渲染时,子组件都会被重新创建(Remount),导致输入焦点丢失、状态重置以及性能极差。
- ❌ 错误写法
function Parent() {
// 每次 Parent 渲染,Child 都是一个新的函数引用
function Child() {
return <div>I am a child</div>;
}
return (
<div>
<Child />
</div>
);
}
- ✅ 正确写法
// 将子组件移到外部
function Child() {
return <div>I am a child</div>;
}
function Parent() {
return (
<div>
<Child />
</div>
);
}
事件处理
立即调用事件处理函数
初学者常犯错误,导致组件渲染时函数就立即执行了,而不是在点击时执行。
- ❌ 错误写法
// 渲染时立即执行 alert
<button onClick={alert('Clicked')}>Click Me</button>
- ✅ 正确写法
// 传递函数引用
<button onClick={() => alert('Clicked')}>Click Me</button>
Hooks 规则
条件性调用 Hooks
React 依赖 Hooks 的调用顺序来管理状态,不能在 if/for/function 内部调用 Hooks。
- ❌ 错误写法
if (isLoading) {
// 错误:Hooks 调用顺序被打乱
useEffect(() => {}, []);
return <Loader />;
}
const [data, setData] = useState(null);
- ✅ 正确写法
// 所有的 Hooks 必须在顶部无条件调用
const [data, setData] = useState(null);
useEffect(() => {}, []);
if (isLoading) {
return <Loader />;
}
性能优化
遗漏 useMemo/useCallback
将对象或函数作为 props 传递给子组件(尤其是被 React.memo 包裹的子组件)时,如果不缓存,会导致子组件无意义的重渲染。
- ❌ 错误写法
function Parent() {
// 每次 Parent 渲染,style 都是新对象,handleClick 都是新函数
const style = { color: 'red' };
const handleClick = () => console.log('click');
return <Child style={style} onClick={handleClick} />;
}
- ✅ 正确写法
function Parent() {
const style = useMemo(() => ({ color: 'red' }), []);
const handleClick = useCallback(() => console.log('click'), []);
return <Child style={style} onClick={handleClick} />;
}