# Vue 中的 render 函数和 jsx
# 从官方文档的例子说起
需要这样一个组件
<anchored-heading :level="1">Hello world!</anchored-heading>
template
写法:
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
render
函数写法:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
})
好处:代码精简,不需要写重复代码
其实,template
也可以:
<component :is="'h' + level"><slot></slot></component>
# 关于 Vue 中模板的写法
Vue 中模板的写法总共有几种?
template
:- '#' + 选择器,使用匹配元素的
innerHTML
作为模板:"#my-component"
- 字符串:
"<li>{{ todo.text }}</li>"
- 单文件组件中的
template
标签,最终会通过vue-loader
提取为字符串,然后通过vue-template-compiler
编译成render
函数,所以可以使用 Pug 写法
- '#' + 选择器,使用匹配元素的
render
:createElement
函数jsx
渲染过程:template
-> AST
(抽象语法树)-> render
函数 -> VNode
(虚拟 dom) -> 真实 dom
render
函数的性能更好,优先级更高
# 运行时 + 编译器 vs. 只包含运行时
// 需要编译器
new Vue({
template: '<div>{{ hi }}</div>'
})
// 不需要编译器
new Vue({
render (h) {
return h('div', this.hi)
}
})
对应 webpack 配置:
module.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
当使用 vue-loader
的时候,*.vue
文件内部的模板会在构建时预编译成 JavaScript
使用运行时版本性能更好(不需要编译 template),体积更小(30%)
Vue2 dist 目录下各个文件的区别 - M.M.F 小屋 (opens new window)
# createElement
参数和数据对象
请看官方文档:渲染函数 & JSX — Vue.js (opens new window)
# 使用 JavaScript 代替模板功能
v-if
:if
&else
,或三元表达式v-for
:Array.prototype.map
v-model
:
const data = {
props: {
value: this.value
},
on: {
input: (val) => {
this.value = val
}
}
}
- 展开数据对象(类似
v-bind
和v-on
):<base-input {...{ props: {}, on: {} }} />
- 事件 & 按键修饰符 (opens new window)
# JSX
更接近模板语法,比使用 createElement
函数简洁
- 用括号包裹 jsx 代码
- 需要写 js 逻辑的地方用
{ ... }
,包括动态属性 - 事件监听用
on-eventName
- 可直接将 jsx 片段赋值给变量
需要插件进行编译,所以不能在 HTML 中直接写 jsx
插件:vuejs/jsx: monorepo for Babel / Vue JSX related packages (opens new window)
# 模板组件中使用渲染函数
当你使用的组件是用 template
的方式编写,但是又想从外部传一个渲染函数进来渲染某一块内容时,可以利用一个更小的组件来做这个事情,如:
<template>
<div class="my-component">
<div v-for="item in arr" :key="item.id">
<custom-header :item="item"></custom-header>
<div class="content">...</div>
</div>
</div>
</template>
<script>
export default {
name: 'my-component',
props: {
renderHeader: Function, // 从父组件传过来的渲染函数
arr: Array
},
components: {
CustomHeader: {
props: {
item: Object
},
render(h) {
// 通过渲染函数渲染自定义内容
return this.$parent.renderHeader(h, this.item)
}
}
}
}
</script>
感兴趣的话可以看看 element ui 中这个更复杂的例子 (opens new window)
# 函数式组件
无状态,无生命周期,无实例
函数式组件只是函数,所以渲染开销也低很多
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
模板语法:
<template functional>
</template>
# 总结
# render 函数
# 优点
- 将js发挥到极致,逻辑性比较强,适合复杂的组件封装
- 性能好
- 灵活,比如可直接传一个函数到子组件中渲染内容
# 缺点
- 可读性不高
- 代码块可能会比较分散
# template
# 优点
- 可读性高,一目了然,代码好管理
- 有更多的语法糖,如指令、事件修饰符等
# 缺点
- 性能不够好(非单文件组件中)
- 编写复杂组件时代码,模板较大,不好管理
# 性能
函数式组件 > render 函数 ≈ 单文件组件 template > 字符串 template > 选择器指定的 template