packages/src/app/components/client-only.mjs
export default defineComponent({
name: 'ClientOnly',
// eslint-disable-next-line vue/require-prop-types
props: ['fallback', 'placeholder', 'placeholderTag', 'fallbackTag'],
setup (_, { slots }) {
const mounted = ref(false)
onMounted(() => { mounted.value = true })
return (props) => {
if (mounted.value) { return slots.default?.() }
const slot = slots.fallback || slots.placeholder
if (slot) { return slot() }
const fallbackStr = props.fallback || props.placeholder || ''
const fallbackTag = props.fallbackTag || props.placeholderTag || 'span'
return createElementBlock(fallbackTag, null, fallbackStr)
}
}
})
CSR
的生命周期与 SSR
的生命周期是有差异的,我们可以利用这些差异来达到我们的目的,也就是利用了 onMounted
钩子只会在客户端执行这一特性。
我们创建了一个标记变量
mounted
,初始值为false
,并且仅在客户端渲染时将其设置为true
,这意味着,在服务端渲染的时候,<ClientOnly>
组件的插槽内容不会被渲染。而在客户端渲染的时候,只有等mounted
钩子触发之后才会渲染<ClientOnly>
组件的插槽内容。这样就实现了被<ClientOnly>
组件包裹的内容仅会在客户端被渲染。
另外,<ClientOnly>
组件并不会导致客户端激活失败,hyr,因为在客户端激活的时候,mounted钩子还没有触发,所以服务端与客户端渲染内容一致,即什么都不渲染。等激活完成,且 mounted钩子触发执行之后,才会在客户端将<ClientOnly>
组件的插槽内容渲染出来。–《Vue.js 设计与实现》
- 当
mounted === true
时,也就是客户端渲染时,<ClientOnly>
组件的默认插槽内容会被渲染; - 当
mounted === false
时,也就是服务端渲染时,<ClientOnly>
组件的 fallback插槽 或 placeholder插槽 或 fallback Prop 或 placeholder Prop 内容会被渲染;