Vue组件化开发中父子组件间传值与联动

🍓 作者主页:💖仙女不下凡💖

🍓 前言介绍:以下👇 内容都是我个人对于前端知识的总结,会定期更新欢迎持续关注!

🍓 欢迎点赞👍 收藏⭐ 留言📝 如有错误敬请指正!

🍑 看这篇文章之前,请先理解两个概念:什么是Vue实例,什么是Vue组件?我在该文章中详细解释过地址https://blog.csdn.net/weixin_55181759/article/details/119957816

🍑 学习重点:实例与组件、父子组件、兄弟组件最重要要了解传参的方法,首先我在"一"中我们介绍了组件的基本使用方法与组件基本类型的区别,另外为了方便对比在示例代码中,我没有用脚手架使用了.html文件引入vue同时将父子组件,兄弟组件都写在一个文档中,但在真实的vue项目中每个组件都是一个独立的.vue文件,希望这点不要给阅读该文章的小伙伴造成误解。


💚一、Vue组件的基本概念

🍑 Vue组件化思想:它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造应用
任何的应用都会被抽象成一颗组件数树(树结构)。

💛1.组件化的基本使用

🍑 组件使用的三个步骤:❶调用Vue.extend()方法创建组件构造器❷调用Vue.component()方法注册组件❸在Vue实例的作用域内使用组件

//示例代码: 该文件为.html文件
<div id="app">
  <my-cpn></my-cpn>  //3.使用组件  (<my-cpn></my-cpn>可以使用多次)
</div>

<script src="../js/vue.js"></script>
<script>
  //1.创建组件构造器
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>组件</h2>
      </div> `
  })
  
  //2.注册组件
  Vue.component('my-cpn',cpnC)
  
  const vm = new Vue({
    el: '#app',
    data: { message: '你好啊!' }
  })
</script>

💛2.全局组件与局部组件

💭 思考: 什么是全局组件与局部组件?下面👇🏻就写一段代码进行对比介绍。

//示例代码: 该文件为.html文件
<div id="app1">
  <my-cpn></my-cpn>  //3.使用组件-app1与vm1是对应的作用域
</div>
<div id="app2">
  <my-cpn></my-cpn>  //3.使用组件-app2与vm2是对应的作用域
</div>

<script src="../js/vue.js"></script>
<script>
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>组件</h2>
      </div> `
  })
  
  Vue.component('my-cpn',cpnC);  /这就是全局组件#app1与app2中都可以使用<my-cpn></my-cpn>/
  
  const vm1 = new Vue({
    el: '#app1',
    data: { message: '你好啊!' },
    components: { cpnC:cpnC },   /<cpnC></cpnC>是局部组件,只能在app1中使用, 在app2中使用就会报错/
  })
  const vm2 = new Vue({
    el: '#app2',
    data: {message: '你好'}, /真实项目中一般会有一个Vue实例,这里是想让大家理解全局与局部区别/
  })
</script>

💛3.什么是父组件与子组件

💭 思考:什么是父组件与子组件?下面👇🏻就写一段代码进行对比介绍。

//示例代码: 该文件为.html文件
<div id="app">
  <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
  const cpnC1 = Vue.extend({  /这个就是子组件/
    template: `
      <div>
        <h2>子组件</h2>
      </div> `
  })
  const cpnC2 = Vue.extend({  /因为在cpnC2中使用了cpnC1, 所以cpnC2就是cpnC1的父组件/
    template: `
      <div>
        <h2>父组件</h2>
        <cpnC1></cpnC1>
      </div> `,
    components:{ cpnC1:cpnC1 }
  })
    
  const vm = new Vue({   /在真实vue项目中, 一般只有一个new Vue()(即Vue实例)/
    el: '#app',
    data: { message: '你好啊!' },
    /cpnC2其实就相当于真实项目中App.vue文件, 是所有组件的集合, 不管有多少个父组件都需要关联在App.vue组件上, 可以理解为App.vue文件是所有组件的祖宗/
    components: { cpnC2:cpnC2 },  
  })
</script>

🍑关于父子组件之间传参等方法,在"二"中会详细说明

💛4.注册组件的语法糖

🍑现在Vue项目中写组件一般都是用了组件语法糖的方法,很少先使用extend()再写component()这样的方法了。下面就用代码演示使用组件的语法糖方法之后的区别。
组件语法糖

该文件为.html文件-使用组件语法糖后
<div id="app">
  <cpn></cpn>
</div>

<script src="../js/vue.js"></script>
<script>
  Vue.component('cpn',{   /内部自动调用了extend()/
    template: `
      <div>
        <h2>组件</h2>
      </div> `,
  });
  const vm = new Vue({
    el: '#app',
    data: { message: '你好啊!' }
  })
</script>

🍑 语法糖写法简化了代码,之后所有的示例代码我都会引用语法糖写法。

💛5.组件的模板抽离

🍑 模板抽离第一种写法👇👇👇,不常用。

该文件为.html文件
<div id="app">
  <cpn></cpn>
</div>

<script type="text/x-template" id="cpn">
  <div>
    <h2>组件</h2>
  </div> 
