vue3.3 必学知识点
前言
vue3.3发布了,针对之前script setup
语法糖做了许多优化,解决了一些痛点,让我感觉这写法终于能正儿八经用一用了,下面就是一些必学的知识点介绍了。
官方博客说明文档:Announcing Vue 3.3
Volar插件
为了完整体验vue3.3的特性,我们需要将Volar插件升级到测试版v1.7.8及以上。
利用组件泛型实现自动推断类型?
现在组件的script上可以配置一个generic
属性,用来接受泛型参数,但是我感觉官方给的示例代码没有什么实战意义。
generic可以接受多个泛型,用逗号分隔,你甚至可以利用extends
做一个接受的类型约束。
<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from "./types";
defineProps<{
id: T;
list: U[];
}>();
</script>
这是一个官方的示例,我们甚至可以import引入外部类型来使用,显然这里不符合先引再用的调用规则,但是官方会在真实使用的时候做特殊处理,所以不用太担心。
看上去很美好,但是它好像并没有解决什么问题,比如props还是得有明确的类型声明,T明显要求是个基本类型,U还得约束于Item
,显然它不能帮助我们减少props类型声明。
但是它也许可以帮助我们去实现slot插槽参数类型声明,在一些复杂的场景中,我们可能会抽象出一个插槽的位置,这个位置用于用户自定义,那么就需要将参数返回给父组件中去,这个数据的类型也许可以用到泛型。
但是目前来说,这个特性可能还需要观望一阵子。
defineProps解构和默认值
这个特性需要手动开启,vite.config.ts中:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue({
script: {
propsDestructure: true //手动开启
}
})
],
});
配置完毕后记得重启下项目,如果配置了eslint,我们还需要手动配置一个规则:
.eslintrc.cjs
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting"
],
parserOptions: {
ecmaVersion: "latest"
},
rules: {
"vue/no-setup-props-destructure": "off", //配置这个
}
};
这样就不会报错了,因为vue3一开始就不允许props可以解构,那样会丢失响应式,现在3.3版本做了特殊处理,解构的值会被转为响应式对象。
<template>
<div>{{ names }}</div>
</template>
<script lang="ts" setup generic="T">
const { names = "默认值" } = defineProps<{
names: T;
}>();
</script>
当我父组件没有传参时会使用默认值。
但是控制台会报警告
因为我们声明类型的时候,是个必填项,我们调整一下
<template>
<div>{{ names }}</div>
</template>
<script lang="ts" setup generic="T">
const { names = "默认值" } = defineProps<{
names?: T;
}>();
</script>
问题解决。
解构出来的值我们打印,发现是string类型,但是如果我们将其放入watch,computed中,却可以监听到变化。
父组件:
<template>
<div>
<TestC :names="names" />
<button @click="onClick">change</button>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import TestC from "@/components/TestC.vue";
const names = ref("测试");
function onClick() {
names.value = "测试" + Math.random();
}
</script>
TestC 组件:
<template>
<div>
<div>{{ names }}</div>
<div>{{ comNames }}</div>
</div>
</template>
<script lang="ts" setup generic="T">
import { computed, watch } from "vue";
const { names = "默认值" } = defineProps<{
names?: T;
}>();
console.log(names);
const comNames = computed(() => `computed: ${names}`);
watch(
() => names,
(newVal, oldVal) => {
console.log("🚀 ~ file: TestC.vue:28 ~ newVal,oldVal:", newVal, oldVal);
}
);
</script>
效果还是很惊艳的,明显是在编译时做了处理。
更加便捷的defineEmits类型声明
之前的:
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
其中e
是事件的名字,剩下的则是事件的参数。
现在调整为:
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
现在key为事件名,值为事件参数 类型。
目前支持这两种声明模式,但是不能混用,如果你的emits返回值都是void,那么推荐使用新版本的方式,会更加符合代码认知。
新增 defineOptions
之前一直不怎么喜欢用setup语法糖,是因为一些缺失了options配置项,比如配置组件的name属性,使用setup语法糖是没法做到的,因为整个script都是setup函数,没有同级选项了。
于是不得不出现这么一种情况,一个vue组件中,会有两个script声明,一个用于配置options,比如组件名称之类的,一个用来setup语法糖。
巨难受好吧!
现在新增量这么一个配置,专门来解决setup语法糖没有同级属性的问题,方便的很。
defineOptions({
name: "MyTestC",
inheritAttrs: false
});
静态常量提升
defineOptions的官方文档中有这么一句话:无法访问 <script setup>
中不是字面常数的局部变量,顾名思义,说明defineOptions中是可以使用变量的,但是这个变量必须是个常量。
本来defineOptions会被提取提升,那么它如何获取到setup中的变量呢,其实就是因为实现了静态常量提升。
用这个功能作者 三咲智子 的例子来说明一下:
<template>
<div id="title">Hello World</div>
</template>
这段代码最终会被编译为:
const _hoisted_1 = { id: 'title' }
function render(_ctx, _cache) {
return _openBlock(), _createElementBlock('div', _hoisted_1, 'Hello World')
}
其中_hoisted_1
变量就是被编译器故意提升到顶层的代码。如果关闭此静态常量提升,则它会在 render 函数内。
而setup中的常量也是同理,也会被提升到顶层,所以defineOptions才能拿到。
新增 defineModel
这个就确实有点香了啊,可惜只有setup语法糖才能用,过分啊。
这个也是实验性功能,我们需要手动开启:
vite.config.ts中:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true, //手动开启
}
})
],
});
我们来看一下我们以前声明一个v-model需要多少代码:
<template>
<div>{{ show }}</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
const props = defineProps<{
modelValue: boolean;
}>();
const emit = defineEmits<{
"update:modelValue": [value: boolean];
}>();
const show = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
}
});
</script>
你需要声明props和emit,为了方便使用我们还需要一个computed做数据拦截。
现在的变化:
<template>
<div>{{ show }}</div>
</template>
<script lang="ts" setup>
const show = defineModel();
</script>
defineModel默认就是用modelValue
字段,如果你想指定名字:
<template>
<div>{{ show }} {{ myShow }}</div>
</template>
<script lang="ts" setup>
const show = defineModel();
const myShow = defineModel("myShow");
</script>
父组件使用时:
<TestC v-model="show" v-model:myShow="show" />
巨方便了。
新增 toRef 和 toValue
vue3之前提供了一个toRefs
的方法,用于将reactive
对象转为可以解构的普通对象,并且保证数据的响应式。
但是toRefs
转的是整个对象,而新增toRef
方法可以处理单个指定属性。
<template>
<div>
<div>{{ age }}</div>
<button @click="age++">+1</button>
<div>{{ a }}</div>
<button @click="a++">+1</button>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRef } from "vue";
const test = reactive({
name: "test",
age: 18,
data: {
a: 1
}
});
const age = toRef(test, "age");
const a = toRef(test.data, "a");
</script>
如果属性层级过深,参考a的写法,toRef的第二个值只能是单个属性名。
除了这种用法,toRef还支持只读写法,也就是getter函数。
这种方式解决了一个问题,当我们书写hooks或者其他方法的时候,可能需要接一个参数,这个参数可能是props的属性,但是如果我们直接通过props.xxx
的方式传入,就会丢失响应式数据,得到一个源数据。
于是可能会有人创建一个ref对象,或者使用computed做一个计算属性,但是它们都不完美,ref是可以修改的,计算属性会有代价,因为它有缓存处理。
于是这次新增的toRef就是为了解决这个问题:
<script lang="ts" setup>
import { toRef } from "vue";
const props = defineProps({
myShow: Boolean
});
const myShow = toRef(() => props.myShow);
console.log(myShow); //GetterRefImpl xxx
</script>
打印出来的是一个GetterRefImpl
对象,这个对象只读不能修改,符合props不允许直接修改的要求,而数据又能响应式。
而新增的toValue从名字的角度来说,它就是取值的意思,所以他可以这样:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
而以前的unref
更多的是用于获取原值:
unref(1) // --> 1
unref(ref(1)) // --> 1
unref(() => 1) // --> () => 1
所以toValue更适合处理GetterRefImpl,它们的关系:
toRef
-----toValue
ref
------unref
其他知识
defineSlots 支持类型参数
文档:typed-slots-with-defineslots
jsx 可以指定全局类型
用于解决react和vue对于jsx的类型定义冲突,由于两个都是定义的全局,混用的情况会出现类型定义的冲突,vue3.4之后删除了jsx的全局类型定义,可以手动指定。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据