VUE详解——组件篇
创始人
2025-01-09 02:30:19
0

前言

本文为本人在学习vue时所记录的有关组件的笔记,其中详细介绍了vue中的组件及其使用,其中大部分内容来自vue官方中文文档,对于其中的一些知识也加入了一些自己的理解。

文章目录

    • 前言
    • 基础
    • 注册
      • 全局注册
      • 局部注册
    • props
      • 声明
      • 校验
    • 事件
    • v-model
    • 透传Attributes
    • 插槽Slots
      • 插槽内容
      • 条件插槽
      • 动态插槽名
      • 作用域插槽
    • 依赖注入
      • provide
      • Inject
    • 异步组件
    • 内置组件
      • component
      • transition
      • keep-alive
      • teleport
      • Suspense

基础

什么是组件?

组件允许我们将 UI 划分为独立的、可重用的部分,就像我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。或者说每一个组件都像是一个函数一样,是独立的,可以进行复用。

我们一般会将 Vue 组件定义在一个单独的 vue文件中,这被叫做单文件组件(简称 SFC),例如:

  

要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue 的文件中,这个组件将会以默认导出的形式被暴露给外部。例如:

   

组件可以被重用任意多次,并且每个组件之中的内容都是独立,不会被影响。例如:

Here is a child component!

注册

一个 Vue 组件在使用前需要先被“注册”,这样 Vue 才能在渲染模板时找到其对应的实现。组件注册有两种方式:全局注册和局部注册。

全局注册

我们可以使用 Vue 应用实例的 .component() 方法,让组件在当前 Vue 应用中全局可用。例如:

import { createApp } from 'vue' const app = createApp({}) app.component(   // 注册的名字   'MyComponent',   // 组件的实现   {     /* ... */   } ) 

如果使用单文件组件,你可以注册被导入的 .vue 文件:

import MyComponent from './App.vue' app.component('MyComponent', MyComponent) 

.component() 方法可以被链式调用:

app   .component('ComponentA', ComponentA)   .component('ComponentB', ComponentB)   .component('ComponentC', ComponentC) 

全局注册的组件可以在此应用的任意组件的模板中使用:

    

局部注册

全局注册虽然很方便,但有以下几个问题:

  1. 全局注册,但并没有被使用的组件无法在生产打包时被自动移除 (也叫“tree-shaking”)。如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。
  2. 全局注册在大型项目中使项目的依赖关系变得不那么明确。在父组件中使用子组件时,不太容易定位子组件的实现。和使用过多的全局变量一样,这可能会影响应用长期的可维护性。

相比之下,局部注册的组件需要在使用它的父组件中显式导入,并且只能在该父组件中使用。它的优点是使组件之间的依赖关系更加明确,并且对 tree-shaking 更加友好。

局部注册需要使用 components 选项:

  

对于每个 components 对象里的属性,它们的 key 名就是注册的组件名,而值就是相应组件的实现。

props

声明

首先什么是props?

Props 是一种特别的 attributes,我个人理解props就是一种用于从父组件向子组件传递数据的机制。具体来说,props 是子组件接收父组件传递的数据的一种方式。子组件通过 props 属性来声明自己接收哪些数据,并可以在其模板中使用这些数据。

props在使用时需要先被显式声明,这样vue才知道你用了哪些props。

首先定义props可以用props选项来定义,例如我想传给该组件一个info信息,那我就必须在该组件内声明一下:

export default {   props: ['info'],   created() {     console.log(this.info)   } } 

除此之外,还可以使用对象式声明的方式,例如,假设我们有一个博文组件 BlogPost.vue,父组件想要向这个博文组件传递标题、内容和作者信息。我们可以这样声明 props:

    

在这个例子中:

  • title 属性必须是一个字符串,并且是必需的。如果父组件未传递 title,Vue.js 将会发出警告。
  • content 属性是一个可选的字符串,默认值为 No content available。如果父组件未传递 content,则显示默认内容。
  • author 属性是一个可选的字符串,默认值为 Anonymous。如果父组件未传递 author,则显示作者为匿名。

父组件可以这样使用 BlogPost组件:

    

上面的例子中props的值都是静态的,实际上也可以是动态的,可以使用v-bind或:来进行动态绑定,其次他还可以是多种数据类型(String,Number,Boolean,Array,Object),例如:

          name: 'Veronica',     company: 'Veridian Dynamics'   }"  />   

一个对象内还可以绑定多个prop,例如

export default {   data() {     return {       post: {         id: 1,         title: 'My Journey with Vue'       }     }   } } 
   

注意:所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。

另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:例如:

