目录

自定义组件,我们一般需要实现这几个点:

为什么使用 script setup ?

刚开始尝试Vue 3的时候用的组合式API都是这样的写法

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
    ...
    return {
        ...
    }
  }
} 

乍一看感觉不如原来的Options API啊,什么逻辑都写到setup里面了,好臃肿的一个方法。

但事实上,Vue 3在对响应式重新设计之后,让我们可以通过refreactive方法来创建声明一个响应式变量,也就意味着我们很多逻辑可以不依赖this.data进行开发和编写,甚至一些响应式逻辑都可以多组件复用。

在了解了这些点之后,即便我们可以将逻辑拆分独立,通过解构的方式导入setup中,让我们代码更加高内聚低耦合,但我们依然避免不了,复杂组件需要return无数的方法或者变量提供给模板使用。

但是,在<script setup> 语法糖出现之后,这个问题得到了极大的改善,不管是import的组件也好,还是声明的变量也罢,都可以不用一个个return了。

编译器会帮助我们转换成setup()函数的内容,这意味着与普通的 <script> 只在组件被首次引入的时候执行一次不同,<script setup> 中的代码会在每次组件实例被创建的时候执行

所以,还不赶紧学起来?

自定义组件

props 与 events

<script setup> 中必须使用 definePropsdefineEmits API 来声明 propsemits ,它们具备完整的类型推断并且在 <script setup> 中是直接可用的:

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup code
</script> 
  • definePropsdefineEmits 都是只在 <script setup> 中才能使用的编译器宏。他们不需要导入且会随着 <script setup> 处理过程一同被编译掉。
  • defineProps 接收与 props 选项相同的值,defineEmits 也接收 emits 选项相同的值。
  • definePropsdefineEmits 在选项传入后,会提供恰当的类型推断。
  • 传入到 definePropsdefineEmits 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它_可以_引用导入的绑定,因为它们也在模块范围内。

以上是官方文档对于定义Props和Emits的相关介绍,笔者觉得说的还是很清楚的,这里在圈一下重点

  • <script setup>不需要导入definePropsdefineEmits
  • 定义props时传入的参数与options APIprops选项一致
  • 在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> 

总结

目前笔者整理的就这么多,我自己在开发组件的过程中常用到的目前也就这些知识点

当然还有函数式组件相关的写法,这个可能大部分人不常会用到,组件库考虑到动态性或许会选择

不过我们做业务组件时我还是建议大家使用单文件组件

维护性还是高了不少的

喜欢的就点赞收藏起来吧~

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