vue - 父子组件通信、插槽

目录

组件

父子组件

组件的语法糖注册方法

组件使用数据

父子通信

 props的大小写

子父通信

父组件访问子组件

子组件访问父组件/访问根组件

插槽

具名插槽 

作用域插槽


组件

组件的定义——实现应用中局部功能代码和资源的集合;

  • 全局组件 Vue.component('标签名' , { })  可以在多个vue实例中使用;
  • 局部组件 components:{'标签名' : { } } 只能在在注册的vue实例中使用;
  1. 创建组件构造器; 传入一个配置对象; 这个对象除了el配置项,其他的都有;
  2. 注册组件; 两个参数, '组件标签' 和 构造器名字
  3. 使用组件; 在挂载点中使用;
<body>
    <div id="app">
        <!--第三步 使用全局组件 -->
        <cpn></cpn>
        <!--第三步 使用局部组件 -->
        <cpn2></cpn2>
    </div>

    <script>
        // 第一步 创建组件的构造器
        const cpnC = Vue.extend({
            template:
            `<div>
                <p>你好</p>
                <h3>hello</h3>
            </div>
            `
        })
        //第二步 注册全局组件
       Vue.component('cpn',cpnC)

        const vm = new Vue({
           el:'#app',
           data:{},
           methods:{},
        // 第二步   注册局部组件
           components:{
               // 对象的key可以用引号,也可以不用
               'cpn2':cpnC,
            //    cpn2:cpnC
           }
        });
    </script>
</body>

注意:

  • 调用Vue.extend()方法 - 创建组件构造器
  • 调用Vue.component()方法 - 注册组件
  • 在Vue实例的作用范围内 - 使用组件
  • 组件构造器中的template中可以用引号(不能换行),用反引号(字符串模板可以换行);要有一个根标签<div>包裹;
  • 在vue实例的配置项,components:{ } 注册局部组件 ;
  • 对象的key值,可以加引号(当做字符串),也可以不加(变量)
  • 对象字面量的增强写法:  函数名: funtion() 可以省略:function ;
  • 对象的字面量增强写法: 对象的属性名和属性值一样,是个变量可以省略一个;

父子组件

子组件在父组件中注册,在父组件中使用;

在父组件的构造器对象的配置项: coponents:{ } 中注册子组件,;在template模板中使用组件;

<body>
    <div id="app">
      <!-- 使用父组件B,包含子组件A -->
      <cpn-b></cpn-b>
      <hr>
      <!-- 在root根组件中注册组件A,直接使用子组件A -->
      <cpn-A></cpn-A>
    </div>

    <script>
      // 创建子组件构造器 cpnA
      const cpnA = Vue.extend({
        template:
        `
        <div>
          <h5> 我是子组件 </h5>
        </div>
        `
      })
      // 创建父组件构造器 cpnB
      const cpnB = Vue.extend({
        template:
        // 在父组件的tempalte模板中使用子组件
        `
        <div>
          <h3> 我是父组件 </h3>
          <cpn-a></cpn-a>
        </div>
        `,
        // 在父组件的components中注册子组件
        components:{
          'cpn-a':cpnA
        }
      })

        const vm = new Vue({
           el:'#app',
           data:{},
           methods:{},
           // 注册局部组件 父组件
           components:{
             'cpn-b':cpnB,
             // 想直接单独使用子组件A,在根组件root组件中注册
             'cpn-a':cpnA
           }
          
          });
    </script>
</body>

组件的语法糖注册方法

省略Vue.extend()书写,直接把配置对象{ }写到注册组件中去(替换了构造器名字);

构造器名字,直接写成一个对象;这个对象就是之前构造器传入的配置对象;

但Vue会仍会自动的将此对象给Vue.extend()

