TL;DR: This SFC playground shows how to use ✨function refs✨ to easily access elements rendered by a render function in Vue 3.
In Vue 3, a neat new feature is that you can return a render function from the setup
option.
This means that your render functions can reference your methods and reactive data directly, all within the scope of the same JavaScript function, without using this
.
// MyComponent.ts
import { defineComponent, h, ref } from 'vue'
export default defineComponent({
setup () {
const myReactiveData = ref(...)
// Return a function that renders a template with the
// help of the `h` function.
return () => h(
'div',
{
// Bind your data to an attribute reactively. Access
// the data directly in scope. No need to use `this`!
someAttribute: myReactiveData.value,
// When the component re-renders, the attribute will
// receive the most up-to-date value of your reactive
// data.
},
'...',
)
}
})
In some cases, though you need to access a template element from within your setup
function.
First, let's look at how this is done with a normal Vue template:
<!-- MyComponent.vue -->
<script lang="ts">
import { defineComponent, h, ref, onMounted } from 'vue'
export default defineComponent({
setup () {
// First, set up an empty reactive reference
const el = ref()
onMounted(() => {
// After the component is mounted, we can easily access
// the DOM element via the reactive reference.
console.log(el.value)
})
// Return that reference from `setup`
return { el }
}
})
</script>
<template>
<!--
In your template, set the `ref` attribute to the name
of that reactive reference.
-->
<div ref="el">
</template>
So, in a normal Vue template, we pass a string to the ref
attribute of a template element.
Does this work the same way with render functions? The answer is no. If you try to assign a string to the ref
attribute in a render function, nothing will happen:
// MyComponent.ts
import { defineComponent, h, ref, onMounted } from 'vue'
export default defineComponent({
setup () {
const el = ref()
onMounted(() => console.log(el.value))
return () => h(
'div',
{
// If you set `ref` to the string 'el', just like you
// would with a normal Vue template, nothing will happen.
ref: 'el',
// When the component is mounted, `el.value` will be
// `undefined`.
},
'...',
)
}
})
How do we solve this problem? The answer is my favorite Vue 3 feature: ✨function refs✨.
If you bind a function to the ref
attribute of any element, Vue will call that function each time the element re-renders, passing the DOM element as the first argument.
Let's see how that works in a normal Vue template:
<!-- MyComponent.vue -->
<script lang="ts">
import { defineComponent, h, ref, onMounted } from 'vue'
export default defineComponent({
setup () {
// Set up the empty reactive reference
const el = ref()
// Define a function that accepts one argument,
// and sets the value of `el` to that argument.
//
// This function is called a ✨function ref✨.
const setEl = e => el.value = e
onMounted(() => console.log(el.value))
// Return the function ref from `setup`
return { setEl }
}
})
</script>
<template>
<!--
In your template, bind `setEl` to the `ref` attribute.
-->
<div :ref="setEl">
</template>
Interesting—the function ref is equally effective in giving us easy access to elements in our template.
And in fact, function refs work exactly the same way in render functions. Instead of assigning a string to ref
in a render function, assign a function ref instead.
// MyComponent.ts
import { defineComponent, h, ref, onMounted } from 'vue'
export default defineComponent({
setup () {
const el = ref()
const setEl = e => el.value = e
onMounted(() => console.log(el.value))
return () => h(
'div',
{
// Bind `setEl` to the `ref` attribute. Vue will
// call `setEl` each time the element re-renders,
// ensuring that you always have easy access to the
// DOM element.
ref: setEl,
},
'...',
)
}
})
This is only one of the many interesting use cases for function refs. If you're interested in learning more, check out my YouTube playlist dedicated to function refs in Vue 3.