1️⃣ render 函数的基本使用
1️⃣ 节点、树、以及虚拟 DOM
在深入渲染函数之前,先来了解一些浏览器的工作原理。例如,下面这段 HTML:
<div><h1>My title</h1>Some text content<!-- TODO: Add tagline --></div>
当浏览器读到这些代码时,它会建立一个DOM 节点树 来保持追踪所有内容,如同你会画一张家谱树来追踪家庭成员的发展一样。
以上 HTML 对应的 DOM 节点树如下图所示:
每个元素都是一个节点。每段文字也是一个节点。甚至注释也都是节点。一个节点就是页面的一个部分。就像家谱树一样,每个节点都可以有孩子节点。
高效地更新所有这些节点是比较困难的,不过幸运的是,我们不需要手动完成这个工作。只需要告诉 Vue 希望页面上的 HTML 是什么,例如在模板中:
<h1>{{ blogTitle }}</h1>
或者是在一个渲染函数中:
render: function (createElement) {return createElement('h1', this.blogTitle)}
在这两种情况下,Vue 都会自动保持页面的更新,即便 blogTitle 发生了改变。
2️⃣ 虚拟 DOM
Vue 通过建立一个虚拟 DOM来追踪自己要如何改变真实 DOM。例如:
return createElement("h1", this.blogTitle);
createElement 会返回什么呢?
1. 它不会返回一个实际的 DOM 元素。更准确的名字可能是:createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括其子节点的描述信息。我们把这样的节点描述为“虚拟节点(virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
1️⃣ createElement 参数
2️⃣ createElement 接收的参数
createElement(标签名(必需), 与模板中属性对应的数据对象(可选), 子级虚拟节点(可选));
createElement(// {String | Object | Function}// 一个 HTML 标签名或者组件选项对象// resolve 了上述任何一种的一个 async 函数, 必填.'div',// {Object}// 一个与模板中 attribute 对应的数据对象。可选。{// (详情见深入数据对象)},// {String | Array}// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,// 也可以使用字符串来生成“文本虚拟节点”。可选。['文本虚拟节点',createElement('h1', '生成一条 h1 标签包含的文本'),createElement(MyComponent, {props: {someProp: 'foobar'}})])
2️⃣ 深入数据对象
有一点要注意:正如 v-bind:class 和 v-bind:style 在模板语法中会被特别对待一样,它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML attribute,也允许绑定如 innerHTML 这样的 DOM property (这会覆盖 v-html 指令)。
{// 与 `v-bind:class` 的 API 相同,接受一个字符串、对象或字符串和对象组成的数组class: {foo: true,bar: false},// 与 `v-bind:style` 的 API 相同,接受一个字符串、对象,或对象组成的数组style: {color: 'red',fontSize: '14px',},// 普通的 HTML attributeattrs: {id: 'foo',},// 组件 propprops: {myProp: 'bar',},// DOM属性domProps: {innerHTML: 'baz',},// 事件监听器,不支持如“v-on:keyup.enter”这样的修饰器on: {click: this.onClick},// 仅用于组件,用于监听原生事件,而不是组件内部使用 vm.$emit 触发的事件。nativeOn: {click: this.nativeClickHandler},// 自定义指令。注意,无法对 `binding` 中的 `oldValue`赋值,因为 Vue 已经自动为你进行了同步。directives: [{name: 'my-custom-directive',value: '2',expression: '1 + 1',arg: 'foo',modifiers: {bar: true}}],// 其它特殊顶层属性key: 'myKey',ref: 'myRef',// 如果在渲染函数中给多个元素都应用了相同的 ref 名,那么 `$refs.myRef` 会变成一个数组。refInFor: true// 作用域插槽,格式为:{ name: props => VNode | Array<VNode> }// 如果组件是其它组件的子组件,需为插槽指定名称slot: 'name-of-slot',scopedSlots: {default: props => createElement('span', props.text)},}
1️⃣ 使用 JavaScript 代替模板功能
2️⃣ v-if 和 v-for
只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for:
这些都可以在渲染函数中用 JavaScript 的 if/else 和 map 来重写:
2️⃣ v-model
渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑:
props: ['value'],render: function (createElement) {var self = thisreturn createElement('input', {domProps: {value: self.value},on: {input: function (event) {self.$emit('input', event.target.value)}}})}
这就是深入底层的代价,但与 v-model 相比,这可以让你更好地控制交互细节。
2️⃣ 事件 & 按键修饰符
对于 .passive、.capture 和 .once 这些事件修饰符,Vue 提供了相应的前缀可以用于 on:
| 修饰符 | 前缀 |
|---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once或 .once.capture |
~! |
例如:
render(createElement) {return createElement("p",{on: {"!click": function () {},"~keyup": function () {},"~!mouseover": function () {},},},"点击");},
对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:
| 修饰符 | 处理函数中的等价操作 |
|---|---|
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target !== event.currentTarget) return |
按键:.enter, .13 |
if (event.keyCode !== 13) return(对于别的按键修饰符来说,可将 13改为另一个按键码 ) |
修饰键:.ctrl, .alt, .shift, .meta |
if (!event.ctrlKey) return(将 ctrlKey分别修改为 altKey、 shiftKey或者 metaKey) |
这里是一个使用所有修饰符的例子:
on: {keyup: function (event) {// 如果触发事件的元素不是事件绑定的元素// 则返回if (event.target !== event.currentTarget) return// 如果按下去的不是 enter 键或者// 没有同时按下 shift 键// 则返回if (!event.shiftKey || event.keyCode !== 13) return// 阻止 事件冒泡event.stopPropagation()// 阻止该元素默认的 keyup 事件event.preventDefault()// ...}}
1️⃣ render 使用插槽
2️⃣ 默认插槽
你可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:
2️⃣ 具名插槽
2️⃣ 作用域插槽
也可以通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数:
如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 字段:
render: function (createElement) {// `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`return createElement('div', [createElement('child', {// 在数据对象中传递 `scopedSlots`// 格式为 { name: props => VNode | Array<VNode> }scopedSlots: {default: function (props) {return createElement('span', props.text)}}})])}
