← 返回文章列表

深入理解 React Hooks:从原理到最佳实践

本文从底层原理出发,深入探讨 React Hooks 的工作机制,以及在实际项目中的最佳实践和常见陷阱。

2025年3月10日·4 分钟阅读
字号

React Hooks 自 16.8 版本引入以来,彻底改变了我们编写 React 组件的方式。但要真正用好 Hooks,必须理解它背后的工作原理。

Hooks 的本质:闭包 + 链表

React 内部通过一个单向链表来存储每个组件的 Hooks 状态。每次渲染时,React 按顺序读取这个链表中的节点。

这就是为什么 Hooks 必须在顶层调用——不能在条件语句或循环中使用:

// ❌ 错误示范
function BadComponent() {
  if (someCondition) {
    const [count, setCount] = useState(0); // 破坏了链表顺序
  }
}
 
// ✅ 正确写法
function GoodComponent() {
  const [count, setCount] = useState(0);
  const [visible, setVisible] = useState(someCondition);
}

useState 的惰性初始化

当初始值需要复杂计算时,应该传入函数而非值:

// ❌ 每次渲染都会执行 heavyComputation()
const [data, setData] = useState(heavyComputation());
 
// ✅ 仅在组件挂载时执行一次
const [data, setData] = useState(() => heavyComputation());

useEffect 依赖数组的正确理解

依赖数组告诉 React "当这些值变化时,重新执行 effect"。它不是一个"只运行一次"的开关:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    subscription.unsubscribe();
  };
}, [props.source]); // 当 props.source 变化时,先清理再重新订阅

常见的闭包陷阱

// ❌ 这里 count 始终是 0(闭包捕获了旧值)
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count); // 永远是 0
  }, 1000);
  return () => clearInterval(timer);
}, []); // 缺少 count 依赖
 
// ✅ 使用函数式更新避免依赖
useEffect(() => {
  const timer = setInterval(() => {
    setCount(prev => prev + 1); // 不需要读取 count
  }, 1000);
  return () => clearInterval(timer);
}, []);

useCallback 和 useMemo 的正确使用时机

不要过度优化。这两个 Hook 本身也有开销:

// ❌ 过度优化,得不偿失
const add = useCallback((a: number, b: number) => a + b, []);
 
// ✅ 真正需要稳定引用时才用(如传给 React.memo 的子组件)
const handleSubmit = useCallback(() => {
  submitForm(formData);
}, [formData]);
 
// ✅ 昂贵计算才用 useMemo
const filteredList = useMemo(
  () => hugeList.filter(item => item.active && item.type === selectedType),
  [hugeList, selectedType]
);

自定义 Hook:封装逻辑复用

自定义 Hook 是 Hooks 最强大的特性——将有状态逻辑提取成可复用的函数:

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 {
      return initialValue;
    }
  });
 
  const setValue = useCallback(
    (value: T | ((val: T) => T)) => {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    },
    [key, storedValue]
  );
 
  return [storedValue, setValue] as const;
}
 
// 使用
const [theme, setTheme] = useLocalStorage('theme', 'dark');

总结

理解 Hooks 的关键点:

  1. 链表顺序 — 永远在顶层调用,保证每次渲染顺序一致
  2. 闭包 — effect 内部捕获的是当时的值,注意依赖声明
  3. 按需优化useCallback/useMemo 不是免费的,用在刀刃上
  4. 自定义 Hook — 逻辑复用的最佳方式,命名以 use 开头

掌握这些原理,你会发现 Hooks 写起来既优雅又强大。

分享

// RELATED_POSTS