# Básico sobre Componentes

# Exemplo Base

Aqui está um exemplo de um componente Vue:

// Criando uma aplicação Vue
const app = Vue.createApp({})

// Definindo um novo componente global chamado button-counter
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Você me clicou {{ count }} vezes.
    </button>`
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

INFO

Estamos mostrando um exemplo simples aqui, mas em uma típica aplicação Vue, usamos Componentes Single File em vez de template strings. Você pode encontrar mais informações sobre eles nesta seção.

Componentes são instâncias reutilizáveis ​​com um nome: neste caso, <button-counter>. Podemos usar este componente como um elemento personalizado dentro de uma instância raiz:

<div id="components-demo">
  <button-counter></button-counter>
</div>
1
2
3
app.mount('#components-demo')
1

Veja o exemplo Básico sobre Componentes por Vue.js Brasil (@vuejs-br) no CodePen.

Uma vez que componentes são instâncias reutilizáveis, eles aceitam as mesmas opções que a instância raiz, como data, computed, watch, methods e gatilhos de ciclo de vida. As únicas exceções são as poucas opções específicas da raiz, como el.

# Reutilizando Componentes

Componentes podem ser reutilizados quantas vezes você quiser:

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
1
2
3
4
5

Veja o exemplo Básico sobre Componentes: Reutilizando Componentes por Vue.js Brasil (@vuejs-br) no CodePen.

Perceba que ao clicar nos botões, cada um mantêm seu próprio e único count. Isso acontece porque cada vez que você usa um componente, uma nova instância dele é criada.

# Organizando Componentes

É comum que um aplicativo seja organizado em uma árvore de componentes aninhados:

Árvore de Componentes

Por exemplo, você pode ter componentes para o cabeçalho, barra lateral e área de conteúdo, cada um normalmente contendo outros componentes para navegação, como links, postagens de blog, etc.

Para usar esses componentes em templates, eles devem ser registrados para que o Vue saiba deles. Há dois tipos de registro de componentes, sendo eles: global e local. Até agora, nós apenas registramos componentes globalmente, usando o método component do app criado:

const app = Vue.createApp({})

app.component('my-component-name', {
  // ... opções ...
})
1
2
3
4
5

Componentes registrados globalmente podem ser usados ​​no template da instância do app criado posteriormente - e até mesmo dentro de todos os subcomponentes da árvore de componentes da instância raiz.

Isso é tudo que você precisa saber sobre registro por hora, mas assim que terminar de ler esta página e se sentir confortável com seu conteúdo, recomendamos voltar mais tarde para ler o guia completo sobre Registro de Componentes.

# Passando Dados aos Filhos com Propriedades

Anteriormente, mencionamos a criação de um componente para postagens de blog. O problema é que esse componente não será útil a menos que você possa passar dados para ele, como o título e o conteúdo da postagem específica que queremos exibir. É aí que entram as propriedades (comumente chamadas apenas de props, já que este é o termo abreviado adotado pelo Vue).

Propriedades são atributos personalizados que você pode registrar em um componente. Quando um valor é passado para uma propriedade de atributo, ele se torna uma propriedade naquela instância do componente. Para passar um título para o componente de postagem do nosso blog, podemos incluí-lo na lista de propriedades que este componente aceita, usando a opção props:

const app = Vue.createApp({})

app.component('blog-post', {
  props: ['title'],
  template: `<h4>{{ title }}</h4>`
})

app.mount('#blog-post-demo')
1
2
3
4
5
6
7
8

Um componente pode ter quantas propriedades você quiser e, por padrão, qualquer valor pode ser passado para qualquer propriedade. No modelo acima, você verá que podemos acessar esse valor na instância do componente, assim como acontece com data.

Uma vez que uma propriedade é registrada, você pode passar dados para ela como um atributo personalizado, dessa forma:

<div id="blog-post-demo" class="demo">
  <blog-post title="Minha jornada com Vue"></blog-post>
  <blog-post title="Escrevendo sobre o Vue"></blog-post>
  <blog-post title="Porquê Vue é tão divertido"></blog-post>
</div>
1
2
3
4
5

Veja o exemplo Básico sobre Componentes: Usando props por Vue.js Brasil (@vuejs-br) no CodePen.

Em uma aplicação comum, no entanto, você provavelmente terá uma série de postagens em data:

const App = {
  data() {
    return {
      posts: [
        { id: 1, title: 'Minha jornada com Vue' },
        { id: 2, title: 'Escrevendo sobre o Vue' },
        { id: 3, title: 'Porquê Vue é tão divertido' }
      ]
    }
  }
}

const app = Vue.createApp(App)

app.component('blog-post', {
  props: ['title'],
  template: `<h4>{{ title }}</h4>`
})

app.mount('#blog-posts-demo')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

E, em seguida, poderá querer renderizar um componente para cada uma delas:

<div id="blog-posts-demo">
  <blog-post
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
  ></blog-post>
</div>
1
2
3
4
5
6
7

Acima, você viu que podemos usar v-bind para passar propriedades dinamicamente. Isso é especialmente útil quando você não sabe o conteúdo exato que irá renderizar com antecedência.

Por enquanto, isso é tudo que você precisa saber sobre propriedades, mas assim que terminar de ler esta página e se sentir confortável com seu conteúdo, recomendamos voltar mais tarde para ler o guia completo sobre Propriedades.

# Escutando Eventos dos Filhos

Conforme desenvolvemos nosso componente <blog-post>, alguns recursos podem exigir comunicação de volta com o componente pai. Por exemplo, podemos decidir incluir um recurso de acessibilidade para ampliar o texto das postagens do blog, deixando o resto da página com o tamanho padrão.

No componente pai, podemos oferecer suporte a esse recurso adicionando uma propriedade postFontSize:

const App = {
  data() {
    return {
      posts: [
        /* ... */
      ],
      postFontSize: 1
    }
  }
}
1
2
3
4
5
6
7
8
9
10

A qual poderia ser usada no template para controlar o tamanho da fonte de todas as postagens do blog:

<div id="blog-posts-events-demo">
  <div v-bind:style="{ fontSize: postFontSize + 'em' }">
    <blog-post v-for="post in posts" :key="post.id" :title="title"></blog-post>
  </div>
</div>
1
2
3
4
5

Agora, vamos adicionar um botão para ampliar o texto após o título de cada postagem:

app.component('blog-post', {
  props: ['title'],
  template: `
    <div class="blog-post">
      <h4>{{ title }}</h4>
      <button>
        Aumentar texto
      </button>
    </div>
  `
})
1
2
3
4
5
6
7
8
9
10
11

O problema é que este botão não executa nada:

<button>
  Aumentar texto
</button>
1
2
3

Ao clicar no botão, precisamos comunicar ao componente pai que deve ampliar o texto de todas as postagens. Felizmente, as instâncias de componente fornecem um sistema de eventos personalizados para resolver esse problema. O componente pai pode escolher ouvir qualquer evento na instância do componente filho com v-on ou @, assim como faríamos com um evento DOM nativo:

<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>
1

Então, o componente filho pode emitir um evento por si próprio chamando o método $emit, passando o nome do evento que o pai poderá escutar:

<button @click="$emit('enlarge-text')">
  Aumentar texto
</button>
1
2
3

Graças à escuta @enlarge-text="postFontSize += 0.1", o componente pai receberá o evento e atualizará o valor de postFontSize.

Veja o exemplo Básico sobre Componentes: Emitindo Eventos por Vue.js Brasil (@vuejs-br) no CodePen.

Para sermos mais explícitos, podemos listar os eventos emitidos na opção emits do componente.

app.component('blog-post', {
  props: ['title'],
  emits: ['enlarge-text']
})
1
2
3
4

Isso permitirá que você verifique todos os eventos emitidos pelo componente e, opcionalmente, validá-los.

# Emitindo um Valor com um Evento

Às vezes é útil emitir um valor específico com um evento. Por exemplo, nós talvez queiramos que o componente <blog-post> seja responsável por definir de quanto em quanto aumentar a fonte. Nesses casos, podemos usar um segundo parâmetro no método $emit para prover tal valor:

<button @click="$emit('enlarge-text', 0.1)">
  Aumentar texto
</button>
1
2
3

Então, quando escutarmos o evento no componente pai, podemos acessar o valor emitido com $event:

<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>
1

Ou, se o manipulador de eventos for um método:

<blog-post ... @enlarge-text="onEnlargeText"></blog-post>
1

Então o valor será passado como o primeiro parâmetro desse método:

methods: {
  onEnlargeText(enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}
1
2
3
4
5

# Usando v-model em Componentes

Eventos personalizados podem também ser usados para criar inputs personalizados que funcionam com v-model. Lembre-se que:

<input v-model="searchText" />
1

Tem a mesma funcionalidade que:

<input :value="searchText" @input="searchText = $event.target.value" />
1

No entanto, quando usado em um componente, v-model faria isso:

<custom-input
  :model-value="searchText"
  @update:model-value="searchText = $event"
></custom-input>
1
2
3
4

WARNING

Observe que usamos model-value com kebab-case aqui porque estamos trabalhando com um template diretamente no DOM. Você pode encontrar uma explicação detalhada sobre os atributos kebab-cased vs. camelCased na seção Ressalvas na Análise do template DOM

Para realmente funcionar, o <input> dentro do componente precisa:

  • Vincular o atributo value com a propriedade modelValue
  • No input, emitir um evento update:modelValue com o novo valor

Então, aqui está isso em ação:

app.component('custom-input', {
  props: ['modelValue'],
  template: `
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
  `
})
1
2
3
4
5
6
7
8
9

Agora o v-model deve funcionar perfeitamente com esse componente:

<custom-input v-model="searchText"></custom-input>
1

Outra maneira de se habilitar a funcionalidade do v-model em um componente personalizado é usar a o recurso dos dados computados em definir um getter e setter separadamente.

No exemplo a seguir, nós refatoramos o componente de custom-input usando um dado computado.

Lembre-se de que o método get deve retornar a propriedade modelValue (ou qualquer outro nome de propriedade que esteja sendo usado para vinculação), e o método set deve disparar o $emit correspondente para essa propriedade ser alterada.

app.component('custom-input', {
  props: ['modelValue'],
  template: `
    <input v-model="value">
  `,
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) {
        this.$emit('update:modelValue', value)
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Isso é tudo que você precisa saber sobre eventos personalizados em componentes por hora, mas assim que você terminar de ler essa página e se sentir confortável com o conteúdo, recomendamos retornar mais tarde para ler o guia completo de Eventos Personalizados.

# Distribuição de Conteúdo com Slots

Assim como em elementos HTML, muitas vezes é útil ser capaz de passar conteúdo dentro de um componente, dessa forma:

<alert-box>
  Algo ruim aconteceu.
</alert-box>
1
2
3

Que renderizaria algo assim:

Veja o exemplo Básico sobre Componentes: slots por Vue.js Brasil (@vuejs-br) no CodePen.

Felizmente, isso é facilmente realizado com um elemento <slot>:

app.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Erro!</strong>
      <slot></slot>
    </div>
  `
})
1
2
3
4
5
6
7
8

