Vue 的模板语法是我们日常开发中最常使用的部分,但它背后的核心其实是「渲染函数」与「模板编译器」。本文将从本质出发,深入探讨模板是如何被编译成渲染函数的,以及编译发生的时机。
🧩 本文结构:
- 渲染函数
- 模板编译原理
- 编译的时机
一、渲染函数
📚 概念:
文档地址:渲染函数官方文档
渲染函数(h 函数)调用后返回的是 虚拟 DOM 节点。
💡 在 Vue 中,我们所写的模板会被一个 模板编译器 编译成 JS 渲染函数。因此,Vue 本质上并不依赖模板,而是依赖渲染函数。
这意味着你可以直接使用纯 JavaScript 描述组件视图结构,下面是一个经典的示例:
// 模板组件
<template>
<div class="user-card">
<img :src="user.avatarUrl" alt="User avatar" class="avatar" />
<div class="user-info">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</div>
</template>
<script setup>
const user = defineProps({
name: String,
email: String,
avatarUrl: String
})
</script>
// 使用渲染函数重写模板组件
import { defineComponent, h } from 'vue'
import styles from './UserCard.module.css'
export default defineComponent({
name: 'UserCard',
props: {
name: String,
email: String,
avatarUrl: String
},
setup(props) {
return () =>
h('div', { class: styles.userCard }, [
h('img', {
class: styles.avatar,
src: props.avatarUrl,
alt: 'User avatar'
}),
h('div', { class: styles.userInfo }, [
h('h2', props.name),
h('p', props.email)
])
])
}
})
对应的样式:
/* UserCard.module.css */
.userCard {
display: flex;
align-items: center;
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 10px;
padding: 10px;
margin: 10px 0;
}
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 15px;
}
.userInfo h2 {
margin: 0;
font-size: 20px;
color: #333;
}
.userInfo p {
margin: 5px 0 0;
font-size: 16px;
color: #666;
}
都得到相同的效果图如下:
✅ 总结:
Vue 提供模板只是为了降低开发者的心智负担,而 Vue 核心只关心渲染函数所返回的虚拟 DOM。
Vue 模板如何转换为 JS 渲染函数呢?这就要说说模板编译原理了...
二、模板编译原理
📌模板本质上是一段字符串,模板编译器需要对这串字符串进行操作,最终生成渲染函数。
编译过程详解:
模板编译器结构概览:
function compile(template){
const ast = parse(template) // 1. 解析器
transform(ast) // 2. 转换器
const code = generate(ast) // 3. 生成器
return code
}
示例:简单模板的编译过程
<div>
<p>Vue</p>
<p>React</p>
</div>
模板编译器将其看成一串字符串:
"<div><p>Vue</p><p>React</p></div>"
Step 1:解析器将字符串解析生成 tokens
[
{ type: 'tag', name: 'div' },
{ type: 'tag', name: 'p' },
{ type: 'text', content: 'Vue' },
{ type: 'tagEnd', name: 'p' },
{ type: 'tag', name: 'p' },
{ type: 'text', content: 'React' },
{ type: 'tagEnd', name: 'p' },
{ type: 'tagEnd', name: 'div' }
]
Step 2:解析器根据 tokens 生成模板 AST(抽象语法树)
{
type: 'Root',
children: [
{
type: 'Element',
tag: 'div',
children: [
{
type: 'Element',
tag: 'p',
children: [{ type: 'Text', content: 'Vue' }]
},
{
type: 'Element',
tag: 'p',
children: [{ type: 'Text', content: 'React' }]
}
]
}
]
}
Step 3:转换器将模板 AST 转换为 JS AST
{
type: 'FunctionDecl',
id: { type: 'Identifier', name: 'render' },
params: [],
body: [
{
type: 'ReturnStatement',
return: {
type: 'CallExpression',
callee: { type: 'Identifier', name: 'h' },
arguments: [
{ type: 'StringLiteral', value: 'div' },
{
type: 'ArrayExpression',
elements: [
{
type: 'CallExpression',
callee: { type: 'Identifier', name: 'h' },
arguments: [
{ type: 'StringLiteral', value: 'p' },
{ type: 'StringLiteral', value: 'Vue' }
]
},
{
type: 'CallExpression',
callee: { type: 'Identifier', name: 'h' },
arguments: [
{ type: 'StringLiteral', value: 'p' },
{ type: 'StringLiteral', value: 'React' }
]
}
]
}
]
}
}
]
}
Step 4:生成器根据 JS AST 生成最终的 JS 代码
function render () {
return h('div', [h('p', 'Vue'), h('p', 'React')])
}
✅ 总结:
模板本质上是一段字符串,模板编译器分为三步对这串字符串进行操作,最终生成渲染函数:
- 解析器(Parser):将字符串解析生成 tokens , 根据 tokens 生成模板 AST(抽象语法树)
- 转换器(Transformer):将模板 AST 转换为 JS AST
- 生成器(Code Generator):将 JS AST 生成最终的渲染函数代码
每一个部件都依赖于上一个部件的执行结果。
三、编译的时机
1. 运行时编译
当我们使用 CDN 引入 Vue,并在 HTML 中直接写模板时,Vue 会在运行时进行模板编译:
<div id="app">
<user-card :name="name" :email="email" :avatar-url="avatarUrl" />
</div>
<template id="user-card-template">
<div class="user-card">
<img :src="avatarUrl" class="avatar" />
<div class="user-info">
<h2>{{ name }}</h2>
<p>{{ email }}</p>
</div>
</div>
</template>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue
const UserCard = {
props: ['name', 'email', 'avatarUrl'],
template: '#user-card-template'
}
createApp({
components: { UserCard },
data() {
return {
name: "夕夕",
email: "123@qq.com",
avatarUrl: './avater.jpg',
}
}
}).mount('#app')
</script>
💡 这种方式的性能较差,适合快速原型开发或教学演示。
2. 预编译(推荐)
在 Vite/Webpack 等构建工具中,Vue 会在打包阶段就把模板预编译成渲染函数,浏览器拿到的是已经编译好的代码。
推荐使用 vite-plugin-inspect 插件查看编译结果:
npm install vite-plugin-inspect --save-dev
vite.config.js 中配置:
import Inspect from 'vite-plugin-inspect'
export default {
plugins: [
Inspect()
]
}
运行后可访问:http://localhost:5173/__inspect/ 查看每个组件编译后的渲染函数。
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
_createElementVNode("img", {
src: $setup.user.avatarUrl,
alt: "User avatar",
class: "avatar",
"data-v-inspector": "src/App.vue:3:5"
}, null, 8 /* PROPS */, _hoisted_2),
_createElementVNode("div", _hoisted_3, [
_createElementVNode("h2", _hoisted_4, _toDisplayString($setup.user.name), 1 /* TEXT */),
_createElementVNode("p", _hoisted_5, _toDisplayString($setup.user.email), 1 /* TEXT */)
])
]))
}
🧠 总结
- Vue 模板只是语法糖,本质是渲染函数。
- 模板编译分为解析器 → 转换器 → 生成器三大步骤。
- Vue 支持运行时编译(不推荐)与预编译(推荐)。
- 学会使用渲染函数有助于更深入理解 Vue 的本质。
📌 下一篇:【重学 Vue:深入本质,脱离八股文,彻底搞定面试】:(三)组件树和虚拟DOM树
敬请期待~
如果你觉得这篇文章对你有帮助,欢迎关注 + 点赞 + 收藏,我会持续输出「Vue 技术本质」系列内容。