// 内部会自动调用Vue.extend()
<body>
    <div id="app">
      <!-- 使用全局组件 -->
      <cpn-a></cpn-a>
      <hr>
      <!-- 使用局部组件 -->
      <cpn-b></cpn-b>
    </div>
    <script>
      // 注册全局组件
      Vue.component("cpn-a",{
        template:'<div><h4>我是全局组件</h4></div>'
      })
        const vm = new Vue({
           el:'#app',
           data:{},
           methods:{},
           // 注册局部组件
           components:{
             "cpn-b":{
               template:`
               <div>
                 <p>我是局部组件</p>
                </div>
                `
             }
           }
        });
    </script>
</body>

组件构造器的配置对象中的template模板的抽离写法: 

template模板写:  '选择器';

template便签内写:  选择器; 需要有个根标签<div>包裹;<div>内写需要的模板;

template标签不会被渲染,

<body>
    <div id="app">
      <!-- 使用组件 -->
      <cpn></cpn>
    </div>
    <!-- template模板的抽离写法 -->
    <template id="aaa"> // 选择器
      <div>
        <h3>模板的分离写法</h3>
      </div>
    </template>
    <script>
      // 注册组件
      Vue.component('cpn',{
        template:'#aaa' //选择器
      })
        const vm = new Vue({
           el:'#app',
           data:{},
           methods:{}
        });
    </script>
</body>

组件使用数据

子组件不能直接使用父组件中的数据;

vue实例也是一个父组件,root根组件;

组件可以使用自己组件内的data配置项中的数据;

组件的data属性,必须是一个函数,且返回一个对象;

组件也有自己的data属性(在vue.extend()的配置对象中),必须是一个函数,且返回一个对象;

<body>
  <div id="app">
    <!-- 使用组件 -->
    <cpn></cpn>
  </div>
  <template id="aaa">
    <div>
      <!-- 使用组件data中的数据 -->
      <h3>{{msg}}</h3>
    </div>
  </template>
  <script>
    // 注册组件
    Vue.component('cpn', {
      template: '#aaa',
      // 必须是一个函数,且返回一个对象
      // 对象的字面量增强写法 省略 :function
      data() {
        return {
          msg: '组件的data必须是一个函数'
        }
      },
      // data:function () {
      //   return{
      //     msg:"组件的data必须是一个函数,且返回一个对象"
      //   }
      // }
    })
    const vm = new Vue({
      el: '#app',
      data: {},
      methods: {}
    });
  </script>
</body>

父子通信

通过props实现父子通信;

把父组件的数据传递给子组件;子组件来使用;

父组件通过props向子组件传递数据 [props=> property 属性]

方法一:   props:[ ]

  • 在子组件的配置对象中的添加props配置项,是一个数组;
  • 数组中的字符串是变量, 用来接受父组件传递过来的数组;
  • 使用子组件时,用v-bind属性绑定, 用props中的变量接受父组件中的数据;
  • 在子组件的模板中使用这些props[ ]中的变量;
<body>
  <div id="app">
    <!-- 使用子组件,父组件是vue实例 -->
    <!-- 通过v-bind绑定props中的变量;
       接收父组件传递给子组件的数据 -->
    <cpn :cmsg="msg" :cmovies="movies"></cpn>
  </div>
  <template id="aaa">
    <div>
      <!-- 使用父组件传递过来的数据 -->
      <h2>{{cmsg}}</h2>
      <ul>
      <!-- 使用父组件传递过来的数据 -->
        <li v-for="item in cmovies">{{item}}</li>
      </ul>
    </div>
  </template>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        msg: '你很棒',
        movies: ['西游记', '三国演义', '红楼梦', '水浒传']
      },
      methods: {},
      // 注册局部组件
      components: {
        // 组件中的对象的key(属性名) 就是组件标签名, 可以加引号,也可以不加
        'cpn': {
          template: '#aaa',
          // 数组中的字符串就是变量,
          props: ['cmsg','cmovies'],
          data() {
            return {
            }
          }
        }
      }
    });
  </script>
</body>

