vuejs 100 Q&As

Vue.js FAQ & Answers

100 expert Vue.js answers researched from official documentation. Every answer cites authoritative sources you can verify.

Composition API

35 questions
A

The correct syntax for defining a ref in Vue 3 Composition API:

import { ref } from 'vue'

const count = ref(0)

Key points:

  1. Import: import { ref } from 'vue'
  2. Create: const variableName = ref(initialValue)
  3. Access in JavaScript: Use .value property → count.value
  4. Access in template: Refs are automatically unwrapped → {{ count }} (no .value needed)

Type signature:

function ref<T>(value: T): Ref<UnwrapRef<T>>

Complete example:

<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++  // Use .value in JavaScript
}
</script>

<template>
  <button @click="increment">
    {{ count }}  <!-- No .value needed in template -->
  </button>
</template>

Important characteristics:

  • The ref object is mutable and reactive
  • Mutating .value triggers reactive updates
  • Objects assigned to refs are deeply reactive (use shallowRef() to avoid)
  • Nested refs are automatically unwrapped

Sources:

99% confidence
A

The correct syntax for defining a reactive object in Vue 3 Composition API is:

import { reactive } from 'vue'

const state = reactive({ count: 0 })

Key points:

  1. Import the reactive function from 'vue'
  2. Pass an object to reactive() - it only accepts objects, not primitives
  3. Deep reactivity - all nested properties are automatically reactive
  4. Returns a Proxy - not the original object, so always use the returned proxy

Complete component example:

<script setup>
import { reactive } from 'vue'

const state = reactive({ 
  count: 0,
  user: {
    name: 'John'
  }
})

function increment() {
  state.count++
}
</script>

<template>
  <button @click="increment">{{ state.count }}</button>
</template>

Type signature:

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

Important: Use the reactive proxy exclusively, not the original object. The returned proxy uses ES Proxy and makes all nested properties reactive automatically.

Sources:

99% confidence
A

In Vue 3 Composition API with <script setup>, you access the value of a ref using the .value property.

import { ref } from 'vue'

const count = ref(0)

// Access the value
console.log(count.value) // 0

// Modify the value
count.value++
console.log(count.value) // 1

The .value property is required in JavaScript/TypeScript code to access or mutate the ref's value. This allows Vue's reactivity system to track when the ref is accessed or changed.

Note: In templates, refs are automatically unwrapped, so you use {{ count }} instead of {{ count.value }}.

Sources:

99% confidence
A

No, you do not need to use .value to access a ref in Vue 3 templates.

Refs are automatically unwrapped when used inside templates, so you can reference them directly:

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <!-- No .value needed in template -->
  <div>{{ count }}</div>
  <button @click="count++">Increment</button>
</template>

Important caveat: Auto-unwrapping only applies to top-level refs in the template render context. If a ref is nested inside an object or reactive array, you would need .value:

<template>
  <!-- Auto-unwrapped (top-level) -->
  <div>{{ count }}</div>
  
  <!-- NOT auto-unwrapped (nested in object) -->
  <div>{{ object.count.value }}</div>
</template>

In JavaScript code (inside <script>), you always need .value to access or modify the ref.

Sources:

99% confidence
A

In Vue 3 Composition API, use the computed() function imported from 'vue':

import { computed } from 'vue'

// Read-only computed property
const doubleCount = computed(() => count.value * 2)

The computed() function accepts a getter function and returns a computed ref. Access the value using .value in JavaScript (auto-unwrapped in templates).

Writable computed (when you need both getter and setter):

const fullName = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

Key rules:

  • Computed properties are cached based on reactive dependencies
  • Getter functions must be pure (no side effects, mutations, or async operations)
  • Treat computed values as read-only; modify source state to trigger updates

Sources:

99% confidence
A

In Vue 3 Composition API, define a computed property with both getter and setter by passing an object with get() and set() methods to the computed() function:

import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

When you assign a value to fullName.value = 'Jane Smith', the setter is invoked and updates the underlying refs accordingly.

Key points:

  • Import computed from 'vue'
  • Pass an object (not a function) to computed()
  • Define get() method for reading the value
  • Define set(newValue) method for writing the value
  • Access with .value property like any ref

Sources:

99% confidence
A

The correct syntax for watching a single ref in Vue 3 Composition API:

import { ref, watch } from 'vue'

const myRef = ref('')

watch(myRef, (newValue, oldValue) => {
  // callback runs when myRef changes
})

Key points:

  • Pass the ref directly as the first argument (not myRef.value)
  • The callback receives newValue and oldValue as parameters
  • The callback triggers automatically when the ref's value changes
  • Returns a stop function to stop watching: const stop = watch(...)

Optional third parameter for configuration:

watch(myRef, (newValue, oldValue) => {
  // callback
}, {
  immediate: true,  // run immediately on creation
  deep: true,       // deep watch for objects
  flush: 'post'     // timing: 'pre' | 'post' | 'sync'
})

Sources:

99% confidence
A

To watch multiple sources in Vue 3 Composition API, pass an array of sources as the first argument to watch(). The callback receives arrays of new and old values corresponding to each source.

Syntax:

watch([source1, source2, ...], ([newVal1, newVal2, ...], [oldVal1, oldVal2, ...]) => {
  // callback logic
})

Complete Example:

import { ref, watch } from 'vue'

const x = ref(0)
const y = ref(0)

watch([x, () => y.value], ([newX, newY], [prevX, prevY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
  console.log(`previous: x was ${prevX}, y was ${prevY}`)
})

Key Points:

  • The first argument is an array of watch sources (refs, reactive objects, or getter functions)
  • The callback's first parameter is an array of new values
  • The callback's second parameter is an array of old values
  • Values correspond to sources by index position
  • The watcher triggers when any source changes

Type Signature:

function watch<T>(
  sources: WatchSource<T>[],
  callback: WatchCallback<T[]>,
  options?: WatchOptions
): WatchHandle

Sources:

99% confidence
A

watch requires you to explicitly specify what to track and only runs when that source changes. watchEffect automatically tracks all reactive dependencies accessed during execution and runs immediately on creation.

Key Differences:

1. Dependency Declaration:

  • watch: Explicit - you must specify what to watch
  • watchEffect: Implicit - automatically tracks reactive properties accessed during execution

2. Execution Timing:

  • watch: Lazy by default - only runs when the watched source changes (unless { immediate: true })
  • watchEffect: Eager - runs immediately on creation and re-runs when dependencies change

3. Access to Previous Values:

  • watch: Yes - callback receives both new and old values
  • watchEffect: No - only has access to current values

4. Control:

  • watch: More precise control over when the callback fires
  • watchEffect: Less control - fires whenever any tracked dependency changes

Code Example:

// watch - explicit source, lazy execution
watch(todoId, async (newId, oldId) => {
  const response = await fetch(`https://api.example.com/todos/${newId}`)
  data.value = await response.json()
})

// watchEffect - automatic tracking, immediate execution
watchEffect(async () => {
  const response = await fetch(`https://api.example.com/todos/${todoId.value}`)
  data.value = await response.json()
})

When to use which:

  • Use watch when you need the previous value or want lazy execution
  • Use watchEffect for convenience when tracking multiple dependencies automatically

Sources:

99% confidence
A

Both watch() and watchEffect() return a stop function that you call to stop the watcher.

const stop = watchEffect(() => {
  // watcher logic
})

// later, when no longer needed
stop()

This works identically for watch():

const stop = watch(source, (newValue, oldValue) => {
  // watcher logic
})

// stop the watcher
stop()

Important: Watchers created synchronously inside setup() or <script setup> are automatically stopped when the component unmounts. Manual stopping is only necessary when:

  • Creating watchers asynchronously (e.g., in a callback or setTimeout)
  • You need to stop the watcher before component unmount

Vue 3.5+ Enhanced API:

const { stop, pause, resume } = watchEffect(() => {})

pause()  // temporarily pause
resume() // resume watching
stop()   // stop permanently

Sources:

99% confidence
A

The correct syntax for onMounted in Vue 3 Composition API:

import { onMounted } from 'vue'

onMounted(() => {
  // callback code here
})

Key Points:

  • Import onMounted from 'vue'
  • Call it with a callback function that executes after the component is mounted
  • Must be called synchronously during component setup (typically in <script setup> or inside setup())
  • The callback receives no arguments
  • Hook is called after all synchronous child components are mounted and the component's DOM tree is inserted into the parent container
  • Not called during server-side rendering

Example:

<script setup>
import { ref, onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // Access DOM elements
  console.log('Component is mounted!')
})
</script>

<template>
  <div ref="el"></div>
</template>

Sources:

99% confidence
A

The correct syntax for onBeforeMount in Vue 3 Composition API is:

import { onBeforeMount } from 'vue'

onBeforeMount(() => {
  // your code here
})

Function signature:

function onBeforeMount(callback: () => void): void

Usage in <script setup>:

<script setup>
import { onBeforeMount } from 'vue'

onBeforeMount(() => {
  console.log('Component is about to mount')
  // Perform initialization before DOM is created
})
</script>

Usage in setup() function:

import { onBeforeMount } from 'vue'

export default {
  setup() {
    onBeforeMount(() => {
      console.log('Component is about to mount')
    })
  }
}

Key characteristics:

  • Must be imported from 'vue'
  • Takes a callback function that executes before the component mounts
  • Called after reactive state setup but before DOM creation
  • Not called during server-side rendering
  • Must be called synchronously during setup()

Sources:

99% confidence
A

The correct syntax for onUpdated in Vue 3 Composition API:

import { onUpdated } from 'vue'

onUpdated(() => {
  // callback executed after component updates its DOM
})

Full example:

<script setup>
import { ref, onUpdated } from 'vue'

const count = ref(0)

onUpdated(() => {
  // text content should be the same as current `count.value`
  console.log(document.getElementById('count').textContent)
})
</script>

<template>
  <button id="count" @click="count++">{{ count }}</button>
</template>

Type signature:

function onUpdated(callback: () => void): void

Important:

  • Must be called synchronously during component setup
  • Called after any DOM update caused by reactive state changes
  • Parent component's onUpdated runs after child components
  • Do not modify state inside onUpdated or you'll create an infinite loop

Sources:

99% confidence
A
import { onBeforeUpdate } from 'vue'

onBeforeUpdate(() => {
  // callback executed before component updates its DOM
})

Syntax:

  • Import onBeforeUpdate from 'vue'
  • Call it with a callback function that takes no arguments
  • The callback executes right before the component updates its DOM tree due to a reactive state change
  • Safe to modify component state inside this hook
  • Not called during server-side rendering

