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
相关产品推荐
相关产品推荐

