Skip to content

React 内置 Hook

字数
1483 字
阅读时间
7 分钟

请查看 Hooks 目录 了解详细内容。

State Hook

存储状态变量值。

useState

最基础的 Hook,用于组件内声明状态变量。

基础语法

ts
const [state, setState] = useState(initialState)
  • setState 可接收:
    • 新值: setState(newValue)
    • 更新函数: setState(prev => newValue)

useReducer

用于处理复杂状态逻辑的 Hook。

基础语法

ts
const [state, dispatch] = useReducer(reducer, initialState)
  • reducer 函数接收 (state, action) 返回新状态
  • 通过 dispatch(action) 触发状态更新

使用示例

ts
// 定义 reducer 函数
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, {
        id: action.id,
        text: action.text,
        completed: false
      }]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    default:
      return state
  }
}

// 在组件中使用
function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, [])

  const addTodo = (text) => {
    dispatch({
      type: 'ADD_TODO',
      id: Date.now(),
      text
    })
  }

  const toggleTodo = (id) => {
    dispatch({
      type: 'TOGGLE_TODO',
      id
    })
  }
}

适用场景

  • 状态逻辑复杂
  • 状态之间有依赖关系
  • 需要集中管理状态更新逻辑

reducer 的本质

reducer 是一个自定义的状态管理器,让开发者完全控制状态的变化逻辑。

与 useState 的对比:

  • useState:直接设置新状态
  • useReducer:通过预定义的 reducer 函数计算新状态

优势:

  • 状态更新逻辑更集中,易于维护
  • 可以处理复杂的状态依赖关系
  • 便于测试(reducer 是纯函数)
  • 方便状态逻辑的复用

Context Hook

上下文 Hook 允许从祖先组件接收信息,而无需将其作为 props 传递

useContext

基础用法

tsx
// 1. 创建 Context
const ThemeContext = createContext('light')

// 2. 提供 Context
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  )
}

// 3. 消费 Context
function ThemedButton() {
  const theme = useContext(ThemeContext)
  return <button className={theme}>按钮</button>
}

实际应用示例

tsx
// 用户信息 Context
const UserContext = createContext({
  user: null,
  login: () => {},
  logout: () => {}
})

// Provider 组件
function UserProvider({ children }) {
  const [user, setUser] = useState(null)

  const login = (userData) => {
    setUser(userData)
  }

  const logout = () => {
    setUser(null)
  }

  return (
    <UserContext.Provider value={{ user, login, logout }}>
      {children}
    </UserContext.Provider>
  )
}

// 在需要用户信息的组件中使用
function UserProfile() {
  const { user, logout } = useContext(UserContext)

  if (!user) return <div>请登录</div>

  return (
    <div>
      <h2>{user.name}</h2>
      <button onClick={logout}>退出登录</button>
    </div>
  )
}

// 应用入口
function App() {
  return (
    <UserProvider>
      <UserProfile />
    </UserProvider>
  )
}

使用场景

  • 全局主题配置
  • 用户认证信息
  • 语言偏好设置
  • 全局状态管理
  • 共享数据或函数

注意事项

Context 适用于需要在组件树中广泛共享的数据。过度使用 Context 可能导致组件复用性降低。因此,对于局部状态,推荐使用 props 传递或状态提升。

Context vs 状态管理库(valtio、zustand等)的区别

  1. Context

    • 是 React 原生的依赖注入机制
    • 主要用于传递数据,不包含状态管理功能
    • 每次 Context 值变化,所有消费该 Context 的组件都会重新渲染
    • 适用于:
      • 静态数据或低频更新的数据
      • 主题、语言等全局配置
      • 较小规模的应用
  2. Valtio

    • 基于 Proxy 的响应式状态管理
    • 可以像普通对象一样直接修改状态
    • 精确更新,只有使用到的属性变化才会触发重渲染
    • 适用于:
      • 需要频繁更新的复杂状态
      • 对性能要求较高的场景
      ts
      // Valtio 示例
      const state = proxy({ count: 0 })
      state.count++ // 直接修改
  3. Zustand

    • 极简的状态管理库
    • 基于 hook 的 API 设计
    • 支持状态分片,可以只订阅部分状态
    • 适用于:
      • 中大型应用的状态管理
      • 需要良好性能和可维护性的场景
      ts
      // Zustand 示例
      const useStore = create((set) => ({
        count: 0,
        increment: () => set((state) => ({ count: state.count + 1 }))
      }))

选择建议:

  • 简单的全局配置 → Context
  • 复杂的状态管理 → Zustand/Valtio
  • 需要响应式特性 → Valtio
  • 需要更好的性能和扩展性 → Zustand

Ref Hook

允许组件保存一些不用于渲染的信息:

  • 希望组件"记住"某些信息,但又不想让这些信息触发新的渲染

useRef 使用场景

  1. 存储 DOM 元素引用
tsx
function VideoPlayer() {
  const videoRef = useRef(null)

  const play = () => {
    videoRef.current.play()
  }

  const pause = () => {
    videoRef.current.pause()
  }

  return (
    <div>
      <video ref={videoRef}>
        <source src="video.mp4" type="video/mp4" />
      </video>
      <button onClick={play}>播放</button>
      <button onClick={pause}>暂停</button>
    </div>
  )
}
  1. 保存定时器 ID
tsx
function AutoSave({ onSave }) {
  const saveTimerRef = useRef(null)

  useEffect(() => {
    // 每 5 秒自动保存
    saveTimerRef.current = setInterval(() => {
      onSave()
    }, 5000)

    return () => {
      // 清理定时器
      clearInterval(saveTimerRef.current)
    }
  }, [onSave])

  return <div>自动保存中...</div>
}
  1. 记录前一次的值
tsx
function MessageList() {
  const [messages, setMessages] = useState([])
  const prevMessagesRef = useRef([])

  useEffect(() => {
    // 保存前一次的消息列表
    prevMessagesRef.current = messages
  }, [messages])

  const hasNewMessages = messages.length > prevMessagesRef.current.length

  return (
    <div>
      {hasNewMessages && <div>有新消息!</div>}
      {/* 消息列表渲染 */}
    </div>
  )
}
  1. 存储可变值(不触发重渲染)
tsx
function StopWatch() {
  const [time, setTime] = useState(0)
  const intervalRef = useRef(null)
  const isRunningRef = useRef(false)  // 不需要触发重渲染的状态

  const start = () => {
    if (isRunningRef.current) return
    isRunningRef.current = true
    intervalRef.current = setInterval(() => {
      setTime(t => t + 1)
    }, 1000)
  }

  const stop = () => {
    isRunningRef.current = false
    clearInterval(intervalRef.current)
  }

  return (
    <div>
      <div>时间: {time}秒</div>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
    </div>
  )
}

useRef 特点

  • ref.current 的变化不会触发组件重新渲染
  • ref 值在组件的整个生命周期内保持不变
  • 适合存储那些不需要参与 UI 渲染的值
  • 常用于存储 DOM 引用、定时器 ID、前一次的值等

使用建议

  • 如果值需要参与渲染,使用 useState
  • 如果值仅用于存储/记忆且不需要触发重渲染,使用 useRef
  • 不要在渲染期间读取或写入 ref.current

贡献者

页面历史