Type Signature:

function onBeforeUpdate(callback: () => void): void

Example in <script setup>:

<script setup>
import { onBeforeUpdate } from 'vue'

onBeforeUpdate(() => {
  // Access DOM state before Vue updates it
  console.log('Component is about to update')
})
</script>

Sources:

99% confidence
A

The correct syntax for onUnmounted in Vue 3 Composition API:

import { onUnmounted } from 'vue'

onUnmounted(() => {
  // cleanup code here
})

Type signature:

function onUnmounted(callback: () => void): void

Key points:

  • Import from 'vue'
  • Takes a callback function with no parameters
  • Callback executes after component unmount and all child components are unmounted
  • Commonly used for cleanup: clearing timers, removing event listeners, closing connections

Example:

import { onMounted, onUnmounted } from 'vue'

let intervalId

onMounted(() => {
  intervalId = setInterval(() => {
    // do something
  }, 1000)
})

onUnmounted(() => {
  clearInterval(intervalId)
})

Note: This hook is not called during server-side rendering.

Sources:

99% confidence
A

The correct syntax for onBeforeUnmount in Vue 3 Composition API:

import { onBeforeUnmount } from 'vue'

onBeforeUnmount(() => {
  // cleanup logic here
})

Type signature:

function onBeforeUnmount(callback: () => void): void

Key characteristics:

  • Called right before a component instance is unmounted
  • The component instance is still fully functional when this hook runs
  • Not called during server-side rendering
  • Used for cleanup operations that need to happen before unmounting

Usage example:

<script setup>
import { onBeforeUnmount } from 'vue'

onBeforeUnmount(() => {
  console.log('Component is about to unmount')
  // Clean up resources, cancel timers, etc.
})
</script>

Sources:

99% confidence
A

<script setup> is compile-time syntactic sugar for using Composition API in Single File Components and is the recommended syntax for Vue 3 SFCs.

Key Differences

1. Syntax & Boilerplate

  • <script setup>: Top-level bindings (imports, variables, functions) are automatically exposed to the template. No return statement needed.
  • setup(): Must explicitly return an object containing all properties/methods you want to expose to the template.
<!-- script setup -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() { count.value++ }
</script>

<!-- regular setup() -->
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    function increment() { count.value++ }
    return { count, increment } // Must explicitly return
  }
}
</script>

2. Performance

  • <script setup>: Template is compiled into a render function in the same scope, without an intermediate proxy. Better runtime performance.
  • setup(): Uses an intermediate proxy for returned properties.

3. TypeScript Support

  • <script setup>: Can declare props and emits using pure TypeScript. Better IDE type-inference performance (less work for the language server).
  • setup(): Requires more verbose type annotations.

4. Execution Timing

  • <script setup>: Code executes every time a component instance is created (compiled as the content of the setup() function).
  • Regular <script>: Executes once when the module is first imported.

5. Props & Emits Declaration

  • <script setup>: Use compiler macros defineProps() and defineEmits() (hoisted to module scope).
  • setup(): Receives props as first argument, context (with emit) as second argument.

6. Limitations

  • <script setup>: Cannot use src attribute, doesn't support in-DOM root component templates.
  • setup(): Must return synchronously (except when component is descendant of Suspense).

When to Use Each

  • <script setup>: Recommended for all new SFC code using Composition API.
  • setup(): Only when integrating Composition API into existing Options API codebases or when not using build tools.

Sources:

99% confidence
A

In Vue 3's <script setup>, use defineProps() to define props. It's a compiler macro that doesn't need to be imported.

Two ways to define props:

  1. Runtime declaration (using object syntax):
<script setup>
const props = defineProps({
  title: String,
  count: Number,
  isActive: Boolean
})
</script>
  1. Type-based declaration (TypeScript):
<script setup lang="ts">
const props = defineProps<{
  title: string
  count?: number
  isActive: boolean
}>()
</script>

Important: You cannot use both runtime and type-based declarations together - choose one approach.

Default values with type-based props:

For Vue 3.5+:

<script setup lang="ts">
const { title = 'Default Title', count = 0 } = defineProps<{
  title?: string
  count?: number
}>()
</script>

For Vue 3.4 and below, use withDefaults:

<script setup lang="ts">
const props = withDefaults(defineProps<{
  title?: string
  count?: number
}>(), {
  title: 'Default Title',
  count: 0
})
</script>

Sources:

99% confidence
A

In Vue 3 <script setup>, use the defineEmits() compiler macro to declare component events.

Basic syntax (array declaration):

<script setup>
const emit = defineEmits(['update', 'delete'])

emit('update', payload)
</script>

Object syntax with validation:

<script setup>
const emit = defineEmits({
  update: (id) => {
    return typeof id === 'number'
  }
})
</script>

TypeScript type-based declaration:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'update', id: number): void
  (e: 'delete', id: number): void
}>()
</script>

TypeScript alternative syntax (Vue 3.3+):

<script setup lang="ts">
const emit = defineEmits<{
  update: [id: number]
  delete: [id: number]
}>()
</script>

Key points:

  • defineEmits() is a compiler macro—no import needed
  • Returns an emit function equivalent to $emit in templates
  • Accepts the same values as the Options API emits option
  • Cannot mix runtime and type-based declarations
  • Options are hoisted and cannot reference local variables

Sources:

99% confidence
A

Use the useAttrs() helper function imported from 'vue':

<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

The attrs object contains all fallthrough attributes (attributes passed to the component that are not declared as props or emits). Note that:

  • The attrs object always reflects the latest fallthrough attributes but is not reactive (for performance reasons)
  • You cannot watch it for changes - use a prop if you need reactivity, or use onUpdated() to perform side effects with the latest attrs
  • In templates, you can access attrs directly as $attrs without needing useAttrs()

Sources:

99% confidence
A

In <script setup>, access slots using the useSlots() helper function:

<script setup>
import { useSlots } from 'vue'

const slots = useSlots()

// Access specific slots
const hasDefaultSlot = !!slots.default
const hasHeaderSlot = !!slots.header
</script>

useSlots() returns an object containing all slot functions, equivalent to setupContext.slots in the regular Composition API setup() function.

Note: In templates, you can access slots directly as $slots without importing anything. The useSlots() function is primarily for programmatic slot access within <script setup> logic, though this use case is relatively rare.

For TypeScript users in Vue 3.3+, use defineSlots() for type-safe slot declarations instead.

Sources:

99% confidence
A

defineExpose explicitly exposes properties and methods from a <script setup> component to parent components via template refs.

Components using <script setup> are closed by default — the public instance retrieved via template refs does not expose any bindings declared inside <script setup>. To make specific properties or methods accessible to parent components, use defineExpose:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

When a parent gets a template ref to this component, the retrieved instance will be { a: number, b: number } (refs are automatically unwrapped).

Key Points:

  • defineExpose is a compiler macro (no import needed)
  • Without it, <script setup> components expose nothing to parent components
  • Must be called before any await expressions
  • Only properties passed to defineExpose will be accessible from outside the component

Sources:

99% confidence
A

In Vue 3 Composition API, create a template ref by declaring a ref with ref(null) and matching its variable name to the template ref attribute value:

<script setup>
import { ref, onMounted } from 'vue'

const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

Vue 3.5+ introduced useTemplateRef() which decouples the ref name from the variable name:

<script setup>
import { useTemplateRef, onMounted } from 'vue'

const input = useTemplateRef('my-input')

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="my-input" />
</template>

Key requirements:

  • Template refs are null until the component mounts
  • Access them in onMounted() or later lifecycle hooks
  • With v-for, the ref value becomes an array of elements

Sources:

99% confidence
A

The immediate option in Vue 3 watch triggers the callback immediately on watcher creation. Its default value is false.

When set to true, the watcher callback executes immediately when the watcher is created, before any changes occur to the watched source. On this first immediate call, the oldValue parameter will be undefined since there is no previous value.

Example:

watch(source, (newValue, oldValue) => {
  // This runs immediately when immediate: true
}, { immediate: true })

Sources:

99% confidence
A

The deep option in Vue 3's watch forces the watcher to traverse all nested properties in an object, triggering the callback when any nested property changes (not just when the object reference changes).

Default behavior without deep:

// Only triggers when state.someObject is reassigned
watch(() => state.someObject, (newValue, oldValue) => {
  // Won't fire if you do: state.someObject.nestedProp = 'new value'
})

With deep: true:

// Options API
watch: {
  someObject: {
    handler(newValue, oldValue) {
      // Fires on nested mutations like someObject.nested.prop = 'changed'
    },
    deep: true
  }
}

// Composition API
watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // Fires on any nested property change
  },
  { deep: true }
)

Vue 3.5+ numeric depth:

watch(source, callback, { deep: 3 }) // Traverses max 3 levels deep

When to use it:

  • When you need to react to mutations inside nested objects/arrays
  • When the data structure's shape matters, not just the reference

When NOT to use it:

  • On large data structures (performance cost)
  • When you can watch specific nested properties with a getter instead:
    watch(() => state.someObject.specific.property, callback) // More efficient
    

Important: When watching a reactive object directly (not via getter), deep watching is implicit:

const obj = reactive({ nested: { count: 0 } })
watch(obj, callback) // Already deep, no need for { deep: true }

Sources:

99% confidence
A

The flush option in Vue 3 watch() controls the timing of when the watcher callback executes relative to Vue's component rendering cycle.

Possible Values:

  1. 'pre' (default) - The watcher runs just prior to component rendering
  2. 'post' - Defers the watcher until after component rendering (use this when you need to access updated DOM)
  3. 'sync' - Triggers the watcher immediately when a reactive dependency changes (use with caution due to performance and data consistency concerns)

Example:

watch(source, callback, {
  flush: 'post'
})

Convenience Aliases:

Vue provides convenience functions that set the flush option:

  • watchPostEffect() - equivalent to flush: 'post'
  • watchSyncEffect() - equivalent to flush: 'sync'

Sources:

99% confidence
A

To watch a nested property in a reactive object in Vue 3, use one of these three methods:

Method 1: Getter Function (Recommended for specific nested properties)

import { watch } from 'vue'

const state = reactive({ user: { name: 'John' } })

watch(
  () => state.user.name,
  (newName, oldName) => {
    console.log(`Name changed from ${oldName} to ${newName}`)
  }
)

Method 2: Deep Watcher (For all nested properties)

import { watch, reactive } from 'vue'

const state = reactive({ user: { name: 'John', age: 30 } })

