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
defineModelMacro para substituir a sintaxe complexa do v-model- Decomposição de props responsiva (padrão: resposta profunda)
useIdGerar um ID exclusivo que seja seguro para SSRuseTemplateRefReferência modular- Melhorias nas animações de transição
- Vue 3.4 x Vue 3.3 x Vue 3.2: Principais diferenças
- 5 caminhos recomendados para atualização
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:
// ❌ 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
// ✅ 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:
- Quantidade de código: 10 linhas → 1 linha (-90%)
- Legibilidade: A intenção fica clara em uma única linha
- Inferência de tipos: O TypeScript infere tipos automaticamente
- Extensível: Compatível com transform, localStorage e muito mais
3. Uma explicação detalhada da macro defineModel
(1) Uso básico
<!-- 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>
<!-- 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
// 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
<!-- 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>
<!-- 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
// ❌ 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
<!-- 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
// 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
// ⚠️ 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.
// ❌ 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
<!-- 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
<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
✅ 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
<!-- 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>
▶ Exemplo: 2. Desestruturação de props reativos
<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>
▶ Exemplo: 3. Principais casos de uso para o useId 5
<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>
▶ Exemplo: 4. Preencha o formulário de inscrição usando useTemplateRef
<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>
▶ 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
defineModelsubstitui toda a sintaxe dov-model? R: Sim.defineModel('modelValue')is equivalent to the 10 lines of code inprops.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
useIdmuda a cada renderização? R: Não. O ID gerado pelouseIdpermanece 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
definePropspode ser usada comwatch? 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: usedefineModelem vez da sintaxe complexav-model; use a desestruturação reativa em vez deprops.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 comuseTemplateRefe suporte mais abrangente ao TypeScript para macros. Recomendamos o uso de<script setup lang="ts">.
📖 Resumo
- A macro
defineModel: 1 linha dev-modelsubstitui 10 linhas de código padrão - Decomposição de props responsivos: o Vue 3.4+ usa a responsividade profunda por padrão
- useId: Um ID exclusivo que é seguro para SSR
- useTemplateRef: referência composável do Vue 3.5+, inferência de tipos forte
- Atualização do Vue 3.4:
defineModel, desestruturação reativa e sinônimos de nomes iguais - Atualização do Vue 3.5: useTemplateRef e useId agora estão em versão estável
- Recomendação: Use o Vue 3.5 ou superior diretamente
📝 Exercícios
-
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
- Componente filho CustomInput.vue: 1 linha de
-
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
-
Problema desafiador (Dificuldade: ⭐⭐⭐)
Exemplo de uma atualização completa para o Vue 3.4+:
- Substituir a sintaxe complexa do modelo V por
defineModelem 5 componentes - Desmontando três componentes usando props reativos
- 5 componentes que utilizam
useId(para conectarlabeleinput) - 3 componentes que utilizam
useTemplateRef(Vue 3.5+) - 5 Componentes fortemente tipados em TypeScript
- Testar a consistência do
useIdem um cenário de SSR
- Substituir a sintaxe complexa do modelo V por