Vue 3.0 在今年2月7日已经正式转正,经过这两年的尝鲜和测试,已经比较稳定,个人建议在支持现代浏览器的项目中都可以使用Vue 3.0+来进行开发,原生支持TypeScript这点是真的香,Vue 3的好处还是很多的,好了话不多说,这次我们就来聊一聊 <script setup>
语法糖里,究竟该如何自定义组件?
目录
自定义组件,我们一般需要实现这几个点:
- props —— 定义属性
- events —— 定义事件
- slots —— 插槽
- expose —— 定义组件可供外部访问的内容
- v-model —— 自定义组件实现双向数据绑定
- provide 与 inject
为什么使用 script setup ?
刚开始尝试Vue 3的时候用的组合式API都是这样的写法
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
...
return {
...
}
}
}
乍一看感觉不如原来的Options API
啊,什么逻辑都写到setup里面了,好臃肿的一个方法。
但事实上,Vue 3在对响应式重新设计之后,让我们可以通过ref
和reactive
方法来创建声明一个响应式变量,也就意味着我们很多逻辑可以不依赖this.data进行开发和编写,甚至一些响应式逻辑都可以多组件复用。
在了解了这些点之后,即便我们可以将逻辑拆分独立,通过解构的方式导入setup中,让我们代码更加高内聚低耦合,但我们依然避免不了,复杂组件需要return无数的方法或者变量提供给模板使用。
但是,在<script setup>
语法糖出现之后,这个问题得到了极大的改善,不管是import的组件也好,还是声明的变量也罢,都可以不用一个个return了。
编译器会帮助我们转换成setup()
函数的内容,这意味着与普通的 <script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。
所以,还不赶紧学起来?
自定义组件
props 与 events
在 <script setup>
中必须使用 defineProps
和 defineEmits
API 来声明 props
和 emits
,它们具备完整的类型推断并且在 <script setup>
中是直接可用的:
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
// setup code
</script>
-
defineProps
和defineEmits
都是只在<script setup>
中才能使用的编译器宏。他们不需要导入且会随着<script setup>
处理过程一同被编译掉。 -
defineProps
接收与props
选项相同的值,defineEmits
也接收emits
选项相同的值。 -
defineProps
和defineEmits
在选项传入后,会提供恰当的类型推断。 - 传入到
defineProps
和defineEmits
的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它可以引用导入的绑定,因为它们也在模块范围内。
以上是官方文档对于定义Props和Emits的相关介绍,笔者觉得说的还是很清楚的,这里在圈一下重点:
- 在
<script setup>
中不需要导入defineProps
和defineEmits
- 定义
props
时传入的参数与options API
中props
选项一致 - 在TS中可以直接纯类型声明
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>();
这里肯定很多小伙伴有疑问了,那如果用TS做纯类型的声明,默认值该怎么定义呢?
呐,看这里!
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
还有一个withDefaults
编译器宏
上面代码会被编译为等价的运行时 props 的 default
选项。此外,withDefaults
辅助函数提供了对默认值的类型检查,并确保返回的 props
的类型删除了已声明默认值的属性的可选标志。
Slots
大部分情况,我们可能需要根据外部的slots的传入情况来决定组件内部的展示部分,在模板中我们可以通过$slots
来访问所有的默认插槽以及具名插槽
比如:Auth组件校验不通过时,隐藏slots的内容
那么我们可以在模板中这样来做
// page
<Auth auth="commit">
<button>提交<button>
</Auth>
// components
<template>
<slot v-if="condition" />
</template>
这样既不会增加dom节点也可以增加逻辑来处理按钮权限的问题
再比如:组件内部有多个插槽及具名插槽的时候
form表单中的form-item
组件是可以自定义插槽来覆盖默认的input内容的,在模板中就可以通过$slots来访问具体的插槽对象
// page
<form-item>
<template #input>
自定义form input内容
</template>
</form-item>
// components
<template>
<slot v-if="$slots.input" name="input" />
<input v-else />
</template>
我们很少情况会在setup中操作Slots,但是它依然提供了useSlots
方法来帮我们操作组件的Slots
<script setup>
import { useSlots } from 'vue'
const slots = useSlots()
</script>
Expose
使用 <script setup>
的组件是默认关闭的,也即通过模板 ref 或者 $parent
链获取到的组件的公开实例,不会暴露任何在 <script setup>
中声明的绑定。
我们组件内部的状态和方法可能会很多,比如一些复杂的组件,但是有些状态外部或许需要在适当时候操作或访问的时候,我们就需要考虑那些属性和方法是可以暴露给外部的
这个时候我们就可以使用defineExpose
来声明绑定
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
当父组件通过模板 ref 的方式获取到当前组件的实例,获取到的实例会像这样 { a: number, b: number }
(ref 会和在普通实例中一样被自动解包)
v-model
v-model其实是一个语法糖
它代表声明了一个modelValue
的属性以及一个update:modelValue
的事件
Vue 3 中你可以通过 propsName + update:propsName
来自定义v-model
也就是说:一个组件里可以定义多个v-model
// page
<cmp v-model:foo="xxx" v-model:bar="xxxx" />
// components
<script setup>
interface Props {
foo: string
bar: string
}
const props = defineProps<Props>();
const emits = defineEmits(["update:foo", "update:bar"]);
</script>
provide 与 inject
这里需要用到 provide() 与 inject()
父组件:
<script setup>
import { provide } from "vue";
const userObj = ref<User>(...);
provide("user", userObj);
const fn = () => {
...
}
provide("change", fn);
</script>
子组件:
<script setup>
import { inject } from "vue";
const injectUserObj = inject("user");
const injectFn = inject("change");
</script>
总结
目前笔者整理的就这么多,我自己在开发组件的过程中常用到的目前也就这些知识点
当然还有函数式组件相关的写法,这个可能大部分人不常会用到,组件库考虑到动态性或许会选择
不过我们做业务组件时我还是建议大家使用单文件组件
维护性还是高了不少的
喜欢的就点赞收藏起来吧~