watch(
  () => state.user,
  (newValue, oldValue) => {
    // fires on any nested property mutation
    // Note: newValue === oldValue (same object reference)
  },
  { deep: true }
)

Method 3: Options API Path Notation

export default {
  data() {
    return {
      user: { profile: { name: 'John' } }
    }
  },
  watch: {
    'user.profile.name'(newValue, oldValue) {
      console.log('Name changed')
    }
  }
}

Key Points:

  • Use getter syntax () => obj.nested.prop for specific nested properties
  • Add { deep: true } to watch all nested changes (can be expensive on large objects)
  • Options API supports dot-delimited paths like 'some.nested.key'
  • In Vue 3.5+, deep can be a number indicating max traversal depth

Sources:

99% confidence
A

The onErrorCaptured lifecycle hook captures errors propagating from descendant components. It allows parent components to intercept and handle errors from their child component tree.

Function Signature:

function onErrorCaptured(callback: ErrorCapturedHook): void

type ErrorCapturedHook = (
  err: unknown,
  instance: ComponentPublicInstance | null,
  info: string
) => boolean | void

Parameters:

  • err: The error object that was captured
  • instance: The component instance that triggered the error
  • info: A string specifying the error source type (e.g., "render", "setup", "watcher")

Captures errors from:

  • Component renders
  • Event handlers
  • Lifecycle hooks
  • setup() functions
  • Watchers
  • Custom directive hooks
  • Transition hooks

Error Propagation:

  • Errors bubble up from child to parent (bottom-to-top), similar to DOM events
  • Multiple onErrorCaptured hooks in the parent chain all execute
  • Return false to stop propagation and prevent further error handlers from being invoked
  • By default, all errors still reach app.config.errorHandler unless propagation is stopped

Common Pattern:

onErrorCaptured((err, instance, info) => {
  // Display error state to user
  errorMessage.value = err.message;
  // Return false to stop propagation
  return false;
});

Warning: Don't render the original content that caused the error in your error state, or you'll create an infinite render loop.

Production Note: In production builds, the info parameter becomes a shortened error code. See the Production Error Code Reference for mappings.

Sources:

99% confidence
A

onRenderTracked is a development-mode-only debug hook that is called when a reactive dependency is tracked by the component's render effect.

Purpose: It helps developers debug and understand which reactive properties are being accessed during component rendering.

When it fires: Every time the component's render function reads a reactive property, onRenderTracked is triggered with details about that dependency.

Type signature:

function onRenderTracked(callback: DebuggerHook): void

type DebuggerEvent = {
  effect: ReactiveEffect
  target: object
  type: TrackOpTypes /* 'get' | 'has' | 'iterate' */
  key: any
}

Common usage pattern:

import { onRenderTracked } from 'vue'

onRenderTracked((event) => {
  debugger // Inspect which dependency was tracked
  console.log(event.target, event.key, event.type)
})

Key characteristics:

  • Only runs in development mode (not production)
  • Not called during server-side rendering
  • Fires during initial render and whenever new dependencies are tracked
  • Used alongside onRenderTriggered for comprehensive reactivity debugging

Sources:

99% confidence
A

The onRenderTriggered lifecycle hook is a development-mode debugging tool that is called when a reactive dependency triggers the component's render effect to re-run.

Purpose

It allows you to debug and track which specific reactive state changes are causing your component to re-render. When the hook fires, it provides a debugger event containing detailed information about what triggered the update.

The Hook Receives

A DebuggerEvent object containing:

  • effect: The reactive effect that was triggered
  • target: The reactive object that was modified
  • type: The operation type ('set', 'add', 'delete', or 'clear')
  • key: The property key that was modified
  • newValue: The new value (if applicable)
  • oldValue: The previous value (if applicable)
  • oldTarget: The previous Map/Set (if applicable)

Example Usage

import { onRenderTriggered } from 'vue'

onRenderTriggered((event) => {
  console.log('Render triggered by:', event.type, event.key)
  console.log('Target:', event.target)
  console.log('New value:', event.newValue)
  debugger
})

Important Notes

  • Development-only: This hook is not called in production builds
  • Not called during SSR: Only works in client-side rendering
  • Used primarily for performance debugging and understanding reactivity flow

Sources:

99% confidence
A

The onActivated lifecycle hook is used for components cached by <KeepAlive>. It registers a callback that executes when the component instance is inserted into the DOM from the cache.

When it fires:

  • On initial mount of a KeepAlive-wrapped component
  • Every time the component is re-inserted from the cache (becomes active again)

Signature:

function onActivated(callback: () => void): void

Example usage:

<script setup>
import { onActivated } from 'vue'

onActivated(() => {
  // called on initial mount
  // and every time it is re-inserted from the cache
  console.log('Component is now active')
})
</script>

Key points:

  • Works with both the root component and descendant components in the cached tree
  • Not called during server-side rendering
  • Commonly paired with onDeactivated for cleanup when component is cached

Sources:

99% confidence
A

The onDeactivated lifecycle hook is called when a component wrapped in <KeepAlive> is removed from the DOM and placed into the cache. It's also called when the component is unmounted.

Primary Use Cases:

  • Pause or stop ongoing operations (timers, animations, video playback)
  • Suspend resource-intensive operations when component becomes inactive
  • Clean up temporary resources while preserving component state

Example:

<script setup>
import { onActivated, onDeactivated } from 'vue'

onDeactivated(() => {
  // Called when removed from DOM into cache
  // Pause timers, stop animations, suspend subscriptions
})

onActivated(() => {
  // Called when re-inserted from cache
  // Resume operations
})
</script>

Key Distinction: Unlike standard unmount hooks, onDeactivated fires when cached components are hidden (not destroyed), allowing you to pause rather than fully tear down component functionality.

Note: Not called during server-side rendering.

Sources:

99% confidence
A

Use withDefaults() to wrap defineProps() with a type parameter, passing an object with default values as the second argument:

interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

Key rules:

  • First argument: defineProps<YourInterface>()
  • Second argument: object literal with default values
  • Arrays and objects must be wrapped in functions to avoid shared references
  • Properties with defaults have their optional flags removed in the resulting type

Complete example:

<script setup lang="ts">
interface Props {
  title?: string
  count?: number
  tags?: string[]
  config?: { theme: string }
}

const props = withDefaults(defineProps<Props>(), {
  title: 'Default Title',
  count: 0,
  tags: () => [],
  config: () => ({ theme: 'light' })
})
</script>

Sources:

99% confidence
A

watchEffect returns a WatchHandle object that is callable as a function and provides methods to control the watcher's lifecycle.

Return Type:

interface WatchHandle {
  (): void // callable, same as `stop`
  pause: () => void
  resume: () => void
  stop: () => void
}

How to use it:

  1. Stop the watcher - Call the returned handle as a function or use .stop():
const stopHandle = watchEffect(() => {
  console.log(count.value)
})

// Later, permanently stop the watcher
stopHandle()
// or
stopHandle.stop()
  1. Pause and resume - Temporarily pause watcher execution:
const handle = watchEffect(() => {
  console.log(count.value)
})

handle.pause()  // Temporarily stop watching
handle.resume() // Resume watching
  1. Automatic cleanup - In components, watchers are automatically stopped when the component unmounts, but manual cleanup is useful for watchers created in composables or outside component lifecycle.

Sources:

99% confidence
A

Yes, you can use multiple onMounted hooks in the same component in Vue 3. All registered callbacks will be executed in the order they were registered when the component mounts.

This is a key feature of Vue 3's Composition API that enables better code organization, especially when using composables:

<script setup>
import { onMounted } from 'vue'

// First onMounted hook
onMounted(() => {
  console.log('First mounted callback')
})

// Second onMounted hook
onMounted(() => {
  console.log('Second mounted callback')
})

// Using a composable that also calls onMounted
useMouse() // This composable may have its own onMounted hook
</script>

Key requirements:

  • All onMounted calls must be registered synchronously during component setup
  • onMounted() can be called in external functions (like composables) as long as the call stack is synchronous and originates from within setup()
  • Each call registers an additional callback rather than replacing the previous one

This pattern is particularly useful when using multiple composables that each need to perform setup work when the component mounts.

Sources:

99% confidence

Components

34 questions
A

In Vue 3 Options API, define props with type checking using the props option with object syntax:

Basic type checking:

export default {
  props: {
    title: String,
    count: Number,
    isActive: Boolean
  }
}

Full validation syntax:

export default {
  props: {
    // Single type
    age: Number,
    
    // Multiple possible types
    id: [String, Number],
    
    // Required with type
    name: {
      type: String,
      required: true
    },
    
    // With default value
    count: {
      type: Number,
      default: 0
    },
    
    // Object/Array defaults use factory function
    settings: {
      type: Object,
      default() {
        return { theme: 'dark' }
      }
    },
    
    // Custom validator
    status: {
      type: String,
      validator(value) {
        return ['active', 'pending', 'inactive'].includes(value)
      }
    }
  }
}

Available types: String, Number, Boolean, Array, Object, Date, Function, Symbol, Error, custom constructor functions, or arrays of these.

Vue validates props in development mode and warns in the console when types don't match.

For TypeScript: Wrap component with defineComponent() for type inference, or use PropType utility for complex types:

import { defineComponent, type PropType } from 'vue'

export default defineComponent({
  props: {
    user: {
      type: Object as PropType<{ name: string; age: number }>,
      required: true
    }
  }
})

Sources:

99% confidence
A

Use defineProps() with a TypeScript generic type parameter:

<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

Alternative: Runtime Declaration

<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})
</script>

With Interface:

<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

With Default Values:

<script setup lang="ts">
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']  // wrap arrays/objects in functions
})
</script>

Key Points:

  • Type-based declaration (generic syntax) is the recommended approach
  • Optional props use ? in the type definition
  • Default values require the withDefaults() macro with type-based declaration
  • Complex reference types (arrays, objects) should be wrapped in functions when providing defaults

Sources:

99% confidence
A

In Vue 3, define required props by setting required: true in the prop definition object.

Options API:

export default {
  props: {
    propName: {
      type: String,
      required: true
    }
  }
}

Composition API (<script setup>):

<script setup>
defineProps({
  propName: {
    type: String,
    required: true
  }
})
</script>

All props are optional by default unless required: true is specified. An absent required prop will trigger a warning in the browser's JavaScript console.

You can combine required with other validations:

propName: {
  type: [String, Number],
  required: true,
  validator: (value) => value.length > 0
}

Sources:

99% confidence
A

In Vue 3, you define props with validator functions by adding a validator property to the prop configuration. The validator receives the prop value (and optionally the full props object in Vue 3.4+) and returns a boolean.

