Comunicação entre componentes

VueJS nos ajuda a componetizar as nossas aplicações Web de maneira simples. Então podemos dividir a nossa aplicação em blocos/módulos para melhor utilização desse poder de componetização.

Mas se dividirmos nossa aplicação em vários componentes, como eles conversam entre si?

É isso que vamos ver aqui!

Instalação

Vou usar o NuxtJS para nos poupar tempo na configuração do projeto e vou usar também o módulo do Vuetify para o Nuxt como UI.

Então vamos instalar:

npm i nuxt
npm i -D @nuxtjs/vuetify

Nosso projeto vai ser mais ou menos assim:

-| package.json
-| nuxt.config.js
-| components/
---| TopBar.vue
---| Menu.vue
---| Container.vue
---| FiltrosLista.vue
---| Lista.vue
-| layouts/
---| default.vue
-| pages/
---! index.vue

Nosso arquivo nuxt.config.js vai ter a configuração do uso do módulo @nuxtjs/vuetify e a importação automática de componentes:

nuxt.config.js
export default {
  buildModules: ['@nuxtjs/vuetify'],
  components: true
}

Estrutura

Digamos que nós vamos ter a seguinte estrutura de componentes:

A ideia é, o <TobBar /> se comunicar com o <Menu /> para indicar se exibe ou recolhe o menu.

E o <FiltroLista /> se comunica com <Lista /> através de um botão que vai dizer para <Lista /> carregar.

Componentes

Vamos usar alguns componentes do Vuetify em nossos componentes, assim nos poupa trabalho de estilização.

Vamos criar nossos componentes em components/*. Assim, eles serão importados automaticamente pelo Nuxt, não sendo necessário o registro deles onde forem inseridos. Legal né?

<TobBar />

O <TopBar /> vai ser basicamente o componente <v-app-bar /> do Vuetify.

components/TopBar.vue
<template>
  <v-app-bar>
    <v-app-bar-nav-icon></v-app-bar-nav-icon>
    <v-toolbar-title>Minha aplicação</v-toolbar-title>
  </v-app-bar>
</template>

O <Menu /> vai ser basicamente o componente <v-navigation-drawer /> do Vuetify.

components/Menu.vue
<template>
  <v-navigation-drawer>
    <!--  -->
  </v-navigation-drawer>
</template>

<Container />

O <Container /> vai ser basicamente o componente <v-main /> do Vuetify.

components/Container.vue
<template>
  <v-main>
    <v-container>
      <slot /> <!-- Todo conteúdo vai nesse slot padrão -->
    </v-container>
  </v-main>
</template>

É importante no <Container /> ter o slot padrão <slot /> para conseguirmos inserir conteúdo dentro do nosso componente.

<FiltrosLista />

O <FiltrosLista /> vai ter um input para definir o parâmetro de busca.

components/FiltrosLista.vue
<template>
  <v-row>
    <v-col>
      <v-text-field placeholder="Pesquisar" prepend-inner-icon="mdi-magnify" />
    </v-col>
  </v-row>
</template>

<Lista />

E a nossa <Lista /> vai ser uma lista... :)

components/Lista.vue
<template>
  <v-row>
    <v-col>
      <v-list>
        <v-list-item v-for="(item, index) of items" :key="index">
          <v-list-item-content>
            <v-list-item-title>{{ item.title }}</v-list-item-title>
            <v-list-item-subtitle>{{ item.content }}</v-list-item-subtitle>
          </v-list-item-content>
        </v-list-item>
      </v-list>
    </v-col>
  </v-row>
</template>
<script>
export default {
  data() {
    return {
      items: [
        {
          title: 'Content filtering',
          content: 'Set the content filtering level to restrict appts that can be downloaded'
        },
        {
          title: 'Password',
          content: 'Require password for purchase or use password to restrict purchase'
        }
      ]
    }
  }
}
</script>

Layout

Vamos para criação do nosso Layout. Criamos então o arquivo para o layout padrão em layouts/default.vue

layouts/default.vue
<template>
  <v-app>    
    <TopBar />
    <Menu />
    <Container>
      <Nuxt />
    </Container>
  </v-app>
</template>

v-bind

Agora, vamos pegar todo e qualquer prop passado para <TopBar />, <Menu /> e <Container /> e associar também aos componentes do Vuetify que usamos dentro dos nossos componentes.

Como? Muito simples: v-bind="$attrs":

<TobBar />

components/TopBar.vue
<template>
  <v-app-bar v-bind="$attrs">
    <v-app-bar-nav-icon></v-app-bar-nav-icon>
    <v-toolbar-title>Minha aplicação</v-toolbar-title>
  </v-app-bar>
</template>

Isso faz com que qualquer props passado para <TobBar /> seja associado também ao <v-app-bar />

Todo componente Vue tem a propriedade $attrs que reúne todos os atributos vinculados a ele.

O uso do v-bind="$attrs" faz com que todos os atributos ($attrs) recebidos pelo nosso componente (<TobBar />) sejam vinculados ao <v-app-bar />. Por exemplo, na imagem abaixo, o último quadro ilustra o que acontece quando usamos o v-bind="$attrs":

Atribuímos as props app e props1="valor1" para <TopBar />.

Dentro do <TopBar />, usamos v-bind="$attrs" no <v-app-bar>.

Isso faz com que <v-app-bar > receba os mesmos atributos associados ao <TopBar />.

Se quisermos usar algum dos atributos dentro do nosso componente <TopBar />, é necessário declarar esse atributo como props, caso contrário não iremos conseguir acessar com o this.props1.

components/TopBar.vue
<template>
  <v-app-bar v-bind="$attrs">
    <v-app-bar-nav-icon></v-app-bar-nav-icon>
    <v-toolbar-title>Minha aplicação - {{ props1 }}</v-toolbar-title>
  </v-app-bar>
</template>
<script>
export default {
  props: [props1],
  method: {
    funcao() {
      // ...
      this.props1
    }
  }
}
</script>

Nosso <TopBar /> ficaria então assim:

components/TopBar.vue
<template>
  <v-app-bar v-bind="$attrs">
    <v-app-bar-nav-icon></v-app-bar-nav-icon>
    <v-toolbar-title>Minha aplicação</v-toolbar-title>
  </v-app-bar>
</template>
components/Menu.vue
<template>
  <v-navigation-drawer v-bind="$attrs">
    <!--  -->
  </v-navigation-drawer>
</template>

<Container />

components/Container.vue
<template>
  <v-main v-bind="$attrs">
    <v-container>
      <slot /> <!-- Todo conteúdo vai nesse slot padrão -->
    </v-container>
  </v-main>
</template>

Layout

No nosso layout precisamos apenas inserir o atributo app nos componentes certos, para o Vuetify trabalhar corretamente:

layouts/default.vue
<template>
  <v-app>    
    <TopBar app />
    <Menu app />
    <Container app>
      <Nuxt />
    </Container>
  </v-app>
</template>

Esse atributo app é um requisito do Vuetify.

Saiba mais aqui.

Comunicação

A comunicação entre esses 2 pode ser feita com eventos ou simplesmente um v-model.

Editar esta página no Github Atualizado em Fri, Apr 1, 2022