O Vue 3 vem com muitos novos recursos interessantes e alterações em alguns dos existentes que visam tornar o desenvolvimento com o framework muito mais fácil e sustentável. Neste artigo, vamos dar uma olhada em alguns desses novos recursos e como começar a usá-los. Também daremos uma olhada em algumas das mudanças feitas nos recursos existentes.
Com o lançamento do Vue 3, os desenvolvedores precisam fazer a atualização do Vue 2, pois ele vem com um punhado de novos recursos que são muito úteis na construção de componentes fáceis de ler e de manutenção e maneiras aprimoradas de estruturar nosso aplicativo no Vue. Vamos dar uma olhada em alguns desses recursos neste artigo.
No final deste tutorial, os leitores irão;
- Conheça
provide / inject
e como usá-lo. - Tenha um conhecimento básico sobre teletransporte e como usá-lo.
- Conheça os fragmentos e saiba como usá-los.
- Saiba mais sobre as mudanças feitas na API Global Vue.
- Conheça as alterações feitas na API de eventos.
Este artigo é direcionado àqueles que têm uma compreensão adequada do Vue 2.x. Você pode encontrar todo o código usado neste exemplo no GitHub .
provide / inject
No Vue 2.x, tornamos props
mais fácil passar dados (strings, arrays, objetos, etc.) de um componente pai diretamente para seu componente filho. Mas, durante o desenvolvimento, frequentemente encontramos instâncias em que precisávamos passar dados do componente pai para um componente profundamente aninhado, o que era mais difícil de fazer props
. Isso resultou no uso do Vuex Store, Event Hub e, às vezes, na transmissão de dados por meio de componentes profundamente aninhados. Vejamos um aplicativo simples;
É importante observar que o Vue 2.2.0 também veio com o provide / inject
qual não foi recomendado para uso em código de aplicativo genérico.
# parentComponent.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Vue 3 is liveeeee!" :color="color" />
<select name="color" id="color" v-model="color">
<option value="" disabled selected> Select a color</option>
<option :value="color" v-for="(color, index) in colors" :key="index">{{
color
}}</option></select
>
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
components: {
HelloWorld,
},
data() {
return {
color: "",
colors: ["red", "blue", "green"],
};
},
};
</script>
cópia de
# childComponent.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<color-selector :color="color"></color-selector>
</div>
</template>
<script>
import colorSelector from "@/components/colorComponent.vue";
export default {
name: "HelloWorld",
components: {
colorSelector,
},
props: {
msg: String,
color: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
cópia de
# colorComponent.vue
<template>
<p :class="[color]">This is an example of deeply nested props!</p>
</template>
<script>
export default {
props: {
color: String,
},
};
</script>
<style>
.blue {
color: blue;
}
.red {
color: red;
}
.green {
color: green;
}
</style>
cópia de
Aqui, temos uma página de destino com uma lista suspensa que contém uma lista de cores e nós estamos passando o escolhido color
para childComponent.vue
como um suporte. Este componente filho também possui um msg
prop que aceita um texto para exibir na seção do modelo. Finalmente, este componente tem um componente filho ( colorComponent.vue
) que aceita um color
prop do componente pai que é usado para determinar a classe para o texto neste componente. Este é um exemplo de como passar dados por todos os componentes.
Mas com o Vue 3, podemos fazer isso de uma forma mais limpa e curta usando o novo par Provide e injete. Como o nome indica, usamos provide
como uma função ou um objeto para disponibilizar dados de um componente pai para qualquer um de seus componentes aninhados, independentemente de quão profundamente aninhado esse componente esteja. Fazemos uso da forma de objeto ao passar valores embutidos em código para provide
assim;
# parentComponent.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Vue 3 is liveeeee!" :color="color" />
<select name="color" id="color" v-model="color">
<option value="" disabled selected> Select a color</option>
<option :value="color" v-for="(color, index) in colors" :key="index">{{
color
}}</option></select
>
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
components: {
HelloWorld,
},
data() {
return {
colors: ["red", "blue", "green"],
};
},
provide: {
color: 'blue'
}
};
</script>
cópia de
Mas, para instâncias em que você precisa passar uma propriedade de instância de componente para provide
, usamos o modo de função para que isso seja possível;
# parentComponent.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Vue 3 is liveeeee!" />
<select name="color" id="color" v-model="selectedColor">
<option value="" disabled selected> Select a color</option>
<option :value="color" v-for="(color, index) in colors" :key="index">{{
color
}}</option></select
>
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
export default {
name: "Home",
components: {
HelloWorld,
},
data() {
return {
selectedColor: "blue",
colors: ["red", "blue", "green"],
};
},
provide() {
return {
color: this.selectedColor,
};
},
};
</script>
cópia de
Como não precisamos dos color
adereços em childComponent.vue
e colorComponent.vue
, vamos nos livrar deles. O bom de usar provide
é que o componente pai não precisa saber qual componente precisa da propriedade que está fornecendo.
Para fazer uso disso no componente que precisa nesse caso, colorComponent.vue
fazemos isso;
# colorComponent.vue
<template>
<p :class="[color]">This is an example of deeply nested props!</p>
</template>
<script>
export default {
inject: ["color"],
};
</script>
<style>
.blue {
color: blue;
}
.red {
color: red;
}
.green {
color: green;
}
</style>
cópia de
Aqui, usamos o inject
que leva em uma matriz das variáveis necessárias que o componente precisa. Nesse caso, precisamos apenas da color
propriedade, portanto, apenas a passamos. Depois disso, podemos usar color
da mesma forma que usamos para usar adereços.
Podemos notar que, se tentarmos selecionar uma nova cor usando o menu suspenso, a cor não será atualizada em colorComponent.vue
e isso ocorre porque, por padrão, as propriedades em provide
não são reativas. Para corrigir isso, usamos o computed
método.
# parentComponent.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Vue 3 is liveeeee!" />
<select name="color" id="color" v-model="selectedColor">
<option value="" disabled selected> Select a color</option>
<option :value="color" v-for="(color, index) in colors" :key="index">{{
color
}}</option></select
>
</div>
</template>
<script>
import HelloWorld from "@/components/HelloWorld.vue";
import { computed } from "vue";
export default {
name: "Home",
components: {
HelloWorld,
},
data() {
return {
selectedColor: "",
todos: ["Feed a cat", "Buy tickets"],
colors: ["red", "blue", "green"],
};
},
provide() {
return {
color: computed(() => this.selectedColor),
};
},
};
</script>
cópia de
Aqui, importamos computed
e passamos nosso selectedColor
para que possa ser reativo e atualizado conforme o usuário seleciona uma cor diferente. Quando você passa uma variável para o método calculado, ele retorna um objeto que tem um value
. Esta propriedade contém o valor da sua variável, portanto, para este exemplo, teríamos que atualizar colorComponent.vue
para ficar assim;
# colorComponent.vue
<template>
<p :class="[color.value]">This is an example of deeply nested props!</p>
</template>
<script>
export default {
inject: ["color"],
};
</script>
<style>
.blue {
color: blue;
}
.red {
color: red;
}
.green {
color: green;
}
</style>
cópia de
Aqui, mudamos color
para color.value
para representar a mudança após torná-la color
reativa usando o computed
método. Nesse ponto, o class
do texto neste componente sempre mudaria sempre que fosse selectedColor
alterado no componente pai.
Teleporte
Existem instâncias em que criamos componentes e os colocamos em uma parte de nosso aplicativo por causa da lógica que o aplicativo usa, mas devem ser exibidos em outra parte de nosso aplicativo. Um exemplo comum disso seria um modal ou pop-up destinado a exibir e cobrir toda a tela. Embora possamos criar uma solução alternativa para isso usando a position
propriedade CSS em tais elementos, com o Vue 3, também podemos fazer usando o Teleport.
O teletransporte nos permite tirar um componente de sua posição original em um documento, do #app
contêiner padrão em que os aplicativos Vue são agrupados e movê-lo para qualquer elemento existente na página em que está sendo usado. Um bom exemplo seria usar o Teleport para mover um componente de cabeçalho de dentro do #app
div para um. header
É importante observar que você só pode Teleportar para elementos que existem fora do Vue DOM.
O componente Teleport aceita dois adereços que determinam o comportamento desse componente e eles são;
to
Este prop aceita um nome de classe, um id, um elemento ou um atributo data- * . Também podemos tornar esse valor dinâmico passando uma:to
prop em oposição ato
e alterando o elemento Teleport dinamicamente.:disabled
Este prop aceita umBoolean
e pode ser usado para alternar o recurso de teletransporte em um elemento ou componente. Isso pode ser útil para alterar dinamicamente a posição de um elemento.
Um exemplo ideal de uso de teleporte é semelhante a este;
# index.html**
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<!-- add container to teleport to -->
<header class="header"></header>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
cópia de
No index.html
arquivo padrão em seu aplicativo Vue, adicionamos um header
elemento porque queremos teletransportar nosso componente de cabeçalho para aquele ponto em nosso aplicativo. Também adicionamos uma classe a este elemento para estilização e para fácil referência em nosso componente Teleport.
# Header.vue**
<template>
<teleport to="header">
<h1 class="logo">Vue 3 🥳</h1>
<nav>
<router-link to="/">Home</router-link>
</nav>
</teleport>
</template>
<script>
export default {
name: "app-header",
};
</script>
<style>
.header {
display: flex;
align-items: center;
justify-content: center;
}
.logo {
margin-right: 20px;
}
</style>
cópia de
Aqui, criamos o componente de cabeçalho e adicionamos um logotipo com um link para a página inicial do nosso aplicativo. Também adicionamos o componente Teleport e atribuímos ao to
prop um valor de header
porque queremos que esse componente seja renderizado dentro desse elemento. Por fim, importamos esse componente para nosso aplicativo;
# App.vue
<template>
<router-view />
<app-header></app-header>
</template>
<script>
import appHeader from "@/components/Header.vue";
export default {
components: {
appHeader,
},
};
</script>
cópia de
Neste arquivo, importamos o componente de cabeçalho e o colocamos no modelo para que fique visível em nosso aplicativo.
Agora, se inspecionarmos o elemento de nosso aplicativo, notaremos que nosso componente de cabeçalho está dentro do header
elemento;
Fragmentos
Com o Vue 2.x, era impossível ter vários elementos raiz no template
arquivo e, como solução alternativa, os desenvolvedores começaram a agrupar todos os elementos em um elemento pai. Embora isso não pareça um problema sério, há casos em que os desenvolvedores desejam renderizar um componente sem um contêiner envolvendo esses elementos, mas precisam se contentar com isso.
Com o Vue 3, um novo recurso chamado Fragments foi introduzido e esse recurso permite que os desenvolvedores tenham vários elementos em seu arquivo de modelo raiz. Portanto, com o Vue 2.x, é assim que se pareceria um componente de contêiner de campo de entrada;
# inputComponent.vue
<template>
<div>
<label :for="label">label</label>
<input :type="type" :id="label" :name="label" />
</div>
</template>
<script>
export default {
name: "inputField",
props: {
label: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
},
};
</script>
<style></style>
cópia de
Aqui, temos um componente de elemento de formulário simples que aceita dois adereços, label
e type
, e a seção de modelo desse componente é envolvida em um div. Isso não é necessariamente um problema, mas se você quiser que o rótulo e o campo de entrada estejam diretamente dentro do seu form
elemento. Com o Vue 3, os desenvolvedores podem facilmente reescrever esse componente para ficar assim;
# inputComponent.vue
<template class="testingss">
<label :for="label">{{ label }}</label>
<input :type="type" :id="label" :name="label" />
</template>
cópia de
Com um único nó raiz, os atributos são sempre atribuídos ao nó raiz e também são conhecidos como atributos não prop . Eles são eventos ou atributos passados para um componente que não possui propriedades correspondentes definidas em props
ou emits
. Exemplos de tais atributos são class
e id
. No entanto, é necessário definir explicitamente a qual dos elementos em um componente de nó multi-raiz deve ser atribuído.
Aqui está o que isso significa usando o inputComponent.vue
de cima;
- Ao adicionar
class
a este componente no componente pai, deve ser especificado a qual componente issoclass
seria atribuído, caso contrário, o atributo não tem efeito.
<template>
<div class="home">
<div>
<input-component
class="awesome__class"
label="name"
type="text"
></input-component>
</div>
</div>
</template>
<style>
.awesome__class {
border: 1px solid red;
}
</style>
cópia de
Quando você faz algo assim sem definir onde os atributos devem ser atribuídos, você recebe este aviso em seu console;
E border
não tem efeito sobre o componente;
- Para corrigir isso, adicione um
v-bind="$attrs"
no elemento para o qual deseja que tais atributos sejam distribuídos;
<template>
<label :for="label" v-bind="$attrs">{{ label }}</label>
<input :type="type" :id="label" :name="label" />
</template>
cópia de
Aqui, estamos dizendo ao Vue que queremos que os atributos sejam distribuídos para o label
elemento, o que significa que queremos awesome__class
que seja aplicado a ele. Agora, se inspecionarmos nosso elemento no navegador, veremos que a classe foi adicionada label
e, portanto, uma borda agora está ao redor do rótulo.
API Global
Não era incomum ver Vue.component
ou Vue.use
em main.js
arquivo um aplicativo Vue. Esses tipos de métodos são conhecidos como APIs globais e há um grande número deles no Vue 2.x. Um dos desafios desse método é que ele torna impossível isolar certas funcionalidades para uma instância de seu aplicativo (se você tiver mais de uma instância em seu aplicativo) sem afetar outros aplicativos porque eles são todos montados no Vue. É isso que eu quero dizer;
Vue.directive('focus', {
inserted: el => el.focus()
})
Vue.mixin({
/* ... */
})
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
cópia de
Para o código acima, é impossível afirmar que a Diretiva Vue esteja associada app1
e o Mixin com, app2
mas, em vez disso, ambas estão disponíveis nos dois aplicativos.
O Vue 3 vem com uma nova API Global em uma tentativa de corrigir esse tipo de problema com a introdução do createApp
. Este método retorna uma nova instância de um aplicativo Vue. Uma instância de aplicativo expõe um subconjunto das APIs globais atuais. Com isso, todas as APIs (componente, mixin, diretiva, uso, etc.) que sofrem mutação Vue
do Vue 2.x agora serão movidas para instâncias de aplicativos individuais e agora, cada instância de seu aplicativo Vue pode ter funcionalidades que são exclusivas para eles sem afetar outros aplicativos existentes.
Agora, o código acima pode ser reescrito como;
const app1 = createApp({})
const app2 = createApp({})
app1.directive('focus', {
inserted: el => el.focus()
})
app2.mixin({
/* ... */
})
cópia de
No entanto, é possível criar funcionalidades que você deseja compartilhar entre todos os seus aplicativos e isso pode ser feito usando uma função de fábrica.
API De Eventos
Uma das formas mais comuns adotadas pelos desenvolvedores para passar dados entre componentes que não têm um relacionamento pai para filho, exceto pelo Vuex Store, é o uso do Event Bus . Uma das razões pelas quais esse método é comum é a facilidade de começar a usá-lo;
# eventBus.js
const eventBus = new Vue()
export default eventBus;
cópia de
Depois disso, o próximo passo seria importar esse arquivo main.js
para torná-lo disponível globalmente em nosso aplicativo ou importá-lo nos arquivos de que você precisa;
# main.js
import eventBus from 'eventBus'
Vue.prototype.$eventBus = eventBus
cópia de
Agora, você pode emitir eventos e ouvir eventos emitidos como este;
this.$eventBus.$on('say-hello', alertMe)
this.$eventBus.$emit('pass-message', 'Event Bus says Hi')
cópia de
Existe uma grande quantidade de codebase do Vue preenchida com códigos como este. No entanto, com o Vue 3, seria impossível porque $on
,, $off
e $once
foram todos removidos, mas $emit
ainda está disponível porque é necessário que o componente filho emita eventos para seus componentes pai. Uma alternativa para isso seria usar provide / inject
qualquer uma das bibliotecas de terceiros recomendadas .