深入了解Vue组件

3,514 阅读11分钟

前言

为了更好的掌握Vue,打算从理解基本使用到模拟实现的方式,重学Vue3

本文是系列文章的第二部分:深入组件

由于篇幅较长,为了阅读提高阅读体验,我将此部分分为上下两篇,本文为上篇

你将收获

看完本文,你将能清楚地知道组件相关的细节,这些细节能帮助你解决日常开发中,遇到的大多数组件相关的问题

本文特点

  • 案例配有动画

好处是,一来不会枯燥无聊,二来可以帮助你快速深入了解组件,三来方便以后复习和查阅

备注

这本文需要你有第一部分的基础,第一部分可移步:

  1. 10张脑图带你快速入门Vue3 | 附高清原图
  2. 理解应用实例和组件实例 | 重学Vue3
  3. 理解模板语法 | 重学Vue3
  4. 深入理解配置选项之data和methods| 重学Vue3
  5. 理解计算属性和监听器 | 重学Vue3
  6. 绑定class和内联样式style | 重学Vue3
  7. 理解条件渲染 | 重学Vue3
  8. 理解列表渲染 | 重学vue3
  9. 理解事件处理及相关修饰符 | 重学Vue3
  10. 理解v-model及其修饰符 | 重学Vue3
  11. 组件的基本使用 | 重学Vue3

主要看第一篇,如有时间可以好好看看最后一篇组件的基本使用

目录结构

本文的目录结构如下

image.png

注册组件

此处分为以下3个部分讲

image.png

组件名

注册组件时,需要提供一个名作为组件名

在 DOM 中使用组件时,建议组件名的字母全小写,且包含连字符‘-’,目的是为了避免与未来HTML元素名冲突

举例🌰:定义一个名叫my-component组件

动画1.gif

在字符串模板或单个文件组件中定义组件时,组件名可以使用 kebab-case,也可以使用 PascalCase

  • 如果使用kabab-case,则在使用处也需要使用kabab-case

  • 如果使用PascalCase,则在使用处既可以使用PascalCase,也可以使用kabab-case的写法,但例外情况是,如果使用处是在DOM中,则DOM只有 kebab-case 是有效的

举例🌰:定义时使用PascalCase,在DOM中使用kebab-case和PascalCase两种方式使用该组件

动画2.gif

发现只有使用kebab-case的才能显示出来,第二种Vue发出警告了

因此,定义组件名时,建议使用kabab-case的写法

全局注册

如之前的例子,通过应用实例身上的component方法注册的组件就是全局组件

全局组件可以用在任何新创建的组件实例的模板中

举例🌰:定义两个全局组件,将一个组件用在另一个组件模板中

动画3.gif

可以看到,被放在另一个组件中的组件也被渲染出来了

全局注册有缺陷,可能会造成了用户下载无谓的JS代码,因此可以使用局部注册

局部注册

使用组件的配置选项components注册局部组件

  • 首先声明一个普通的JS对象来定义组件
  • 然后在 components 选项中注册

对于components中的每个属性,属性名就是自定义元素名,属性值就是组件的选项对象

举例🌰:通过components注册两个局部组件component-1和component-2

动画4.gif

需要注意:局部注册的组件在其子组件中不可用,即component-1中不能使用component-2,如果要使用,则必须想注册局部组件那样,在component-1的配置项components中注册

可以在模块系统中局部注册

举例🌰:通过模块化导入组件配置,并将配置引入components中

动画5.gif

小结

为了方便,我将注册组件相关内容汇总为一图

1 注册组件.png

组件Props

此处分为以下5个部分讲

image.png

属性的类型

在组件配置项props中,可以以字符串数组形式列出的组件的属性

举例🌰:添加一个自定义属性author,并在props中列出来

动画6.gif

也可以在props中,以对象的形式为每个属性指定值类型

举例🌰:将author的类型设置为字符串

动画7.gif

指定属性类型的好处

  • 相当于为组件提供了文档
  • 当输入错误类型时,浏览器的控制台会给予提示

静态属性和动态属性

传递静态属性

如上例,在使用组件时,直接在组件上添加属性author,并可以为属性传递一个静态属性值LBJ

