[TOC]

和搭积木,分类一样,把功能模块划分开。先搭好一个一个组件,然后合起来。
components.png

组件的基本使用方法

全局组件

创建构造器和注册组件要放在new Vue()之前

<div id="app1">
  <!-- 3.使用注册的组件 -->
  <X></X>
  <X></X>
</div>

<script>
  //1.创建组件构造器
  //extend方法传入一个对象
  const cpnC = Vue.extend({
    template:
    `<div>
<h2>这是组件的标题</h2>
<p>这是组件的一段文字</p>
  </div>`
  });
  //2.注册组件(全局组件,意味着可以在多个Vue实例下使用)
  //两个参数,①标签名,你想要什么样的标签使用这个组件。②上面组成主键的cpnC
  Vue.component("X",cpnC);


  const app =new Vue({
    el:"#app1",
    data:{
      massage:"   你好",
      age:0
    }
  })
</script>

局部组件

<div id="app1">
  <!-- 3.使用注册的组件 -->
  <X></X>
  <X></X>
</div>

<script>
  //1.创建组件构造器
  //extend方法传入一个对象
  const cpnC = Vue.extend({
    template:
    `<div>
<h2>这是组件的标题</h2>
<p>这是组件的一段文字</p>
  </div>`
  });
  const app =new Vue({
    el:"#app1",
    data:{
      massage:"   你好",
      age:0
    },
    components:{ //2.局部组件,X是组件名,cpnC就是上面的组件构建器
      X:cpnC
    }
  })
</script>

全局组件可以在多个Vue实例中使用,而局部组件只能在某个Vue实例中使用

语法糖简便写法

<div id="app1">
  <!-- 使用注册的组件 -->
  <X></X>
  <X2></X2>
</div>

<script>

  //简便注册全局组件
  Vue.component("X",{
    template:
    `<div>
<h2>组件标题</h2>
<p>一行字</p>
  </div>`
  });
  //Vue实例就是个root组件
  const app =new Vue({
    el:"#app1",
    data:{
      massage:"   你好",
      age:0
    },
    components:{
      X2:{template:`<div><h2>组件标题</h2><p>一行字</p></div>`}//局部组件
    }
  })
</script>

抽离模板写法

抽离模板一定要写一个大的标签,包裹子组件里面的全部标签。template里面只能有一层子标签

<div id="app1">
  <!-- 使用注册的组件 -->
  <X></X>
  <X2></X2>
</div>

<!-- 第一种抽离模板写法 -->
<script type="text/x-template" id="cpn1">
            <div>
                <h2>抽离模板写法1</h2>
  </div>   
</script>
<!-- 第二种抽离模板写法 -->
<template id="cpn2">
  <div>
    <h2>抽离模板写法2</h2>
  </div> 
</template>

<script>

  //简便注册全局组件
  Vue.component("X",{
    template:"#cpn1"
  });

  Vue.component("X2",{
    template:"#cpn2"
  });
  const app =new Vue({
    el:"#app1",
    data:{
    },
    components:{
    }
  })
</script>

父子组件

注册使用父子组件

<div id="app1">
  <!-- 使用注册的组件 -->
  <cpn2></cpn2>

  <!-- 在root组件里面没有注册cpn1组件,所以不能在这里使用,只能在注册它的地方使用 -->
  <!-- 子组件可以同时在多个父组件中注册 -->
</div>

<script>
  const cpnC1 = Vue.extend({
    template:
    `<div>
<h2>我是子组件</h2>
<p>这是组件的一段文字</p>
  </div>`
  });

  const cpnC2 = Vue.extend({
    template:
    `<div>
<h2>我是父组件</h2>
<p>下一行就是我的子组件cpn1</p>
<cpn1></cpn1>
  </div>`,
    components:{
      cpn1:cpnC1 //在第二组件里面注册组件
    }
  });

  //Vue实例就是个root组件
  const app =new Vue({
    el:"#app1",
    data:{
      massage:"   你好",
      age:0
    },
    components:{
      cpn2:cpnC2   //cpnC1在cpnC2里面注册了,但是cpnC2作为父组件还没注册,注册cpnC2的局部组件
    }
  })
</script>

组件数据

Vue实例有一个data,如果所有的数据全部放在Vue实例的data里面,将会非常臃肿。每个组件都有自己保存数据的地方

组件的data数据必须是一个方法,不是像Vue实例那样是一个对象。这个方法里面返回一个对象,这个返回的对象里面写数据。 为什么组件里面的data必须是一个方法? 组件是经常复用的,一个组件用了多次,如果data是一个对象的话,这些复用的组件用的是同一个data对象,数据共用了。组件里面是方法,每次复用都会调用data方法,返回一个新的对象,所以不会造成数据复用

