The Vue team has announced the release of version 3.3 – “Rurouni Kenshin.”
In this new version, the developers focused on improving the development experience. For example, they enhanced the interaction with SFC <script setup>
in TypeScript.
Many long-standing issues with using Vue and TypeScript have also been resolved.
Main Changes
Dependency Updates
To upgrade to Vue 3.3, the following dependencies also need to be updated:
- volar/vue-tsc@^1.6.4
- vite@^4.3.5
- @vitejs/plugin-vue@^4.2.0
- vue-loader@^17.1.0 (if using webpack or vue-cli)
Support for Imported and Complex Types in Macros
Prior to version 3.3, types in defineProps
and defineEmits
could only be local types and supported only type literals.
Copyright TechPlanet.today
This was because Vue needed to analyze prop properties to generate options during runtime.
Now, in version 3.3, the compiler allows the use of imported and complex types.
<script setup lang="ts">
import type { Props } from './foo'
// imported + intersection type
defineProps<Props undefined { extraProp?: string }>()
</script>
Please note that support for complex types is based on AST, so not all types can be fully supported. For example, conditional types are not supported at all.
You can use conditional types for a single parameter, but not for an object of parameters.
Universal Components
Components with <script setup>
now accept universal parameters through the generic
attribute:
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
The generic
attribute works as a parameter list between <...>
in TypeScript.
Now you can use multiple parameters, extends
, default types, and imported types.
<script setup lang="ts" generic="T extends string | number, U extends Item">
import type { Item } from './types'
defineProps<{
id: T
list: U[]
}>()
</script>
Previously, this feature had to be manually enabled. However, in the latest version of volar/vue-tsc, it is included by default.
Enhanced defineEmits Syntax
Previously, the parameter for defineEmits
only supported the call signature syntax:
// BEFORE
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
The type corresponds to the return type for emit
, but it is short and inconvenient to write. In version 3.3, a more ergonomic way of declaring emit
has been introduced:
// AFTER
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
In the type literal, the key represents the event name, and the value represents an array type that defines additional arguments.
The old signature syntax is still supported.
Typed Slots with defineSlots
The new defineSlots
macro can be used to declare expected slots and their properties:
<script setup lang="ts">
defineSlots<{
default?: (props: { msg: string }) => any
item?: (props: { id: number }) => any
}>()
</script>
The defineSlots()
function only accepts a parameter of type, but does not accept runtime arguments.
The type parameter must be a type literal where the property key is the name of the slot, and the value is the slot function.
The first argument of the function is props
, the type of which will be used for slot props in the template.
The value of defineSlots
is the same slot object that is returned from useSlots
.
Current limitations:
- Slot validation is not yet implemented in volar/vue-tsc.
- The return type of the slot function can be anything, but in the future it may be used to check the contents of the slot.
- There is also a slots option for use in
defineComponent
. Both APIs are used exclusively as type hints for IDE and vue-tsc.
Experimental features:
- Destructuring reactive props: This function allows destructured props to maintain reactivity and offers a more convenient way to declare values.
<script setup>
import { watchEffect } from 'vue'
const { msg = 'hello' } = defineProps(['msg'])
watchEffect(() => {
// accessing `msg` in watchers and computed getters
// tracks it as a dependency, just like accessing `props.msg`
console.log(`msg is: ${msg}`)
})
</script>
<template>{{ msg }}</template>
This function is experimental and requires explicit consent.
defineModel
Previously, in order for a component to support two-way binding with v-model, it had to declare a property and create an update:propName
event to update the property.
<!-- BEFORE -->
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
Version 3.3 simplifies this process with the defineModel
macro. The macro automatically declares the property and returns a reference to it.
<!-- AFTER -->
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>
<template>
<input v-model="modelValue" />
</template>
This function is experimental and requires explicit consent.
Other notable features:
defineOptions
The new defineOptions macro allows you to declare component options directly in <script setup>
. This eliminates the need for a separate <script>
block.
<script setup>
defineOptions({ inheritAttrs: false })
</script>
Improved support for Getters with toRef and toValue
toRef
has been enhanced to better support value normalization, getters, and refs
.
// equivalent to ref(1)
toRef(1)
// creates a readonly ref that calls the getter on .value access
toRef(() => props.foo)
// returns existing refs as-is
toRef(existingRef)
Calling toRef
can be more efficient when the getter simply needs to access properties. In such cases, lengthy and complex computations are not required.
The new utility method toValue
does the opposite, normalizing everything into values.
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
toValue
can be used in composite components instead of unref
to have components accept getters as reactive data sources.
// before: allocating unnecessary intermediate refs
useFeature(computed(() => props.foo))
useFeature(toRef(props, 'foo'))
// after: more efficient and succinct
useFeature(() => props.foo)
The difference between toRef
and toValue
is the same as between ref and unref
. The only difference lies in how getter functions are handled.
Importing JSX Source Code
Currently, Vue types automatically register global JSX typings, which can conflict with other libraries that need to define JSX types, particularly React.
Starting from version 3.3, Vue supports specifying JSX typings using the TypeScript jsxImportSource
parameter.
Improvements in Service Infrastructure
Here are the improvements made in the 3.3 release:
- Build times are now 10 times faster due to separating type checking from the monolithic build and transitioning from rollup-plugin-typescript2 to rollup-plugin-esbuild.
- Tests have been accelerated by switching from Jest to Vitest.
- Type generation has been sped up by switching from @microsoft/api-extractor to rollup-plugin-dts.
- Comprehensive regression tests using ecosystem-ci help identify regressions in key dependencies before release.
In this post, we have covered the main changes in version 3.3. You can find the full list of updates on GitHub.
In case you have found a mistake in the text, please send a message to the author by selecting the mistake and pressing Ctrl-Enter.
#Vue #Overview #Whats #Whats #TypeScript