Vue的组件化

Vue的组件化

经过两天的学习,现在终于来到了组件化开发的环节,Vue的组件化是很重要的一个点,一定要认真学习!!!

一、组件化的基本使用

  1. Vue中想要定义一个组件需要两步。首先,需要定义一个组件构造器,通过Vue.extend方法来创建,然后通过Vue.component方法来注册组件。

    <div id="app">
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
      <cpn></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        //第一步:创建组件构造器
        const cpn = Vue.extend({
          template:`
            <div>
                <h2>我是标题</h2>
                <p>今天天气不错,我们去玩吧</p>
            </div>
          `
        });
        //第二步:注册组件
        Vue.component('cpn',cpn);
        const app = new Vue({
          el: '#app',
          data: {
          message: '你好啊,李银河!'
          }
        });
      </script>
    

这里定义template时,我使用的是飘号,这个之前在MySQL中见到过,老师还说是不通用的。这里的飘号有个强大的功能,就是只要在飘号中间写的字符串是不需要通过字符串连接符串起来的,妈妈再也不怕我字符串拼接出问题了。

注意:

1.组件构造器中的模板里若是不止一个标签需要有一个根标签来将其余子标签包裹,不然Vue会报错的。

2.对于Vue中自定义的组件,只能在Vue实例对象所以管理的Dom元素中使用,要是离开了Vue管理的DOM元素,那Vue跟它就没有联系了,HTML又不认识自己定义的标签,那么肯定报错了。

3.在HTML5之后,HTML标签就不支持大写了,所以不管是定义HTML标签,还是定义标签的一些属性的时候都要用小写,要是字与字需要隔开的就用"-"隔开。

二、全局组件和局部组件

  1. 全局组件

    ​ 我们上面那种通过Vue.component注册的组件就是全局组件,它可以在所有咱们创建的Vue实例对象中使用。

    <div id="app">
      <my-cpn></my-cpn>
    </div>
    <div id="app1">
      <my-cpn></my-cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
      //第一步:创建组件构造器
      const cpn = Vue.extend({
        template:`
            <div>
                <h2>我是标题</h2>
                <p>今天天气不错,我们去玩吧</p>
            </div>
          `
      });
      //第二步:注册组件
      Vue.component('my-cpn',cpn);
      const app = new Vue({
        el: '#app',
        data: {
          message: '你好啊,李银河!'
        }
      });
      const app1 = new Vue({
        el: '#app1',
        data: {
          message: '你好啊,李银河!'
        }
      });
    </script>
    

    ​ 页面正常渲染了两份自定义的组件,而控制台也没有报错,说明的确是可以在多个Vue实例对象中访问的。其实也很好理解,毕竟这种定义方式没有和哪个实例对象扯上关系。


  1. 局部组件

    ​ 局部组件就是在Vue实例对象中通过component属性来注册的组件,这样这个组件就是直接注册到该实例对象的作用域中了,其它实例对象是访问不了的。

    <div id="app">
      <my-cpn></my-cpn>
    </div>
    <div id="app1">
      <my-cpn></my-cpn>
    </div>
    <script src="../js/vue.js"></script>
    <script>
      //第一步:创建组件构造器
      const cpn = Vue.extend({
        template:`
            <div>
                <h2>我是标题</h2>
                <p>今天天气不错,我们去玩吧</p>
            </div>
          `
      });
      //第二步:注册组件
      const app1 = new Vue({
        el: '#app1',
        data: {
          message: '你好啊,李银河!'
        },
        components:{
          myCpn :cpn
            //标签名:标签模板(组件构造器)
        }
      });
    </script>
    

You can see,我的确是注册了组件,但是在app1这个实例对象中的确是用不了,这就是局部组件的概念了。跟你没半毛钱关系,你怎么可能用得了呢。

