Nuxt 3 [Vue 警告]:<div> 中的水合子节点不匹配:服务器渲染元素包含的子节点多于客户端 vdom

问题描述 投票:0回答:1

我在我的项目中使用 Nuxt 3 + Pinia + FirebaseStore,当我通过 AsyncData 函数包装来自 Firebese 的加载数据函数时,我开始收到错误“Hydration 子项不匹配”。我没有找到带有 ssr 的 nuxt 3 应用程序的好示例,或者如何修复此警告的解决方案,或者带有 ssr 的 nuxt 3 应用程序的好示例,或者如何修复此问题的解决方案。

在布局组件中,我从页脚收到错误。

在 ArticleLisctBlock 组件的页面部分中。

布局默认组件:

<script setup lang="ts">
import { useAuthStore } from "@/stores/authStore";
import { storeToRefs } from "pinia";


//Get  Authorized user
const { isAuthorized } = useAuthStore();
const { statAuth } = storeToRefs(useAuthStore());

//Fetch AboutUs  data
const { aboutUs } = storeToRefs(useAboutUsStore());
const { getAboutUs } = useAboutUsStore();

//Fetch Contactsr links data
const { stateData } = storeToRefs(useContactStore());
const { getContacts } = useContactStore();

//Fetch Nav and Footer links data
const { navLinks, footerLinks } = storeToRefs(useNavStorage());
const { getList, getFooterList } = useNavStorage();

//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { getPostList } = useArticleStore();

const { imageList } = storeToRefs(useGalaryStore());
const { getGalaryDBList } = useGalaryStore();

//CategoryLinks
const { categoryState } = storeToRefs(useCategoryStorage());
const { getGategoryList } = useCategoryStorage();

//Fetch Podcast List
const { podcastsState } = storeToRefs(usePodcastsStore());
const { getPodCastList } = usePodcastsStore();

//Fetch Advertisement List
const { advertiseList } = storeToRefs(useAdvertiseStore());
const { getAdvertiseList } = useAdvertiseStore();

async function loadStores2() {
  await getPostList();
  await getFooterList();
  await getPodCastList();
  await getList();
  await getGategoryList();
  await getGalaryDBList();
  await getAdvertiseList();
  await getAboutUs();
  await getContacts();
}
await useAsyncData("loadData", loadStores2);

onMounted(async () => {
//check auth user in firebase
  await isAuthorized();
});
</script>

<template>
  <div class="defaultLayout">
    <div class="header_block">
      <UiAddTopHeader v-if="footerLinks" :category-links="navLinks" :top-links="footerLinks" />
      <UiAddHeaderMiddle />
      <UiAddHeader v-if="navLinks" :nav-links="navLinks" />
    </div>

    <div class="body_block">
      <slot />
    </div>
    <div class="footer_block">
      <UiAddFooter
        v-if="footerLinks?.length && postsState?.postList?.length"
        :about-us-links="footerLinks"
        :favorites="postsState?.favoriteList"
        :categories="categoryState" />
    </div>
  </div>
</template>

页脚组件。

<script setup lang="ts">
import type { IArticle } from "types/IArticle";
import type { ICategory } from "types/ICategory";
 
import type { INavigation } from "types/INavigation";
 
defineProps({
  categories: {
    type: Array as PropType<ICategory[]>,
    default: null,
  },
  aboutUsLinks: {
    type: Array as PropType<INavigation[]>,
    default: null,
  },
  favorites: {
    type: Array as PropType<IArticle[]>,
    default: null,
  },
});
 
</script>
 
