In Vue 3, when is a function ref such as `:ref="(el) => ..."` called, and what arguments does it receive?
answer
- dynamic :ref binding
- element or instance first
- null on unmount
- runs on every update
- you choose where to store it
basics
~20 sA function bound with :ref is called with the element or component instance when it mounts, again on each update of that element, and with null when it unmounts. You decide where to store the value, which suits maps keyed by id.
solid answer
~40 sInstead of a string key, `:ref` can be bound to a function. Vue calls it with the **element** (or, on a component, the component's public instance) when the node mounts, calls it **again on every update** of that node, and calls it with `null` when the node unmounts. It also receives the owner's refs object as a second argument, though you rarely need it. Unlike string refs, the function is invoked during the patch itself rather than assigned as a post-render step. The value of a function ref is flexibility: you can store elements in a `Map` keyed by item id inside `v-for`, register them with a library, or ignore them conditionally. Because it runs on every update, keep it cheap and idempotent, and always handle the `null` call.
code
vue · 23 lines<script setup>
import { ref } from 'vue'
const fields = ref([{ id: 'name' }, { id: 'email' }])
const inputById = new Map()
function setInput(id, el) {
if (el) inputById.set(id, el)
else inputById.delete(id)
}
function focusField(id) {
inputById.get(id)?.focus()
}
</script>
<template>
<input
v-for="f in fields"
:key="f.id"
:ref="(el) => setInput(f.id, el)"
/>
</template>go deeper
Remember that :ref can take a function that receives the element, and null when it goes away.
Explain the call moments, mount, each update and unmount, and why the function must be idempotent.
Use function refs to build id-keyed maps for lists and to register nodes with external libraries without leaks.
Judge when a function ref, a directive or a small component API is the right boundary for imperative DOM work.
## What a function ref is The `ref` attribute usually takes a string key. With a **dynamic binding**, `:ref`, you can pass a function instead: ```vue-html <input :ref="(el) => { searchEl = el }" /> ``` Vue then calls the function rather than writing into a ref for you. The colon matters: `ref="setEl"` without it is a string key named `setEl`, not a function call. ## When Vue calls it The runtime invokes a function ref at three kinds of moment: | Moment | First argument | |---|---| | The element or component mounts | the element, or the component's public instance | | The owning component re-renders and patches that node | the same element again | | The node unmounts (for example `v-if` turns false) | `null` | The Vue guide describes it as a function that "will be called on each component update". Two practical consequences follow: - It is **not** a one-time mount callback. If you do work in it, such as attaching a listener, you repeat that work on every update unless you guard it. - An inline arrow is a new function on every render, which is fine, because Vue simply calls whichever function the current render supplies. The function also receives a second argument, the owner component's refs object, which is rarely useful in `<script setup>` code. ## Timing compared with string refs For a string key or a ref object, Vue queues the assignment of a non-null value to run after rendering, so the value is ready when `onMounted` or `onUpdated` callbacks run. A function ref is called directly during the patch of that node. In practice both are ready for use in `onMounted`; the difference matters only if your function assumes that sibling elements elsewhere in the template are already in place. ## What the argument is for components On a component, the argument is its public instance. For a `<script setup>` child that instance shows only what the child passed to `defineExpose`, plus built-in instance properties such as `$el`. The same `null` on unmount applies. ## Typical uses 1. **Keyed maps in `v-for`.** A ref array's order is not guaranteed, so a function ref can store each element under its item's id: ```js const rowById = new Map() const setRow = (id, el) => (el ? rowById.set(id, el) : rowById.delete(id)) ``` 2. **Handing elements to a non-Vue library** that needs registration and deregistration: register on a non-null element you have not seen, deregister on `null`. 3. **Conditional storage**, such as keeping only the first invalid field of a form to focus it. ## Writing a safe function ref - **Handle `null`.** Every function ref must expect it on unmount, or it throws or leaks stale nodes. - **Be idempotent.** Repeated calls with the same element must not duplicate work: `Map.set` is naturally idempotent, `addEventListener` with a fresh arrow is not. - **Keep it cheap.** It runs on each update of the node. - **Do not trigger state you render from.** Writing reactive state that the same template reads can cause another render, which calls the function again. ## When not to use one If you need a single element, `useTemplateRef` (Vue 3.5) is clearer. If you need behaviour attached to an element's lifecycle, a custom directive with `mounted` and `unmounted` hooks may express it better. Function refs are best when you need **control over where** the reference goes. ## Pairing registration with the null call When a function ref hands elements to an external library, the `null` call is your cleanup signal, but it carries no element. Remember the last element you saw so you can release it: ```js let current = null function bindTooltip(el) { if (el === current) return // repeated update call if (current) tooltips.detach(current) // element replaced or removed current = el if (el) tooltips.attach(el) } ``` This handles all three moments: the first mount attaches, repeated update calls with the same element return early, and the unmount call with `null` detaches the stored element. `tooltips` stands for any imperative library; the pattern, not the library, is the point.
- Why is `el.addEventListener('input', handler)` inside a Vue 3 function ref a bug?The function ref runs again on each update of that element, so a fresh arrow handler would be added every time and never removed. Register listeners with `@input` in the template, or guard registration so it happens once per element and is undone when the function receives `null`.
- In Vue 3, what is the difference between `ref="setEl"` and `:ref="setEl"`?Without the colon, `setEl` is a literal string key, so Vue looks for a ref named `setEl` to write into. With `:ref`, the expression is evaluated and, since it is a function, Vue calls it with the element, on updates, and with `null` on unmount.
saying these in an interview costs you the question
- Thinks a function ref runs only once, on mount
- Forgets that the function receives null on unmount
- Writes ref="fn" without the colon and expects a call
- Attaches event listeners inside a function ref without a guard
- Believes Vue calls the previous inline function with null on every render