简述

今天来到新的一篇,组件篇,从webpack那篇博客我们知道,现在前端开发的页面都是单页面富应用,即SPA页面
单页面富应用指的是:前端只需要写一个页面,使用vue去动态生成Dom元素,并更新到页面中,从而达到动态交互的效果。
vue生成的Dom元素,就是生成一个个组件,当页面需要跳转到其他页面时,其实就是将对应的组件,渲染到这个页面的相应位置。
下面画了一张图,表明组件的含义

说完组件概念,下面来看看组件篇中,具体应该学习哪些内容。

组件的使用步骤

分为创建组件,注册组件,使用组件,下面是个简单的案例:

<div id="app">
    //3,vue实例(根组件)管理的div中使用组件
    <mycon></mycon>
</div>
<script>
    //1,创建组件mycon
    let mycon = Vue.extend({
        template:`
            <div>
            <h3>万一爱明月</h3>
            </div>
		`
    });
    //2,注册组件
    Vue.component('mycon',mycon);
	
	//全局组件的创建和注册必须在vue实例之前
    let app = new Vue({
        el: '#app',
        data: {
            message:'加油!'
        },
    });
</script>

/*注意:上面创建组件和注册组件可以简写成下面的方式*/
Vue.component("mycon",{
    template:`
        <div>
        <h3>万二爱明月</h3>
        </div>
	`
});

全局组件和局部组件

像上面案例中,使用Vue.component方法注册的组件就是全局组件,全局组件可以在多个vue实例中使用
需要注意的是,全局组件的创建和注册必须在vue实例之前,不然会报错。
而局部组件指的是,将组件挂载到其他组件之下,使用组件时,只能在被挂载组件的模板中使用
所以可知,全局组件既然能在所有vue实例中使用,说明全局组件被挂载到了每个vue实例之中。
下面来看看局部组件:

<div id="app">
    //使用组件 
    <wanyi></wanyi>
</div>

//1,创建组件mycon
let mycon = Vue.extend({
    template:`
        <div>
        <h3>万一爱明月</h3>
        </div>
    `
});
let app = new Vue({
    el: '#app',
    data: {
        message:'加油!'
    },
    components: {
        //2,注册组件
        //wanyi是组件标签名,mycon是创建的组件名
        wanyi: mycon
    }
});

/*
	上面就是局部组件的创建,注册,使用,mycon组件被挂载到指定的vue实例的components中,
	只能在该vue实例中使用组件,即wanyi标签,下面是局部组件的另一种写法,效果是一样的
*/
let app = new Vue({
    el: '#app',
    data: {
        message:'加油!'
    },
    components: {
        //wanyi是组件标签名,使用时也是直接使用wanyi标签,该组件没有组件名,匿名组件
        wanyi: {
            template:`
                <div>
                <h3>万一爱明月</h3>
                </div>
            `
        	}
    }
});

/*
	使用组件有三点需要注意的是:
    	1,注册组件时的组件标签名,除了第一个字母可以大写外,其他必须小写,如果组件名有大写
			那么组件标签名推荐使用-插入到其中,并把大写改成小写,例如,如果组件名为HelloWorld
			那么组件标签名应为:hello-world,使用时<hello-world></hello-world>
		2,在创建组件时,指定template时,模板的值是字符串,字符串中需要有个div标签包裹住。
		3,全局组件的创建和注册,必须在vue实例创建之前
*/

父子组件

当一个组件在另一个组件中挂载后,就会形成父子关系,被挂载的组件为父组件,挂载的组件为子组件
子组件只能在父组件的template模板中使用,下面是一个父子组件的案例:

<div id="app">
    <h3>{{message}}</h3>
    <mycon2></mycon2>
</div>
<script>
    //组件1,子组件
    let mycon1 = Vue.extend({
        template:`	//模板字符串,多行字符串
            <div>
            	<h3>万一爱明月</h3>
            </div>
		`
    });
	//组件2,父组件
    let mycon2 = Vue.extend({
        template:`
            <div>
                <h3>万二爱明月</h3>
                <mycon1></mycon1>
            </div>
            `
        ,
        components:{
            mycon1:mycon1
        }
    });
    //vue实例,根组件
    let app = new Vue({
        el: '#app',
        data: {
            message:'加油!'
        },
        components: {
            mycon2:mycon2
        }
    });
