Vue.js: provide / inject
Última atualização: 2026-08-26
provide e inject são as APIs de comunicação entre níveis do Vue 3 — os componentes ancestrais fornecem dados, e qualquer componente descendente (independentemente da profundidade) pode injetá-los e utilizá-los. Isso resolve o problema do “prop drilling”.
O provide do Vue 3 oferece suporte a dados reativos (ref / reactive), e os componentes filhos se tornam automaticamente reativos após serem injetados. Esta lição ajudará você a dominar esse mecanismo e cinco casos de uso práticos.
1. O que você vai aprender
provide/injectSintaxe básica- Tipo de símbolo como chave de injeção
- Fornecimento reativo (ref / reativo)
- Duas maneiras de modificar
provide - Comparação com props, o event bus e o Pinia
- 5 cenários-chave do mundo real (Tema / Usuário / i18n)
- 3 antipadrões relacionados a “provide” e “inject”
2. O pesadelo de perfurar com o Prop uma “cor temática” ao longo de 5 níveis
(1) Ponto crítico: Aplicativo → Layout → Cabeçalho → Menu do usuário → Botão, passagem por 5 camadas
O painel de administração da Alice precisava oferecer suporte à troca de temas:
<!-- ❌ The "Broken" Version: 5-Layer prop drilling -->
<!-- App.vue -->
<Layout :theme="theme" :user="user" :locale="locale">
<router-view />
</Layout>
<!-- Layout.vue -->
<Header :theme="theme" :user="user" :locale="locale" />
<!-- Header.vue -->
<UserMenu :theme="theme" :user="user" :locale="locale" />
<!-- UserMenu.vue -->
<ThemeButton :theme="theme" />
<LocaleSelector :locale="locale" />
O gerente de produto Charlie adiciona mais 3 configurações:
“Alice, precisamos adicionar configurações para a barra lateral recolhida, as configurações de notificações e os sinalizadores de recurso. São 8 propriedades para passar por 5 componentes. Isso é impossível de manter.”
(2) Solução de fornecimento/injeção do Vue: injetar nos ancestrais; os descendentes recebem
<!-- App.vue Ancestors -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const user = ref({ name: 'Alice' })
const locale = ref('zh-CN')
// ✅ Once provide,All descendants may inject
provide('theme', theme)
provide('user', user)
provide('locale', locale)
</script>
<!-- UserMenu.vue Any descendant at any depth -->
<script setup>
import { inject } from 'vue'
// ✅ Get it directly, No props passthrough needed
const theme = inject('theme')
const user = inject('user')
const locale = inject('locale')
</script>
<template>
<button :class="theme">Toggle {{ user.name }}</button>
<p>{{ locale }}</p>
</template>
5 níveis de aninhamento → 1 linha de provide + 1 linha de inject.
(3) Receita
Após o fornecimento/injeção:
- Profundidade de aninhamento dos componentes: Ilimitada (a injeção é possível em qualquer nível da árvore)
- Número de argumentos: 5+ → 0 (não são mais passados)
- Nova configuração: É necessário apenas 1
provide - Facilidade de manutenção: O componente pai gerencia centralmente o estado global
3. Sintaxe básica de provide e inject
(1) Forneça os dados de entrada
<!-- App.vue Ancestor Components -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const user = { name: 'Alice' }
// 1. Inject a static value
provide('appName', 'My Admin')
// 2. Injecting Responsive Data(ref)
provide('theme', theme)
// 3. Injecting Responsive Data(reactive)
provide('user', user)
// 4. Injection Methods
provide('updateUser', (newUser) => {
user.value = newUser
})
</script>
(2) O inject recebe dados
<!-- Child.vue Any descendant component -->
<script setup>
import { inject } from 'vue'
// 1. Basic Reception
const theme = inject('theme')
// 2. With default values(My ancestors are no longer with us provide When to Use)
const theme = inject('theme', 'light')
// 3. Include factory function defaults
const theme = inject('theme', () => 'light')
// 4. Type Assertion(TypeScript)
const theme = inject<string>('theme', 'light')
</script>
(3) Exemplo completo
<!-- Ancestor.vue -->
<script setup>
import { ref, provide } from 'vue'
const count = ref(0)
const user = reactive({ name: 'Alice', age: 25 })
provide('count', count) // ref
provide('user', user) // reactive
provide('config', { theme: 'dark' }) // Object
provide('reset', () => { count.value = 0 }) // Methods
</script>
<!-- DeepChild.vue(Any depth) -->
<script setup>
import { inject } from 'vue'
const count = inject('count') // Responsive
const user = inject('user') // Responsive
const config = inject('config') // Ordinary Object
const reset = inject('reset') // Function
// Edit
function increment() {
count.value++ // ✅ Ancestor components will also respond
}
</script>
4. O tipo “Symbol” como chave de injeção
(1) Por que usar o Symbol?
Chaves do tipo string estão sujeitas a conflitos (várias instruções provide com a mesma chave se sobrescreverão mutuamente). Os símbolos são únicos, o que evita conflitos.
<!-- keys.js - Centrally manage all provide key -->
<script>
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const LOCALE_KEY = Symbol('locale')
export const CONFIG_KEY = Symbol('config')
</script>
<!-- Ancestor.vue Ancestor Components -->
<script setup>
import { provide, ref } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY } from './keys'
provide(THEME_KEY, ref('light'))
provide(USER_KEY, ref({ name: 'Alice' }))
provide(LOCALE_KEY, ref('zh-CN'))
</script>
<!-- DeepChild.vue Any descendants -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY } from './keys'
// ✅ Type Safety:Must use Symbol talent inject
const theme = inject(THEME_KEY)
const user = inject(USER_KEY)
</script>
(2) 5 principais vantagens
| Vantagem | Descrição |
|---|---|
| Exclusividade | O símbolo é globalmente exclusivo; não ocorrem conflitos |
| Segurança de tipos | O TypeScript consegue inferir com precisão |
| Gerenciamento centralizado | Todas as chaves são armazenadas em um único arquivo |
| Fácil de refatorar | A modificação de uma chave não afeta as demais |
| Legibilidade | Dá para saber para que serve só de olhar para o nome da chave |
5. Uma explicação detalhada sobre o Responsive provide
(1) 3 tipos de modos de “fornecimento”
<!-- Ancestor.vue -->
<script setup>
import { ref, reactive, provide } from 'vue'
// 1. Inject ref(Responsive)
const count = ref(0)
provide('count', count)
// 2. Inject reactive(Responsive)
const user = reactive({ name: 'Alice' })
provide('user', user)
// 3. Injecting Read-Only Objects(Non-responsive)
const config = { theme: 'dark' }
provide('config', config)
</script>
(2) Duas maneiras de fazer alterações
<!-- Method 1:Directly Modify Ancestors(Recommendations) -->
<!-- Ancestor.vue -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
provide('theme', theme)
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
</script>
<!-- Method 2:Through their descendants inject Edit Citation -->
<!-- DeepChild.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
function toggleTheme() {
theme.value = 'dark' // ✅ The change is ref,Both the ancestors and other descendants responded
}
</script>
(3) 5 principais cenários de resposta
| Cenário | Uso |
|---|---|
| Alterar tema | ref + fornecer |
| Login do usuário | reactive + provide |
| localização i18n | referência + fornecimento + método de alternância |
| Carregamento global | ref(false) + fornecer |
| Notificações de mensagens globais | reativo + fornecer |
6. 5 cenários-chave do mundo real
(1) Cena 1: Mudança de assunto
// keys.js
export const THEME_KEY = Symbol('theme')
<!-- App.vue Ancestors -->
<script setup>
import { ref, provide } from 'vue'
import { THEME_KEY } from './keys'
const theme = ref('light')
provide(THEME_KEY, theme)
</script>
<!-- ThemeButton.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY } from './keys'
const theme = inject(THEME_KEY)
</script>
<template>
<button @click="theme.value = theme === 'light' ? 'dark' : 'light'">
{{ theme === 'light' ? '🌞' : '🌙' }}
</button>
</template>
(2) Cenário 2: Autenticação do usuário
// keys.js
export const AUTH_KEY = Symbol('auth')
<!-- App.vue -->
<script setup>
import { reactive, provide } from 'vue'
import { AUTH_KEY } from './keys'
const auth = reactive({
user: null,
isLoggedIn: false,
login(credentials) { /* API call */ },
logout() { this.user = null; this.isLoggedIn = false }
})
provide(AUTH_KEY, auth)
</script>
<!-- UserMenu.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { AUTH_KEY } from './keys'
const auth = inject(AUTH_KEY)
</script>
<template>
<div v-if="auth.isLoggedIn">{{ auth.user.name }}</div>
<button v-else @click="auth.login(creds)">Login</button>
</template>
(3) Cenário 3: i18n
// keys.js
export const I18N_KEY = Symbol('i18n')
<!-- App.vue -->
<script setup>
import { ref, provide, computed } from 'vue'
import { I18N_KEY } from './keys'
const messages = {
'zh-CN': { hello: 'Hello', welcome: 'Welcome' },
'en-US': { hello: 'Hello', welcome: 'Welcome' }
}
const locale = ref('zh-CN')
const t = computed(() => (key) => messages[locale.value][key])
provide(I18N_KEY, { locale, t })
</script>
<!-- Hello.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { I18N_KEY } from './keys'
const { t } = inject(I18N_KEY)
</script>
<template>
<h1>{{ t('hello') }}, {{ t('welcome') }}</h1>
</template>
(4) Cenário 4: Carregamento global
<!-- App.vue -->
<script setup>
import { ref, provide } from 'vue'
const loading = ref(false)
provide('loading', loading)
</script>
<!-- AnyComponent.vue Descendants -->
<script setup>
import { inject } from 'vue'
const loading = inject('loading')
async function fetchData() {
loading.value = true
await fetch('/api/data')
loading.value = false
}
</script>
<template>
<button @click="fetchData">Refresh</button>
</template>
(5) Cenário 5: Tema + Dados Empresariais
<!-- App.vue -->
<script setup>
import { ref, reactive, provide } from 'vue'
const appState = reactive({
theme: 'light',
user: { name: 'Alice' },
permissions: ['read', 'write'],
config: { sidebar: true }
})
provide('appState', appState)
</script>
7. provide / inject x outras abordagens
(1) Comparação entre 5 métodos de comunicação
| Aspecto | props/emit | provide/inject | barramento de eventos | Pinia | mitt |
|---|---|---|---|---|---|
| Correspondência entre pai e filho | ⭐⭐⭐⭐⭐ | - | - | - | - |
| Entre níveis | ❌ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Brothers Communications | ❌ (Requer a intervenção dos pais) | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Responsivo | ✅ | ✅ | ❌ (Manual) | ✅ | ❌ |
| Segurança de tipos | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| DevTools | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Curva de aprendizado | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
(2) 5 cenários-chave de seleção
| Cenário | Solução recomendada |
|---|---|
| Comunicação entre pai e filho | props/emit |
| Estado global entre níveis (tema/autenticação/i18n) | fornecer/injetar |
| Estado global em aplicações de médio a grande porte | Pinia (Fase 4.2) |
| Barramento de Eventos Simples | mitt / tiny-emitter |
| Gerenciamento de estados complexos de negócios | Pinia + Vue Router |
8. Exemplo completo: como alternar temas no backend de um site de comércio eletrônico
▶ Exemplo: 1. Gerenciamento centralizado de chaves
// src/keys.js
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const LOCALE_KEY = Symbol('locale')
export const CART_KEY = Symbol('cart')
▶ Exemplo: 2. App.vue — Única vez provide
<!-- src/App.vue -->
<script setup>
import { ref, reactive, provide, readonly } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY, CART_KEY } from './keys'
// Topic
const theme = ref('light')
function setTheme(newTheme) {
theme.value = newTheme
localStorage.setItem('theme', newTheme)
}
// User
const user = reactive({
id: 1,
name: 'Alice',
role: 'admin'
})
// Internationalization
const locale = ref('zh-CN')
const messages = {
'zh-CN': { home: 'Home', cart: 'Shopping Cart' },
'en-US': { home: 'Home', cart: 'Cart' }
}
const t = (key) => messages[locale.value][key]
// Shopping Cart
const cart = reactive({ items: [], total: 0 })
// Disposable provide 4 Global state
provide(THEME_KEY, { theme, setTheme })
provide(USER_KEY, { user })
provide(LOCALE_KEY, { locale, t })
provide(CART_KEY, { cart })
</script>
<template>
<router-view />
</template>
▶ Exemplo: 3. Uso por qualquer descendente (5 maneiras de usá-lo)
<!-- DeepChild.vue Any depth -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY, CART_KEY } from './keys'
// 1. Deconstruction
const { theme, setTheme } = inject(THEME_KEY)
const { user } = inject(USER_KEY)
const { locale, t } = inject(LOCALE_KEY)
const { cart } = inject(CART_KEY)
</script>
<template>
<div :class="['app', theme]">
<p>{{ user.name }} ({{ user.role }})</p>
<p>{{ t('home') }} | {{ t('cart') }}</p>
<p>{{ cart.items.length }} items, ${{ cart.total }}</p>
<button @click="setTheme('dark')">Dark Mode</button>
</div>
</template>
▶ Exemplo: 4. Demonstração das 5 principais vantagens das chaves de injeção de símbolos
// src/keys.js
export const THEME_KEY = Symbol('theme') // The Only One
export const ANOTHER_THEME_KEY = Symbol('theme') // Also the only one(Different Symbol)
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'
import { THEME_KEY, ANOTHER_THEME_KEY } from './keys'
const theme1 = ref('light')
const theme2 = ref('dark')
// ✅ There will be no conflict:Different Symbol Even if the names are the same
provide(THEME_KEY, theme1)
provide(ANOTHER_THEME_KEY, theme2)
</script>
▶ Exemplo: 5. Referência rápida para 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| Conflitos de chaves de string | Substituir | Usar símbolo |
| Não é um ancestral | indefinido | Verificar hierarquia de componentes |
| Modificar valor somente leitura | Aviso | Modificar ancestral; os descendentes são somente leitura |
| Ignorar valor padrão | indefinido | inject(key, defaultValue) |
Grande número de instruções provide |
Difícil de manter | Centralizado em keys.js |
▶ Exemplo: 6. 5 Principais comparações de desempenho
| Modo | Desempenho | Aplicável |
|---|---|---|
provide('key', value) |
⭐⭐⭐⭐⭐ | Valor estático |
provide('key', ref) |
⭐⭐⭐⭐⭐ | Avaliação interativa |
provide('key', reactive) |
⭐⭐⭐⭐ | Objetos complexos |
provide('key', computed) |
⭐⭐⭐⭐⭐ | Valor derivado |
provide('key', function) |
⭐⭐⭐⭐ | Como usar |
❓ Perguntas Frequentes
P: O que devo usar,
provideouprops? R: Usepropspara comunicação direta entre componentes pai e filho (explícita e segura em termos de tipos). Useprovidepara comunicação entre níveis (3 ou mais níveis) a fim de evitar a prop drilling. Use o Pinia (Fase 4.2) para o estado global em aplicações de grande porte.
P: Os dados fornecidos são reativos? R: Depende dos dados que estão sendo fornecidos.
provide('key', ref)Reativos,provide('key', {a: 1})Não reativos (objeto comum). Recomendamos usarprovide refouprovide reactive.
P: O
provideprecisa estar no nível superior dosetup? R: Sim. Oprovidedeve ser chamado no nível superior do<script setup>(não é permitido dentro de ganchos de ciclo de vida); caso contrário, oprovidenão terá efeito.
P: Posso modificar o valor de
provideapósinject? R: Você pode modificarref.value, mas recomenda-se fazê-lo apenas em componentes ancestrais (usando o métodoprovide). Modificarrefdiretamente pode tornar o fluxo de dados pouco claro.
P: Onde está definida a tabela de símbolos? R: Em um arquivo separado
src/keys.js, que exporta todos os símbolos. Todos os componentes utilizados são importados desse arquivo.
P: Como faço para escolher entre
provide/injecte o Pinia? R: Para projetos pequenos (< 5 estados globais), oprovide/injecté suficiente. Para projetos de médio a grande porte (10 ou mais estados globais), use o Pinia, pois ele oferece melhor suporte ao DevTools.
P: O que acontece quando um componente com
provideé desmontado? R: Todos os componentes descendentes também são desmontados (ciclo de vida da árvore de componentes do Vue).injectirá procurar novamente pelos ancestrais na árvore.
📖 Resumo
provideeinjectsão as APIs de comunicação entre camadas do Vue 3- fornecer (ancestral), inserir (qualquer descendente)
- 5 tipos de
provide: valor estático / referência / reativo / calculado / função - Chave de símbolos: para evitar conflitos, centralize o gerenciamento no arquivo keys.js
- 5 cenários práticos: Tema / Autenticação / i18n / Carregamento / Dados comerciais
- Comparação com props: use props para relações pai-filho; use
providepara comunicação entre níveis - Comparação com o Pinia: use o Provide para projetos pequenos e o Pinia para projetos grandes
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Implemente um seletor de temas simples:
- keys.js Define THEME_KEY
- O arquivo App.vue fornece o tema (ref.) + o método setTheme
- ThemeButton.vue: injetar tema + definir tema
- Clique no botão para alternar entre os modos claro e escuro
-
Problemas avançados (Dificuldade: ⭐⭐)
Implementar a autenticação do usuário (fornecer/injetar):
- Injeção de AUTH_KEY: métodos user, login e logout
- O arquivo App.vue inicializa user = null
- No arquivo LoginPage.vue, chame
login(creds)para simular um login - O arquivo Header.vue exibe uma interface de usuário diferente dependendo do valor de isLoggedIn
-
Problema desafiador (Dificuldade: ⭐⭐⭐)
Implementar um sistema abrangente de estado global para o back-end do comércio eletrônico:
- 4 teclas: TEMA / USUÁRIO / CARRINHO / CONFIGURAÇÃO REGIONAL
- O App.vue fornece quatro estados globais ao mesmo tempo
- Uso de 5 componentes em diferentes níveis (Cabeçalho / Barra lateral / Lista de produtos / Carrinho / Rodapé)
- Comunicação entre componentes: o cabeçalho aciona o logout → a barra lateral é atualizada automaticamente
- Troca de tema + Troca de internacionalização (i18n) + Adição e remoção de itens do carrinho de compras
- Uso do TypeScript para tipagem forte
- 5 Definir padrões (Value/ref/reactive/computed/function)