我们先来看这样一个场景: 有一个div
,默认用v-if
将它隐藏,点击一个按钮后,改变v-if
的 值,让它显示出来,同时拿到这个div
的文本内容。如果v-if
的值是false
,直接去获取div
的内容是获取不到的,因为此时div
还没有被创建出来,那么应该在点击按钮后,改变v-if
的值为true
,div
才会被创建,此时再去获取,示例代码如下:
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div的内容</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
showDiv: false
},
methods: {
getText: function() {
this.showDiv = true
var divText = document.getElementById('div').innerHTML
consloe.log(divText)
}
}
})
</script>
这段代码并不难理解,但是运行后在控制台会抛出一个错误: Cannot read property 'innerHTML' of null
,意思就是获取不到div
元素。这里就涉及Vue
一个重要的概念:异步更新队列。
Vue
在观察到数据变化时并不是直接更新DOM
,而是开启一个队列,并缓冲在同一事件循环 中发生的所有数据改变。在缓冲时会去除重复数据,从而避免不必要的计算和DOM
操作。然后, 在下一个事件循环tick
中,Vue
刷新队列井执行实际(己去重的)工作。所以如果你用一个for
循 环来动态改变数据100
次,其实它只会应用最后一次改变,如果没有这种机制,DOM
就要重绘100
次,这固然是一个很大的开销
Vue
会根据当前浏览器环境优先使用原生的Promise.then
和MutationObserver
,如果都不支持, 就会采用setTimeout
代替。 知道了Vue
异步更新DOM
的原理,上面示例的报错也就不难理解了 。 事实上,在执行this.showDiv = true
同时,div
仍然还是没有被创建出来,直到下一个Vue
事件循环时,才开始创建。$nextTick
就是用来知道什么时候DOM
更新完成的,所以上面的示例代码需要修改为:
<div id="app">
<div id="div" v-if="showDiv">这是一段文本</div>
<button @click="getText">获取div的内容</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
showDiv: false
},
methods: {
getText: function() {
this.$nextTick(function() {
this.showDiv = true
var divText = document.getElementById('div').innerHTML
consloe.log(divText)
})
}
}
})
</script>
这时再点击按钮,控制台就打印出div
的内容"这是一段文本"了。
理论上,我们应该不用去主动操作DOM
,因为Vue
的核心思想就是数据驱动 DOM,但在很 多业务里,我们避免不了会使用一些第三方库,比如 popper、 swiper 等,这些基于原生JavaScript
的库都有创建和更新及销毁的完整生命周 期,与Vue
配合使用时,就要利用好$nextTick
。
如果你没有使用webpack
、gulp
等工具,试想一下你的组件template
的内容很冗长、复杂,如果都在JavaScript
里拼接字符串,效率是很低的,因为不能像写HTML
那样舒服。Vue
提供了另 一种定义模板的方式,在<script>
标签里使用text/x-template
类型,井且指定一个id
, 将这个id
赋 给template
。示例代码如下:
<div id="app">
<my-component></my-component>
<script type="x-template" id="my-component">
<div>这是组件的内容</div>
</script>
</div>
<script>
Vue.componennt('my-component', {
template: '#my-component'
})
var vm = new Vue({
el: '#app'
})
</script>
在<script>
标签里,你可以愉快地写HTML
代码,不用考虑换行等问题。 很多刚接触Vue
开发的新手会非常喜欢这个功能,因为用它,再加上组件知识,就可以很轻松地完成交互相对复杂的页面和应用了。如果再配合一些构建工具(gulp
)组织好代码结构,开发一些中小型产品是没有问题的。不过,Vue
的初衷并不是滥用它,因为它将模板和组件的其他定义隔离了。在后面的文章中,我们会介绍如何使用webpack
来编译.vue
的单文件,从而优雅地解决HTML
书写的问题。
我们现在所创建的实例都是通过new Vue()
的形式创建出来的。在一些非常特殊的情况下,我 们需要动态地去创建Vue
实例,Vue
提供了Vue.extend
和$mount
两个方法来手动挂载一个实例。Vue.extend
是基础Vue
构造器,创建一个“子类”,参数是一个包含组件选项的对象。 如果Vue
实例在实例化时没有收到el
选项,它就处于“未挂载”状态,没有关联的DOM
元 素。可以使用$mount()
手动地挂载一个未挂载的实例。这个方法返回实例自身,因而可以链式调用其他实例方法。示例代码如下:
<div id="mount-div">
</div>
<script>
var MyComponent = Vue.extend({
template: '<div>hello: {{name}}</div>',
data() {
return {
name: 'Aresn'
}
}
})
new MyComponent().$mount('#mount-div')
</script>
运行后,id
为mount-div
的div
元素会被替换为组件MyComponent
的template
的内容:<div>hello: {{name}}</div>
除了这种写法外,以下两种写法也是可以的
new MyComponent().$mount('#mount-div')
//同上
new Mycomponent({
el: '#mount-div'
})
// 或者在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElelmentById('#mount-div').appendChild(component.$el)
手动挂载实例(组件)是一种比较极端的高级用法,在业务中几乎用不到,只在开发一些复 杂的独立组件时可能会使用,所以只做了解就好。(书上是这样写的)