我的用例是,给定一个用户输入是一个文件数组,应用程序并行地启动所有文件的上传,并为正在进行的每个上传显示一个组件。我尝试使用@tanstack/react-query
的useMutation
钩子来做到这一点,因为你不能有一个钩子数组,我做了这样的事情:
async function uploadFile(file) { ... }
// Supposed to start the file upload on mount.
function UploadingFile({file, ...}) {
const { mutate } = useMutation(() => uploadFile(file), ...);
// This doesn't work in React strict/concurrent mode
useEffect(() => {
mutate();
}, [mutate]);
}
function UploadingFileList({files, ...}) {
return (
<div>
{files.map((file, index) => (
<UploadingFile key={index} file={file} />
)};
</div>
);
}
useEffect
在React严格/并发模式下触发两次(我理解为什么会发生这种情况)。我考虑了两种可能的解决方案:
- 实现一些hack,使
useEffect
只火一次。有很多关于使用useRef
的例子。这种方法的问题是,它是一个黑客。 - 使用
useQuery
/useQueries
代替useMutation
,它已经自动触发了。这种方法的问题是,它需要查询键,但文件上传操作没有固有的唯一键。解决这个问题的一种方法可能是为每个文件上传使用随机生成的ID。
这两种方法似乎都很粗糙,所以我想听听其他人的意见。有没有更地道的方法来达到我想要的?
你可以尝试使用一个空数组作为第二个参数的useEffect钩子,这将导致效果只运行一次。
如何在UploadingFile组件中使用useEffect钩子启动文件上传过程的示例:
function UploadingFile({file, ...}) {
const { mutate } = useMutation(() => uploadFile(file), ...);
useEffect(() => {
mutate();
}, []); // This will cause the effect to run only once
}
或者,你可以在UploadingFile组件中使用useMutation钩子来创建一个函数来启动文件上传过程,然后从UploadingFile组件的render方法中调用这个函数:
function UploadingFile({file, ...}) {
const [mutate, { isLoading, isError }] = useMutation(() => uploadFile(file), ...);
const startUpload = () => {
mutate();
}
return (
<div>
{isLoading ? 'Uploading...' : 'Ready to upload'}
{isError && 'An error occurred'}
<button onClick={startUpload}>Start upload</button>
</div>
);
}
这种方法不会有在严格/并发模式下多次触发useEffect钩子的相同问题,因为mutate函数不会从效果内部调用。
实现这一点的方法是不在useEffect
中触发突变,而是在触发用户上传文件的事件处理程序中触发突变。
你当然可以连续多次调用mutate
,或者你可以有一个突变,只触发多次调用Promise.all()
让我们假设事件处理程序是一个点击,接收一个数组:
const { mutate } = useMutation({
mutationFn: files => {
return Promise.all(files.map(uploadFile))
}
})
<MyUploadComponent
onClick={(files) => mutate(files)}
/>