You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useEffect添加函数依赖、useCallback包裹仍无限重渲染怎么解决

问题根因

你的循环渲染是连锁触发的,核心原因如下:

  • 父组件传入的setFile函数没有用useCallback包裹,每次父组件的selectedFile状态更新时,都会生成全新的setFile函数实例
  • 子组件的setBlobFile依赖setFile,所以setFile更新后,useCallback返回的setBlobFile引用也会更新
  • 子组件的useEffect依赖setBlobFile,因此setBlobFile更新后会重新执行,再次走图片加载、转Blob、调用setBlobFile的逻辑,触发父组件selectedFile更新,形成无限循环。
    另外你代码中还存在一个显性错误:return的div上绑定了onClick={onClick},但没有定义对应的onClick变量,会直接触发运行时报错。
修复方案

第一步:父组件用useCallback包裹setFile

useState返回的setSelectedFile是引用稳定的函数,所以包裹后的setFile不会随父组件渲染频繁更新:

const [selectedFile, setSelectedFile] = useState(new File([""], ""))
// 用useCallback包裹,依赖只有稳定的setSelectedFile
const setFile = useCallback((aBlob) => {
  // 删掉无用的img变量声明赋值代码
  setSelectedFile(new File([aBlob], "image.png", {
    type: 'image/png',
  }))
}, [setSelectedFile])

第二步:子组件增加重复处理拦截

用ref存储上一次处理过的图片地址,避免同一个图片被重复处理触发更新:

import React, { useEffect, useRef, useCallback } from 'react'

const Canvas = (props) => {
  const { width = 180, height = 220, img, onChange = null, setFile = null} = props
  const canvas = useRef(null)
  // 存储上一次处理的图片地址
  const lastProcessedImg = useRef('')

  const setBlobFile = useCallback(
    (blob) => {
      setFile(blob)
    },
    [setFile]
  )

  useEffect(() => {
    // 图片为空或者已经处理过直接跳过
    if (!img || img === lastProcessedImg.current) return
    lastProcessedImg.current = img

    console.log("Canvas useEffect ...", img)
    const image = new Image()
    image.src = img
    image.onload = () => {
      const ctx = canvas.current.getContext("2d")
      ctx.clearRect(0, 0, width, height);
      ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height)
      canvas.current.toBlob((blob) => {
        setBlobFile(blob)
      })
    }
    // 清理回调,避免组件卸载后执行逻辑报错
    return () => {
      image.onload = null
    }
  }, [img, width, height, setBlobFile])

  const onFileSelect = (e) => {
    const file = e.target.files[0]
    if (!file) return
    const objectURL = URL.createObjectURL(file)
    onChange(objectURL)
    // 释放objectURL避免内存泄漏
    URL.revokeObjectURL(objectURL)
  }

  return (
    // 删掉未定义的onClick属性,或者补充对应的逻辑
    <div>
      <label htmlFor='upload'>
        <canvas ref={canvas} width={width} height={height} />
        <input type='file' id='upload' onChange={(e) => onFileSelect(e)} style={{ display: 'none' }} />
      </label>
    </div>
  )
}

export default Canvas
额外说明

如果修改后依然有依赖警告,且你确认依赖项不需要加入,可以在useEffect上方加// eslint-disable-next-line react-hooks/exhaustive-deps忽略警告,但更推荐优先通过稳定引用的方式解决依赖问题。


内容的提问来源于stack exchange,提问作者ngwp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 22:24:05