Vue.js: Novidades do Vue 3.4

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

O Vue 3.4+ (lançado em dezembro de 2023) traz várias melhorias revolucionárias: defineModel simplificação de macros v-model, desestruturação reativa de props, useTemplateRef a API de Composição e useId geração de IDs exclusivos. Esses recursos simplificam significativamente o código dos modelos e aprimoram a experiência de desenvolvimento.

Esta aula vai te ajudar a dominar cinco novos recursos essenciais que tornarão seu código em Vue 3 mais conciso e moderno.

1. O que você vai aprender


2. O desafio da “sincronização pai-filho” em um componente do v-model

(1) Problema: 5 linhas de props + 5 linhas de emit = 10 linhas de código repetitivo

Alice criou um componente de entrada personalizado usando o v-model:

JS
// ❌ The "Broken" Version:v-model Required 10 Sample Code
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])

const internalValue = ref(props.modelValue)
watch(() => props.modelValue, (val) => {
  internalValue.value = val
})
watch(internalValue, (val) => {
  emit('update:modelValue', val)
})

10 linhas de código padrão — você deve escrever isso para cada componente do v-model.

O desenvolvedor-chefe:

“Alice, isso está muito prolixo. O Vue 3.4 tem o defineModel, que faz isso em uma linha.”

(2) Figura 3.4: Solução geral para a macro defineModel

JS
// ✅ Correct Version:1 All set v-model
const modelValue = defineModel('modelValue')

// It's possible now:
// - Read: modelValue.value
// - Write: modelValue.value = 'new value' (Automatic emit)
// - watch:watch(modelValue, ...)
// - Parent Component v-model Two-way binding works as usual

10 linhas → 1 linha. Cinco componentes do modelo V economizam 50 linhas de código.

(3) Receita

Após a atualização para o defineModel:


3. Uma explicação detalhada da macro defineModel

(1) Uso básico

VUE
<!-- Child component:CustomInput.vue -->
<template>
  <input :value="modelValue" @input="modelValue = $event.target.value">
</template>

<script setup>
// ✅ 1-line v-model
const modelValue = defineModel('modelValue')
</script>
VUE
<!-- Parent Component:Usage v-model -->
<template>
  <CustomInput v-model="searchQuery" />
  <p>Search: {{ searchQuery }}</p>
</template>

<script setup>
import { ref } from 'vue'
const searchQuery = ref('')
</script>

(2) 5 usos avançados

JS
// 1. Default value
const modelValue = defineModel('modelValue', { default: '' })

// 2. Type Constraints(TypeScript)
const count = defineModel<number>('count', { default: 0 })

// 3. transform Convert(Conversion at the Time of Writing)
const trimmed = defineModel('text', {
  set(value) {
    return value.trim()  // Automatic trim
  }
})

// 4. getter(Conversion During Reading)
const upper = defineModel('text', {
  get(value) {
    return value.toUpperCase()  // Always uppercase
  }
})

// 5. Several v-model
const name = defineModel('name')
const email = defineModel('email')
const phone = defineModel('phone')

(3) Exemplo completo

VUE
<!-- Child component:TrimInput.vue -->
<template>
  <input :value="value" @input="value = $event.target.value">
</template>

<script setup>
const value = defineModel('value', {
  // get: Conversion During Reading
  get(val) {
    return val?.toUpperCase() || ''
  },
  // set: Conversion at the Time of Writing
  set(val) {
    return val?.trim() || ''
  },
  default: ''
})
</script>
▶ Experimente
VUE
<!-- Parent Component -->
<CustomInput v-model="name" />
<!-- User Input "alice" → trim Unchanged → uppercase → "ALICE" -->

4. Desestruturação de propriedades reativas

(1) Problemas antes do Vue 3.4

JS
// ❌ Vue 3.3 and before that
const props = defineProps({ count: Number, name: String })

