「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。
34.说一下 Vue 模版和 render 函数
Vue模版(template)是什么?
Vue模版(template
) 是 Vue 提供的类html语法,用来书写页面结构,可以在上面写html、指令、插值和 js 表达式,语法易于理解,书写简单,但缺点是不够灵活。
render 函数是什么?
渲染函数(render 函数)是 Vue 提供的另一种书写页面的方式,Vue 除了可以用 template 写页面,也可以用 render 函数结合 createElement
方法或者 JSX
书写页面,render 函数比 template 更接近编译器。
createElement 是什么?
createElement 是 Vue 提供的一个函数,可以通过传入 tag
(元素名)、data
(属性)和 children
(子元素) 这三个参数,来生成一个 VNode
。
createElement 在 Vue 源码中定义如下:
$createElement: (tag?: string | Component, data?: Object, children?: VNodeChildren) => VNode;
JSX是什么?
JSX 是一个 JavaScript 的语法扩展,可以很好地描述 UI 应该呈现出它应有交互的本质形式,并且具有 JavaScript 的全部功能,十分灵活。 -- react 文档
在 Vue 开发中,使用 render 函数 + JSX 来开发,比使用 render 函数 + createElement 写起来方便很多。
我们用一张图来总结一下常用组件写法:
日常开发大多数情况都是使用 template 开发组件,但某些特殊情况使用 render 函数 + JSX 来开发更方便。
三种组件写法的区别
举个例子,假设我们要实现一个标题组件,根据传入的 level 值决定用 h1 - h6 中的某一个标签。
我们用上面说的三种方式分别实现一下这个组件:
template
<custom-title :level="1">hello world</custom-title>
CustomTitle 组件内部
<div>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-if="level === 2">
<slot></slot>
</h2>
<h3 v-if="level === 3">
<slot></slot>
</h3>
<h4 v-if="level === 4">
<slot></slot>
</h4>
<h5 v-if="level === 5">
<slot></slot>
</h5>
<h6 v-if="level === 6">
<slot></slot>
</h6>
</div>
props: {
level: {
type: Number,
default: 1
}
}
使用 template,代码比较冗长,且写了很多重复的逻辑。
render函数 + createElement
props: {
level: {
type: Number,
default: 1
}
},
render(createElement) {
return createElement('h' + this.level, this.$slots.default)
}
使用 render 函数 + createElement,就简洁多了,但是代码不容易被理解,需要理解 createElement 方法。
render函数 + JSX
props: {
level: {
type: Number,
default: 1
}
},
render() {
const Tag = `h${this.level}`
return <Tag>{this.$slots.default}</Tag>
}
使用 render 函数 + JSX,代码简洁,且代码容易理解,虽然也全是 js 语法,但看着像 html。
再举一个例子,这样一段代码:
<custom-title :level="1">
<span>Hello</span> world!
</custom-title>
使用 createElement 编写:
render: function (createElement) {
return createElement(
'custom-title', {
props: {
level: 1
}
}, [
createElement('span', 'Hello'),
' world!'
]
)
}
使用 JSX 编写:
render: function () {
return (
<CustomTitle level={1}>
<span>Hello</span> world!
</CustomTitle>
)
}
很明显,使用 JSX 的体验好很多。
注意:vue-cli 3.0版本以上自动支持 JSX,如果是自己搭的 Vue 项目,就需要引入一些 babel 插件了,比如
@vue/babel-preset-jsx
35.讲一下 Vue 模板编译的过程
在 Vue 文档中,有一个实时编译模板字符串的示例,地址在这里
这样一个模板:
<div>
<p>{{ message }}</p>
</div>
会被编译为:
function anonymous(
) {
with(this){return _c('div',[_c('p',[_v(_s(message))])])}
}
这是如何实现的呢,我们来分析一下。
with语句
我们看到,被编译出的 js 代码使用了 with 语句。
with 这个语法不常用也不推荐用,不过在 Vue 模板编译的过程中用到了,我们就来学习一下这个语法。
with 语句的用途是将代码作用域设置为特定的对象,其语法是:
with (expression) {
statement
}
- 它可以改变代码块内自由变量的查找规则,当做对象的属性来查找。
- 如果找不到匹配的对象属性,会报错。 举个例子:
name 和 age 都打印出来了,打印 sex 报错。
with 要慎用,它打破了作用域规则,易读性变差。请看下面的例子:
function f(x, obj) {
with (obj)
print(x);
}
x的值就不知道取函数的值,还是对象 obj 的值。
跑题了,更多请查看 mdn
我们继续来分析 with 在 Vue 源码中的使用。
with(this){return _c('div',[_c('p',[_v(_s(message))])])}
这里的 with 就是为了让代码更简洁,代码块里的 _c
、_v
、 _s
、message
,其实就是
this._c
this._v
this._s
this.message
我们知道,this 是 vm 实例,this.message 好理解,但是 this._c、this._v、this._s 是啥?
函数缩写
看了 Vue 源码才知道,这三个东西其实是函数名的缩写
this._c = function (a, b, c, d) { return createElement(contextVm, a, b, c, d, needNormalization); };
export function installRenderHelpers (target: any) {
...
target._s = toString
...
target._v = createTextVNode
...
}
_c: (
vnode?: VNode,
data?: VNodeData,
children?: VNodeChildren,
normalizationType?: number
) => VNode | void;
_s: (value: mixed) => string;
_v: (value: string | number) => VNode;
_c 是·createElement
,_s 是 toString
, _v 是 createTextVNode
。
翻译一下:
<div>
<p>{{ message }}</p>
</div>
with(this){return _c('div',[_c('p',[_v(_s(message))])])}
with(this){
return createElement('div', [
createElement('p', [
createTextVNode(toString(message))
])
])
}
是不是感觉能看懂了,类 html 的 template 被转成了 js 的代码。
编译过程
那么 template 被转成 js 的过程是怎样的呢?可以总结成下面的几步
- 解析模板字符串生成 AST(抽象语法树)
- 优化 AST,方便后续虚拟 DOM 更新
- 将 AST 转化为可执行的代码(render函数)
- 执行 render 函数生成 VNode
在 Vue 源码中是这样的:
const baseCompile = (template, options) => {
const ast = parse(template.trim(), options) // 解析模板字符串生成 AST
optimize(ast, options) // 优化 AST,方便后续虚拟 DOM 更新
const code = generate(ast, options) // 将 AST 转化为可执行的代码
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
中间过程很复杂,阿林确实肝不动,更多详细的过程可以参考这两篇文章:
小结
Vue 模板编译的过程就是 tempalte -> render 函数 -> VNode,最终生成一个虚拟DOM,为响应式做准备。
如果我的文章对你有帮助,你的赞👍就是对我的最大支持^ _ ^
往期
Vue 100 问 (30-33问)Vue 组件之间的通信方式有哪些?
Vue 100问(27-29)说一下你对 vue 插槽的理解
Vue 100问(18-22问)谈谈你对 Vue 生命周期的理解
Vue100问(第5问)为什么 v-for 的 key 值不能是 index?