- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
导读
在 Quasar 构建 SSR(服务端渲染)网站时,常常会遇到一类"非同构"代码——它们依赖浏览器专属 API(如window、document、localStorage)或第三方库,无法在 Node.js 服务端执行。QNoSsr组件正是为此而生:它让开发者轻松地将默认插槽内容标记为"仅客户端渲染",同时通过placeholder属性或插槽为服务端提供占位内容,实现 SSR 与客户端渲染内容的精准分工。读完本文,你将掌握QNoSsr的全部用法、其底层实现原理(useHydration组合式函数与平台标志),以及如何结合测试用例验证它的行为。
组件定位与适用场景
QNoSsr是 Quasar UI 框架中专门面向 SSR/SSG 场景设计的 Vue 组件,只在构建 SSR 网站/应用时有意义。它解决的核心痛点是:
- 避免在服务端渲染其内容,将渲染工作完全留给客户端浏览器;
- 适用于那些非同构(non-isomorphic)、只能在浏览器端运行的代码;
- 反过来,它也可以用于仅在服务端渲染内容——当这些内容最终运行在客户端浏览器时会被自动移除(借助 placeholder 机制)。
组件名中的 "No SSR" 即"不做服务端渲染"之意。它由 ui/src/components/no-ssr/QNoSsr.js 实现,并从 ui/src/components.js 统一导出,全局注册后可直接以<q-no-ssr>标签使用。
底层原理:useHydration 与 isHydrated 标志
要真正用好QNoSsr,先理解它的运行机制。从源码看,组件的setup中调用了useHydration()组合式函数:
setup(props, { slots }) { const { isHydrated } = useHydration() return () => { if (isHydrated.value) { // 客户端已水合 → 渲染 default 插槽内容 const node = hSlot(slots.default) return node === void 0 ? node : node.length > 1 ? h(props.tag, {}, node) : node[0] } // 未水合(服务端或客户端水合前)→ 渲染 placeholder // ... } }useHydration定义在 ui/src/composables/use-hydration/use-hydration.js:
import { isRuntimeSsrPreHydration } from '../../plugins/platform/Platform.js' export default function useHydration() { const isHydrated = ref(!isRuntimeSsrPreHydration.value) if (!isHydrated.value) { onMounted(() => { isHydrated.value = true }) } return { isHydrated } }其核心是一个响应式标志isHydrated:
- 在非 SSR 环境下,
isHydrated初始即为true,QNoSsr直接渲染默认插槽内容,等价于一个透传包装组件,零额外开销; - 在SSR 服务端或客户端水合前,
isHydrated为false,组件渲染占位内容; - 客户端挂载完成(
onMounted)后,isHydrated翻转为true,自动切换到默认插槽内容。
isHydrated的初始值来自 ui/src/plugins/platform/Platform.js 中的isRuntimeSsrPreHydration——它由构建期注入的编译常量(__QUASAR_SSR_SERVER__、__QUASAR_SSR_CLIENT__、__QUASAR_SSR_PWA__)推导而来。在 SSR/SSG 的 PWA 混合模式下,还会通过检查document.body.dataset中的serverRendered标记做运行时判定,确保水合逻辑准确。这也解释了为什么QNoSsr能同时服务 SSR 与 SSG 两种构建产物。
完整 Props 与 Slots API
根据组件 API 定义文件 ui/src/components/no-ssr/QNoSsr.json,QNoSsr的公开接口非常精简:
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
tag | String | 'div' | 当需要包裹多个子节点(或占位内容)时,用于包裹这些节点所使用的 HTML 标签,如'div'、'span'、'blockquote' |
placeholder | String | — | 服务端渲染时展示的文本内容(未使用placeholder插槽时生效) |
Slots
| Slot | 说明 |
|---|---|
default | 默认插槽,用于渲染客户端侧的内容 |
placeholder | 服务端渲染时用作占位的插槽,客户端水合后会被默认插槽内容替换;优先级高于placeholder属性 |
注意placeholder插槽与placeholder属性是"插槽优先"的关系:当两者同时存在时,插槽内容胜出,属性被忽略——这一点在测试用例 ui/src/components/no-ssr/QNoSsr.test.js 中有明确验证(同时传入 prop 与 slot 时,断言文本只包含插槽内容)。
使用指南:六种典型写法
1. 基本用法
最简单的场景——默认插槽内容只在客户端渲染,服务端完全不输出:
<q-no-ssr> <div>This won't be rendered on server</div> </q-no-ssr>2. 多个客户端节点
当默认插槽包含多个根节点时,组件会自动用一个div(默认tag)把它们包起来,保证返回单一根节点:
<q-no-ssr> <div>This won't be rendered on server.</div> <div>This won't either.</div> </q-no-ssr>从 QNoSsr.js 的实现可见:渲染函数会检查插槽节点数量,node.length > 1时用h(props.tag, {}, node)包裹,否则直接返回单节点(不产生多余包裹元素)。
3. 通过 tag 属性指定包裹标签
如果你不想用默认的div,可以用tag属性自定义包裹标签:
<q-no-ssr tag="blockquote"> <div>This won't be rendered on server.</div> <div>This won't either.</div> </q-no-ssr>这里渲染结果等价于<blockquote><div>…</div><div>…</div></blockquote>。测试用例 QNoSsr.test.js 使用tag="section"验证了包裹元素会变成<section>。
4. 通过 placeholder 属性提供服务端占位文本
服务端渲染时,你可能希望展示"加载中""该区域需浏览器支持"等占位提示,而不是空白。用placeholder属性即可:
<q-no-ssr placeholder="Rendered on server"> <div>This won't be rendered on server</div> </q-no-ssr>服务端输出占位文本,客户端水合后无缝替换为默认插槽的真实内容。测试 QNoSsr.test.js 还验证了占位文本会被加上q-no-ssr-placeholder类(便于样式定位)。
5. 通过 placeholder 插槽提供富占位内容
当占位内容需要包含多个元素或复杂结构(而非单条文本)时,使用#placeholder插槽:
<q-no-ssr> <div>This won't be rendered on server</div> <template #placeholder> <div>Rendered on server</div> </template> </q-no-ssr>6. placeholder 插槽中的多内容与仅占位场景
占位插槽同样支持多个根节点,此时组件会像默认插槽一样用tag标签包裹:
<q-no-ssr> <div>This won't be rendered on server</div> <template #placeholder> <div>Rendered on server (1/2)</div> <div>Rendered on server (2/2)</div> </template> </q-no-ssr>甚至可以只提供占位插槽、不提供默认内容——此时客户端水合后该区域会保持为空,适合"内容仅服务端输出"的反向场景:
<q-no-ssr> <template #placeholder> <div>Rendered on server</div> </template> </q-no-ssr>这印证了文档所述的第二类用途:只在服务端渲染、客户端自动移除。
无障碍(Accessibility)
自 v2.25 起,文档明确了QNoSsr的无障碍特性:它本质上是一个透传包装组件,最终输出的只有你自己的内容(或占位内容),自身不引入任何额外的无障碍语义表面(如隐式 role、aria 属性等)。因此,无障碍质量完全取决于你放入插槽的内容本身,例如占位文本应使用语义化标签、对比度足够的颜色等。
用测试验证组件行为
Quasar 仓库为QNoSsr提供了两层测试,可作为理解组件契约的权威参考:
单元测试ui/src/components/no-ssr/QNoSsr.test.js:通过 mock
useHydration返回的isHydrated值,分别断言:tagprop 对多节点包裹标签的影响;placeholder属性渲染文本且带q-no-ssr-placeholder类;- 默认插槽内容正常渲染;
placeholder插槽优先于placeholder属性。
SSR 水合测试ui/src/components/no-ssr/QNoSsr.hydration.test.js:基于 ui/test/hydration/hydrate.js 的真实 SSR 往返流程(服务端 bundle 渲染 → 客户端水合),验证:
- 服务端 HTML 中包含占位内容(
Server placeholder); - 客户端挂载后内容被替换为默认插槽内容(
Client only content); - 整个水合过程无控制台警告(
consoleOutput为空)。
- 服务端 HTML 中包含占位内容(
对应的水合夹具定义在 ui/src/components/no-ssr/QNoSsr.hydration.fixtures.js,它要求渲染必须确定性、可复现,这正是 SSR 场景的硬性约束。
小结与最佳实践
- 何时使用:只有 SSR/SSG 项目需要关心
QNoSsr;纯 SPA 中它是零成本的透传组件。 - 如何分工:默认插槽放浏览器专属逻辑的内容,
placeholder属性/插槽放服务端可安全输出的降级内容;两者结合可避免水合不一致(hydration mismatch)与白屏闪烁。 - 接口最小化:仅两个 prop、两个 slot,学习成本极低;多节点时留意
tag对包裹元素的影响。 - 验证手段:可直接复用仓库中的单元测试与 SSR 水合测试模式,把"服务端出占位、客户端出真内容"作为验收断言。
通过QNoSsr,Quasar 将 SSR 中最棘手的"非同构代码"处理收敛为一个声明式组件,配合useHydration的响应式切换,让开发者无需手写process.client之类的环境分支,即可安全、优雅地完成服务端与客户端内容的差异化渲染。
- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
相关推荐
Vuetify v-no-ssr 组件:用 VNoSsr 实现"仅客户端渲染"的 SSR 内容控制
Vuetify v no ssr 组件:用 VNoSsr 实现"仅客户端渲染"的 SSR 内容控制 v no ssr 是 Vuetify 中一个体积最小、职责最
前端UI组件TanStack Start Selective SSR 完全指南:按路由精准控制服务端渲染
TanStack Start Selective SSR 完全指南:按路由精准控制服务端渲染 TanStack Start(即本仓库 react start /
前端路由SSRZustand 服务端渲染与水合(SSR and Hydration)实战指南:从 React 服务器渲染到客户端状态同步
Zustand 服务端渲染与水合(SSR and Hydration)实战指南:从 React 服务器渲染到客户端状态同步 本篇技术指南以 zustand 官方
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考