Skip to content

React Hooks 深入理解

React Hooks 是 React 16.8 引入的革命性特性,它让我们可以在函数组件中使用状态和生命周期等特性。本文将深入探讨 React Hooks 的原理、使用场景和最佳实践。

什么是 Hooks

Hooks 是一些特殊的函数,让你可以在函数组件中"钩入" React 的特性。它们让你无需编写类组件就能使用 React 的功能。

为什么需要 Hooks

在 Hooks 出现之前,函数组件只能作为"无状态组件"使用。如果需要状态或生命周期,必须使用类组件。Hooks 的出现解决了以下问题:

  • 逻辑复用困难:高阶组件和渲染属性模式复杂
  • 复杂组件难以理解:相关逻辑分散在不同生命周期中
  • 类组件的复杂性this 绑定、性能优化等问题

基础 Hooks

useState

useState 是最常用的 Hook,用于在函数组件中添加状态。

javascript
import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)
  
  return (
    <div>
      <p>你点击了 {count} 次</p>
      <button onClick={() => setCount(count + 1)}>
        点击我
      </button>
    </div>
  )
}

函数式更新:当新的状态依赖于旧状态时,使用函数式更新:

javascript
const [count, setCount] = useState(0)

// ❌ 可能不正确
setCount(count + 1)
setCount(count + 1)

// ✅ 正确
setCount(prevCount => prevCount + 1)
setCount(prevCount => prevCount + 1)

useEffect

useEffect 用于处理副作用,相当于类组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 的组合。

javascript
import { useState, useEffect } from 'react'

function UserProfile({ userId }) {
  const [user, setUser] = useState(null)
  
  useEffect(() => {
    // 组件挂载和更新时执行
    fetchUser(userId).then(setUser)
    
    // 清理函数(可选)
    return () => {
      // 组件卸载时执行清理
    }
  }, [userId]) // 依赖数组
  
  return user ? <div>{user.name}</div> : <div>加载中...</div>
}

依赖数组的作用

  • 空数组 []:只在组件挂载时执行一次
  • 有依赖项:依赖项变化时执行
  • 无依赖数组:每次渲染都执行

useContext

useContext 用于在函数组件中访问 Context。

javascript
import { createContext, useContext } from 'react'

const ThemeContext = createContext('light')

function ThemedButton() {
  const theme = useContext(ThemeContext)
  
  return <button className={theme}>按钮</button>
}

高级 Hooks

useReducer

useReduceruseState 的替代方案,适用于复杂的状态逻辑。

javascript
import { useReducer } from 'react'

const initialState = { count: 0 }

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 }
    case 'decrement':
      return { count: state.count - 1 }
    case 'reset':
      return initialState
    default:
      throw new Error()
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState)
  
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </>
  )
}

useMemo

useMemo 用于缓存计算结果,避免不必要的重复计算。

javascript
import { useMemo } from 'react'

function ExpensiveComponent({ items, filter }) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.category === filter)
  }, [items, filter])
  
  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

useCallback

useCallback 用于缓存函数,避免子组件不必要的重新渲染。

javascript
import { useState, useCallback } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  
  const handleClick = useCallback(() => {
    console.log('点击了')
  }, []) // 依赖数组
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child onClick={handleClick} />
    </div>
  )
}

function Child({ onClick }) {
  return <button onClick={onClick}>子组件</button>
}

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。

javascript
import { useRef, useEffect } from 'react'

function TextInputWithFocusButton() {
  const inputEl = useRef(null)
  
  const onButtonClick = () => {
    inputEl.current.focus()
  }
  
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>聚焦输入框</button>
    </>
  )
}

保存可变值useRef 还可以用来保存任何可变值,类似于类组件的实例属性。

javascript
function Timer() {
  const intervalRef = useRef(null)
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      console.log('定时器运行中')
    }, 1000)
    
    return () => {
      clearInterval(intervalRef.current)
    }
  }, [])
  
  return <div>定时器组件</div>
}

自定义 Hooks

自定义 Hook 是一个以 "use" 开头的函数,可以在其中调用其他 Hooks。

数据获取 Hook

javascript
import { useState, useEffect } from 'react'

function useFetch(url) {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  
  useEffect(() => {
    setLoading(true)
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data)
        setLoading(false)
      })
      .catch(error => {
        setError(error)
        setLoading(false)
      })
  }, [url])
  
  return { data, loading, error }
}

// 使用
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`)
  
  if (loading) return <div>加载中...</div>
  if (error) return <div>错误: {error.message}</div>
  return <div>{user.name}</div>
}

本地存储 Hook

javascript
import { useState, useEffect } from 'react'

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key)
      return item ? JSON.parse(item) : initialValue
    } catch (error) {
      return initialValue
    }
  })
  
  const setValue = (value) => {
    try {
      setStoredValue(value)
      window.localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.error(error)
    }
  }
  
  return [storedValue, setValue]
}

// 使用
function App() {
  const [name, setName] = useLocalStorage('name', '')
  
  return (
    <input
      value={name}
      onChange={e => setName(e.target.value)}
      placeholder="输入你的名字"
    />
  )
}

Hooks 规则

只在顶层调用 Hooks

不要在循环、条件或嵌套函数中调用 Hooks。

javascript
// ❌ 错误
function Component({ condition }) {
  if (condition) {
    const [state, setState] = useState(0) // 错误!
  }
}

// ✅ 正确
function Component({ condition }) {
  const [state, setState] = useState(0) // 正确
  if (condition) {
    // 使用 state
  }
}

只在 React 函数中调用 Hooks

  • ✅ 在 React 函数组件中调用
  • ✅ 在自定义 Hook 中调用
  • ❌ 在普通 JavaScript 函数中调用

性能优化

避免不必要的渲染

javascript
import { memo, useMemo, useCallback } from 'react'

// 使用 memo 包装组件
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
  // 组件逻辑
})

// 使用 useMemo 缓存计算结果
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b)
}, [a, b])

// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
  doSomething(a, b)
}, [a, b])

最佳实践

  1. 按功能组织 Hooks:将相关的状态和副作用放在一起
  2. 提取自定义 Hooks:将可复用的逻辑提取到自定义 Hook 中
  3. 合理使用依赖数组:确保依赖数组包含所有使用的值
  4. 避免过度优化:不要过早优化,先确保代码正确
  5. 遵循 Hooks 规则:始终在顶层调用 Hooks

总结

React Hooks 为函数组件带来了强大的能力,让我们可以编写更简洁、可复用的代码。通过合理使用基础 Hooks 和自定义 Hooks,我们可以构建出高效、易维护的 React 应用。掌握 Hooks 是现代 React 开发的关键技能。


辛田信息技术 · 内部技术分享 · 仅供学习与参考