VUE详解——组件篇

avatar
作者
猴君
阅读量:0

前言

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

文章目录

基础

什么是组件?

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

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

<script> export default {   data() {     return {       count: 0     }   } } </script> <template>   <button @click="count++">You clicked me {{ count }} times.</button> </template> 

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

<script> import ButtonCounter from './ButtonCounter.vue'  export default {   components: {     ButtonCounter   } } </script>  <template>   <h1>Here is a child component!</h1>   <ButtonCounter /> </template> 

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

<h1>Here is a child component!</h1> <ButtonCounter /> <ButtonCounter /> <ButtonCounter /> 

注册

一个 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) 

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

<!-- 这在当前应用的任意组件中都可用 --> <ComponentA/> <ComponentB/> <ComponentC/> 

局部注册

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

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

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

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

<script> import ComponentA from './ComponentA.vue' export default {   components: {     ComponentA   } } </script> <template>   <ComponentA /> </template> 

对于每个 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:

<!-- BlogPost.vue --> <template>   <div class="blog-post">     <h2>{{ title }}</h2>     <p>{{ content }}</p>     <p>Written by: {{ author }}</p>   </div> </template> <script> export default {   props: {     title: {       type: String,       required: true     },     content: {       type: String,       default: 'No content available'     },     author: {       type: String,       default: 'Anonymous'     }   } } </script>  

在这个例子中:

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

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

<!-- ParentComponent.vue --> <template>   <div>     <BlogPost       title="Vue.js Props Example"       content="This is an example of using props in Vue.js."       author="John Doe"     />   </div> </template> <script> import BlogPost from './BlogPost.vue'; export default {   components: {     BlogPost   } } </script>  

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

<BlogPost :title="post.title" /> <BlogPost :title="post.title + ' by ' + post.author.name" /> <BlogPost :likes="42" /> <BlogPost :is-published="false" /> <BlogPost :comment-ids="[234, 266, 273]" /> <BlogPost   :author="{     name: 'Veronica',     company: 'Veridian Dynamics'   }"  /> <!-- 根据一个变量的值动态传入 --> <BlogPost :author="post.author" /> 

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

export default {   data() {     return {       post: {         id: 1,         title: 'My Journey with Vue'       }     }   } } 
<BlogPost v-bind="post" /> <!-- 等价于 --> <BlogPost :id="post.id" :title="post.title" /> 

注意:所有的 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   } } 

该组件可以被这样使用:

<!-- 等同于传入 :disabled="true" --> <MyComponent disabled /> <!-- 等同于传入 :disabled="false" --> <MyComponent /> 

当一个 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方法触发自定义事件,例如在一个按钮中

<button @click="$emit('要触发的事件')">Click Me</button> 

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

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

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

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

<MyComponent @some-event="callback" /> <MyComponent @some-event.once="callback" /> 

事件参数

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

<button @click="$emit('increaseBy', 1)">Increase by 1</button> 

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

<MyButton @increase-by="(n) => count += n" /> 

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

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

<MyButton @increase-by="increaseCount" /> 

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

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

v-model

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

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

<script> export default {   props: ['modelValue'],   emits: ['update:modelValue'] } </script> <template>   <input     :value="modelValue"     @input="$emit('update:modelValue', $event.target.value)"   /> </template> 

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

<script> import CustomInput from './CustomInput.vue' export default {   components: { CustomInput },   data() {     return {       message: '初始值'     }   } } </script> <template>   <CustomInput v-model="message" /> {{ message }} </template> 

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

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

<MyComponent v-model:title="bookTitle" /> 

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

<script> export default {   props: ['title'],   emits: ['update:title'] } </script> <template>   <input     type="text"     :value="title"     @input="$emit('update:title', $event.target.value)"   /> </template> 

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

例如:

<UserName v-model:first-name="first"  v-model:last-name="last"/> 
<script> export default {   props: {     firstName: String,     lastName: String   },   emits: ['update:firstName', 'update:lastName'] } </script> <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)"   /> </template> 

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

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

父组件中代码如下

<MyComponent v-model.capitalize="myText" /> 

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

