Aguarde...

8 de dezembro de 2019

Simplificando o código com o Maps em JavaScript

Simplificando o código com o Maps em JavaScript

Os desenvolvedores geralmente se encontram em situações em que precisam retornar resultados diferentes com base em várias condições. Um caso específico em que isso acontece frequentemente é quando queremos renderizar JSX dentro de um componente diferente com base em alguma variável de estado que pode ser alternada. 


Como resultado, muitas vezes o código acaba assim:

const DataCard = ({ data }) => {
  const [cardType, setCardType] = useState("sessions");
  const Icon = cardType === "sessions" ? IconSession : IconPost;
  const title = cardType === "sessions" ? "Daily user sessions" : "Post data";
  return (
    <div className="data-card">
      <Icon/>
      <Button
        onClick={() =>
          setCardType(type => (type === "sessions" ? "post" : "sessions"))
        }
      >
        Switch view
      </Button>
      <h2 className="data-card__title">{title}</h2>
      {data[cardType].map(item => (
        <div className="data-card__data">
          <p>{item.name}</p>
          <p>{item.data}</p>
        </div>
      ))}
    </div>
  );
};


Aqui está um exemplo simples em que temos um cartão de dados, como parte de algum painel de análise, com estilos e layout predefinidos. O cartão permite alternar entre sessions e post dados. Os únicos elementos que estão mudando são o ícone e o título do cartão, portanto, faz sentido introduzir cardType booleano, com base no qual o ícone e o título apropriados são renderizados. Além disso, os dados do tipo correto serão exibidos com base nessa alternância.

Além do código ser repetitivo, há outro problema com essa abordagem. Vamos imaginar que nosso componente agora tenha um tipo de dados adicional para exibir – pageViews. Nesse ponto, precisamos refatorar o botão de alternância em uma lista suspensa de tipos disponíveis como primeira etapa. Em seguida, podemos introduzir uma switch declaração em vez de if/else condições detalhadas . Como resultado, o componente atualizado terá a seguinte aparência:
 

const DataCard = ({ data }) => {
  const [cardType, setCardType] = useState({
    value: "sessions",
    label: "Sessions"
  });
  let Icon, title;

  switch (cardType.value) {
    case "sessions":
      Icon = IconSession;
      title = "Daily user sessions";
      break;
    case "post":
      Icon = IconPost;
      title = "Post data";
      break;
    case "pageViews":
      Icon = IconPage;
      title = "Page views";
      break;
    default:
      throw Error(`Unknown card type: ${cardType}`);
  }

  return (
    <div className="data-card">
      <Icon />
      <Dropdown
        options={[
          { value: "sessions", label: "Sessions" },
          { value: "post", label: "Posts" },
          { value: "pageViews", label: "Page Views" }
        ]}
        onChange={selected => setCardType(selected)}
      />
      <h2 className="data-card__title">{title}</h2>
      {data[cardType.value].map(item => (
        <div className="data-card__data">
          <p>{item.name}</p>
          <p>{item.data}</p>
        </div>
      ))}
    </div>
  );
};


O código parece muito menos repetitivo e, no caso de precisarmos exibir mais tipos de dados, é muito fácil adicionar novos case e uma opção ao menu suspenso. No entanto, ainda podemos fazer melhor. E se pudéssemos obter titlee obter Iconalgum tipo de objeto de configuração, dependendo do valor de dataType? Parece que precisamos de uma espécie de mapeamento entre os tipos de dados e as variáveis ​​de componente. É aqui que podemos usar Map a estrutura de dados. 

O mapa é uma adição ao ES6 e é simplesmente uma coleção de pares de valores-chave. Historicamente, em objetos JS, eram usados ​​para armazenar dicionários desses pares, no entanto, o Map tem algumas vantagens sobre os objetos:
1. O Map mantém a ordem das chaves pela inserção, o que não é o caso dos objetos, nos quais a ordem não é garantida. . 
2. O mapa pode ter qualquer valor como chave, enquanto que para objetos são apenas seqüências de caracteres e símbolos. 
3. O mapa pode ser iterado diretamente, enquanto os objetos na maioria dos casos requerem algum tipo de transformação antes disso (por exemplo Object.keys, com , Object.values ou Object.entries).
4. Da mesma forma, o tamanho do mapa pode ser facilmente determinado usando size prop. O objeto deve ser transformado em matriz usando um dos métodos mencionados acima.
5. O mapa possui alguns benefícios de desempenho em casos de operações frequentes de adição / remoção.

