返回博客列表
React

React 知识碎片

cooler404
2025-12-11

状态管理

直接修改 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} />;
}