传递动态属性

在使用组件时,可以使用v-bind为属性动态赋值

可以赋值为一个数字、布尔值、数组、对象等

举例🌰:为组件添加一个动态属性like,值为数字

动画8.gif

可以使用不带参数的 v-bind,将一个对象的所有 property 都作为组件的 prop 传入

举例🌰:在自定义组件my-component上使用v-bind,不带参数

动画9.gif

可以看到最终的结果,传入的对象被展开了

单项数据流

父级组件的更新 prop 会向下流动到子组件,反之则不行,Vue会发出警告

举例🌰:在子组件中修改title的数据

动画10.gif

可以看到报错了:Vue warn]: Attempting to mutate prop "title". Props are readonly.

这其实时好的,防止子组件意外变更父级组件的状态,从而导致应用的数据流向难以理解

当父级组件的prop值发生变更,子组件中所有的 prop 都将会刷新为最新的值

如果真的想在子组件内部改变 prop,官方提供了两种可能的情形,及对应的做法

情形1:这个 prop 传递一个初始值作为子组件的一个本地数据

推荐做法:在子组件的data中声明一个属性,值为prop的值

举例🌰:父级组件传递initialCounter,子组件声明另外一个变量,将initialCounter的值赋值过去

动画11.gif

以后如果要修改,就直接修改counter

情形2:这个 prop 传递一个初始值需要在子组件中进行计算或转换

推荐做法:在子组件中定义一个对应的计算属性

举例🌰:父组件传递size,子组件通过计算属性重新计算出一个值normalizedSize

动画12.gif

特别注意,对于一个数组或对象类型的 prop,在子组件中变更这个对象或数组本身,将会影响到父组件

属性验证

属性验证有利于给使用组件的人提供帮助

属性验证方式

指定验证的方式,只要给组件的配置项props提供一个特定对象,对象的属性名即使组件prop,值为对应的验证

举例🌰:以下多种验证方式

动画13.gif

propA: Number表示propA的值类型是Number

propB: [String, Number]表示propB的值类型是String或Number

propC: {type: String,required: true} 表示类型为String且必填

除此之外以上展示的,还有其他的验证方式:

propD: {type: Number,default: 1}表示类型为Number且有默认值

propE: {type: Object,default() {return {message: 'hello' }}}表示默认值为对象,注意:对象或数组默认值必须从一个函数获取

propF: {validator(value) {return ['success','warning', 'danger'].includes(value)}}表示自定义验证函数

propG:{type:Function,default(){return 'xxx' }}表示类型为函数且带默认值 开发环境中,当 prop 验证失败时,Vue会发出警告

注意:属性验证会在组件实例创建之前进行验证,因此所有的组件实例的property如data、computed 等, 在 default 或 validator 函数中是不可用的

type的类型

可以是下列原生构造函数中的一个:String、Number、Boolean、Array、Object、Date、Function、Symbol

也可以是一个自定义的构造函数

举例🌰:使用自定义构造函数作为类型

动画14.gif

Prop的大小写命名

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

因此,在组件注册时,prop名使用camelCase (驼峰命名法),prop的名需要使用与HTML中等价的写法

举例🌰:使用在注册组件时,prop名是camelCase

动画15.gif

如果在字符串模板使用,则没有这个限制,既可以使用camelCase也可以使用kebab-case

小结

为了方便,我将注册Props相关内容汇总为一图

2组件Props.png

非属性特性

此处分为以下4个部分讲

image.png

概念

非 Prop 的 Attribute指的是组件中没有定义在 props 或 emits选项中的特性

常见的如:class、style 和 id等等

非属性特性具有继承的特点

所谓继承,即当组件返回单个根节点时,非 prop attribute 将自动添加到根节点的 attribute 中

继承的特点也适用于事件监听器,即事件监听器从父级组件传递到子组件

举例🌰:为组件添加一个class属性和一个事件onclick

动画16.gif

可以看到,最终两个属性都继承在组件的根节点中

事件监听器需要注意事件与根节点是否匹配

禁用非属性特性的继承

在组件的选项中设置inheritAttrs:false即可