</script>

组件模板分离方法

上面组件中的template模板中都是html代码,在js中写html代码显然不太好看,下面就是两种抽离模板中的html代码的方式

  • script标签方式,类型为text/x-template

    <div id="app">
        //使用全局组件
        <wanyi></wanyi>
    </div>
    
    //将wanyi组件中模板抽离出来
    <script type="text/x-template" id="wanyi">
        <div>
            <h3>万一爱明月</h3>
            <mycon1></mycon1>	//父组件的template中使用子组件
    	</div>
    </script>
    
    //创建组件,通过id,关联组件和script标签
    Vue.component('wanyi',{
        template:"#wanyi",	//绑定js,wanyi的template被抽离
        components:{
            mycon1: {	//mycon1的模板没有被抽离
                template:`
                    <div>
                        <h3>万二爱明月</h3>
                    </div>
    			`
            }
        }
    });
  • template标签的方式

    <template id="wanyi">
        <div>
        	<h3>万一爱明月</h3>
    		<mycon1></mycon1>
    	</div>
    </template>
    //绑定方式和上面一样的,不过是用template标签代替了script标签

为什么组件中的data是一个函数

组件如何动态获取数据?我们的vue实例可以看做一个root根组件,在vue实例中,data属性是一个对象,
我们可以在页面通过{{}}来获取data中的数据,但是在组件中,data属性是一个函数,这个函数返回一个对象,
在返回的对象中定义属性,我们的组件模板中才可以使用动态的值。这是组件与vue实例或者说根组件的差别。
下面是关于组件中动态获取数据的案例

/*
	这个被vue实例管理的div可以看做是vue的template模板,全局组件wanyi既然能在这里使用
	说明wanyi组件在注册的时候,已经挂载到了vue实例下,被作为子组件,既然全局组件能在每个vue实例下使用
	说明只要是全局组件,就会注册到所有vue实例中,是所有vue实例的子组件,不过通常项目中只有一个vue实例。
*/
<div id="app">
    <wanyi></wanyi>
</div>

<template id="wanyi">
    <div>
    	<h3>万一爱{{name}}</h3>	//组件中只能动态获取本组件的data数据
	</div>
</template>

<script>
    Vue.component('wanyi',{
        template:"#wanyi",
        //组件中的data是个函数,这是es6方法增强写法
        data(){
            //在该函数返回值(一个对象)中定义属性,才能在组件模板中使用变量
            return{
                name: "明月"
            }
        },
        components:{
        }
    });

    let app = new Vue({
        el: '#app',
        data: {
            message:'加油!'
        },
        components: {
        }
    });
</script>

重点来啦,看完上面的代码,想想为什么vue实例中data是一个对象,而组件中的data却是一个函数,在函数中返回一个对象呢?
因为我们使用组件的时候,可能会使用多个组件实例(组件标签),多个组件实例复用一个组件,到时候如果组件的data是一个对象,
则所有组件实例共用一个data对象就会发生数据紊乱,而通过data函数创建的对象,每次调用data函数的时候都会创建一个新的对象,
使得每个组件实例都有自己的data数据就不会发生数据紊乱,下面是一个简单的例子

<div id="app">
    <wanyi></wanyi>		//创建多个组件实例(组件标签),每个组件实例都有自己的name
    <wanyi></wanyi>		//这些组件实例的数据是隔离的
    <wanyi></wanyi>
</div>

<template id="wanyi">
    <h2>{{name}}</h2>
</template>

<script>
    Vue.component("wanyi",{
        template: '#wanyi',
        data(){				
            return {		//每个组件实例都有独立的对象,数据相互隔离
                name: '万一的编码时光'
            }
        }
    })

    let app = new Vue({
        el: '#app',
        data: {
            message:'加油!',
        }
    });
