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
组件控制的。