前言
提示:Vue3.2 版本开始才能使用语法糖!
<script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。在 Vue3.2 中只需要在 script 标签上加上setup
属性,无需 return,template 便可直接使用。相比于普通的
- 更少的样板内容,更简洁的代码。
- 能够使用纯
TypeScript
声明props
和抛出事件。 - 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
一、基本语法
要使用这个语法,需要将setup attribute
添加到 <script>
代码块上:
<script setup>
console.log('hello script setup')
</script>
里面的代码会被编译成组件setup()
函数的内容。这意味着与普通的<script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。
二、设置name
新增加一个script
标签,在这个标签中写入name
属性,代码如下:
<template>
<button>demo</button>
</template>
<script>
export default {
name: "VButton",
};
</script>
<script setup>
</script>
<style scoped lang="less">
</style>
三、data的设置和使用
<template>
<div class="home">
<button>+1</button>
{{ num }}
</div>
</template>
<script setup>
import { ref } from 'vue';
const num = ref(0);
</script>
四、method使用
<template>
<div class="home">
<button @click="btnClick">+1</button>
{{ num }}
</div>
</template>
<script setup>
import { ref } from 'vue';
const num = ref(0);
const btnClick = () => {
num.value = num.value + 1
}
</script>
五、store仓库的使用
<script setup>
import { useStore } from '@/store';
const store = useStore();
store.getters.userInfo
</script>
六、computed计算属性的使用
接受一个 getter 函数,并根据getter
的返回值返回一个不可变的响应式 ref 对象。
<script setup>
import { computed, reactive } from 'vue';
import { useStore } from '@/store';
const store = useStore();
const userInfo = computed(() => store.getters.userInfo);
let person = reactive({
firstName:'小',
lastName:'叮当'
})
const fullName = computed(() => {
return person.firstName + '-' + person.lastName
})
</script>
七、路由userRoute和userRouter使用
<script setup>
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
route.query // 获取路由query
route.params // 获取路由params
router.push({ path: '/login', query: {} });
</script>
八、使用组件以及动态组件
<template>
<!-- PascalCase,建议使用,有助于区分原生的自定义元素 -->
<MyComponent />
<!-- kebab-case -->
<my-component />
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
<FooBarChild />
<Form.Input>
<Form.Label>label</Form.Label>
</Form.Input>
</template>
<script setup>
import MyComponent from './MyComponent.vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
// 有命名的 import 导入和组件的推断名冲突了,可以使用 import 别名导入
import { FooBar as FooBarChild } from './components'
// 从单个文件中导入多个组件
import * as Form from './form-components'
</script>
九、使用指令
<template>
<h1 v-my-directive>This is a Heading</h1>
<h1 py-directive>This is a Heading</h1>
</template>
<script setup>
// 内部定义
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
// 外部导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范
import { myDirective as pyDirective } from './MyDirective.js'
</script>
十、emit和defineEmits的使用
<script setup>
const emit = defineEmits(['change', 'delete'])
// setup code
emits('change', 'cancel');
emits('delete');
</script>
十一、props和defineProps的使用
<script setup>
const props = defineProps({
foo: String,
});
console.log(props.foo);
</script>
十二、watch的使用
<script setup>
import { ref, watch, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
const num = ref(0);
// 监听一个数据源
watch(num, (num, prevNum) => {
/* ... */
})
const fooRef = ref(1);
const barRef = ref(2);
// 监听多个数据源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
console.log('fooRef或barRef变了')
})
// 监听路由参数
const route = useRoute();
watch(
() => route.fullPath,
() => {
// code
}
);
</script>
十三、watchEffect的使用
立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
<script setup>
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
</script>
十四、defineExpose的使用
使用<script setup>
的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在<script setup>
中声明的绑定。
为了在<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 会和在普通实例中一样被自动解包)
父组件代码如下:
<template>
<button @click="btnClick">点击</button>
<Content ref="content" />
</template>
<script setup>
import { ref } from 'vue'
// content组件ref
const content = ref('content')
// 点击设置
const btnClick = () => {
console.log(content.value.b)
}
</script>
十五、provide 和 inject 的使用
不限层级
<script setup>
import { ref, provide } from 'vue'
let name = ref('张三')
// 使用provide
provide('provideState', {
name,
changeName: () => {
name.value = '李四'
}
})
</script>
子组件或子孙组件
<script setup>
import { inject } from 'vue'
const changeState = inject('changeState')
changeState.changeName()
</script>
十六、useSlots 和 useAttrs的使用
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
十七、await的使用
<script setup>
const handleLogin = async (phone: string, code: string) => {
try {
const { token } = await requestAuthLogin({
phone,
code,
});
console.log(token);
} catch (e) {
console.log(e);
} finally {
}
};
</script>
十八、style特性
单文件组件的<style>
标签可以通过 v-bind
这一 CSS 函数将 CSS 的值关联到动态的组件状态上:
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
实际的值会被编译成 hash 的 CSS 自定义 property,CSS 本身仍然是静态的。自定义 property 会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式更新。
十九、使用vue实例
<script setup>
import { getCurrentInstance } from 'vue';
// 获取当前组件实例
const instance = getCurrentInstance();
// 获取当前组件的上下文,下面两种方式都能获取到组件的上下文。
// 方式一,这种方式只能在开发环境下使用,生产环境下的ctx将访问不到
const { ctx } = getCurrentInstance();
// 方式二,此方法在开发环境以及生产环境下都能放到组件上下文对象(推荐)
const { proxy } = getCurrentInstance();
// ctx 中包含了组件中由ref和reactive创建的响应式数据对象,以及以下对象及方法;
proxy.$attrs
proxy.$data
proxy.$el
proxy.$emit
proxy.$forceUpdate
proxy.$nextTick
proxy.$options
proxy.$parent
proxy.$props
proxy.$refs
proxy.$root
proxy.$slots
proxy.$watch
</script>
二十、仅限 TypeScript 的功能
设置类型声明时的默认 props 值
<script setup>
interface IProps {
foo: string
bar?: number
}
const props = defineProps<IProps>()
const emit = defineEmits<{
(event: 'change', id: number): void;
(event: 'update', value: string): void;
(event: 'close'): void;
}>()
</script>
仅限类型的 defineProps 声明的不足之处在于,它没有可以给 props 提供默认值的方式。为了解决这个问题,提供了withDefaults
编译器宏:
<script setup>
interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
</script>
注意事项
- setup执行的时机:在beforeCreate之前执行一次,this是undefined。
- 由于模块执行语义的差异,
<script setup>
中的代码依赖单文件组件的上下文。当将其移动到外部的 .js 或者 .ts 文件中的时候,对于开发者和工具来说都会感到混乱。因而<script setup>
不能和src attribute
一起使用。