</script>

父子组件之间的通信

  • 父向子传递数据

    • 通过props,父组件向子组件传递数据
      在子组件中,使用选项props来声明需要从父级接收到的数据。props的值可以有两种方式接收数据:

      //方式一:字符串数组,数组中的字符串就是传递时子组件标签的属性,例如下面案例的wo和ni
      <div id="app">
          <wanyi :wo="me" :ni="you"></wanyi>	//子组件标签中绑定父组件data中数据
      </div>
      
      <template id="yue">
          <div>
          	{{wo}}{{ni}}	//和data数据一样直接使用
      	</div>
      </template>
      
      <script>
          const wanyi = {
              template:"#yue",
              props:['wo','ni'],	//props中wo和ni,就拿到了父组件中传递过来的数据
              data(){
                  return {};
              }
          }
          let app = new Vue({
              el: '#app',
              data: {
                  me:'万一',
                  you:'明月'
              },
              components: {	
                  wanyi	//es6属性的增强写法,相当于wanyi: wanyi
              }
          });
      </script>
      //方式二:对象形式接收数据,对象可以设置传递时的类型,也可以设置默认值等。
      //除了props接收属性不同,其他都相同
      props:{
          // wo:String
          wo:{
              type: String,	//表示数据的类型
              default: "我",  //默认值
              required: true	//表示该数据是否必须传入
          },
          ni:{
              type: String,
              default: "明月",
              required: true
          },
          shuiguo:{
              type: Array,
              default(){    //如果传递的是数组或者对象,default属性必须是一个函数
                  return ['我',"喜欢",'的','人']
              },
          }
      }
      let app = new Vue({
          el: '#app',
          data: {
              me:'万一',
              you:'明月',
              fruit:['苹果','香蕉','橘子','桃子']
          },
          components: {
              wanyi
          }
      });
      
      /*
      	注意:props中的变量,如wo,ni,shuiguo,都必须是小写,因为html标签解析不了大写,会将大写转成小写
      	如果你想要你的变量名有大写,你需要在子组件标签绑定属性时将大写字母变成小写字母并加上-分隔
      	例如:<wanyi :shui-guo="fruit"></wanyi>,这样你的props的变量可以为shuiGuo
      */
    • 通过在子组件标签上标记ref属性,拿到子组件实例,应该可以传递数据,仅仅猜测,尚未求证

  • 子向父传递数据

    • 通过props,子向父传递数据
      父组件先传递回调函数给子组件,然后子组件调用该函数,将数据作为参数,传递到父组件中。

    • 通过自定义事件,子组件向父组件传递数据,这里有两种方式,
      其一是在子组件标签上绑定自定义事件,并指定自己的回调函数,然后在子组件中通过$emit方法
      触发自定义函数并传递参数,从而回调父组件中的事件函数。下面是这种方式的案例:

      //这个vue管理的div也可以看做是根组件的模板template
      <div id="app">
          <!--
              以前如果是@click事件,不传参数的话,会默认传一个event对象
              现在子组件发射一个自定义事件,此时监听该事件不传参数,会默认将发射时带的参数传递给父组件中。
          -->
          <wanyi @get-count="getCount"></wanyi>
      </div>
      
      //子组件的模板
      <template id="yue">
          <div>
              <!--遍历子组件中的数据,并监听点击事件-->
              <h3 v-for="item in fruit" @click="getClick(item)">{{item}}</h3>
      	</div>
      </template>
      
      <script>
      //创建子组件
      const wanyi = {
          template:"#yue",
          //子组件中的data必须是一个函数,在函数返回的对象中定义组件的变量
          data(){
              return {
                  count:0,
                  fruit:['苹果','香蕉','橘子','桃子']
              }
          },
          methods:{
              getClick(item){
                  console.log(item);
                  this.count++;
                  //自定义事件的名字,自定义事件的参数,该参数会传递到父组件的方法参数中
                  //这里写驼峰在脚手架(vue-cli)中可以使用,这里使用vue原生的东西,所以不能有大写。
                  this.$emit("get-count",this.count);
              }
          }
      }
      
      //父组件
      let app = new Vue({
          el: '#app',
          data: {
              message:'加油!'
          },
          //注册子组件,ES6增强属性的方式
          components:{
              wanyi
          },
          methods: {
              //当触发子组件的事件后,执行父组件中的方法
              getCount(count){
                  console.log("被点击:"+count+"次");
              }
          }
      });
      </script>

      其二是通过在子组件标签上标记ref属性,拿到子组件实例,通过$on为子组件实例上绑定自定义事件
      并指定相应的回调函数,然后子组件依然通过$emit触发自定义事件,从而回调父组件中方法,达到传递数据

      //父组件app.vue
      <template>
        <div class="home">
          {{msg}}
          <HelloWorld ref="helloWorld"/>
        </div>
      </template>
      
      <script>
      import HelloWorld from '@/components/HelloWorld.vue'
      export default {
        name: 'Home',
        data(){
          return {
            msg: ''
          }
        },
        components: {
          HelloWorld
        },
        mounted() {
          /*
            注意:$on用来绑定自定义事件,该方法第二个参数务必要使用箭头函数,或者指定methods中的方法,
            如果是普通函数,则内部的this是子组件。
           */
          this.$refs.helloWorld.$on('wanyi',(value)=>{
            this.msg=value;
          })
        }
      }
      </script>
      
      //子组件HelloWorld
      <template>
        <div class="hello">
          <button @click="sendMsg">点击给父组件发送数据</button>
        </div>
      </template>
      
      <script>
      export default {
        name: 'HelloWorld',
        methods: {
          sendMsg(){
            this.$emit('wanyi','肖武聪大傻叉')
          }
        }
      }
      </script>
      /*
      	scoped样式的作用:让样式在局部生效,防止冲突
      	另外app.vue中最好不要写scoped,因为如果在app.vue中配置样式,肯定是用于各个组件的,使用了scoped反而限制只能自己使用
      */
      <style scoped>
      
      </style>
      /*
      	补充一下这个$on
          $on,绑定事件,$once绑定一次性事件
          解绑一个自定义事件:this.$off("自定义事件名")
          解绑多个自定义事件:this.$off(['自定义事件1','自定义事件2'],'..3')
          解绑所有自定义事件:this.$off()
          另外,也可以在子组件绑定原生事件,不过需要在事件名上加上.native修饰符
          <Student @click.native="show"/>,如果不使用.native会被子组件当做自定义事件
      		
      	最后,只要在子组件标签上绑定任何属性,事件,都是直接绑定到子组件实例上,本身子组件标签就是子组件实例。
      */

