这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
系列文章
本文介绍 Vue 3 的组件相关基础,主要针对 ? 与 Vue 2 的不同点。
Vue 的组件本质上是一个具有预定义选项的实例,我们使用小型的、独立和通常可复用的组件,通过层层拼装,最终形成了一个完整的页面。
组件必须先注册以便 Vue 应用能够识别,有两种组件的注册类型:
- 全局注册
- 局部注册
全局组件
(在根组件中)使用 ? 方法 app.component('component-Name', {})
来注册全局组件,全局注册的组件可以在应用中的任何组件的模板中使用。
其中第一个参数时组件名,推荐遵循 W3C 规范中的自定义组件名(避免与当前以及未来的 HTML 元素发生冲突):字母全小写且必须包含一个连字符。第二个参数是组件的配置选项。
const app = Vue.createApp();
app.component('my-component', {
template: `<h1>Hello World!</h1>`
});
const vm = app.mount('#app')
复制代码
⚠️ 全局组件虽然可以方便地在各种组件中使用(包括其各自的内部),但是这可能造成构建项目时体积增大,用户下载 JavaScript 的无谓增加。
? 需要在 app.mount('#app')
应用挂载到 DOM 之前进行全局组件的注册
局部组件
在一个(父)组件中组件的 components
选项中注册的组件。
这些子组件通过一个普通的 JavaScript 对象来定义,其接收的参数和全局组件一样,但是它们只能在该父组件中使用,称为局部组件。
对于 components
对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。
const ComponentA = {
/* ... */
}
const ComponentB = {
/* ... */
}
const ComponentC = {
/* ... */
}
复制代码
// 然后在父组件的 `components` 选项中定义你想要使用的组件
const app = Vue.createApp({
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
复制代码
动态组件
内置的标签 <component :is="componentName />"
用以动态显式不同的组件,通过控制绑定在属性 is
上的参数值,即可显示相应的同名组件。
属性 is
可以是:
- 已注册组件的名字
- 一个组件的选项对象
? 有时候为了在切换时,保存动态组件的状态,例如组件中的输入框的值,可以用标签 <keep-alive></keep-alive>
包裹动态组件。
? 属性 is
还可以用于解决 HTML 元素嵌套的规则限制,将它应用到原生的 HTML 标签上,它的值就是组件名,这样原生标签实际渲染出来的内容就是组件。
因为对于 <ul>
、<ol>
、<table>
和 <select>
这些元素,其内部允许放置的直接子元素是有严格限制的,如果嵌入其他元素会被视为无效的内容,而提升到外部造成最终渲染问题。但如果我们需要在这些元素中使用组件作为直接子元素,则可以在「合法」的子元素上使用属性 is
,指定渲染的实际内容,这时属性 is
用在原生的 HTML 元素上,如 <tr>
其值 ? 需要使用 vue:
作为前缀,以表示解析的实际上是一个 Vue 组件
<table>
<tr is="vue:blog-post-row"></tr>
</table>
复制代码
但以上限制只是在 HTML 中直接使用 Vue 模板时才会遇到,如果是在一下前进使用模板就没有这种限制:
- 字符串,例如
template: '...'
- 单文件组件
.vue
<script type="text/x-template">
异步组件
现在的大型网页往往需要异步获取不同的数据,Vue 有一个 defineAsyncComponent
方法定义异步组件,以优化应用的加载和用户体验。
异步组件在加载页面时并不立即渲染,而是要等带一些业务逻辑完成后,才会执行组件内的逻辑和渲染到页面上。
// 全局组件
app.component('async-example', Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve({
template: '<div>I am async!</div>'
})
})
}))
// 局部组件
import { createApp, defineAsyncComponent } from 'vue'
createApp({
// ...
components: {
AsyncComponent: defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve({
template: '<div>I am async!</div>'
})
})
})
}
})
复制代码
异步组件的注册和一般的同步组件类似,如果是注册全局组件,也是使用 app.component()
进行注册,不过第二个参数使用 Vue.defineAsyncComponent()
方法告诉 Vue 应用该组件是异步组件。
defineAsyncComponent()
方法的参数是一个匿名函数,而且函数是返回一个 Promise。在 Promise 内应该 resovlve({})
一个对象,其中包含了构建组件相关配置参数。只有当 Promise resolve
或 reject
才执行异步组件的处理。
组件间传值
props
通过 Prop 向子组件传递数据。Prop 是在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
需要注意以下几点:
- 如果在父组件中传入的数据是非字符串的,需要通过绑定
v-bind:propName
的方式来传递(即使传递纯数字这一类的静态值);否则直接传递数据都会转换为字符串。 - prop 应该遵循单向数据流,即不应该在子组件中修改 prop 值(因为这些数据是在父层的)
可以以字符串数组形式列出,也可以以对象形式列出(可指定 prop 接收的数据类型、默认值、是否必须、验证条件等)。
// ...
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 有多个配置项可设
propB: {
type: String,
required: true,
default: 'abc',
validator: func() // 自定义验证函数,返回 true/false
}
// 带有默认值的对象
propC: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default() {
return { message: 'hello' }
}
},
// 具有默认值的函数
propD: {
type: Function,
// 与对象或数组默认值不同,这不是一个工厂函数,而是一个函数,用作默认值
default() {
return defaultFunction() {...}
}
}
}
复制代码
其中选项 type
可以对 prop 进行类型检查,支持原生类型和自定义构造函数(Vue 会通过 instanceof
进行检测):
-
String
-
Number
-
Boolean
-
Array
-
Object
-
Date
-
Function
-
Symbol
-
自定义构造函数
// 自定义构造函数 function Person(firstName, lastName) { this.firstName = firstName this.lastName = lastName } app.component('blog-post', { props: { author: Person // 验证 author 的值是否是通过 new Person 创建的 } }) 复制代码
⚠️ 这些 prop 的验证会在一个组件实例创建之前进行,所以组件实例的 property 例如 data
、computed
等,在 prop 验证的选项 default
或 validator
函数中是不可用的。
? 如果希望一个对象的所有属性都分别作为 prop 传入,可以使用不带参数的 v-bind
,实现类似对象解构的效果
post: {
id: 1,
title: 'My Journey with Vue'
}
复制代码
调用组件
<blog-post v-bind="post"></blog-post>
复制代码
等价
<blog-post v-bind:id="post.id" v-bind:title="post.title"></blog-post>
复制代码
? 除了传递基础数据类型,还可以传递函数作为 prop,将在父级定义的功能传递给子组件使用。
// 注册一个全局组件
app.component('date-component',{
props:['getDate'],
methods:{
clickHandler(){
this.getDate() // 获取当前时间
}
},
template:`<div @click="clickHandler">打印当前时间</div>`
})
复制代码
const app = Vue.createApp({
data() {
return {
getDate: () => {
const today = new Date();
console.log(today.toLocaleDateString())
}
}
},
template: `
<h2>Date</h2>
<date-component :getDate="getDate" />
`
})
复制代码
provide 和 inject
除了通过 props 从父组件向子组件传值,还可以用 provide
和 inject
实现从祖先组件向其子孙后代组件传值,这种方法不论组件层次嵌套有多深。
父组件有一个 provide
选项来提供数据,子组件有一个 inject
选项来开始使用这个数据。
// 父组件
app.component('todo-list', {
provide: {
user: 'John Doe'
},
})
// 子组件
app.component('todo-list-statistics', {
inject: ['user'],
created() {
console.log(`Injected property: ${this.user}`) // 注入 property: John Doe
}
})
复制代码
⚠️ 要访问父组件实例 property,我们需要将 provide
转换为返回对象的函数
// 父组件
app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide() {
return {
todoLength: this.todos.length // 访问 todos property
}
},
template: `
...
`
})
复制代码
? 默认情况下,provide/inject
绑定并不具有响应式,可以通过组合式 API computed
来实现数据的响应式。也可以在 setup
中可以使用ref
property 或 reactive
对象包装变量,再传递给 provide
来实现响应式。
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length) // 通过 computed 进行包装实现响应式
}
}
})
app.component('todo-list-statistics', {
inject: ['todoLength'],
created() {
console.log(`Injected property: ${this.todoLength.value}`) // Injected property: 5
}
})
复制代码
emit
当组件需要修改父层控管的数据(这些数据通过 props 传递进来)时,基于单向数据流原则不能在组件内进行修改,而是需要通过 $emit('eventName', value)
向外抛出自定义的事件,通知父层进行数据修改,其中 value
一般是向外传递的需要变动的数据。
然后在父层通过 v-on:eventName
或简写 @eventName
来监听这个子组件抛出的自定义事件。
如果子组件伴随着事件,同时抛出数据(第二个参数 value
),那么在父级的内联事件处理器中可以通过 $event
访问到被抛出的这个值。
<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
复制代码
如果父级的事件处理函数是一个方法,抛出的数据会作为第一个参数传入这个方法
<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
复制代码
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
复制代码
? 自定义事件名推荐使用 kebab-case 方式(小写单词之间用连字符 -
连接,因为与组件和 prop 名称不同,Vue 不会对事件名进行任何大小写转换),以便于 HTML 正确识别
? Vue 3 目前为组件提供一个 emits
选项,和选项 props
作用类似,用来定义子组件可以向其父组件触发的事件。
该选项可以接受字符串作为元素的数组,也可以接收一个对象。在对象中可以为事件设置验证器,和 props
定义里的验证器类似。
? 在对象语法中,每个 property 的值可以为 null
或验证函数,该函数将接收调用 $emit
时传递的数据。验证函数应返回布尔值,以表示事件参数是否有效。
const app = createApp({})
// 数组语法
app.component('todo-item', {
emits: ['check'],
created() {
this.$emit('check')
}
})
// 对象语法
app.component('reply-form', {
emits: {
// 没有验证函数
click: null,
// 带有验证函数
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
})
复制代码
强烈建议使用选项 emits
记录每个组件可以触发的事件,因为 ? Vue 3 移除了 .native
修饰符,对于未在子组件中的选项 emits
定义的事件,Vue 现在将把它们作为原生事件监听器(这一点和 Vue 2 不同,Vue 2 对于在使用组件时才添加的事件监听,都认为是监听自定义事件,即只有通过 $emit
触发的事件),添加到子组件的根元素中(除非在子组件的选项中设置了 inheritAttrs: false
)。
<!-- 监听从组件触发的自定义事件 close 和原生事件 click -->
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
<script>
// component
app.component('MyComponent', {
emits: ['close']
})
</script>
复制代码
在组件上使用 v-model
在使用自定义的输入型组件时,Vue 为它们进行优化,也支持使用 v-model
实现双向绑定。
<custom-input v-model="searchText"></custom-input>
<!-- 等价于以下代码 -->
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
复制代码
在外部(引用该组件时,在组件的标签上)通过指令 v-model
双向绑定需要同步的变量,它的数据默认作为一个? 名为 modelValue
的 prop 传递到组件内(因此记得在组件内部,需要在 props
选项里声明 modelValue
作为 prop),作为子组件内部表单元素的 value
属性的值。
只需要在这些组件的内部进行相应的处理:
- 在组件内使用指令
v-bind
将表单元素的value
属性绑定到以上声明的 prop 变量modelValue
上,即v-bind:value="modelValue"
(这样就可以将外部传进来的值作为表单的值,实现与外部数据的「同步」) - 在组件内使用指令
v-on
监听表单元素input
输入事件,并通过抛出update:modelValue
事件$emit('update:modelValue', $event.target.value)
向外传递相应的数据,然后外部就会接收抛出的事件,并对指令v-model
绑定的变量基于抛出的数据进行改变 (即数据的修改的操作还是在父层完成)
// conpoment
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
复制代码
<custom-input v-model="searchText"></custom-input>
复制代码
⚠️ Vue 2 提供了 .sync
修饰符,通过 v-bind:propName.sync="variable"
和 $emit('update:propName', newValue)
实现类似双向绑定的功能;但 ? 在 Vue 3 中 .sync
修饰符已被移除,而现在 Vue 3 使用类似的语法(组件也是抛出以 update:
为前缀的事件)实现的。
默认传递的 prop 变量是 modelValue
默认监听抛出的事件是 update:modelValue
,这种方法更通用,对于 type="text"
或 type=checkbox
其语义都不会混淆。如果需要修改 modelValue
这个默认值,在 Vue 2 中可以通过选项 model
进行配置,但 ? 在 Vue 3 中 model
选项已移除,作为替代可以在父级中为 v-model
添加参数,这个 argument 就是 prop 变量名和需要监听的事件名(加上 update:
前缀)
<!-- 使用组件 -->
<my-component v-model:title="bookTitle"></my-component>
<!-- 是以下的简写 -->
<my-component :title="bookTitle" @update:title="bookTitle = $event" />
复制代码
// 组件
app.component('my-component', {
props: {
title: String
},
emits: ['update:title'],
template: `
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
`
})
复制代码
在 Vue 3 中利用特定的 prop(通过调用组件时使用指令 v-model
的参数设置)和抛出相应事件,? 可以在单个组件上创建多个 v-model
,从而实现多个 prop 的双向绑定
<!-- 使用组件,实现 first-name 和 last-name 的双向绑定 -->
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
复制代码
app.component('user-name', {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName'],
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`
})
复制代码
在输入型组件上使用指令 v-model
时,不仅支持 Vue 内置的修饰符 .trim
、.number
、.lazy
,? Vue 3 还支持添加自定义的修饰符,添加到组件 v-model
的修饰符将通过 modelModifiers
prop 提供给组件。
? 修饰符应该在指令的参数后,而且修饰符支持多个链式使用
<!-- 使用自定义修饰符,实现字符串首字母大写 -->
<my-component v-model.capitalize="myText"></my-component>
复制代码
// 组件
app.component('my-component', {
props: {
modelValue: String,
// modelModifiers prop 这里我们提供了初始值
modelModifiers: {
default: () => ({})
}
},
emits: ['update:modelValue'],
methods: {
// 表单 input 事件的处理函数
emitValue(e) {
let value = e.target.value
// 如果 v-model 有修饰符 capitalize 就对表单输入值实现首字母大写
if (this.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
// 抛出事件
this.$emit('update:modelValue', value)
}
},
template: `
<input type="text"
:value="modelValue"
@input="emitValue">`,
created() {
// 当组件的 created 生命周期钩子触发时,会根据调用组件的情况对 modelModifiers prop 进行设置
// 由于这个例子中 v-model 的修饰符为 capitalize,因此 modelModifiers 对象会包含 capitalize 属性
console.log(this.modelModifiers) // { capitalize: true }
}
})
复制代码
? 如果 v-model
设置了参数,则添加到组件 v-model
的自定义的修饰符将通过 arg
+ Modifiers
形式的 prop 提供给组件。
<my-component v-model:description.capitalize="myText"></my-component>
复制代码
app.component('my-component', {
props: ['description', 'descriptionModifiers'],
emits: ['update:description'],
template: `
<input type="text"
:value="description"
@input="$emit('update:description', $event.target.value)">
`,
created() {
console.log(this.descriptionModifiers) // { capitalize: true }
}
})
复制代码
非 prop 属性
非 prop 的 attribute 是指未在组件的 props
或 ? emits
选项中显式声明的,但在引用组件时传递给子组件的 attribute,这些 attribute会默认被添加到这个组件的根元素上,即在组件的模板中作为容器的第一层的 <tag>
上。
? 如果希望 attribute 添加到组件的非根节点上,可以设定组件的选项 inheritAttrs: false
,然后将特殊的变量 $attrs
(这是一个包含所有非 prop 的 attribute 的对象)绑定 v-bind="$attrs"
到指定的节点上,? 在 Vue 3 中包括 class
、 style
、v-on
也指定到该元素上。也可指定某个 attribute 绑定 :attributeName="$attrs.propertyName"
app.component('date-picker', {
inheritAttrs: false,
template: `
<div class="date-picker">
<input type="datetime-local" v-bind="$attrs" />
</div>
`
})
复制代码
? ? 非 prop attribute 在 Vue 3 中包括 class
、 style
、v-on
,将添加的事件监听也纳入其中,因此在 Vue 3 中移除了 $listeners
对象
<date-picker @change="submitChange"></date-picker>
复制代码
app.component('date-picker', {
created() {
console.log(this.$attrs) // { onChange: () => {} }
}
})
复制代码
⚠️ 由于 ? Vue 3 支持组件存在多个根节点,因此与 Vue 2 只允许单个根节点组件不同,具有多个根节点的组件不具有自动 attribute 回退行为,因此如果未显式绑定 $attrs
,将发出运行时警告。
// 这将发出警告
app.component('custom-layout', {
template: `
<header>...</header>
<main>...</main>
<footer>...</footer>
`
})
// 没有警告,$attrs被传递到<main>元素
app.component('custom-layout', {
template: `
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
`
})
复制代码
插槽
插槽 slot 是 Vue 实现的一套内容分发的 API,一般用于组件作为布局 layout 使用。
在子组件的模板中使用 <slot>
元素作为占位符
<!-- todo-button 组件模板 -->
<button class="btn-primary">
<slot></slot>
</button>
复制代码
然后在调用组件时,可以传递内容到插槽指定的位置
<todo-button>
Add todo
</todo-button>
复制代码
? 当组件渲染的时候,将会用给定的内容替换插槽,实现内容的分化。
<!-- 渲染 HTML -->
<button class="btn-primary">
Add todo
</button>
复制代码
? 可以在子组件模板的插槽标签内预置一些默认内容,它只会在没有提供内容的时候被渲染。
具名插槽
如果要将多个内容分发到子组件不同节点,可以在子组件模板中设置多个具名操作,即为子组件的标签 <slot>
添加属性 name
。
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
复制代码
然后在父组件中使用 <template>
元素,并以 v-slot
指令和相应的参数指定内容分化到子组件的哪一个插槽。
? 其中参数 default
是默认插槽,如果分化内容不指定插槽名称时,都放到子组件的默认插槽中
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
复制代码
? 设置具名插槽的指令 v-slot:slotName
有缩写 #slotName
<base-layout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</base-layout>
复制代码
⚠️ 然而,和其它指令如 v-bind:param
缩写 :param
、v-on:eventName
缩写 @eventName
一样,该缩写只在其有参数的时候才可用,这意味着以下语法是无效的:
<!-- This will trigger a warning -->
<todo-list #="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
<!-- The right way -->
<todo-list #default="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
复制代码
作用域插槽
在父级组件中分化的内容,只能使用其作用域中的数据,而不能访问子组件的数据。
如果要让分化的内容能够访问子组件中才有的数据,需要在子组件模板设置插槽时,将数据绑定到插槽标签 <slot>
的相应属性上,这些 attribute 称为插槽 props
// 子组件 todo-list 的模板
<ul>
<li v-for="( item, index ) in items">
<slot :item="item"></slot>
</li>
</ul>
复制代码
然后在父组件中,可以用指令 v-slot
的值来接收子组件「抛出」的数据,该值是包含所有插槽 prop 的一个对象,以下例子中将该对象命名为 slotProps
你可以使用其他名称
<todo-list>
<template v-slot:default="slotProps">
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
复制代码
? 如果组件只有一个插槽,即只有默认插槽时,在调用组件时可以把 v-slot
直接用在组件标签上,即把组件的标签当作插槽的模板 <template>
来使用,即以上例子父组件的简写形式:
// 简写形式
<todo-list v-slot:default="slotProps">
<span class="green">{{ slotProps.item }}</span>
</todo-list>
// 甚至省略 default 参数
<todo-list v-slot="slotProps">
<span class="green">{{ slotProps.item }}</span>
</todo-list>
复制代码
⚠️ 以上简写只能将内容分化到一个插槽,只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template>
的语法。
? 如果父组件在分化内容时只使用了插槽 prop 对象中某几个属性时,可以使用解构来简化代码
<todo-list v-slot="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
<!-- 解构也可以实现插槽 prop 属性的重命名,以下将 item 重命名为 todo -->
<todo-list v-slot="{ item: todo }">
<i class="fas fa-check"></i>
<span class="green">{{ todo }}</span>
</todo-list>
<!-- 定义备用内容,用于插槽 prop 是 undefined 的情形 -->
<todo-list v-slot="{ item = 'Placeholder' }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
复制代码
混入
混入 mixin 是指将组件选项中可复用的部分「抽取」出来成为一个对象(可以包含 computed
、method
、watch
等),以供其他组件复用。然后在组件(包括根组件)的选项 mixins
中使用该对象即可复用;或通过 app.mixin({})
进行全局混入。
? 但是由于 Mixin 很容易引起 property 名冲突,而且不能向 mixin 传递任何参数而降低了它们的灵活性。为了解决这些问题,让代码复用更高效,在 Vue 3 新增了一种通过逻辑关注点组织代码的新方法——组合式 API。
// 定义一个混入对象
const myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
const Component = Vue.extend({
mixins: [myMixin]
})
const component = new Component() // "hello from mixin!"
复制代码
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行「合并」
模板引用
如果想引用组件在挂载到页面后的 DOM 节点,可以为组件的模板添加 ref
属性,然后就可以通过变量 $refs
来访问相应的元素。
例如在组件挂载时,以编程的方式 focus 到这个 input 上
const app = Vue.createApp({})
app.component('base-input', {
template: `
<input ref="input" />
`,
methods: {
focusInput() {
this.$refs.input.focus()
}
},
mounted() {
this.focusInput()
}
})
复制代码
⚠️ 变量 $refs
只会在组件渲染完成之后生效,这仅作为一个用于直接操作子元素的「逃生舱」,应该尽量避免在模板或计算属性中通过 $refs
直接操作 DOM 节点,而应该通过数据驱动 DOM 变更。
? 在 Vue 2 中在 v-for
里使用的 ref
attribute 则变量 $refs
会是相应的数组。? 而在 Vue 3 中在这种情况下不再会为 $ref
自动创建数组,如果要在 v-for
的单个绑定获取多个 ref(列表),可以将 ref
绑定到一个函数上,默认传递 DOM 作为参数:
<div v-for="item in list" :ref="setItemRef"></div>
复制代码
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
if (el) {
this.itemRefs.push(el)
}
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
复制代码
对于以上实例 itemRefs
不必是数组,也可以是一个对象,然后通过可迭代对象的 key 设置 ref;如果需要,itemRef
也可以是响应式的且可以被监听
生命周期函数
每个组件在被创建时都要经过一系列的初始化过程:设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。Vue 在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
beforeCreate()
在实例生成之前created()
在实例生成之后,可以访问响应性数据beforeMount()
在模板渲染后,但还没挂载到 DOM 之前,可以访问app.$el
(其中app
是 Vue 实例)虚拟 DOMmounted()
实例挂载到 DOM 之后beforeUpdate()
响应式数据发生变换,虚拟 DOM 执行重新渲染和更新前updated()
响应式数据发生变换,虚拟 DOM 执行重新渲染和更新后- ?
beforeUnmount()
销毁 Vue 实例时,销毁之前执行 - ?
unmounted()
销毁 Vue 实例时,销毁之后执行
? ? 可以通过调用函数 app.unmount()
销毁实例(其中 app
是 Vue 实例),相关的生命周期函数是 beforeUnmount()
和 unmounted()
⚠️ 生命周期钩子的 this
上下文指向调用它的当前活动实例,一般是组件实例 vm
,因此注意不要在回调上(对于选项 property 而言也是)使用箭头函数,因为箭头函数并没有 this
,this
会作为变量一直向上级词法作用域查找,可能会导致错误。