本文全部按照组合式API风格
注册
全局注册
javascript
使用app.component('MyComponent',{/* 组件的实现 */})
或者直接导入单文件
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
局部注册 直接在组件中引入直接使用
xml
<script setup> import ComponentA from './ComponentA.vue' </script>
<template> <ComponentA /> </template>
Props
props声明:
javascript
//字符串数组
const props = defineProps(['foo'])
//对象声明
const props = defineProps({
title: String,
likes: Number
})
单向数据流 props是不支持在子组件中更改的,如果你有更改的需求通常来源于以下三种场景:
-
prop被用于初始值
建议重新定义一个局部数据属性
-
需要对传入的prop值做进一步的转换
基于prop值定义一个计算属性
-
子组件需要更改prop值的状态
建议使用emit让父组件去执行更改
一些补充的细节:
- 所有的prop默认是可选的,除非声明了 required:true
- 除Boolean外未传递的值将会有一个默认值
undefined
,Boolean会默认false
- 如果声明了
default
值,那么在 prop 的值被解析为undefined
时,无论 prop 是未被传递还是显式指明的undefined
,都会改为default
值
事件
事件触发和监听
php
//组件模版内
@click=$emit('函数名',参数一,参数二,$event) $event事件参数
//js内:
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() { emit('submit') }
tip: 和原生 DOM 事件不一样,组件触发的事件没有冒泡机制
。你只能监听直接子组件触发的事件。平级组件或是跨越多层嵌套的组件间通信,应使用一个外部的事件总线,或是使用一个全局状态管理方案。
事件校验
对事件进行校验,接受的参数就是抛出事件时传入 emit
的内容,返回一个布尔值来表明事件是否合法。
xml
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
将事件作为prop: on-submit="submitFun"
组件v-model
实现v-modal的两种方式
方式一:
xml
<!-- CustomInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
//使用
<CustomInput v-model="searchText" />
方式二:使用一个可写的计算属性
v-modal在组件上默认使用modelValue作为prop,并以 update:modelValue
作为对应的事件;
我们可以指定参数 v-modal:title来更改这些名字,并通过update:title
事件更新title值
xml
<!-- CustomInput.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps(['title'])
const emit = defineEmits(['update:title'])
const value = computed({
get() {
return props.title
},
set(value) {
emit('update:title', value)
}
})
</script>
<template>
<input v-model="value" />
</template>
v-modal多个绑定,每一个v-modal都会同步不同的prop值
v-modal修饰符 例如 .trim .number .lazy 除此之外,我们还可以自定义修饰符
举例:capitalize 它会自动将 v-model
绑定输入的字符串值第一个字母转为大写:
xml
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
//此时 capitalize为true
console.log(props.modelModifiers) // { capitalize: true }
function emitValue(e) {
let value = e.target.value
//处理字符串
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
//更新modelValue的值
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
//使用
<MyComponent v-model.capitalize="myText" />
带参数的 v-model
修饰符
又有参数又有修饰符的 v-model
绑定,生成的 prop 名将是 arg + "Modifiers"
ini
<MyComponent v-model:title.capitalize="myText">
arduino
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }
透传Attributes
透传Attributes 指的是没有被该组件声明为 props 或 emits的attribute 或者 v-on
事件监听器,比如 class、id、style;如果组件是单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
禁用 Attributes 继承
xml
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
透传的attribute可以用 $attrs访问到,同时别忘了设置inheritAttrs: false; 多个根节点的组件需要被显示绑定:
xml
<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">click me</button>
<span>Fallthrough attribute: {{ $attrs }}</span>
</div>
在js中访问
xml
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
插槽
<slot>
元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容,有内容的时候就会替代默认内容
插槽作用域
1、插槽内容可以访问到父组件的作用域 2、访问子组件的作用域需要子组件在渲染时将一部分数据提供给插槽
默认作用域插槽
xml
<!-- <MyComponent> 的模板 -->
<div>
//子组件通过props传递数据
<slot :text="greetingMessage" :count="1"></slot>
</div>
arduino
//父组件通过v-slot接收slotProps
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
//or 解构slotProps
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
具名作用域插槽
v-slot:name="slotProps",缩写是#name
arduino
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
</MyComponent>
xml
<template>
<div class="container">
<slot name="header" title="header"></slot>
<slot title="default"></slot>
</div>
</template>
动态插槽名
xml
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
依赖注入
provide
和 inject
可以解决prop逐级透传的问题

provide提供 格式是: provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
注入名可以是字符串或者Symbol
,第二个参数可以提供任意类型
建议使用 Symbol 作注入名,以避免潜在的冲突
csharp
import { ref, provide } from 'vue'
const count = ref(0)
provide('key', count)
//使用symbol作为注入名
const myInjectionKey = Symbol()
provide(myInjectionKey, { /* 要提供的数据 */ });
注入
xml
<script setup>
import { inject } from 'vue'
const message = inject('message','可以设置一个默认值')
</script>
通过调用一个工厂函数或初始化一个类来取得默认值(避免用不到的时候浪费) ,第三个参数表示默认值应该被当作一个工厂函数。
javascript
const value = inject('key', () => new ExpensiveClass(), true)
和响应式数据配合使用
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
xml
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
xml
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
如果想提供一个不被更改的值,readonly()
来包装提供的值
csharp
<script setup>
import { ref, provide, readonly } from 'vue'
const count = ref(0)
provide('read-only-count', readonly(count))
</script>
异步组件
可以直接在父组件中直接定义它们:
javascript
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
const AsyncComp = defineAsyncComponent(() =>
//ES模块动态导入也会返回一个 Promise
import('./components/MyComponent.vue') )
也可以实现全局注册
javascript
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
异步组件的一些配置
javascript
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
还可以搭配 Suspense 使用,稍后详解