React Hooks性能优化实战:useMemo与useCallback的正确用法

React Hooks性能优化:useMemo与useCallback的正确使用场景

React Hooks重构组件逻辑时,useMemo与useCallback是出现频率最高的两个优化钩子,但大多数项目用错了地方。useMemo缓存计算结果,useCallback缓存函数引用,它们解决的并不是渲染慢,而是避免不必要的子组件重渲染。只有在把函数或计算结果作为props传给被React.memo包裹的子组件时,这两个Hook才有实际意义。

import { useMemo, useCallback, useState } from 'react'

function ProductList({ items, onSelect }) {
  // items 变化时才重新计算过滤结果
  const visibleItems = useMemo(
    () => items.filter(i => i.stock > 0),
    [items]
  )
  return visibleItems.map(i => <Row key={i.id} item={i} />)
}

// 函数引用稳定,子组件 memo 后避免无效重渲染
const handleSelect = useCallback((id) => {
  setSelectedId(id)
}, [])

依赖数组写错是常见的性能坑:依赖项缺失导致闭包捕获旧值,行为异常;依赖项过多导致每次渲染都重新计算,useMemo形同虚设。判据只有一个——值是否被频繁计算,以及引用是否传给memo子组件,两者都不满足就别用。

React Hook依赖数组:useEffect的闭包陷阱与防重复执行

useEffect的依赖数组决定副作用何时执行。空数组表示只在挂载时执行一次,适合事件监听和初始化请求;但副作用里用到的state或props必须写进依赖,否则闭包会一直捕获首次渲染的旧值,页面显示与数据不同步。

useEffect(() => {
  fetch(`/api/user?id=${userId}`)
    .then(r => r.json())
    .then(setUser)
}, [userId]) // 缺少 userId 会导致请求参数永远不变

依赖数组里放对象或数组字面量时,每次渲染引用都不同,副作用会被反复触发,需要用useMemo稳定引用或拆分数据字段。另一个常见问题是在副作用里直接修改state后再次触发副作用,形成死循环,这种情况应检查依赖是否包含了可变的派生值。

React Hook进阶:useRef与自定义Hook封装复用逻辑

useRef在React Hooks里承担两件事:保存跨渲染的变量,以及引用DOM节点。前者常用于保存定时器ID、上一次的值或避免重复执行的标记,改动ref不会触发渲染,与state形成互补。

function useThrottledSearch(query, delay = 300) {
  const timerRef = useRef(null)
  const [result, setResult] = useState('')

  useEffect(() => {
    clearTimeout(timerRef.current)
    timerRef.current = setTimeout(() => {
      setResult(query.trim())
    }, delay)
    return () => clearTimeout(timerRef.current)
  }, [query, delay])

  return result
}

function SearchBox() {
  const [input, setInput] = useState('')
  const result = useThrottledSearch(input)
  return <input value={input} onChange={e => setInput(e.target.value)} />
}

自定义Hook就是把共享逻辑抽成以use开头的函数,返回状态或动作。它让多个组件复用同一套副作用逻辑,同时保持各自的状态隔离。判断一个逻辑是否适合拆Hook:只要它用了多个内置Hook且有复用需求,就值得抽。

React 19新特性:useOptimistic与useActionState处理异步提交

React 19把一批数据获取和表单处理的原语集成到框架层。useOptimistic在提交表单时先显示预期结果,服务端确认后回滚或修正,优化了表单交互的感知速度;useActionState整合了action的执行状态、错误与pending标记。

import { useOptimistic, useActionState } from 'react'

function CommentForm() {
  const [optimisticList, addOptimistic] = useOptimistic(list)

  async function submitComment(formData) {
    addOptimistic(formData.get('text')) // 先乐观更新 UI
    await api.postComment(formData)
  }

  const [state, action, isPending] = useActionState(submitComment, null)
  return (
    <form action={action}>
      <input name="text" disabled={isPending} />
      <button type="submit">提交</button>
    </form>
  )
}

这两个Hook在表单提交、评论列表、点赞等即时反馈场景效果明显,可减少手写useState加loading状态的样板代码。依赖react版本需要19.x,使用时需确认项目已升级。

React Hooks性能排查:React DevTools Profiler定位无效渲染

性能问题出现后先用Profiler量化:录制一次交互,查看哪个组件渲染耗时最长,以及哪些组件在props未变化时被重复渲染。DevTools的Profiler面板会按渲染耗时排序,火焰图下方的”rendered by”能直接找到触发渲染的父组件。

// 手动标记组件,方便 Profiler 中定位
import { Profiler } from 'react'

<Profiler id="ProductList" onRender={(id, phase, actualDuration) => {
  console.log(id, phase, actualDuration)
}}>
  <ProductList />
</Profiler>

实际优化顺序建议是:先用Profiler找到渲染热点,再用React.memo包住接收稳定props的子组件,最后才考虑useCallback/useMemo。绝大多数真实应用的性能瓶颈在大型列表渲染和重计算,而不是函数引用传递。

React Hooks的优化逻辑是让不必要的渲染不发生。理解useMemo、useCallback、useRef的三者边界,配合Profiler数据做验证,性能问题就可以被量化地排查。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/reacthooks-xing-neng-you-hua-shi-zhan-usememo-yu/

(0)
小编小编
上一篇 1小时前
下一篇 1小时前

相关推荐