Syntax:

defineProps({
  status: {
    type: String,
    validator(value, props) {
      // return true if valid, false if invalid
      return ['success', 'warning', 'danger'].includes(value)
    }
  }
})

Key Details:

  • The validator function receives two parameters:
    • value - the prop value being validated
    • props - full props object (available in Vue 3.4+)
  • Must return true for valid, false for invalid
  • Props are validated before the component instance is created, so you cannot access data, computed, methods, or other instance properties inside validators
  • Failed validation produces a console warning in development builds

Example with Options API:

export default {
  props: {
    status: {
      type: String,
      validator(value) {
        return ['success', 'warning', 'danger'].includes(value)
      }
    }
  }
}

Sources:

99% confidence
A

In Vue 3 Composition API, use defineEmits() to declare and emit events.

With <script setup> (recommended):

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>

With explicit setup() function:

export default {
  emits: ['inFocus', 'submit'],
  setup(props, { emit }) {
    emit('submit')
  }
}

Emitting with arguments:

emit('submit', { email: '[email protected]', password: '123' })

TypeScript type-safe syntax:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

Important: defineEmits() must be called directly within <script setup>, not inside functions.

Sources:

99% confidence
A

In Vue 3, validate emitted events by using the object syntax for the emits option instead of the array syntax. Assign each event a validator function that receives the emitted arguments and returns true (valid) or false (invalid).

Composition API (<script setup>):

const emit = defineEmits({
  // No validation
  click: null,

  // Validate submit event
  submit: ({ email, password }) => {
    if (email && password) {
      return true
    } else {
      console.warn('Invalid submit event payload!')
      return false
    }
  }
})

function submitForm(email, password) {
  emit('submit', { email, password })
}

Options API:

export default {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}

The validator function executes at runtime when the event is emitted. Returning false does not prevent the event from being emitted but allows you to log warnings or perform validation logic.

Sources:

99% confidence
A

In Vue 3, named slots are defined using the v-slot directive on a <template> element:

Full syntax:

<template v-slot:slotName>
  <!-- slot content -->
</template>

Shorthand syntax (recommended):

<template #slotName>
  <!-- slot content -->
</template>

Complete example:

<BaseLayout>
  <template #header>
    <h1>Page Title</h1>
  </template>

  <template #default>
    <p>Main content</p>
  </template>

  <template #footer>
    <p>Footer content</p>
  </template>
</BaseLayout>

Dynamic slot names:

<template #[dynamicSlotName]>
  <!-- content -->
</template>

The # symbol is the shorthand for v-slot:, just as @ is shorthand for v-on: and : is shorthand for v-bind:.

Sources:

99% confidence
A

In Vue 3, scoped slots use the v-slot directive (shorthand #) to receive props from child components.

Default scoped slot:

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

With destructuring:

<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

Named scoped slots:

<MyComponent>
  <template #header="headerProps">
    {{ headerProps.title }}
  </template>
  
  <template #default="{ text }">
    {{ text }}
  </template>
</MyComponent>

Defining in child component:

<template>
  <slot :text="message" :count="1"></slot>
  <slot name="header" :title="headerTitle"></slot>
</template>

Key syntax rules:

  • Use v-slot="propName" or shorthand #default="propName" for default slot
  • Use v-slot:slotName="propName" or shorthand #slotName="propName" for named slots
  • v-slot must be used on <template> tags (except when component has only default slot)
  • Destructuring works: v-slot="{ prop1, prop2 }"

Sources:

99% confidence
A

In Vue 3 render functions, access the default slot content by calling slots.default() (Composition API) or this.$slots.default() (Options API). Each slot is a function that returns an array of vnodes.

Composition API:

export default {
  setup(props, { slots }) {
    return () => h('div', slots.default())
  }
}

Options API:

export default {
  render() {
    return h('div', this.$slots.default())
  }
}

With JSX:

// Composition API
export default {
  setup(props, { slots }) {
    return () => <div>{slots.default()}</div>
  }
}

// Options API
export default {
  render() {
    return <div>{this.$slots.default()}</div>
  }
}

The slots object is accessed from the setup context in Composition API, while in Options API it's available as this.$slots.

Sources:

99% confidence
A

The purpose of provide and inject in Vue 3 is to solve prop drilling — passing data through multiple intermediate components that don't need it just to reach a deeply nested child component.

How it works:

  • A parent component uses provide() to make data/dependencies available to all its descendants
  • Any descendant component (regardless of depth) can use inject() to access the provided values directly
  • Intermediate components don't need to know about or pass along this data

Key benefits:

  • Eliminates passing props through components that don't use them
  • Creates a dependency injection pattern for component trees
  • Maintains reactivity when providing refs (injected values stay reactive)
  • Works at app-level (app.provide()) to make data available to all components

Example:

// Parent component (provider)
import { provide, ref } from 'vue'

const message = ref('Hello')
provide('message', message)

// Descendant component (injector)
import { inject } from 'vue'

const message = inject('message')

Sources:

99% confidence
A

Wrap the value in ref() or reactive() before providing it.

import { ref, provide } from 'vue'

// Using ref
const count = ref(0)
provide('count', count)

// Using reactive
import { reactive, provide } from 'vue'

const state = reactive({ count: 0 })
provide('state', state)

The provided ref or reactive object maintains its reactivity when injected in descendant components. The injected value automatically updates when the provider changes it.

Best practice: Provide mutation functions alongside reactive values to keep state changes centralized in the provider:

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})

Options API: Use computed() to ensure reactivity:

export default {
  provide() {
    return {
      message: computed(() => this.message)
    }
  }
}

Sources:

99% confidence
A

In Vue 3 Composition API, provide() and inject() enable dependency injection between ancestor and descendant components.

provide()

Syntax:

provide(key, value)
  • key: string or Symbol
  • value: any value, including reactive refs

Example:

import { ref, provide } from 'vue'

// Provide a static value
provide('message', 'hello')

// Provide a reactive value
const count = ref(0)
provide('count', count)

// Provide with Symbol key (recommended for libraries)
import { myInjectionKey } from './keys'
provide(myInjectionKey, count)

inject()

Syntax:

// Without default
inject(key)

// With default value
inject(key, defaultValue)

// With factory function
inject(key, factoryFn, true)

Example:

import { inject } from 'vue'

// Basic injection
const message = inject('message')

// With default value
const count = inject('count', 0)

// With factory function for expensive defaults
const obj = inject('key', () => new ExpensiveClass(), true)

Important notes:

  • Must be called synchronously during setup() or <script setup>
  • Injected refs remain reactive (not auto-unwrapped)
  • Vue walks up the parent chain to find the matching key
  • Returns undefined if no provider found (unless default specified)

Sources:

99% confidence
A

In Vue 3, you define default values for inject() by passing a second argument:

Static Default Values

const value = inject('key', 'default-value')

The second parameter is the default value used when no matching provide key is found.

Factory Function Defaults

For expensive-to-create defaults, use a factory function with true as the third argument:

const obj = inject('key', () => new ExpensiveObject(), true)

The third parameter (true) tells Vue to treat the function as a factory, not as the literal default value.

Type Signatures

// Without default (may be undefined)
inject<T>(key: InjectionKey<T> | string): T | undefined

// With static default
inject<T>(key: InjectionKey<T> | string, defaultValue: T): T

// With factory function
inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: () => T,
  treatDefaultAsFactory: true
): T

The default value removes undefined from the return type when using TypeScript.

Sources:

99% confidence
A

defineAsyncComponent is a Vue 3 function that defines an async component which is lazy loaded only when it is rendered. This enables code splitting to reduce initial bundle size in large applications.

Basic Usage

The simplest form uses ES module dynamic import:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

The component will only be loaded when it's actually rendered in your template.

Advanced Usage with Options

For loading states and error handling:

const AsyncComp = defineAsyncComponent({
  loader: () => import('./components/MyComponent.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,        // ms before showing loading component
  timeout: 3000      // ms before showing error component
})

Full Type Signature

function defineAsyncComponent(
  source: AsyncComponentLoader | AsyncComponentOptions
): Component

type AsyncComponentLoader = () => Promise<Component>

interface AsyncComponentOptions {
  loader: AsyncComponentLoader
  loadingComponent?: Component
  errorComponent?: Component
  delay?: number
  timeout?: number
  suspensible?: boolean
  onError?: (
    error: Error,
    retry: () => void,
    fail: () => void,
    attempts: number
  ) => any
}

Configuration Options

  • loader - Function returning Promise that resolves to component
  • loadingComponent - Component to display while loading (default: none)
  • delay - Milliseconds before showing loading component (default: 200)
  • errorComponent - Component to display on load failure (default: none)
  • timeout - Milliseconds before timeout error (default: Infinity)
  • suspensible - Whether component works with <Suspense> (default: true)
  • onError - Error handler with retry/fail callbacks and attempt counter

Registration Examples

Global:

app.component('MyComponent', defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
))

Local (Composition API):

<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)
</script>

<template>
  <AdminPage />
</template>

Sources:

99% confidence
A

defineAsyncComponent in Vue 3 accepts either a simple loader function or an options object with the following properties:

Options Object Properties:

  • loader (required): Function that returns a Promise resolving to the component definition
  • loadingComponent: Component to display while the async component is loading
  • errorComponent: Component to display if the load fails
  • delay: Number (milliseconds) before showing the loading component. Default: 200
  • timeout: Number (milliseconds) before timing out and showing error component. Default: Infinity
  • suspensible: Boolean controlling whether the component should be controllable by Suspense. Default: true
  • onError: Function for custom error handling with signature (error, retry, fail, attempts) => any

Usage Example:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./MyComponent.vue'),
  loadingComponent: LoadingComponent,
  delay: 200,
  errorComponent: ErrorComponent,
  timeout: 3000,
  suspensible: false,
  onError(error, retry, fail, attempts) {
    if (attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})

Sources:

99% confidence
A

<Suspense> is a built-in Vue 3 component that orchestrates async dependencies in a component tree. It displays a loading state (fallback) while waiting for multiple nested async operations to resolve.

Status: Experimental (API may change before stable release)

What Async Dependencies It Handles

  1. Components with async setup() hooks (including <script setup> with top-level await)
  2. Async Components (defined using defineAsyncComponent)

Basic Usage

<Suspense>
  <!-- default slot: component with async dependencies -->
  <Dashboard />
  
  <!-- fallback slot: shown while loading -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

With Async Setup

<script setup>
// Top-level await makes this component an async dependency
const res = await fetch('/api/user')
const user = await res.json()
</script>

