Prop validation with type requirements
Components can specify validation requirements for props. If a requirement is not met, Vue will warn in the browser's JavaScript console. Validation is specified by providing an object with validation requirements to defineProps() or the props option instead of an array of strings.
Basic prop type check
Basic type checking is done by specifying the constructor function as the prop value. For example: propA: Number. A null or undefined value allows any type.
Multiple prop types
To allow a prop to accept multiple types, use an array of constructor functions. For example: propB: [String, Number]
Required prop validation
To mark a prop as required, set required: true in the prop validation object. For example: propC: { type: String, required: true }. All props are optional by default unless required: true is specified.
Prop default value
A default value for a prop is specified with the default property. For example: propE: { type: Number, default: 100 }. The default value is used if the resolved prop value is undefined, including when the prop is absent or an explicit undefined value is passed.
Object and array default values must use factory functions
For object or array default values, a factory function must be used that returns the default value. The function receives the raw props as an argument. For example: propF: { type: Object, default(rawProps) { return { message: 'hello' } } }. This ensures each component instance gets its own copy of the default value.
Function default values are not factory functions
Unlike object or array defaults, function default values are not factory functions. The function is the actual default value to serve. For example: propH: { type: Function, default() { return 'Default function' } }
Custom validator function for props
A custom validator function can be specified to validate prop values. The validator receives the value as the first argument and the full props object as the second argument (in Vue 3.4+). It should return true if the value is valid. For example: propG: { validator(value, props) { return ['success', 'warning', 'danger'].includes(value) } }
Absent optional non-boolean prop value
An absent optional prop other than Boolean will have the value undefined.
Absent boolean prop defaults to false
An absent boolean prop will be cast to false. This can be changed by setting a default value explicitly, such as default: undefined to make it behave like a non-boolean prop.
defineProps code scope limitation
Code inside the defineProps() argument cannot access other variables declared in <script setup> because the entire expression is moved to an outer function scope when compiled.
Options API prop validation scope limitation
Props are validated before a component instance is created, so instance properties like data and computed will not be available inside default or validator functions.
Runtime type checks - native constructors
The type in prop validation can be one of the following native constructors: String, Number, Boolean, Array, Object, Date, Function, Symbol, Error.
Custom class type validation
The type can also be a custom class or constructor function, and the assertion is made with an instanceof check. For example, a prop can be validated to be an instance of a custom Person class.
Nullable type with array syntax
If a type is required but nullable, use the array syntax that includes null. Note that if type is just null without the array syntax, it will allow any type.
Boolean casting rules
Props with Boolean type have special casting rules to mimic the behavior of native boolean attributes. A prop declared with Boolean type without any value (e.g., <MyComponent disabled />) is equivalent to passing true, and omitting it is equivalent to passing false.
Boolean casting with multiple types - order matters
When a prop is declared to allow multiple types including Boolean and String, the boolean casting rule only applies if Boolean appears before String in the type array. For example, [Boolean, String] casts to true, but [String, Boolean] parses as an empty string (disabled='').
TypeScript prop declarations with script setup
With TypeScript in <script setup>, props can be declared using pure type annotations. For example: defineProps<{ title?: string; likes?: number }>(). This provides type safety at compile time.
Type-based props compilation to runtime declarations
When using Type-based props declarations with TypeScript, Vue will try to compile the type annotations into equivalent runtime prop declarations. For example, defineProps<{ msg: string }> is compiled into { msg: { type: String, required: true } }.
Provide/Inject solves prop drilling
Provide/Inject allows a parent component to serve as a dependency provider for all descendants, allowing any nested component to inject dependencies directly from ancestor components without passing props through intermediate components. This solves the prop drilling problem where props must be passed through every level of a component tree.
Composition API provide() function syntax
The provide() function from Vue accepts two arguments: an injection key (string or Symbol) and a provided value of any type. Example: provide('message', 'hello!'). Provide must be called synchronously inside setup() or directly in <script setup>. Reactive values like refs can be provided and will establish a reactive connection to descendant components.
Options API provide option syntax
In Options API, use the provide option to provide data to descendants. For static values, provide an object with key-value pairs: provide: { message: 'hello!' }. For per-instance state, use a function that returns an object, which allows access to this: provide() { return { message: this.message } }. Note that this approach does not make injections reactive.
App-level provide
Use app.provide(key, value) to provide data at the app level, making it available to all components rendered in the app. App-level provides are especially useful when writing plugins, as plugins typically cannot provide values using components.
Composition API inject() function syntax
The inject() function takes an injection key and optionally a default value. Example: const message = inject('message'). If the provided value is a ref, it is injected as-is and not automatically unwrapped, allowing the injector component to retain the reactivity connection to the provider. inject() should be called synchronously inside setup() or directly in <script setup>.
Composition API inject with default value
Pass a default value as the second argument to inject(): const value = inject('message', 'default value'). To avoid unnecessary computation or side effects, use a factory function for the default value by passing true as the third parameter: const value = inject('key', () => new ExpensiveClass(), true).
Options API inject option syntax
Use the inject option with an array for simple cases: inject: ['message']. Injections are resolved before the component's own state, so injected properties can be accessed in data(). For aliasing or default values, use object syntax: inject: { message: { from: 'message', default: 'default value' } }. For non-primitive values, use a factory function: inject: { user: { default: () => ({ name: 'John' }) } }.
Injection aliasing in Options API
Use the object syntax in inject to expose an injected property under a different local key: inject: { localMessage: { from: 'message' } }. This injects the value provided with key 'message' and exposes it as this.localMessage.
Multiple parents with same injection key resolution
When multiple parents provide data with the same injection key, inject() resolves to the value from the closest parent in the component's parent chain.
Injection default values in Composition API
By default, inject assumes the injection key is provided somewhere in the parent chain and will produce a runtime warning if not found. Declare a default value to make an injected property optional: const value = inject('message', 'default value').
Best practice for reactive provide/inject mutations
Keep any mutations to reactive state inside the provider component whenever possible. This co-locates the provided state and its mutations in the same component, making it easier to maintain. If an injector component needs to update data, the provider should expose a mutation function.
Provide mutation function example
When an injector component needs to update provided state, the provider can expose an object containing both the state and a function to mutate it. Example: provide('location', { location, updateLocation }) where updateLocation is a function that mutates the location ref.
Readonly wrapper for provided values
Use readonly() to wrap provided reactive values if you want to ensure the data cannot be mutated by injector components. Example: provide('read-only-count', readonly(count)).
Options API reactive provide/inject with computed
To make injections reactively linked to the provider in Options API, provide a computed property: provide() { return { message: computed(() => this.message) } }. This establishes a reactive connection between provider and injector components.
Symbol injection keys for large applications
In large applications or when authoring components for other developers, use Symbol injection keys instead of strings to avoid potential key collisions. Export Symbols in a dedicated file and import them in both provider and injector components.
Symbol injection key example
Create a keys.js file exporting Symbols: export const myInjectionKey = Symbol(). In the provider, import and use it: provide(myInjectionKey, { /* data */ }). In the injector, import and use it: const injected = inject(myInjectionKey).
Local registration with script setup in SFCs
When using `<script setup>` in Single File Components, imported components can be locally used without explicit registration. The component is automatically available in the template when imported.
Global component registration with .component() method
Use the `.component()` method on a Vue application instance to register components globally. The method takes two arguments: the registered name (a string) and the implementation (the component object). Global registration makes components available in templates of any component within that application, including all subcomponents.
Global registration example with imported SFC
When registering a Single File Component globally, import the .vue file and pass it to app.component(). Example: `import MyComponent from './App.vue'; app.component('MyComponent', MyComponent)`.
Chaining .component() calls for multiple global registrations
The `.component()` method can be chained to register multiple components in sequence. Example: `app.component('ComponentA', ComponentA).component('ComponentB', ComponentB).component('ComponentC', ComponentC)`.
Global registration makes dependency relationships less explicit
Global registration makes it difficult to locate a child component's implementation from a parent component using it in large applications. This reduces explicitness of dependencies and can negatively affect long-term maintainability, similar to overusing global variables.
Local registration example with script setup
Example of local registration with `<script setup>`: import the component in the script section and use it directly in the template without registering it. The component is automatically available.
Local registration using components option
In non-`<script setup>` components, use the `components` option to locally register components. The `components` object has component names as keys and their implementations as values. Locally registered components are only available to the current component, not to descendant components.
Local registration example with components option
Example of local registration using the components option: `export default { components: { ComponentA }, setup() { /* ... */ } }`. The component name is the key and the imported component is the value.
Locally registered components not available in descendants
Locally registered components are only available to the current component where they are registered. They are not automatically available in child or descendant components.
PascalCase recommended for component naming
Use PascalCase when registering components because PascalCase names are valid JavaScript identifiers (making imports and registration easier and improving IDE auto-completion), and `<PascalCase />` clearly indicates a Vue component rather than native HTML in templates, differentiating Vue components from custom elements.
Vue supports kebab-case tag resolution for PascalCase components
Vue resolves kebab-case tags to components registered using PascalCase. A component registered as `MyComponent` can be referenced in templates as both `<MyComponent>` and `<my-component>`, allowing the same JavaScript registration code to work regardless of template source.
PascalCase tags not usable in in-DOM templates
PascalCase component tags cannot be used directly in in-DOM templates due to HTML parsing caveats. Use kebab-case tags instead (e.g., `<my-component>` instead of `<MyComponent>`) when working with in-DOM templates.
defineModel() under the hood implementation
defineModel() is a convenience macro that the compiler expands to a prop named modelValue and an event named update:modelValue. The parent component's v-model="foo" is compiled to :modelValue="foo" @update:modelValue="$event => (foo = $event)" on the child component.
defineModel() macro for v-model binding
Starting in Vue 3.4, the recommended approach to implement two-way binding on a component is using the defineModel() macro in the Composition API. The macro returns a ref whose .value is synced with the value bound by the parent v-model. When the child mutates the ref, it causes the parent bound value to be updated. The value returned by defineModel() can be accessed and mutated like any other ref.
defineModel() with prop options
Prop options can be passed to defineModel() by passing an options object. For example: const model = defineModel({ required: true }) makes the v-model required, or const model = defineModel({ default: 0 }) provides a default value.
defineModel() default value desynchronization pitfall
If defineModel() has a default value and the parent component does not provide a value for the prop, it can cause desynchronization between parent and child. The parent value will be undefined while the child value will be the default. Additionally, default values for mutable reference types like arrays or objects should be wrapped in functions to avoid accidental modification and external side effects when using withDefaults.
Component v-model implementation via props and emits
In Options API prior to Vue 3.4, v-model on a component can be implemented by declaring a modelValue prop and emitting an update:modelValue event. The child component binds the value attribute of a native input element to the modelValue prop and emits update:modelValue with the new value when an input event is triggered.
Component v-model using writable computed property
In Options API, v-model can be implemented using a writable computed property with a getter and setter. The getter returns the modelValue prop and the setter emits the update:modelValue event. The computed property can then be bound to a native input with v-model.
v-model argument binding on components
v-model on a component can accept an argument to support multiple bindings. For example, v-model:title="bookTitle" instead of the default v-model. In the Composition API with Vue 3.4+, this is supported by passing a string to defineModel() as its first argument: const title = defineModel('title').
Multiple v-model bindings on single component
Multiple v-model bindings can be created on a single component instance by using different arguments. For example, v-model:first-name="first" v-model:last-name="last". Each v-model will sync to a different prop without needing extra options in the component.
v-model custom modifiers in Composition API
In Composition API with Vue 3.4+, modifiers added to a component v-model can be accessed by destructuring the defineModel() return value: const [model, modifiers] = defineModel(). The modifiers object contains the applied modifiers as properties. To conditionally adjust how the value should be read or written based on modifiers, pass get and set options to defineModel() that receive and return transformed values.
v-model custom modifiers in Options API
In Options API, modifiers added to a component v-model are provided via a modelModifiers prop that defaults to an empty object. The component can check the modelModifiers object keys and write a handler to change the emitted value based on the modifiers. For example, if v-model.capitalize="myText" is used, this.modelModifiers will contain { capitalize: true }.
v-model modifiers with arguments naming convention
For v-model bindings with both an argument and modifiers, the generated prop name for modifiers will be arg + 'Modifiers'. For example, v-model:title.capitalize="myText" results in a titleModifiers prop containing { capitalize: true }.
Multiple v-model with different modifiers
Multiple v-model bindings with different arguments and modifiers can be used on the same component. For example: v-model:first-name.capitalize="first" v-model:last-name.uppercase="last". In Composition API with Vue 3.4+, destructure each with defineModel: const [firstName, firstNameModifiers] = defineModel('firstName') and const [lastName, lastNameModifiers] = defineModel('lastName').
v-model wrapping native input elements
A component that wraps native input elements with v-model can bind the defineModel() ref directly to the native input using v-model. This makes it straightforward to wrap native input elements while providing the same v-model usage: const model = defineModel() then <input v-model="model" />.