Vue3组件化开发之父子组件拆分与通信(十)

713 阅读1分钟

1. 组件的嵌套和拆分

之前我们创建了一个App.vue组件,如将逻辑全放一个组件这太过臃肿

组件化的核心就是拆分一个个小组件,然后进行组合嵌套形成我们的应用程序

如果App.vue组件内容如下:

image-20220131194707514

我们可以按照如下的方式进行拆分:

image-20220131194429933

拆分后App组件是Header、Main、Footer组件的父组件,Main组件是Banner、ProductList组件的父组件

但是拆分后就要涉及到每个组件的通信,即相互交换数据

image-20220131200620476

2.组件样式的scoped属性

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>

3.父子组件之间通信的方式

父组件传递给子组件:通过props属性

子组件传递给父组件:通过$emit触发事件

image-20220131195044855

通过props属性

  • 功能:让组件接收外部传过来的数据
  • 传递数据:<Demo name="xxx"/>

接收数据:

  1. 第一种方式(只接收):props: ['name']

  2. 第二种方式(限制类型):props: {name: String}props: [String, Number]

  3. 第三种方式(限制类型、限制必要性、指定默认值):

    image-20220131203750875

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据

type的属性可以为String、Number、Boolean、Array、Object、Date、Function、Symbol

TML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符

使用传值时候建议kebab-case (短横线分隔命名) ,接受的时候可以为camelCase (驼峰命名法)

非Prop的Attribute

当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的 Attribute

常见的包括class、style、id属性等

当组件由单个根节点的时候,非Prop的Attribute将自动添加到根节点的Attribute中

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

根元素不继承了,但是我们依然可以通过 $attrs来访问所有的 非props的attribute赋值给其他元素

image-20220131204511884

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

image-20220131204616481

通过$emit触发自定义事件

一种组件间通信的方式,适用于:子组件 ===> 父组件

我们需要在子组件中定义好在某些情况下触发的事件名称

然后在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中

最后,在子组件中触发事件,根据事件名触发对应父组件绑定的事件

绑定自定义事件:

  1. 在父组件中:<Demo @handlerYun="test"/><Demo v-on:handlerYun="test"/>

  2. 在在父组件中:

<Demo ref="demo"/>
......
mounted(){
    // 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
    // 回调要么配置在methods中,要么用箭头函数,否则this指向会出问题
   this.$refs.xxx.$on('handlerYun',this.test)
}

子组件触发自定义事件:

this.$emit('handlerYun',数据)

解绑自定义事件:

this.$off('handlerYun'), // 解绑自定义事件
this.$off(['name1','name2'])  // 解绑多个自定义事件
this.$off()  // 解绑全部的自定义事件
this.destroy() // 销毁当前组件的实例,销毁后的全部组件实例的自定义事件全都不奏效。

注意:组件上也可以绑定原生DOM事件,需要使用native修饰符。

我们封装一个CounterOperation.vue的组件,内部其实是监听两个按钮的点击,点击之后通过 this.$emit的方式发出去事件

image-20220131205222011 image-20220131205239432

我们也可以传递参数给自定义事件

image-20220131205403436

在vue3当中,我们可以对传递的参数进行验证,emits变为对象写法

image-20220131205503170

4.商品页面的切换

App.vue

<template>
  <div>
    <tab-control :titles="titles" @titleClick="titleClick"></tab-control>
    <h2>{{ contents[currentIndex] }}</h2>
  </div>
</template>

<script>
import TabControl from "./TabControl.vue";

export default {
  components: {
    TabControl,
  },
  data() {
    return {
      titles: ["衣服", "鞋子", "裤子"],
      contents: ["衣服页面", "鞋子页面", "裤子页面"],
      currentIndex: 0,
    };
  },
  methods: {
    titleClick(index) {
      this.currentIndex = index;
    },
  },
};
</script>

<style scoped></style>

TabControl.vue

<template>
  <div class="tab-control">
    <div
      class="tab-control-item"
      :class="{ active: currentIndex === index }"
      v-for="(title, index) in titles"
      :key="title"
      @click="itemClick(index)"
    >
      <span>{{ title }}</span>
    </div>
  </div>
</template>

<script>
export default {
  emits: ["titleClick"],
  props: {
    titles: {
      type: Array,
      default() {
        return [];
      },
    },
  },
  data() {
    return {
      currentIndex: 0,
    };
  },
  methods: {
    itemClick(index) {
      this.currentIndex = index;
      this.$emit("titleClick", index);
    },
  },
};
</script>

<style scoped>
.tab-control {
  display: flex;
}

.tab-control-item {
  flex: 1;
  text-align: center;
}

.tab-control-item.active {
  color: red;
}

.tab-control-item.active span {
  border-bottom: 3px solid red;
  padding: 5px 10px;
}
</style>