Перейти к содержанию

Ссылки на элементы шаблона

Хотя декларативная модель отрисовки Vue абстрагирует большую часть прямых операций с DOM, всё же могут быть случаи, когда нам понадобится прямой доступ к базовым элементам DOM. Для этого мы можем использовать специальный атрибут ref:

template
<input ref="input">

ref — это специальный атрибут, аналогичный атрибуту key, о котором говорилось в главе v-for. Он позволяет получить прямую ссылку на конкретный элемент DOM или экземпляр дочернего компонента после его установки. Это может быть полезно, когда вы хотите, например, программно сфокусировать ввод на компоненте mount или инициализировать стороннюю библиотеку на элементе.

Доступ к рефам

Чтобы получить реф с помощью Composition API, используйте хелпер useTemplateRef() :

vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
// первый аргумент должен соответствовать значению ref в шаблоне
const input = useTemplateRef('my-input')
onMounted(() => {
  input.value.focus()
})
</script>
<template>
  <input ref="my-input" />
</template>

При использовании TypeScript поддержка IDE Vue и vue-tsc будут автоматически определять тип input.value на основе того, в каком элементе или компоненте используется соответствующий атрибут ref.

Использование до версии 3.5

В версиях до 3.5, где useTemplateRef() не была введена, нам нужно объявить реф с именем, которое соответствует значению атрибута ссылки на элемент шаблона:

vue
<script setup>
import { ref, onMounted } from 'vue'

// объявляем ref для хранения ссылки на элемент шаблона
// имя должно соответствовать значению атрибута ref в разметке
const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>

Если вы не используете <script setup>, не забудьте также вернуть реф из setup():

js
export default {
  setup() {
    const input = ref(null)
    // ...
    return {
      input
    }
  }
}

Полученная ссылка доступна через this.$refs:

vue
<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
</script>

<template>
  <input ref="input" />
</template>

Обратите внимание, что вы можете получить доступ к ссылке только после того, как компонент смонтирован. Если вы попытаетесь получить доступ к $refs.inputinput в выражении шаблона это будет undefinenull при первой отрисовке. Это потому, что элемент не существует до первой отрисовки!

Если вы пытаетесь следить за изменениями ссылки на шаблон, не забудьте учесть случай, когда ссылка имеет значение null:

js
watchEffect(() => {
  if (input.value) {
    input.value.focus()
  } else {
    // ещё не смонтирован, или элемент был размонтирован (например, с помощью v-if)
  }
})

Смотрите также: Типизация ссылок на элементы шаблона

Ссылка на компонент

Этот раздел предполагает знание Компонентов. Не стесняйтесь пропустить его и вернуться позже.

ref можно использовать и для дочернего компонента. В этом случае ссылка будет принадлежать экземпляру компонента:

vue
<script setup>
import { useTemplateRef, onMounted } from 'vue'
import Child from './Child.vue'
const childRef = useTemplateRef('child')
onMounted(() => {
  // childRef.value будет содержать экземпляр <Child />
})
</script>
<template>
  <Child ref="child" />
</template>
Использование до версии 3.5
vue
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'

const child = ref(null)

onMounted(() => {
  // child.value будет содержать экземпляр <Child />
})
</script>

<template>
  <Child ref="child" />
</template>
vue
<script>
import Child from './Child.vue'

export default {
  components: {
    Child
  },
  mounted() {
    // this.$refs.child будет содержать экземпляр <Child />
  }
}
</script>

<template>
  <Child ref="child" />
</template>

Если дочерний компонент использует Options API или не использует <script setup>, то экземплярЭкземпляр, на который ссылаются, будет идентичен this дочернего компонента, что означает, что родительский компонент будет иметь полный доступ к каждому свойству и методу дочернего компонента. Это позволяет легко создавать тесно связанные детали реализации между родительским и дочерним компонентами, поэтому ссылки на компоненты следует использовать только в случае крайней необходимости — в большинстве случаев вы должны попытаться реализовать взаимодействие родитель/потомок, используя стандартные интерфейсы props и emit.

Исключением является то, что компоненты, использующие <script setup>, по умолчанию являются приватными: Родительский компонент, ссылающийся на дочерний компонент с помощью <script setup>, не сможет получить доступ ни к чему, если дочерний компонент не решит раскрыть публичный интерфейс с помощью макроса defineExpose:

vue
<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

// Макросы компилятора, такие как defineExpose, не нужно импортировать
defineExpose({
  a,
  b
})
</script>

Когда родитель получает экземпляр этого компонента через ссылки шаблона, полученный экземпляр будет иметь форму { a: число, b: число } (ссылки автоматически разворачиваются, как и в обычных экземплярах).

Обратите внимание, что defineExpose должен быть вызван до любой операции await. В противном случае свойства и методы, предоставленные после операции await, будут недоступны.

Смотрите также: Типизация ссылок на компоненты

Опция expose может быть использована для ограничения доступа к дочернему экземпляру:

js
export default {
  expose: ['publicData', 'publicMethod'],
  data() {
    return {
      publicData: 'foo',
      privateData: 'bar'
    }
  },
  methods: {
    publicMethod() {
      /* ... */
    },
    privateMethod() {
      /* ... */
    }
  }
}

В приведённом выше примере родитель, ссылающийся на этот компонент через шаблон ref, сможет получить доступ только к publicData и publicMethod.

Рефы внутри v-for

Требуется версия 3.5 или выше

Когда ref используется внутри v-for, он должен содержать массив, который будет заполнен элементами после монтирования:

vue
<script setup>
import { ref, useTemplateRef, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = useTemplateRef('items')

onMounted(() => console.log(itemRefs.value))
</script>
<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

Попробовать в Песочнице

Использование до версии 3.5

В версиях до 3.5, где useTemplateRef() не был введён, нам нужно объявить реф с именем, которое соответствует значению атрибута ссылки на элемент шаблона. Реф также должен содержать массив значений:

vue
<script setup>
import { ref, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = ref([])

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

Когда ref используется внутри v-for, результирующее значение ref будет представлять собой массив, содержащий соответствующие элементы:

vue
<script>
export default {
  data() {
    return {
      list: [
        /* ... */
      ]
    }
  },
  mounted() {
    console.log(this.$refs.items)
  }
}
</script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

Попробовать в Песочнице

Следует отметить, что массив ref не гарантирует тот же порядок, что и исходный массив.

Ссылка на функцию

Вместо строкового ключа атрибут ref можно привязать к функции, которая будет вызываться при каждом обновлении компонента и обеспечит вам полную гибкость в выборе места хранения ссылки на элемент. В качестве первого аргумента функция получает ссылку на элемент:

template
<input :ref="(el) => { /* присвоить el свойству или ссылке */ }">

Обратите внимание, что мы используем динамическую привязку :ref, поэтому мы можем передать ей функцию вместо строки с именем ref. Когда элемент будет размонтирован, аргумент станет null. Конечно, вы можете использовать метод вместо встроенной функции.

Ссылки на элементы шаблона