Como você viu acima, nós só adicionamos o slot para aonde queremos que o conteúdo vá – e é isso!

Isso é tudo que você precisa saber sobre slots por hora, mas assim que você terminar de ler essa página e se sentir confortável com o conteúdo, recomendamos retornar mais tarde para ler o guia completo sobre Slots.

# Componentes Dinâmicos

Às vezes, é útil alternar dinamicamente entre componentes, como em uma interface de abas:

Veja o exemplo Básico sobre Componentes: Componentes Dinâmicos por Vue.js Brasil (@vuejs-br) no CodePen.

O exemplo acima é possível por causa do elemento <component> com o atributo especial is:

<!-- O componente atualiza quando currentTabComponent muda -->
<component :is="currentTabComponent"></component>
1
2

No exemplo acima, currentTabComponent pode conter:

  • o nome do componente registrado, ou
  • o objeto de opções de inicialização um componente

Veja esse exemplo (opens new window) para experimentar com este código por sua conta, ou veja essa versão (opens new window) para um exemplo vinculando ao objeto de opções de inicialização de um componente, ao invés de vincular ao seu nome registrado.

Tenha em mente que esse atributo pode ser utilizado com elementos HTML comuns, porém, eles serão tratados como componentes, o que significa que todos os atributos serão vinculados como atributos do DOM. Para algumas propriedades como value funcionarem da maneira esperada, você terá que vinculá-las usando o modificador .prop.