<template>
  <div class="footer_container">
    <img class="footer_image" src="/images/footer.jpg" alt="footer" />
    <div class="content">
      <div class="media_block grid_block">
        <h2 class="media_block_title">WORLD IMPULSE</h2>
        <p>© Munich, LLC. All rights reserved, LLC</p>
        <UiAddSocialMediaList :is-inline-block="true" />
      </div>
      <div class="about_block grid_block">
        <h2 class="title_block">About Us</h2>
        <UiAddAboutLinks
          v-if="aboutUsLinks"
          direction="flex"
          gaps="20px"
          :aboutlinks="aboutUsLinks" />
      </div>
      <div class="popular_block grid_block">
        <h2 class="title_block">Popular Category</h2>
        <UiAddAboutLinks
          v-if="categories?.length"
          direction="grid"
          gaps="20px"
          :categorylinks="categories?.slice(1, 8)" />
      </div>
      <div class="editor_picks grid_block">
        <h2 class="title_block">Editor Piks</h2>
 
        <ArticleSingleArticle
          class-type="image_content_rigth"
          :show-title="true"
          :show-image="true"
          font-size="16px"
          :font-weight="400"
          v-for="(el, i) in favorites.slice(0, 2)"
          :key="i"
          :single-post="el" />
      </div>
    </div>
  </div>
</template>

[Id],ArticleListBlock组件调用的页面

<script setup lang="ts">
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
 
//Get Route params
const route = useRoute();
const routeSlug = String(route.params.slug);
const routeId = String(route.params.id);
 
//Fetch Articles data
const { postsState } = storeToRefs(useArticleStore());
const { isExistPost } = useArticleStore();
 
//Get Category data
const categoryStore = useCategoryStorage();
const { isExistCategory } = categoryStore;
 
// Check route params
const isRouteCorrect = () => {
  if (routeSlug === "search") {
    return true;
  } else if (routeId && (routeId === "list" || isExistPost(routeId))) {
    return true;
  }
 
  return false;
};
 
//Throw  an error id route params are not  correct
if (!isRouteCorrect()) {
  throw createError({ statusCode: 404, statusMessage: `The Page not found` });
}
</script>
 
<template>
  <div class="wrapper">
    <ArticleListBlock v-if="route.params.slug === 'search' || (routeId && routeId === 'list')" />
    <ArticleSingleBlock v-else-if="routeId && isExistPost(routeId)" />
  </div>
</template>

ArticleListBlock 组件

<script setup lang="ts">
import type { NuxtError } from "nuxt/app";
import { storeToRefs } from "pinia";
import type { IArticle } from "types/IArticle";
 
const route = useRoute();
const articlesByCategory = ref<IArticle[]>([]);
const search = ref("");
const errorResponse = ref<NuxtError>();
 
// Articles state
const { postsState } = storeToRefs(useArticleStore());
const { getArticlesByCategory, findArticlesByName } = useArticleStore();
 
// Advertisement galary state
const { advertiseList } = storeToRefs(useAdvertiseStore());
 
if (route.params.slug === "search") {
  search.value = String(route.params.id);
 
  console.log(search.value);
}
 
// Getting data by search request
const searchComputed = computed(() => {
  if (!search.value) {
    route.params.slug === "search" && (articlesByCategory.value = postsState.postList);
    route.params.id === "list" &&
      (articlesByCategory.value = getArticlesByCategory(String(route.params.slug)));
 

    return articlesByCategory.value;
  } else {
    articlesByCategory.value = findArticlesByName(search.value);
 
    if (!articlesByCategory.value.length) {
      errorResponse.value = createError({ statusCode: 404, statusMessage: "Not found results" });
    }
    return articlesByCategory.value;
  }
});
 
useHead({
  title: `${String(route.params.slug)} list`,
  meta: [
    { name: `${String(route.params.slug)}`, content: `${String(route.params.slug)} daily news` },
  ],
});
</script>
 