Agora que estamos familiarizados com os mapas, refatoremos nosso componente para tirar proveito dessa estrutura de dados. 

const typeMap = new Map([
  ["sessions", ["Daily user sessions", IconSession]],
  ["post", ["Post data", IconPost]],
  ["pageViews", [" Page views", IconPage]]
]);

const DataCard = ({ data }) => {
  const [cardType, setCardType] = useState({
    value: "sessions",
    label: "Sessions"
  });
  const [title, Icon] = typeMap.get(cardType.value);

  return (
    <div className="data-card">
      <Icon />
      <Dropdown
        options={[
          { value: "sessions", label: "Sessions" },
          { value: "post", label: "Posts" },
          { value: "pageViews", label: "Page Views" }
        ]}
        onChange={selected => setCardType(selected)}
      />
      <h2 className="data-card__title">{title}</h2>
      {data[cardType.value].map(item => (
        <div className="data-card__data">
          <p>{item.name}</p>
          <p>{item.data}</p>
        </div>
      ))}
    </div>
  );
};


Observe o quanto o componente ficou mais enxuto após a refatoração switch em um mapa. A princípio, o mapa pode parecer um pouco estranho, parecendo uma matriz multidimensional. O primeiro elemento é a chave e o segundo é o valor. Como chaves e valores podem ser qualquer coisa, mapeamos nossos tipos de dados para matrizes, onde o primeiro elemento é o título e o segundo é o componente do ícone. Normalmente, obter esses dois valores dessa matriz aninhada seria um pouco trabalhoso, no entanto, a destruição da  sintaxe de atribuição torna uma tarefa fácil. O benefício adicional dessa sintaxe é que podemos nomear nossas variáveis ​​como qualquer coisa, o que é útil caso desejemos renomear title ouIcon em outra coisa, sem modificar o próprio mapa. O mapa é declarado fora do componente para não ser recriado desnecessariamente em cada renderização.

Enquanto estamos nisso, por que não refatorar o conjunto de opções suspensas em um mapa também? As opções são apenas mapeamentos entre valores e rótulos, um caso de uso perfeito para um mapa!

const typeMap = new Map([
  ["sessions", ["Daily user sessions", IconSession]],
  ["post", ["Post data", IconPost]],
  ["pageViews", [" Page views", IconPage]]
]);

const typeOptions = new Map([
  ["sessions", "Sessions"],
  ["post", "Posts"],
  ["pageViews", "Page Views"]
]);


const DataCard = ({ data }) => {
  const [cardType, setCardType] = useState({
    value: "sessions",
    label: "Sessions"
  });
  const [Icon, title] = typeMap.get(cardType.value);

  return (
    <div className="data-card">
      <Icon />
      <Dropdown
        options={[...typeOptions].map(([value, label]) => ({ value, label }))}
        onChange={selected => setCardType(selected)}
      />
      <h2 className="data-card__title">{title}</h2>
      {data[cardType.value].map(item => (
        <div className="data-card__data">
          <p>{item.name}</p>
          <p>{item.data}</p>
        </div>
      ))}
    </div>
  );
};


Como o Mapa não possui map método, ele precisa ser transformado em matriz primeiro. Isso pode ser feito usando a dispersão da matriz  ou Array.from . Aqui, novamente, nos beneficiamos da atribuição de desestruturação, para que possamos acessar facilmente label e value dentro do retorno de chamada do método de mapa e criar um objeto com essas chaves e seus valores.

O resultado final parece bastante enxuto e sustentável, onde precisamos apenas fazer algumas alterações em nossos mapas, caso mais tipos de datas sejam adicionados.

Postado em Blog
Escreva um comentário