任意组件间传递数据

2021年9月30日,新增一栏,将组件间任意通信,就随便放在这里写吧,目前有哪些方法可以实现任意组件间的通信呢?(不需要父子条件)

  • 全局事件总线
    全局事件总线方式可以做到只使用自定义事件达到任意两个组件间通信,英文名globalEventBus
    全局事件总线可以认为是一种设计模式,方便组件之间传递数据,当我们将vue实例放到Vue原型对象上后
    每个组件都能拿到vue实例对象,我们在vue实例对象上绑定自定义事件后,其他组件通过$emit方法触发自定义事件,
    这样就能实现多个组件间通信,而不需要这些组件是父子关系。
    案例如下,app.vue下HelloWorld组件与Home组件通信,这两个组件并不是父子

    //main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      store,
      beforeCreate() {
        Vue.prototype.$bus=this		//入口函数中,将vue实例绑定到Vue的原型对象上
      },
      render: h => h(App)
    }).$mount('#app')
    
    //app.vue,仅用于显示各个组件
    <template>
      <div id="app">
        <HelloWorld/>
        <about/>
        <home/>
        </div>
    </template>
    <script>
      import Home from './views/Home';
      import About from './views/About'
      import HelloWorld from './components/HelloWorld';
      export default {
        components: {
          Home,
          About,
          HelloWorld
        }
    }
    </script>
    
    //Home.vue
    <template>
      <div class="home">
        {{msg}}
      </div>
    </template>
    
    <script>
    	import HelloWorld from '@/components/HelloWorld.vue'
      export default {
        name: 'Home',
        data(){
          return {
            msg: ''
          }
        },
        mounted() {
          /*
          	给vue实例上绑定自定义事件,务必是箭头函数或methods中函数,
          	如果是普通函数,则函数内的this是vue实例,因为是vue实例身上的回调函数,
          	vue实例去调用它,和上面自定义事件案例类似。
          */
          this.$bus.$on('wanyi',(value)=>{	//自定义事件被触发后,回调该函数
            this.msg=value;
          })
        },
        /*
           将vue实例上的自定义事件清除
        */
        beforeDestroy() {
          this.$bus.$off()
        }
    }
    </script>
    
    //HelloWorld.vue
    <template>
      <div class="hello">
        <button @click="sendMsg">点击给父组件发送数据</button>
      </div>
    </template>
    
    <script>
      export default {
      name: 'HelloWorld',
      methods: {
        sendMsg(){
          this.$bus.$emit('wanyi','一二三四五')	//触发vue实例身上的wanyi事件并传递数据
        }
      }
    }
    </script>
  • 消息订阅与发布
    同全局事件总线一样,消息订阅与发布内部也是使用了自定义事件实现任意组件通信,我们借助第三方库实现:pubsub-js
    使用步骤如下:

    • 安装pubsub:npm i pubsub-js

    • 引入:import pubsub from 'pubsub-js'
      下面案例使用局部引入,如果想任意组件使用,还是需要在main.js中引入依赖,然后将pubsub对象绑定到Vue原型对象上。

    • 接收数据:A组件想接收数据,则在A中订阅消息,订阅的回调函数使用A组件中方法或者箭头函数,
      使用普通函数则this是undefined,这个和上面例子类似

    • 提供数据:B组件中通过pubsub.publish('消息名',数据),发布数据到订阅了这个消息的组件中,
      会调用订阅该消息的组件的回调函数

    • 组件销毁时,最好在A组件中取消订阅,在A组件beforeDestroy中:pubsub.unsubscribe(消息的id)

      总体看来,和自定义事件方式类似,发布方发布消息后,回调接收数据方的回调函数。

      本次案例类似上面,main.js中不需要将Vue实例放到Vue原型对象上了,App.vue一样,HelloWorld组件向Home组件传递数据

      //Home.vue,接收数据方
      <template>
        <div class="home">
          {{msg}}
        </div>
      </template>
      
      <script>
      	import pubsub from 'pubsub-js'
        export default {
          name: 'Home',
          data(){
            return {
              msg: ''
            }
          },
          mounted() {
            //接收数据方订阅一个消息,消息名随意,例如"waner"
            this.msgId = pubsub.subscribe('waner',(name,data)=>{
              this.msg=data
            });
          },
          beforeDestroy() {
            //组件销毁时,清除消息
            pubsub.unsubscribe(this.msgId)
          }
        }
      </script>
      
      //HelloWorld.vue,发送数据方
      <template>
        <div class="hello">
          <button @click="sendMsg">点击给父组件发送数据</button>
        </div>
      </template>
      
      <script>
        import pubsub from 'pubsub-js'
        export default {
          name: 'HelloWorld',
          methods: {
            sendMsg(){
              //发布消息,参数一是消息名,参数二是数据,表示向订阅这些消息的组件发送数据
              pubsub.publish('waner',"万二")
            }
          }
        }
      </script>
  • Vuex,略,请看Vuex篇

  • vue-router,略,请看路由篇