方法二:  props:{ }

  • props是一个对象时, 可以对props中的变量(key)进行类型限制;
  • 这些变量是用来接受父组件传递过来的数据的;
  • 这些变量是key,可以加引号,也可以不加;
  • 这些变量对应的value可以是一个对象,对这个变量进行限制;
  • required: true 表示这个变量必传;
  • default 是当没有数据接收时,显示默认值;
  • 当数据类型是数组或对象时, 默认值必须是一个函数,且return 默认值;
   props: {
    cmsg:String, //字符串类型
     cbooks: {
      type:[ Array, Object ]// 数组或对象类型
      // 当数据类型是对象或数组时, default必须是一个函数,且返回默认值
      //default是没有接受数据时,才会显示默认值,与required一起添加到同一个对象上有点冲突;
       default() {
         return ['大闹天宫']
         //return { }
         },
      // 必须要传的值 
       required: true
             }
       }

props的数据类型验证:

 props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)

    propA: Number,// 要求这个变量的数据类型是Number;

    // 多个可能的类型
    propB: [String, Number],//数据类型是字符串或数字类型;

    // 必填的字符串
    propC: {
      type: String, // 数据类型是字符串类型,且这个变量必须使用;
      required: true
    },

    // 带有默认值的数字
    propD: {
      type: Number,
      default: 100  // 默认值,当没有接受数据时,默认值显示
    },

    // 带有默认值的对象或数组
    propE: {
      type:[Object,Array],
      // 当数据类型是对象或数组时, 默认值必须函数获取且 return 默认值;
      default: function () {
        return { message: 'hello' }
        //return [ ]
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        // 这个值必须匹配下列字符串中的一个
        return ['success', 'warning', 'danger'].indexOf(value) !== -1
      }
    }
  }

 props的大小写

  • camelCase (驼峰命名法)   myName;
  • kebab-case (短横线分隔命名)  my-name;

  1.    在props里 接收数据的变量定义时, 用驼峰命名; 
  2.    但接收父组件的数据时(v-bin:) 要用短横线分割法命名;
  3.     在template模板中,使用变量时 用驼峰命名
<body>
    <div id="app">
      <!-- v-bind: props的变量名要用短横线分割法,来接收父组件的数据 -->
      <cpn v-bind:my-first-name='name'></cpn>
    </div>
<template id="aaa">
  <div>
    <!-- 使用变量接收来的数据 这里使用驼峰名 -->
    <h2>{{myFirstName}}</h2>
  </div>
</template>
    <script>
      Vue.component('cpn',{
        template:"#aaa",
        props:{
          // 在props里 接收数据的变量定义时, 用驼峰命名; 
          // 但接收父组件的数据时(v-bin:)要用短横线分割法命名;
          // 在template模板中,使用变量时 用驼峰命名;
          myFirstName:{ // 对象的key也可以加引号;'myFirstName'
            type:String,
            // default:'牛B',
            required:true
          }
        }
      })
      // vue的实例 => root根组件
        const vm = new Vue({
           el:'#app',
           data:{
             name:'Liu'
           },
           methods:{}
        });
    </script>
</body>

子父通信

通过自定义事件,进行子父通信;

把子组件中的数据传递给父组件,父组件来使用;

  • 在子组件的methods{ }中,子组件的事件处理函数中,通过 this.$emit('自定义事件' ,参数 ) 把自定义事件发射出去;
  • 在父组件的标签中,接收这个自定义事件,并通过v-on进行事件监听这个自定义事件, 把自定义事件 赋值给父组件,让父组件来处理这个自定义事件;
  • 在父组件的methods{ } 中, 处理父组件的处理函数;
  • 子组件传递过去的参数,默认父组件的处理函数可以直接使用;

