Vue 的组件系统允许我们使用小型、独立和可复用的组件构件大型应用。Vue3 引入了<script setup>语法糖,使得组件的options(如data,methods等)会自动从setup函数的返回中导出,无须通过return暴露它们,提高了开发效率。
1 组件进阶
1.1 Props
1)使用defineProps()宏来声明Props。
2)可以声明对props 的校验(required或自定义校验器)。
3)type选项用于指定类型,可以是原生构造函数,例如String、Number、Boolean等,也可以是自定义的类或构造函数。
<script setup lang="ts">
import {defineProps} from "vue";
const { name } = defineProps({
name: {
type: String,
required: true,
validator(value:String): boolean {
return 'hello' === value
}
}
})
</script>
1.2 事件
1)使用defineEmits()宏来声明要触发的事件。
2)事件可以使用对象形式来描述,可以为事件添加一个校验函数,接受的参数是抛出事件时传入的内容,返回一个布尔值来表名事件是否合法。
<script setup lang="ts">
import {defineEmits} from "vue";
const emits = defineEmits({
customTrigger(name: string,num: number) {
return name.length > 0 && num > 1
},
customTrigger2(args: {name: string, num: number}) {
return args.name.length > 0 && args.num > 1
}
})
</script>
<template>
<button @click="emits('customTrigger','hmf',2)">触发事件1</button>
<button @click="emits('customTrigger2',{name: '2', num: 2})">触发事件2</button>
</template>
1.3 组件v-model
v-model可以在组件上使用以实现双向绑定。从Vue3.4开始,推荐实现方式是使用defineModel()宏。传入选项用于指定默认值、是否必须等。其底层机制为:
1)在子组件,声明一个名为modelValue的prop。
2)当需要在组件修改该值时,触发名为update:modelValue的事件。
3)父组件中处理update:modelValue的事件,来更新ref的值。
<script setup lang="ts">
import {defineEmits, defineModel, defineProps} from "vue";
const name = defineModel({default: 'hello'})
const emits = defineEmits(['customModelValue:update'])
const props = defineProps(["customModelValue"])
</script>
<template>
<div><input v-model="name"/></div>
<div><input :value="props.customModelValue" @input="emits('customModelValue:update',$event.target.value)"/></div>
</template>
4) 可以声明多个v-model绑定。
5) 可以使用修饰符,在子组件定义v-model时,传入选项用于设置set函数,来设置model的最终值。
<script setup lang="ts">
import {defineModel} from "vue";
const [name, nameModifiers] = defineModel('name',{
set(val:string) {
if(nameModifiers.trim) {
val = val.trim()
}
return val
}
})
</script>
<template>
<input v-model="name"/>
</template>
1.4 透传Attributes
“透传attributes”指传递给一个组件,却没有被该组件声明为props 或 emits的attribute或者v-on事件监听器。
1)当一个组件以单个元素为根作渲染时,透传的attribute会自动添加到根元素上。
2)当以多个元素为根元素时,可以使用$attrs 为元素指定要继承的属性。否则将会警告。
3)如果不想要一个组件自动继承attribute,可以在组件选项中设置 inheritAttrs: false。
4)在<script setup>中使用useAttrs()来访问一个组件所有透传的attributes.
<a-attributes-view @click="clickHandle" style="color: red;font-weight: bold"/>
<script setup lang="ts">
import {defineOptions, useAttrs} from "vue";
defineOptions({
inheritAttrs: false
})
const attrs = useAttrs();
function clickHandle() {
console.log("children click",attrs)
}
</script>
<template>
<div @click="clickHandle">h1</div>
<div v-bind="$attrs">h2</div>
</template>
1.5 插槽Slots
1)<slot>元素的name属性用于给各个插槽分配唯一的ID,以确定每一处要渲染的内容。
2)在子组件中,使用$slots来访问插槽的内容。
3)向对组件传递props那样,向<slot>元素传递attributes来向插槽内容传递数据。
<template>
<div style="border: solid 1px red">
<slot :info="'hello js'">默认</slot>
</div>
<div style="margin-top: 20px; border: solid 1px blue" v-if="$slots.default">
<slot name="footer" :count="1234"/>
</div>
</template>
<template>
<a-slot-view>
<template #default="{ info }">
<div>hello default</div>
<div>还是 default1 {{info}}</div>
</template>
<template #footer="{count}">
<div>footer {{ count }}</div>
</template>
</a-slot-view>
</template>
1.6 依赖注入
1) 要为组件后代提供数据,需要使用到provide()函数。可提供响应式值,这样在后代组件修改值,父组件的值也会发送改变。
2)要注入上层组件提供的数据,需要使用inject()函数。可以指定默认值。
3)如果要在后代中修改注入的值,不建议在后代中直接修改,父组件可以在注入数据时,提供一个修改数据的方法给后代。
<script setup lang="ts">
import {provide, ref} from "vue";
import AInjectView from "@/article/components/AInjectView.vue";
const count = ref(0)
const num = ref(0)
function addNum() {
num.value++
}
provide("count",count)
provide("num",{ num,addNum })
</script>
<template>
<div>count: {{ count }}</div>
<div>num: {{ num }}</div>
<a-inject-view />
</template>
<script setup lang="ts">
import {inject} from "vue";
const count = inject("count",-1)
const { num, addNum} = inject("num")
</script>
<template>
<button @click="count++">修改count{{ count }}</button>
<div @click="addNum">修改num {{ num }}</div>
</template>
1.7 异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。defineAsyncComponent可以实现此功能。
1)改函数支持传入一个选项,用于指定加载函数、加载异步组件时使用的组件、失败后展示的组件、超时时间等。
const SubjectView = defineAsyncComponent({
loader: () => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(import('./components/ASubjectView.vue'))
},3000)
}),
loadingComponent: ALoadingView,
errorComponent: AErrorView,
timeout: 2000
})