You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Composable返回后Vue Ref失去响应性问题求助

问题:Nuxt3自定义Composable返回的Ref在组件中失去响应性

问题场景

使用Nuxt3 Composition API + Vue3 <script setup>开发项目,通过VueUse的useFileDialog创建多个文件对话框实例,在自定义Composable中返回对应的files Ref,但组件中监听这些Ref的变化没有触发回调,而Composable内部的watchEffect能正常捕获到materialerFiles的变化。

代码示例

自定义Composable代码

import { useFileDialog } from '@vueuse/core'
import { ref as storageRef, getStorage, listAll, getDownloadURL, deleteObject } from 'firebase/storage'
import { useStorageFile } from 'vuefire'
import { ref, computed, watch } from 'vue'

export const useFileUpload = (props) => {
    const storage = getStorage();
    const filename = ref();
    const fileRef = ref(null);
    const selectedImages = ref([]);
    const images = ref({});
    const imagePreviews = computed(() => {
        if (images.value) {
            return Array.from(images.value).map(file => URL.createObjectURL(file));
        }
        return [];
    })

    const { url, uploadTask, upload } = useStorageFile(fileRef)

    const { files, open } = useFileDialog()
    const { open: openMaterialer, files: materialerFiles } = useFileDialog();
    const { open: openPlantegning, files: plantegningFiles } = useFileDialog();

    watch(files, (newFiles) => {
        if (newFiles.length > 0) {
            const fileType = newFiles[0].type.split('/')[0]
            if (fileType === 'image') {
                images.value = newFiles
            }
            else return
        }
    })

    const uploadFile = async () => {
        // 上传逻辑
    }

    // Composable内部能正常捕获变化
    watchEffect(() => {
        console.log('materialerFiles in upload:', materialerFiles.value);
    });

    return {
        imagePreviews,
        files,
        materialerFiles,
        plantegningFiles,
        uploadTask,
        open,
        openMaterialer,
        openPlantegning,
        uploadFile,
    };
};

组件使用代码

// 解构Composable返回值
const { files, materialerFiles, plantegningFiles } = useFileUpload()

// 监听materialerFiles变化,但无输出
watch(materialerFiles, (newMaterialerFiles) => {
    console.log('changed')
    userInput.value = {
        ...userInput.value,
        materialerFiles: newMaterialerFiles,
    };
});

原因分析

VueUse的useFileDialog返回的files是一个Ref,当用户选择文件时,它是直接修改数组内部元素(而非替换整个数组引用)。而Vue的watch默认只监听Ref的引用变化,不会追踪数组内部的修改,因此组件中的watch回调不会触发。

解决方法

方法1:开启深度监听

在组件的watch中添加deep: true选项,让watch追踪数组内部的变化:

watch(materialerFiles, (newMaterialerFiles) => {
    console.log('changed')
    userInput.value = {
        ...userInput.value,
        materialerFiles: newMaterialerFiles,
    };
}, { deep: true }); // 开启深度监听

方法2:改用watchEffect

watchEffect会自动追踪所有响应式依赖,包括数组内部的变化,无需手动设置深度:

watchEffect(() => {
    if (materialerFiles.value.length > 0) {
        console.log('changed')
        userInput.value = {
            ...userInput.value,
            materialerFiles: materialerFiles.value,
        };
    }
});

额外验证

可以先在组件中用watchEffect测试是否能捕获变化,确认响应性正常后,再根据需求选择watch(带深度选项)或watchEffect。


内容的提问来源于stack exchange,提问作者Mattyb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 04:53:09