The Components In-Depth chapters are the ones interviewers quote when they want to know whether you learned Vue or only used it. Prop validation options, the Boolean casting rule, what declaring an emit changes, and how v-model desugars on a component are all checkable facts with precise answers.
app.component('MyComponent', MyComponent): usable anywhere, but not tree-shaken and dependencies become implicit. Local: import in <script setup> (usable directly) or list in the components option.<MyComponent>); in-DOM templates need kebab-case (<my-component>). PascalCase registration lets both forms work.defineProps(['foo']), an object with types and options, or a TypeScript type argument defineProps<{ foo: string }>(). Declared props are removed from $attrs.const { foo = 'x' } = defineProps<...>() stays reactive because the compiler rewrites foo to props.foo; pass it to watch as a getter, and to composables as () => foo or through toValue. Before 3.5 destructuring loses reactivity, and defaults use withDefaults.greeting-message). Static and dynamic (:title) values; numbers, booleans, arrays and objects need : even when literal. v-bind="post" passes every property of an object as props.type (native constructor, custom class, array of types, or null/undefined for any), required, default (function for objects and arrays; functions as values need default: () => fn), validator(value, props). Absent optional props are undefined except Boolean; default applies to undefined. Type checks happen in development only.false; a present prop with no value (<Comp disabled>) is true; with mixed [Boolean, String] the order decides how "" is cast (Boolean first means "" becomes true).instanceof for custom classes and Object.prototype.toString for natives.$emit('name', ...args) in templates, emit from defineEmits in setup. Arguments after the name are passed to the listener; an inline listener receives them as $event only for the first one, otherwise use an arrow function.defineEmits(['inFocus']), object syntax with validators, or the TypeScript call-signature or named tuple syntax defineEmits<{ change: [id: number] }>() 3.3+): declared events are documented, validated and excluded from $attrs, so an @click on the component does not also attach a native listener to the root.@some-event). Events do not bubble; a grandparent needs a chain of emits, a provide/inject callback, or a store.defineModel() (3.4+) returns a ref bound to the parent's v-model; it declares a modelValue prop and an update:modelValue event under the hood. Options: required, default, type, and get/set transformers.v-model:title on the parent pairs with defineModel('title'); several v-models are allowed on one component.const [model, modifiers] = defineModel() exposes them; a set option can apply them (capitalize example in the guide). With arguments, modifiers arrive as titleModifiers.modelValue prop + update:modelValue emit, either bound directly on the native input (:value + @input="$emit('update:modelValue', $event.target.value)") or through a writable computed.<script setup lang="ts">
// Reactive props destructure with a default (3.5+), Boolean casting, custom validator
const { currency = 'EUR', compact } = defineProps<{
currency?: string
compact?: boolean // absent -> false, `<PriceInput compact>` -> true
}>()
// Named tuple syntax (3.3+); declared events are not passed through as native listeners.
const emit = defineEmits<{
blur: []
change: [amount: number, currency: string]
}>()
// v-model:amount with a `.cents` modifier, normalised in the setter
const [amount, modifiers] = defineModel<number>('amount', {
default: 0,
set: v => (modifiers.cents ? Math.round(v * 100) / 100 : v),
})
function onInput(e: Event) {
amount.value = Number((e.target as HTMLInputElement).value)
emit('change', amount.value, currency)
}
</script>
<template>
<input :value="amount" type="number" :class="{ compact }" @input="onInput" @blur="emit('blur')">
</template>
<PriceInput v-model:amount.cents="price" currency="CZK" compact @change="save" />
Runtime validation in the object syntax, for the facts that only exist there:
defineProps({
size: { type: String, default: 'md', validator: (v: string) => ['sm', 'md', 'lg'].includes(v) },
items: { type: Array, default: () => [] }, // factory: a shared array would leak between instances
author: Person, // custom class, checked with instanceof
meta: { type: [Object, null], required: true }, // null allows any, here "object or null"
})
default: [] would share one array across every instance of the component (the same object reference), so mutating it in one instance changes it everywhere. Objects and arrays need default: () => []. Functions used as values need a wrapper too, otherwise Vue calls them as the factory.
[Boolean, String] prop and a [String, Boolean] prop, pass <Comp flag> to both, and compare the received values.@click to a component that declares emits: ['click'] and to one that does not; check which one fires a native click from the root element.modelValue/update:modelValue component with defineModel, keeping a .trim modifier working."Three things. It documents the component's events and, with the object or TypeScript syntax, validates their payloads in development. It removes those events from $attrs, so a listener for a declared event is not also attached as a native listener on the root element, which prevents double handling for names like click or submit. And it makes the events part of the component's typed contract, which is what IDEs and vue-tsc check at the call site. Without declaration a listener falls through as an attribute and fires as a native event, which is the source of a classic 'my handler ran twice' bug."
Nothing yet. Notes filed here show up on this page and the section overview.
Essentials II — rendering, events and forms
Conditional and List Rendering, Event Handling, Form Input Bindings, Watchers, Template Refs, Components Basics and Lifecycle Hooks, as the guide states them.
Components II — attrs, slots, provide/inject, async
Fallthrough attributes and inheritAttrs, slots in every form, provide/inject with reactivity and Symbol keys, and async components with loading states and lazy hydration strategies.