É comum ver a Orientação a Objetos ser relacionado a herança, mais especificamente reuso de lógicas e comportamentos. Porém, herança não é algo exclusivo da Orientação a Objetos.
Existem diversas técnicas que provém herança. Dentro do contexto da Orientação a Objetos existe a composição, que infelizmente muitos deixam de lado ou simplesmente não conhecem.
Nesse artigo vou listar as diversas maneiras de reaproveitamento e herança de código com Vue.js.
Estrutura de um componente
Um componente Vue.js é criado a partir de um objeto de configuração. Cada propriedade desse objeto determina os recursos e comportamentos do componente. As mais comuns são data, methods, computed e props, há ainda alguns hooks como created, mounted e beforeDestroy.
Essas propriedades podem ser objetos ou funções, a documentação é bem rica e explica cada um deles. Alguns plugins como vue-router adicionam outros opções a essa lista.
Esses plugins usam de mixins ou manipulação do prototype do Vue para adicionar features e comportamentos ao Vue.
Antes de entrar em detalhes sobre mixins e até mesmo o extends, quero que você se atente a algumas coisas.
Estrutura dinamica
Estamos falando de JavaScript, e objetos JavaScript podem ser criados dinamicamente.
const data = () => ({
title: 'Construindo um componente'
})
const methods = {
changeTitle(title) {
this.title = title
}
}
const template = `<div>
<h1 @click="changeTitle('novo titulo')" >{{ title }}<h1/>
</div>`
export default { data, methods, template }
Esse é um exemplo muito bobo e simplório, porém ilustra a capacidade de criar um componente a partir de outras “partes” de código, algo parecido com isso é usado nos helpers mapState, mapGetters e mapActions do Vuex.
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapState(['user']),
...mapGetters(['isLogged']),
},
methods: {
...mapActions(['logout'])
}
}
Que pode ser “traduzido” para o código abaixo, ilustrando o que acontece por baixo dos panos: mapState([‘user’]) dinamicamente cria um objeto com funções que acessam o state do store da aplicação.
const state = {
user() {
return this.$store.state.user
}
}
const getters = {
isLogged() {
return this.$store.gettes.isLogged
}
}
const actions = {
logout() {
return this.$store.dispatch('logout')
}
}
export default {
// computed: { ...state, ...getters }
computed: Object.assign({ }, state, getters),
methods: Object.assign({ }, actions)
}
Ter uma plena compreensão desses mecanismos abre inúmeras possibilidades de reuso e herança.
Extends
A opção extends é bem simples de se entender, pois lembra muito herança clássica. Essa opção permite herdar/injetar comportamento/código no componente.
Ao estender um componente, você pode sobrescrever seu comportamento, criando um comportamento local novo, extremamente semelhante a herança clássica.
Como você também herda coisas como template, é possível criar um componente base e estender ele adicionando coisas especificas.
Imagine uma componente responsável por listar um determinado recurso.
import Grid from 'my-core/components/grid/common'
export default {
extends: Grid,
data () {
return {
resource: '/clients/',
actions: ['edit'],
columns: [{
key: 'name',
label: 'Name'
}, {
key: 'email',
label: 'E-Mail'
}]
}
},
methods: {
callEdit(user) {
// do something
}
}
}
As possibilidades são inúmeras, é possível criar um factory que receba as opções e construa em tempo de execução um objeto a ser estendido. Tudo depende do objetivo e da maneira que se quer usar.
import Grid from 'my-core/components/grid/common'
export default {
extends: Grid({
resource: '/clients/',
actions: ['edit'],
columns: [{
key: 'name',
label: 'Name'
}, {
key: 'email',
label: 'E-Mail'
}]
}),
methods: {
callEdit(user) {
// do something
}
}
}
É possível estender componentes de uma biblioteca de terceiros por exemplo, isso facilita e muito o reaproveitamento.
Mixins
Os mixins são extremamente semelhantes ao extends. A principal diferença é a possibilidade de usar mais de um mixin em um único componente.
Há dois usos comuns para mixins, encapsular regras ou métodos muito utilizados em vários componentes, e injetar comportamentos em todos os componentes da aplicação (global mixin), muito comuns em plugins.
export default Vue => {
Vue.mixin({
created () {
const { title } = this.$options
if (title !== undefined) {
this.$title = title
}
}
})
Object.defineProperty(Vue.prototype, '$title', {
get () {
return this.$store.state.title
},
set () {
this.$store.dispatch('setTitle', ...arguments)
}
})
}
import Vue from 'vue' import titlePlugin from './title-plugin' Vue.use(titlePlugin) ///
<script>
export default {
title: 'Page Title'
}
</script>
<template>
<div>
<h1> {{ $title }} </h1>
</div>
</template>
Esses snippets demostram o uso de mixins de maneira global. Partindo do principio que você esta usando vuex e possui um estado chamado title e uma action chamada setTitle.
A função created do mixin será executada em todos os componentes, e quando a opção title existir, ela vai modificar $title, que usa get/set para recuperar e modificar o state title do vuex.
Usar mixins abre um grande leque de possibilidades, ainda é possível usar factories e ampliar ainda mais as possibilidades.
import ProtectMinStep from './steps/mixins/protect-min-step'
import StepData from './steps/mixins/step-data'
import UserData from './users/mixins/user-data'
export default {
mixins: [ ProtectMinStep(3), StepData(4), UserData ],
}
Imagine um cenário onde você tem uma aplicação que possui etapas a serem cumpridas, ou onde você usa …mapState([‘user’]) com muita frequência…
Qualquer situação de “repetição de código” um mixin pode ser utilizado.
Vue.prototype
JavaScript é uma linguagem multiparadigma baseada em protótipos. Ela permite mudar comportamentos e objetos em tempo de execução (runtime).
Essas características dão ao JavaScript um poder incrível.
Um componente Vue.js é uma instancia do próprio Vue, isso significa que podemos mudar ou adicionar comportamentos em todos os componentes da aplicação.
No exemplo acima, foram injetadas duas propriedades no protótipo do Vue: $http e $avatar, $http expõe um cliente HTTP do axios, enquanto $avatar é apenas uma função que retorna uma url.
Essas propriedades ficam disponíveis em todos os componentes, então é possível usar this.$http no script e $avatar na template. $avatar é um exemplo bobo, porém $http é uma implementação muito comum.
Essa manipulação não se limita a isso, é possível fazer modificações profundas, como no exemplo abaixo (abra o exemplo em uma nova janela e veja o console do navegador)
Não é um exemplo muito comum, porém prova o quão longe podemos ir.
Slots
Slots é uma maneira extremamente comum e simples de se compor componentes. Há um excelente artigo no Vue.js Brasil sobre isso, como o tema é muito bem abordado na documentação e no artigo, não entrarei em detalhes, apenas deixarei dois exemplos de como uma aplicação poderia ser feita com slots. A dica é pensar em legos.
<template>
<div>
<Modal ref="modal">
<h1 slot="title">{{ user.name }}</h1>
<p>{{ user.bio }}</p>
</Modal>
<button type="button" @click="open">Open</button>
</div>
</template>
<script>
import Modal from './modal'
export default {
components: { Modal },
data () {
return {
user: {
name: 'Vinicius',
bio: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...'
}
}
},
methods: {
open () {
this.$refs.modal.open()
}
}
}
</script>
<template>
<RootContainer :menu="menuData" :show-sidebar="showSidebar">
<view-router></view-router>
<footer slot="footer">copyright</footer>
</RootContainer>
</template>
<script>
import RootContainer from './root-container'
export default {
components: { RootContainer },
data () {
return {
menuData: {}
}
},
computed: {
showSidebar () {
// logic here
}
}
}
</script>
Se quiser saber mais sobre meu trabalho, visite codecasts.com.br. Lá você vai ver vídeos sobre JavaScript, jQuery, Gulp, ES6, Vue.JS, Docker, e muito mais.
Thanks to William and Emanuel G de Souza.