<template>
  <div>{{ user.name }}</div>
</template>

Then wrap it:

<Suspense>
  <UserProfile />
  <template #fallback>
    Loading user...
  </template>
</Suspense>

Key Properties

  • Two slots required: #default (main content) and #fallback (loading state)
  • One child per slot: Each slot accepts only one immediate child node
  • Events: Emits pending, resolve, and fallback events
  • timeout prop: Controls delay before showing fallback (default: 0ms)

Error Handling

Suspense doesn't handle errors automatically. Use errorCaptured hook or onErrorCaptured():

<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((err) => {
  // handle error
  return false // prevent propagation
})
</script>

Sources:

99% confidence
A

The Vue 3 <Suspense> component has exactly two slots:

  1. #default - The primary content slot containing components with async dependencies
  2. #fallback - The loading/fallback content displayed while async dependencies are resolving

Both slots only allow one immediate child node. The default slot content is shown when all async dependencies are resolved; otherwise, the fallback slot content is displayed during the pending state.

Example:

<Suspense>
  <!-- component with nested async dependencies -->
  <Dashboard />
  
  <!-- loading state via #fallback slot -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

Sources:

99% confidence
A

Use onErrorCaptured() hook or errorCaptured lifecycle option in the parent component containing <Suspense>.

<Suspense> does not provide error handling via the component itself. When async components fail inside Suspense, you must handle errors at the parent level:

Composition API:

<script setup>
import { onErrorCaptured } from 'vue'

onErrorCaptured((err, instance, info) => {
  // Handle the error from async component
  console.error('Async component error:', err)
  // Return false to prevent error propagation
  return false
})
</script>

<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Options API:

<script>
export default {
  errorCaptured(err, instance, info) {
    // Handle the error
    console.error('Async component error:', err)
    return false // Prevent propagation
  }
}
</script>

<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Important: When async components are used with Suspense (they are suspensible by default), their own errorComponent, loading, and timeout options are ignored. Error handling must be done via the parent's error capture hooks.

Alternative: Set suspensible: false in defineAsyncComponent() to opt-out of Suspense control and use the component's own errorComponent option instead.

Sources:

99% confidence
A

The Teleport component in Vue 3 is a built-in component that renders a part of a component's template into a DOM node outside the component's DOM hierarchy.

Purpose: Teleport solves the problem of rendering content (like modals, tooltips, or notifications) at a different location in the DOM tree while keeping the component logic together.

Basic Syntax:

<Teleport to="body">
  <div class="modal">I'm rendered in the body element</div>
</Teleport>

Why it's needed:

  • CSS positioning issues: When a parent has transform, perspective, or filter CSS properties, position: fixed children won't position relative to the viewport. Teleporting to body fixes this.
  • Z-index stacking: Nested elements inherit stacking contexts from parents, causing z-index conflicts. Teleporting to the root avoids these issues.
  • Modal/overlay patterns: Keeps modal button and modal content in the same component for better code organization, while rendering the modal at the document root for proper styling.

Key Props:

  • to: CSS selector string or DOM element (required)
  • disabled: Boolean to toggle teleporting on/off
  • defer: (Vue 3.5+) Defers target resolution until after app mount

Important: Teleport only changes where content is rendered in the DOM. The logical component hierarchy remains unchanged—props, events, and provide/inject still work normally.

Sources:

99% confidence
A

The to prop in Vue's Teleport component specifies the target container where the content will be rendered.

Type: string | HTMLElement

Accepted values:

  • CSS selector string (e.g., "#some-id", ".some-class", "body", "[data-teleport]")
  • Actual DOM element (an HTMLElement reference)

The prop is required for the Teleport component to function.

Example:

<Teleport to="body">
  <div class="modal">Modal content</div>
</Teleport>

<!-- Or with a DOM reference -->
<Teleport :to="domElement">
  <div>Content</div>
</Teleport>

Sources:

99% confidence
A

The disabled prop controls whether the Teleport component moves its content to the target location or keeps it in its original place in the DOM.

When disabled is true, the content remains in its original location instead of being teleported to the target container. When false (default), the content is moved to the target.

The prop can be changed dynamically, making it useful for responsive designs where you want different behavior based on conditions:

<Teleport :disabled="isMobile">
  <video src="./my-movie.mp4">
</Teleport>

In this example, the video would render inline when isMobile is true (content stays in place), but teleport to the target when isMobile is false (content moves to target).

Type: boolean
Default: false

Sources:

99% confidence
A

In Vue 3, you define a component with multiple v-model bindings by using named v-model arguments. Each v-model targets a different prop.

Parent component usage:

<UserName
  v-model:first-name="first"
  v-model:last-name="last"
/>

Child component definition (Vue 3.4+):

<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>

<template>
  <input type="text" v-model="firstName" />
  <input type="text" v-model="lastName" />
</template>

Pre-Vue 3.4 approach:

<script setup>
defineProps({
  firstName: String,
  lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

Each v-model syncs to a different prop without requiring extra configuration. The syntax v-model:argumentName replaces the deprecated .sync modifier from Vue 2.

Sources:

99% confidence
A

In Vue 3, v-model uses modelValue as the default prop name and update:modelValue as the default event name.

Default v-model:

  • Prop: modelValue
  • Event: update:modelValue

When you write v-model="foo", it expands to:

:modelValue="foo" @update:modelValue="$event => (foo = $event)"

Custom v-model with arguments:

For v-model:title="bookTitle":

  • Prop: title
  • Event: update:title

Pattern: The prop name is the argument itself, and the event name is update:{propName}.

Component implementation example:

<!-- Child component -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Modifier props:

  • Default v-model modifiers: modelModifiers prop
  • Custom argument modifiers: {propName}Modifiers prop (e.g., titleModifiers for v-model:title.capitalize)

Sources:

99% confidence
A

In Vue 3, custom v-model modifiers are accessed through the modelModifiers prop, which provides an object containing all applied modifiers as boolean flags.

Modern approach (Vue 3.4+) - Use defineModel() with a setter:

const [model, modifiers] = defineModel({
  set(value) {
    if (modifiers.capitalize) {
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
    return value
  }
})

Traditional approach - Accept modelModifiers prop:

export default {
  props: {
    modelValue: String,
    modelModifiers: { default: () => ({}) }
  },
  emits: ['update:modelValue'],
  methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) {
        value = value.charAt(0).toUpperCase() + value.slice(1)
      }
      this.$emit('update:modelValue', value)
    }
  }
}

With named v-models, modifiers follow the pattern [argName]Modifiers:

const [firstName, firstNameModifiers] = defineModel('firstName')
// Access via firstNameModifiers.capitalize

Usage:

<MyComponent v-model.capitalize="text" />
<UserName v-model:first-name.capitalize="first" />

Sources:

99% confidence
A

inheritAttrs is a boolean component option in Vue 3 that controls whether attributes passed from a parent component automatically fall through to the child component's root element.

Default value: true

Type: boolean

Behavior:

  • When true (default): Attributes not declared as props are automatically applied to the component's root element
  • When false: Automatic fallthrough is disabled, and attributes remain accessible via $attrs for manual application

When to use inheritAttrs: false:
When you want to apply parent attributes to a non-root element instead of the root element. This is common in wrapper components.

Example with Options API:

export default {
  inheritAttrs: false,
  mounted() {
    console.log(this.$attrs) // Access fallthrough attributes
  }
}

Example with <script setup> (Vue 3.3+):

<script setup>
import { useAttrs } from 'vue'

defineOptions({
  inheritAttrs: false
})

const attrs = useAttrs()
</script>

<template>
  <div class="wrapper">
    <!-- Apply attrs to inner element instead of root -->
    <button v-bind="attrs">Click me</button>
  </div>
</template>

Note: In Vue 3, $attrs includes class and style (unlike Vue 2 where these were excluded), making it easier to forward all attributes to any element.

Sources:

99% confidence
A

Use v-bind="$attrs" on the target element after disabling automatic inheritance with inheritAttrs: false.

Step 1: Disable automatic attribute inheritance

<script>
export default {
  inheritAttrs: false
}
</script>

Or with <script setup>:

<script setup>
defineOptions({
  inheritAttrs: false
})
</script>

Step 2: Apply $attrs to your chosen element

<template>
  <div class="wrapper">
    <input v-bind="$attrs" />
  </div>
</template>

The v-bind directive without an argument binds all properties of the $attrs object as attributes to the target element. The $attrs object contains all attributes passed to the component that are not declared as props or emits (including class, style, and event listeners).

Complete example:

<template>
  <div class="date-picker">
    <input type="datetime-local" v-bind="$attrs" />
  </div>
</template>

<script>
export default {
  inheritAttrs: false
}
</script>

Sources:

99% confidence
A

defineComponent() is a type helper function in Vue 3 that enables TypeScript to properly infer types inside component options. It's essentially a runtime no-op—it returns the same options object you pass in—but provides critical type inference for TypeScript users.

When to use it:

  1. TypeScript projects with Options API - Required to get proper type inference for this in component options
  2. TypeScript projects with Composition API using object syntax - Helps with type inference when using the object-based component definition
  3. Render functions or JSX with TypeScript - Necessary for type inference when the component is used as a tag in TSX

When you DON'T need it:

  • Using <script setup> - This has built-in type inference without needing defineComponent()
  • JavaScript projects (no TypeScript) - No type inference benefit, though it doesn't hurt

Basic usage example:

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    message: String
  },
  setup(props) {
    // props.message is properly typed as string | undefined
  }
})

Function signature (Vue 3.3+):

import { defineComponent } from 'vue'

export default defineComponent((props) => {
  // Composition API setup function
  return () => {
    // render function
  }
})

Sources:

99% confidence
A

In Vue 3, you define a functional component as a plain JavaScript function that receives props and a context object:

function MyComponent(props, context) {
  // return vnode
}

Two arguments:

  1. props - Component props (first argument)
  2. context - Object containing { slots, emit, attrs } (equivalent to $slots, $emit, $attrs)

Declaring props and emits:

MyComponent.props = ['message', 'count']
MyComponent.emits = ['update']

Complete example:

function MyButton(props, { emit }) {
  return h('button', {
    onClick: () => emit('click')
  }, props.text)
}

MyButton.props = ['text']
MyButton.emits = ['click']

Functional components are stateless - they have no reactive state, lifecycle hooks, or this context. They act as pure functions: props in, vnodes out.

Note: In Vue 2, you used the functional: true option or functional attribute. In Vue 3, this approach was removed. All functional components are now plain functions.

Sources:

99% confidence
A

