手把手教你Vue3自定义组件(setup语法糖版)

Vue3 setup Cmp.png

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在对响应式重新设计之后,让我们可以通过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>

总结

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

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

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

维护性还是高了不少的

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容