SSR 实现
原理:构建出客户和服务端,通过 node 运行服务端,将服务端渲染后的 html 返回给客户端,同时激活客户端的代码
在 vue3 中, setup 函数可以在服务端执行到,await 异步组件也可以被等待,因此拿到的 html 是完整的。 如果是只需要在客户端进行的逻辑,可以在 onMounted hook 中执行,避免 setInterval 等带副作用的函数滥用,因为服务端也会执行该代码,但是服务端没有 onUnMounted hook,因此该副作用将无法被清除(客户端不影响)
数据的获取: 通过服务端的 ctx 传递要获取的数据,并通过字符串替换的方式注入到 window 中,然后客户端判断有无该数据从而决定是使用还是执行请求
typescript
import { ref, useSSRContext } from 'vue'
import { SSR_CTX } from '~/types/comm'
function createSSRProvide() {
const _map = new Map<string, any>()
async function SSRRef<T>(SSRKey: string, generator?: () => Promise<T>) {
if (_map.has(SSRKey) && generator)
throw new Error(`SSR_Key: ${SSRKey} already exists, please use another unique string`)
else _map.get(SSRKey)
const reactiveValue = ref<T>()
if (import.meta.env.SSR) {
const context = useSSRContext<SSR_CTX>()!
const data = generator ? await generator() : context.injectData[SSRKey]
generator && Reflect.set(context.injectData, SSRKey, data)
reactiveValue.value = data
}
else {
const data = window.__INJECT_DATA__[SSRKey] || _map.get(SSRKey) || (await generator!())
!_map.has(SSRKey) && _map.set(SSRKey, data)
reactiveValue.value = data
}
return reactiveValue
}
return { SSRRef }
}
export const { SSRRef } = createSSRProvide()useSSRContext 用法
javascript
// entry-server.ts
const ctx = { name: 'rick' };
const html = await renderToString(app, ctx);
// App.vue
const ctx = useSSRContext();
const message = ctx?.name ? ctx.name : "message";
// 这里可以拿到 ctx 的上下文,然后进行逻辑处理pinia 使用
entry-client.ts
javascript
import { createApp } from './main'
const { app, pinia } = createApp()
window.__pinia && (pinia.state.value = window.__pinia) // pinia 会自动将数据进行合并(有些可以,但有些不行,所以要保证数据来源都能一一对应,这样才不会出现意料外的情况) __pinia = { userStore: { state: { name: 'rick' } } }entry-server.ts
typescript
import { getMessage } from '@/api'
import { renderToString } from 'vue/server-renderer'
import { createApp } from './main'
export async function render(url: string, ssrManifest: string) {
const { app, pinia } = createApp()
const ctx = {}
const html = await renderToString(app, ctx)
pinia.state.value.userStore.userInfo = await getMessage() // 修改 store 中的数据
const __pinia = JSON.stringify(pinia.state.value) // 将 store 数据进行序列化
return { html, __pinia }
// server.js 将 __pinia 渲染到 index.html 中
// template.replace(`<!--__pinia-->`, `window.__pinia = ${rendered.__pinia ?? ''}`)
}