从“心”认识Vue(五):父组件与子组件


前言

学习vue的时候学完模板语法,上来就开始了脚手架,虽然上手快了点,但是感觉还是少了一点衔接,知识遇到了断层,于是自己就再补了一点,理解起来比较顺畅一些。


一、父组件与子组件的关系

我们将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该封装的组件的文件叫做父组件,被引入的组件叫做子组件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
<!--  4.使用父组件2-->
  <comp2></comp2>
</div>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
  //1.注册组件构造器对象
  const comp1 = Vue.extend({
    template: `
        <div>
        <h2>我是标题1</h2>
        <p>我是内容,哈哈哈哈</p>
        </div>
        `
  })

  const comp2 = Vue.extend({
    template: `
        <div>
        <h2>我是标题2</h2>
        <p>我是内容,嘻嘻嘻嘻</p>
          <comp1></comp1>
        </div>
        `,
    //3.注册子组件1,并在comp2里面使用组件1
    components: {
      comp1:comp1
    }
  })


  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    },
    //2.注册父组件2
    components:{
      comp2:comp2
    }
  })
</script>
</body>
</html>

二、注册组件语法糖

语法糖,也称糖衣语法,指的是计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。即在功能实现上与原代码一模一样,但是用法很简洁方便,提高程序员工作效率。

例子1中的注册组件比较管繁琐,vue提供了注册组件的语法糖,即省略了注册组件的步骤,直接用一个对象来代替:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <comp1></comp1>
  <comp2></comp2>
</div>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
  //1.全局组件注册的语法糖
  // const comp1 = Vue.extend()
  //2。注册组件
  Vue.component("comp1",{
    template: `
    <div>
    <h2>我是标题1</h2>
    <p>我是内容,哈哈哈哈</p>
    </div>
    `
  })

  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    },
    //局部组件语法糖
    components: {
      "comp2": {
        template: `
          <div>
          <h2>我是标题2</h2>
          <p>我是内容,xixix</p>
          </div>
          `
      }
    }
  })
</script>
</body>
</html>

三、模板与组件的分离写法

将模板与组件分离开来写,将使代码变得更加清晰。

vue提供了两种方案定义html内容:

  • 一是使用script标签
  • 二是使用templete标签
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <comp1></comp1>
</div>

<!--  1.script标签写法,注意类型必须是text/x-template-->
<!--<script type="text/x-template" id="comp1">-->
<!--  <div>-->
<!--    <h2>我是标题1</h2>-->
<!--    <p>我是内容,哈哈哈哈</p>-->
<!--  </div>-->
<!--</script>-->

<!--2.template标签-->
<template id="comp1">
    <div>
      <h2>我是标题1</h2>
      <p>我是内容,哈哈哈哈</p>
    </div>
</template>
<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
  //1。注册一个全局组件
  Vue.component("comp1", {
    template: "#comp1"
  })
  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    }
  })
</script>
</body>
</html>

四、组件可以访问vue实例数据吗?

先测试一下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <comp></comp>
</div>

<template id="comp1">
  <div>
    <h2>{{title}}</h2>
    <p>我是内容,哈哈哈哈</p>
  </div>
</template>

<script src="node_modules/vue/dist/vue.min.js"></script>

<script>
  //1.注册全局组件
  Vue.component("comp",{
    template:"#comp1",
  })

  new Vue({
    el: "#app",
    data: {
      msg: "haha",
      title: "我是标题"
    }
  })
</script>
</body>
</html>

结果:vue报错,title未定义,那么说明组件不可以访问vue实例数据。
在这里插入图片描述

  • 组件是一个单独功能模块的封装,有自己的html模板,也应该有自己的数据data;
  • 组件里也有一个自己的data属性
  • 组件的data是一个函数, 而且这个函数返回一个对象,对象内保存着数据

五、为什么组件的data必须是一个函数?

  • 首先来看如果不是一个函数会是怎样,按照vue实例里的写法:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
<!--  创建组件实例-->
  <comp></comp>
  <comp></comp>
  <comp></comp>
</div>

<template id="comp">
  <div>
    <h3>当前计数:{{count}}</h3>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>

  //1.注册全局组件
  Vue.component("comp",{
    template:"#comp",

     data:{
       count:0
     },//报错,如果这样不同组件会共享同一个数据,造成相互影响

    methods:{
      increment(){
        this.count++
      },
      decrement(){
        this.count--
      }
    }
  })
  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    }
  })