<div id="app1">
  <!-- 使用注册的组件 -->
  <X></X>
  <X2></X2>
</div>


<template id="cpn1">
  <div>
    <h2>{{title}}</h2>
  </div> 
</template>

<script>


  Vue.component("X",{
    template:"#cpn1",
    data(){   //组件里面的data不能是一个对象,必须是一个方法,方法返回一个对象
      return {
        title:"这是标题"
      }
    }
  });

  const app =new Vue({
    el:"#app1",
    data:{
    },
    components:{
    }
  })
</script>

父子组件通信

每个组件里面都有生命周期,数据,方法等。父组件里面包含了很多子组件。一层套一层。但是执行网络请求,向服务器要数据的通常是最外面的一层组件。这种时候就需要数据在父子组件间传来传去了。

父传子

子组件要接受父组件传来到的数据,子组件里面选项props的值是可以是一个数组也可以是对象。
相当于给这个子组件设置属性,在Vue实例,el挂载的范围内,使用这个子组件,用v-ind绑定子组件自定义的props属性,绑定Vue实例里面的data数据。
在子组件里面就可以调用这个属性值。
如果不用v-bind,就相当于传入字符串而已。如果props对象值里面设置了默认值,v-bind没有传参,就使用默认值

props属性命名尽量不要用驼峰标识,因为html页面不区分大小写,在props里面用了驼峰命名,v-bind里面哪怕你写了驼峰也会被识别成小写,v-bind和props就不一致,传不过来数据

<div id="app1">
  <!-- 使用注册的组件 -->
  <cpn1 :ctext="text" :clist="list"></cpn1>
</div>
<template id="cpn1">
  <div>
    <h2>{{ctext}}</h2>
    <ul>
      <li v-for="item in clist">{{item}}</li>
    </ul>
  </div> 
</template>

<script>
  const cpn1={
    template:"#cpn1",
    data(){
      return {
        title:"这是标题"
      }
    },
    //props:['ctext',"clist"] //数组形式

    //还可以是对象形式,可以直接 限定传入的数据类型,值也可以是对象
    props:{
      ctext:{
        type:String,
        default:'默认值',
        required:true   //毕传值,不传就报错
      },
      clist:Array,
      clist2:{
        type:Array,
        default(){ //类型是对象或者数组时,默认值必须是一个方法
          return []
        }
      }
    }
  }

  const app =new Vue({
    el:"#app1",
    data:{
      text:"这是父组件的一段文字",
      list:["海贼王","火影忍者","名侦探柯南"],
      list2:["a","b","c","d"]
    },
    components:{
      cpn1     //增强字面量写法,直接把上面定义好的cpn1写到这里。cpn1就是组件名,cpn1里面的对象就是值
    }
  })
</script>

子传父

以下代码,在子组件的button点击过后,执行了子组件里面的方法btnclick,在这个子组件的方法里面,用this指向当前的子组件,定义了3个事件。第一个参数为事件名,第二个参数为需要传递的数据。

在父组件里面使用这个子组件,然后v-on监听这个子组件cpn1独有的自定义事件,和v-on监听click等默认事件一样的用法。绑定到父组件的方法。

如果子事件传过来的有数据,父组件方法里面写上形参,并不用传实参,因为会像默认事件一样。默认事件不写实参就会传入event事件对象,子组件的自定义事件没有event事件对象,所以默认传入的就是自定义事件的参数。如果要传入多个参数,自定义时写数组,对象。
父组件传给子组件数据经过子组件加工后,又传回给父组件,同理,看代码有写

<div id="app1">
  <!-- 使用注册的组件 -->
  <cpn1 :cmassage="massage" @new_event="vue_method" @new_event2="vue_method2" @new_event3="vue_method3"></cpn1>
</div>
<template id="cpn1">
  <div>
    <input type="text" v-model="text" placeholder="子组件自带数据"/>
    <input type="text" v-model="cmassage" placeholder="父组件传过来的数据"/>
    <button @click="btnclick">点击发送给父组件</button>
  </div>

</template>

<script>
  const cpn1={
    template:"#cpn1",
    data(){
      return {
        text:"这是文字"
      }
    },
    props:{
      cmassage:{
        type:String,
      }
    },
    methods:{
      btnclick(){
        //自定义事件
        this.$emit('new_event');
        this.$emit('new_event2',this.text)
        this.$emit('new_event3',[this.text,this.cmassage])//多个参数用数据,对象
      }
    }
  }

  const app =new Vue({
    el:"#app1",
    data:{
      massage:"父组件的massage"
    },
    components:{
      cpn1     
    },
    methods:{
      vue_method(){
        console.log("点击子组件的按钮,传到了父组件这里");
      },
      vue_method2(text){
        console.log("这个父组件第二个方法,传过来的参数:"+text);
      },
      vue_method3(datas){
        console.log("传过来子组件本身自带的数据:"+datas[0]+",父组件传给子组件加工后又传给父组件的数据:"+datas[1]);
      }
    }
  })
