Vue.js: Refs e DOM

Última atualização: 2026-08-26

As referências de modelo permitem que você acesse diretamente elementos do DOM ou instâncias de componentes filhos — por exemplo, chamando input.focus() ou acessando métodos de componentes. O Vue 3.5 introduziu a API de Composição useTemplateRef, mais poderosa, que funciona em conjunto com a inferência de tipos do TypeScript.

Os Template Refs são um “último recurso” — o Vue recomenda o uso de refs, reactive, props e emit para resolver a maioria dos problemas; os Template Refs só devem ser usados quando você precisar manipular diretamente o DOM ou uma instância de componente.

1. O que você vai aprender


2. O dilema do “foco automático” em um formulário de login

(1) Problema: Como fazer com que o campo de entrada receba o foco automaticamente quando a janela modal for aberta?

Alice criou um modal de login que deveria colocar o foco automaticamente no campo de nome de usuário:

JS
// ❌ The "Broken" Version:Directly querySelector
onMounted(() => {
  const input = document.querySelector('.username-input')
  input.focus()  // ❌ Does not meet the requirements Vue Philosophy
})

A filosofia do Vue: evite a manipulação direta do DOM. Em vez disso, use Template Refs.

O gerente de produto Charlie:

“Alice, quando a janela modal abrir, o campo do nome de usuário deve receber o foco automaticamente para que os usuários possam começar a digitar imediatamente.”

(2) Solução para referências de modelos de visualização

VUE
<template>
  <!-- ref="usernameInput" Mark this element -->
  <input ref="usernameInput" type="text" class="username-input">
</template>

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

// 1. Create ref Variable(The name must match the template ref Match)
const usernameInput = ref(null)

onMounted(() => {
  // 2. DOM Ready,Visit input Element
  usernameInput.value.focus()
})
</script>
VUE
<!-- Pop-up Scenarios:Click the button to open modal,Automatic focus -->
<template>
  <button @click="showModal = true">Login</button>
  
  <Modal v-if="showModal" @close="showModal = false">
    <input ref="usernameInput" type="text">
  </Modal>
</template>

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

const showModal = ref(false)
const usernameInput = ref(null)

async function openModal() {
  showModal.value = true
  // ✅ Wait for DOM update, then focus
  await nextTick()
  usernameInput.value.focus()
}
</script>

(3) Receita

Após usar as referências de modelo:


3. Uso básico do ref

(1) Referência de string (estilo Vue 2, obsoleta)

JS
// ❌ Not recommended:String ref
export default {
  mounted() {
    this.$refs.input.focus()
  }
}

(2) A variável ref (recomendada no Vue 3)

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

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

const inputRef = ref(null)

onMounted(() => {
  console.log(inputRef.value)  // <input> DOM Element
  inputRef.value.focus()        // Call DOM API
})
</script>

(3) 5 Operações básicas

JS
// 1. Visit DOM Element
inputRef.value  // <input> Element

// 2. Call DOM API
inputRef.value.focus()
inputRef.value.blur()
inputRef.value.select()
inputRef.value.scrollIntoView()

// 3. Read/Edit DOM Properties
inputRef.value.value  // input value
inputRef.value.disabled  // disabled Properties
inputRef.value.style.color = 'red'  // Edit Style

// 4. Monitoring DOM Events (Not recommended, use @event)
inputRef.value.addEventListener('focus', handler)

// 5. Accessing a Child Component Instance(For more details, see 17.4)
childRef.value.someMethod()

4. useTemplateRef (Recomendado para Vue 3.5+)

(1) Por que o useTemplateRef é necessário?

A variável ref em <script setup> requer dois nomes (o modelo ref e a variável), o que pode facilmente levar a inconsistências. useTemplateRef lida com isso usando um único nome, e a inferência de tipos do TypeScript é mais poderosa.

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

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

// ✅ One Name Does It All(Vue 3.5+)
const inputRef = useTemplateRef('usernameInput')

