vue中父子组件通信

122 阅读1分钟

组件间的通信

在开发过程中,经常遇到需要组件之间相互进行通信:

比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么就需要使用者传递给Header进行展示

又比如在 main 中一次性请求了 Banner 数据和 ProductList 数据,那么就需要父组件传递给它们来进行展示

也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件

父组件传递给子组件---通过 props,props:接收父组件传递过来的属性 子组件传递给父组件---通过 $emit 触发事件

组件的嵌套关系

组件的嵌套

如果一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护

所以组件化的核心思想就是对组件进行拆分,拆分成一个个小的组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序

类型定义

在传入数据之前,我们需要对数据进行一个类型定义,关于类型定义有两种写法:数组写法和对象写法(常用)。

  • 数组写法(在实际开发中并不常见---不能对数据类型进行验证):
export default {
    name: "ShowInfo",
    props: ["name", "age", 'height']
}
  • 对象写法

type:类型 required:必填 default:默认值

export default {
    name: "ShowInfo",
    //对象写法
    props: {
        name: {
            type: String,
            required: true,
        },
        //注:如果 type 是一个对象或者数组,那么必须写成一个函数!!!
        propsA: {
            type: Object,
            //default:()=>({name:'zhangsan'})
            default() {
                return {message: 'hello'}
            }
        },
        propsG: {
            //写组件库或者框架时需要
            validator(value) {
                //这个值必须匹配下列字符串中的一个
                return ['success', 'warning', 'danger'].includes(value)
            }
        }
    },
}
  • 细节一:

type的类型有:String、Number、Boolean、Array、Object、Date、Function、Symbol

  • 细节二:对象类型的其他写法
export default{
    props:{
        messagteInfo:{
            type:String
        },
        //基础的类型检查('null'和‘undefined’会通过任何类型验证)
        paopA:{
            type:Number
        },
        paopB:[String, Number],
        propC:{
            type:String,
            required:true
        },
        propE:{
            type:Object,
        default(){
            return {message: 'hello'}
            }
        },
        //自定义验证函数
        propF:{
            validator(value){
            //这个值必须匹配下列字符串中的一个
            return ['success', 'warning', 'danger'].includes(value)
            }
        },
        propG:{
            type:Function,
            default(){
            return "Default function"
            }
        }
    }
}

细节三:Props的大小写命名

HTML中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当使用DOM中的模板时, camelCase 的 prop 名需要使用其等价的 kebab-case 命名

父传子

案例:

<!--App:父组件-->
<template>
  <div>
    <!--这里的 address 和 class 是传递到子组件的内容-->
    <show-info name="why" :age="18" :height="1.88" address="广州" class="active"/>
    <show-info name="kebi" :age="20" :height="1.88"/>
    <show-info name="kebi" :age="20" :height="1.88" show-msg="jafls;jas;f"/>
  </div>
</template>

<script>
import showInfo from "./ShowInfo.vue";
export default {
  components: {showInfo},
  setup() {
    return {showInfo}
  }
}
</script>
<!--showInfo:子组件-->
<template>
  <div class="infos">
    <!--不继承就传递到下面绑定 $attrs 的元素上,继承就传递到根元素上-->
    <h2 :class="$attrs">姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>身高:{{ height }}</h2>
    <h2>信息:{{ showMsg }}</h2>
  </div>
</template>

<script>
export default {
  name: "ShowInfo",
  //不继承
  inheritAttrs: false,
  //props:接收父组件传递过来的属性
  //数组写法---弊端:不能对数据类型进行验证
  //props: ["name", "age", 'height']
  //对象写法
  props: {
    name: {
      type: String,
      required: true,
    },
    age: {
      type: Number,
      required: true
    },
    height: {
      type: Number,
      required: true
    },
    showMsg: {
      type: String,
      default: '啊哈哈哈'
    }
  }
}
</script>

非 props 的 attribute

当我们传递给一个组件某个属性,但是该属性并没有定义对应的 props 时,就称之为非 Prop 的 attribute,常见的包括class、style、id属性等

如果当前的属性是一个非 Prop 的 attribute,那么该属性会默认添加到子组件的根元素上

如果不希望组件的根元素继承 attribute,可以在组件中设置 inheritAttrs:false

inheritAttrs:false

禁用 attribute 继承的常见情况是需要将 attribute 应用与根元素之外的其他元素

可以通过 $attrs 来访问所有非 props 的 attribute

<!--这样就将父组件的数据放到了姓名这里了-->
<h2 :class="$attrs">姓名:{{name}}</h2>  

多个根节点的attribute:多个根节点的 attribute 如果没有显示的绑定,那么会报警告,必须手动的指定要绑定到哪一个属性上

总结