Isso é tudo que você precisa saber sobre componentes dinâmicos por hora mas, assim que você terminar de ler essa página e se sentir confortável com o conteúdo, recomendamos retornar mais tarde para ler o guia completo sobre Componentes Dinâmicos & Assíncronos.

# Ressalvas na Análise do template DOM

Alguns elementos HTML, como <ul>, <ol>, <table> e <select> têm restrições do que pode aparecer dentro deles, e alguns elementos como <li>, <tr>, e <option> podem aparecer apenas dentro de certos elementos.

Isso nos leva a problemas quando usamos componentes com elementos que tem tais restrições. Por exemplo:

<table>
  <blog-post-row></blog-post-row>
</table>
1
2
3

O componente <blog-post-row> será removido como um conteúdo inválido, causando erros na eventual renderização. Felizmente, podemos usar a diretiva especial is como uma forma de contornar o problema:

<table>
  <tr v-is="'blog-post-row'"></tr>
</table>
1
2
3

WARNING

O valor de v-is deve ser uma String literal válida no JavaScript:

<!-- Incorreto, nada será renderizado -->
<tr v-is="blog-post-row"></tr>

<!-- Correto -->
<tr v-is="'blog-post-row'"></tr>
1
2
3
4
5

Além disso, atributos HTML são insensíveis à notação de maiúsculas/minúsculas (case-insensitive), então os navegadores interpretarão quaisquer caracteres maiúsculos como minúsculos. Isso significa que, quando você está usando templates diretamente no DOM, nomes de propriedades camelCased e parâmetros de manipuladores de eventos precisarão usar seus equivalentes kebab-cased (ou seja, em minúsculas e delimitados por hífen):

// camelCase em JavaScript
app.component('blog-post', {
  props: ['postTitle'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
})
1
2
3
4
5
6
7
<!-- kebab-case em HTML -->
<blog-post post-title="hello!"></blog-post>
1
2

Deve-se observar que essas limitações não se aplicam se você estiver usando templates baseados em Strings, de uma das seguintes fontes:

Isso é tudo que você precisa saber sobre componentes dinâmicos por hora – na verdade, esse é o fim da seção Essenciais do Vue. Parabéns! Ainda há mais a ser aprendido, mas antes, recomendamos que tire um tempo para brincar com o Vue por conta própria e criar alguma coisa divertida.

Assim que você se sentir confortável com o conteúdo que vimos, recomendamos retornar mais tarde para ler o guia completo de Componentes Dinâmicos & Assíncronos, como também as outras páginas na seção Componentes em Detalhes da barra lateral.

Deployed on Netlify.
Atualizado pela última vez: 10/6/2020, 9:44:13 AM