Vue3 学习笔记(十四)——渲染函数API(模板的代替方案)与服务端渲染 (SSR)
一、渲染函数API(模板的代替方案)
在绝大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了。
注:Vue 的渲染系统基于“虚拟 DOM”;虚拟 DOM (Virtual DOM,简称 VDOM) 是一种编程概念,意为将目标所需的 UI 通过数据结构“虚拟”地表示出来,保存在内存中,然后将真实的 DOM 与之保持同步。这个概念是由 React 率先开拓,随后在许多不同的框架中都有不同的实现,当然也包括 Vue。
Vue 组件渲染管线:
① 编译:Vue 模板被编译为渲染函数:即用来返回虚拟 DOM 树的函数。
② 挂载:运行时渲染器调用渲染函数,遍历返回的虚拟 DOM 树,并基于它创建实际的 DOM 节点。
③ 更新:当一个依赖发生变化后,副作用会重新运行,这时候会创建一个更新后的虚拟 DOM 树。运行时渲染器遍历这棵新树,将它与旧树进行比较,然后将必要的更新应用到真实 DOM 上去。
1、基本用法
1)Vue创建 Vnodes(Vnodes 必须唯一)
h()
是 hyperscript 的简称——意思是“能生成 HTML (超文本标记语言) 的 JavaScript”(createVnode()
)。这个名字来源于许多虚拟 DOM 实现默认形成的约定。
import { h } from 'vue'
const vnode = h(
'div', // type
{ id: 'foo', class: 'bar' }, // props
[
/* children */
]
)
补充:h()
函数示例:
h() 函数示例
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都能在 prop 中书写;Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })
// 像 `.prop` 和 `.attr` 这样的的属性修饰符;可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })
// 类与样式可以像在模板中一样;用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
//上面的 vnode 得到的结果:
//const vnode = h('div', { id: 'foo' }, [])
//vnode.type // 'div'
//vnode.props // { id: 'foo' }
//vnode.children // []
//vnode.key // null
2)声明渲染函数
我们可以使用render
选项来声明渲染函数,render()
函数可以访问同一个this
组件实例。
import { h } from 'vue'
export default {
data() {
return {
msg: 'hello'
}
},
render() {
return h('div', this.msg)
}
}
2、案例:
1)v-if
模板:
<div>
<div v-if="ok">yes</div>
<span v-else>no</span>
</div>
解析后:
h('div', [this.ok ? h('div', 'yes') : h('span', 'no')])
JSX 语法(JSX 是 JavaScript 的一个类似 XML 的扩展):
<div>{this.ok ? <div>yes</div> : <span>no</span>}</div>
2)v-for
模板:
<ul>
<li v-for="{ id, text } in items" :key="id">
{{ text }}
</li>
</ul>
解析后:
h(
'ul',
this.items.map(({ id, text }) => {
return h('li', { key: id }, text)
})
)
JSX 语法(JSX 是 JavaScript 的一个类似 XML 的扩展):
<ul>
{this.items.map(({ id, text }) => {
return <li key={id}>{text}</li>
})}
</ul>
3)更多案例见:渲染函数案例
二、服务端渲染 (SSR)
1、为什么要使用“服务端渲染 (SSR)”:
与客户端的单页应用 (SPA) 相比,SSR具有以下好处:
1)更快的首屏加载:服务端渲染的 HTML 无需等到所有的 JavaScript 都下载并执行完成之后才显示,所以你的用户将会更快地看到完整渲染的页面。这通常可以带来更高的核心 Web 指标评分、更好的用户体验,而对于那些“首屏加载速度与转化率直接相关”的应用来说,这点可能至关重要。(可以使用 SSG 静态站点生成技术来代替)
2)统一的心智模型:就是指合并前后端的“技术栈”,可以统一使用nodejs技术栈进行开发。你可以使用相同的语言以及相同的声明式、面向组件的心智模型来开发整个应用,而不需要在后端模板系统和前端框架之间来回切换。
3)更好的 SEO:搜索引擎爬虫可以直接看到完全渲染的页面。
2、SSR 渲染的弊端:
1)增大运营成本:服务端需要安装Node.js 服务运行环境;增加了服务器的负载。
2)开发中的限制:浏览器端特定的代码只能在某些生命周期钩子中使用;一些外部库可能需要特殊处理才能在服务端渲染的应用中运行。
3)跨请求状态污染:如果使用了响应式 API 的简单状态管理模式,该模式会在一个 JavaScript 模块的根作用域中声明共享的状态,这是一种单例模式——即在应用的整个生命周期中只有一个响应式对象的实例。这在纯客户端的 Vue 应用中是可以的(因为对于浏览器的每一个页面访问,应用模块都会重新初始化)然而,在 SSR 环境下,该模式会造成状态污染(应用模块通常只在服务器启动时初始化一次。同一个应用模块会在多个服务器请求之间被复用)。
4)js获取到的时间与客户端不一致:获取到的时间为服务器时区的时间,不会跨时区。
5)不能访问平台上浏览器的特有 API:这些API在nodejs环境中是不被支持的。
6)Html解析兼容性降低:存在错误的Html在nodejs环境中的解析完不会被浏览器优化,SSR模式下返回的页面效果和浏览器解析的会有些许不同。
3、官方提供的更通用的 SSR应用框架
1)Nuxt:一个构建于 Vue 生态系统之上的全栈框架,它为编写 Vue SSR 应用提供了丝滑的开发体验。更棒的是,你还可以把它当作一个静态站点生成器来用!我们强烈建议你试一试。
2)Quasar:是一个基于 Vue 的完整解决方案,它可以让你用同一套代码库构建不同目标的应用,如 SPA、SSR、PWA、移动端应用、桌面端应用以及浏览器插件。除此之外,它还提供了一整套 Material Design 风格的组件库。
3)Vite SSR:Vite 提供了内置的 Vue 服务端渲染支持,但它在设计上是偏底层的。如果你想要直接使用 Vite,可以看看 vite-plugin-ssr,一个帮你抽象掉许多复杂细节的社区插件。
4、手写一个原生SSR应用
1)创建SSR应用(服务器nodejs环境中编程)
① 创建一个新的文件夹,cd
进入
② 执行 npm init -y
③ 在 package.json
中添加 "type": "module"
使 Node.js 以 ES modules mode 运行
④ 执行 npm install vue
⑤ 创建一个 example.js
文件:
// 此文件运行在 Node.js 服务器上
import { createSSRApp } from 'vue'
// Vue 的服务端渲染 API 位于 `vue/server-renderer` 路径下
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => { // 接收一个 Vue 应用实例作为参数,返回一个 Promise,当 Promise resolve 时得到应用渲染的 HTML。
console.log(html)
})
⑥ 接着运行:> node example.js
⑦ 它应该会在命令行中打印出如下内容:
<button>1</button>
⑧ 执行 npm install express
⑨ 创建下面的 server.js
文件:
import express from 'express'
import { renderToString } from 'vue/server-renderer'
import { createSSRApp } from 'vue'
const server = express()
server.get('/', (req, res) => {
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`)
})
})
server.listen(3000, () => {
console.log('ready')
})
⑩ 最后,执行 node server.js
;访问 http://localhost:3000
,可以看到应用的静态页面。
如果你点击该按钮,你会发现数字并没有改变。这段 HTML 在客户端是完全静态的,因为我们没有在浏览器中加载 Vue。
2)客户端激活应用(服务器nodejs环境中编程)
在激活过程中,Vue 会创建一个与服务端完全相同的应用实例,然后将每个组件与它应该控制的 DOM 节点相匹配,并添加 DOM 事件监听器。为了在激活模式下挂载应用,我们应该使用 createSSRApp()
而不是 createApp()
① 单独的应用创建逻辑文件 app.js
// app.js (在服务器和客户端之间共享)
import { createSSRApp } from 'vue'
export function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
}
② 客户端入口导入通用代码,创建应用并执行挂载:
// client.js
import { createApp } from './app.js'
createApp().mount('#app')
③ 服务器在请求处理函数中使用相同的应用创建逻辑:
import express from 'express';
import { renderToString } from 'vue/server-renderer';
import { createApp } from './app.js';
const server = express();
server.get('/', (req, res) => {
const app = createApp();
renderToString(app).then((html) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
<!-- 通过在 HTML 外壳中添加 Import Map 以支持在浏览器中使用 import * from 'vue' -->
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<!-- 将 <script type="module" src="/client.js"></script> 添加到 HTML 外壳以加载客户端入口文件。 -->
<script type="module" src="/client.js"></script>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`);
});
});
server.use(express.static('.')); // 添加 server.use(express.static('.')) 来托管客户端文件。
server.listen(3000, () => {
console.log('ready');
});
本文来自博客园,作者:꧁执笔小白꧂,转载请注明原文链接:https://www.cnblogs.com/qq2806933146xiaobai/articles/17384345.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
2020-05-09 Halcon的write_image函数