服务端渲染的特殊处理
服务端渲染的特殊处理
Vue3的服务端渲染(SSR)与客户端渲染存在显著差异,核心在于如何在不同环境中处理组件生命周期、状态管理和DOM操作。运行时需要区分当前执行环境,对特定API进行替换或降级处理。
环境变量与全局状态隔离
服务端渲染首要问题是避免单例状态污染。在Node.js环境下,每个请求应该获得全新的应用实例:
// 创建工厂函数避免单例
export const createApp = () => {
const app = createSSRApp(App)
const store = createStore()
app.use(store)
return { app, store }
}
Vue3通过__VUE_PROD_SSR__
编译标志区分环境,内部会自动处理window
等客户端特有对象的引用问题。对于全局变量访问,推荐使用ssrContext
注入:
// 服务端设置上下文
const ctx = {}
const { app } = createApp()
renderToString(app, ctx)
// 组件内获取
import { useSSRContext } from 'vue'
const ctx = inject(SSR_CONTEXT)
生命周期钩子调整
服务端执行时仅会调用特定生命周期,需要特别注意:
beforeCreate
和created
会在服务端执行beforeMount
和mounted
只会在客户端执行beforeUpdate
和updated
不会在服务端执行activated
和deactivated
需要配合KeepAlive
使用
异步组件需要特殊处理:
defineAsyncComponent({
loader: () => import('./Component.vue'),
suspensible: false // 禁止Suspense包装
})
模板编译差异
服务端渲染时,编译器会生成优化后的字符串拼接代码。通过compileSSR
方法会产生不同的渲染函数:
const { compile } = require('vue/compiler-ssr')
const result = compile(`<div>{{ msg }}</div>`, {
isCustomElement: tag => tag.startsWith('x-')
})
关键差异点包括:
- 跳过事件监听器编译
- 不生成虚拟DOM补丁标志
- 静态节点提升为字符串常量
- 禁用
v-model
等双向绑定语法
客户端激活处理
混合渲染时需要确保客户端激活正确执行。createSSRApp
会生成特殊标记:
<div id="app" data-server-rendered="true">
<!-- 服务端渲染内容 -->
</div>
激活过程会对比现有DOM与虚拟DOM,关键源码逻辑在packages/runtime-core/src/renderer.ts
:
if (container._vnode) {
// 激活现有DOM
hydrate(container._vnode, container)
} else {
// 全新挂载
render(vnode, container)
}
数据预取与状态同步
服务端数据预取需要特殊处理以避免客户端重复请求:
// 服务端路由配置
router.beforeResolve(async (to, from, next) => {
const matched = to.matched.flatMap(record => {
return Object.values(record.components).map(component => {
return component.asyncData?.({ store, route: to })
})
})
await Promise.all(matched)
next()
})
// 客户端数据同步
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__)
}
流式渲染处理
高性能场景下可以使用流式渲染,Vue3通过renderToStream
实现:
const stream = renderToStream(app)
stream.on('data', chunk => {
res.write(chunk)
})
stream.on('end', () => {
res.end()
})
流式渲染需要特别注意组件顺序,异步组件需要提前声明:
defineComponent({
ssrPrefetch: true, // 强制预加载
async setup() {
const data = await fetchData()
return { data }
}
})
跨平台API适配
Vue3通过@vue/server-renderer
提供平台无关的渲染API,内部实现了跨平台的基本方法:
interface RendererOptions {
createElement: (tag: string) => any
insert: (el: any, parent: any) => void
setElementText: (el: any, text: string) => void
patchProp: (el: any, key: string, prevValue: any, nextValue: any) => void
}
对于特殊环境如小程序,可以自定义实现这些接口。
错误处理与降级方案
服务端渲染需要完善的错误捕获机制:
try {
const html = await renderToString(app)
} catch (err) {
// 记录错误日志
console.error('SSR error:', err)
// 降级为客户端渲染
res.send(`<div id="app"></div>`)
}
对于第三方库兼容性问题,可以通过ssrMock
模拟浏览器对象:
global.window = {
localStorage: {
getItem: () => null
}
}
性能优化策略
SSR性能关键点在于缓存和复用:
const LRU = require('lru-cache')
const microCache = new LRU({
max: 100,
maxAge: 1000 * 60 // 1分钟缓存
})
function render(req, res) {
const cacheKey = req.url
if (microCache.has(cacheKey)) {
return res.send(microCache.get(cacheKey))
}
renderToString(app).then(html => {
microCache.set(cacheKey, html)
res.send(html)
})
}
组件级别缓存通过serverCacheKey
实现:
export default {
name: 'CachedComponent',
props: ['item'],
serverCacheKey: props => props.item.id
}
构建配置差异
SSR构建需要生成不同资源:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.entry('app').clear().add('./src/entry-client.js')
config.entry('server').clear().add('./src/entry-server.js')
}
}
Webpack配置需要处理Node.js模块:
externals: [
nodeExternals({
allowlist: [/\.css$/, /\?vue&type=style/]
})
]
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:异步渲染的实现原理
下一篇:自定义渲染器的扩展机制