// Use directly in the template props.count
// JS Key Points props.count
// Deconstruction may result in a loss of response
const { count, name } = props  // count/name Convert to a regular value

(2) Vue 3.4: Desestruturação sem comprometer a capacidade de resposta

VUE
<!-- Child component -->
<template>
  <p>{{ count }} - {{ name }}</p>
  <button @click="count++">+1</button>
</template>

<script setup>
// ✅ Vue 3.4:Remains responsive even after deconstruction
const { count, name } = defineProps<{
  count: number
  name: string
}>()
</script>

Os props desconstruídos continuam sendo reativos! Essa é uma das maiores melhorias do Vue 3.4.

(3) 5 principais cenários de aplicação

JS
// 1. Simplify props Usage
const { count, name } = defineProps<{ count: number; name: string }>()

// 2. Deconstruction + Rename
const { count: total, name: userName } = defineProps<{ count: number; name: string }>()

// 3. Deconstruction + Default value
const { count = 0, name = 'Guest' } = defineProps<{ count?: number; name?: string }>()

// 4. In conjunction with watch(After deconstruction watch Still working)
const { count } = defineProps<{ count: number }>()
watch(count, (newVal) => console.log('count changed:', newVal))

// 5. In conjunction with computed(Stay responsive as well)
const { count } = defineProps<{ count: number }>()
const double = computed(() => count * 2)

(4) Observações importantes

JS
// ⚠️ Deconstructed props Cannot be reassigned
const { count } = defineProps<{ count: number }>()
count = 10  // ❌ Error:Assignment to constant variable

// ✅ Correct:Via emit Have the parent component modify
const emit = defineEmits(['update:count'])
const handleClick = () => emit('update:count', count + 1)

5. useId: ID exclusivo

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

Durante a SSR (renderização do lado do servidor), IDs definidos manualmente podem causar conflitos (várias instâncias de componentes usando o mesmo ID). O useId gera IDs globalmente únicos e compatíveis com SSR.

JS
// ❌ Old notation:Handwritten ID,SSR There will be a conflict
const id = `input-${Math.random()}`

// ✅ Vue 3.4+:useId
import { useId } from 'vue'
const id = useId()  // 'v-0', 'v-1', ...

(2) 5 principais casos de uso

VUE
<!-- 1. Form label Relationship -->
<template>
  <label :for="id">Username</label>
  <input :id="id" v-model="username">
</template>

<script setup>
import { useId } from 'vue'
const id = useId()
const username = ref('')
</script>

<!-- 2. ARIA Properties -->
<template>
  <button :aria-describedby="`${id}-help`">Click</button>
  <p :id="`${id}-help`">This button does X</p>
</template>

<!-- 3. Form Error Messages -->
<template>
  <input v-model="email" :aria-invalid="!!error" :aria-errormessage="`${id}-error`">
  <p v-if="error" :id="`${id}-error`">{{ error }}</p>
</template>

<!-- 4. Tooltip Relationship -->
<template>
  <button :aria-describedby="`${id}-tip`">?</button>
  <span :id="`${id}-tip`" class="tooltip">Help text</span>
</template>

<!-- 5. Modal Window -->
<template>
  <div :id="`${id}-modal`" role="dialog" aria-modal="true">
    ...
  </div>
</template>

(3) 5 principais vantagens

Vantagem Descrição
Segurança SSR Correspondência entre ID do servidor e do cliente
Único Várias instâncias na mesma página não entram em conflito
Estável A reexibição do componente permanece inalterada
Legível Os IDs incluem o prefixo v- para facilitar a identificação
Leve 0 dependências (integradas ao Vue)

6. useTemplateRef (Uma explicação detalhada para o Vue 3.5+)

(1) Recursos completos

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

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

// ✅ Vue 3.5+:One Name Does It All
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

// ✅ TypeScript Perfect Inference
onMounted(() => {
  inputRef.value?.focus()  // HTMLInputElement Type
  chartRef.value?.refresh()  // Component Instance Methods
})
</script>

