Skip to content

React Hooks 的设计哲学

从类组件到函数组件的革命性转变

如何在函数组件中管理状态和副作用?

hooks 的解决方案:

  • 状态管理:useStateuseReducer
  • 副作用管理:useEffectuseLayoutEffect
  • 上下文管理:useContext
  • 性能优化:useMemouseCallback
  • 引用管理:useRef
  • ...

Hooks 的设计原则

1. 稳定的调用顺序

Hooks 链表机制

React 内部将组件的 Hooks 组织成一个链表结构:

jsx
function UserProfile() {
  const [name, setName] = useState('Alice') // Hook 1
  const [age, setAge] = useState(25) // Hook 2

  useEffect(() => {}) // Hook 3
}

❌ 错误示例:条件性调用 Hooks

jsx
function UserProfile({ showAge }) {
  const [name, setName] = useState('Alice') // Hook 1
  if (showAge) {
    const [age, setAge] = useState(25) // ❌ Hook 2
  }

  useEffect(() => {}) // Hook 3
}

2. 明确的依赖关系

依赖数组的三种模式:无依赖数组、空依赖数组、包含依赖项

  1. 无依赖数组

    jsx
    useEffect(() => {
      console.log('每次渲染后都会执行')
    })
  2. 空依赖数组

    jsx
    useEffect(() => {
      console.log('仅在首次渲染后执行')
    }, [])
  3. 包含依赖项

    jsx
    useEffect(() => {
      console.log('仅在 userId 变化时执行')
    }, [userId])

依赖检查机制

React 使用 Object.is 比较:

  1. 基本类型:比较值
  2. 引用类型:比较引用地址

❌ 错误示例:陈旧闭包问题

先看问题:

jsx
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timerId = setInterval(() => {
      console.log(count) // 闭包捕获了初始的 count 值
      setCount(count + 1) // 闭包捕获了初始的 count 值
    }, 1000)

    return () => clearInterval(timerId)
  }, []) // 空依赖数组,副作用只在首次渲染时执行

  return <h1>{count}</h1>
}

解决方案:

jsx
// 虽然可以,但是需要频繁地创建和销毁定时器,性能较差
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timerId = setInterval(() => {
      console.log(count)
      setCount(count + 1)
    }, 1000)

    return () => clearInterval(timerId)
  }, [count])

  return <h1>{count}</h1>
}
jsx
// 推荐使用函数式更新,避免闭包问题
function Timer() {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timerId = setInterval(() => {
      setCount((count) => {
        console.log(count)
        return count + 1
      })
    }, 1000)

    return () => clearInterval(timerId)
  }, [])

  return <h1>{count}</h1>
}

基于 MIT 许可发布