Pular para conteúdo

Renderização de listas

O uso de listas é muito comum em aplicações web. Em geral, as listas são geradas a partir de dados que são obtidos de um servidor. Sempre que precisamos renderizar uma lista de dados, precisamos de um mecanismo que nos permita iterar sobre os dados e gerar um elemento para cada item da lista. O VueJS oferece uma forma simples de renderizar listas de dados.

Alguns exemplos de listas são:

  • Lista de produtos
  • Lista de usuários
  • Lista de tarefas
  • Lista de cidades
  • Lista de países

De forma geral, os dados em listas podem estar em um array ou em um objeto. Em um array, os dados são indexados por números. Em um objeto, os dados são indexados por chaves. Ainda, no caso dos arrays, podemos ter dados um array simples, como um array de strings, ou um array de objetos.

A manipulação de listas é um dos recursos mais importantes do VueJS. Nessa aula, vamos aprender a renderizar listas de dados.

Renderização dos elementos de listas

Nessa aula, vamos aprender a renderizar listas de dados. Usamos esse termo para descrever a renderização de arrays e objetos, bem como arrays de objetos.

Renderização de arrays de strings

Para isso, inicialmente vamos criar um array de strings e renderizar cada item da lista em uma tag <li>.

./src/App.vue
1
2
3
4
5
6
7
8
<script setup>
  const items = ['Item 1', 'Item 2', 'Item 3'];
</script>
<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">{{ item }}</li>
  </ul>
</template>

Neste exemplo, o array items contém três strings. A diretiva v-for é usada para iterar sobre os itens do array. A variável item é usada para referenciar cada item da lista. A sintaxe v-for="item in items" é equivalente a v-for="(item, index) in items". O segundo parâmetro da diretiva v-for é o índice do item na lista. O índice é opcional e pode ser omitido. Contudo, como precisamos de um valor único para a propriedade key, vamos usar o índice do item na lista.

A propriedade key é usada pelo VueJS para identificar cada item da lista e é obrigatória quando usamos a diretiva v-for. Sem a propriedade key, o VueJS não consegue identificar cada item da lista e não consegue atualizar corretamente a lista quando os dados são alterados.

Para reforçar os conceitos, é importante entender que o elemento <li> é renderizado uma vez para cada item da lista. O VueJS renderiza o elemento <li> e, em seguida, atualiza o conteúdo do elemento com o valor do item da lista. No exemplo, o elemento <li> é renderizado três vezes. A primeira vez, o conteúdo do elemento é atualizado com o valor do primeiro item da lista. A segunda vez, o conteúdo do elemento é atualizado com o valor do segundo item da lista. A terceira vez, o conteúdo do elemento é atualizado com o valor do terceiro item da lista.

Por fim, fizemos a iteração no elemento <li>, mas poderíamos ter feito com qualquer outro elemento ou mesmo com um componente. Por exemplo, poderíamos ter feito a iteração no elemento <p>.

./src/App.vue
1
2
3
4
5
6
<script setup>
  const items = ['Item 1', 'Item 2', 'Item 3'];
</script>
<template>
  <p v-for="(item, index) in items" :key="index">{{ item }}</p>
</template>

Renderização de arrays de objetos

Vamos agora renderizar uma lista de objetos. Para isso, vamos criar um array de objetos e renderizar cada item da lista em uma tag <li>.

./src/App.vue
<script setup>
  const items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
  ];
</script>
<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

Neste exemplo, o array items contém três objetos. A diretiva v-for é usada para iterar sobre os itens do array. A variável item é usada para referenciar cada item da lista. A sintaxe v-for="item in items" é equivalente a v-for="(item, index) in items". Contudo, como não precisamos do índice do item na lista, vamos usar a sintaxe mais simples. Porém, ainda precisamos de um valor único para a propriedade key e, neste caso, usaremos o valor da propriedade id do objeto.