(2) Comparação entre o Vue 3.5 e o Vue 3.4

Dimensão Variável ref do Vue 3.4 useTemplateRef do Vue 3.5
Referência do modelo <input ref="inputRef"> <input ref="usernameInput">
Referência JS const inputRef = ref(null) const inputRef = useTemplateRef('usernameInput')
Inferência de tipos Manual ref<HTMLInputElement | null> Inferência automática
Consistência dos nomes Possíveis erros ortográficos não são sinalizados Inconsistências são sinalizadas
Concisão 2 linhas 1 linha

7. 5 Caminhos de atualização recomendados

(1) Hora da atualização

Versão do Vue Data de lançamento Status
Vue 3.0 setembro de 2020 Desatualizado (recomenda-se atualizar)
Vue 3.2 agosto de 2021 Estável
Vue 3.3 maio de 2023 Estável
Vue 3.4 dezembro de 2023 Recomendado atualmente
Vue 3.5 setembro de 2024 Versão estável mais recente

(2) Lista de atualizações

TEXT 📖 Somente leitura
✅ Vue 3.4:
  - defineModel Macro Replacement v-model Complex Notation
  - Responsive props Deconstruction
  - Abbreviations with the Same Name(<input :value> Replace :value="value")
  - Improvements to Error Handling
  - Template Parser Override(2x Performance)

✅ Vue 3.5:
  - useTemplateRef(Recommendations)
  - Responsive props Deconstruction and Improvement
  - useId Stable
 - Deferred teleport
  - Improved Suspense

(3) Recomendações de atualização

Cenário Recomendação
Novo projeto Use o Vue 3.5+ diretamente
Projeto Legado (Vue 3.3–) Atualização para a versão 3.5, migração por recurso
Projeto legado (Vue 3.0–3.2) Atualização para a versão 3.3 e, em seguida, para a 3.5
Megaprojetos Atualizações incrementais (atualizar o Vue primeiro, depois migrar o v-model, etc.)

8. Exemplo completo: Aplicativo abrangente com Vue 3.4+

▶ Exemplo: 1. 5 maneiras de usar defineModel

VUE
<!-- 1. Basics -->
<script setup>
const modelValue = defineModel('modelValue')
</script>

<!-- 2. Default value -->
<script setup>
const count = defineModel('count', { default: 0 })
</script>

<!-- 3. TypeScript Type -->
<script setup lang="ts">
const user = defineModel<User>('user', { default: () => ({}) })
</script>

<!-- 4. transform Convert -->
<script setup>
const trimmed = defineModel('text', {
  set(val) { return val?.trim() || '' }
})
</script>

<!-- 5. Several v-model -->
<script setup>
const username = defineModel('username')
const password = defineModel('password')
</script>
▶ Experimente

▶ Exemplo: 2. Desestruturação de props reativos

VUE
<template>
  <p>{{ count }} - {{ name }}</p>
  <button @click="emit('update:count', count + 1)">+1</button>
</template>

<script setup lang="ts">
// ✅ Vue 3.4+:Deconstructing Responsiveness
const { count, name } = defineProps<{
  count: number
  name: string
}>()

const emit = defineEmits<{
  'update:count': [value: number]
}>()
</script>
▶ Experimente

▶ Exemplo: 3. Principais casos de uso para o useId 5

VUE
<template>
  <label :for="id">Email</label>
  <input :id="id" v-model="email" :aria-invalid="!!error" :aria-errormessage="`${id}-error`">
  <p v-if="error" :id="`${id}-error`" class="error">{{ error }}</p>
</template>

<script setup>
import { ref, useId } from 'vue'
const id = useId()  // v-0, v-1, ...
const email = ref('')
const error = ref('')
</script>
▶ Experimente

▶ Exemplo: 4. Preencha o formulário de inscrição usando useTemplateRef