</script>

</body>
</html>

结果:报错.
在这里插入图片描述

  • 我们再给它传入一个对象试试:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
<!--  创建组件实例-->
  <comp></comp>
  <comp></comp>
  <comp></comp>
</div>

<template id="comp">
  <div>
    <h3>当前计数:{{count}}</h3>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>

  const obj1 = {
    count:0
  }
  //1.注册全局组件
  Vue.component("comp",{
    template:"#comp",

    // data:{
    //   count:0
    // },//报错,如果这样不同组件会共享同一个数据,造成相互影响
    data(){
      return obj1;//不同组件获取到同一个对象,数据相互影响
    },
    methods:{
      increment(){
        this.count++
      },
      decrement(){
        this.count--
      }
    }
  })
  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    }
  })
</script>

</body>
</html>

再来看一下结果:
在这里插入图片描述
结果发现三个组件之间的数据被相互影响了,这是我们不愿意看到的。因为我们需要组件之间数据相互独立。

接着我们将它改为正确的写法:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
<!--  创建组件实例-->
  <comp></comp>
  <comp></comp>
  <comp></comp>
</div>

<template id="comp">
  <div>
    <h3>当前计数:{{count}}</h3>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>

  //const obj1 = {
  //  count:0
  //}
  //1.注册全局组件
  Vue.component("comp",{
    template:"#comp",
    //data数据相互独立
    data(){
      return {
        count:0
      }
    },
    // data数据相互影响
    // data:{
    //   count:0
    // },//报错,如果这样不同组件会共享同一个数据,造成相互影响
    // data(){
    //   return obj1;//不同组件获取到同一个对象,数据相互影响
    // },
    methods:{
      increment(){
        this.count++
      },
      decrement(){
        this.count--
      }
    }
  })
  new Vue({
    el: "#app",
    data: {
      msg: "haha"
    }
  })
</script>

</body>
</html>

结果正确了:
在这里插入图片描述
到这里我们可以得出结论:

  • 1.组件data如果不是一个函数,vue会报错;
  • 2.组件data需要每个组件之间都保持数据的独立,每个组件调用data时都会返回一个新的对象,这就确保了组件之间数据不会相互影响。

六、父子之间通信

1.父传子

步骤:

  • 1.在父级添加上数据
  • 2.在子级添加props声明需要传递的数据变量
  • 3.在组件实例中传递数据
  • 4.在子组件中使用数据
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
  <!--      2.carr是子组件的属性,接收到来自父级的数据-->
  <comp :carr="arr" :cmsg="msg"></comp>
  <!--    <comp :carr="arr" ></comp> 当cmsg为required时这里会报错-->
</div>


<template id="comp">
  <div>
    <ul>
      <!--        3.在子组件内可以使用到来自父级的数据-->
      <li v-for="(v,i) in carr">{{v}}</li>
    </ul>
    {{cmsg}}
  </div>
</template>
<!--  使用生产版本一些错误会被忽略,而开发版本则不会-->
<script src="node_modules/vue/dist/vue.js"></script>
<script>
  //子组件
  const comp = {
    template: "#comp",
    //1.props声明需要从父级接收到的数据
    // props:["carr","cmsg"]
    //1.props验证
    props: {
      // 1.1限制类型
      // carr:Array,
      // cmsg:String

      // 1.2提供默认值
      cmsg: {
        type: String,
        default: "abcabc",
        required: true  //该属性必须传值
      },
      carr: {
        type: Array,
        //在高版本中数组默认值必须是一个函数
        default() {
          return []
        }
      }
    }
  }
  //root组件
  new Vue({
    el: "#app",
    data: {
      //0.父级里的数据
      msg: "haha",
      arr: ["xixi", "haha", "heiehie"]
    },
    components: {
      comp
    }
  })
// 总结步骤:
//   1.在父级添加上数据
//   2.在子级添加props声明需要传递的数据变量
//   3.在组件实例中传递数据
//   4.在子组件中使用数据
</script>
</body>
</html>

2.子传父

步骤:

  • 1.子组件里添加数据
  • 2.子组件模板添加点击事件
  • 3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
  • 4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
  • 5.父组件里添加自定义事件处理函数
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<!--  4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号-->
  <comp @item-click="comClick"></comp>
  <h2>你点击的是:{{item.name}}</h2>
</div>

<!--子组件模板-->
<template id="comp">
  <div>
