本文主要是讲解 与 TypeScript 的基本使用。
是什么?
是在单文件组件 (SFC) 中使用 composition api 的编译时语法糖。
本文写作时,vue 使用的 3.2.26 版本。
我们先看看 vue3 的发展历程:
Vue3 在早期版本( 3.0.0-beta.21 之前)中对 composition api 的支持,只能在组件选项 setup 函数中使用。-
- <h1>{{ msg }}h1>
-
- <button type="button" @click="add">count is: {{ count }}button>
-
- <ComponentA />
-
- <ComponentB />
-
-
- <script>
-
- import { defineComponent, ref } from 'vue'
-
- import ComponentA from '@/components/ComponentA'
-
- import ComponentB from '@/components/ComponentB'
-
-
-
- export default defineComponent({
-
- name: 'HelloWorld',
-
- components: { ComponentA, ComponentB },
-
- props: {
-
- msg: String,
-
- },
-
- setup(props, ctx) {
-
- const count = ref(0)
-
-
-
- function add() {
-
- count.value++
-
- }
-
- // 使用return {} 把变量、方法暴露给模板
-
- return {
-
- count,
-
- add,
-
- }
-
- },
-
- })
-
- script>
在 3.0.0-beta.21 版本中增加了 的实验特性。如果你使用了,会提示你 还处在实验特性阶段。
在 3.2.0 版本中移除 的实验状态,从此,宣告 正式转正使用,成为框架稳定的特性之一。
- x
-
- <h1>{{ msg }}h1>
-
- <button type="button" @click="add">count is: {{ count }}button>
-
- <ComponentA />
-
- <ComponentB />
-
与组件选项 setup 函数对比, 的优点:
return {} 暴露变量和方法了,使用组件时不需要主动注册了;Typescript 支持,使用纯 Typescript 声明 props 和抛出事件,不会再像 option api 里那么蹩脚了;当然, 也是有自己的缺点的,比如需要学习额外的 API。
那么 怎么使用呢?有哪些使用要点?与TypeScript如何结合?
Vue3 单文件组件 (SFC) 的 TS IDE 支持请用
若需要使用 TypeScript,则将 lang 属性添加到 代码块上,并赋值 ts。
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <button type="button" @click="add">count is: {{ count }}button>
-
- template>
块中的脚本会被编译成组件选项 setup 函数的内容,也就是说它会在每次组件实例被创建的时候执行。
在 声明的顶层绑定(变量、函数、import引入的内容),都会自动暴露给模板,在模板中直接使用。
-
- import { ref } from 'vue'
-
- // 外部引入的方法,不需要通过 methods 选项来暴露它,模板可以直接使用
-
- import { getToken } from './utils'
-
- // 外部引入的组件,不需要通过 components 选项来暴露它,模板可以直接使用
-
- import ComponentA from '@/components/ComponentA'
-
-
-
- defineProps({
-
- msg: String
-
- })
-
- // 变量声明,模板可以直接使用
-
- const count = ref(0)
-
- // 函数声明,模板可以直接使用
-
- function add() {
-
- count.value++
-
- }
-
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <h1>{{ getToken() }}h1>
-
- <button type="button" @click="add">count is: {{ count }}button>
-
- <ComponentA />
-
- template>
注意:
每个 *.vue 文件最多可同时包含一个 块 (不包括);
每个 *.vue 文件最多可同时包含一个 块 (不包括常规的 );
编译器宏(compiler macros) 有:defineProps、defineEmits、withDefaults、defineExpose 等。
编译器宏只能在 块中使用,不需要被导入,并且会在处理 块时被一同编译掉。
编译器宏必须在 的顶层使用,不可以在 的局部变量中引用。
在 块中是没有组件配置项的,也就是说是没有 props 选项,需要使用 defineProps 来声明 props 相关信息。defineProps 接收的对象和组件选项 props 的值一样。
-
- const props = defineProps({
-
- msg: String,
-
- title: {
-
- type: String,
-
- default: '我是标题'
-
- },
-
- list: {
-
- type: Array,
-
- default: () => []
-
- }
-
- })
-
-
-
- // 在 js 中使用 props 中的属性
-
- console.log(props.msg)
-
-
-
-
- <template>
-
-
- <h1>{{ msg }}h1>
-
- <div>{{ title }}div>
-
- template>
TS 版本:
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <div>{{ title }}div>
-
- template>
从代码中可以发现 TS 写法里 props 没有定义默认值。
Vue3 为我们提供了 withDefaults 这个编译器宏,给 props 提供默认值。
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <div>{{ title }}div>
-
- template>
一个需要注意的地方:在顶层声明一个和props的属性同名的变量,会有些问题。
- <script setup>
-
- const props = defineProps({
-
- title: {
-
- type: String,
-
- default: '我是标题'
-
- }
-
- })
-
- // 在顶层声明一个和props的属性title同名的变量
-
- const title = '123'
-
-
-
- script>
-
-
-
- <template>
-
-
- <div>{{ props.title }}div>
-
-
- <div>{{ title }}div>
-
- template>
所以,和组件选项一样,不要定义和 props 的属性同名的顶层变量。
一样的,在 块中也是没有组件配置项 emits 的,需要使用 defineEmits 编译器宏声明 emits 相关信息。
- // ./components/HelloWorld.vue
-
-
- defineProps({
-
- msg: String,
-
- })
-
-
-
- const emits = defineEmits(['changeMsg'])
-
-
-
- const handleChangeMsg = () => {
-
- emits('changeMsg', 'Hello TS')
-
- }
-
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <button @click="handleChangeMsg">handleChangeMsgbutton>
-
- template>
使用组件:
-
- import { ref } from 'vue'
-
- import HelloWorld from './components/HelloWorld.vue'
-
-
-
- const msg = ref('Hello Vue3')
-
- const changeMsg = (v) => {
-
- msg.value = v
-
- }
-
-
-
-
- <template>
-
- <HelloWorld :msg="msg" @changeMsg="changeMsg" />
-
- template>
TS 版本:
- // ./components/HelloWorld.vue
-
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- <button @click="handleChangeMsg">handleChangeMsgbutton>
-
- template>
使用组件:
-
-
-
- <template>
-
- <HelloWorld :msg="msg" @changeMsg="changeMsg" />
-
- template>
在 Vue3中,默认不会暴露任何在 中声明的绑定,即不能通过模板 ref 获取到组件实例声明的绑定。
Vue3 提供了 defineExpose 编译器宏,可以显式地暴露需要暴露的组件中声明的变量和方法。
- // ./components/HelloWorld.vue
-
-
- import { ref } from 'vue'
-
- const msg = ref('Hello Vue3')
-
-
-
- const handleChangeMsg = (v) => {
-
- msg.value = v
-
- }
-
- // 对外暴露的属性
-
- defineExpose({
-
- msg,
-
- handleChangeMsg,
-
- })
-
使用组件:
-
- import { ref, onMounted } from 'vue'
-
- import HelloWorld from './components/HelloWorld.vue'
-
-
-
- const root = ref(null)
-
-
-
- onMounted(() => {
-
- console.log(root.value.msg)
-
- })
-
-
-
- const handleChangeMsg = () => {
-
- root.value.handleChangeMsg('Hello TS')
-
- }
-
-
-
-
- <template>
-
- <HelloWorld ref="root" />
-
- <button @click="handleChangeMsg">handleChangeMsgbutton>
-
- template>
TS 版本:
- // ./components/HelloWorld.vue
-
-
-
-
- <template>
-
- <h1>{{ msg }}h1>
-
- template>
使用组件:
-
-
-
- <template>
-
- <HelloWorld ref="root" />
-
- <button @click="handleChangeMsg">handleChangeMsgbutton>
-
- template>
在 中常用的辅助函数hooks api,主要有:useAttrs、useSlots、useCssModule,其他的辅助函数还在实验阶段,不做介绍。
在模板中使用 $attrs 来访问 attrs 数据,与 Vue2 相比,Vue3 的 $attrs 还包含了 class 和 style 属性。
在 中使用 useAttrs 函数获取 attrs 数据。
-
- import HelloWorld from './components/HelloWorld.vue'
-
-
-
-
- <template>
-
- <HelloWorld class="hello-word" title="我是标题" />
-
- template>
- // ./components/HelloWorld.vue
-
-
- import { useAttrs } from 'vue'
-
-
-
- const attrs = useAttrs()
-
- // js中使用
-
- console.log(attrs.class) // hello-word
-
- console.log(attrs.title) // 我是标题
-
-
-
-
- <template>
-
-
- <div>{{ $attrs.title }}div>
-
- template>
在模板中使用 $slots 来访问 slots 数据。
在 中使用 useSlots 函数获取 slots 插槽数据。
-
- import HelloWorld from './components/HelloWorld.vue'
-
-
-
-
- <template>
-
- <HelloWorld>
-
- <div>默认插槽div>
-
- <template v-slot:footer>
-
- <div>具名插槽footerdiv>
-
- template>
-
- HelloWorld>
-
- template>
-
- import { useSlots } from 'vue'
-
-
-
- const slots = useSlots()
-
- // 在js中访问插槽默认插槽default、具名插槽footer
-
- console.log(slots.default)
-
- console.log(slots.footer)
-
-
-
-
- <template>
-
- <div>
-
-
- <slot>slot>
-
- <slot name="footer">slot>
-
- div>
-
- template>
在 Vue3 中,也是支持 CSS Modules 的,在 上增加 module 属性,即。
代码块会被编译为 CSS Modules 并且将生成的 CSS 类作为 $style 对象的键暴露给组件,可以直接在模板中使用 $style。而对于如 具名 CSS Modules,编译后生成的 CSS 类作为 content 对象的键暴露给组件,即module 属性值什么,就暴露什么对象。
-
-
-
- <template>
-
- <div class="success">普通style reddiv>
-
-
-
- <div :class="$style.success">默认CssModule pinkdiv>
-
- <div :class="style.success">默认CssModule pinkdiv>
-
-
-
- <div :class="contentStyle.success">具名CssModule bluediv>
-
- <div :class="content.success">具名CssModule bluediv>
-
- template>
-
-
-
-
- <style>
-
- .success {
-
- color: red;
-
- }
-
- style>
-
-
-
-
- <style module lang="less">
-
- .success {
-
- color: pink;
-
- }
-
- style>
-
-
-
-
- <style module="content" lang="less">
-
- .success {
-
- color: blue;
-
- }
-
- style>
注意,同名的CSS Module,后面的会覆盖前面的。
在组件选项中,模板需要使用组件(除了全局组件),需要在 components 选项中注册。
而在 中组件不需要再注册,模板可以直接使用,其实就是相当于一个顶层变量。
建议使用大驼峰(PascalCase)命名组件和使用组件。
-
- import HelloWorld from './HelloWorld.vue'
-
-
-
-
- <template>
-
- <HelloWorld />
-
- template>
是没有组件配置项 name 的,可以再使用一个普通的 来配置 name。
- // ./components/HelloWorld.vue
-
-
- export default {
-
- name: 'HelloWorld'
-
- }
-
-
-
-
- <script setup>
-
- import { ref } from 'vue'
-
- const total = ref(10)
-
- script>
-
-
-
- <template>
-
- <div>{{ total }}div>
-
- template>
使用:
-
- import HelloWorld from './components/HelloWorld.vue'
-
- console.log(HelloWorld.name) // 'HelloWorld'
-
-
-
-
- <template>
-
- <HelloWorld />
-
- template>
注意: 如果你设置了 lang 属性, 和 的 lang 需要保持一致。
inheritAttrs 表示是否禁用属性继承,默认值是 true。
是没有组件配置项 inheritAttrs 的,可以再使用一个普通的 。
-
- import HelloWorld from './components/HelloWorld.vue'
-
-
-
-
- <template>
-
- <HelloWorld title="我是title"/>
-
- template>
./components/HelloWorld.vue
- <script>
-
- export default {
-
- name: 'HelloWorld',
-
- inheritAttrs: false,
-
- }
-
- </script>
-
-
-
- <script setup>
-
- import { useAttrs } from 'vue'
-
- const attrs = useAttrs()
-
- </script>
-
-
-
- <template>
-
- <div>
-
- <span :title="attrs.title">hover一下看title</span>
-
- <span :title="$attrs.title">hover一下看title</span>
-
- </div>
-
- </template>
中可以使用顶层 await。结果代码会被编译成 async setup()
-
- const userInfo = await fetch(`/api/post/getUserInfo`)
-
注意:async setup() 必须与 Suspense 组合使用,Suspense 目前还是处于实验阶段的特性,其 API 可能随时会发生变动,建议暂时不要使用。
在 vue3 中,我们可以使用点语法来使用挂载在一个对象上的组件。
- // components/Form/index.js
-
- import Form from './Form.vue'
-
- import Input from './Input.vue'
-
- import Label from './Label.vue'
-
- // 把Input、Label组件挂载到 Form 组件上
-
- Form.Input = Input
-
- Form.Label = Label
-
-
-
- export default Form
-
-
-
-
- // 使用:
-
-
-
-
- <template>
-
- <Form>
-
- <Form.Label />
-
- <Form.Input />
-
- Form>
-
- template>
/ |
命名空间组件在另外一种场景中的使用,从单个文件中导入多个组件时:
- // FormComponents/index.js
-
- import Input from './Input.vue'
-
- import Label from './Label.vue'
-
-
-
- export default {
-
- Input,
-
- Label,
-
- }
-
-
-
-
- // 使用
-
-
- import * as Form from './FormComponents'
-
-
-
-
- <template>
-
- <Form.Input>
-
- <Form.Label>labelForm.Label>
-
- Form.Input>
-
- template>
Vue3 中 标签可以通过 v-bind 这一 CSS 函数将 CSS 的值关联到动态的组件状态上。
-
- const theme = {
-
- color: 'red'
-
- }
-
-
-
-
- <template>
-
- <p>hellop>
-
- template>
-
-
-
- <style scoped>
-
- p {
-
- // 使用顶层绑定
-
- color: v-bind('theme.color');
-
- }
-
- style>
全局指令:
| |
| |
|
自定义指令:
-
- import { ref } from 'vue'
-
- const total = ref(10)
-
-
-
- // 自定义指令
-
- // 必须以 小写字母v开头的小驼峰 的格式来命名本地自定义指令
-
- // 在模板中使用时,需要用中划线的格式表示,不可直接使用vMyDirective
-
- const vMyDirective = {
-
- beforeMount: (el, binding, vnode) => {
-
- el.style.borderColor = 'red'
-
- },
-
- updated(el, binding, vnode) {
-
- if (el.value % 2 !== 0) {
-
- el.style.borderColor = 'blue'
-
- } else {
-
- el.style.borderColor = 'red'
-
- }
-
- },
-
- }
-
-
-
- const add = () => {
-
- total.value++
-
- }
-
-
-
-
- <template>
-
- <input :value="total" v-my-directive />
-
- <button @click="add">add+1button>
-
- template>
导入的指令:
-
- // 导入的指令同样需要满足命名规范
-
- import { directive as vClickOutside } from 'v-click-outside'
-
-
-
-
- <template>
-
- <div v-click-outside />
-
- template>
更多关于指令,见官方文档(https://v3.cn.vuejs.org/guide/custom-directive.html#%E7%AE%80%E4%BB%8B、https://v3.cn.vuejs.org/api/application-api.html#directive)。