Supporting Props Destructure
About this chapter
This chapter explains how to implement Vue 3.5's Reactive Props Destructure feature.
Learn how to maintain reactivity while destructuring props.
What is Reactive Props Destructure?
Starting from Vue 3.5, you can destructure the return value of defineProps in <script setup>.
<script setup>
const { count, message = 'default' } = defineProps({
count: Number,
message: String
})
</script>
<template>
<p>{{ count }} - {{ message }}</p>
</template>This feature makes accessing props simpler.

In regular JavaScript, destructuring an object copies values and breaks the connection to the original object.
However, Vue's props need to be reactive.
The compiler transforms destructured access to __props.xxx access to maintain reactivity!
How It Works
Props destructure is implemented through these steps:
- Pattern detection: Detect
const { ... } = defineProps(...) - Binding registration: Register each destructured property as
PROPS - Default value handling: Transform default values into
withDefaultsequivalent - Code transformation: Transform props access to
__props.xxx
Transformation Example
<!-- Input -->
<script setup>
const { count, message = 'hello' } = defineProps({
count: Number,
message: String
})
console.log(count, message)
</script>// Output
export default {
props: {
count: Number,
message: { type: String, default: 'hello' }
},
setup(__props) {
console.log(__props.count, __props.message)
return (_ctx) => {
// ...
}
}
}Detecting Destructure Patterns
Detect if defineProps return value is assigned to an ObjectPattern (destructure pattern).
// packages/compiler-sfc/src/compileScript.ts
interface PropsDestructureBindings {
[key: string]: {
local: string // Local variable name
default?: string // Default value
}
}
let propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
function processDefineProps(node: Node, declId?: LVal): boolean {
if (!isCallOf(node, DEFINE_PROPS)) {
return false
}
propsRuntimeDecl = node.arguments[0]
// Handle destructure pattern
if (declId && declId.type === "ObjectPattern") {
processPropsDestructure(declId)
} else if (declId) {
propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
}
return true
}Processing Destructure
Extract each property from ObjectPattern and register as bindings.
function processPropsDestructure(pattern: ObjectPattern) {
for (const prop of pattern.properties) {
if (prop.type === "ObjectProperty") {
const key = prop.key
const value = prop.value
// Get property name
let propKey: string
if (key.type === "Identifier") {
propKey = key.name
} else if (key.type === "StringLiteral") {
propKey = key.value
} else {
continue
}
// Process local variable name and default value
let local: string
let defaultValue: string | undefined
if (value.type === "Identifier") {
// const { count } = defineProps(...)
local = value.name
} else if (value.type === "AssignmentPattern") {
// const { count = 0 } = defineProps(...)
if (value.left.type === "Identifier") {
local = value.left.name
defaultValue = scriptSetup!.content.slice(
value.right.start!,
value.right.end!
)
} else {
continue
}
} else {
continue
}
// Register binding
propsDestructuredBindings[propKey] = { local, default: defaultValue }
bindingMetadata[local] = BindingTypes.PROPS
}
}
}Default Value Handling
When default values are specified in destructure, merge them into props definition.
function genRuntimeProps(): string | undefined {
if (!propsRuntimeDecl) return undefined
let propsString = scriptSetup!.content.slice(
propsRuntimeDecl.start!,
propsRuntimeDecl.end!
)
// Merge default values if present
const defaults: Record<string, string> = {}
for (const key in propsDestructuredBindings) {
const binding = propsDestructuredBindings[key]
if (binding.default) {
defaults[key] = binding.default
}
}
if (Object.keys(defaults).length > 0) {
// Process equivalent to withDefaults
propsString = mergeDefaults(propsString, defaults)
}
return propsString
}
function mergeDefaults(
propsString: string,
defaults: Record<string, string>
): string {
// Actual implementation manipulates AST to merge defaults
// Simplified example here
const ast = parseExpression(propsString)
// ... merge default values
return generate(ast).code
}Transforming Props Access
Transform access to destructured variables into __props.xxx in templates and scripts.
function processPropsAccess(source: string): string {
const s = new MagicString(source)
// Walk identifiers and transform
walk(scriptSetupAst, {
enter(node: Node) {
if (node.type === "Identifier") {
const binding = propsDestructuredBindings[node.name]
if (binding && binding.local === node.name) {
// Transform to props access
s.overwrite(node.start!, node.end!, `__props.${node.name}`)
}
}
}
})
return s.toString()
}
Destructuring normally loses reactivity in JavaScript,
but the compiler transforms it to __props.xxx access,
allowing you to use destructure syntax as syntactic sugar!
Rest Pattern Support
Support for ...rest patterns is also possible.
<script setup>
const { id, ...attrs } = defineProps(['id', 'class', 'style'])
</script>function processPropsDestructure(pattern: ObjectPattern) {
for (const prop of pattern.properties) {
if (prop.type === "RestElement") {
// Handle rest pattern
if (prop.argument.type === "Identifier") {
const restName = prop.argument.name
// rest requires special handling
// Actually uses computed to get remaining props
bindingMetadata[restName] = BindingTypes.SETUP_REACTIVE_CONST
}
}
// ...
}
}Testing
<!-- Parent.vue -->
<script setup>
import { ref } from 'chibivue'
import Child from './Child.vue'
const count = ref(0)
const message = ref('Hello')
</script>
<template>
<Child :count="count" :message="message" />
<button @click="count++">Increment</button>
</template><!-- Child.vue -->
<script setup>
const { count, message = 'default' } = defineProps({
count: Number,
message: String
})
// count and message are transformed to __props.count, __props.message
console.log(count, message)
</script>
<template>
<p>{{ count }} - {{ message }}</p>
</template>Future Enhancements
These features could also be considered:
- Alias support: Support for
const { count: c } = defineProps(...)
Source code up to this point: chibivue (GitHub)
Summary
- Props Destructure was introduced in Vue 3.5
- Detect destructure patterns and register each property as
PROPSbinding - Default values are merged into props definition
- Transform variable access to
__props.xxxto maintain reactivity
References
- Vue.js - Reactive Props Destructure - Vue Official Documentation
- RFC - Reactive Props Destructure - Vue RFC
