一、Vue3.3最新特性
Vue 3.3 “Rurouni Kenshin”——浪客剑心的最新版本主要目标是优化开发者的使用体验,包括引入一些新的简化语法和宏,以及在TypeScript方面的进一步提升。
以下是主要的更新内容:
1.为setup语法糖组件引入了泛型功能;
2.允许在组件文件中导入外部的ts类型;
3.对defineEmits语法进行了优化;
4.新增defineSlots定义插槽类型;
5.新增defineModel来简化modelValue的语法;
6.Reactive Props 解构
7.新增defineOptions定义组件名称以及其他一些配置项;
8.对toRef和toValue的功能进行了增强。
接下来对这些改动一一介绍
1.setup语法糖泛型
使用<script setup>的组件现在可以通过generic属性接受泛型类型参数,一般情况下用不到,但有时候组件比较复杂时无法推断类型的时候非常有用
<script setup lang="ts" generic="T">
import {defineProps} from "vue";
const props = defineProps<{
items: T[];
selected: T;
}>();
</script>
2.支持导入外部ts类型
defineProps和defineEmits支持使用import外部导入的类型声明。
<script setup lang="ts">
import type { People } from './type.ts';
// 使用导入的类型 + 交集类型(导入类型基础上增加一个字段)
defineProps< People & { extraProp?: string }>() //在vue3.3之前不支持使用import导入的类型
</script>
generic 的值与 TypeScript 中 <...> 之间的参数列表用法完全相同。例如,您可以使用多个参数、extend 约束、默认类型和引用导入的类型:
<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
id: T
list: U[]
}>()
</script>
3.defineEmits语法优化
之前defineEmits的类型参数只支持函数调用签名语法:
// 以前
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
// 或者不定义类型
const emit = defineEmits(['update:modelValue'])
在vue3.3中可以简化为以下写法,更加简洁
// 现在
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
在类型字面量中,key 是事件名称,value 是事件参数的数组类型。
以前的函数调用签名语法仍然受支持。
4.新增defineSlots
新的defineSlots宏可以用来声明插槽的类型,例如:
子组件DefineSlots
<script setup lang="ts">
defineSlots<{
default?: (props: { msg: string }) => any
item?: (props: { id: number }) => any
}>()
</script>
其中,defineSlots中的item就是我们定义的插槽名称,props就是我们定义的插槽的参数,any就是我们定义插槽的返回值,msg、id就是插槽的参数。
父组件
<template>
<DefineSlots>
<template #default="{msg}" >{{msg}}</template>
<template #item="{id}" >{{id}}</template>
</DefineSlots>
</template>
<script setup lang="ts">
import DefineSlots from './components/defineSlots.vue';
</script>
插槽函数的返回类型目前被忽略。
试验性功能:
5.新增defineModel
用于简化自定义v-model双向绑定语法,在vue3.3中此功能是实验性的,需要明确的选择加入。
// vite.config.js
export default {
plugins: [
vue({
defineModel: true
})
]
}
以前组件想要支持 v-model,需要两个步骤:
1.声明 props
2.在打算更新 props 时,emit update:propName 事件
子组件支持 v-model 的写法:
<template>
<input :value="modelValue" @input="onInput" />
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
简化后的写法
<template>
<input v-model="modelValue" />
</template>
<script setup>
const modelValue = defineModel()
// 也可以直接修改,等价于emit('update:modelValue', '新值')
// modelValue.value = '新的值'
</script>
6.Reactive Props 解构
该功能可以解构的 props 并保持响应性,并提供了一种更符合人体工程学的方式来声明 props 的默认值:
<script setup>
import { watchEffect } from 'vue'
const { msg = 'hello' } = defineProps(['msg'])
watchEffect(() => {
// 在 watch 和 computed 中使用 msg
// 能够正常收集依赖,就好像使用 props.msg
console.log(`msg is: ${msg}`)
})
</script>
<template>{{ msg }}</template>
此功能是实验性的,需要明确的选择加入。
// vite.config.js
export default {
plugins: [
vue({
propsDestructure: true
})
]
}
其它值得注意的功能:
7.新增 defineOptions
新的defineOptions宏允许直接在<script setup>中声明组件选项,而不需要单独的<script>块
当我们想使用 mounted
钩子函数时,会报错,因为 <script setup>
会将所有的代码都放在 setup
函数中,而 mounted
是在 setup
函数之后执行的,所以会报错。
此外某些场景缓存页面数据,可能需要设置组件名称 name。
可以用 defineOptions 定义任意选项,但 props, emits, expose, slots 除外
<script setup>
import { onMounted } from 'vue';
// eslint-disable-next-line no-undef
defineOptions({
name: 'DefineOptions',
inheritAttrs: false,
mounted() {
console.log('mounted');
}
})
onMounted(()=>{
console.log('onMounted')
})
</script>
8.toRef和toValue增强
toRef已得到增强,将元素变成响应式,支持将值/getter/现有refs规范化为refs:
// 等价于ref(1)
toRef(1)
// 创建一个readonly ref,在.value访问时调用getter
toRef(() => props.foo)
// 按原样返回现有的引用
toRef(existingRef)
使用getter调用toRef类似于computed,但当getter只是执行属性访问而没有昂贵的计算时,效率会更高。
新的toValue实用程序方法提供了相反的功能,将values / getters / refs标准化为值:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
toValue可以在composable中代替unref使用,这样你的composable就可以接受getter作为反应式数据源:
// 以前:分配不必要的中间引用
useFeature(computed(() => props.foo))
useFeature(toRef(props, 'foo'))
// 现在:更高效和简洁
useFeature(() => props.foo)
toRef和toValue之间的关系类似于ref和unref之间的关系,主要区别在于对getter函数的特殊处理。
9.依赖更新
依赖更新
升级到 3.3 时,建议同时更新以下依赖项:
volar / vue-tsc@^1.6.4
vite@^4.3.5
@vitejs/plugin-vue@^4.2.0
vue-loader@^17.1.0(如果使用 webpack 或 vue-cli)
二、Vue Router新特性
在使用vue-router4中params 进行路由组件之间传参
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const params = { id: '1', name: 'wjj', phone: 123456789, age: 23 }
function toFirst() {
router.push({ name: 'first', params })
}
</script>
在接收页面尝试渲染params传递的数据:
template>
<div>姓名:{{ route.params?.name }}</div>
<div>电话:{{ route.params?.phone }}</div>
<div>年龄:{{ route.params?.age }}</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>
跳转页面接收不了并出现如下Vue Router警告:
警告
我们点击连接后发现了原因:
github更新日志
也就是说,从Vue Router的2022-8-22 这次更新后,我们使用上面的方式在新页面无法获取。
Vue也给我们提出了代替方案:
1.使用query的方式传参
修改为query方式传参数,注意query方式只能用路由表中的path,不是name,并且所有的参数都会显示在URL地址上。
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const params = { id: '1', name: 'wjj', phone: 123456789, age: 23 }
function toFirst() {
// query方式
router.push({ path: '/first', query: params })
}
</script>
跳转之后参数显示在URL上
2.将数据放在pinia或者vuex这样的状态管理库里面
实际工作中,咱们非必要不会使用这种方式。
3.使用动态路由匹配
如果传递参数较少的情况下,可以尝试使用动态路由匹配方式,只要修改一下path定义部分就可以了:
{
// path: '/first',
path: '/first/:id/:name/:phone/:age', // 动态路由,修改一下path定义
name: 'first',
component: () => import('../views/FirstView.vue')
}
跳转页面通过path,接收页面使用route.params
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
function toFirst() {
// 动态路由
router.push({ path: '/first/1/wjj/123456789/23'})
}
</script>
<!--动态路由方式-->
<p>姓名:{{ route.params?.name }}</p>
<p>电话:{{ route.params?.phone }}</p>
<p>年龄:{{ route.params?.age }}</p>
注意:如果使用了动态路由匹配方式,path中的每个参数都是必须传递,否则会报错。
警告
个人觉得动态路由匹配方式接收参数与params方式一样,如果不把params参数写在路由路径上无法得到params参数,虽然不算弃用了params,但是每次都把params参数都写在路由路径上也是非常麻烦的一件事。
4.使用HistoryAPI的方式
在跳转页面使用state参数
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const params = { id: '1', name: 'wjj', phone: 123456789, age: 23 }
function toFirst() {
// state
router.push({ name: 'first', state: { params } })
}
</script>
跳转后的页面接收:
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const historyParams = history.state.params
</script>
网友评论