注意:

  1. 我注册组件时组件名写的是myCpn,为什么上面用的时候写的是my-cpn呢?我之前说过在H5之后标签中是不允许有大写的,所以咱们定义变量使用的驼峰标识法是无法让浏览器认同的,所以就有个"-"等于首字母大写的,额,不清楚是规定还是什么。
  2. 有些人可能会想了,那既然H5标签支持小写和"-",那咱们定义的时候直接定义成my-cpn不就行了吗。那你可能忘记了,定义标识符不是有语法规定吗,只能存在数字,字母,下划线和美元符号,还不能以数字开头嘛

三、父组件和子组件

​ 其实组件构造器和Vue实例对象有很多点相似,比如说Vue实例对象中可以注册组件,然后Vue构造器中也是可以注册组件的,后面还会介绍更多的相同点。什么是父子组件呢,就是那个创造出你的男人叫你的父亲,那创建出这个组件的组件是不是就得叫父组件嘛。这就是父子组件,嗯,可以把Vue实例对象也看成一个组件。

<div id="app">
  <my-cpn></my-cpn>
</div>
<script src="../js/vue.js"></script>
<script>
  //第一步:创建组件构造器
  const cpn = Vue.extend({
    template:`
        <div>
            <h2>我是标题</h2>
            <p>今天天气不错,我们去玩吧</p>
        </div>
      `
  });
  const cpn1 = Vue.extend({
    template:`
        <div>
            <h2>我是标题</h2>
            <p>今天天气不错,我们去玩吧</p>
            <cpn></cpn>
        </div>
      `,
    components:{
      cpn
    }
  });
  //第二步:注册组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊,李银河!'
    },
    components:{
      myCpn :cpn1
    }
  });
</script>

Vue实例对象中的el是不是和组件构造器中的template有点点类似啊,在Vue实例对象中定义的组件可以在el中的Dom元素中使用,而在组件构造器中注册的组件可以在template中使用。

四、注册组件的语法糖方式

​ 在之前,我们注册组件是不是要先创建构造器,Vue.extend然后传入一个对象来创建组件构造器嘛。现在就可以省略这一步,直接把那个对象放到注册组件时组件构造器的位置就可以了,然后Vue会自动帮我们把这个对象拿去通过Vue.extend方法创建一个组件构造器。

<div id="app">
  <my-cpn></my-cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    //全局组件的语法糖写法
    Vue.component('myCpn',{
      template:`
      <div>
        <h2>我是标题</h2>
        <p>希望疫情早点过去,大家能够和从前一样</p>
      </div>
      `
    });
    //局部组件的语法糖写法
    const app = new Vue({
      el: '#app',
      data: {
      message: '你好啊,李银河!'
      },
      components:{
        myCpn:{
          template:`
          <div>
            <h2>我是标题</h2>
            <p>希望疫情早点过去,大家能够和从前一样摘下口罩勇敢地走在阳光下</p>
          </div>
          `
        }
      }
    });
  </script>

​ 这里我还做了一个测试,就是定义相同的全局组件和局部组件,然后在Vue实例对象所管理的Dom元素中使用这个组件,结果证明全局组件和局部组件和全局变量和局部变量是差不多的,就像是局部变量会覆盖全局变量一样,局部组件也是会覆盖全局组件的。

五、组件模板的分离写法

大家有没有觉得上面那种代码写的很杂很乱的感觉,HTML的东西杂糅在Script中,代码看起来很不清爽。下面咱们就来将这个template给他抽离出来。

<!--<script type="text/x-template" id="cpn">-->
<!--  <div>-->
<!--    <h2>我是标题</h2>-->
<!--    <p>希望疫情早点过去,大家能够和从前一样摘下口罩勇敢地走在阳光下</p>-->
<!--  </div>-->
<!--</script>-->
<template id="cpn">
  <div>
    <h2>我是标题</h2>
    <p>希望疫情早点过去,大家能够和从前一样摘下口罩勇敢地走在阳光下</p>
  </div>