<body>
    <div id="app">
      <!-- 使用子组件 -->
      <!-- 父组件中接受这个自定义事件,并通过v-on事件监听,把子组件发射出来的自定义事件给父组件,让父组件来处理 -->
      <!-- 默认会把子组件发射出来的参数赋值给父组件 -->
      <cpn @itemclick='fclick'></cpn>
    </div>
<template id="aaa">
  <div>
    <!-- 子组件自己的事件处理函数,item参数就是数组中的每一项 -->
    <button v-for="item in fenlei" @click='btnClick(item)'>
      {{item.name}}
    </button>
  </div>
</template>
    <script>
        const vm = new Vue({
           el:'#app',
           data:{

           },
           // 父组件的方法
           methods:{
             // 这个参数,是子组件发射出来的自定义事件传递过来的参数
             fclick(item){
               console.log('子父通信',item);
             }
           },
          //  注册子组件
           components:{
             cpn:{
               template:'#aaa',
               data() {
                 return {
                   fenlei:[
                     {id:1,name:'西游记'},
                     {id:2,name:'水浒传'},
                     {id:3,name:'三国演义'},
                     {id:4,name:'红楼梦'}
                   ]
                 }
               },
               //  子组件自己的方法,里面写事件处理函数
               methods: {
                 btnClick(item){
                  //  console.log(item); //子组件自己的item
                //  在子组件的事件中,把自定义的事件发射给父组件
                // 两个参数,'自定义事件名',参数 (把参数传出去,默认的传到父组件去)
                // 这个item是子组件自己的数据
                  this.$emit('itemclick',item)

                 },
               },
             }
           }
        });
    </script>
</body>

父组件访问子组件

父组件可以直接访问子组件的数据,调用子组件的方法等;

  • $refs ( 子组件中 ref='标记')     =>访问指定的子组件
  • $children  => 访问所有子组件
  • 父组件通过$children访问子组件;
  • 会得到一个数组,通过数组的下标访问,不推荐这种方式,因为可能插入新的组件,导致下标错位;
  • vue组件数组  [VueComponent]
  • <body>
        <div id="app">
          <cpn></cpn>
          <cpn2></cpn2>
          <button @click=fbtnClick>点击我</button>
        </div>
        <!-- 第一个组件的模板 -->
    <template id="aaa">
      <div>
        <h4>第一个子组件</h4>
      </div>
    </template>
    <template id="bbb">
      <div>
        <h4>第二个子组件</h4>
      </div>
    </template>
    
        <script>
            const vm = new Vue({
               el:'#app',
               data:{},
               methods:{
                fbtnClick(){
                  console.log(this.$children);//vue组件数组  [VueComponent]
                  this.$children[0].btnClick() // 调用第一个子组件的里的方法
                  console.log(this.$children[0].msg); // 打印第一个子组件的数据
                  this.$children[1].btnClick2() // 调用第二个子组件的里的方法
                  console.log(this.$children[1].msg);// 打印第二个子组件的数据
                }
                },
               components:{
                 // 第一个子组件
                 'cpn':{
                   template:"#aaa",
                   data() {
                     return {
                       msg:'第一个子组件的msg数据'
                     }
                   },
                   methods: {
                     btnClick(){
                       console.log("第一个子组件的方法");
                     }
                   },
                 },
                 // 第二个子组件
                 'cpn2':{
                   template:"#bbb",
                   data() {
                     return {
                       msg:'第二个子组件的msg数据'
                     }
                   },
                   methods: {
                     btnClick2(){
                       console.log("第二个子组件的方法");
                     }
                   },
                 },
               }
            });
        </script>
    </body>
    
  • 父组件通过$refs访问子组件 (推荐)
  • 得到的, 默认是一个空的对象
  • 需要在组件上添加 ref='标记',这个属性; 这个对象不为空,'标记'作为对象的key;
  • {a: VueComponent}
  • 这个'标记'会作为对象的key,通过点语法来访问,得到value;
  • 父组件通过这个方法访子组件更准确,拿到指定的子组件;