onMounted(() => {
  inputRef.value.focus()  // Type automatically inferred as HTMLInputElement
})
</script>

(2) 5 principais vantagens

Vantagem Descrição
Inferência de tipo O TypeScript reconhece automaticamente como um HTMLInputElement
Renomeação segura Altere um nome em um único lugar; o IDE sincroniza a alteração
Como evitar incompatibilidades de nomes Ocorrem erros quando as referências de modelo e os nomes das variáveis não coincidem
Configuração simplificada Não é necessário o const inputRef = ref(null)
DevTools aprimoradas Suporte ao Vue DevTools 5.x

(3) Comparação completa

VUE
<!-- Old notation:Variable ref -->
<template>
  <input ref="usernameInput">
</template>

<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)  // The name may not match the template.
onMounted(() => inputRef.value.focus())
</script>

<!-- New Writing Style:useTemplateRef(Vue 3.5+ Recommendations)-->
<template>
  <input ref="usernameInput">
</template>

<script setup>
import { useTemplateRef, onMounted } from 'vue'
const inputRef = useTemplateRef('usernameInput')  // One Name Does It All
onMounted(() => inputRef.value.focus())
</script>

5. Acessando uma instância de um componente filho

(1) defineExpose: Método Expose

VUE
<!-- Child component:MyInput.vue -->
<template>
  <input ref="inputRef" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>

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

const props = defineProps({ modelValue: String })
const inputRef = ref(null)

// Exposed for use by the parent component
defineExpose({
  focus: () => inputRef.value?.focus(),
  select: () => inputRef.value?.select(),
  clear: () => { inputRef.value.value = '' }
})
</script>

(2) Acesso ao componente pai

VUE
<template>
  <MyInput ref="myInputRef" v-model="searchQuery" />
  <button @click="focusInput">Focus Input</button>
</template>

<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'

const searchQuery = ref('')
const myInputRef = ref(null)

function focusInput() {
  myInputRef.value.focus()  // Calling Methods Exposed by Child Components
  myInputRef.value.select() // You can also use chained calls
}
</script>

(3) 5 principais casos de uso

Cenário Exposto pelo componente filho Chamado pelo componente pai
Destaque do formulário focus() inputRef.focus()
Limpar formulário clear() formRef.clear()
Atualizar dados reload() tableRef.reload()
Abrir janela pop-up open() modalRef.open()
Enviar formulário submit() formRef.submit()

6. A matriz ref em v-for

(1) Uso básico

VUE
<template>
  <ul>
    <!-- In v-for, ref auto-collected into array -->
    <li v-for="item in items" :key="item.id" ref="itemRefs">
      {{ item.name }}
    </li>
  </ul>
</template>

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

const items = ref([
  { id: 1, name: 'Apple' },
  { id: 2, name: 'Banana' },
  { id: 3, name: 'Cherry' }
])

// ✅ Array Format
const itemRefs = ref([])

onMounted(() => {
  // Page 2 Items DOM
  itemRefs.value[1].style.color = 'red'
})
</script>

(2) Referência dinâmica (v-for com contagem dinâmica)

VUE
<template>
  <button v-for="i in count" :key="i" :ref="el => buttonRefs[i] = el">
    Button {{ i }}
  </button>
</template>

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

const count = ref(3)
const buttonRefs = ref({})

onMounted(() => {
  // buttonRefs[0] = 1st button
  // buttonRefs[1] = 2nd button
  console.log(buttonRefs.value[0])
})
</script>

(3) 5 pontos importantes a serem lembrados

Pontos a serem observados Descrição
Ordem da matriz Corresponde à ordem dos dados no v-for
Reativo Alterações na matriz ref exigem um observador
Renderização condicional as referências podem não ser atualizadas após o v-if
Número de variáveis dinâmicas Uso de referência a objeto ou função
Desempenho Um grande número de referências (mais de 100) pode tornar a renderização mais lenta

