Migration to Vue 3, Part 4. – Render Function

h is now globally imported instead of passed to render function as an argument.

Vue 2

export default {
    render(h) {
        return h('div')
    }
}

Vue 3

import { h } from 'vue'

export default {
    render() {
        return h('div')
    }
}

VNodes now have a flat props structure.

Vue 2

{
    staticClass: 'button',
    class: { 'is-outlined': isOutlined },
    staticStyle: { color: '#34495E' },
    style: { backgroundColor: buttonColor },
    attrs: { id: 'submit' },
    domProps: { innerHTML: '' },
    on: { click: submitForm },
    key: 'submit-button'
}

Vue 3

{
    class: ['button', { 'is-outlined': isOutlined }],
    style: [{ color: '#34495E' }, { backgroundColor: buttonColor }]
    id: 'submit',
    innerHTML: '',
    onClick: submitForm,
    key: 'submit-button'
}

String ID can no longer be used to implicitly lookup registered components.

Vue 2

Vue.component('button-counter', {
    data() {
        return {
            count: 0
        }
    }
    template: `
        <button @click="count++">
            Clicker {{ count }} times.
        </button>
    `
})

export default {
    render(h) {
        return h('button-counter')
    }
}

Vue 3

import { h, resolveComponent } from 'vue'

export default {
    setup() {
        const ButtonCounter = resolveComponent('button-counter')
        return () => h(ButtonCounter)
    }
}

Slots are defined as children of the current node as an object.

Vue 2

h(LayoutComponent, [
    h('div', { slot: 'header' }, this.header),
    h('div', { slot: 'content' }, this.content),
])

Vue 3

h(LayoutComponent, {}, {
    header: () => h('div', this.header),
    content: () => h('div', this.content),
})

Scoped slots are now unified into the $slots option.

Vue 2

this.$scopedSlots.header

Vue 3


$listeners object has been removed – event listeners are now part of $attrs.

Vue 2

<!-- template -->

<label>
    <input type="text" v-bind="$attrs" v-on="$listeners" />
</label>

// script

export default {
    inheritAttrs: false
}

Vue 3

<!-- template -->

<label>
    <input type="text" v-bind="$attrs" />
</label>

// script

export default {
    inheritAttrs: false
}

$attrs now contains all attributes passed to a component, including class and style.

Vue 2

<!-- ChildComponent.vue - template -->

<label>
    <input type="text" v-bind="$attrs" />
</label>

// ChildComponent.vue - script

export default {
    inheritAttrs: false
}


<!-- ParentComponent.vue - template -->

<ChildComponent id="my-id" class="my-class" />

<!-- output -->

<label class="my-class">
    <input type="text" id="my-id" />
</label>

Vue 3

<!-- ChildComponent.vue - template -->

<label>
    <input type="text" v-bind="$attrs" />
</label>

// ChildComponent.vue - script

export default {
    inheritAttrs: false
}


<!-- ParentComponent.vue - template -->

<ChildComponent id="my-id" class="my-class" />

<!-- output -->

<label>
    <input type="text" id="my-id" class="my-class" />
</label>