Migration to Vue 3, Part 2. – Template Directives

On custom components v-model prop and event default names are changed.

  • prop: value -> modelValue
  • event: input -> update:modelValue

Vue 2

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

Vue 3

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent 
    :modelValue="pageTitle" 
    @update:modelValue="pageTitle = $event" 
/>

Vue 2

// ChildComponent.vue

export default {
    model: {
        prop: 'title',
        event: 'change'
    },
    props: {
        // this allows using the `value` prop for a different purpose
        value: String,
        // use `title` as the prop which take the place of `value`
        title: {
            type: String,
            default: 'Default title'
        }
    }
}

<ChildComponent v-model="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :title="pageTitle" @change="pageTitle = $event" />

Vue 3

<ChildComponent v-model:title="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent 
    :title="pageTitle" 
    @update:title="pageTitle = $event" 
/>

v-bind.sync and component model option are removed and replaced with an argument on v-model.

Vue 2

// ChildComponent.vue

this.$emit('update:title', newValue)

<!-- ParentComponent.vue -->

<ChildComponent :title.sync="pageTitle" />

<!-- would be shorthand for: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

Vue 3

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />

<!-- would be shorthand for: -->

<ChildComponent 
    :title="pageTitle" 
    @update:title="pageTitle = $event" 
    :content="pageContent" 
    @update:content="pageContent = $event" 
/>

Keys are no longer necessary on v-if, v-else, v-else-if branches, since Vue now automatically generates unique keys. If provided, then each branch must use a unique key.

Vue 2

<div v-if="condition" key="a">Yes</div>
<div v-else key="a">No</div>

Vue 3

<div v-if="condition">Yes</div>
<div v-else>No</div>

<!-- or -->

<div v-if="condition" key="a">Yes</div>
<div v-else key="b">No</div>

<template v-for> key should be placed on the <template> tag (rather than on its children).

Vue 2

<template v-for="item in list">
    <div :key="'heading-' + item.id">...</div>
    <span :key="'content-' + item.id">...</span>
</template>

Vue 3

<template v-for="item in list" :key="item.id">
    <div>...</div>
    <span>...</span>
</template>

If used on the same element, v-if will have higher precedence than v-for.

Vue 2

<li v-for="todo in todos" v=if="!todo.isComplete">
    {{ todo.name }}
</li>

Vue 3

<template v-for="todo in todos">
    <li v-if="!todo.isComplete">
        {{ todo.name }}
    </li>
</template>

Order of bindings for v-bind now affects the rendering result.

Vue 2

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>

Vue 3

<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>

<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>

The native modifier for v-on has been removed.

Vue 2

<ChildComponent
    v-on:close="handleComponentEvent"
    v-on:click.native="handleNativeClickEvent"
/>

Vue 3

// ChildComponent.vue

export default {
    emits: ['close']
}

<!-- ParentComponent.vue -->

<ChildComponent
    v-on:close="handleComponentEvent"
    v-on:click="handleNativeClickEvent"
/>