我有一个 React 代码,可以让您添加图像文件、预览、单击删除以及添加更多。我对这个功能很满意,但我注意到一些性能问题。
function App() {
const [selectedFiles, setSelectedFiles] = React.useState([])
function GenerateGuid() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => {
const randomValue = crypto.getRandomValues(new Uint8Array(1))[0];
const shiftedValue = (randomValue & 15) >> (+c / 4);
return shiftedValue.toString(16);
});
}
const handleImageDelete = (id) => {
const updatedFiles = selectedFiles.filter((file) => file.id !== id);
setSelectedFiles(updatedFiles);
};
const handleChange = (e) => {
const files = Array.from(e.target.files)
const filesObject = files.map(file => {
return (
{ id: GenerateGuid(), fileObject: file }
)
})
setSelectedFiles([...selectedFiles, ...filesObject])
}
return (
<div className='App'>
<h1>Hello React.</h1>
<h2>Start editing to see some magic happen!</h2>
<input accept="image/png, image/gif, image/jpeg" type="file" onChange={handleChange} multiple />
{selectedFiles.map(
(file, index) => {
return (
<img
key={file.id}
onClick={() => handleImageDelete(file.id)}
style={{ height: "5rem", backgroundColor: "black" }}
src={URL.createObjectURL(file.fileObject)}
alt={"training spot"}
/>
)
}
)}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
在小文件量中工作得很好,但让我们回顾一下以下场景。
- 添加 10 个图像,每个图像约 2MB 大小,因此我们上传了约 20MB 的数据
- 单击一张图像即可将其删除
- 9 个图像重新渲染,网络选项卡显示 9 个图像再次重新加载,因此总共有 19 个请求来完成此操作。
有没有一种聪明的方法来防止重新加载剩余图像?
到目前为止,只是在沙箱中进行测试并寻找解决方案。
主要问题可能是
src={URL.createObjectURL(file.fileObject)}
。要理解为什么,我们首先看一下文档URL.createObjectURL()
:正如您所看到的,URL 具有
document
. 在您的场景中,每次渲染时App
,都会创建新的 URL,而不释放旧的 URL。即使无论何时App
被卸载,创建的 URL 仍然存在。您可以通过检查图像来查看其实际效果。每当您删除单个图像时,所有剩余图像都会被分配一个新的
src
.要解决此问题,请不要在渲染时创建 URL,而是在将图像添加到状态时创建,就像使用 ids 一样。然后,每当删除图像时,就撤销该 URL。(而且您很可能还想在组件卸载时撤销 URL。)
我已替换
GenerateGuid()
为crypto.randomUUID()
以将代码片段简化为仅相关代码。在上面的代码中,您将看到每当文件添加到文件列表时都会生成 URL。它不再为每个渲染生成。每当图像被删除时,我也会撤销该 URL。这些变化大有帮助。
然而,为了确保每当组件卸载时 URL 都会被撤销,我们还必须添加一个
useEffect
钩子。一开始你可能会想做这样的事情:但上面的方法不起作用,因为它不仅在卸载时运行,而且在
selectedFiles
更改时运行。这意味着每当您添加文件时,都会创建 URL,并在渲染后立即撤销。为了解决这个问题,我使用了
useRef
钩子。这将创建一个对于该组件实例来说是唯一的对象,其身份是稳定的(对象引用永远不会改变)。该引用保存了一个Set
我用作 URL 寄存器的实例。这允许我们访问 URL,而无需指定挂钩的依赖项useEffect
。这确实意味着,每当我们创建或撤销 URL 时,我们都需要向寄存器添加/删除 URL 条目,从而使添加/删除文件变得更加复杂。