1、非父子组件传值的问题
1、Vue是一种轻量级的视图层的框架,那么遇到复杂的非父组件传值的问题我们有两种解决方法:总线机制和vuex。
1.1、总线机制
1、总线也可以叫做(Bus/总线/发布订阅模式/观察者模式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>总线</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child content = "Dell"></child>
<child content = "Lee"></child>
</div>
<script>
Vue.prototype.bus = new Vue();
//1、在Vue的原型上挂载一个bus的属性,这个属性就是个Vue的实例
//1、也就是说所有之后创建的Vue的实例上的bus属性都指向同一个Vue实例,这个实例就是我们上面new出来的那个
Vue.component('child', {
props:{
content: String,
},
data: function() {
return {
selfContent: this.content,
}
},
template:"<div @click='handleClick'>{{selfContent}}</div>",
methods: {
handleClick: function() {
this.bus.$emit('change',this.selfContent);
//2、通过这个bus属性(实际就是每个Vue实例上的bus属性指向的同一个Vue实例)去触发事件
}
},
mounted: function() {//3、在mounted这个生命周期钩子里面
var this_ = this; //4、保存this
this.bus.$on('change',function(msg) {//5、监听触发的事件
this_.selfContent = msg;
})
}
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>
2、这段代码如果和以前对比,不难发现:以前我们说的是子组件向父组件去传递参数,父组件去监听参数,实际都是同一个Vue实例整体,自己触发事件,自己监听。所以总线也可以自己理解,不同的组件之间只要共同拥有同一个Vue实例不就行了?所以不同组件都是Vue实例,在原型最上层去挂载一个属性,这个属性指向同一个Vue实例,这样就实现了所有Vue实例都共同拥有一个公共的Vue实例,通过这个Vue实例去触发去监听。
2、插槽
2.1、普通插槽
1、插槽实际上是父组件向子组件传递DOM元素的一种方式。DOM元素可以是任何模板代码,也可以是其他自定义的组件,但是假如在子组件的模板当中没有solt元素,则父组件传入的任何DOM元素都会被抛弃。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>插槽</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<child>
<p>Dell</p> <!--1、父组件向子组件传递了DOM元素-->
</child>
</div>
<script>
Vue.component('child',{//2、在子组件中通过slot的方式来接受DOM元素
template: `<div>
<slot></slot>
<p>hello</p>
</div>
`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>
2、slot语法还可以设置默认值,也就是说在slot标签里面写默认的DOM元素,当父组件没有传递DOM元素的时候,我们使用slot里面的默认DOM,当有父组件传递来的DOM的时候,我们就使用父组件传来的DOM,默认值就不起作用了。
<body>
<div id="root">
<child>
<!--1、父组件没有向子组件传递DOM元素-->
</child>
</div>
<script>
Vue.component('child',{//2、在子组件中通过slot的方式来显示默认的DOM元素
template: `<div>
<slot><h1>这里是默认值<h1></slot>
<p>hello</p>
</div>
`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
3、具名slot:当我们调整需求,我们的父组件向子组件传递了头部和尾部的DOM元素,需要在子组件中的不同位置显示出来:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>插槽</title>
<script src="./vue.js"></script>
</head>
<body>
<div id="root">
<body-content><!--1、父元素向子元素传递了两个不同的DOM元素-->
<div class = "header" slot = "header">header</div><!--2、通个slot属性给不同的DOM元素起名字-->
<div class = "footer" slot = "footer">footer</div>
</body-content>
</div>
<script>
Vue.component('body-content',{//3、在子组件中的模板中使用name属性去接收父组件中传来的不同的DOM元素
template: `<div>
<slot name = "header"></slot>
<p>hello</p>
<slot name = "footer"></slot>
</div>
`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
</html>
2.2、作用域插槽
1、作用域插槽使用场景:当子组件做循环,或者子组件的DOM的格式样式需要由父组件传入的时候,就会使用到作用域插槽。
2、首先我们来看一下普通的这种循环一个数组的方式:这种方式数组的元素的显示方式和格式都是由子组件定义好的,不管谁来调用这个子组件都只能使用这一种形式,但是我们有需求,不同的地方调用,我们可能需要不同格式的显示方式,那么这种方式我们需要父组件去告诉子组件你使用什么样的格式,这里就使用到了插槽。
<body>
<div id="root">
<child></child>
</div>
<script>
Vue.component('child',{
data: function(){
return {
list: [1,2,3,4]
}
},
template:`<div>
<ul>
<li v-for="item of list">{{item}}</li>
</ul>
</div>
`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
3、使用作用域插槽,在父组件的slot-scope也可以使用解构的写法:
<body>
<div id="root">
<child>
<!--2、父组件拿到数据,使用slot-scope接受,并使用template(固定格式)告诉子组件显示格式(或者显示的模板)-->
<template slot-scope="props">
<li>{{props.item}}</li>
</template>
</child>
</div>
<script>
Vue.component('child',{
data: function(){
return {
list: [1,2,3,4]
}
},
//1、子组件拿到数据的每一项,放在item中传给父组件
template:`<div>
<ul>
<slot v-for="item of list" :item=item></slot>
</ul>
</div>
`
})
var vm = new Vue({
el: "#root",
})
</script>
</body>
3、动态组件
3.1动态组件和v-once(静态)
1、在Vue当中,有一个component的标签,称为动态组件,可以根据属性is的不同值来切换不同的组件的显示:
2、在我们下面这个例子中,在点击button的时候,切换child-one组件和child-two组件的过程,其实是销毁当前显示的DOM,然后再创建即将显示的DOM,所以其实比较浪费性能,使用v-once指令后,child-one和child-two这两个组件第一次被渲染的时候,就被放在内存中了,下次用的时候就直接从内存中拿。所以通过v-once指令可以提高静态内容的展示效率。
<body>
<div id="root">
<component :is="type"></component><!--1、component来显示动态组件,is属性决定显示哪个组件-->
<button @click="handleBtnClick">change</button>
</div>
<script>
Vue.component('child-one',{
template: `<div v-once>chlid-one</div>`,
})
Vue.component('child-two',{
template: `<div v-once>child-two</div>`,
})
var vm = new Vue({
el: "#root",
data: {
type: 'child-one',
},
methods: {
handleBtnClick: function() {
this.type = this.type === 'child-one' ? 'child-two' : 'child-one';
}
}
})
</script>
</body>
3.2、动态组件和keep-alive(动态)
1、我们之前曾经在一个多标签的界面中使用 is
特性来切换不同的组件
<component v-bind:is="currentTabComponent"></component>
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,
你每次切换新组件的时候,Vue 都创建了一个新的 currentTabComponent
实例。重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive>
元素将其动态组件包裹起来。
<!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive>