有很多Vue3.3新增的特性

defineProps

编译宏形式定义prop(常用于TS项目),Vue3.3中对defineProps增添了泛型支持

1
2
3
4
5
6
7
8
9
10
11
12
// Child.vue
<template>
<div>
{{ arr }}
</div>
</template>

<script generic="T" setup lang="ts">
defineProps<{
arr: T[]
}>()
</script>
1
2
3
4
5
6
7
8
9
10
11
// App.vue
<template>
<div>
<Child :arr="['omg']"></Child>
<Child :arr="[114514, 1919, 810]"></Child>
</div>
</template>

<script setup lang="ts">
import Child from './components/Child.vue'
</script>

defineEmits

编译宏形式定义emit,Vue3.3中简化了defineEmits TS字面量模式的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<button @click="send">派发事件</button>
</div>
</template>

<script setup lang="ts">
// 旧写法
// const emit = defineEmits<{
// (event: 'send', name: string): void
// }>()

// 新写法
const emit = defineEmits<{
'send': [name:string]
}>()

const send = ()=> {
emit('send', 'omg')
}
</script>

defineSlots

在定义插槽时为slot实现类型提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Child.vue
<template>
<div>
<ul>
<li v-for="(item, index) in data">
<slot :item="item" :index="index"></slot>
</li>
</ul>
</div>
</template>

<script generic="T" setup lang="ts">
defineProps<{
data: T[]
}>()

defineSlots<{
default(props: {item: T, index: number}): void
}>()
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// App.vue
<template>
<div>
<Child :data="list">
<template #default="{item, index}">
{{ item.name }} {{ item.age }} {{ index }}
</template>
</Child>
</div>
</template>

<script setup lang="ts">
import Child from './components/Child.vue'
const list = [
{
name: 'yjsp',
age: 24
},
{
name: 'omg',
age: 33
}
]
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 源码 compiler-sfc/src/script/defineSlots.ts
import { LVal, Node } from '@babel/types'
import { isCallOf } from './utils'
import { ScriptCompileContext } from './context'

export const DEFINE_SLOTS = 'defineSlots'

export function processDefineSlots(
ctx: ScriptCompileContext,
node: Node,
declId?: LVal
): boolean {
// 是否调用了defineSlots
if (!isCallOf(node, DEFINE_SLOTS)) {
return false
}
// 是否重复调用defineSlots
if (ctx.hasDefineSlotsCall) {
ctx.error(`duplicate ${DEFINE_SLOTS}() call`, node)
}
// 一个flag,表示有调用defineSlots
ctx.hasDefineSlotsCall = true

// 参数个数检查(不能传参())
if (node.arguments.length > 0) {
ctx.error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
}

// 如果运行正常,会有一个编译id,表示插槽定义的标识符的节点对象
if (declId) {
// 这个方法会替换内容,将该节点对象替换为一个使用插槽的帮助函数的调用
ctx.s.overwrite(
ctx.startOffset! + node.start!, // 开始位置
ctx.startOffset! + node.end!, // 结束位置
`${ctx.helper('useSlots')}()` // 替换的内容,此时就拥有了类型检查
)
}

return true
}

defineOptions

Vue3.3中内置defineOptions,无需下载插件

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
</div>
</template>

<script setup lang="ts">
// props、emits等编译宏不能在此处定义
defineOptions({
name: 'Child',
inheritAttrs: false
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 源码 compiler-sfc/src/script/defineOptions.ts
import { Node } from '@babel/types'
import { ScriptCompileContext } from './context'
import { isCallOf, unwrapTSNode } from './utils'
import { DEFINE_PROPS } from './defineProps'
import { DEFINE_EMITS } from './defineEmits'
import { DEFINE_EXPOSE } from './defineExpose'
import { DEFINE_SLOTS } from './defineSlots'

export const DEFINE_OPTIONS = 'defineOptions'

export function processDefineOptions(
ctx: ScriptCompileContext,
node: Node
): boolean {
// 是否调用了defineOptions
if (!isCallOf(node, DEFINE_OPTIONS)) {
return false
}
// 是否重复调用defineOptions
if (ctx.hasDefineOptionsCall) {
ctx.error(`duplicate ${DEFINE_OPTIONS}() call`, node)
}
// 泛型字面量检查(不能传递泛型字面量<>)
if (node.typeParameters) {
ctx.error(`${DEFINE_OPTIONS}() cannot accept type arguments`, node)
}
// 参数检查,一个参数
if (!node.arguments[0]) return true

// 一个flag,表示有调用defineOptions
ctx.hasDefineOptionsCall = true
// 将ctx上下文的optionsRuntimeDecl属性设为defineOptions的参数
ctx.optionsRuntimeDecl = unwrapTSNode(node.arguments[0])

let propsOption = undefined
let emitsOption = undefined
let exposeOption = undefined
let slotsOption = undefined
// 遍历属性,查找有无props、emits、expose和slots
if (ctx.optionsRuntimeDecl.type === 'ObjectExpression') {
for (const prop of ctx.optionsRuntimeDecl.properties) {
if (
(prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
prop.key.type === 'Identifier'
) {
if (prop.key.name === 'props') propsOption = prop
if (prop.key.name === 'emits') emitsOption = prop
if (prop.key.name === 'expose') exposeOption = prop
if (prop.key.name === 'slots') slotsOption = prop
}
}
}

// 如果有则报错
if (propsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare props. Use ${DEFINE_PROPS}() instead.`,
propsOption
)
}
if (emitsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare emits. Use ${DEFINE_EMITS}() instead.`,
emitsOption
)
}
if (exposeOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare expose. Use ${DEFINE_EXPOSE}() instead.`,
exposeOption
)
}
if (slotsOption) {
ctx.error(
`${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
slotsOption
)
}

return true
}