Vue 3 可组合控制台错误:类型错误:无法读取未定义的属性(读取"isError")



我有一个Vue 3/TypeScript组件,它可以将图像上传到Firebase存储:

interface ImageUpload {
uploadTask?: UploadTask;
downloadURL?: string;
progress?: number;
error?: Error;
isCanceled: boolean;
isRunning: boolean;
isPaused: boolean;
isSuccess: boolean;
isError: boolean;
}
export const useImageUpload = (file: File) => {
const imageUpload = ref<ImageUpload>({
uploadTask: undefined,
downloadURL: undefined,
progress: undefined,
error: undefined,
isRunning: true,
isCanceled: false,
isPaused: false,
isSuccess: false,
isError: false,
});
// Create the file metadata
const metadata = {
contentType: 'image/jpeg',
};
// Upload file and metadata to the object 'images/mountains.jpg'
const filePath = `user_images/${user.uid}/${file.name + '_' + uid()}`;
const storageReference = storageRef(store, filePath);
const uploadTask = uploadBytesResumable(storageReference, file, metadata);
//Put the task into the ref
imageUpload.value.uploadTask = uploadTask;
// Listen for state changes, errors, and completion of the upload.
const unsubscribe = uploadTask.on(
'state_changed',
(snapshot) => {
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
imageUpload.value.progress = progress;
console.log(`Upload is ${progress}% done`);
switch (snapshot.state) {
case 'canceled':
imageUpload.value.isCanceled = true;
imageUpload.value.isRunning = false;
console.log('Upload is canceled');
break;
case 'paused':
imageUpload.value.isPaused = true;
imageUpload.value.isRunning = false;
console.log('Upload is paused');
break;
case 'running':
imageUpload.value.isPaused = false;
imageUpload.value.isRunning = true;
console.log('Upload is running');
break;
}
},
(error) => {
// Upload error
imageUpload.value.isError = true;
imageUpload.value.isRunning = false;
imageUpload.value.error = error;
console.log(error);
},
() => {
// Upload completed successfully, now we can get the download URL
getDownloadURL(uploadTask.snapshot.ref)
.then((downloadURL) => {
imageUpload.value.downloadURL = downloadURL;
imageUpload.value.isSuccess = true;
imageUpload.value.isRunning = false;
console.log('File available at', downloadURL);
})
.catch((error) => {
// getDownloadURL error
imageUpload.value.isError = true;
imageUpload.value.isRunning = false;
if (error instanceof Error) {
imageUpload.value.error = error;
}
console.log(error);
});
}
);
// Unsub the listener when the composable is not in use
watchEffect((onInvalidate) => {
onInvalidate(() => {
unsubscribe();
});
});
return imageUpload;
};

我正在像这样的Vue组件中使用它:

<template>
<div>
<q-img :src="picUrl" />
<q-file
ref="picFileRef"
v-model="picFile"
style="display: none"
@update:model-value="handlePicUpdate"
/>
<div v-if="imageUpload.isError">
{{ imageUpload.error?.message }}
</div>
<div v-if="imageUpload.isRunning">Loading...</div>
<q-btn type="button" label="Upload Image" @click="handleUploadClick" />
<q-btn type="button" label="Pause" @click="handlePauseClick" />
<q-btn type="button" label="Resume" @click="handleResumeClick" />
</div>
</template>
<script setup lang="ts">
import { ref, Ref, watchEffect } from 'vue';
import { useImageUpload } from 'src/composables/storage';
import { QFile } from 'quasar';
import { UploadTask } from 'firebase/storage';
const picFile = ref<File>();
const picUrl = ref<string>();
const picFileRef = ref() as Ref<QFile>;
interface ImageUpload {
uploadTask?: UploadTask;
downloadURL?: string;
progress?: number;
error?: Error;
isCanceled: boolean;
isRunning: boolean;
isPaused: boolean;
isSuccess: boolean;
isError: boolean;
}
let imageUpload: Ref<ImageUpload>; // Had to declare this at the top level so I could access it's properties in the template and in other functions
const handleUploadClick = () => {
picFileRef.value.pickFiles();
};
const handlePicUpdate = () => {
if (picFile.value) {
imageUpload = useImageUpload(picFile.value);
watchEffect(() => {
console.log('progress', imageUpload.value.progress);
console.log('isRunning', imageUpload.value.isRunning);
console.log('isPaused', imageUpload.value.isPaused);
if (imageUpload.value.downloadURL) {
picUrl.value = imageUpload.value.downloadURL;
}
});
}
};
const handlePauseClick = () => {
imageUpload.value.uploadTask?.pause();
};
const handleResumeClick = () => {
imageUpload.value.uploadTask?.resume();
};
</script>

我的IDE(Visual Studio代码(中没有错误,但当我运行它时,我会收到这个控制台错误,以及其他3个看起来非常相似的错误:

WebsitePage.vue?bfce:21 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'isError')
at Proxy.render (WebsitePage.vue?bfce:21:1)
at renderComponentRoot (runtime-core.esm-bundler.js?9e79:893:1)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js?9e79:5030:1)
at ReactiveEffect.run (reactivity.esm-bundler.js?0a2b:167:1)
at setupRenderEffect (runtime-core.esm-bundler.js?9e79:5156:1)
at mountComponent (runtime-core.esm-bundler.js?9e79:4939:1)
at processComponent (runtime-core.esm-bundler.js?9e79:4897:1)
at patch (runtime-core.esm-bundler.js?9e79:4489:1)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js?9e79:5037:1)
at ReactiveEffect.run (reactivity.esm-bundler.js?0a2b:167:1)

我该如何解决这个问题?

如果由于let imageUpload: Ref<ImageUpload>没有设置任何默认值而显示这些错误,我该如何设置默认值?

有没有更好的方法可以做到这一点,这样我就不必在顶级声明let imageUpload: Ref<ImageUpload>了?

这是通过初始化非可选的imageUpload属性来修复的。

所以删除这一行:

let imageUpload: Ref<ImageUpload>;

并将其替换为:

let imageUpload = ref<ImageUpload>({
isCanceled: false,
isRunning: false,
isPaused: false,
isSuccess: false,
isError: false,
});

最新更新