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

Vue3中使用解构无法从组合式函数获取数据的问题

Vue组合式函数从Firebase取数无报错但页面不展示的问题

我在Vue组件(Book.vue)中尝试从Firebase获取图书数据,组件内部写函数时运行正常,但抽离为组合式函数后无法正常工作——控制台无报错,但页面始终不展示数据。

以下代码可正常运行(showBooks作为组件内函数)
Book.vue

<template>
  <!-- books list -->
  <div v-for="book in books" :key="book.id" class="book">
    <span class="material-icons"> favorite </span>
    <h2>{{ book.title }}</h2>
    <div>
      <span class="material-icons"> done </span>
      <span class="material-icons"> edit </span>
      <span class="material-icons"> delete </span>
    </div>
  </div>
</template>

<script setup>
import { ref } from "@vue/reactivity";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";
import { onMounted } from "@vue/runtime-core";

const books = ref([]);

async function showBooks() {
  const colRef = collection(db, "books");
  const docs = await getDocs(colRef);
  docs.forEach((doc) => {
    const book = { ...doc.data(), id: doc.id };
    books.value.push(book);
  });
}
onMounted(() => {
  showBooks();
});
</script>

以下代码无法运行(showBooks抽离到组合式函数中)
getBooks.js (组合式函数)

import { ref } from "@vue/reactivity";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";

async function showBooks() {
    const books = ref([]);
    const colRef = collection(db, "books");

    const docs = await getDocs(colRef);
    docs.forEach((doc) => {
        const book = { ...doc.data(), id: doc.id };
        books.value.push(book);
    });

    return { books }
}

export { showBooks }

Books.vue

<template>
  <!-- books list -->
  <div v-for="book in books" :key="book.id" class="book">
    <span class="material-icons"> favorite </span>
    <h2>{{ book.title }}</h2>
    <div>
      <span class="material-icons"> done </span>
      <span class="material-icons"> edit </span>
      <span class="material-icons"> delete </span>
    </div>
  </div>
</template>

<script setup>
import { ref } from "@vue/reactivity";
import { onMounted } from "@vue/runtime-core";
import { showBooks } from "../use/getBooks.js";

const { books } = showBooks();

onMounted(() => {
  showBooks();
});
</script>

问题根源

  1. showBooks是异步函数,直接解构const { books } = showBooks()得到的是Promise对象,不是实际的响应式ref,模板无法绑定响应式数据。
  2. onMounted中再次调用showBooks(),会创建一个全新的ref实例,和模板绑定的books不是同一个,数据更新不会同步到页面。

修复方案

方案1:调整组合式函数结构(推荐)

将响应式ref提前创建并返回,异步逻辑单独封装为方法,确保组件始终绑定同一个ref实例:

修改getBooks.js:

import { ref } from "@vue/reactivity";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";

function useBooks() {
  // 提前创建响应式ref
  const books = ref([]);

  async function fetchBooks() {
    const colRef = collection(db, "books");
    const docsSnap = await getDocs(colRef);
    // 用map替代forEach,直接赋值更高效
    books.value = docsSnap.docs.map(doc => ({ ...doc.data(), id: doc.id }));
  }

  return { books, fetchBooks };
}

export { useBooks };

修改Books.vue:

<template>
  <!-- books list -->
  <div v-for="book in books" :key="book.id" class="book">
    <span class="material-icons"> favorite </span>
    <h2>{{ book.title }}</h2>
    <div>
      <span class="material-icons"> done </span>
      <span class="material-icons"> edit </span>
      <span class="material-icons"> delete </span>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from "@vue/runtime-core";
import { useBooks } from "../use/getBooks.js";

// 获取同一个ref实例和异步方法
const { books, fetchBooks } = useBooks();

onMounted(() => {
  fetchBooks();
});
</script>

方案2:使用await结合Suspense

如果需要异步返回数据,可在组件中使用await,同时在外层组件包裹Suspense处理加载状态:

修改getBooks.js:

import { ref } from "@vue/reactivity";
import { collection, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";

async function useBooks() {
  const books = ref([]);
  const colRef = collection(db, "books");
  const docsSnap = await getDocs(colRef);
  books.value = docsSnap.docs.map(doc => ({ ...doc.data(), id: doc.id }));
  return { books };
}

export { useBooks };

修改Books.vue:

<template>
  <!-- books list -->
  <div v-for="book in books" :key="book.id" class="book">
    <span class="material-icons"> favorite </span>
    <h2>{{ book.title }}</h2>
    <div>
      <span class="material-icons"> done </span>
      <span class="material-icons"> edit </span>
      <span class="material-icons"> delete </span>
    </div>
  </div>
</template>

<script setup>
import { useBooks } from "../use/getBooks.js";

// await异步组合式函数
const { books } = await useBooks();
</script>

在父组件中添加Suspense:

<Suspense>
  <template #default>
    <Books />
  </template>
  <template #fallback>
    <div>加载图书数据中...</div>
  </template>
</Suspense>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 02:35:38