</template>
<div id="app">
  <my-cpn></my-cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
      message: '你好啊,李银河!'
      },
      components:{
        myCpn:{
          template:"#cpn"
        }
      }
    });
  </script>

组件模板的分离有两种方式,一种是使用script标签,设置type为text/x-template,另一种就是直接使用template标签。通过这个例子你能更加直观的看到,Vue实例对象的el属性和组件构造器的template属性真的挺像的,都是将一些Dom元素作为操作的盒子。嗯,应该可以这么说吧,在这个盒子中可以使用这个对象的一些数据啊属性什么的。

六、组件中的数据存放问题

上面说了,组件其实和Vue实例对象是非常相似的,那么Vue实例对象中可以定义一些数据,一些方法什么的,那接下来就看一下组件中的数据存放的相关东西吧。

<script type="text/x-template" id="cpn">
  <div>
    <h2>{{message}}</h2>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <button @click="btnClick">点击使得文字变红</button>
  </div>
</script>
<div id="app">
  <my-cpn></my-cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊,李银河!'
      },
      components:{
        myCpn:{
          template:"#cpn",
          methods:{
            btnClick() {
              console.log("the button is clicked...");
            }
          }
        }
      }
    });
  </script>

这个例子证明了,组件的确可以看作另类的Vue实例对象,或者说Vue实例对象可以看作另类的组件,他们内部都是可以定义方法methods的,都是可以执行的。然后这里我先是通过Mustache语法获取message,一开始在组件中没有定义message,而在他的父组件中有message,但是很明显控制台报错了说明每个组件只能通过自己的data来拿自己的变量。

注意:组件中的data和Vue实例对象还是有不同的,组件中的data只能为函数,而且返回一个对象

<script type="text/x-template" id="cpn">
  <div>
    <h2>{{message}}</h2>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <button @click="btnClick">点击使得文字变红</button>
  </div>
</script>
<div id="app">
  <my-cpn></my-cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊,李银河!'
      },
      components:{
        myCpn:{
          template:"#cpn",
          data:{
            message: '中国万岁,马克思主义万岁'
          },
          methods:{
            btnClick() {
              console.log("the button is clicked...");
            }
          }
        }
      }
    });
  </script>

上面说到我需要拿到message,就必须在我组件内部自己定义一个data,于是我就照着Vue实例的样子画瓢,也就是同样这样定义了message,于是控制台报了这么个错误。他说data属性应该是一个函数,并且返回一个实例对象。

[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

为什么会这样呢?为什么实例对象就没有这样的要求呢?

其实也很好理解,就是我们组件定义好之后是不是可以使用多次啊,而Vue实例对象就只是使用一次,所以组件中若是data为一个对象的话,在多个组件中使用的就是同一份数据了。那要是发生修改的话肯定乱套了。

<script type="text/x-template" id="cpn">
  <div>
    <h2>{{message}}</h2>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
    <button @click="btnClick">点击使得文字变红</button>
  </div>
</script>
<div id="app">
  <my-cpn></my-cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊,李银河!'
      },
      components:{
        myCpn:{
          template:"#cpn",
          data(){
            return {
              message: '中国万岁,马克思主义万岁'
            }
          },
          methods:{
            btnClick() {
              console.log("the button is clicked...");
            }
          }
        }
      }
    });
  </script>

这样子就可以了,控制台没有报错,然后也能在组件中拿到这个数据。

七、父子组件通信

