7.5高级组件用法
本节会介绍组件的一些高级用法,这些用法在实际业务中不是很常用,但在独立组件开发时可能会用到。如果你感觉以上内容已经足够完成你的业务开发,你可以跳过本节;如果你想继续探索Vue组件的奥秘,读完本节会对你有很多的启发。
7.5.1 递归组件
组件在它的模板内可以递归的调用自己,只要给组件设置name的选项就可以了。示例代码如下:
<div id="app">
<child-component :count="1"></child-component>
</div>
<script>
Vue.component('child-component',{
name:'child-component',
props:{
count:{
type:Number,
default:1
}
},
template:'\
<div class="child">\
<child-component :count="count+1" v-if="count<3"></child-component>\
</div>',
});
var app =new Vue({
el:'#app'
})
</script>
设置name后,在组件模板内就可以递归使用了,不过需要注意的是,必须给定一个条件来限制递归数量,否则会抛出错误:max stack size exceeded.
组件递归使用可以开发一些具有未知层级关系的独立组件,比如级联选择器(省市区)和树形控件(mytudolist)等。实战篇中,会详细介绍级联选择器的实现。
7.5.2 内联模板
组件的模板一般都是在template选项内定义的,Vue提供了一个内联模板的功能,在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当作模板,而不是把它当内容分发,这让模板更灵活。示例代码如下:
<div id="app">
<child-component inline-template>
<div>
<h2>在父组件中定义子组件的模板</h2>
<p>{{ message}}</p>
<p>{{ msg }}</p>
</div>
</child-component>
</div>
<script>
Vue.component('child-component',{
data:function(){
return {
msg:'在子组件声明的数据'
}
}
});
var app = new Vue({
el:'#app',
data:{
message:'在父组件中声明的数据'
}
})
</script>
渲染后的结果为:
<div id="app">
<div>
<h2>在自父组件中定义子组件的模板</h2>
<p>在父组件声明的数据</p>
<p>在子组件声明的数据</p>
</div>
</div>
在父组件中声明的数据message和子组件中声明的数据msg,两个都可以渲染(如果同名,优先使用子组件的数据)。这反而是内嵌模板的缺点,就是作用域比较难理解,如果不是非常特殊的场景,建议不要轻易使用内联模板。
7.5.3 动态模板
Vue.js提供了一个特殊的元素<component>用来动态地挂载不同的组件,使用is特性来选择要挂载的组件。示例代码如下:
<div id="app">
<component :is="currentView"></component>
<button @click="handleChangeView('A')">切换到A</button>
<button @click="handleChangeView('B‘)">切换到B</button>
< button @click="handleChangeView('C')">切换到C</button>
</div>
<script>
var app = new Vue({
el:'#app',
components:{
comA:{ template:'<div>组件A</div>'},
comB:{ template:'<div>组件B</div>'},
comC:{ template:'<div>组件C</div>'}
},
data:{
currentView:comA
},
methods:{
handleChangeView:function(component){
this.currentView = 'com'+component;
}
}
})
</script>
动态地改变currenView的值就可以动态挂载组件了。也可以直接绑定在组件对象上:
<div id="app">
<component :is="currentView"></component>
</div>
<script>
var Home={
template:' <p>Welcome Home</p>'
};
var app = new Vue({
el:'#app',
data:{
currentView:Home
}
})
</script>
7.5.4 异步组件
当你的工程足够大,使用的组件足够多时,是时候考虑下性能问题了,因为一开始把所有的组件都加载是必要的一笔开销。还在Vue.js允许将组件定义为一个工厂函数,动态地解析组件。Vue.js只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。示例代码如下:
<div is="app">
<child-component></child-component>
</div>
<script>
Vue.component('child-component',function(resolve,reject){
window.setTimeout(function(){
resolve({
template:'<div>我是异步渲染</div>'
});
},2000)
});
var app = new Vue({
el:'#app'
})
</script>
工厂函数接受一个resolve回调,在收到从服务器下载的组件定义时调用。也可以调用reject(reason)指示加载失败。这里setTimeout只是为了演示异步,具体的下载逻辑可以自己决定,比如把组件配置写成一个对象配置,通过Ajax来请求,然后调用resolve传入配置选项。
在进阶篇里,我们还会介绍主流的打包编译工具webpack和.vue单文件的用法,更优雅地实现异步组件(路由)。
7.6 其他
7.6.1 $nextTick
我们先来看这样一个场景:有一个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 app =new Vue({
el:'#app',
data:{
showDiv:false
},
methods:{
getText:function(){
this.showDiv =true;
var text =document.getElementById('div').innerHTML;
console.log(text);
}
}
})
</script>
这段代码并不难理解,但是运行后在控制台会抛出一个错误:Cannot read property 'innerHTML' of null,意思就是获取不到div元素。这里就涉及Vue一个重要概念:异步更新队列。
Vue在观察到数据变化时并不是直接去除重复数据,从而避免不必要的计算和DOM操作。然后,在下一个事件循环tick中,Vue刷新队列并执行实际(已去重的)工作 。所以如果你用一个for循环来动态改变数据100次,其实它只会应用最后一次改变,如果没有这种机制,DOM就要重绘100次,这固然是一个很大的开销。
Vue会根据当前浏览器环境优先使用原生的Promise.then和MutationObsever,如果都不支持,就会采用setTimeout代替。
知道了Vue异步更新DOM的原理,上面示例的报错也就不难理解了。事实上,在执行this.showDiv=true时,div仍然还是没有被创建出来,直到下一个Vue事件循环时,才开始创建。$nextTick就是用来知道什么时候DOM更新完成的,所以上面的示例代码修改为:
…………
this.showDiv=true;
this.$nextTick (function(){
var text =document.getElementById('div').innerHTML;
console.log(text);
});
…………
这时再点击按钮,控制台就打印出div的内容”这是一段文本“了。
理论上,我们不应该去主动操作DOM,因为Vue的核心思想就是数据驱动DOM,但在很多业务里,我们避免不了会用一些第三方库,比如swiper等,这些基于原生JavaScript的库都有创建和更新及销毁的完整生命周期,与Vue配合使用时,就要利用好$nextTick..