提示:本网站现在为维护调试状态

React 18 Hooks 最佳實踐

Choyeon
Choyeon
2026年8月22日 1 分鐘閱讀 680 閱讀
704 字 1,060 字元 更新於 2026年11月8日
React 18 Hooks 最佳實踐
目錄

React 18 Hooks 最佳实践

React 18 的并发特性改变了我们对"渲染过程"的认知。传统 Hooks 的使用方式在并发模式下可能暴露新的问题。掌握这些最佳实践,才能真正享受到 React 18 的性能红利。

useEffect 是同步的,但外部世界不是

并发模式下,一个 Effect 可能在其清理函数执行后,异步回调才姗姗来迟。代码示例中使用 AbortController + mountedRef 的组合就是标准解法:

import { useState, useEffect, useTransition, useDeferredValue, useMemo, useCallback, useRef } from 'react'

interface User {
  id: number
  name: string
  email: string
  bio: string
}

function useDebounce<T>(value: T, delay: number = 300): T {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])
  return debounced
}

function UserSearch() {
  const [query, setQuery] = useState('')
  const [users, setUsers] = useState<User[]>([])
  const [isPending, startTransition] = useTransition()
  const [error, setError] = useState<string | null>(null)
  const abortRef = useRef<AbortController | null>(null)
  const mountedRef = useRef(true)

  const debouncedQuery = useDebounce(query, 250)
  const deferredQuery = useDeferredValue(debouncedQuery)

  useEffect(() => {
    mountedRef.current = true
    return () => {
      mountedRef.current = false
      abortRef.current?.abort()
    }
  }, [])

  useEffect(() => {
    if (!deferredQuery.trim()) {
      setUsers([])
      return
    }
    const controller = new AbortController()
    abortRef.current?.abort()
    abortRef.current = controller
    ;(async () => {
      try {
        setError(null)
        const res = await fetch(`/api/users?q=${encodeURIComponent(deferredQuery)}`, {
          signal: controller.signal
        })
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        const data = await res.json()
        if (mountedRef.current) {
          startTransition(() => setUsers(data))
        }
      } catch (e: any) {
        if (e.name !== 'AbortError' && mountedRef.current) {
          setError(e.message)
        }
      }
    })()
    return () => controller.abort()
  }, [deferredQuery])

  const highlightText = useCallback((text: string, keyword: string) => {
    if (!keyword) return text
    const parts = text.split(new RegExp(`(${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'))
    return parts.map((p, i) =>
      p.toLowerCase() === keyword.toLowerCase() ? <mark key={i}>{p}</mark> : <span key={i}>{p}</span>
    )
  }, [])

  const filteredList = useMemo(() => users.slice(0, 50), [users])

  return (
    <div className="search-box">
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="搜索用户..."
        className="input"
      />
      {error && <div className="error">{error}</div>}
      <div className={isPending ? 'results pending' : 'results'}>
        {filteredList.map(user => (
          <div key={user.id} className="user-item">
            <strong>{highlightText(user.name, deferredQuery)}</strong>
            <span className="email">{user.email}</span>
          </div>
        ))}
        {users.length === 0 && deferredQuery && !isPending && !error && (
          <div className="empty">无匹配结果</div>
        )}
      </div>
    </div>
  )
}

useMemo 与 useCallback 优化边界

过度使用 useMemo/useCallback 是初学者的典型特征。下面的决策表帮你判断什么时候该用:

场景 是否应该使用? 理由
子组件被 React.memo 包裹且 props 是引用类型 ✅ 需要 避免不必要重渲染
给 useEffect 作依赖且是数组/对象 ✅ 需要 防止 Effect 意外触发
计算量大(排序/遍历 >1000 项/正则 heavy) ✅ 需要 纯 CPU 性能收益远大于 memo 开销
简单计算如 a + b 或对象只有 3 个字段 ❌ 不要 memo 比较本身更贵
传递给原生 DOM 元素的函数 ❌ 不要 React 不 memo DOM 回调
传递给自定义 Hook 的函数但 Hook 不依赖它 ❌ 不要 无实质好处

并发模式的三大武器

React 18 提供了三把钥匙来解锁并发体验:

// 1. startTransition:标记非紧急更新
startTransition(() => setFilteredItems(heavyCompute()))

// 2. useDeferredValue:延迟某个值的下游计算
const deferredList = useDeferredValue(hugeList)

// 3. Suspense + use:等待异步资源(React 18.2+)
const user = use(fetchUserPromise)

关键区别:useTransition 由你主动包裹更新动作;useDeferredValue 是被动延迟某个 prop/state 的传播。

避免陈旧闭包的三个技巧

  1. Ref 保存最新值:用 useRef + useEffect 同步值给异步回调读取
  2. 函数式更新:setState 传 (prev) => next 而非直接依赖外部值
  3. 把 effect 当"订阅"看:严格填依赖数组,信不过的工具就用 eslint-plugin-react-hooks

最佳实践

  1. Effect 的依赖永远诚实写,实在想模拟"只执行一次"的语义,先反思是不是设计问题
  2. 一个 Hook 只做一件事,自定义 Hook 超过 100 行就考虑拆分
  3. 数据获取用成熟库(SWR / React Query / TanStack Query)而不是手写 useEffect
  4. 状态提升到合适的层级:能用 Lifting State 解决就不要急着上 Context/Redux
  5. Context 拆分细粒度:一个全局 UserContext 会让所有消费组件无脑重渲染

总结

React 18 不是"加了几个 Hook"的增量升级,而是底层调度模型的重构。拥抱并发模式的关键,在于放弃"Effect 是 componentDidMount"的旧思维,改用"声明副作用的同步与清理关系"的新范式。你的代码越纯、越声明式,React 18 就能帮你做得越快。

X微博Telegram

本文作者

投票

你最希望 Rosetta 优先加入哪项功能?

社区投票(演示数据,可后台删除)

留言 (5)

0 / 3000
开源贡献者小Z
开源贡献者小Zoss-z.dev2026年9月26日

有个小小的建议:代码示例如果能加上语法高亮和行号就更好啦,阅读体验会更上一层楼~

运维老司机
运维老司机ops-notes.cn2026年9月25日

感谢分享,刚好需要这个!

安全小白白
安全小白白seclab.digital2026年9月25日

这个组件封装得太优雅了,拿走直接用在项目里了,省了我半天时间,给博主点个大大的赞!

算法小菜鸟
算法小菜鸟2026年9月24日

按你的代码跑了一下,为什么在 Safari 下过渡动画会卡帧?Chrome 和 Firefox 都正常。

咖啡与代码
咖啡与代码2026年9月24日

终于有人把 CSS Grid 的各种布局技巧讲明白了,我之前看 MDN 文档看半天没搞懂,看完这篇直接上手了!

相關文章

Vue 3 組合式 API 實戰

Vue 3 組合式 API 實戰

Vue 3 的組合式 API (Composition API) 徹底改變了 Vue 組件的組織方式。本文通過構建一個複雜的電商篩選器組件,演示 ref/reactive 的選擇、computed/watch 的合理使用、自定義組合函數 (Composable) 的抽離原則、生命週期鉤子的時機以及 provide/inject 進行跨層級狀態傳遞的正確姿勢。

1 分鐘閱讀
Tailwind CSS v4 新特性:Zero-config + CSS-native 變數

Tailwind CSS v4 新特性:Zero-config + CSS-native 變數

Tailwind CSS v4 是一次架構級升級:徹底拋棄 tailwind.config.js,用 @theme 塊在原生 CSS 文件聲明設計令牌;新增 @import "tailwindcss" 單入口代替 postcss.config;內置 CSS 變量、容器查詢原生支持、oxc 引擎提速 10-40 倍、顏色函數簡化。

1 分鐘閱讀
Astro 靜態站點深度:群島架構 & 內容集合

Astro 靜態站點深度:群島架構 & 內容集合

深入理解 Astro 核心創新:群島架構實現最小化 JS 注入,僅交互元件按需水合;內容集合提供類型安全的 Markdown/MDX 管理,構建零 JS 的高性能靜態站點。

1 分鐘閱讀
CSS Container Queries 完全指南

CSS Container Queries 完全指南

徹底掌握現代響應式 CSS:Container Queries 讓元件基於父容器尺寸自適應(而非視口),配合 :has() 父選擇器實現依賴後代狀態的樣式變化,徹底改變元件化響應式設計範式。

1 分鐘閱讀
PWA 離線應用實戰:Service Worker + IndexedDB + Background Sync

PWA 離線應用實戰:Service Worker + IndexedDB + Background Sync

逐步構建可安裝、可離線運行、網絡恢復自動同步的 Progressive Web App:Service Worker 緩存策略(Cache First / Stale-While-Revalidate)、IndexedDB 封裝結構化本地數據、Background Sync 後台隊列在用戶離線操作後網絡恢復時自動提交變更,附完整 TS 代碼。

1 分鐘閱讀
前端性能優化:Core Web Vitals 實戰

前端性能優化:Core Web Vitals 實戰

Google 搜索排名核心指標完全實戰:LCP 最大內容繪製(預連接/圖像優先級/CDN緩存)、CLS 累積佈局偏移(圖像尺寸/字體FOIT/動態佔位)、INP 交互響應(長任務拆分/防抖節流)全面落地。

1 分鐘閱讀