The h function in Vue 3 is a utility for creating virtual DOM nodes (vnodes) programmatically. The "h" stands for "hyperscript" – JavaScript that produces HTML.

Function signature:

function h(
  type: string | Component,
  props?: object | null,
  children?: Children | Slot | Slots
): VNode

// props can be omitted
function h(type: string | Component, children?: Children | Slot): VNode

Parameters:

  • type - String for HTML elements (like 'div') or a Vue component definition
  • props (optional) - Object with attributes, properties, event handlers
  • children (optional) - Child content: string, number, VNode, array of VNodes, or slot functions

Import in Vue 3:

import { h } from 'vue'

Usage examples:

Creating HTML elements:

h('div')
h('div', { id: 'foo' })
h('div', { class: 'bar', innerHTML: 'hello' })
h('div', 'hello')
h('div', [h('span', 'hello')])

Creating components:

h(MyComponent, { someProp: 'hello', onUpdate: () => {} })
h(MyComponent, () => 'default slot')
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo')
})

Sources:

99% confidence
A

The shorthand syntax for v-slot in Vue 3 is the hash symbol (#).

Basic Syntax

Replace v-slot: with #:

<!-- Full syntax -->
<template v-slot:header>
  <h1>Here might be a page title</h1>
</template>

<!-- Shorthand -->
<template #header>
  <h1>Here might be a page title</h1>
</template>

With Scoped Slots

<template #default="slotProps">
  {{ slotProps.message }}
</template>

With Dynamic Slot Names

<template #[dynamicSlotName]>
  ...
</template>

Important Limitation

The shorthand is only available when an argument is provided. You must specify the slot name even for the default slot when using the shorthand (e.g., #default).

Sources:

99% confidence
A

While technically possible, it is strongly discouraged to use v-if and v-for on the same element in Vue 3.

What happens if you do it:

In Vue 3, v-if has higher precedence than v-for. This means the v-if condition will not have access to variables from the v-for scope, which typically causes errors.

<!-- ❌ This will error - `item` is not available to v-if -->
<div v-for="item in items" v-if="item.isActive">
  {{ item.name }}
</div>

Recommended approaches:

1. For filtering lists - Use a computed property:

<script setup>
import { computed } from 'vue'

const activeItems = computed(() => items.filter(item => item.isActive))
</script>

<template>
  <div v-for="item in activeItems" :key="item.id">
    {{ item.name }}
  </div>
</template>

2. For conditional list rendering - Move v-if to a wrapper element:

<ul v-if="shouldShowItems">
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

3. Alternative - Use a wrapper <template>:

<template v-for="item in items" :key="item.id">
  <div v-if="item.isActive">
    {{ item.name }}
  </div>
</template>

Sources:

99% confidence
A

In Vue 3, v-if has higher precedence than v-for when both directives are used on the same element.

This is a breaking change from Vue 2, where v-for had higher precedence than v-if.

Key implications:

  • The v-if condition is evaluated first
  • The v-if expression does NOT have access to variables from the v-for scope
  • Using both on the same element is not recommended due to syntax ambiguity

Recommended solutions:

  1. To conditionally filter items: Use a computed property instead
<!-- Avoid -->
<li v-for="user in users" v-if="user.isActive">

<!-- Instead -->
<li v-for="user in activeUsers">
  1. To conditionally render the entire list: Move v-if to a wrapper element
<!-- Avoid -->
<li v-for="user in users" v-if="shouldShowUsers">

<!-- Instead -->
<ul v-if="shouldShowUsers">
  <li v-for="user in users">
</ul>

Sources:

99% confidence
A

In Vue 3, you emit events with payload data by passing additional arguments to the $emit() function after the event name. All extra arguments are forwarded to the listener.

Composition API (Script Setup):

<script setup>
const emit = defineEmits(['increaseBy'])

function buttonClick() {
  emit('increaseBy', 1)  // Emit event with payload
}
</script>

Options API:

export default {
  emits: ['increaseBy'],
  methods: {
    submit() {
      this.$emit('increaseBy', 1)  // Emit event with payload
    }
  }
}

Parent Component Receiving Payload:

<!-- Inline arrow function -->
<MyButton @increase-by="(n) => count += n" />

<!-- Or method reference -->
<MyButton @increase-by="increaseCount" />

Multiple Arguments:

emit('eventName', arg1, arg2, arg3)
// Listener receives all three arguments as separate parameters

Sources:

99% confidence
A

Fallback content in slots is default content that renders only when the parent component doesn't provide any content for that slot.

You define it by placing content between the <slot> tags in your component:

<button type="submit">
  <slot>
    Submit <!-- fallback content -->
  </slot>
</button>

Behavior:

  • When parent provides no content: <SubmitButton /> → renders "Submit"
  • When parent provides content: <SubmitButton>Save</SubmitButton> → renders "Save"

Parent-provided content always takes precedence over fallback content.

Sources:

99% confidence

Reactivity System

24 questions
A

toRef creates a reactive ref that maintains a live, bidirectional connection to a property on a reactive object. Mutating the ref updates the original property, and vice versa.

Primary purposes:

  1. Pass reactive object properties to composables - Allows you to pass individual properties from a reactive object (like props) to composable functions while maintaining reactivity
  2. Normalize values into refs (Vue 3.3+) - Converts values, existing refs, or getter functions into refs

Syntax:

// Property binding (creates two-way sync)
const fooRef = toRef(props, 'foo')

// Getter syntax (Vue 3.3+, read-only)
const fooRef = toRef(() => props.foo)

// With default value
const fooRef = toRef(props, 'foo', 'defaultValue')

Key difference from ref():

  • ref() creates a copy - no connection to the original value
  • toRef() creates a live connection - changes sync both ways

Common use case:

// In a component
const props = defineProps(['foo'])

// Pass a reactive ref of props.foo to a composable
useSomeFeature(toRef(props, 'foo'))

This is essential when working with component props because destructuring props loses reactivity, but toRef preserves it.

Sources:

99% confidence
A

toRefs converts a reactive object into a plain object where each property is a ref that points to the corresponding property of the original object. This preserves reactivity when destructuring or spreading reactive objects.

Primary use case: Enabling destructuring of reactive objects without losing reactivity. When you destructure a reactive object normally, the destructured properties lose their reactive connection. toRefs solves this by wrapping each property in a ref.

Type signature:

function toRefs<T extends object>(
  object: T
): { [K in keyof T]: ToRef<T[K]> }

Example:

const state = reactive({ foo: 1, bar: 2 })
const stateAsRefs = toRefs(state)

// Maintains two-way reactive connection
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
console.log(state.foo) // 3

Common patterns:

  • Returning reactive objects from composables so consumers can destructure them
  • Destructuring props while maintaining reactivity
  • Spreading reactive state into template or component data

Limitation: Only generates refs for properties that exist at call time. For potentially missing properties, use toRef() instead.

Sources:

99% confidence
A

ref() and reactive() are two different ways to create reactive state in Vue 3:

ref() - Wraps any value in an object with a .value property:

  • Works with all types: primitives (string, number, boolean), objects, arrays, Map, Set
  • Accessed via .value in JavaScript: count.value++
  • Auto-unwraps in templates (no .value needed)
  • Can be reassigned entirely: myRef.value = newObject
  • Maintains reactivity when destructured

reactive() - Makes an object reactive using JavaScript Proxies:

  • Only works with objects: plain objects, arrays, Map, Set
  • Cannot hold primitives (string, number, boolean)
  • Accessed directly: state.count++
  • Cannot replace entire object - must keep same reference
  • Loses reactivity when destructured into primitive variables

Example:

import { ref, reactive } from 'vue'

// ref - any type
const count = ref(0)
count.value++ // need .value in JS

// reactive - objects only
const state = reactive({ count: 0 })
state.count++ // direct access

// ref maintains reactivity
const { value } = count // still reactive

// reactive loses reactivity
const { count: localCount } = state // NOT reactive

Vue's recommendation: Use ref() as the primary API for declaring reactive state due to reactive()'s limitations with primitives, reassignment, and destructuring.

Sources:

99% confidence
A

shallowRef() creates a reactive reference where only the .value property itself is reactive—the inner value is stored as-is and not made deeply reactive.

How it differs from ref():

  • ref(): Makes nested properties reactive (deep reactivity)
  • shallowRef(): Only the .value assignment is reactive (shallow reactivity)

Behavior:

const state = shallowRef({ count: 1 })

// Does NOT trigger reactivity
state.value.count = 2

// DOES trigger reactivity
state.value = { count: 2 }

When to use shallowRef():

  1. Performance optimization for large data structures - When you have large objects/arrays and don't need to track changes to nested properties, shallow refs avoid the overhead of deep reactivity tracking.

  2. Integration with external state management - When integrating Vue with external state systems that manage their own reactivity (e.g., immutable data libraries), you can hold the external state in a shallowRef and replace the entire .value when the external state updates.

Note: You can use triggerRef() to manually trigger effects after mutating nested properties of a shallow ref.

Sources:

99% confidence
A

shallowReactive() creates a reactive proxy where only root-level properties are reactive—nested objects are exposed as-is and do not trigger updates when mutated.

Type Signature:

function shallowReactive<T extends object>(target: T): T

Behavior:

  • Root-level property changes trigger reactivity ✓
  • Nested object mutations do NOT trigger reactivity ✗
  • Ref values are NOT automatically unwrapped (unlike deep reactive())

Example:

const state = shallowReactive({
  foo: 1,
  nested: {
    bar: 2
  }
})

state.foo++        // reactive - triggers updates
state.nested.bar++ // NOT reactive - no updates triggered

When to Use:

  • For root-level state in a component where nested objects don't need reactivity
  • Performance optimization when working with large objects where only top-level properties change
  • When nested objects are managed independently or should remain immutable

Important Warning: Shallow data structures should only be used for root-level state in a component. Avoid nesting shallowReactive() within deep reactive objects—this creates inconsistent reactivity that's difficult to understand and debug.

Sources:

99% confidence
A

toRaw() returns the raw, original object of a Vue-created proxy.

Type signature:

function toRaw<T>(proxy: T): T

What it does:

  • Extracts the underlying original object from proxies created by reactive(), readonly(), shallowReactive(), or shallowReadonly()
  • Provides an escape hatch to read without proxy access/tracking overhead or write without triggering changes
  • Warning: Not recommended to hold a persistent reference to the original object

Example:

const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

Use case: Temporarily bypass reactivity for performance-critical operations where you need direct access to the original object without triggering reactive effects.

Sources:

99% confidence
A

triggerRef() forces effects that depend on a shallow ref to re-run. It's used when you make deep mutations to a shallow ref's inner value that wouldn't normally trigger reactivity.

Function signature:

function triggerRef(ref: ShallowRef): void

Why it's needed: shallowRef() only tracks reactivity at the .value level, not nested properties. When you mutate nested properties, Vue doesn't detect the change. triggerRef() manually notifies Vue to re-run dependent effects.

Example:

const shallow = shallowRef({
  greet: 'Hello, world'
})

watchEffect(() => {
  console.log(shallow.value.greet)
})

// This WON'T trigger the effect (shallow ref doesn't track nested properties)
shallow.value.greet = 'Hello, universe'

// This WILL trigger the effect - manually force update
triggerRef(shallow)  // Logs "Hello, universe"

Use case: Performance optimization when working with large objects where you want manual control over when updates trigger, rather than Vue's automatic deep reactivity tracking.

Sources:

99% confidence
A

readonly() is a Vue 3 reactivity API function that creates a read-only proxy of an object, ref, or reactive object. It prevents mutations while maintaining reactivity tracking.

Key behaviors:

  1. Deep readonly conversion - All nested properties become readonly, not just root-level properties
  2. Reactivity tracking preserved - Read operations are still tracked, so watchers and computed properties work
  3. Mutation blocking - Write operations fail and trigger warnings in development
  4. Ref unwrapping - Like reactive(), it unwraps refs automatically, but the unwrapped values become readonly

Type signature:

function readonly<T extends object>(
  target: T
): DeepReadonly<UnwrapNestedRefs<T>>

Example:

const original = reactive({ count: 0 })
const copy = readonly(original)

watchEffect(() => {
  console.log(copy.count) // tracked for reactivity
})

original.count++ // triggers watchers relying on copy
copy.count++ // generates warning, mutation fails

Use case: Exposing state that should be read but not modified, like when a parent component shares state with children but wants to prevent direct mutations.

For shallow readonly conversion (only root-level properties readonly), use shallowReadonly() instead.

Sources:

99% confidence
A

shallowReadonly() creates a shallow readonly proxy where only root-level properties are readonly, while nested objects remain mutable and reactive.

Type Signature:

function shallowReadonly<T extends object>(target: T): Readonly<T>

Behavior:

  • Root-level properties cannot be modified
  • Nested objects remain mutable (mutations succeed)
  • Ref values are NOT automatically unwrapped
  • Property values stored and exposed as-is

Example:

const state = shallowReadonly({
  foo: 1,
  nested: {
    bar: 2
  }
})

state.foo++        // ❌ fails - root property is readonly
state.nested.bar++ // ✅ succeeds - nested object is mutable

Key Difference from readonly():

  • readonly() creates deep readonly conversion (all nested levels readonly)
  • shallowReadonly() only protects the first level

Important Warning:
Shallow data structures should only be used for root-level state in a component. Avoid nesting it inside a deep reactive object as it creates inconsistent reactivity behavior.

Use Case:
Performance optimization when you need readonly protection only at the top level and don't need deep readonly conversion.

Sources:

99% confidence
A

markRaw() is a Vue 3 API function that marks an object to permanently opt-out of Vue's reactivity system. When you pass an object to markRaw(), it returns the same object unchanged, but Vue will never convert it into a reactive proxy.

Type signature:

function markRaw<T extends object>(value: T): T

Key behavior:

  • Returns the original object, preventing Vue from wrapping it in a Proxy
  • Works when nested inside reactive objects - the marked object stays non-reactive
  • The marking is permanent for that object

Example:

const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// also works when nested inside other reactive objects
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

Common use cases:

  1. Third-party class instances - Complex external library objects that shouldn't be made reactive (e.g., chart libraries, map instances, large data structures)
  2. Performance optimization - Skipping proxy conversion for large immutable data sources when rendering big lists

Important caveat: markRaw() only works at the root level. If you nest a non-marked raw object inside a marked object, then access it through a reactive parent, you may get both proxied and non-proxied versions of the same object, causing identity mismatches.

Sources:

99% confidence
A

isRef() is a utility function in Vue 3's reactivity system that checks whether a value is a ref object.

Type Signature:

function isRef<T>(r: Ref<T> | unknown): r is Ref<T>

Key Feature: The return type is a type predicate (r is Ref<T>), which means isRef can be used as a type guard in TypeScript. This allows automatic type narrowing in conditional blocks.

Usage Example:

let foo: unknown
if (isRef(foo)) {
  // foo's type is narrowed to Ref<unknown>
  foo.value
}

Purpose: This utility allows you to safely determine whether a value is a reactive ref before attempting to access its .value property. It's particularly useful in composable functions and reusable logic where you need to handle values that might optionally be refs.

Sources:

99% confidence
A

isReactive() is a utility function in Vue 3's reactivity system that checks whether an object is a reactive proxy created by reactive() or shallowReactive().

Function signature:

function isReactive(value: unknown): boolean

Returns: true if the value is a proxy created by reactive() or shallowReactive(), false otherwise.

Usage example:

import { reactive, isReactive } from 'vue'

const state = reactive({ count: 0 })
const plain = { count: 0 }

console.log(isReactive(state))  // true
console.log(isReactive(plain))  // false

Important note: isReactive() does NOT return true for readonly proxies created by readonly() or shallowReadonly(). For those, use isReadonly() instead.

Sources:

99% confidence
A

isReadonly() is a utility function in Vue 3's reactivity system that checks whether a value is a readonly proxy.

Function signature:

function isReadonly(value: unknown): boolean

What it does:
Returns true if the passed value is a readonly object created by readonly(), shallowReadonly(), or a computed() ref without a setter function. Returns false otherwise.

Key behavior:
A readonly object's properties can still change (if the original object changes), but they cannot be modified directly through the readonly proxy itself.

Example:

import { readonly, isReadonly } from 'vue'

const original = { count: 0 }
const readonlyObj = readonly(original)

isReadonly(readonlyObj) // true
isReadonly(original)    // false

Sources:

99% confidence
A

isProxy() is a utility function in Vue 3's reactivity system that checks whether an object is a proxy created by reactive(), readonly(), shallowReactive(), or shallowReadonly().

Signature:

function isProxy(value: any): boolean

Returns: true if the value is a proxy created by any of Vue's reactivity functions listed above, false otherwise.

Usage:

import { reactive, isProxy } from 'vue'

const obj = reactive({ count: 0 })
console.log(isProxy(obj)) // true

const plain = { count: 0 }
console.log(isProxy(plain)) // false

This utility is helpful when you need to programmatically determine if an object has been wrapped in a reactive proxy by Vue's reactivity system.

Sources:

99% confidence
A

unref() is a utility function in Vue 3's reactivity system that returns the inner value if the argument is a ref, otherwise returns the argument itself unchanged.

Type signature:

function unref<T>(ref: T | Ref<T>): T

What it does:

  • If you pass a ref, it returns ref.value
  • If you pass a regular value, it returns that value
  • It's sugar syntax for: val = isRef(val) ? val.value : val

Use case:
Useful when writing composables or functions that accept flexible parameters (either refs or plain values) but need to work with the unwrapped value internally.

Example:

function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x)
  // unwrapped is guaranteed to be number now
}