父子组件通信结合双向绑定案例

这里主要说的是,在父子组件通信时,子组件最好不要修改props中的变量(属性),即v-model不能绑定props中的值
而是绑定data中的变量,我们可以将props中的值赋值给data中的变量,props的值始终只能是父组件进行修改的。
下面是一个简单的案例,同时有父向子,子向父通信,案例的功能是,当上面的input值更改时,下面的值乘100

<div id="app">
    <wanyi :cnum1="num1" :cnum2="num2" @change-num1="changeNum1" @change-num2="changeNum2"></wanyi>
</div>

<template id="yue">
    <div>
    	props:{{cnum1}}
        data:{{number1}}
        <input type="text" :value="number1" @input="change1"><br>
        
        props:{{cnum2}}
        data:{{number2}}
        <!--<input type="text" v-model="number2">-->    //这里不使用v-model双向绑定是因为需要执行其他任务。所以代替了
        <input type="text" :value="number2" @input="change2"><br>
    </div>
</template>
<script>
    let app = new Vue({
        el: '#app',
        data: {
            num1:1,
            num2:2
        },
        methods: {
            changeNum1(num1){
                //子组件向父组件传递值时,数据是字符串,需要转成整数类型
                this.num1=num1+0;
            },
            changeNum2(num2){
                //子组件向父组件传递值时,数据是字符串,需要转成整数类型
                this.num2=num2+0;
            }
        },
        components:{
            wanyi:{
                template:"#yue",
                data(){
                    return {
                        number1:this.cnum1,
                        number2:this.cnum2
                    }
                },
                props:{
                    cnum1:{
                        type:Number,
                    },
                    cnum2:Number
                },
                methods:{
                    change1(event){
                        this.number1=event.target.value;
                        this.number2=this.number1*100;
                        this.$emit('change-num1',this.number1);
                        this.$emit('change-num2',this.number2);
                    },
                    change2(event){
                        this.number2=event.target.value;
                        this.number1=this.number2/100
                        this.$emit('change-num2',this.number2);
                        this.$emit('change-num1',this.number1);
                    }
                }
            }
        }
    });