</script>

父访问子

在父组件里面用$children或者$refs就能访问到子组件的属性方法了

  • $children能拿到在当前组件下的全部使用的子组件,并不是全部注册的,有时候一个组件重复使用。通过索引来拿,但是如果中途加进入几个子组件,索引就乱了。通常不使用这个方法访问子组件
  • $refs是一个对象类型,默认为空。需要在使用子组件时给子组件加上ref属性,给属性赋值一个名字。就能通过refs.名字 来访问到对应的子组件,一般用这个方法访问子组件 ```html

<a name="ESOlD"></a>
## 子访问父
在子组件里面可以通过**$parent**来访问他的父组件,但是不能访问爷爷组件,而且不建议这么写,子组件本来就是抽出来的模块,需要复用。子组件如果知道了父组件的相关特性,那就依赖死了,组件之间需要独立性。
```html
<div id="app1">
  <cpn1></cpn1>
</div>

<template id="cpn1">
  <div>
    <h2>这是第二层组件</h2>
    <cpn2></cpn2>
  </div>
</template>

<template id="cpn2">
  <div>
    <h2>这是最里面的一层组件</h2>
    <button @click="btnclick">按钮</button>
  </div>
</template>

<script>
  const cpn2={
    template:"#cpn2",
    data(){
      return{
        name:"李四"
      }
    },
    methods:{
      btnclick(){
        //访问父组件,不能访问爷爷组件
        console.log(this.$parent);
        console.log(this.$parent.name);
        //访问根组件
        console.log(this.$root.massage);
      }
    }
  }

  const cpn1={
    template:"#cpn1",
    data(){
      return {
        name:"张三"
      }
    },
    methods:{

    },
    components:{
      cpn2
    }
  }

  const app =new Vue({
    el:"#app1",
    data:{
      massage:"一条消息"
    },
    components:{
      cpn1
    },
    methods:{

    }
  })
</script>

slot插槽(vue2.6以上用v-slot)

生活中,比如电脑上就有很多插槽,usb插槽好几个,插上鼠标,键盘,数位板,就相当于给原来的电脑扩展了一些功能。组件也一样,让其更加具有拓展性。抽取共性,保留不通。一样的地方封装在组件里面,复用时可能会不一样的地方设置个slot插槽,需要什么东西使用组件时插进入

在定义子组件模板时,考虑到组件复用时可能不一样的地方,用标签预留一个插槽。
使用组件时,在组件标签中间插入需要插入的东西,会自动填充到slot里面

单个slot

<div id="app1">
  <cpn1><button>按钮</button></cpn1>
  <cpn1></cpn1>
  <cpn1></cpn1>
</div>

<template id="cpn1">
  <div>
    <h2>这是子组件</h2>
    <input type="text" />
    <slot></slot>
  </div>
</template>


<script>

  const cpn1={
    template:"#cpn1",
    data(){
      return {
        name:"张三",
        age:30
      }
    },
    methods:{

    }
  }

  const app =new Vue({
    el:"#app1",
    data:{
      massage:"一条消息"
    },
    components:{
      cpn1
    },
    methods:{

    }
  })
</script>

默认slot

可以在slot里面设置一个默认的内容,如果使用组件时,没有插入内容,就使用默认的

<div id="app1">
  <cpn1><button>非默认</button></cpn1>
  <cpn1></cpn1>
  <cpn1></cpn1>
</div>

<template id="cpn1">
  <div>
    <h2>这是子组件</h2>
    <input type="text" />
    <slot><button>默认按钮</button></slot>
  </div>
</template>

多个slot

如果多个slot,使用组件时只插入一个,会替换掉全部slot

<div id="app1">
  <cpn1><h1>新内容</h1></cpn1>
  <cpn1></cpn1>
  <cpn1></cpn1>
</div>

<template id="cpn1">
  <div>
    <slot><h2>左边</h2></slot>
    <slot><h2>中间</h2></slot>
    <slot><h2>右边</h2></slot>
  </div>
</template>

v-slot

一般使用插槽都使用v-slot,上面的是老版本的写法,v-slot在vue2.6之后版本才有。
多个插槽,一般都要给标签加一个name属性取个名字。
在使用组件时,在组件中间加入