<body>
  <div id="app">
    <!--
    $refs访问子组件 ;
    要给子组件添加 ref='标记' 这个属性;
    这个'标记'作为对象的key, 通过点语法 获取对应的value值;
    通过 this.$refs.'标记'. 来访问指定的子组件
   -->
    <cpn ref="a"></cpn>
    <button @click=fclick>点击我</button>
  </div>
  <template id="aaa">
    <div>
      <h4>{{msg}}</h4>
    </div>
  </template>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {},
      methods: {
        fclick(){
          console.log(this.$refs); // 不给子组件添加ref='标记'属性,默认是一个空对象 {},
          //  加了这个属性之后,就会添加 {a: VueComponent},这个'标记'作为key
          // 子组件指定的'标记'作为这个对象的key, 通过点语法 来获取对应组件的value;
          console.log(this.$refs.a.msg); // 打印指定子组件的data中的数据msg
          this.$refs.a.btnClick() //调用指定子组件的方法
        }
      },
      components: {
        'cpn': {
          template:"#aaa",
          data() {
            return {
              msg:'我是子组件msg'
            }
          },
          methods: {
            btnClick(){
              console.log('子组件的方法');
            }
          },
        }
      }
    });
  </script>
</body>

子组件访问父组件/访问根组件

子组件可以访问父组件,直接使用父组件的数据,或调用父组件的方法

  • this.$parent 访问上一级父组件
  • this.$root 访问根组件
<body>
  <div id="app">
    <!-- 在父组件中使用子组件 son -->
    <son></son>
  </div>
  <!--  -->
  <!-- 子组件模板 son -->
  <template id="son">
    <div>
      <h2>我是子组件</h2>
      <button @click='sonClick'>子组件按钮</button>
      <!-- 在子组件中使用孙组件 -->
      <sun></sun>
    </div>
  </template>
  <!--  -->
  <!-- 孙组件模板 sun -->
  <template id="sun">
    <div>
      <h4>我是孙组件</h4>
      <button @click='sunClick'>孙组件按钮</button>
    </div>
  </template>
  <!--  -->
  <script>
    // 根组件(vue实例)
    const vm = new Vue({
      el: '#app',
      data: {
        'rootMsg': "我是根组件(vue实例)的数据---rootMsg"
      },
      methods: {
        rootClick() {
          console.log('我是根组件的事件---rootClick');
        }
      },
      // 
      //  注册子组件 son
      components: {
        'son': {
          template: '#son',
          data() {
            return {
              sonMsg: '我是子组件的数据-----sonMsg'
            }
          },
          methods: {
            sonClick() {
              console.log('我是子组件的事件----sonClick');
              console.log('子组件的this.$parent==>',this.$parent); //打印this.$parent
              console.log('子组件的this.$root==>',this.$root);   //打印根组件this.$root(vue的实例)
              console.log('子组件的this.$parent==>',this.$parent.rootMsg); // 打印父组件的data中的rootMsg
              this.$parent.rootClick()  // 调用父组件的rootClick()方法 // 同时父组件的methods配置项中也处理着其他业务;
              console.log('-------------分割线--------------');
            }
          },
          // 
          //  注册孙组件
          components: {
            'sun': {
              template: '#sun',
              data() {
                return {
                  'sunMsg': '我是孙组件的数据-----sunMsg'
                }
              },
              methods: {
                sunClick() {
                  console.log('我是孙组件的事件----sunClick');
                  console.log('孙组件的this.$parent==>',this.$parent); //打印this.$parent
                  console.log('孙组件的this.$root==>',this.$root);   //打印根组件this.$root(vue的实例)
                  console.log('孙组件的this.$parent==>',this.$parent.sonMsg); // 打印父组件的data中的rootMsg
                  this.$parent.sonClick()  // 调用父组件的sonClick()方法
                  console.log('-------------分割线--------------');
                }
              },
            }
          }
        }
      }
    });
  </script>
</body>