7. Exemplos completos: 5 cenários do mundo real

▶ Exemplo: 1. Foco automático no formulário de login

VUE
<template>
  <form @submit.prevent="handleLogin">
    <input ref="usernameRef" v-model="username" placeholder="Username">
    <input ref="passwordRef" v-model="password" type="password" placeholder="Password">
    <button>Login</button>
  </form>
</template>

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

// ❌ Old notation
const usernameRef = ref(null)
const passwordRef = ref(null)

// ✅ New Writing Style(Vue 3.5+)
// const usernameRef = useTemplateRef('usernameRef')
// const passwordRef = useTemplateRef('passwordRef')

const username = ref('')
const password = ref('')

onMounted(() => {
  usernameRef.value.focus()  // Autofocus Username
})

function handleLogin() {
  console.log('Login:', username.value, password.value)
}
</script>
▶ Experimente

▶ Exemplo: 2. Rolagem automática até o final da página

VUE
<template>
  <div ref="messagesRef" class="messages">
    <div v-for="msg in messages" :key="msg.id">{{ msg.text }}</div>
  </div>
  <input v-model="newMessage" @keyup.enter="sendMessage">
  <button @click="sendMessage">Send</button>
</template>

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

const messages = ref([])
const newMessage = ref('')
const messagesRef = ref(null)

async function sendMessage() {
  messages.value.push({ id: Date.now(), text: newMessage.value })
  newMessage.value = ''
  
  // ✅ Wait for DOM update, then scroll
  await nextTick()
  messagesRef.value.scrollTop = messagesRef.value.scrollHeight
}
</script>
▶ Experimente

▶ Exemplo: 3. Componente pai chamando um método de um componente filho

VUE 📖 Somente leitura
<!-- Child component:FormValidator.vue -->
<template>
  <form>
    <input v-model="email" placeholder="Email">
    <input v-model="password" type="password" placeholder="Password">
  </form>
</template>

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

const email = ref('')
const password = ref('')

const emailRef = ref(null)
const passwordRef = ref(null)

defineExpose({
  validate: () => {
    if (!email.value) {
      emailRef.value.focus()
      return false
    }
    if (!password.value || password.value.length < 6) {
      passwordRef.value.focus()
      return false
    }
    return true
  },
  reset: () => {
    email.value = ''
    password.value = ''
  }
})
</script>

<!-- Parent Component:LoginPage.vue -->
<template>
  <FormValidator ref="formRef" />
  <button @click="submit">Submit</button>
</template>

<script setup>
import { ref } from 'vue'
import FormValidator from './FormValidator.vue'

const formRef = ref(null)

function submit() {
  if (formRef.value.validate()) {
    console.log('Valid!')
  } else {
    console.log('Invalid!')
  }
}
</script>
46 linhas de lógica (limite de 40, somente leitura)

▶ Exemplo: 4. Inferência de tipo para useTemplateRef

VUE
<template>
  <input ref="usernameInput" type="text">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

<script setup lang="ts">
import { ref, useTemplateRef, onMounted } from 'vue'
import MyChart from './MyChart.vue'

// ✅ TypeScript Inference:HTMLInputElement | null
const usernameInput = useTemplateRef<HTMLInputElement>('usernameInput')

// ✅ TypeScript Inference:InstanceType<typeof MyChart> | null
const chartComponent = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

onMounted(() => {
  // usernameInput.value Automatic is HTMLInputElement
  usernameInput.value?.focus()
  
  // chartComponent.value "Auto" is a component instance
  chartComponent.value?.refresh()
})
</script>
▶ Experimente

▶ Exemplo: 5. Referência rápida para 5 erros comuns

Erro Sintoma Solução
Erro ortográfico na referência do modelo o valor da referência é nulo Verifique se os nomes das referências correspondem
Acesso no nível superior setup ref.value é nulo Use onMounted
Acesso dentro de uma janela pop-up Não funciona Use nextTick ou métodos DOM semelhantes
Acesso à matriz de referência no v-for Desalinhamento de índices Use :key para preservar a ordem
Referência entre componentes indefinido Os componentes filhos usam defineExpose

