The implementation method of template references
Basic Concepts of Template Refs
In Vue 3, template refs allow direct access to DOM elements or component instances. By using the ref
attribute in templates, you can obtain the corresponding reference in the Composition API or Options API. This mechanism is particularly useful when direct DOM manipulation or access to child components is needed.
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
How the ref
Attribute Works
When Vue processes templates, it handles the ref
attribute specially during compilation. The compilation phase converts ref
into corresponding runtime code, primarily in two scenarios:
- When used on a regular DOM element: assigns the DOM element to the corresponding ref value.
- When used on a component: assigns the component instance to the ref value.
The compiled code roughly transforms into a structure like this:
const _ctx = this
const { ref: _ref } = Vue
return function render() {
with (_ctx) {
return _createVNode('input', { ref: inputRef }, null, 512 /* NEED_PATCH */)
}
}
Implementation Details of Template Refs
Internally, Vue 3 handles template refs through the setRef
function, which is called during the patching process. The key implementation logic resides in runtime-core/src/renderer.ts
:
const setRef = (
rawRef: VNodeNormalizedRef,
oldRawRef: VNodeNormalizedRef | null,
parentComponent: ComponentInternalInstance,
vnode: VNode
) => {
if (isRef(rawRef)) {
// Handle ref object
if (oldRawRef) {
if (rawRef === oldRawRef) return
setRef(oldRawRef, null, parentComponent, null)
}
const value = vnode.component?.proxy || vnode.el
rawRef.value = value
} else if (typeof rawRef === 'function') {
// Handle callback ref
const value = vnode.component?.proxy || vnode.el
rawRef(value, oldValue => {
if (oldRawRef) setRef(oldRawRef, null, parentComponent, null)
})
}
}
Special Handling for Component Refs
When ref
is used on a component, Vue returns the component instance instead of the DOM element. This allows the parent component to directly call child component methods or access its state:
<template>
<ChildComponent ref="child" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const child = ref(null)
function callChildMethod() {
child.value.someMethod()
}
</script>
During component instantiation, Vue controls what can be accessed by external refs via the expose
function:
// In child component
export default {
setup(props, { expose }) {
const internalState = ref(0)
const publicMethod = () => {
// ...
}
expose({
publicMethod
})
return {
internalState // Not exposed
}
}
}
Dynamic Template Refs
Vue 3 supports dynamic ref names, which is particularly useful in loops or conditional rendering:
<template>
<div v-for="item in list" :key="item.id">
<input :ref="el => { if (el) inputs[item.id] = el }" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([{ id: 1 }, { id: 2 }])
const inputs = ref({})
</script>
Functional Refs
In addition to ref objects, you can use function-style refs, which offer more flexibility in certain scenarios:
<template>
<input :ref="(el) => { if (el) inputEl = el }" />
</template>
<script setup>
let inputEl = null
</script>
Functional refs are called during each update and pass null
when the element is unmounted. Vue's internal handling of functional refs is as follows:
if (isRef(newRef)) {
// Handle ref object
} else if (typeof newRef === 'function') {
// Handle functional ref
try {
newRef(vnode.component ? vnode.component.proxy : vnode.el)
} catch (e) {
handleError(e, parentComponent, ErrorCodes.FUNCTION_REF)
}
}
Template Refs and Rendering Order
The timing of template ref assignments is closely related to component lifecycle and rendering order. In the Composition API, note:
<template>
<div ref="divRef">{{ message }}</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const divRef = ref(null)
const message = ref('Hello')
onMounted(() => {
console.log(divRef.value.textContent) // Output: "Hello"
})
</script>
Reactive Refs vs. Template Refs
Although template refs themselves are reactive, their values are not. This means directly observing changes to ref.value
won't trigger reactive updates:
watch(
() => inputRef.value,
(newVal) => {
// Won't trigger when inputRef.value changes
console.log('ref changed', newVal)
}
)
To observe DOM property changes, use MutationObserver
or specialized libraries.
Template Refs in Server-Side Rendering
In SSR environments, template refs behave differently. Since there's no actual DOM, ref.value
will be null
on the server:
onMounted(() => {
// Execute only on client side
if (import.meta.env.SSR) return
inputRef.value.focus()
})
Performance Considerations and Best Practices
- Avoid excessive template refs in large lists.
- Consider using event delegation instead of direct DOM manipulation.
- Clean up timers and event listeners when components unmount.
onBeforeUnmount(() => {
// Cleanup
if (inputRef.value) {
inputRef.value.removeEventListener('input', handler)
}
})
Interaction with Other Features
Template refs can work with other Vue features like v-model
and custom directives:
<template>
<input
ref="inputRef"
v-model="text"
v-focus
/>
</template>
<script setup>
const text = ref('')
const inputRef = ref(null)
const vFocus = {
mounted: el => el.focus()
}
</script>
Deep Dive into Underlying Implementation
The core logic for handling refs in Vue's virtual DOM patching process resides in the patch
function:
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = false
) => {
// ...
if (n1 == null) {
// Mount new node
if (n2.ref !== null) {
setRef(n2.ref, null, parentComponent, n2)
}
} else {
// Update existing node
if (n1.ref !== n2.ref) {
setRef(n1.ref, null, parentComponent, null)
if (n2.ref !== null) {
setRef(n2.ref, n1.ref, parentComponent, n2)
}
}
}
// ...
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:响应式状态在组件间的共享
下一篇:异步组件的加载机制