在上一篇中,我们探讨了封装组件时的传值、回调以及自定义 v-model,今天让我们更进一步,深入了解组件封装相关的关键要点。
透传 Attributes
透传是指传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。通俗讲就是你向子组件传递了一些没有在子组件中定义的props和emits,这些都是透传,最常见的例子就是 class、style 和 id。
一、单根元素组件
1.Attributes 继承
当一个组件只有一个根元素,在渲染时透传的attribute会自动的添加到根元素上。
<MyButton> 并没有将 class 声明为一个它所接受的 prop,所以 class 被视作透传 attribute,自动透传到了 <MyButton> 的根元素上。
如果在子组件的根元素上已经有了class或者style,它会从父组件上透传的值合并。
v-on的监听也适用于上面的规则。
click 点击事件会被添加到 <MyButton> 的根元素,即那个原生的 <button> 元素之上。当原生的 <button> 被点击,会触发父组件的 click方法。同样的子组件根元素自身也通过 v-on 绑定了一个事件监听器,那么这个监听器和从父组件继承的事件监听器都会被触发。
如果子组件的根元素是另外一个组件,那么子组件接收到的透传attribute会继续的透传给这个组件。
2.禁用 Attributes 继承
在某些场景下,我们并不希望子组件自动继承透传 attribute。这时,在 Vue3.3 + 版本中,可直接借助 defineOptions 函数,将组件选项中的 inheritAttrs 设置为 false。而在旧版本中,同样是设置 inheritAttrs: false 来达成目的。
禁用透传attribute最常见的场景就是透传的attribute需要应用到根元素之外的元素上,通过设置inheritAttrs: false,你可以完全控制透传进来的Attribute如何使用。
在使用时候需要使用$attrs来访问。这个$attrs对象包含了除子组件声明的props和emits外的所有attribute,如class、style、v-on、其他自定义attribute。当在元素上使用v-bind="$attrs"时,透传的attribute都会自动应用。
二、多根元素组件
与单根元素组件形成对比的是,多根元素组件不会自动继承透传 attribute。这是因为 Vue 无法确定要将 attribute 透传到哪个根元素,若不手动显式绑定 $attrs,将会抛出运行时警告。所以,在多根元素组件中使用透传 attribute 时,务必记得手动处理。
三、JavaScript 中使用透传 attribute
在使用组合式 API setup 语法糖进行开发时,若要访问子组件的所有透传 attribute,可借助 useAttrs () API 来实现。
依赖注入
父组件向子组件传值时会使用props,但是当组件层级大于2层以上时,某一个深层次的组件需要使用它的祖先组件的部分属性,这个时候不管中间组件有没有用到这些属性,都要通过props一层层的传递下去,这个问题被称为“prop 逐级透传”。
要解决上面的问题就要使用到依赖注入provide 和 inject。一个祖先组件相对于它的所有后代组件,会作为“依赖提供者”,任何后代组件都可以注入由祖先组件提供的依赖。
一、Provide (提供)
要为组件后代提供数据,需要使用到 provide() 函数,第一个参数是注入名,第二个参数为提供的值,值可以是任意类型,包括响应式的状态,如ref和reactive。
一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。
二、Inject (注入)
当后代组件需要注入上层组件提供的数据时,就要用到 inject () 函数。需要留意的是,如果提供的值是一个响应式数据,如 ref,那么注入进来的将是 ref 对象本身,并不会自动解构其内部值。这样的设计能够确保注入方的组件始终保持与源数据的响应性链接,实时反映数据变化。
插槽
在 Vue 组件开发过程中,父组件向子组件传递任意类型的 JavaScript 值时,props 是常用手段。但倘若父组件期望传递一块模板内容,此时就轮到插槽大显身手了。
一、插槽内容与出口
<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。插槽内容可以是任意合法的模板内容,不局限于文本。可以传入多个元素或者是组件。
使用插槽使组件更加灵活和具有可复用性。组件可以用在不同的地方渲染不同的内容,但同时还保证都具有相同的样式。
二、默认内容
插槽还具备指定默认显示内容的功能,这一特性十分贴心。当父组件没有为插槽指定具体内容时,插槽便会显示预先设定的默认内容,有效避免了因内容缺失而导致的页面空白或异常。
三、具名插槽
当一个组件内有多个插槽时,就需要使用到具名插槽,也就是给每一个插槽起一个名字。如果插槽没有名字,会隐式的命名为“default”。
在使用具名插槽的时候,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令,v-slot:插槽名字。
v-solt也有对应的缩写#,写法是#插槽名字。
四、作用域插槽
上面说的各种插槽,都是能访问到父组件的状态,而无法访问到子组件的状态,在某些情况下可能需要使用子组件状态或同时使用父组件和子组件的状态来渲染内容,这时候就需要用到作用域插槽。
我们可以使用类似于父组件向子组件传递props那样的方式,向插槽的上方出口传递attributes。
在接受插槽的props时,需要使用v-slot指令去接收,或者简化成#。
我们也可以直接在v-slot 中使用解构。