在开发中,经常会有大的组件获取到后端传来的数据然后交给子组件显示,然后子组件发生了一些用户操作后,传信息给父组件,让他去找服务端要数据。所以父子通信的问题是很重要的。接下来,就学习一下父子通信。

  1. 父传子

    ​ 父传子是通过props属性来传的,在子组件中定义props属性,然后父组件通过v-bind指令来向props指令中绑定值。

    1. 父传子的props的第一种用法,数组用法

      <template id="cpn">
        <div>
          <h2>{{cMessage}}</h2>
          <h3>{{cTitle}}</h3>
          <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
          <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
          <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
          <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
        </div>
      </template>
      <div id="app">
        <my-cpn :c-message="message" :c-title="title"></my-cpn>
      </div>
        <script src="../js/vue.js"></script>
        <script>
          const app = new Vue({
            el: '#app',
            data: {
              message: '中国万岁,马克思主义万岁',
              title: '天下大同'
            },
            components:{
              myCpn:{
                template:'#cpn',
                props:['cMessage','cTitle']
              }
            }
          });
        </script>
      

    这种方式不太好,你看,数组中定义的变量是不是字符串来的,是不是阅读起来有点不太理解。所以下面这种方式会更加好一点,这里我将组件中的变量命名为cMessage也是想测试一下,H5中的属性是不是也要小写,结果证明的确是要小写的,若是你命名中有大写字母,那就得用-加小写字母来替换掉。

    ​ b.父传子的props的第二种用法,对象用法

    <template id="cpn">
      <div>
        <h2>{{cMessage}}</h2>
        <h3>{{cTitle}}</h3>
        <ul>
          <li v-for="(value,key) in cAutor">{{key}}-{{value}}</li>
        </ul>
        <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
        <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
        <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
        <p>五星红旗飘扬,将国家变得更加繁荣富强</p>
      </div>
    </template>
    <div id="app">
      <my-cpn :c-message="message" :c-title="title"></my-cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data: {
            message: '中国万岁,马克思主义万岁',
            title: '天下大同'
          },
          components:{
            myCpn:{
              template:'#cpn',
              // props:['cMessage','cTitle']
              props:{
                cMessage:{
                  //验证传过来的值的类型
                  type:String,
                  //定义若是没有传值过来的话的默认值
                  default:"全世界工人联合起来",
                },
                cTitle:{
                  required:true,
                  validator(value){
                    //自定义验证函数,返回一个布尔值,若是false表示发送来的数据不合法会报错。
                    return value.indexOf("大同")>=0;
                  }
                },
                cAutor:{
                  type:Object,
                  default:{
                    age:20,
                    name:"WaiGo",
                    height:1.80
                  }
                }
              }
            }
          }
        });
      </script>
    

    你很明显的能够感觉到,这种用法绝对是要更好的,因为它能对父组件传来的值进行过滤,若是不合法会报错。能够显示传来的值的类型(type),

    能够设置是否一定要传值(required),可以设置默认值(default)。不过需要注意一点:就是我上面这个例子报了一个这个错误

    [Vue warn]: Invalid default value for prop "cAutor": Props with type Object/Array must use a factory function to return the default value.

    它说,cAutor设置了无效的初始值,若是Props设置类型为数组或者对象的时候必须使用一个工厂函数返回一个默认值。

    其实,这也是很好理解的,就和之前说的子组件中的data不能为一个对象是一样的,就是防止多个组件使用时数据乱套的问题。

    cAutor:{
      type:Object,
      default(){
        return {
          age:20,
          name:"WaiGo",
          height:1.80
        }
      }
    }
    

    这样就不会报错了。

  2. 子传父

    我们已经学会父传子了,那怎么子传父呢?子传父是通过自定义事件,然后,父组件监听这个自定义事件实现的。子组件通过$emit来发送事件。

    <template id="cpn">
      <div>
        <button v-for="category in categories" :key="category.id" @click="choose(category.id)">{{category.name}}</button>
      </div>
    </template>
    <div id="app">
      <cpn @cgychoose="chrChoose"></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const cpn = {
          template:"#cpn",
          data() {
            return {
              categories: [
                {id: 'aaa', name: '热门推荐'},
                {id: 'bbb', name: '手机数码'},
                {id: 'ccc', name: '家用家电'},
                {id: 'ddd', name: '电脑办公'},
              ]
            }
          },
          methods:{
            choose(id){
              console.log(id + "is clicked ...");
              this.$emit("cgychoose",id);
            }
          }
        };
        const app = new Vue({
          el: '#app',
          data: {
            message: '你好啊,李银河!'
          },
          components:{
            cpn
          },
          methods:{
            chrChoose(id){
              console.log(id);
            }
          }
        });
      </script>
    

