Vue3组件化(一):父子组件的通信

本文整理来自深入Vue3+TypeScript技术栈-coderwhy大神新课,只作为个人笔记记录使用,请大家多支持王红元老师。

认识组件的嵌套

前面我们是将所有的逻辑放到一个App.vue组件中,如果我们将所有的代码逻辑都放到一个App.vue组件中,我们会发现,代码是非常的臃肿和难以维护的。并且在真实开发中,我们会有更多的内容和代码逻辑,对于扩展性和可维护性来说都是非常差的。所以,在真实开发中,我们会对组件进行拆分,拆分成一个个功能的小组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序。

组件的拆分

原来的代码:

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

按照如上的拆分方式后,我们开发对应的逻辑只需要去对应的组件编写就可。

推荐插件

Vue代码高亮的插件:Vetur、Volar。
代码片段插件:Vue VSCode Snippets、Vue3 Snippets。

对于组件的导入,不加后缀名也不会报错,因为VueCLI是基于webpack的,而webpack又有resolve.extensions用来解析扩展名,Vue已经内置在extensions里面添加了.vue,所以不写.vue后缀名也可以。

但是最好带上后缀名,比如:import Header from './Header.vue';,如果不带有两个问题:

  1. 使用组件的时候没有提示
  2. 点击路径不会跳转到对应组件代码

如果加上.vue后缀就没有上面两个问题了。

Vue3的scoped偶尔失效的问题

vue2中我们给样式添加scoped就会避免样式被污染的问题,这是因为标签上被添加上了一个属性,设置样式的时候用了属性选择器,有这个属性才会设置此样式。

但是vue3中经常会出现样式污染的问题,因为vue2中组件都有根元素,但是vue3中可以没有根元素,没有根元素的时候就会出现这个bug。因为没有根元素,上个组件的属性会穿透到下个组件,所以他们就有重复的属性了,样式就会被污染,如下:

所以,Vue3组件也要有个根元素

组件的通信

上面的嵌套逻辑如下,它们存在如下关系:
App组件是Header、Main、Footer组件的父组件;
Main组件是Banner、ProductList组件的父组件;

在开发过程中,我们会经常遇到需要组件之间相互进行通信。比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示。又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给它们来进行展示。也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件。

总之,在一个Vue项目中,组件之间的通信是非常重要的环节,所以接下来我们就具体学习一下组件之间是如何相互之间传递数据的。

父子组件之间通信的方式

父子组件之间如何进行通信呢?

  • 父组件传递给子组件:通过props属性;
  • 子组件传递给父组件:通过$emit触发事件;

父组件给子组件传递数据

在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示,这个时候我们可以通过props来完成组件之间的通信。

什么是Props呢?
Props是你可以在组件上注册一些自定义的attribute,父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值。

Props有两种常见的用法:
方式一:字符串数组,数组中的字符串就是attribute的名称;
方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;

Props的数组用法

上面是传递写死的值,如果传递data里面的值就需要进行绑定:

//直接绑定
<show-message :title="title" :content="content"></show-message>
//绑定对象的属性
<show-message :title="message.title" :content="message.content"></show-message>
//绑定对象,就会把对象的所有属性绑定到组件上,这种写法和上一行效果一样
<show-message v-bind="message"></show-message>

//数据
data() {
  return {
    title: "嘻嘻嘻",
    content: "我是嘻嘻嘻嘻",
    message: {
      title: "嘿嘿嘿",
      content: "我是嘿嘿嘿"
    }
  }
}

Props的对象用法

数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制,接下来我们来看一下对象的写法是如何让我们的props变得更加完善的,真实开发中我们就使用对象用法。

当使用对象语法的时候,我们可以对传入的内容限制更多:

  • 比如指定传入的attribute的类型;
  • 比如指定传入的attribute是否是必传的;
  • 比如指定没有传入参数时,attribute的默认值;

那么type的类型都可以是哪些呢?

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

Props对象语法补充

如果有多个可能的类型,按如下写法:

props: {
  //可能是String也可能是Number
  IDNumber: [String, Number]
}
  • 对象或数组的默认值必须从一个工厂函数中获取。这是因为组件是复用的,如果默认传递一个对象,那么这个对象(引用类型)也会被其他组件引用,所以我们传递一个函数,返回一个对象。
  • 我们也可以自定义验证函数,保证传递的值是我们指定的值。

Prop的大小写命名

  • 如果是浏览器解析,因为HTML 中的 attribute 名是大小写不敏感的,所以下面camelCase (驼峰命名)方式的的messageInfo会被解析成messageinfo,就会有问题,所以我们可以换成等价的kebab-case (短横线分隔命名) 写法。
  • 但是在.vue文件中,template是给vue-loader解析的,vue-loader解析的就不会有问题,所以在.vue文件中,两种写法都可以,但是官方还是推荐kebab-case (短横线分隔命名)。

非Prop的Attribute

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

Attribute继承:当组件有单个根节点时,非Prop的Attribute将自动添加到组件的根节点的Attribute中。想想也很容易理解,因为给组件添加属性没啥意义啊,所以只要组件有单个根节点就会把属性添加到单个根节点上。

如果我们不希望组件的根节点继承attribute,可以在组件中设置 inheritAttrs: false。禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素。比如下面我们不想将class属性添加到div上,可以在组件中设置inheritAttrs: false,然后通过$attrs来访问所有的非props的attribute,然后再设置到h2上就行了。

当非props的attribute比较多的时候,我们也可以直接绑定对象:

<div>
  我是NotPropAttribute组件
  <h2 v-bind="$attrs"></h2>
</div>

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

子组件给父组件传递数据

什么情况下子组件需要传递内容到父组件呢?
当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
子组件有一些内容想要传递给父组件的时候;

我们如何完成上面的操作呢?
首先,我们需要在子组件中定义好在某些情况下触发的事件名称,其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中,最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件。

传递流程

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

子组件通过$emit触发事件,并且通过emits指明我们都有哪些触发事件。

父组件通过v-on监听事件:

上面代码和vue2比,其实就是多了emits: ["add", "sub", "addN"]。

传递参数和验证

自定义事件的时候,我们也可以传递一些参数给父组件:

在vue3中,emits除了是数组,还可以是个对象,对象写法的目的是为了进行参数的验证。实际开发中,emits一般我们都是使用数组。

// emits: ["add", "sub", "addN"],
// 对象写法的目的是为了进行参数的验证
emits: {
  add: null, //不需要验证
  sub: null,
  addN: (num, name, age) => {
    console.log(num, name, age);
    if (num > 10) {
      return true
    }
    return false; //如果验证不通过,参数还是可以传过去,只不过会报警告
  }
}

组件间通信案例练习

我们来做一个相对综合的练习:父组件给子组件传递标题数据,点击子组件通知父组件切换到相应页面。

父组件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里面放标题, 设置下面的红色横线 -->
      <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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容