</script>
<script src="../js/vue.js"></script>
<script>
  Vue.component('cpn',{
    template: "#cpn",  /这样就可以了/
  });
  const vm = new Vue({
    el: '#app',
    data: { message: '你好啊!' }
  })
</script>

🍑 模板抽离第二种写法👇👇👇,较为常用。

该文件为.html文件
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
</div>

<template id="cpn">  /这是组件模板/
  <div>
    <h2>组件</h2>
  </div> 
</template>

<script src="../js/vue.js"></script>
<script>
  Vue.component('cpn',{
    template: "#cpn",  /这样就可以了/
  });
  const vm = new Vue({
    el: '#app',
    data: { message: '你好啊!' }
  })
</script>

💛6.为什么Vue组件中的data必须是函数

💭 思考:组件可以直接访问Vue实例的数据data吗?
Vue组件中的data
🍑 结论Vue组件不可以直接访问Vue实例的数据dataVue组件把数据保存在自己的data函数中,并且data函数中的数据用return返回,所以此data非彼data,。
🍑 原因: 将Vue组件中data设计成函数的原因:当我们在不同页面(或者相同的页面)重复使用组件的时候,我们传的数据应该是不同的,如果Vue组件中dataVue实例中data一样是对象类型那么就造成各个组件之间数据相互影响;所以只有Vue组件中的data是函数类型才能保证组件多次复用时数据互不影响,根本原因是在函数的栈空间中每次调用函数返回的对象保存在不同的地址,互不影响

💙二、父子组件通信(重点)

🍑 父子组件通讯如下图
父子通信

💛1.父组件向子组件传递数据

🍑 在组件中,使用 构造选项props来声明需要从父级接收到的数据。
🍑 props的值有两种方式: ▶️字符串数组,数组中的字符串就是传递时的名称。
▶️对象,对象可以设置传递的类型,也可以设置默认值等。

💨⑴ props字符串数组类型

🍑 props字符串数组类型的基本使用
props基本使用

💨⑵ props对象类型-可以进行数据验证

🍑 props数据验证-支持所有数据类型(string、number、Boolean、array、object、)

Vue.components('cpn', {
  props: {
    /基础的类型检查(null匹配任何类型)/
    propA: Number,
    /多个可能的类型/
    propB: [String, Number],
    /必填的字符串/
    propC: {
      type: String,
      required: true
    },
    /带有默认值的数字/
    propD: {
      type: Numder,
      default: 100
    },
    /带有默认值的对象/
    propE: {
      type: Object,
      default: function(){  /对象成数组默认值必须从工厂函数获取/
        return {message:'你好'}
      }
    },
    /自定义验证函数/
    propF: {
      validator: function(val){
        return ['success', 'warning', 'danger'].indexOf(val) !== -1
      }
    }
  }
})

💛2.子组件向父组件传递数据

🍑 一般子组件向父组件传值都是在发生某些事件(如点击事件)之后,向父组件传递数据,所以传值使用自定义事件

<div id="app">
  <cpn @item-click="cpnClick"></cpn>
</div>  /父组件模板/

<template id="cpn">
  <div>
    <button v-for="item in categories" @click="btnClick(item)">
    {{item.name}}  /点击哪个按钮传递给父组件哪个数据/
    </button>  
  </div>
</template>  /子组件模板/

<script src="../js/vue.js"></script>
<script>
  const cpn = {  /子组件/
    template: '#cpn',
    data(){
      return {
        categories: [
          {id:'001', name: '热门推荐'},
          {id:'002', name: '手机数码'},
          {id:'003', name: '家具家电'},
          {id:'004', name: '电脑办公'}
        ]
      }
    },
    methods: {
      /点击之后发射emit了一个叫itemClick的事件, 让父组件接收/
      btnClick(val){ this.$emit('itemClick',val) }
    }
  }
  
  const vm = new Vue({  /父组件/
    el: '#app',
    data:{},
    components: {cpn},
    methods: {
      cpnClick(val){ console.log(val) }  /这样就得到了子组件的值了/
    }
  })
</script>

💜三、父子组件的访问方式

🍑 有时候父子组件之间不仅仅只需要传参,也需要直接访问其中的方法等。
🍑所以▶️父组件访问子组件:使用$children$refs
▶️子组件访问父组件:使用$parent

💛1.父组件访问子组件

💨⑴$children属性

<div id="app">
  <cpn></cpn>
  <button @click="btnClick"></button>
</div>

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const cpn = {  /子组件/
    template: '#cpn',
    data(){},
    methods: { showMessage(){'我是子组件的方法'}
    }
  }
  
  const vm = new Vue({
    el: '#app',
    data:{},
    methods: {
      btnClick(){ console.log(this.$children) }   /这种方法需要用下标值来获取(this.$children[0])不太好/
    }
    components: { cpn }  /引入子组件/
    }
  })
</script>

🍑 因为$children属性需要用下标值来获取(this.$children[0]),如果顺序改变会有问题,所以引出了$refs属性。

💨⑵$refs属性