这里就是子组件中点击了某个按钮就将这个按钮对应的id传给父组件,这里有个注意点,就是事件的名字好像是不能有-的,我一开始定义事件为cgyChoose,想要通过cgy-choose来监听发现没有报错却是监听不到的,然后我就将它全部改成小写,这才监听得到。所以自定义事件尽量都小写不然就会出问题,然后你还不知道问题出在哪里。

八、父子组件通信的案例

需求:子组件中的数据有两个数据来自于父组件,子组件中有两个个输入框,输入框和子组件的数据实现了双向绑定,若是输入框中的数据发生了改变要求父组件中的数据也随之变化。第一个数据要求在每次输入后都为第二个数据的100倍,第二个数据要求每次输入后都变为第一个数据的1/100;

  1. 先实现子组件获取父组件的值

  2. 实现输入框和子组件数据的双向绑定

    vue.js:634 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "cNum1"

    注意:如果将v-model直接绑定到props上的话会报错的,它建议我们把值绑定到data或者computed属性上。

    <template id="cpn">
      <div>
        <h2>props1:{{pNum1}}</h2>
        <h2>data1:{{dNum1}}</h2>
        <label for="num1">
            <input type="number" id="num1"  :value="dNum1" @input="num1Input"/>
        </label>
        <h2>props2:{{pNum2}}</h2>
        <h2>data2:{{dNum2}}</h2>
        <label for="num2">
          <input type="number" id="num2"  :value="dNum2" @input="num2Input" />
        </label>
      </div>
    </template>
    <div id="app">
      <cpn :p-num1="num1" :p-num2="num2" @dnum1change="c1Change" @dnum2change="c2Change" ></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data: {
            num1:0,
            num2:1
          },
          methods:{
            c1Change(cnum1){
              this.num1 = cnum1;
            },
            c2Change(cnum2){
              this.num2 = cnum2;
            }
          },
          components:{
            cpn:{
              template:"#cpn",
              data(){
                return {
                  dNum1:this.pNum1,
                  dNum2:this.pNum2
                }
              },
              props:{
                pNum1:{
                  type:Number,
                  required:true,
                  default:0
                },
                pNum2:{
                  type:Number,
                  required:true,
                  default:0
                }
              },
              methods:{
                num1Input(event){
                  this.dNum1 = parseFloat(event.target.value);
                  this.dNum2 = this.dNum1 / 100;
                  this.$emit("dnum1change",this.dNum1);
                  this.$emit("dnum2change",this.dNum1 / 100);
                },
                num2Input(event){
                  this.dNum2 = parseFloat(event.target.value);
                  //每一个dNum1,dNum2都是只初始化一次,后期就和props里的pNum1和pNum2没关系了
                  this.dNum1 = this.dNum2 * 100;
                  this.$emit("dnum2change",this.dNum2);
                  this.$emit("dnum1change",this.dNum2 * 100);
                }
              }
            }
          }
        });
      </script>
    

    这个案例告诉我们,1.只有在第一次使用data的时候才会初始化返回一个data对象,而在之后的使用中,都是用的一个data对象。2.输入框中的输入的东西不管是什么,它都是当作字符串的。不过在js运算中会隐式强转。

    这个案例还有一种简单的实现方式,就是使用watch来实现,watch可以观察某个属性的改变,若是这个属性的值发生了改变就会执行相应的函数,这就省去了将v-model拆分为v-on:input和v-bind:value的过程了

    <template id="cpn">
      <div>
        <h2>props1:{{pNum1}}</h2>
        <h2>data1:{{dNum1}}</h2>
        <label for="num1">
            <input type="number" id="num1"  v-model="dNum1" />
        </label>
        <h2>props2:{{pNum2}}</h2>
        <h2>data2:{{dNum2}}</h2>
        <label for="num2">
          <input type="number" id="num2"  v-model="dNum2" />
        </label>
      </div>
    </template>
    <div id="app">
      <cpn :p-num1="num1" :p-num2="num2" @dnum1change="c1Change" @dnum2change="c2Change" ></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data: {
            num1:0,
            num2:1
          },
          methods:{
            c1Change(cnum1){
    
              this.num1 = parseFloat(cnum1);
            },
            c2Change(cnum2){
              this.num2 = parseFloat(cnum2);
            }
          },
          components:{
            cpn:{
              template:"#cpn",
              data(){
                return {
                  dNum1:this.pNum1,
                  dNum2:this.pNum2
                }
              },
              props:{
                pNum1:{
                  type:Number,
                  required:true,
                  default:0
                },
                pNum2:{
                  type:Number,
                  required:true,
                  default:0
                }
              },
              watch:{
                dNum1(newValue){
                  this.dNum2 = newValue / 100;
                  this.$emit("dnum1change",newValue);
                },
                dNum2(newValue){
                  this.dNum1 = newValue * 100;
                  this.$emit("dnum2change",newValue);
                }
              }
            }
          }
        });
      </script>
    

    九、父子组件的访问

