初体验
Vue.js 在现今使用有多广泛不用多说,而 Vue 的一大特点就是组件化。本期要讲的,便是 Vue 组件间通信方式的总结,这也几乎是近年 Vue 面试中的必考题。注:文中示例都基于 Vue 脚手架讲解,会用到一些 Element UI 示例。
【前端进阶之路】会作为一个新系列连载,后续会更多优质前端内容,感兴趣的同学不妨关注一下。
①组件
组件是可以复用的 Vue 实例。 — Vue 官方文档;
在进入主题之前,还是决定先简单聊聊组件。在 Vue 中,根据注册方式的不同,可以分为:
▶局部组件 (局部注册)
▶全局组件 (全局注册)
顾名思义,全局注册的组件,可以用在 Vue 实例的任意模板中。但是带来的隐患是,在 webpack 模块化构建时,即便你没有在项目中使用这个组件,依然会打包到最终的项目代码中。而局部组件,则需要在使用到的实例中注册该组件。
根据应用场景的不同,又可以分为:
1.页面组件:我们使用 Vue 时,每个路由代表的页面,都可以称之为组件。
2.基础组件:就像上面栗子中的 Icon 组件,就是一个典型的基础组件。基本上不掺杂业务逻辑,在项目中可能被大量使用,易于移植。类似的基础组件还有 Button、Input 等,常见于各类 UI 组件库。
3.业务组件:业务组件和项目具体的业务逻辑有大量耦合,一般抽离于当前项目。
以上就是组件的简单介绍,那我们到底为什么要推崇组件化?组件化有什么好处?复用?我个人认为组件化最大的好处,便是解耦,易于项目管理。所以在大型项目管理中,组件化是非常有必要的。当然,这并不是今天学习的重点,以后有机会再聊。
正因为在 Vue 中处处都是组件,而我们也偏向于组件化、模块化。那我们在一堆组件中,便需要解决一个问题 — 组件间通信。下面,我们就进入今天的主题,Vue 的组件间通信。
②组件间通信
组件间通信是我们在 Vue 项目中不可避免的问题,深刻了解了 Vue 组件间通信的几种方式,才能让我们在处理各种交互问题时游刃有余。
Props
Vue 中,最基本的通信方式就是 Props,它是父子组件通信中父组件传值给子组件的一种方式。它允许以数组形式接收,但是更推荐你开启类型检查的形式。更详细的类型检查前往 vue prop文档。
我们都知道,Props 是单向数据流,这是 Vue 为了避免子组件意外改变父组件的状态,从而导致数据流向难以理解而做出的限制。所以 Vue 推荐需要改动的时候,通过改变父组件的值从而触发 Props 的响应。或者,我们可以在接收非引用类型的值时,使用子组件自身的 data 做一次接收。
为什么是非引用类型呢,因为在 JavaScript 中,引用类型的赋值,实际是内存地址的传递。所以上面栗子中的简单赋值,显然会指向同一个内存地址,所以如果是数组或是对象,你可能需要一次深拷贝。
上面这个操作有一些缺陷,不能序列化函数、undefined、循环引用等……
事实上,在 Props 是引用类型时,单独修改对象、数组的某个属性或下标,Vue 并不会抛出错误。当然,前提是你要非常清楚自己在做什么,并写好注释,防止你的小伙伴们疑惑。
有的同学可能知道,在组件上绑定的属性,如果没有在组件内部用 Props 声明,会默认绑定到组件的根元素上去。还是之前的栗子:
结果如下:
这是 Vue 默认处理的,而且,除了 class 和 style 采用合并策略,其它特性(如上栗 type)会替换掉原来根元素上的属性值。当然,我们也可以显示的在组件内部关闭掉这个特性:
利用 inheritAttrs,我们还可以方便的把组件绑定的其它特性,转移到我们指定的元素上。这就需要用到下一个我们要讲的 $attrs 了。
attrs、listeners
我们在使用组件库的时候经常会这么写:
实际渲染后:
可以看到我们指定的的 placeholder 是渲染在 input 上的,但是 input 并不是根元素。难道都用 Props 声明后,再赋值给 input?这种情况就可以用到 $attrs 了,改造一下我们之前那个栗子。
可以看到,type 已经转移到了子元素 input 标签上,但是 class 没有。这是因为inheritAttrs: false选项不会影响 style 和 class 的绑定。可以看出$attrs则是将没有被组件内部 Props 声明的传值(也叫非 Props 特性)收集起来的一个对象,再通过 v-bind 将其绑定在指定元素上。这也是 Element 等组件库采用的策略。
这里需要注意一点,通过 $attrs 指定给元素的属性,不会与该元素原有属性发生合并或替换,而是以原有属性为准。举个例子,假如我将上述 input 的 type 默认设置为 password。
则不会采用 $attrs 中的 type: 'text',将以 password 为准,所以如果需要默认值的属性,建议不要用这种方式。
$listeners同$attrs类似,可以看做是一个包含了组件上所有事件监听器(包括自定义事件、不包括.native修饰的事件)的对象。它也支持上述的写法,适用于将事件安放于组件内指定元素上。
给之前的栗子绑定一个聚焦事件,在子组件中通过$listeners绑定给 input,则会在 input 聚焦时触发。
那么除了用在这种给组件内指定元素绑定特性和事件的情况,还有哪些场景可以用到呢?官方说明:在创建更高层次的组件时非常有用。比如在祖孙组件中传递数据,在孙子组件中触发事件后要在祖辈中做相应更新。我们继续之前的栗子:在孙辈组件触发点击事件,然后在祖辈中修改相应的 data。
这样就能很方便的在多级组件的子级组件中,快速访问到父组件的数据和方法。正如在刚才的例子中,button 点击时,是直接调用的 communication.vue 中定义的方法。
总结:
1、子组件触达父组件的方式:Props、$parent、$attrs、$listeners、provide 和 inject、$dispatch
2、父组件触达子组件的方式:$emit和$on、$children、$ref、broadcast
3、全局通信:EventBus、Vuex