Neste exemplo, o elemento <li> é renderizado três vezes. A primeira vez, o conteúdo do elemento é atualizado com o valor da propriedade name do primeiro item da lista. A segunda vez, o conteúdo do elemento é atualizado com o valor da propriedade name do segundo item da lista. A terceira vez, o conteúdo do elemento é atualizado com o valor da propriedade name do terceiro item da lista.

Por fim, fizemos a iteração no elemento <li>, mas poderíamos ter feito com qualquer outro elemento ou mesmo com um componente. Por exemplo, poderíamos ter feito a iteração no elemento <div>.

./src/App.vue
<script setup>
  const items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
  ];
</script>
<template>
  <div v-for="item in items" :key="item.id">
    <p>{{ item.name }}</p>
  </div>
</template>

Renderização de objetos

Vamos agora renderizar um objeto. Para isso, vamos criar um objeto e renderizar cada propriedade do objeto em uma tag <li>.

./src/App.vue
<script setup>
  const items = {
    id: 1,
    description: 'Item 1',
    price: 10.0,
  };
</script>
<template>
  <ul>
    <li v-for="(value, key) in items" :key="key">{{ key }}: {{ value }}</li>
  </ul>
</template>

Neste exemplo, o objeto items contém três propriedades. A diretiva v-for é usada para iterar sobre as propriedades do objeto. As variáveis value e key são usadas para referenciar o valor e a chave de cada propriedade do objeto. A sintaxe v-for="(value, key) in items" é equivalente a v-for="(value, key, index) in items". Contudo, como não precisamos do índice do item na lista, vamos usar a sintaxe mais simples. Porém, ainda precisamos de um valor único para a propriedade key e, neste caso, usaremos a própria chave da propriedade.

Neste exemplo, o elemento <li> é renderizado três vezes. A primeira vez, o conteúdo do elemento é atualizado com o valor da propriedade id do objeto. A segunda vez, o conteúdo do elemento é atualizado com o valor da propriedade description do objeto. A terceira vez, o conteúdo do elemento é atualizado com o valor da propriedade price do objeto.

O resultado da renderização é o seguinte:

<ul>
  <li>description: Item 1</li>
  <li>price: 10</li>
  <li>id: 1</li>
</ul>

Este exemplo é um pouco diferente dos exemplos anteriores. No exemplo anterior, o objeto items era iterado e cada item da lista era renderizado em um elemento <li>. Neste exemplo, o objeto items é iterado e cada propriedade do objeto é renderizada em um elemento <li>. Por isso, o resultado da renderização é diferente.

Por fim, fizemos a iteração no elemento <li>, mas poderíamos ter feito com qualquer outro elemento ou mesmo com um componente. Por exemplo, poderíamos ter feito a iteração no elemento <p>.

./src/App.vue
<script setup>
  const items = {
    id: 1,
    description: 'Item 1',
    price: 10.0,
  };
</script>
<template>
  <p v-for="(value, key) in items" :key="key">{{ key }}: {{ value }}</p>
</template>

Renderização de arrays de objetos com objetos aninhados

Vamos agora renderizar uma lista de objetos que contém objetos aninhados. Para isso, vamos criar um array de objetos e renderizar cada item da lista em uma tag <li>.

./src/App.vue
<script setup>
  const items = [
    {
      id: 1,
      name: 'Item 1',
      details: { description: 'Item 1 description', price: 10.0 },
    },
    {
      id: 2,
      name: 'Item 2',
      details: { description: 'Item 2 description', price: 20.0 },
    },
    {
      id: 3,
      name: 'Item 3',
      details: { description: 'Item 3 description', price: 30.0 },
    },
  ];
</script>
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <p>{{ item.name }}</p>
      <p>{{ item.details.description }}</p>
      <p>{{ item.details.price }}</p>
    </li>
  </ul>
</template>

Neste exemplo, o array items contém três objetos. A diretiva v-for é usada para iterar sobre os itens do array. A variável item é usada para referenciar cada item da lista.

Poderíamos ter feito duas iterações. A primeira iteração seria para iterar sobre os itens do array e a segunda iteração seria para iterar sobre as propriedades do objeto. Veja o exemplo a seguir:

./src/App.vue
<script setup>
  const items = [
    {
      id: 1,
      name: 'Item 1',
      details: { description: 'Item 1 description', price: 10.0 },
    },
    {
      id: 2,
      name: 'Item 2',
      details: { description: 'Item 2 description', price: 20.0 },
    },
    {
      id: 3,
      name: 'Item 3',
      details: { description: 'Item 3 description', price: 30.0 },
    },
  ];
</script>
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <p>Nome: {{ item.name }}</p>
      <p>Detalhes</p>
      <p v-for="(value, key) in item.details" :key="key">
        {{ key }}: {{ value }}
      </p>
    </li>
  </ul>
</template>

Manipulação de listas

É comum precisarmos adicionar, remover ou atualizar elementos de uma lista. O VueJS oferece uma forma simples de manipular listas de dados.

Adicionando elementos

Para adicionar um elemento a uma lista, basta usar o método push do array. O método push adiciona um elemento ao final do array. Veja o exemplo abaixo:

./src/App.vue
<script setup>
  import { ref } from 'vue'

  const novoItem = ref('')
  const listaCompras = ref(['arroz', 'batata', 'feijão'])

  function adicionar {
    listaCompras.value.push(novoItem.value)
    novoItem.value = ''
  }
</script>
<template>
  <input type="text" v-model="novoItem" />
  <button @click="adicionar">Adicionar</button>
  <ul>
    <li v-for="item in listaCompras">{{ item }</li>
  </ul>
</template>

Note uma diferença importante entre o exemplo acima e o exemplo da aula anterior. No exemplo anterior, a lista de compras era uma constante. Agora, a lista de compras é uma variável reativa. Isso significa que, sempre que o valor da variável listaCompras for alterado, o VueJS vai atualizar a lista de compras na tela.

Para isso, precisamos usar a função ref do VueJS. A função ref cria uma variável reativa. A variável reativa é um objeto que contém o valor da variável e uma propriedade value. A propriedade value contém o valor da variável. Por exemplo, a variável novoItem é uma variável reativa. A variável novoItem contém a propriedade value. A propriedade value contém o valor da variável novoItem. Veja o exemplo abaixo:

Removendo elementos

Para remover um elemento de uma lista, basta usar o método splice do array. O método splice recebe dois parâmetros: o índice do elemento a ser removido e a quantidade de elementos a serem removidos. Veja o exemplo abaixo:

./src/App.vue
<script setup>
  import { ref } from 'vue';

  const listaCompras = ref(['arroz', 'batata', 'feijão']);

  function remover(index) {
    listaCompras.value.splice(index, 1);
  }
</script>
<template>
  <ul>
    <li v-for="(item, index) in listaCompras">
      {{ item }}<button @click="remover(index)">Remover</button>
    </li>
  </ul>
</template>

Atualizando elementos

Para atualizar um elemento de uma lista, basta atribuir um novo valor ao elemento. Vamos usar como exemplo, um array de objetos. Veja o exemplo abaixo:

./src/App.vue
<script setup>
  import { ref } from 'vue';

  const listaCompras = ref([
    { nome: 'arroz', quantidade: 1 },
    { nome: 'batata', quantidade: 2 },
    { nome: 'feijão', quantidade: 3 },
  ]);

  function incrementar(index) {
    listaCompras.value[index].quantidade++;
  }
</script>
<template>
  <ul>
    <li v-for="(item, index) in listaCompras">
      {{ item.nome }} - {{ item.quantidade }}
      <button @click="incrementar(index)">Incrementar</button>
    </li>
  </ul>
</template>

Neste exemplo, a lista de compras é um array de objetos. Cada objeto representa um item da lista de compras. Cada item da lista de compras possui duas propriedades: nome e quantidade. Para atualizar a quantidade de um item, basta incrementar a propriedade quantidade do item.

Exercícios

Para fixar o conteúdo, vamos resolver alguns exercícios.

Exercício 1

Faça uma tela que renderize uma lista de cidades. O componente deve receber um array de cidades como propriedade. A lista deve ser renderizada em um elemento ul. Cada item da lista deve ser renderizado em um elemento li. As cidades iniciais devem ser:

[
  'São Paulo',
  'Rio de Janeiro',
  'Belo Horizonte',
  'Salvador',
  'Fortaleza',
  'Curitiba',
  'Manaus',
  'Recife',
  'Porto Alegre',
  'Brasília',
];

Exercício 2

Usando o mesmo Array do exercício anterior, deve ser renderizada uma lista deve ser renderizada em ordem alfabética. A lista deve ser renderizada em um elemento ul. Cada item da lista deve ser renderizado em um elemento li. Dica: para ordenar um array, você pode usar o método sort do array, preferencialmente em uma propriedade computada.

Exercício 3

Neste exercício você deve criar uma lista de notícias (newsletter), contendo título, data, resumo da notícia, link da notícia e, opcionalmente, a imagem da notícia. Deve haver um botão que, quando clicado, direcionará para a página da notícia, abrindo o link em uma nova aba. Note que você deve abrir a url que está informada na chave link do objeto.

Dica 1: como o link está numa variável e você precisa atribuir esse valor para o atributo href da tag a, você pode usar a diretiva v-bind para fazer isso.

Dica 2: para as imagens você pode apenas usar o endereço público da Internet de uma imagem.

Abaixo, um exemplo de como o objeto de notícias pode ser estruturado:

noticias = [
  {
    titulo: 'Título 1',
    data: '01/01/2023',
    resumo: 'Resumo da notícia 1',
    link: 'https://www.google.com',
    imagem: 'https://placehold.co/600x400.png',
  },
  {
    titulo: 'Título 2',
    data: '02/01/2023',
    resumo: 'Resumo da notícia 2',
    link: 'https://www.google.com',
    imagem: 'https://placehold.co/600x400.png',
  },
];

Correção dos exercícios

Faremos a correção dos exercícios usando a API de composição. Vamos, além disso, corrigir todos os itens num só exemplo. Sugiro que você tente resolver os exercícios antes de ver a correção. A correção é apenas uma sugestão de como você pode resolver os exercícios. Não é a única forma de resolver os exercícios.

Aviso

Importante: Não foi apresentada a correção do exercício 3, referente à lista de notícias (newsletter).

Correção dos exercícios
./src/App.vue
<script setup>
  import { computed, ref } from 'vue';

  const cidade = ref('');
  const cidades = ref([
    'São Paulo',
    'Rio de Janeiro',
    'Belo Horizonte',
    'Salvador',
    'Fortaleza',
    'Curitiba',
    'Manaus',
    'Recife',
    'Porto Alegre',
    'Brasília',
  ]);

  const cidadesOrdenadas = computed(() => [...cidades.value].sort());

  function adicionarCidade() {
    cidades.value.push(cidade.value);
    cidade.value = '';
  }

  function removerCidade(index) {
    cidades.value.splice(index, 1);
  }
</script>

<template>
  <div class="correcao">
    <h1>Correção dos exercícios</h1>
    <div class="form">
      <h2>Formulário de cidades</h2>
      <input type="text" v-model="cidade" />
      <button @click="adicionarCidade">Adicionar</button>
    </div>
    <div class="resultados">
      <div class="cidades">
        <h2>Lista de cidades</h2>
        <ul>
          <li v-for="(cidade, index) in cidades" :key="index">
             {{ cidade }}
            <button @click="removerCidade(index)">Remover</button>
          </li>
        </ul>
      </div>
      <div class="cidades">
        <h2>Cidades ordenadas</h2>
        <ul>
          <li v-for="cidade in cidadesOrdenadas" :key="cidade">
             {{ cidade }}
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<style scoped>
  button {
    padding: 10px;
    border: none;
    border-radius: 5px;
    background-color: #007bff;
    color: #fff;
    cursor: pointer;
  }

  button:hover {
    background-color: #0056b3;
  }

  .correcao {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .form {
    margin: 20px auto;
    background-color: #f5f5f5;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    width: 70%;
  }

  .form input {
    padding: 10px;
    margin-right: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    width: 50%;
  }

  .resultados {
    display: flex;
    justify-content: space-between;
    width: 70%;
  }

  .cidades {
    margin-bottom: 20px;
    background-color: #f5f5f5;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    width: 45%;
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .cidades ul {
    list-style: none;
    padding: 0;
    width: 80%;
  }

  .cidades li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
  }

  .cidades button {
    margin-left: 10px;
  }
</style>

Note que esta é uma das formas de corrigir os exercícios. Existem outras formas de fazer a mesma coisa. O importante é entender os conceitos e como utilizá-los.

Projeto

Neste projeto você deve desenvolver um protótipo de um carrinho de compras de uma livraria. Nesse caso, crie uma lista fixa de produtos, como descrito abaixo:

Estrutura do objeto de produtos

App.vue
const produtos = [
    {
        id: 1,
        titulo: 'Livro 1',
        resenha: 'Descrição breve 1'
        preco: 49.9,
        capa: 'https://placehold.co/600x400.png',
    },
    {
        id: 2,
        titulo: 'Livro 2',
        resenha: 'Descrição breve 2'
        preco: 99.9,
        capa: 'https://placehold.co/600x400.png',
    },
    {
        id: 3,
        titulo: 'Livro 3',
        resenha: 'Descrição breve 3'
        preco: 9.9,
        capa: 'https://placehold.co/600x400.png',
    },
    {
        id: 4,
        titulo: 'Livro 4',
        resenha: 'Descrição breve 4'
        preco: 199.9,
        capa: 'https://placehold.co/600x400.png',
    },
    {
        id: 5,
        titulo: 'Livro 5',
        resenha: 'Descrição breve 5'
        preco: 29.9,
        capa: 'https://placehold.co/600x400.png',
    }
];

O usuário deve poder adicionar os livros ao carrinho. Para isso, deve ser renderizada uma lista de livros. O usuário deve poder clicar em um produto para adicioná-lo ao carrinho. O carrinho deve ser renderizado em uma div ou section abaixo da listagem dos produtos ou ficar escondida e ser apresentada quando clicar no ícone carrinho do layout proposto. (Dica: pode ser criada uma variávelbooleana para controlar essa função de visibilidade do carrinho).

Em cada item do carrinho deve ter a opção de adicionar quantidade ou remover o item do carrinho. Ao final, deve ser apresentado o valor total dos itens no carrinho, que pode ser calculado a partir da quantidade de cada item e do preço do livro.

Uma sugestão para a estrutura do objeto do carrinho é:

Estrutura do objeto do carrinho

App.vue
const carrinho = {
    items: [
        {
            id: 1,
            nome: 'Livro 1',
            preco: 49.9,
            quantidade: 1,
            valorTotal: 49.9,
            },
        {
            id: 2,
            nome: 'Livro 2',
            preco: 99.9,
            quantidade: 2,
            valorTotal: 199.8,
        },
    ],
    frete: 0,
    desconto: 0,
    total: 288.3,
};

Note que o objeto do carrinho possui um atributo total que é o valor total dos itens no carrinho. Ainda, note que cada item do carrinho possui um atributo valorTotal que é o valor total do item multiplicado pela quantidade (1). Ainda, esses dois valores não devem ser informados pelo usuário, mas sim calculados a partir dos valores informados.

  1. Esse valor pode também ser calculado direto na renderização do item. Exemplo: {{ item.preco * item.quantidade }}. Mas, nesse caso, o valor total do item não estará disponível para ser usado em outros lugares do código, como no cálculo do total do carrinho.

Dica 1: você pode calcular o valor total de cada item da compra ao aumentar ou diminuir a quantidade de itens no carrinho, na mesma função que manipula o incrementar e decrementar item do carrinho.

Dica 2: o mesmo pode ser feito com o total da compra, que pode ser recalculado sempre que um item é modificado no carrinho.

Dica 3: se você desejar, todos esses cálculos podem ser feitos em propriedades computadas, mas não é obrigatório.

Dica 4: um modelo de layout pode ser visto aqui