父组件传属性到子组件需要一下几个步骤:

  • 首先,需要定义数据类型(传props)
  • 其次,思考父组件传递到子组件的属性是否可继承(是否是根元素上)
    • 如果可继承,那么就直接传递到根元素上了(默认)
    • 如果不可继承需要加 inheritAttrs:false,然后使用 $attrs 来访问所有非 props 的属性

子组件传父组件

  • 子组件需要传递内容到父组件的情况

当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容

子组件有一些内容想要传递给父组件的时候

  • 如何完成

    • 首先,需要在子组件中定义好在某些情况下触发的事件名称
    • 其次,在父组件中以 v-on 的方式传入要监听的事件名称,并且绑定到对应的方法中
    • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
  • 自定义事件的事件的流程

    • 封装一个CounterOperation组件
    • 内部其实是监听两个按钮的点击,点击之后通过 this.$emit(vue2) 或 context.text 或 defineEmits(语法糖) 的方式发出去事件
<!--App:父组件-->
<template>
  <div>
    <h2>当前计数:{{ counter }}</h2>
    <AddCounter @add="addBtnClick"/>
    <SubCounter @sub="subBtnClick"/>
  </div>
</template>

<script>
import AddCounter from "./AddCounter.vue";
import SubCounter from "./SubCounter.vue";
import {ref} from "vue";
export default {
  components: {AddCounter, SubCounter},
  //emits: ['onClick'],
  setup() {
    const counter = ref(0)
    function addBtnClick(count) {
      //console.log(counter)
      counter.value += count
    }

    function subBtnClick(count){
      counter.value -= count
    }
    return {AddCounter, SubCounter, counter, addBtnClick,subBtnClick}
  }
}
</script>
<template>
  <!--AddCounter:子组件-->
  <div class="add">
    <!--让子组件事件发出去一个自定义事件-->
    <button @click="btnClick(1)">+1</button>
    <button @click="btnClick(5)">+5</button>
    <button @click="btnClick(10)">+10</button>
    
    <!--其他写法-->
    <!--<button @click="$emit('onAdd',1)">+1</button>-->
    <!--<button @click="$emit('onAdd',5)">+5</button>-->
    <!--<button @click="$emit('onAdd',10)">+10</button>-->
  </div>
</template>

<script>
export default {
  name: "AddCounter",
  //这里的 props 不能因为只写 context 就不写!!!
  setup(props, context) {
    //第一个参数是自定义的事件名称
    //第二个参数是传递的参数
    const btnClick = (count) => {
      //console.log(context.emit)
      context.emit('add',count)
    }
    return {btnClick}
  }
}
</script>
<!--SubCounter:子组件-->
<template>
  <div class="sub">
    <button @click="btnClick(1)">-1</button>
    <button @click="btnClick(5)">-5</button>
    <button @click="btnClick(10)">-10</button>
  </div>
</template>

<script>
export default {
  name: "SubCounter",
  setup(props, context) {
    const btnClick = (count) => {
      context.emit('sub', count)
    }
    return {btnClick}
  }
}
</script>

使用语法setup语法糖

以上案例都没有使用 setup 语法糖,接下来展示使用setup语法糖展示

父传子

<!--APP:父组件-->
<template>
  <div>
    <show-info_setup name="why" :age="18" :height="1.88" address="广州" class="active"/>
    <show-info_setup name="kebi" :age="20" :height="1.88"/>
    <show-info_setup name="kebi" :age="20" :height="1.88" show-msg="jafls;jas;f"/>
  </div>
</template>

<script setup>
import ShowInfo_setup from "./ShowInfo_setup.vue";
</script>
<template>
  <div class="infos">
    <h2 :class="$attrs">姓名:{{ name }}</h2>
    <h2>年龄:{{ age }}</h2>
    <h2>身高:{{ height }}</h2>
    <h2>信息:{{ showMsg }}</h2>
  </div>
</template>
<script setup>
const props = defineProps({
  name: {
    type: String,
    required: true,
  },
  age: {
    type: Number,
    required: true
  },
  height: {
    type: Number,
    required: true
  },
  showMsg: {
    type: String,
    default: '啊哈哈哈'
  }
})
</script>

子传父

<!--APP;父组件-->
<template>
  <div>
    <h2>当前计数:{{ counter }}</h2>
    <AddCounter_setup @add="addBtnClick"/>
  </div>
</template>

<script setup>
import AddCounter_setup from "./AddCounter_setup.vue";
import {ref} from "vue";

const counter=ref(0)
const addBtnClick=(count)=>{
  counter.value+=count
}
</script>
<!--子组件-->
<template>
  <button @click="btnClick(1)">+1</button>
  <button @click="btnClick(5)">+5</button>
  <button @click="btnClick(10)">+10</button>
</template>

<script setup>
const emits = defineEmits(['add'])

const btnClick = (count) => {
  emits('add', count)
}
</script>