Note: In Vue 3.3+, toValue() is similar but also normalizes getter functions, not just refs.

Sources:

99% confidence
A

customRef is a Vue 3 function that creates a ref with explicit control over dependency tracking and updates. Unlike standard refs, you control exactly when to track dependencies and trigger reactivity.

Signature:

function customRef<T>(factory: CustomRefFactory<T>): Ref<T>

How it works:
customRef accepts a factory function that receives two callbacks:

  • track() - Call this in the getter to register reactive dependencies
  • trigger() - Call this in the setter to notify watchers of changes

The factory must return an object with get() and set() methods.

Example - Debounced Ref:

import { customRef } from 'vue'

export function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()  // Register dependency
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()  // Trigger reactivity after delay
        }, delay)
      }
    }
  })
}

Usage:

<script setup>
import { useDebouncedRef } from './debouncedRef'
const text = useDebouncedRef('hello')
</script>

<template>
  <input v-model="text" />
</template>

This delays value updates and change notifications, useful for performance optimization with frequently-changing inputs.

Sources:

99% confidence
A

Yes, you can add new properties to a reactive object after creation in Vue 3, and they will be reactive.

This is a major improvement from Vue 2. In Vue 3, reactive() uses JavaScript Proxies instead of Object.defineProperty, which means newly added properties are automatically tracked.

import { reactive } from 'vue'

const state = reactive({
  name: 'John'
})

// Add new property - it will be reactive
state.age = 30  // ✅ This works and is reactive
state.email = '[email protected]'  // ✅ This also works

However, there's one critical caveat: You must maintain the same reference to the reactive object. Destructuring or assigning to a new variable breaks reactivity:

// ❌ Loses reactivity
let { name } = state

// ❌ Loses reactivity  
let newState = { ...state }

// ✅ Keeps reactivity
const ref = state
ref.newProp = 'value'  // Still reactive

Note: While the Options API data() documentation states that properties added directly to this won't be reactive, this refers to bypassing the data object itself. Properties added to objects within data (which are made reactive via Proxy) will be reactive.

Sources:

99% confidence
A

The effectScope() API in Vue 3 creates an effect scope object that captures reactive effects (computed properties, watchers, watchEffect) created within it so they can be disposed together.

Key Methods:

  • scope.run(fn) - Executes a function within the scope and captures any reactive effects created
  • scope.stop() - Disposes all effects captured in the scope

Parameters:

  • detached (optional, boolean) - When true, the scope operates independently from parent scopes

Type Signature:

function effectScope(detached?: boolean): EffectScope

interface EffectScope {
  run<T>(fn: () => T): T | undefined
  stop(): void
}

Example:

const scope = effectScope()

scope.run(() => {
  const doubled = computed(() => counter.value * 2)
  watch(doubled, () => console.log(doubled.value))
  watchEffect(() => console.log('Count: ', doubled.value))
})

// Dispose all effects at once
scope.stop()

Use Case: Managing lifecycle of multiple reactive effects together, particularly useful in reusable composition functions where cleanup is necessary.

Related APIs:

  • getCurrentScope() - Returns the current active effect scope
  • onScopeDispose(fn) - Registers a cleanup callback on the current active effect scope

Sources:

99% confidence
A

getCurrentScope() returns the current active effect scope if one exists, otherwise returns undefined.

Type Signature:

function getCurrentScope(): EffectScope | undefined