举例🌰:给上例中添加inheritAttrs:false

动画17.gif

可以看到非props的特性并没有继承在button节点上

$attrs

可以使用组件的$attrs属性访问这些非属性特性

举例🌰:通过$attrs找到所有非属性特性

动画18.gif

于是可以利用v-bind="$attrs",将所有非属性特性批量添加到指定节点中

多个根节点也一样,可以为特定的节点实现非属性特性的继承

小结

为了方便,我将非属性特性相关内容汇总为一图

3非属性特性.png

自定义事件

此处分为以下5个部分讲

image.png

定义自定义事件

可将自定义事件定义在组件 emits 选项中,所有事件名可装在一个数组

举例🌰:在emits选择中,放入一个自定义事件inFocus

动画19.gif

当在emits选项中如果定义了原生事件 (如click) 时,将使用组件中的事件替代原生事件

派发事件和监听事件

可以在子组件中通过$emit()派发事件并传递参数

this.$emit('submit', { email, password })

可以在父级组件通过v-on监听事件

举例🌰:在子组件中触发my-event事件,在父级组件中监听该事件

动画20.gif

事件名

自定义事件的事件名与组件和 prop 一样,提供了自动大小写转换

在DOM中,为组件添加自定义事件监听时,事件名建议使用kebab-case命名法,派发的也是kebab-case

如之前的这个例子🌰

动画20.gif

如果在字符串模板中使用,则可以是驼峰也可以是kebab-case命名法

验证事件

验证事件和验证prop类似,只是前者使用emits后者使用props

emits选项的值可以为对象,对象中每个属性名表示事件名,属性的值表示事件相关的验证

相关验证通常是一个函数,函数会自动接收所监听事件的参数,其实验证事件主要就是验证事件传递的参数是否合规

举例🌰:验证my-event事件,必须传入两个参数

动画21.gif

如上,验证my-event需要两个参数,但是我没有传,结果就是Vue发出警告:[Vue warn]: Invalid event arguments: event validation failed for event "my-event".

验证函数最终会返回一个布尔值,表示事件是否有效

组件上的v-model

v-model的参数

默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件

可以通过v-model的参数来修改modelValue,如v-model:xxx

修改后的事件名对应变成update:xxx

举例🌰:下面事件名称变成了update:my-title,而不是默认的update:modelValue

动画22.gif

多个v-model绑定

可以在单个组件实例上创建多个 v-model 绑定

每个 v-model 将同步到不同的 prop,而不需要在组件中添加额外的选项

其实使用上,跟单个v-model没啥区别,这里就不举例🌰了

处理v-model修饰符

v-model的内置修饰符有.trim、.number 和 .lazy,作用分别是:去除两边空白、将其变成数字和变成懒模式

用法很简单,直接在v-model后或在其修饰符后添加即可,举例🌰:将title两边空格去掉

动画23.gif

Vue3中可以为v-model自定义修饰符,通过modelModifiers

对于带参数的 v-model 绑定,生成的 prop 名称将为 arg + "Modifiers"

举例🌰:自定义一个将首字母大写的修饰符capitalize

<div id="app">
    <my-component v-model.capitalize='title'></my-component>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
    const { createApp } = Vue
    const MyComponent = {
        template: '<input @input="onInput" :value="modelValue" />',
        emits: ['update:mode-value'],
        props: {
            modelValue: {
                type: String,
                default: ''
            },
            modelModifiers: {
                default: () => ({})
            }
        },
        methods: {
            onInput(e) {
                let val = e.target.value
                if (this.modelModifiers.capitalize) {
                    val = val.charAt(0).toUpperCase() + val.slice(1)
                    console.log(val);
                }
                this.$emit('update:mode-value', val)
            }
        }
    }
    const app = createApp({
        data() {
            return {
                title: 'v-model修饰符'
            }
        },
        components: { 'my-component': MyComponent }
    })
    app.mount('#app');
</script>

小结

为了方便,我将自定义事件相关内容汇总为一图

4自定义事件.png

END

为了方便批量下载,脑图原图会上传到GitHub:

github.com/jCodeLife/m…

文中如有其他问题,欢迎留言告知~