阿里云主机折上折
  • 微信号
Current Site:Index > The implementation method of template references

The implementation method of template references

Author:Chuan Chen 阅读数:27228人阅读 分类: Vue.js

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:

  1. When used on a regular DOM element: assigns the DOM element to the corresponding ref value.
  2. 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

  1. Avoid excessive template refs in large lists.
  2. Consider using event delegation instead of direct DOM manipulation.
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.