[英]Trying to use cleanup function in useEffect hook to cleanup img.onload
我最近构建了一个 React 组件(称为 ItemIndexItem),它在我的应用程序上显示图像。 例如,我有一个 Search 组件,它将显示已过滤项目的索引。 显示的每个项目都是一个 ItemIndexItem 组件。 单击 ItemIndexItem 会将您带到使用相同 ItemIndexItem 的 ItemShow 页面。
搜索.jsx
render() {
return (
<ul>
<li key={item.id}>
<div>
<Link to={`/items/${item.id}`}>
<ItemIndexItem src={item.photos[0].photoUrl} />
<p>${item.price}</p>
</Link>
</div>
</li>
...more li's
</ul>
)
}
ItemIndexItem.jsx
import React, { useState, useEffect } from "react";
export default function ItemIndexItem(props) {
const [imageIsReady, setImageIsReady] = useState(false);
useEffect(() => {
let img = new Image();
img.src = props.src;
img.onload = () => {
setImageIsReady(true);
};
});
if (!imageIsReady) return null;
return (
<div>
<img src={props.src} />
</div>
);
}
除了控制台中抛出的 memory 泄漏错误外,该组件的工作方式完全符合预期:
无法对未安装的组件执行 React state 更新。 这是一个无操作,但它表明您的应用程序中存在 memory 泄漏。 要修复此问题,请在 useEffect 清理 function 中取消所有订阅和异步任务。
在 ItemIndexItem(由 ItemShow 创建)
在 div 中(由 ItemShow 创建)
作为参考,这是我渲染 ItemIndexItem 的 ItemShow 中的代码:
ItemShow.jsx
return (
...
<div>
<ul>
{this.props.item.photos.map(photo => {
return (
<li key={photo.photoUrl}>
<div>
<ItemIndexItem type='show' src={photo.photoUrl} />
</div>
</li>
);
})}
</ul>
</div>
...
我尝试使用 useEffect return function 将img
设置为 null:
return () => img = null;
然而,这无济于事。 由于我没有创建订阅,因此没有要删除的订阅。 所以我认为问题在于.onload
的异步性质。
您正在设置不再安装的组件的 state。 您可以使用useRef
挂钩来确定您的组件是否仍然安装,例如:
function useIsMounted() {
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
并在您的ItemIndexItem
...
export default function ItemIndexItem(props) {
const isMounted = useIsMounted();
const [imageIsReady, setImageIsReady] = useState(false);
...
img.onload = () => {
if (isMounted.current) {
setImageIsReady(true);
}
...
}
如useRef的 React 文档中所述。
useRef 返回一个可变的 ref object,其.current 属性初始化为传递的参数 (initialValue)。 返回的 object 将在组件的整个生命周期内持续存在。
这意味着您可以使用它来创建对 HTML 元素的引用,但您也可以在该引用中放置其他变量,例如 boolean。 对于我的“useIsMounted”钩子,它在初始化时将其设置为已安装,并在卸载时将其设置为未安装。
您正在设置树中不再存在的组件的 state。 我有一个小钩子实用程序来帮助解决这种情况:
import { useCallback, useEffect, useRef } from 'react'
export const useIfMounted = () => {
const isMounted = useRef(true)
useEffect(
() => () => {
isMounted.current = false
}, [])
const ifMounted = useCallback(
func => {
if (isMounted.current && func) {
func()
} else {
console.log('not mounted, not doing anything')
}
},[])
return ifMounted
}
export default useIfMounted
然后您可以像这样使用它:
const ifMounted = useIfMounted()
//other code
img.onload = () => {
ifMounted(() => setImageIsReady(true))
}
虽然这个问题已经有两个可行的答案,但我想给出第三个(希望更简单)一个:
你不需要另一个useRef
或useIfMounted
钩子——你只需要一个局部变量来跟踪效果是否仍然处于活动状态,并且你的效果应该返回一个清理 function,它将这个变量设置为false
。
此外,您的效果应该取决于[props.src]
而不是[]
,因为如果props.src
发生变化,您可能需要等待新图像:
import React, { useState, useEffect } from "react";
export default function ItemIndexItem(props) {
const [imageIsReady, setImageIsReady] = useState(false);
useEffect(() => {
if (imageIsReady) {
// Oh, oh, props.src changed ...
setImageIsReady(false);
}
let effectActive = true;
let img = new Image();
img.src = props.src;
img.onload = () => {
// Only call setImageIsReady if the effect is still active!
if (effectActive) {
setImageIsReady(true);
}
};
// The cleanup function below will be called,
// when either the ItemIndexItem component is
// unmounted or when props.src changes ...
return () => { effectActive = false; }
});
if (!imageIsReady) return null;
return (
<div>
<img src={props.src} />
</div>
);
}
类似的问题,我用这个解决了。
useEffect(() => {
let img = new Image()
//only continue if img is not null
if (img)
img.onload = () => {
setHeight(img.height)
setWidth(img.width)
img.src = src
}
}, [src])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.