<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn ref="aaa"></cpn>
  <button @click="btnClick"></button>
</div>

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const cpn = {  /子组件/
    template: '#cpn',
    data(){},
    methods: { showMessage(){'我是子组件的方法'}
    }
  }
  
  const vm = new Vue({
    el: '#app',
    data:{},
    methods: {
      btnClick(){ console.log(this.$refs.aaa) }   /这样就获取到aaa子组件了/
    }
    components: { cpn }  /引入子组件/
    }
  })
</script>

💛2.子组件访问父组件

💨⑴$parent属性(很少使用)

🍑 以下代码演示$parent使用方法,但是该方法在实际开发中用的很少,因为使用后导致父子组件耦合性太高了,所以不建议这样使用。

<div id="app">
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>我是子组件</h2>
    <button @click="btnClick"></button>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const cpn = {  /子组件/
    template: '#cpn',
    data(){},
    methods: {
      btnClick(){console.log(this.$parent)}  /这样就可以了/
    }
  }
  
  const vm = new Vue({  /父组件/
    el: '#app',
    data:{},
    components: { cpn }  /引入子组件/
    methods: {showMessage(){'我是父组件的方法'}}
    }
  })
</script>

💨⑵$root属性

🍑 获取根组件,即可以直接获取到组件的Vue实例对象,与$parent用法相同。

🖤四、组件的插槽

💭💭💭 思考组件的插槽slot有什么存在的实际意义?
🍑 如下图整个导航栏就可以为一个组件,但是其中的展示的文字图标会略有不同,slot插槽就是解决了这个问题,所以slot是为了让我们封装的组件更加具有延展性,让使用者可以决定组件内部的一些内容到底展示的是什么。
示例图

💛1.插槽的基本使用

🍑 如下👇代码为slot的基本使用。
插槽的基本使用

💛2.具名插槽

💭💭💭 思考:为什么有具名插槽?
🍑 因为很多组件都需要多个插槽,所以产生了具名插槽的概念,使用方法如下👇代码。
具名插槽

💛3.作用域插槽

🍑 在学习作用域插槽之前需要理解一个概念叫做编译作用域

💨⑴编译作用域

🍑 写一段代码辅助理解,请💭💭💭思考一下代码运行后页面是空白还是显示?

<div id="app">
  <cpn v-show="isShow"></cpn> 
</div>

<template id="cpn">  /子组件模板/
  <div>
    <h2>我是组件</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const vm = new Vue({  /这个是父组件/
    el: '#app',
    data: { isShow: true },
    components: {
      cpn: {  /这是引入的子组件/
        template:'#cpn',
        data(){
          return { isShow: false }
        }
      }  
    }
  })
</script>

🍑 答案是显示
🍑 原因:父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译,即编译作用域。

💨⑵作用域插槽的使用

🍑 作用域插槽意义:父组件替换插槽标签,但是内容是由子组件来提供。下面👇我用一段代码演示什么意思!
作用域插槽
🍑 总结:❶子组件需要用<slot :data="数据"></slot>暴露数据❷父组件通过<template slot-scope="slot"></template>接收数据。

💛4.作用域插槽

🍑 在学习作用域插槽之前需要理解一个概念叫做编译作用域

💨⑴编译作用域

🍑 写一段代码辅助理解编译作用域,请思考💭💭💭一下代码运行后页面是空白还是显示?

<div id="app">
  <cpn v-show="isShow"></cpn> 
</div>

<template id="cpn">  /子组件模板/
  <div>
    <h2>我是组件</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const vm = new Vue({  /这个是父组件/
    el: '#app',
    data: { isShow: true },
    components: {
      cpn: {  /这是引入的子组件/
        template:'#cpn',
        data(){
          return { isShow: false }
        }
      }  
    }
  })
</script>

🍑 答案是显示 原因就是:父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译,即编译作用域。

💨⑵作用域插槽的使用

**🍑作用域插槽意义:**父组件替换插槽标签,但是内容是由子组件来提供。下面我用一段代码演示什么意思!
作用域插槽
总结:❶子组件需要用<slot :data="数据"></slot>暴露数据❷父组件通过<template slot-scope="slot"></template>接收数据。

<div id="app">
  <cpn></cpn>
  
  <-- 比如下面这个组件展示效果想要成一行用"-"分割,这时候就需要用作用插槽实现了 -->
  <cpn></cpn>  
</div>

<template id="cpn">
  <div>
    <ul>
      <li v-for="item in pLanguages">{{item}}</li>
    </ul>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const vm = new Vue({
    el: '#app',
    data: {},
    components: {
      cpn: {
        template:'#cpn',
        data(){ 
          return { pLanguages: ['JavaScript','C++','java','c#','python','go'] }
        }
      }
    }
  })
</script>

🍑 总结: 插槽中除了作用域插槽外,其他的都是父组件控制子组件对应插槽的“样式”与“值”,而作用域插槽不同于他的插槽“样式”是父组件控制,但是插槽“值”是由子组件控制,所以在父子组件通信(传值)时,作用域插槽也是其中的选择之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值