script setup 语法糖
使用方式极其简单,仅需要在 script 标签加上 setup 关键字即可
<script setup></script>
组件
组件导入即可,无需在注册
<template>
<Child />
</template>
<script setup>
import Child from './Child.vue'
</script>
2
3
4
5
6
7
props
通过defineProps指定当前 props 类型,获得上下文的props对象
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
title: String,
})
</script>
2
3
4
5
6
7
emits
使用defineEmit定义当前组件含有的事件,并通过返回的上下文去执行 emit。
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['change', 'delete'])
const handleChange = () => {
emits('change', { id: id })
}
const handleDel = () => {
emits('delete', { id: id })
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
获取 slots 和 attrs
<script setup>
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
</script>
2
3
4
5
6
defineExpose API
传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问子组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template模板,谁都不能访问内部的数据和方法。 如果需要对外暴露 setup 中的数据和方法,需要使用 defineExpose API。
<script setup>
import { defineExpose } from 'vue'
const a = 1
const b = 2
defineExpose({
a,
})
</script>
2
3
4
5
6
7
8
属性和方法的使用
这可能是带来的较大便利之一,在vue3.0正常的写法中,定义数据和方法,都需要在结尾 return 出去,才能在模板中使用。在 script setup语法糖中,定义的属性和方法无需返回,可以直接使用即可。
<template>
<div>
<p>My name is {{ name }},I am {{ state.year }} old</p>
</div>
<button @click="change"></button>
</template>
<script setup>
import { reactive, ref } from 'vue'
const name = ref('Sam')
const state = reactive({ year: 26 })
const change = () => {
name.value = 'Li'
state.year = 18
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
自定义 hook
自定义hook(钩子函数)钩子函数的作用类似于vue2中的mixin,是一个可复用的功能函数,相对于mixin自定义hook的优势可以很清楚的知道复用功能代码的来源,简洁易懂。
例子:自定义一个获取鼠标点击的x,y轴的方法的hook
新建一个useMouseClick.ts文件
import { onBeforeUnmount, onMounted, ref } from 'vue'
export function useMouseClick() {
// 初始化x,y轴坐标
let x = ref(0)
let y = ref(0)
// 鼠标点击后的方法
let mouseClick = (event: MouseEvent) => {
x.value = event.pageX
y.value = event.pageY
}
// 页面初始化的时候监听点击事件
onMounted(() => {
window.addEventListener('click', mouseClick)
})
// 页面销毁的时候移除监听点击事件
onBeforeUnmount(() => {
window.removeEventListener('click', mouseClick)
})
// 导出坐标
return {
x,
y,
}
}
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
最后在需要的页面引入这个公共方法
<template>
<div>
<p>x: {{ x }}</p>
<p>y: {{ y }}</p>
</div>
</template>
<script lang="ts" setup>
import { useMouseClick } from '@/hooks/useMouseClick'
const { x, y } = useMouseClick()
</script>
2
3
4
5
6
7
8
9
10
11
12
ref 语法糖
由于 ref 语法糖目前还处于实验性的(Experimental)阶段,所以在 Vue3 中不会默认支持 ref 语法糖。
在使用 Vite + Vue3 项目开发时,是由 @vitejs/plugin-vue 插件来实现对 .vue 文件的代码转换(Transform)、热更新(HMR)等。所以,我们需要在 vite.config.js 中给@vitejs/plugin-vue 插件的选项(Options)传入 refTransform: true:
这样一来 @vitejs/plugin-vue 插件内部会根据传入的选项中 refTransform 的值判断是否需要对 ref 语法糖进行特定的代码转换。由于,这里我们设置的是 true,显然它是会对 ref 语法糖执行特定的代码转换。
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue({
refTransform: true,
}),
],
})
2
3
4
5
6
7
8
9
10
11
接着,我们就可以在 .vue 文件中使用 ref 语法糖
<template>
<div>{{ count }}</div>
<button @click="add">click me</button>
</template>
<script setup>
let count = $ref(1)
function add() {
count++
//使用的时候无需加 .value
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
获取子组件 ref 变量和 defineExpose 暴露
子组件代码如下(示例):
<template>
<p>{{ data }}</p>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
/**
* 数据部分
* */
const data = reactive({
modelVisible: false,
historyVisible: false,
reportVisible: false,
})
defineExpose({
...toRefs(data),
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
父组件代码如下(示例):
<template>
<button @click="onClickSetUp">点击</button>
<Content ref="content" />
</template>
<script setup>
import { ref } from 'vue'
// content组件ref
const content = ref('content')
// 点击设置
const onClickSetUp = ({ key }) => {
content.value.modelVisible = true
}
</script>
<style scoped lang="less"></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Teleport
Teleport 形象的比喻是哆啦 A 梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。 例子: 在子组件中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。 Dialog从用户感知的层面,应该是一个独立的组件,从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM;同时还可以使用到 Vue 组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。 此时就需要 Teleport 上场,我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。
Teleport 的使用
我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
2
3
4
定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:
<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{ title }}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
最后在一个子组件Child.vue中使用Dialog组件。
<div class="child">
<Dialog v-if="dialogVisible"></Dialog>
</div>
2
3
使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制的。