VUE
<template>
  <input ref="usernameInput" v-model="username">
  <MyChart ref="chartComponent" :data="chartData" />
</template>

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

const username = ref('')
const chartData = ref([1, 2, 3])

// ✅ Vue 3.5+:TypeScript Perfect Inference
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')

onMounted(() => {
  inputRef.value?.focus()
  chartRef.value?.refresh()
})
</script>
▶ Experimente

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

Erro Sintoma Solução
Desconstrução de defineModel Sem resposta Use const modelValue = defineModel() diretamente
Atribuição após a desestruturação de props Erro Use emit para permitir que o pai seja atualizado
useId com uma string codificada diretamente Conflito de SSR Mudar para useId()
Incorrect useTemplateRef name ref.value is null The name must match
A atualização para o Vue 3.4+ envolve muitas mudanças Problemas de compatibilidade Migração progressiva

▶ Exemplo: 6. 5 principais benefícios da atualização para o Vue 3.4+

Receita Dados
Redução no volume de código 30–50% (simplificação de defineModel)
Melhorias de desempenho Análise de modelos duas vezes mais rápida
Inferência de tipos 100% precisa (TypeScript)
Compatível com SSR useId para resolver conflitos de ID
Experiência do desenvolvedor Melhorias significativas na experiência do desenvolvedor

❓ Perguntas Frequentes

P: O defineModel substitui toda a sintaxe do v-model? R: Sim. defineModel('modelValue') is equivalent to the 10 lines of code in props.modelValue + emit('update:modelValue'). É totalmente compatível com versões anteriores.

P: Quando a desestruturação de props reativos será suportada? R: Vue 3.4+ (dezembro de 2023). Não é suportada no Vue 3.3 e versões anteriores. Os props desestruturados continuam sendo reativos (reatividade profunda por padrão).

P: O useId muda a cada renderização? R: Não. O ID gerado pelo useId permanece constante ao longo de todo o ciclo de vida do componente (consistente entre o SSR e o cliente).

P: useTemplateRef ou variáveis ref? R: O useTemplateRef foi introduzido no Vue 3.5+; ele oferece uma inferência de tipos mais robusta no TypeScript e uma renomeação mais segura. Para projetos comuns, as variáveis ref são suficientes.

P: A desestruturação defineProps pode ser usada com watch? R: Sim. const { count } = defineProps() + watch(count, ...) funciona perfeitamente.

P: Como faço para atualizar do Vue 3.3 para a versão 3.4 ou superior? R: npm update vue@latest. No nível do código: use defineModel em vez da sintaxe complexa v-model; use a desestruturação reativa em vez de props.xxx. Sem nenhuma quebra de compatibilidade.

P: Quais melhorias o Vue 3.5+ trouxe para o TypeScript? R: Desestruturação reativa com defineProps, inferência de tipos perfeita com useTemplateRef e suporte mais abrangente ao TypeScript para macros. Recomendamos o uso de <script setup lang="ts">.


📖 Resumo


📝 Exercícios

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

    Reestruturar um componente v-model usando defineModel:

    • Componente filho CustomInput.vue: 1 linha de defineModel
    • Parent component: v-model two-way binding
    • Suporta valores padrão
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Reescrevendo componentes usando desestruturação reativa no Vue 3.4+:

    • Desestruturar as propriedades count, name etc.
    • Com relógio + calculado
    • Testar o layout responsivo
  3. Problema desafiador (Dificuldade: ⭐⭐⭐)

    Exemplo de uma atualização completa para o Vue 3.4+:

    1. Substituir a sintaxe complexa do modelo V por defineModel em 5 componentes
    2. Desmontando três componentes usando props reativos
    3. 5 componentes que utilizam useId (para conectar label e input)
    4. 3 componentes que utilizam useTemplateRef (Vue 3.5+)
    5. 5 Componentes fortemente tipados em TypeScript
    6. Testar a consistência do useId em um cenário de SSR
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%