插槽

  • 具名插槽
  • 匿名插槽
  • 作用域插槽
  • 预留插槽就是预留一个空间,插槽内放东西,让组件具有更强的扩展性;
  • 插槽写在组件的template模板中;
  • <slot>的位置即组件标签的位置;
  • 插槽就是 把组件标签内容的显示;
  • 插槽的三种情况如下:
  • 1.组件标签内无内容, <slot>内放内容, 显示<slot>里的内容,插槽的默认值;
  • 2.组件标签内有内容,<slot>内无内容,显示组件标签的内容;
  • 3.组件标签内和<slot>内都有那内容, 组件里的内容会替换<slot>里的内容;
  • 3.组件标签内的内容会作为一个整体  替换插槽内所有的内容

    <div id="app">
      <!-- 使用组件 -->
      <cpn>
        <p>你好</p>
        <p>hello</p>
      </cpn>
      <hr />
      <cpn></cpn>
    </div>
    <!-- 子组件的template模板 -->
    <template id="aaa">
      <div>
        <!-- 插槽 -->
        <slot>
          <p>AAA</p>
        </slot>
        <h4>匿名插槽</h4>
        <!--  -->
        <slot>默认值</slot>
      </div>
    </template>

具名插槽 

 具名插槽 : 有名字的插槽;

<span slot="aaa"></span>
<slot name="aaa"></slot>
  • 组件标签内的 元素添加slot属性
  • 插槽slot标签 添加 name 属性
  • 这两个属性值相同,即可更改对应的内容;
  • 组件标签内的内容会作为一个整体  替换插槽内所有的内容
  <div id="app">
    <cpn>
      <button slot="center">Center</button>
    </cpn>
    <!-- <cpn></cpn> -->
  </div>
  <template id="aaa">
    <div>
      <slot name='left'><span>左边</span></slot>
      <slot name='center'><span>中间</span></slot>
      <slot name='right'><span>右边</span></slot>
    </div>
  </template>

编译作用域

父组件模板的所有东西都会在父级作用域内编译;

子组件模板的所有东西都会在子级作用域内编译。

作用域插槽

父组件替换插槽的标签,但是内容由子组件来提供。

  • 在子组件模板的使用插槽, 这个插槽通过v-bind属性绑定, 把子组件的数据赋值给标签B;  v-bind:bbb="books"
  • 使用子组件标签时,使用<template>包裹内容,<template>添加slot-scope属性, slot-scope = '标记A';
  • 通过 标记A.标记B 使用子组件的数据
  <body>
    <h2>作用域插槽: 父组件替换插槽的标签,但是内容由子组件来提供。</h2>
    <div id="app">
      <!-- 使用第一个子组件 -->
      <cpn></cpn>
      <!-- 使用第二个子组件 -->
      <cpn>
        <!-- 在使用组件时,子组件标签的内部写template标签 添加固定的 slot-scope 属性 -->
        <!-- slot-scope = "标记aaa" -->
        <template slot-scope="aaa">
          <div>
            <!-- 通过点语法 使用子组件的传过来的数据bbb -->
            <div>{{aaa.bbb.join("***")}}</div>
            <span v-for="item in aaa.bbb">{{item}}***</span>
          </div>
        </template>
      </cpn>
    </div>
    <template id="aaa">
      <!-- 作用域编译,子组件模板的所有东西都会在子级作用域内编译 -->
      <div>
        <!--在slot插槽上, 把子组件的数据books通过v-bind属性绑定 赋值给 标记 bbb -->
        <slot v-bind:bbb="books">
          <span v-for="item in books">{{item}}---</span>
        </slot>
      </div>
    </template>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {},
        methods: {},
        methods: {},
        components: {
          cpn: {
            template: "#aaa",
            data() {
              return {
                books: ["西游记", "水浒传", "红楼梦", "三国演义"],
              };
            },
          },
        },
      });
    </script>
  </body>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值