<!--    2.子组件模板添加点击事件-->
    <button v-for="items in categories" @click="btnClick(items)">{{items.name}}</button>
  </div>
</template>

<script src="node_modules/vue/dist/vue.js"></script>
<script>
  //子组件
  const comp = {
    template: "#comp",
    //1.子组件里添加数据
    data() {
      return {
        categories:[
          {id:"1",name:"推荐"},
          {id:"2",name:"手机"},
          {id:"3",name:"电脑"},
          {id:"4",name:"箱包"},
        ]
      }
    },
    methods:{
      btnClick(item){
        //3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
        this.$emit("item-click",item)
      }
    }
  }

  //父组件
  new Vue({
    el: "#app",
    data: {
      msg: "haha",
      item:""
    },
    components: {
      comp
    },
    methods:{
      // 5.父组件里添加自定义事件处理函数
      comClick(item){
        console.log(item)
        this.item = item
      }
    }
  })
  // 总结:
  // 1.子组件里添加数据
  // 2.子组件模板添加点击事件
  // 3.添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
  // 4.在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
  // 5.父组件里添加自定义事件处理函数
</script>
</body>
</html>

七、父访问子/子访问父

有时我们不想传递数据了,直接拿到父或子级的数据:(不常用)

  • 父访问子:$children 或者 $refs方法
  • 子访问父:$parent方法

1.父访问子

一、ref方法
  • 1.给组件添加ref属性xxx
  • 2.父级方法调用this.$refs.xxx.子级数据
二、children方法
  • 1.父级方法中直接通过children索引取值,this.$children[索引].子级数据

2.子访问父:

  • 1.父级定义数据
  • 2.子级方法里直接调用this.$parent.父级数据
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div id="app">
<!--  2.添加ref属性-->
  <comp ref="xixi"></comp>
  <button @click="btnClick()">点击访问子组件</button>
  <h2>拿到子组件的数据是:{{sonName}}</h2>
</div>

<template id="comp">
  <div>
    {{name}}
    <br>
  <button @click="showMsg()">点击访问父组件</button>
    <h2>拿到父组件的数据是:{{parentData}}</h2>
  </div>
</template>

<script src="node_modules/vue/dist/vue.min.js"></script>
<script>
  const comp = {
    template:"#comp",
    data(){
      return {
        name:"我是子组件的name",
        parentData:""
      }
    },
    methods:{
      showMsg(){
        this.parentData = this.$parent.parentMsg
      }
    }
  }

  new Vue({
    el: "#app",
    data: {
      parentMsg: "我是父组件的haha",
      sonName:""
    },
    components: {
      comp
    },
    methods:{
      btnClick(){
        // 1.$children 用的少 因为不容易确定组件的索引值
        // console.log(this.$children[0].name);
        // 2.$refs 常用 对象类型,默认为空,必须在组件上添加ref属性后生效
        console.log(this.$refs.xixi.name)
        this.sonName = this.$refs.xixi.name

      }
    }
  })
</script>
</body>
</html>

在这里插入图片描述


八、非父子通信(以后再讲)

1.中央事件总线

2.vuex状态管理


总结

  • 1.父组件与子组件的关系:将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该封装的组件的文件叫做父组件,被引入的组件叫做子组件。
  • 2.注册组件语法糖:将注册组件的步骤用一个对象代替;
  • 3.vue提供两种方法分享模板与组件:script标签与template标签
  • 4.组件不能访问vue实例数据,组件有自己存放数据的地方;
  • 5.组件data必须要是一个函数,不然vue会报错,而且为了不同组件之间必须保证数据的独立,每个组件的data函数要返回一个新的对象。
  • 6.父传子用props
    • 1)在父级添加上数据
    • 2)在子级添加props声明需要传递的数据变量
    • 3)在组件实例中传递数据
    • 4)在子组件中使用数据
  • 7.子传父用自定义事件$emit
    • 1)子组件里添加数据
    • 2)子组件模板添加点击事件
    • 3)添加点击事件方法,使用$emit进行数据传递,第一个参数是自定义事件名称,第二个参数是需要传递的数据
    • 4)在父组件模板里添加自定义事件,添加方法,注意方法名称后面不加括号,vue默认传递数据
    • 5)父组件里添加自定义事件处理函数
  • 8.父访问子数据用$children或者 $refs, $children不常用 ,因为常常不能准确知道索引值
  • 9.子访问父数据用$parent,而这两种访问方式都不建议使用,因为代码耦合度太高,不利于代码的利用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值