上面我们学习了父子之间的通信,就是能够互相发送消息了,但是有时候我们需要父组件直接访问子组件,或者子组件直接访问父组件,子组件直接访问根组件。现在就来学习一下父子组件的访问。

  1. 父组件访问子组件:通过children或者refs

    1. $children

      <template id="cpn">
        <div>
          <h2>{{message}}</h2>
        </div>
      </template>
      <div id="app">
        <cpn></cpn>
        <cpn></cpn>
        <cpn></cpn>
        <cpn></cpn>
        <cpn></cpn>
        <cpn></cpn>
        <button @click="btnClick">获得子组件的Message</button>
        <ul>
          <li v-for="meg in fMessage">{{meg}}</li>
        </ul>
      </div>
        <script src="../js/vue.js"></script>
        <script>
          const app = new Vue({
            el: '#app',
            data:{
              fMessage : []
            },
            components:{
              cpn:{
                template:"#cpn",
                data(){
                  return {
                    message : "这里是子组件"
                  }
                }
              }
            },
            methods:{
              btnClick(){
                for(let chr of this.$children){
                  this.fMessage.push(chr.message);
                }
              }
            }
          });
        </script>
      
    2. refs,使用children查找子元素的时候有个弊端,就是若是想要找到某个指定的元素的时候肯定要使用下标,但是这个数组的下标称为margic number,是非常容易变的,所以项目很容易崩。这时候就要使用refs来解决这个问题了。refs和ref属性一起使用,通过ref设置这个标签的ID,然后通过$refs.ID的方式拿到这个子元素。

    <template id="cpn">
      <div>
        <h2>{{message}}</h2>
      </div>
    </template>
    <template id="cpn1">
      <div>
        <h2>{{message}}</h2>
      </div>
    </template>
    <div id="app">
      <my-cpn ref="cpnFir"></my-cpn>
      <cpn ref="cpnFir"></cpn>
      <cpn></cpn>
      <cpn></cpn>
      <button @click="btnClick">获得子组件的Message</button>
      <ul>
        <li v-for="meg in fMessage">{{meg}}</li>
      </ul>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data:{
            fMessage : []
          },
          components:{
            cpn:{
              template:"#cpn",
              data(){
                return {
                  message : "这里是子组件cpn"
                }
              }
            },
            myCpn:{
              template:"#cpn1",
              data(){
                return {
                  message : "这里是子组件cpn1的message"
                }
              }
            }
          },
          methods:{
            btnClick(){
              console.log(this.$refs.cpnFir.message);
              for(let chr of this.$children){
                this.fMessage.push(chr.message);
              }
            }
          }
        });
      </script>
    

    我在这里还做了一个测试,就是当两个不一样的子元素使用同一个ref名字会怎么样,结果显示,后面那一个会覆盖掉前面那个。不过在开发中应该很少会出现重名的情况吧。

  1. 子组件访问父组件:$parent

    <template id="cpn">
      <div>
        <h2>这是一个标题</h2>
        <button @click="btnClick">获得父组件的Message</button>
      </div>
    </template>
    <div id="app">
      <cpn></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data:{
            message : "这里是父组件cpn"
          },
          components:{
            cpn:{
              template:"#cpn",
              methods:{
                btnClick(){
                  console.log(this.$parent.message);
                }
              }
            }
          }
        });
      </script>
    

    没什么好讲的就是通过$parent拿到父组件对象,然后可以获取其中的数据。不过不推荐使用,这样子组件和父组件的耦合度太高了,换个地方就不适用了。

  2. 子组件访问根组件:$root

    <template id="cpn1">
      <div>
        <h2>这是一个来自cpn1的标题</h2>
        <button @click="btnClick">获得根组件的Message</button>
      </div>
    </template>
    <template id="cpn">
      <div>
        <h2>这是一个来自cpn的标题</h2>
        <cpn1></cpn1>
      </div>
    </template>
    <div id="app">
      <cpn></cpn>
    </div>
      <script src="../js/vue.js"></script>
      <script>
        const app = new Vue({
          el: '#app',
          data:{
            message : "这里是根组件cpn"
          },
          components:{
            cpn:{
              template:"#cpn",
              components:{
                cpn1:{
                  template:"#cpn1",
                  methods:{
                    btnClick(){
                      console.log(this.$root.message);
                    }
                  }
                }
              }
            }
          }
        });
      </script>
    