<template>
  <div class="list_container grid_block">
    <div class="top grid_block">
      <div class="advertise_block">
        <UiElementsAdvertise label="Advertisement" :link="advertiseList.databaseList[1]" />
      </div>
 
      <div class="topic grid_block">
        <h5 class="category">CATEGORY</h5>
        <h1 class="title_category">{{ String(route?.params?.slug)?.toLocaleUpperCase() }}</h1>
 
        <h4 class="info">
          Read the latest news with the Best WordPress News Theme – Newspaper by Sergio Belov!
        </h4>
      </div>
      <div class="form_block">
        <UiElementsAddPostInput
          label="Search"
          width-form="100%"
          font-size="2rem"
          name="search"
          placeholder="Input search"
          v-model:value.trim="search" />
      </div>
    </div>
 
    <div class="main_section">
      <div class="list grid_block" v-if="searchComputed.length">
        <div class="item" v-for="(el, i) in searchComputed" :key="i">
          <ArticleSingleArticle
            class-type="image_content_left"
            :show-image="true"
            :show-title="true"
            :show-short-body="true"
            :show-date="true"
            :single-post="el"
            :id="el?.id" />
        </div>
      </div>
 
      <ErrorResponse v-else-if="errorResponse" :error-event="errorResponse" />
 
      <div class="right_bar" v-if="postsState.postList.length">
        <h3>Latest News</h3>
        <ArticleSingleArticle
          class-type="image_content_rigth"
          :show-title="true"
          :show-image="true"
          :show-date="true"
          v-for="(el, i) in postsState.postList?.slice(4, 7)"
          :key="i"
          :single-post="el" />
      </div>
    </div>
  </div>
</template>

来自 ArticleListBlock 的调用的ArticleSingleArticle 组件

<script setup lang="ts">
import type { IArticle } from "types/IArticle";

defineProps({
  singlePost: {
    type: Object as PropType<IArticle>,
    default: null,
  },
  fontSize: {
    type: String,
    default: "18px",
  },
  fontWeight: {
    type: Number,
    default: 600,
  },
  rowSize: {
    type: String,
    default: "auto",
  },
  showContent: {
    type: Boolean,
    default: false,
  },
  showShortBody: {
    type: Boolean,
    default: false,
  },
  showCategory: {
    type: Boolean,
    default: false,
  },
  showTitle: {
    type: Boolean,
    default: false,
  },
  showImage: {
    type: Boolean,
    default: false,
  },
  showDate: {
    type: Boolean,
    default: false,
  },
  classType: {
    type: String,
    default: "",
  },
});
</script>

<template>
  <div class="single_post" :class="classType">
    <div class="tag" v-if="showCategory">
      <h5>{{ singlePost?.category?.toLocaleUpperCase() }}</h5>
    </div>
    <div class="title_post" v-if="showTitle">
      <NuxtLink :to="{ path: `/${singlePost?.category}/${singlePost?.id}` }">
        <p>{{ singlePost?.title }}</p>
      </NuxtLink>
    </div>

    <div class="preview_image" v-if="showImage">
      <img class="image" :src="singlePost?.image" alt="postPriview" />
    </div>

    <div v-if="showShortBody" class="shortn_block">
      <p>{{ singlePost?.shortBody }}</p>
    </div>
    <div class="post_created" v-if="showDate">
      <span>
        By <b class="author">{{ singlePost?.author }}</b>
        {{ singlePost?.date && formatDate(singlePost?.date) }}
      </span>
    </div>

    <div v-if="showContent" class="description_block">
      <div class="pre" v-html="singlePost?.body"></div>
      <div class="share_block_container">
        <UiElementsShareElement />
        <UiAddSocialMediaList :is-inline-block="true" :enable-bg="true" color-icon="white" />
      </div>
    </div>
  </div>
</template>

我删除了每个部分和组件中的所有注释标签,并尝试尽可能删除 v-if,但错误并没有消失。标签 ClientOnly 不适合我,因为我希望我的数据在服务器上呈现。

firebase vue.js nuxt.js server-side-rendering pinia
1个回答
0
投票

对于任何面临需要在组件中使用 html 标签的问题的人,例如p、href 等标签。使用“ClientOnly”来包装这些元素。

例如-

<ClientOnly>
  <div>
    <p>some text in here</p>
    <a href="<my-link>">Click Me</a>
  </div>
</ClientOnly>
© www.soinside.com 2019 - 2024. All rights reserved.