Purpose:
This function is part of Vue 3's Advanced Reactivity API and allows you to access the currently active effect scope. It's commonly used when you need to determine whether your code is running within an effect scope context, or when you need to register cleanup callbacks with onScopeDispose().

Use Case:
Useful in composables and libraries when you need to conditionally execute logic based on whether an effect scope is active.

Example:

import { getCurrentScope, onScopeDispose } from 'vue'

function myComposable() {
  const scope = getCurrentScope()
  
  if (scope) {
    // Running within an effect scope
    onScopeDispose(() => {
      // Cleanup logic
    })
  }
}

Sources:

99% confidence
A

onScopeDispose is a function in Vue 3's reactivity system that registers a cleanup callback on the current active effect scope. The callback executes when the associated effect scope is stopped.

Type Signature:

function onScopeDispose(fn: () => void, failSilently?: boolean): void

Purpose:
It serves as a non-component-coupled replacement for onUnmounted in reusable composition functions. Since each Vue component's setup() function runs within an effect scope, onScopeDispose enables cleanup logic in standalone composables that can be used both inside and outside of components.

Behavior:

  • Registers a disposal callback on the currently active effect scope
  • The callback runs when the scope is stopped (via scope.stop())
  • Throws a warning if called without an active effect scope
  • In Vue 3.5+, the warning can be suppressed by passing true as the second argument

Example Use Case:

function useTimer() {
  const interval = setInterval(() => {
    // do something
  }, 1000)
  
  onScopeDispose(() => {
    clearInterval(interval)
  })
}

Sources:

99% confidence
A

When you destructure a reactive object in Vue 3, you lose reactivity. The destructured variables become plain values disconnected from the reactive source.

What happens:

const state = reactive({ count: 0 })

// count is now disconnected from state.count
let { count } = state

// This does NOT affect the original state
count++

// state.count is still 0

Why: Destructuring extracts the value at that moment, not a reactive reference. Vue's reactivity system relies on property access through the proxy. Once destructured into a local variable, changes to that variable no longer trigger the get/set proxy traps.

Solution - Use toRefs():

import { reactive, toRefs } from 'vue'

const state = reactive({ foo: 1, bar: 2 })

// Convert to refs so destructuring preserves reactivity
const { foo, bar } = toRefs(state)

// Now foo and bar are refs that maintain reactivity
foo.value++ // Updates state.foo

Sources:

99% confidence
A

Use toRefs() to preserve reactivity when destructuring a reactive object in Vue 3.

toRefs() converts a reactive object to a plain object where each property becomes a ref that maintains a synced connection to the original property. Changes to the ref update the source, and vice versa.

import { reactive, toRefs } from 'vue'

const state = reactive({ foo: 1, bar: 2 })
const stateAsRefs = toRefs(state)

// Now safe to destructure without losing reactivity
const { foo, bar } = stateAsRefs
foo.value++ // Updates state.foo

Common pattern in composables:

function useFeatureX() {
  const state = reactive({ foo: 1, bar: 2 })
  return toRefs(state) // Convert before returning
}

const { foo, bar } = useFeatureX() // Safe to destructure

Important: toRefs() only creates refs for properties that exist at call time. For individual optional properties, use toRef() instead.

Without toRefs(), destructuring breaks reactivity:

const state = reactive({ count: 0 })
let { count } = state // count is disconnected
count++ // Does NOT affect state.count

Sources:

99% confidence
A

toRef creates a ref for a single property of a reactive object. toRefs converts all properties of a reactive object into refs.

toRef

Creates a ref for one property on a source reactive object. The ref stays synchronized with the source property (two-way binding).

const state = reactive({ count: 0, name: 'Vue' })
const countRef = toRef(state, 'count')

countRef.value++ // state.count is now 1
state.count = 5  // countRef.value is now 5

Use case: Pass a single reactive property to a composable function while maintaining reactivity.

toRefs

Converts all enumerable properties of a reactive object into refs. Each ref is created using toRef() internally.

const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = toRefs(state)

count.value++ // state.count is now 1
state.count = 5 // count.value is now 5

Use case: Destructure a reactive object (like props) while preserving reactivity for all properties.

Key Difference

  • toRef(object, key) → single ref for one property
  • toRefs(object) → object containing refs for all properties

Both maintain two-way synchronization with the original reactive object.

Sources:

99% confidence
A

No, reactive() cannot accept primitive values in Vue 3.

Limitation: reactive() only works with object types (objects, arrays, and collection types like Map and Set). It cannot handle primitive types such as string, number, or boolean.

Type Signature:

function reactive<T extends object>(target: T): UnwrapNestedRefs<T>

The extends object constraint explicitly prevents primitive values.

Solution: Use ref() for primitive values instead. ref() wraps the primitive in a reactive object with a .value property:

// ❌ Won't work
const count = reactive(0)

// ✅ Use ref() instead
const count = ref(0)
console.log(count.value) // 0

Why this matters: If you destructure a reactive object's primitive property or pass it to a function, you lose the reactivity connection. This is why Vue recommends ref() as the primary API for declaring reactive state.

Sources:

99% confidence

Vue Router 4

7 questions
A

In Vue Router 4, you create a router instance using the createRouter() function from the vue-router package.

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // your route definitions
  ]
})

Required steps:

  1. Import the createRouter function and a history mode (e.g., createWebHistory)
  2. Call createRouter() with a configuration object containing:
    • history: A history mode instance (required)
    • routes: Array of route definitions (required)
  3. Register the router with your Vue app using app.use(router)

Complete example:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

const app = createApp(App)
app.use(router)
app.mount('#app')

Sources:

99% confidence
A

Based on the official Vue Router documentation, here are the key differences:

URL Format

createWebHistory() - Creates clean URLs matching the HTML5 History API:

  • https://example.com/user/id
  • Normal-looking paths without hash symbols

createWebHashHistory() - Uses hash mode with # character:

  • https://example.com/#/user/id
  • URL portion after # is used for routing

Server Configuration

createWebHistory():

  • Requires server configuration - needs a catch-all fallback route
  • Without proper server setup, users get 404 errors when accessing URLs directly
  • Server must redirect all routes to index.html

createWebHashHistory():

  • No server configuration needed - works out of the box
  • The hash portion is never sent to the server, so direct URL access works automatically

SEO & Search Engines

createWebHistory():

  • Full URLs are visible to search engines
  • Better for SEO

createWebHashHistory():

  • Hash portion is not processed by search engines
  • Poor SEO performance

Recommendation

The official Vue Router documentation recommends createWebHistory() for most web applications due to cleaner URLs and better SEO, despite requiring server configuration.

Sources:

99% confidence
A

Based on the official Vue Router documentation, I have a verified answer:

createMemoryHistory() creates an in-memory based history that doesn't interact with the browser URL. It's primarily used for:

  1. Server-Side Rendering (SSR) - The main use case, as it works in Node.js environments where there's no browser URL
  2. Testing - Useful for unit/integration tests where you don't need actual browser navigation
  3. Non-browser environments - Any scenario where URL manipulation isn't needed or available

Key characteristics:

  • Does not read from or write to the browser URL
  • Doesn't automatically trigger initial navigation
  • Starts at a special "nowhere" location
  • Requires manual router.push() or router.replace() call after app.use(router) to set the initial location

Example:

import { createRouter, createMemoryHistory } from 'vue-router'

const router = createRouter({
  history: createMemoryHistory(),
  routes: [...]
})

For browser applications, use createWebHistory() or createWebHashHistory() instead.

Sources:

99% confidence
A

Based on the official Vue Router documentation, here's how to define dynamic route parameters:

Define dynamic route parameters using a colon (:) prefix in the path:

const routes = [
  // dynamic segment starts with a colon
  { path: '/users/:id', component: User }
]

Multiple parameters:

const routes = [
  // multiple params
  { path: '/users/:username/posts/:postId', component: UserPost }
]

Accessing parameters:

In the component, access parameters via $route.params:

<template>
  <div>User {{ $route.params.id }}</div>
</template>

With Composition API:

import { useRoute } from 'vue-router'

const route = useRoute()
console.log(route.params.id)

Matching behavior:

  • /users/:id matches /users/johnny, /users/123, etc.
  • :id becomes available as route.params.id
  • /users/:username/posts/:postId matches /users/eduardo/posts/123
    • route.params.username = "eduardo"
    • route.params.postId = "123"

Sources:

99% confidence
A

Based on the official Vue Router documentation, here's how to access route parameters in Vue 3 Composition API:

Use the useRoute() composable to access route parameters inside setup():

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // Access route parameters
    const userId = route.params.id
    
    // The route object is reactive
    console.log(route.params)
    
    return {
      userId
    }
  }
}

Key points:

  • Import useRoute from vue-router
  • Call useRoute() inside setup() to get the current route object
  • Access parameters via route.params.parameterName
  • The route object is reactive, so it updates when the route changes
  • You cannot use this.$route in Composition API (no this in setup())

Alternative approach using props (decoupled from route):

// In route config
const routes = [
  { path: '/user/:id', component: User, props: true }
]

// In component
export default {
  props: ['id'],
  setup(props) {
    console.log(props.id) // Access as regular prop
  }
}

Sources:

99% confidence
A

In Vue 3 Composition API, access query parameters using the useRoute() composable from Vue Router:

import { useRoute } from 'vue-router'

export default {
  setup() {
    const route = useRoute()
    
    // Access query parameters
    console.log(route.query.myParam)
    
    // Example: URL is /search?q=vue&filter=recent
    console.log(route.query.q)      // 'vue'
    console.log(route.query.filter) // 'recent'
  }
}

Key points:

  • Use useRoute() instead of this.$route (which doesn't exist in setup())
  • Query parameters are accessed via route.query object
  • route.query is reactive and will update when query parameters change
  • To navigate with query parameters, use useRouter() and router.push({ query: { param: 'value' } })

Accessing in <script setup> syntax:

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
const searchQuery = route.query.q
</script>

Sources:

99% confidence
A

Based on the official Vue Router documentation search results, I can provide you with an authoritative answer:

useRouter() is a composable function in Vue Router 4 that returns the router instance when called inside a Vue 3 Composition API setup() function or <script setup>.

It provides access to the same router instance that would be available as this.$router in the Options API, but in a way that works with the Composition API where this is not accessible.

Usage:

import { useRouter } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    
    // Navigate programmatically
    router.push('/about')
    
    // Access router methods
    router.back()
    router.forward()
  }
}

In <script setup>:

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function navigateToAbout() {
  router.push('/about')
}
</script>

The composable takes no parameters and must be called inside setup() or <script setup> to work properly.

Sources:

99% confidence