- Virtual Dom
- Render函数例子
- createElement
- 事件修饰符和按键修饰符
Virtual Dom 虚拟DOM节点
一般来说,我们的组件的模板都是写在template元素里的,当然也可以写在其他的元素里。不管什么元素,都会被Vue解析成Virtual Dom。
更新DOM的操作要比执行JS代码开销大很多,所以Vue在更新页面的时候,实际上先对Virtual
Dom进行计算,看是否有变化,然后渲染有变化的DOM部分,而不是整个页面重绘,这样的单页面应用要比从服务器获取静态页面速度好很多。
在自定义指令的例子里,打印vnode参数在控制台里看到VNode对象,这个就是Vue的虚拟DOM节点对象。这个对象用一些属性封装了DOM节点的一些内容
其中的一堆属性里比较重要的有两个:
tag,当前节点的标签名称,像例子里指令绑定的是div元素,就会是div字符串。
data,当前节点的数据对象,Vue源码中对应的类叫做VNodeData类。
children,子节点数组,其中元素也全部是VNode类型
text,当前节点的文本,一般文本和注释节点有此属性
elm,即element,当前虚拟节点对应的真实DOM节点
ns,节点的命名空间
content,编译作用域
functionalContext,函数化组件的作用域
key,节点的key属性,用于唯一标识,方便更新DOM
componentOptions,创建组件实例用到的设置
child,当前节点对应的组件实例
parent,组件的占位节点
raw,原始HTML
istatic,是否是静态节点
isRootlnsert,是否作为根节点插入
isComment,是否是注释节点
isCloned,是否是克隆节点
isOnce,当前节点是否存在V-once指令
例子里的data点开之后,由于这是一个指令,能看到其中有一个directives: Array(1),其中存放着指令的参数,表达式等一系列内容。
Vnode可以分为如下几类:
TextVNode,文本节点
ElementVNode,普通元素节点
ComponentVNode,组件节点
EmptyVNode,无内容的注释节点
CloneVNode,克隆节点,可以是上边四种的任意一个,属性isCloned为true
通过理论知识可以知道,Vue实际上是把被绑定的所有DOM节点,用自己的方式在后台写了一套对应的虚拟节点。通过操作虚拟节点来更新实际DOM页面,实现前端也有控制器和view的功能。
Render函数的例子
render实际上也是Vue实例的一个属性,与用模板渲染不同,render就是进行渲染的函数,可以把要渲染的内容都使用这个函数来完成,先看一个例子。
这个例子想编写一个组件,传入级别和标题,插槽插入显示的内容,自动生成对应级别的H元素包含的链接,点击之后可以自动添加锚点。
标准的做法,就是编写一个组件,传入level参数用于控制显示哪个级别的标题,然后留个插槽供父组件插入内容,还需要一个文本属性控制锚点的内容:
<div id="app">
<anchor :level="2" title="自定义标题">标题</anchor>
</div>
<script type="text/x-template" id="mytemp">
<div>
<h1 v-if="level === 1">
<a :href="'#' + title"><slot></slot></a>
</h1>
<h2 v-if="level === 2">
<a :href="'#' + title"><slot></slot></a>
</h2>
<h3 v-if="level === 3">
<a :href="'#' + title"><slot></slot></a>
</h3>
<h4 v-if="level === 4">
<a :href="'#' + title"><slot></slot></a>
</h4>
<h5 v-if="level === 5">
<a :href="'#' + title"><slot></slot></a>
</h5>
<h6 v-if="level === 6">
<a :href="'#' + title"><slot></slot></a>
</h6>
</div>
</script>
<script>
Vue.component('anchor',{
template:"#mytemp",
props:{
level:{
type:Number
},
title:{
type:String
}
}
});
var app = new Vue({
el: "#app"
})
</script>
仔细观察一下,其实组件模板的大部分内容都相同,只是v-if控制的不同,如果能直接能拼出一段HTML字符串,那变量其实只有level和title,剩下的都是重复内容。这个时候就可以使用render函数,不再用模板。
修改后的全部代码是:
<div id="app">
<anchor :level="4" title="saner">ttile</anchor>
</div>
<script>
Vue.component('anchor',{
props:{
level:{
type:Number
},
title:{
type:String
}
},
render:function (createElement) {
return createElement(
'h' + this.level, [
createElement(
'a', {
domProps: {
href: '#' + this.title
}
},
this.$slots.default
)
]
);
}
});
var app = new Vue({
el: "#app"
})
</script>
虽然还没有学,但是能看出来,Render函数内置的createElement参数是其中的关键,可以通过字符串和其他的createElement返回的结果进行嵌套,最后生成一段HTML代码。下边就来具体看createElement。
createElement的参数
在开始之前,首先要想一下,要渲染一个HTML元素,需要一些什么内容。很显然,首先必须要知道渲染一个什么元素,可能是标准的HTML元素,也可能是Vue的自定义组件的元素。之后很显然还需要渲染这个元素的属性,可能是HTML标准属性,也可能是指令。最后很显然,还需要渲染标签中的内容,内容可能是文本,可能是另外一个元素,可能是表达式。
想了这些,再看createElement函数就会比较清晰了。由于这个方法最后是拼出来一段HTML模板,很显然必须知道上边的三个内容才能渲染出来模板。
createElement有三个参数,依次是:
{String | Object | Function),一个HTML标签名称字符串,或者组件,或者一个函数。是必须传入的参数。
{AttrObject),属性数据对象,后边会详细解释,可选参数。
{String | Array),子节点数组,可选参数。
很显然,这个就与上边一一对应。这里要解释一下第二个属性数据对象,里边其实就封装了渲染为元素属性的各个数据。来看一个渲染一个简单组件的例子:
<div id="app">
<comp>saner</comp>
</div>
<script>
Vue.directive('mydir',{
inserted:function (el,binding) {
console.log("bind");
console.log("value的值是 " + binding.expression);
}
});
Vue.component('comp', {
render: function (createElement) {
return createElement(
'h1', {
class: {
title: true,
ff: false
},
style: {
fontSize: "30px",
textAlign: "center"
},
attrs: {
id: "test"
},
domProps: {
},
props:{
value:
},
on:{
click: this.clickHandler
},
nativeOn:{
click: this.nativeClickHandler
},
directives:[
{
name:'mydir',
value: 'true',
expression:'true',
arg:'foo',
modifiers:{
bar:true
}
}
],
scopedSlots: {
default: props => createElement('span', props.text)
},
slot: 'name-of-slot',
key: 'myKey',
ref: 'myRef',
refInFor: true
},this.$slots.default
);
},
methods:{
clickHandler:function () {
console.log("clicked")
},
nativeClickHandler: function () {
console.log("native clicked")
}
}
});
var app = new Vue({
el: "#app"
})
</script>
这里的第一个参数是h1,表示是一个h1标题元素,第二个参数就是属性对象,里边包含了所有可以作用在元素上的渲染内容:
class,类似v-bind:class的属性写法,控制CSS类
style,类似v-bind:style的属性写法,控制内联样式
attrs,标准HTML属性
domProps,DOM节点属性,比如可以设置innerHTML,不过这里要渲染插槽,所以没有插入具体内容。
props,定义组件的props。不过这里我定义了,但是传入似乎没效果。不过由于是组件,可以直接在外边定义。现在还没搞清楚
on,监听事件,可以监听$emit抛出的自定义事件。但是这里不支持直接使用修饰符,必须在事件处理方法里判断。
nativeOn,监听原生事件
directives,自定义指令的相关内容,写了这些就等于在元素上使用了自定义指令。
scopedSlots,作用域插槽,先这么了解一下
slot,如果当前组件还是其他组件的子组件,需要给插槽指定名称。
key,元素的key值,用于唯一标识
ref,元素的ref属性。如果多个元素都使用了同一个ref,$refs.myRef会成为一个数组。
refInFor,这个官方文档也没解释,不知道了。
这里还在第三个参数中写了一个标准的slot插槽元素,这个会成为h1元素的子元素。这个例子渲染完之后的HTML元素是:
<div id="app">
<h1 id="test" class="title" style="font-size: 30px; text-align: center;">saner</h1>、
</div>
在控制台里则打印了自定义指令的内容,点击标题会发现事件也正常得到处理。
所以creatElement的作用,就是渲染出一段像模板一样可以正常供Vue工作的页面。实际上creatElement返回的是一个Vnode对象,不是真正的DOM元素。Vue实例会完成实际渲染的工作。
在组件树中,所有的组件都必须唯一,不能直接简单复用组件,那样只会渲染出一个组件。
但是在Vue的文档里,提到如下的渲染函数不合法:
render: function (createElement) {
var myParagraphVNode = createElement('p', 'hi')
return createElement('div', [
// 错误 - 重复的 VNode
myParagraphVNode, myParagraphVNode
])
}
但是奇怪的是我竟然可以渲染出两个元素。。。。难道Vue 2.6.10有所改动?
另外这里还展示了render函数可以不传第二个参数,直接传第一个和数组参数,也是可以的。
事件修饰符和按键修饰符
在render里无法使用修饰符,所以必须要通过事件处理函数的event属性来调用。一些事件和按键修饰符对应关系如下:
| 修饰符 |
调用方法 |
.stop |
event.stopPropagation() |
.prevent |
event.preventDefault() |
.self |
if (event.target != event.currentTarget) return |
.enter.13 |
if(event.keyCode != 13) return,判断按钮 |
.ctrl 、.alt 、.shift 、.meta |
if (!event.ctrlKey) return 根据需要替换ctrlKey 为altK、shiftKey 或metaKey,也是用于判断按钮 |
对于.capture,.once可以使用特殊前缀,写在on的事件名称字符串里即可:
| 修饰符 |
前缀 |
.capture |
! |
.once |
~ |
.capture.once 或.once.capture |
~! |
现在基本上就把Vue的内容都学完了,后边是Webpack和Vue的其他生态了。想要做一个SPA,还真的没那么简单哦。