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

React 18 Hooks 最佳实践

Choyeon
Choyeon
2026年8月22日 1 分钟阅读 685 阅读
704 字 1,060 字符 更新于 2026年11月8日
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 分钟阅读