这个root和parent的用法差不多

十、插槽

我们前面讲的组件还不具备复用的性质,因为所有东西都是写死的,无法根据需求替换其中的东西。而下面讲的这个插槽就能够很好的完成这种复用的要求。抽取共性,保留不同的地方作为插槽。

1.编译的作用域

什么是编译作用域呢,就是指父组件中的东西编译的时候都会在父组件的作用域中编译,其中使用到的变量都是来自父组件的,然后子组件也是这样。

<template id="cpn">
  <div>
    <h2>我是否能够显示呢</h2>
  </div>
</template>
<div id="app">
  <cpn v-show="isShow"></cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isShow:true
      },
      components:{
        cpn:{
          template:"#cpn",
          data(){
            return {
              isShow:false
            }
          }
        }
      }
    });
  </script>

这是能够显示出来的,说明这里使用的isShow是来自父组件中的isShow,若是将v-show提到子组件内部就显示不出来了,因为这时候就是使用的子组件的isShow.这就是编译作用域,在谁的地盘,就按谁的命令行事。

2.插槽的基本使用

通过slot标签来定义插槽,插槽中间可以定义默认值,若是不往插槽中插入标签则显示默认值。若是有多个元素同时插入插槽,则会加在一起替换一个插槽位置。而若是有多个插槽位,那么就会往多个插槽中都插入同样的标签。

<template id="cpn">
  <div>
    <h2>我是标题</h2>
    <p>我是内容,真是有够好笑的呢</p>
    <p>我是内容,真是有够好笑的呢</p>
    <slot><button>原来的按钮</button></slot>
    <slot><button>原来的按钮</button></slot>
<!--    默认值,若是没有插入元素的话默认显示这个按钮-->
  </div>
</template>
<div id="app">
  <cpn><button>这是插入插槽的按钮</button></cpn>
  <cpn>
<!--    如果有多个dom元素,则会全部加在一起替换插槽-->
    <p>hello</p>
    <p>hello</p>
    <p>hello</p>
    <p>hello</p>
  </cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      components:{
        cpn:{
          template:"#cpn"
        }
      }
    });
  </script>