</script>
/*
	我们使用<input type="text" :value="number2" @input="change2">代替了<input type="text" v-model="number2">
    是因为我们不仅需要双向绑定更改number2的值,还需要相应的修改number1的值,所以在事件函数中修改number1的值,
    你还可以使用v-model和watch属性的方式做到上面的效果,当v-model绑定的值发生改变的时候,watch属性中的函数会发生回调,
    可以在该函数中修改numebr1的值,也就是说不监听input事件了。例如下面的更改:
*/
//使用v-model和watch同样能做到当绑定的变量发生改变的时候,也能更改其他变量的值并且向父组件通信。
//watch中监听的就是data数据中的变量,变量名为函数,当变量发生修改后,执行该函数。
<input type="text" v-model="number2">
watch:{
    number1(newValue,oldValue){
        this.number2=newValue*100;
        this.$emit('change-num1',this.number1);
        this.$emit('change-num2',this.number2);
    },
    number2(newValue,oldValue){
        this.number1=newValue/100;
        this.$emit('change-num2',this.number2);
        this.$emit('change-num1',this.number1);
    }
}

父子组件之间的访问

之前说的是父子组件之间的通信,指的是,父子组件相互获取对方的属性值,
而父子组件之间的访问指的是,拿到对方的对象,使用对象的属性和方法
网课定义:有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件直接访问根组件

  • 父组件访问子组件:使用$children$refs
    $children$refs不同的是,$children获取的是全部的子组件,是一个数组,数组的元素是该组件下的所有子组件
    所以取出某个子组件需要遍历,一般很少用,当需要全部的子组件的时候才会使用$children,而$refs可以指定获取某个子组件
    只需要在子组件标签上使用ref属性标记该子组件,就可以在父组件中获取到该子组件,你也可以标记多个子组件
    到时候this.$refs是一个对象,对象的属性是子组件标签的ref属性,通过this.$refs.ref属性就可以获取子组件对象
    使用this.$refs.ref属性.子组件属性/子组件方法,即可获取子组件属性和方法。下面是一个简单的案例:

    <div id="app">
        <wanyi ref="yueyue"></wanyi>    //加上ref属性,则该子组件会被加到$refs对象之中
    	<button @click="getChild">点击获取</button>
    </div>
    <template id="yue">
        <div>
    
        </div>
    </template>
    
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                message:'加油!'
            },
            methods:{
                getChild(){
                    console.log(this.$children);    //[VueComponent],该对象是数组,数组中的元素就是子组件对象
                    console.log(this.$children[0].name);
                    console.log(this.$refs);        //{yueyue: VueComponent},它是一个对象,子组件对象以属性的方式存储在该对象中
                    console.log(this.$refs.yueyue.name);
                    this.$refs.yueyue.show()
                }
            },
            components:{
                wanyi:{
                    template: '#yue',
                    data(){
                        return{
                            name: '子组件'
                        }
                    },
                    methods:{
                        show(){
                            console.log("子组件中的方法");
                        }
                    }
                }
            }
        });
    </script>
  • 子组件访问父组件:使用$parent,以及$root
    this.$parent访问父组件实例,不常用,this.$root访问根组件实例,也就是vue中的属性和方法
    用法和上面的差不多,就不举例子了。