<script> export default {   props: {     modelValue: String,     modelModifiers: {       default: () => ({})     }   },   emits: ['update:modelValue'],   methods: {     emitValue(e) {       let value = e.target.value       if (this.modelModifiers.capitalize) {         value = value.charAt(0).toUpperCase() + value.slice(1)       }       this.$emit('update:modelValue', value)     }   } } </script> <template>   <input type="text" :value="modelValue" @input="emitValue" /> </template> 

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

例如:

<UserName   v-model:first-name.capitalize="first"   v-model:last-name.uppercase="last" /> 
<script> export default {   props: {     firstName: String,     lastName: String,     firstNameModifiers: {       default: () => ({})     },     lastNameModifiers: {       default: () => ({})     }   },   emits: ['update:firstName', 'update:lastName'],   created() {     console.log(this.firstNameModifiers) // { capitalize: true }     console.log(this.lastNameModifiers) // { uppercase: true }   } } </script> 

透传Attributes

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

class继承

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

<button>我是按钮</button> 

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

<MyButton class="large" /> 

最后渲染出的结果就是

<button class="large">我是按钮</button> 

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

例如子组件中有按钮:

<button class="small">我是按钮</button> 

最后的结果就会变为

<button class="small large">我是按钮</button> 

v-on继承

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

深层组件继承

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

时传递的attribute会直接传递给

禁用继承

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

例如:

<template>   <div>     <!-- 组件内容 -->   </div> </template> <script> export default {   inheritAttrs: false, // 禁止继承父组件的属性到根元素   // 其他组件选项 }; </script> 

多根节点的Attribute继承

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

<name v-bind:"$attrs">...</name> <age>..</age> <weight>...</weight> 

js中访问attribute

在js中可以在

例如:

<script setup> import { useAttrs } from 'vue' const attrs  userAttrs() </script> 

插槽Slots

插槽内容

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

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

<template>   <button class="fancy-btn">   	<slot/> 	</button> </template>  <style> .fancy-btn {   color: #fff;   background: linear-gradient(315deg, #42d392 25%, #647eff);   border: none;   padding: 5px 10px;   margin: 5px;   border-radius: 8px;   cursor: pointer; } </style> 

在使用时如下:

<template>   <FancyButton>     请点击我 <!-- slot content -->  	</FancyButton> </template> 

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

 <template>❤️</template> 

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

<script> import FancyButton from './FancyButton.vue' import AwesomeIcon from './AwesomeIcon.vue'   export default {   components: { FancyButton, AwesomeIcon },     data() {        return {           count: 1        }     } } </script> <template>    <span >{{ count }}</span>    <FancyButton @click="count++">点一下加一,当前为:{{ count }}</FancyButton>   <FancyButton>     <span style="color:cyan">点我! </span>     <AwesomeIcon />   </FancyButton> </template> 

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

<template>   <button type="submit"> 	  <slot>     	默认内容   	</slot> 	</button> </template> 

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

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

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

例如现有组件如下:

<div class="container">   <header>     <slot name="header"></slot>   </header>   <main>     <slot></slot>   </main>   <footer>     <slot name="footer"></slot>   </footer> </div> 

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

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

例如:

<BaseLayout>   <template #header>     <h1>Here might be a page title</h1>   </template>        <!-- 隐式的默认插槽 -->   <p>A paragraph for the main content.</p>   <p>And another one.</p>        <template #footer>     <p>Here's some contact info</p>   </template> </BaseLayout> 

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

最终渲染效果如下:

<div class="container">   <header>     <h1>Here might be a page title</h1>   </header>   <main>     <p>A paragraph for the main content.</p>     <p>And another one.</p>   </main>   <footer>     <p>Here's some contact info</p>   </footer> </div> 

条件插槽

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

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

<template>   <div class="card">     <div v-if="$slots.header" class="card-header">       <slot name="header" />     </div>     <div v-if="$slots.default" class="card-content">       <slot />     </div>     <div v-if="$slots.footer" class="card-footer">       <slot name="footer" />     </div>   </div> </template> 

动态插槽名

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

<base-layout>   <template v-slot:[dynamicSlotName]>     ...   </template>    <!-- 缩写为 -->   <template #[dynamicSlotName]>     ...   </template> </base-layout> 

作用域插槽

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

<div>   <slot :text="欢迎光临" :count="1"></slot> </div> 

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

<MyComponent v-slot="slotProps">   {{ slotProps.text }} {{ slotProps.count }} </MyComponent> 

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

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

<MyComponent>   <template #header="headerProps">     {{ headerProps }}   </template> </MyComponent> 

向具名插槽中传入 props:

<slot name="header" message="hello"></slot> 

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

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

<div>   <slot :message="hello"></slot>   <slot name="footer" /> </div> 
<!-- 该模板无法编译 --> <MyComponent v-slot="{ message }">   <p>{{ message }}</p>   <template #footer>     <!-- message 属于默认插槽,此处不可用 -->     <p>{{ message }}</p>   </template> </MyComponent> 

为默认插槽使用显式的 <template> 标签有助于更清晰地指出 message 属性在其他插槽中不可用:

<MyComponent>   <!-- 使用显式的默认插槽 -->   <template #default="{ message }">     <p>{{ message }}</p>   </template>   <template #footer>     <p>Here's some contact info</p>   </template> </MyComponent> 

高级列表组件示例

下面展示一个作用域插槽的应用场景,来看一个 `组件的例子。它会渲染一个列表,并同时会封装一些加载远端数据的逻辑、使用数据进行列表渲染、或者是像分页或无限滚动这样更进阶的功能。然而我们希望它能够保留足够的灵活性,将对单个列表元素内容和样式的控制权留给使用它的父组件。

<template>   <FancyList api-url="url" :per-page="10">     <template #item="{ body, username, likes }">       <div class="item">         <p>{{ body }}</p>         <p class="meta">by {{ username }} | {{ likes }} likes</p>       </div>     </template>   </FancyList> </template> 
<script> export default {   props: ['api-url', 'per-page'],   data() {     return {       items: []     }   },   mounted() {     setTimeout(() => {       this.items = [         { body: 'Scoped Slots Guide', username: 'Evan You', likes: 20 },         { body: 'Vue Tutorial', username: 'Natalia Tepluhina', likes: 10 }       ]     }, 1000)   } } </script>  <template>   <ul>     <li v-if="!items.length">       Loading...     </li>     <li v-for="item in items">       <slot name="item" v-bind="item"/>     </li>   </ul> </template> 

最终效果如下:
请添加图片描述

依赖注入

通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦。
请添加图片描述provide 和inject 可以帮助我们解决这一问题[1]。一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入中父组件提供给整条链路的依赖。

请添加图片描述

provide

可以使用provide()为组件后代提供数据,例如

<script setup> import { provide } from 'vue' provide('注入名',) <script> 

import { provide } from 'vue' export default {    setup() {       provide('注入名',)    } } 

provide()会接收两个参数,第一个是注入名,可以是字符串或是一个Symbol。第二个参数就是要提供的值,可以是任意类型的数据。

除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖,例如

import { createApp } from 'vue' const app = createApp({}) app.provide('message',1) 

在应用级别提供的数据在该应用内的所有组件中都可以注入。

Inject

要注入上层组件提供的数据,需使用inject()函数,例如:

<script setup> import {inject } from 'vue' const message = inject('message') </script> 

如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。

在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:

const value = inject('key', () => new ExpensiveClass(), true) 

第三个参数表示默认值应该被当作一个工厂函数。

异步组件

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了defineAsynccomponent 方法来实现此功能。例如:

import { defineAsyncComponent } from 'vue'  const AsyncComp = defineAsyncComponent(() => {   return new Promise((resolve, reject) => {     // ...从服务器获取组件     resolve(/* 获取到的组件 */)   }) }) 

defineAsynccomponent方法接收一个返回 Promise 的加载函数。这个 Promise的 resolve 回调方法应该在从服务器获得组件定义时调用。也可以调用 reject(reason)表明加载失败。

最后得到的 AsyncComp 是一个外层包装过的组件,仅在负面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。

与普通组件一样,异步组件可以使用app.component全局注册,例如

app.component('MyComponent', defineAsyncComponent(() =>   import('./components/MyComponent.vue') )) 

或者在父组件中直接定义

<script setup> import { defineAsyncComponent } from 'vue' const AdminPage = defineAsyncComponent(() =>import('./components/AdminPageComponent.vue') ) </script> <template>   <AdminPage /> </template> 

加载和错误

异步操作不可避免地会涉及到加载和错误状态,因此 defineAsynccomponent()也支持在高级选项中处理这些状态,例如:

const AsyncComp = defineAsyncComponent({   // 加载函数   loader: () => import('./Foo.vue'),   // 加载异步组件时使用的组件   loadingComponent: LoadingComponent,   // 展示加载组件前的延迟时间,默认为 200ms   delay: 200,   // 加载失败后展示的组件   errorComponent: ErrorComponent,   // 如果提供了一个 timeout 时间限制,并超时了   // 也会显示这里配置的报错组件,默认值是:Infinity   timeout: 3000 }) 

内置组件

component

是一个抽象的组件,用于动态地渲染不同的组件或元素。

通过绑定 is 属性可以实现动态组件的切换和渲染。

<component :is="currentComponent"></component> 

transition

和 提供了在 Vue.js 中实现过渡和动画效果的功能。

通过定义过渡的 CSS 类名,可以控制元素在进入或离开 DOM 时的动画效果。

当一个 组件中的元素被插入或移除时,会发生下面这些事情:

  1. Vue 会自动检测目标元素是否应用了 CSS 过渡或动画。如果是,则一些 CSS 过渡 class会在适当的时机被添加和移除。
  2. 如果有作为监听器的 JavaScript 钩子,这些钩子函数会在适当时机被调用。
  3. 如果没有探测到 CSS 过渡或动画、也没有提供 JavaScript 钩子,那么 DOM 的插入、删除操作将在浏览器的下一个动画帧后执行。

使用例子:最后的效果为点击一次按钮后,下面的文字会出现或者消失。实现的方法为定义好元素进入时和消失时的状态,在写好过渡时的效果。

<script setup> import { ref } from 'vue'  const show = ref(true) </script> <template>   <button @click="show = !show">按钮</button>   <Transition>     <p v-if="show">我的名字</p>   </Transition> </template> <style> .v-enter-active, .v-leave-active {   transition: opacity 0.5s ease; } .v-enter-from, .v-leave-to {   opacity: 0; } </style> 

过程如下:
请添加图片描述

  1. v-enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  2. v-enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  3. v-enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  4. v-leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  5. v-leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  6. v-leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

keep-alive

是一个抽象组件,用于保持组件状态或避免多次渲染。

当组件被 包裹时,其状态将会被缓存,而不是每次切换时重新渲染。

例如:下方引用了两个组件A和B

<script setup> import { shallowRef } from 'vue' import CompA from './CompA.vue' import CompB from './CompB.vue' const current = shallowRef(CompA) </script> <template>   <div class="demo">     <label><input type="radio" v-model="current" :value="CompA" /> A</label>     <label><input type="radio" v-model="current" :value="CompB" /> B</label>     <KeepAlive>       <component :is="current"></component>     </KeepAlive>   </div> </template> 

页面中渲染效果如下:点击上面的选项会切换下面的内容,不过由于组件使用了KeepAlive,因此每次切换时不会重复渲染,会保留之前的值。

请添加图片描述
请添加图片描述

teleport

允许你将 DOM 元素渲染到应用的任何地方,而不受当前 DOM 结构的限制。这在需要在应用中动态移动元素时非常有用,例如在模态框中渲染弹出内容。

<button @click="open = true">Open Modal</button> <Teleport to="body">   <div v-if="open" class="modal">     <p>Hello from the modal!</p>     <button @click="open = false">Close</button>   </div> </Teleport> 

Suspense

是 Vue.js 3.x 中新增的组件,用于处理异步组件的加载和状态。它可以在异步组件加载完成之前显示占位内容,并处理加载状态和错误。例如:

<Suspense>   <template #default>     <AsyncComponent />   </template>   <template #fallback>     <div>Loading...</div>   </template> </Suspense> 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!