▶ Exemplo: 6. Comparação dos 5 principais tipos de referência

Tipo Exemplo Aplicável a
Elemento DOM ref="inputRef" → HTMLInputElement Acessar input/div
Instância do componente ref="childRef" → Instância do componente Métodos do componente de tom
v-for array ref="itemRefs" → Array Acessando itens da lista
Referência funcional :ref="el => ..." → Elemento único Contagem dinâmica
Referência de string ref="name" → this.$refs Estilo da Visualização 2 (não recomendado)

❓ Perguntas Frequentes

P: Quando se deve usar Template Refs? R: Em quatro situações: (1) manipulação do DOM (foco/rolagem/canvas); (2) chamada de métodos de componentes; (3) integração de bibliotecas de terceiros (ECharts/Mapbox); (4) medição das dimensões dos elementos. Em outros cenários, use refs, reactive ou props.

P: O useTemplateRef é obrigatório no Vue 3.5+? R: Sim. Ele só é compatível com o Vue 3.5+. No Vue 3.4 e versões anteriores, use a variável ref. É preferível usar a inferência de tipos do TypeScript.

P: Qual é a diferença entre Template Refs e useRef (React)? R: O useRef do React retorna uma ref mutável, enquanto a template ref do Vue retorna um objeto ref. O useTemplateRef do Vue 3.5+ possui uma API mais próxima da do useRef do React.

P: Quais métodos um componente filho deve expor? R: Exponha apenas os métodos de que o componente pai realmente precisa (como focus, clear e validate). Não exponha outros métodos internos (princípio da encapsulação).

P: Quando a matriz ref em v-for é atualizada? R: Ela é atualizada sempre que v-for é renderizada novamente. Também é atualizada quando v-if é ativada ou desativada. Você pode usar watch na matriz ref para responder às alterações.

P: As Template Refs entram em conflito com o provide/inject? R: Não, não entram. As Template Refs são usadas para o “acesso de pai para filho”, enquanto o provide/inject é usado para o “compartilhamento de dados entre níveis”. Elas podem ser usadas em conjunto.

P: Como faço para adicionar um tipo a uma referência no TypeScript? R: Use useTemplateRef<HTMLInputElement>('usernameInput'). Ou ref<HTMLInputElement | null>(null).


📖 Resumo


📝 Exercícios

  1. Questões básicas (Dificuldade: ⭐)

    Implemente um formulário simples de foco automático:

    • 1 campo de entrada + 1 botão
    • O campo de entrada recebe o foco automaticamente após o carregamento da página
    • Quando o botão é clicado, o texto do botão muda para “Enviado” e o botão fica desativado
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Implementar uma janela de bate-papo que role automaticamente até o final da página:

    • Lista de mensagens (contêiner div)
    • Campo de entrada + botão “Enviar”
    • Depois que uma mensagem é enviada, ela é adicionada à lista e a tela rola automaticamente até o final
    • Use v-for para renderizar mensagens e uma matriz ref para gerenciar o DOM
  3. Problema desafiador (Dificuldade: ⭐⭐⭐)

    Implementar um sistema completo de “componentes pai-filho + Template Refs”:

    1. Subcomponente FormValidator: disponibiliza os métodos validate() e reset()
    2. Componente pai da LoginPage: chama o método validate para realizar a validação; exibe uma mensagem de erro caso a validação falhe
    3. 5 campos de preenchimento (nome de usuário/e-mail/senha/telefone/captcha)
    4. useTemplateRef (Vue 3.5+) + tipagem forte do TypeScript
    5. Colocar o foco automaticamente no primeiro campo de entrada quando a janela pop-up for aberta
    6. Quando ocorrer um erro, concentre-se no primeiro campo de erro
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%