Eu tenho um código React que permite adicionar arquivos de imagem, visualizar, excluir com um clique e adicionar mais. Estou feliz com a funcionalidade, mas notei alguns problemas de desempenho.
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>
Funciona bem em pequenas quantidades de arquivos, mas vamos revisar o cenário a seguir.
- Adicione 10 imagens cada uma com tamanho de aproximadamente 2 MB, então carregamos aproximadamente 20 MB de dados
- Clique em uma imagem para excluí-la
- 9 imagens re-renderizadas e a guia de rede mostra 9 imagens recarregadas novamente, ou seja, 19 solicitações no total para concluir esta operação.
Existe uma maneira inteligente de impedir que as imagens restantes sejam recarregadas?
Até agora apenas testando no sandbox e procurando solução.
A questão principal provavelmente seria
src={URL.createObjectURL(file.fileObject)}
. Para entender por que, vamos primeiro dar uma olhada na documentação deURL.createObjectURL()
:Como você pode ver, o URL tem o tempo de vida do arquivo
document
. No seu cenário, cada vez que você renderizaApp
, novos URLs são criados, sem liberar os URLs antigos. Mesmo sempre queApp
forem desmontados, os URLs criados permanecem ativos.Você pode ver isso em ação inspecionando as imagens. Sempre que você exclui uma única imagem, todas as imagens restantes recebem um novo arquivo
src
.Para corrigir isso, não crie os URLs na renderização, mas sempre que adicionar a imagem ao estado, assim como faz com os ids. Então, sempre que uma imagem for removida, revogue o URL. (E provavelmente você também deseja revogar o URL na desmontagem do componente.)
Substituí
GenerateGuid()
porcrypto.randomUUID()
para simplificar o snippet apenas para código relevante.No código acima você verá que a URL é gerada sempre que o arquivo é adicionado à lista de arquivos. Não é mais gerado para cada renderização. Também revogo o URL sempre que a imagem é excluída. Essas mudanças vão longe.
No entanto, para garantir que os URLs sejam revogados sempre que o componente for desmontado, também precisamos adicionar um
useEffect
gancho. A princípio você pode ficar tentado a fazer algo assim:Mas o acima NÃO funciona, porque não funciona apenas na desmontagem, mas também sempre que
selectedFiles
for alterado. O que significa que sempre que você adicionar arquivos, os URLs serão criados e imediatamente após a renderização serão revogados.Para resolver esse problema eu uso o
useRef
gancho. Isto criará um objeto que é exclusivo para esta instância do componente cuja identidade é estável (a referência do objeto nunca muda). Esta referência contém umaSet
instância que uso como registro de URL. Isso nos permite acessar as URLs sem a necessidade de especificar dependências para ouseEffect
gancho.Isso significa que precisamos adicionar/remover as entradas de URL de/para o registro sempre que criarmos ou revogarmos uma URL, tornando a adição/remoção de arquivos um pouco mais complexa.