Back-End

31 jul, 2017

Herança e Composição com Vue.js

Publicidade

Herança e Composição com Vue.js

É 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.