这里出现了八个hello,验证了说法。

3.具名插槽的使用

在之前使用的插槽中我们无法控制只插入到那个插槽中,因为只要还有插槽在,它就会把它填满。而具名插槽就解决了这个问题,可以给每个插槽命名,然后指定需要插入的插槽进行插入元素。

<template id="cpn">
  <div>
    <slot name="left"><button>左边</button></slot>
    <slot name="center"><button>中间</button></slot>
    <slot name="right"><button>右边</button></slot>
  </div>
</template>
<div id="app">
  <cpn>
    <span slot="left">这是插到左边的span</span>
    <span slot="center">
      这是插到中间的span
    </span>
    <span slot="right">
      这是插到右边的span
    </span>
  </cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      components:{
        cpn:{
          template:"#cpn"
        }
      }
    });
  </script>
4.作用域插槽的案例

有些时候父组件对子组件中展示数据的方式不满意,希望按照自己的想法进行展示,这就需要用到作用域插槽,通过v-bind将子组件展示的数据绑定到子组件的属性中。然后在父组件中通过一个<template>标签,然后通过slot-scope属性拿到子组件传来的数据,slot-scope="命名拿到子组件传来的数据对象的变量名",后面就通过这个变量名来拿到需要展示的数据。

<template id="cpn">
  <div>
<!--    通过这样绑定属性的方式向slot-scope传入数据,就像是sessionScope,requestScope一样
        就是往这个空间中写入数据
-->
    <slot :languages="pLanguages" :message="message" name="list">
      <ul>
        <li v-for="language in pLanguages">{{language}}</li>
      </ul>
    </slot>
<!--
        若不止一个插槽,那就会传入多个数据对象,以slot为计数单位,几个slot就会有几个
        数据对象,若是没有数据的就是一个空对象。但是好像不是一个数组,使用数组的操作方式
        根本操作不了。而且开发中的话应该就只会拿一个插槽中的数据进行重新显示吧。所以若
        是有多个插槽,想要拿到指定的那个插槽的数据的话就可以使用具名插槽的方式。
-->
    <slot :languages="pLanguages" :message="message">

    </slot>
  </div>
</template>
<div id="app">
  <cpn>
<!--    slotData是我为拿到的数据对象的命名-->
    <template slot-scope="slotData" slot="list">
<!--    这里就可以按照父组件自己的想法进行显示-->
      {{slotData.languages.join(" - ")}}
    </template>
<!--    其实这里就是将父组件自己定义的模板来替代显示的,若是定义
        了多个的话后面的就会覆盖掉前面的,只会留下一个-->
<!--    <template slot-scope="slotData" slot="list">-->
<!--      {{slotData.languages.join(" * ")}}-->
<!--    </template>-->
  </cpn>
</div>
  <script src="../js/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      components:{
        cpn:{
          template:"#cpn",
          data() {
            return {
              pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift'],
              message:"你好呀"
            }
          }
        }
      }
    });
  </script>
<div>
<!--    通过这样绑定属性的方式向slot-scope传入数据,就像是sessionScope,requestScope一样
        就是往这个空间中写入数据
-->
    <slot :languages="pLanguages" :message="message" >
      <ul>
        <li v-for="language in pLanguages">{{language}}</li>
      </ul>
    </slot>
<!--
        若不止一个插槽,那就会传入多个数据对象,以slot为计数单位,几个slot就会有几个
        数据对象,若是没有数据的就是一个空对象。但是好像不是一个数组,使用数组的操作方式
        根本操作不了。不过我发现它会自动遍历所有的数据对象,将每个数据对象按照我们想要展
        示的形式进行展示,这需要每个对象中变量名要一致。若是有多个插槽,想要拿到指定名字
        的那个插槽的数据的话就可以使用具名插槽的方式。
-->
    <slot :languages="pLanguages" :message="message">

    </slot>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容