Skip to content

客户端渲染组件

ClientOnly

只在客户端进行渲染。基础语法如下:

<ClientOnly>
<div>这段内容只会在客户端渲染</div>
</ClientOnly>

在 SSR 场景下,<ClientOnly> 包裹的内容会被完全跳过,等到客户端激活后再渲染。这对于那些在服务器端渲染时可能导致问题的代码非常有用。例如有一些第三方库依赖浏览器环境(window、document、navigator)

<ClientOnly> 提供了服务端渲染期间的占位内容。也就是说,在客户端渲染之前,服务端会渲染备用内容。指定占位内容的方式有两种:

<ClientOnly> 类似的还有 <DevOnly>,内部的内容仅在开发环境下渲染。例如:

<DevOnly>
<!-- 该组件仅在开发环境渲染 -->
<LazyDebugBar />
<!-- 同样支持占位 -->
<template #fallback>
<div><!-- 占位内容 --></div>
</template>
</DevOnly>

岛屿组件

英文是 Island Components. 岛屿组件的核心思想是不做水合

传统的 SSR 应用中,服务器会生成完整的首屏页面,然后客户端拿到首屏的 html 后再做水合操作,将整个页面转为可以交互的实例。

在这一过程中,从静态 html 转为 Vue 实例,涉及到生成虚拟 DOM以及响应式数据,这一过程是存在性能消耗的。

例如:

app.vue

<template>
<h1>我的博客</h1>
<button @click="count++">+1</button>
<Blog />
</template>
<script setup>
const count = ref(0)
</script>

Blog.vue

<template>
<div>
<h1>博客标题</h1>
<div>
<!-- 博客正文 -->
<!-- 10,000 字的静态内容 -->
</div>
</div>
</template>

这种场景可以使用岛屿组件:

<NuxtIsland name="Blog" />

app.vue

<template>
<h1>我的博客</h1>
<button @click="count++">+1</button>
<NuxtIsland name="Blog" />
</template>
<script setup>
const count = ref(0)
</script>

有些时候,组件的内容是由 props 传入的,此时也可以使用岛屿组件。例如:

Blog.vue

<template>
<div>
<h1>{{ article.title }}</h1>
<div v-html="article.content"></div>
</div>
</template>
<script setup>
defineProps({
article: Object,
})
</script>

app.vue

<template>
<h1>我的博客</h1>
<button @click="count++">+1</button>
<NuxtIsland name="Blog" :props="{ article }" />
</template>
<script setup>
const count = ref(0)
</script>

此时岛屿组件的工作流程:

  1. 初次渲染时:Blog 在服务器端生成好静态的 HTML 返回给客户端,客户端直接展示不做水合。
  2. props改变时:与之前 SPA 在客户端更新内容的方式有区别,因为没有做水合,该组件不具备客户端交互的能力。因此这里props发生变化的时候,会向服务器重新发请求生成新的html结构,返回给客户端进行更新。

岛屿组件适用场景就是展示大片静态内容的场景。

两者区别

如下表所示:

组件核心目标
ClientOnly完全跳过服务端渲染,确保子组件仅在客户端渲染
NuxtIsland服务端渲染成静态内容,客户端直接展示这部分静态内容,不做水合。

实际使用哪个,需要根据具体的场景来进行选择。

由于 NuxtIsland 目前处于实验阶段,使用前需要在 nuxt.config.ts 中启用 experimental.componentIslands 选项。

export default defineNuxtConfig({
experimental: {
componentIslands: true, // 或 'local+remote' 支持远程加载
},
})

-EOF-