组件中插槽的使用

  • 组件的插槽slot基本使用
    组件的插槽是为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么
    简单案例如下:

    <div id="app">
        <!--向插槽中添加万二标签,会替换模板中的slot标签,当插入多个标签时,同样会全部替换-->
        <wanyi><h3>万二</h3></wanyi>
        <!--如果没有插入标签,不会显示slot标签,所以slot插槽很形象,预留给使用者一个接口-->
        <wanyi></wanyi>
    </div>
    <template id="yue">
        <div>
            <h3>万一</h3>
        	<!--预留的插槽,使用者可以使用自己的定制的东西,插槽还可以使用默认值,即在slot标签中配置默认值标签-->
            <!--配置好默认值后,如果使用者没有定制化插槽,则会使用插槽中的默认值显示出来-->
            <slot></slot>
    	</div>
    </template>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                message:'加油!'
            },
            components:{
                wanyi:{
                    template: '#yue',
                }
            }
        });
    </script>
  • 具名插槽
    指的是当模板中存在多个插槽的时候,需要给插槽取个名字,这样使用者在替换插槽的时候,就能替换指定的插槽,案例如下:

    <div id="app">
        <wanyi><button slot="left">返回</button></wanyi>
    </div>
    <template id="yue">
        <div>
            <h3>万一</h3>
            <slot name="left">左边</slot>
            <slot name="center">中间</slot>
            <slot name="right">右边</slot>
        </div>
    </template>
    //使用的时候,使用slot指定覆盖(替换)哪一个插槽,如果没有指定slot名称,则只会替换没有名称的插槽。
  • 作用域插槽

    • 作用域的概念
      作用域又称编译作用域,指的是,组件中的变量只会在自己的模板中有效
      例如,在父子组件中存在同名变量,在使用该变量时,如果使用在父模板中,则使用的变量是父组件中的变量,
      如果使用在子模板中,则使用的变量是子组件中的变量,下面是一个简单的案例:

      <div id="app">
          <wanyi v-show="isShow"></wanyi>
      </div>
      <template id="yue">
          <div>
          	<h3>万一</h3>
      	</div>
      </template>
      <script>
          let app = new Vue({
              el: '#app',
              data: {
                  isShow: true,
                  message:'加油!'
              },
              components:{
                  wanyi:{
                      template: '#yue',
                      data(){
                          return {
                              isShow: false
                          }
                      }
                  }
              }
          });
      </script>
      //上面这个例子,isShow变量是在父组件中使用,也就是vue实例的模板中使用,所以引用的变量是vue实例中的变量。
    • 作用域插槽旧语法
      作用域插槽,用一句话对其总结,父组件替换插槽的标签,但是内容由子组件来提供,下面是一个简单的案例:

      <div id="app">
          <wanyi></wanyi>
      	<wanyi>
              <template slot-scope="slot">
                  <span>{{slot.data.join(' - ')}}</span>
              </template>
      	</wanyi>
      </div>
      
      <template id="yue">
          <div>
              <slot :data="fruit">
                  <h4 v-for="item in fruit">{{item}}</h4>
              </slot>
      	</div>
      </template>
      
      <script>
      	let app = new Vue({
              el: '#app',
              data: {
              },
              components:{
                  wanyi:{
                      template: '#yue',
                      data(){
                          return {
                              fruit:['芒果','火龙果','草莓','桃子','樱桃']
                          }
                      }
                  }
              }
          });
      </script>
      /*
      结果如下:
          芒果
          火龙果
          草莓
          桃子
          樱桃
          芒果 - 火龙果 - 草莓 - 桃子 - 樱桃
      可以看到,我们使用子组件中的数据,而样式使用的是父组件的样式,父组件中可以对子组件的数据有不同的展示
      值得注意的是:这种方式好像已经过时
      */
    • 作用域插槽新语法
      上面这种作用域插槽在vue2.6之后就不使用了
      普通插槽或者具名插槽作用:父组件传递数据/元素/组件给子组件,而子组件定义slot标签接收
      作用域插槽:为子组件<slot>绑定属性,传递数据给父组件,父组件通过v-slot:xxx="props"接收子组件传递的属性
      感觉作用域插槽就是父组件拿出子组件数据,对数据进行一些重新展示。

      <div id="app">
          <wanyi></wanyi>
          <wanyi v-slot:cslot="param">
              {{param.fruit.join(' - ')}}
              {{param.cname}}
          </wanyi>
      </div>
      
      <template id="yue">
          <div>
              <slot name="cslot" :fruit="fruit" :cname="name">
                  <h4 v-for="item in fruit">{{item}}</h4>
              </slot>
      	</div>
      </template>
      <script>
      	let app = new Vue({
              el: '#app',
              data: {
              },
              components:{
                  wanyi:{
                      template: '#yue',
                      data(){
                          return {
                              fruit:['芒果','火龙果','草莓','桃子','樱桃'],
                              name: '科一',
                              age: 20
                          };
                      },
                  },
              },
          });
      </script>
      /*
      	新语法在子组件中使用name属性表示该插槽的名字,即具名插槽,绑定数据还是和旧语法一样
      	父组件在使用的时候不是必须使用template标签,你可以直接使用在子组件标签上,
      	当然,如果有多个插槽,还是建议使用template标签
      	v-slot:子组件插槽名="xxx",该对象和旧语法中的对象一致,取值也是一样的
      */
  • 更新
    21年9月14号,对插槽进行更新,在vue3之后,之前的插槽语法不再适用,而是使用v-slot语法
    不管是普通插槽,具名插槽,作用域插槽都需要使用v-slot,并且这个属性只能使用在组件标签上或者
    template标签上,下面是一段代码,表示新版本具名插槽的使用:

    //父组件插入数据
    <template>
      <div>
        <router-view/>
        <tab-bar>
          <!--
            这里是父子组件传递,tab-bar-item是子组件,mainTabBar是父组件,这里是传固定值
            如果是:path,则是绑定mainTabBar组件中data的变量
          -->
          <tab-bar-item path="/home">
            <template v-slot:item-icon>	//v-slot需要放在template标签上
              <img src="~assets/img/tabbar/home.png">	//定义了别名,在标签上使用时需加上~
            </template>
            <template v-slot:item-icon-active>
              <img src="~assets/img/tabbar/home-active.png">
            </template>
            <template v-slot:item-text>
              <div>首页</div>
            </template>
          </tab-bar-item>
        </tab-bar>
      </div>
    </template>
    
    <script>
    import TabBar from '../../common/tabbar/TabBar.vue'
    import TabBarItem from '../../common/tabbar/TabBarItem.vue'
    export default {
      name: "MainTabBar",
      components:{
        TabBar,TabBarItem
      }
    }
    </script>
    <style scoped>
    </style>
    
    //子组件定义具名插槽,还是原来的定义方式
    <template>
      <div class="tab-bar-item" @click="itemClick">
        <div  v-if="!isActive"><slot name="item-icon"></slot></div>
        <div  v-else><slot name="item-icon-active"/></div>
        <div :style="activeStyle"><slot name="item-text"/></div>
      </div>
    </template>