export default {   props: ['foo'],   created() {     // ❌ 警告!prop 是只读的!     this.foo = 'bar'   } } 

也即是说爸爸让你改你才能改,子组件不能擅作主张自己更改自己的props。

校验

Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。要声明对 props 的校验,你可以向 props 选项提供一个带有 props 校验选项的对象,校验选项中的 type 可以是下列这些原生构造函数:String、Number、Boolean、Array、Object、Date、Function、Symbol、Error。例如:

export default {   props: {     // 基础类型检查     //(给出 `null` 和 `undefined` 值则会跳过任何类型检查)     propA: Number,     // 多种可能的类型     propB: [String, Number],     // 必传,且为 String 类型     propC: {       type: String,       required: true     },     // 必传但可为 null 的字符串     propD: {       type: [String, null],       required: true     },     // Number 类型的默认值     propE: {       type: Number,       default: 100     },     // 对象类型的默认值     propF: {       type: Object,       // 对象或者数组应当用工厂函数返回。       // 工厂函数会收到组件所接收的原始 props       // 作为参数       default(rawProps) {         return { message: 'hello' }       }     },     // 自定义类型校验函数     // 在 3.4+ 中完整的 props 作为第二个参数传入     propG: {       validator(value, props) {         // The value must match one of these strings         return ['success', 'warning', 'danger'].includes(value)       }     },     // 函数类型的默认值     propH: {       type: Function,       // 不像对象或数组的默认,这不是一个       // 工厂函数。这会是一个用来作为默认值的函数       default() {         return 'Default function'       }     }   } } 

另外,type 也可以是自定义的类或构造函数,Vue 将会通过 instanceof 来检查类型是否匹配。例如下面这个类:

class Person {   constructor(firstName, lastName) {     this.firstName = firstName     this.lastName = lastName   } } 
export default {   props: {     author: Person   } } 

一些补充细节:

  • 所有 prop 默认都是可选的,除非声明了 required: true。
  • 除 Boolean外的未传递的可选 prop 将会有一个默认值 undefined。
  • Boolean 类型的未传递 prop 将被转换为 false。这可以通过为它设置 default 来更改——例如:设置为 default: undefined 将与非布尔类型的 prop 的行为保持一致。
  • 如果声明了 default 值,那么在 prop 的值被解析为 undefined 时,无论 prop 是未被传递还是显式指明的 undefined,都会改为 default 值。

当 prop 的校验失败后,Vue 会抛出一个控制台警告 (在开发模式下)。

校验选项中的 type 可以是下列这些原生构造函数:String、Number、Boolean、Array、Object、Date、Function、Symbol、Error

为了更贴近原生 boolean attributes 的行为,声明为 Boolean 类型的 props 有特别的类型转换规则。以带有如下声明的 组件为例

export default {   props: {     disabled: Boolean   } } 

该组件可以被这样使用:

    

当一个 prop 被声明为允许多种类型时Boolean 的转换规则也将被应用。然而,当同时允许 String 和 Boolean 时,有一种边缘情况——只有当 Boolean出现在 String 之前时,Boolean 转换规则才适用:

// disabled 将被转换为 true export default {   props: {     disabled: [Boolean, Number]   } } // disabled 将被转换为 true export default {   props: {     disabled: [Boolean, String]   } } // disabled 将被转换为 true export default {   props: {     disabled: [Number, Boolean]   } } // disabled 将被解析为空字符串 (disabled="") export default {   props: {     disabled: [String, Boolean]   } } 

事件

可以使用$emit方法触发自定义事件,例如在一个按钮中

 

e m i t ( ) 方法在组件实例上也同样以 t h i s . emit() 方法在组件实例上也同样以 this. emit()方法在组件实例上也同样以this.emit() 的形式可用:

export default {   methods: {     submit() {       this.$emit('someEvent')     }   } } 

父组件可以通过 v-on (缩写为 @) 来监听事件:

同样,组件的事件监听器也支持 .once 修饰符,也就是修饰了只会触发一次:

  

事件参数

有时事件在触发时需要携带一些参数,举例来说,我们想要 组件来管理文本会缩放得多大。我们可以在按钮上绑定一个“increaseBy”事件,并传递一个额外的参数1。

 

然后我们在父组件中监听事件,我们可以先简单写一个内联的箭头函数作为监听器,此函数会接收到事件附带的参数:

 count += n" /> 

这样在每次点击按钮后,count的值就会加一。

或者,也可以用一个组件方法来作为事件处理函数:

 

再写个方法来接收传递的参数

methods: {   increaseCount(n) {     this.count += n   } } 

v-model

v-model 可以在组件上使用以实现双向绑定

例如现有组件,其界面为让输入框内的文字和参数modelValue保持一致,输入框内更新,参数值也会更新。

  

那么在使用该组件时就可以用v-model来和组件中的值进行绑定,例如:

  

这样message的值就和输入框中的值就行了绑定,最后呈现的效果为在输入框后面的文字回合输入框中保持一致。
请添加图片描述

此外,v-model还可以接收一个参数。例如现在有一个标题参数title

 

子组件中使用 title prop 和 update:title 事件来更新父组件的值,而非默认的 modelValue prop 和 update:modelValue事件:。例如:

  

上面的都是一个v-model的,一个组件上还可以绑定多个有参数的v-model

例如:

 
  

v-model 有一些内置的修饰符,例如 .trim,.number 和 .lazy。在组件中还可以自定义一些修饰符。

例如:创建一个自定义的修饰符 capitalize,它会自动将 v-model 绑定输入的字符串值第一个字母转为大写:

父组件中代码如下

 

子组件:每次输入更新时都会触发emitValue方法,方法中会将第一个单词变为大写

  

另外,在使用修饰符的同时加上参数也是可以的

例如:

 
 

透传Attributes

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或emits的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和id 。

class继承

例如现有模板MyButton中一个按钮:

 

在父组件中使用时加上了class样式,如:

 

最后渲染出的结果就是

 

父组件中的样式将会传递给模板中按钮,倘若子组件中已有class,则会把样式进行合并。

例如子组件中有按钮:

 

最后的结果就会变为

 

v-on继承

这样的规则也适用于v-on事件监听器中,如果父组件中有个点击事件,那么模板中的元素被点击时也会触发父组件的事件,同样,如果子组件中已有事件,那么点击后子组件的事件和父组件的事件都会被触发。

深层组件继承

如果子组件中又使用了另一个子组件,例如组件中根节点,那么在使用

时传递的attribute会直接传递给

禁用继承

这种继承关系有时可能我们不想让它生效,此时可以自己在组件选项中设置inheritAttrs:false

例如:

  

多根节点的Attribute继承

如果一个组件中有多个根节点,vue并不会自动透传所有的根节点,此时需要使用$attrs进行显示绑定,否则vue不知道要把attribute透传到哪里。例如在组件中,这样父组件中的attribute就会精准透传到中

... .. ... 

js中访问attribute

在js中可以在

例如:

 

插槽Slots

插槽内容

插槽可以让接收模板内容,为子组件传递一些模块片段,并让子组件渲染这些片段。

例如当前有一个组件如下:

   

在使用时如下:

 

最终渲染如下
请添加图片描述
使用时中的“请点击我”就会替换掉,实现如上效果,插槽中的内容可以是任意合法的模板内容,并且插槽内容可以访问到父组件的数据作用域,因此可以传入多个元素或者是组件,例如有一个点击后数值加一的按钮,还有个组件,内容很简单只有一个❤如下:

  

在使用时就可以把它也传入:

  

最终效果如下:
请添加图片描述
在外部没有提供任何内容的情况下,可以为插槽指定默认内容,比如有这样一个 组件:

 

在使用时如果不传入内容就会默认展示默认内容,有新内容则展示新内容

最终效果如下:
请添加图片描述### 具名插槽

一个组件中还可以包含多个插槽出口,每一个元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:

例如现有组件如下:

这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 出口会隐式地命名为“default”。

在父组件中使用该组件时,可以将多个插槽内容传入到各自目标插槽的出口。具体做法为,我们需要使用一个含 v-slot (可以简写为#)指令的 元素,并将目标插槽的名字传给该指令:

例如:

              

A paragraph for the main content.

And another one.

这样就做到了具体的替换。

最终渲染效果如下:

Here might be a page title

A paragraph for the main content.

And another one.

Here's some contact info

条件插槽

有时需要根据插槽是否存在来渲染某些内容,这时可以结合使用$slot属性与v-if来实现。

在下面的示例中,我们定义了一个卡片组件,它拥有三个条件插槽:header、footer 和 default。 当 header、footer 或 default 存在时,我们希望包装它们以提供额外的样式,也就是会将div中符合v-if的提供额外的样式。

 

动态插槽名

动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名:

            

作用域插槽

如果想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:

当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:

   {{ slotProps.text }} {{ slotProps.count }}  

这样在子组件域内就拿到了父组件中的text和count
请添加图片描述
具名作用域插槽

具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name=“slotProps”。当使用缩写时是这样:

     

向具名插槽中传入 props:

 

这里要特别注意一点:
插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps 的结果是 { message: ‘hello’ }。

如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 标签。尝试直接为组件添加 v-slot 指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑。举例:

    

{{ message }}

为默认插槽使用显式的