Guia de Padrões React

Guia de Padrões React

Um guia de padrões React em Português.

Baseado no Original por Michael Chan @chantastic

Traduzido para Português e revisado por @rubenmarcus

com contribuição de @LhuizF, @matheusinfo, @luizwbr, @arimariojesus, @gabepinheiro, @GusttavoCastilho e @azraelgarden.

Conteúdo

Traduções

Traduções não verificadas, e links não significam que são aprovadas.

Chinese

Elementos

Elementos são tudo que está envolvido por <>.

<div></div>
<MeuComponente />

Componentes retornam Elementos.

Componentes

Um Componente é definido por uma função que declarada retorna um Elemento React.

function MeuComponente() {
  return <div>Olá Mundo</div>;
}

Fragmentos

Um Fragmento permite agrupar uma lista de filhos sem adicionar nós extras ao DOM.

function MeuComponente() {
  return (
    <React.Fragment>
      <div>Olá</div>
      <div>Mundo</div>
    </React.Fragment>
  );
}

Isto renderizará no DOM apenas os seguintes elementos:

<body>
  <div>Olá</div>
  <div>Mundo</div>
</body>

Sintaxe Curta

Existe uma sintaxe nova e mais curta que você pode utilizar para declarar fragmentos. São as tags vazias:

function MeuComponente() {
  return (
    <>
      <div>Olá</div>
      <div>Mundo</div>
    </>
  );
}

Expressões

Use chaves para Incorporar expressões no JSX.

function OlaUsuario() {
  const nome = "Ruben";

  return <div>Olá {nome}!</div>;
}

Props (Propriedades)

Entenda como props como um argumento externo para possibilitar customizações para seu componente.

function DigaOla(props) {
  return <div>Olá {props.nome}!</div>;
}

defaultProps (Propriedades Padrão)

Especificar valores padrão de props com defaultProps.

function OlaUsuario(props) {
  return <div>Olá {props.nome}!</div>;
}
OlaUsuario.defaultProps = {
  nome: "Visitante",
};

Default function parameters (parâmetros predefinidos de uma função)

Especificar valores padrão de props com default function parameters. Os parâmetros predefinidos de uma função permitem que parâmetros regulares sejam inicializados com valores iniciais caso undefined ou nenhum valor seja passado.

// Utilizamos aqui uma desestruturação no objeto `props` para pegarmos a prop `nome`
function OlaUsuario({ nome = "Visitante" }) {
  return <div>Olá {nome}!</div>;
}

Desestruturando props

Atribuição via desestruturação é um recurso do Javascript moderno.

Foi adicionado a linguagem no ES2015.

const usuario = { nome: "Ruben" };
const { nome } = usuario;

Funciona com Array também.

const numeros = ["um", "dois"];
const [um, dois] = numeros;

Atribuição via desestruturação (Destructuring assignment) é usado muito em componentes funcionais. Essas declarações de componente são equivalentes.

const Ola = (props) => <div>Olá {props.name}!</div>;

const Ola = ({ name }) => <div>Olá {name}!</div>;

Existe uma sintaxe para atribuir as props restantes em um objeto. Se chama Parâmetros e parece assim:

const Ola = ({ name, ...restProps }) => <div>Olá {name}!</div>;

Esses três pontos (...) pegam todas a props que faltam e atribuem ao parâmetro restProps.

Então o que fazer com restProps quando você o tem?

Continue lendo...


Atributos de spread JSX

Atributos de Spread é uma feature do JSX. É uma sintaxe para fornecer as propriedades de um objeto como atributos JSX.

Seguindo o exemplo de Destructuring props, Podemos fazer spread com restProps em nossa <div>.

const Ola = ({ name, ...restProps }) => {
  return <div {...restProps}>Hi {name}!</div>;
};

Isso torna a função Ola super flexível. Podemos passar atributos DOM para Ola e que eles vão ser passados a nossa div.

<Ola name="Fancy pants" className="fancy-greeting" id="user-greeting" />

Atribuição via desestruturação é popular porque fornece uma maneira de separar props específicas de componentes, de atributos específicos de plataforma / DOM.

function Greeting({ name, ...platformProps }) {
  return <div {...platformProps}>Hi {name}!</div>;
}

Mergeando props desestruturadas com outros valores

Componentes são abstrações. Boas abstrações permitem extensão.

Considere esse componente que usa um atributo class para estilizar um button.

function MyButton(props) {
  return <button className="btn" {...props} />;
}

Isso funciona muito bem até tentarmos estendê-lo com outra classe.

<MyButton className="delete-btn">Delete...</MyButton>

Nesse caso, delete-btn substitui btn.

A ordem importa para Atributos de spread JSX. O props.className sendo passado, substitui o className do nosso componente.

Podemos mudar a ordem, mas agora o className nunca vai ser nada além de btn.

function MyButton(props) {
  return <button {...props} className="btn" />;
}

Precisamos usar a atribuição via desestruturação para obter o className e mergear com o className base. Podemos fazer isso simplesmente adicionando todos os valores a uma array e juntando-os com um espaço..

function MyButton({ className, ...props }) {
  const classNames = ["btn", className].join(" ");

  return <button className={classNames} {...props} />;
}

Para não ter problemas com undefined aparecendo no seu className, você pode atualizar sua lógica para pegar valores booleanos falso:

function MyButton({ className, ...props }) {
  const classNames = ["btn", className].filter(Boolean).join(" ").trim();

  return <button className={classNames} {...props} />;
}

Porém, lembre-se de que, se um objeto vazio for passado, ele também será incluído na classe, resultando em: btn [object Object].

A melhor abordagem é fazer uso de packages disponíveis, como classnames ou clsx,que poderia ser usado para unir nomes de classe, evitando que você tenha que lidar com isso manualmente.

Renderização Condicional

Você não consegue usar if else em suas declarações de componente.. Então vocês pode usar o operador ternário conditional (ternary) operator ou short-circuit tranquilamente.

if

{
  !!condition && <span>Irá renderizar quando for `verdadeiro`</span>;
}

Dica não utilize if dessa maneira:

{
  condition && <span>Renderiza quando `verdadeiro`</span>;
}

O React pode imprimir um 0 no seu componente. Quando vem 0 nos seus dados, ele não considera sua variável como falsa, utilizando o !! , ele converte 0 para falso

unless (ao menos que)

{
  condition || <span>Renderizado quando `falso`</span>;
}

if-else (Operador Ternário)

{
  condition ? (
    <span>Renderizado quando for `verdadeiro`</span>
  ) : (
    <span>Renderizado quando for `falso`</span>
  );
}

Tipos de filhos (Children Types)

O React consegue renderizar children da maioria dos tipos. Na maioria dos casos é um array ou uma string.

String

<div>Olá Mundo!</div>

Array

<div>{["Olá ", <span>Mundo</span>, "!"]}</div>

Array como filho (Array as children)

Prover um array como children é muito comum. É como as listas são renderizadas no React.

Usamos o método map() para criar um array de elementos React para cada valor da array.

<ul>
  {["primeiro", "segundo"].map((item) => (
    <li>{item}</li>
  ))}
</ul>

Esse é o equivalente a renderizar um array literal.

<ul>{[<li>primeiro</li>, <li>segundo</li>]}</ul>

Este padrão pode ser combinado com desestruturação, Atributos de Spread JSX e outros componentes, para alguma coesão mais séria.

<ul>
  {arrayOfMessageObjects.map(({ id, ...message }) => (
    <Message key={id} {...message} />
  ))}
</ul>

Função como filha (Function as children)

Componentes React não suportam funções como children. Porém com o padrão, render props conseguimos criar componentes que tomam funções como children filhas.

Render prop

Aqui um componente que utiliza render callback. Não é útil, mas é um exemplo fácil para começar.

const Width = ({ children }) => children(500);

Esse componente chama children como função, com alguns argumentos, nesse caso o número 500.

Para usar esse componente estamos utilizando uma Função como filha (Function as children).

<Width>{(width) => <div>window é {width}</div>}</Width>

Recebemos esse output.

<div>window é 500</div>

Com esta configuração, podemos usar essa prop width para fazer decisões de renderização.

<Width>
  {(width) =>
    width > 600 ? <div>condição de largura mínima atingida!</div> : null
  }
</Width>

Se planejamos usar muito essa condição, podemos definir outros componentes para encapsular a lógica reutilizada.

const MinWidth = ({ width: minWidth, children }) => (
  <Width>{(width) => (width > minWidth ? children : null)}</Width>
);

Claro que um componente Width estático não é útil, mas aquele que observa o window do navegador é. Aqui está um exemplo de implementação.

function WindowWidth({ children }) {
  const [width, setWidth] = useState(0);

  useEffect(() => {
    setWidth(window.innerWidth);
    window.addEventListener("resize", ({ target }) =>
      setWidth(target.innerWidth)
    );
  }, []);

  return children(width);
}

Muitos desenvolvedores preferem Higher Order Components para este tipo de funcionalidade. É uma questão de preferência.

Passando um Filho (Children)

Você pode criar um componente projetado para usar context e renderizar children.

class SomeContextProvider extends React.Component {
  getChildContext() {
    return { some: "context" };
  }

  render() {
    // como retornamos children?
  }
}

Você está diante de uma decisão. Envolver os filhos em uma <div /> estranha que retorne o children diretamente. As primeiras opções adicionam marcação extra (que pode quebrar alguns css). O segundo resultará em erros inúteis.

// option 1: extra div
return <div>{children}</div>;

// option 2: erros inúteis
return children;

É melhor tratar children como um tipo de dados opaco. O React fornece React.Children para lidar com children apropriadamente.

return React.Children.only(this.props.children);

Componente Proxy

(Não tenho certeza se esse nome faz sentido)

Os botões estão em todos os lugares nos aplicativos da web. E cada um deles deve ter o atributo type definido como button .

<button type="button">

Escrever este atributo centenas de vezes pode trazer muitos erros. Podemos escrever um High Level Component para passar props para um componente de button de nível inferior.

const Button = props =>
  <button type="button" {...props}>

Podemos usar Button no lugar button e garantir que o atributo type vai ser sempre aplicado.

<Button />
// <button type="button"><button>

<Button className="CTA">Enviar Dinheiro</Button>
// <button type="button" class="CTA">Enviar Dinheiro</button>

Estilizando componentes

Esse é um Proxy component aplicado às práticas de estilo.

Então temos um botão. Ele usa classes para serem estilizadas como um botão "principal".

<button type="button" className="btn btn-primary">

Podemos gerar esse resultado usando alguns componentes de propósito único.

import classnames from "classnames";

const PrimaryBtn = (props) => <Btn {...props} primary />;

const Btn = ({ className, primary, ...props }) => (
  <button
    type="button"
    className={classnames("btn", primary && "btn-primary", className)}
    {...props}
  />
);

Pode ajudar a visualizar isso.

PrimaryBtn()
  ↳ Btn({primary: true})
    ↳ Button({className: "btn btn-primary"}, type: "button"})
      ↳ '<button type="button" class="btn btn-primary"></button>'

Usando esses componentes, todos eles resultam no mesmo resultado.

<PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />

Isso pode ser uma grande vantagem para a manutenção do estilo. Ele isola todas as preocupações de estilo em um único componente.

Switch de Eventos

Quando criamos Event Handlers (Controladores de Eventos) é comum nomeá-los assim:handle{eventName}.

handleClick(e) { /* do something */ }

Para componentes que controlam vários tipos de eventos, essas funções podem ser tornar repetitivas. os nomes podem não trazer muito valor, pois na verdade são proxy de outras ações/funções.

handleClick() { require("./actions/doStuff")(/* action stuff */) }
handleMouseEnter() { this.setState({ hovered: true }) }
handleMouseLeave() { this.setState({ hovered: false }) }

Considere escrever um unico Controlador de eventos e fazer o switch com o event.type.

handleEvent({type}) {
  switch(type) {
    case "click":
      return require("./actions/doStuff")(/* action dates */)
    case "mouseenter":
      return this.setState({ hovered: true })
    case "mouseleave":
      return this.setState({ hovered: false })
    default:
      return console.warn(`No case for event type "${type}"`)
  }
}

Para componentes simples você pode chamar funções importadas de componentes direto, usando arrow functions.

<div onClick={() => someImportedAction({ action: "DO_STUFF" })}

Componente de Layout

Os componentes de layout resultam em alguma forma de elemento DOM estático. Pode não ser necessário atualizar com frequência, ou nunca.

Considere um componente que renderize dois children lado a lado

<HorizontalSplit
  startSide={<SomeSmartComponent />}
  endSide={<AnotherSmartComponent />}
/>

Podemos otimizar agressivamente esse componente.

Embora HorizontalSplit seja pai para ambos os componentes, nunca será seu dono. Podemos dizer para ele nunca atualizar, sem interromper o lifecycle dos componentes internos.

const HorizontalSplit = ({ startSide, endSide }) => {
  return (
    <FlexContainer>
      <div>{startSide}</div>
      <div>{endSide}</div>
    </FlexContainer>
  );
}

export default memo(HorizontalSplit);

Container Components

"Um container faz a busca de dados e, em seguida, renderiza seu subcomponente correspondente. É isso." - Jason Bonta

Olhando esse componente CommentList.

const CommentList = ({ comments }) => (
  <ul>
    {comments.map((comment) => (
      <li>
        {comment.body}-{comment.author}
      </li>
    ))}
  </ul>
);

Podemos criar um novo componente responsável por buscar dados e renderizar o componente CommentList

const CommentListContainer = () => {
  const [comments, setComments] = useState([])

  useEffect(() => {
    const fetchComments = async () => {
       try {
          const response = await fetch("/my-comments.json");
          const data = await response.json();
          setComments(data);

       } catch (error) {
          console.log("error: ", error);
       }

    fetchComments();
  }, [])

  return <CommentList comments={comments} />
}

Podemos escrever diferentes containers para diferentes contextos de aplicação.

Higher-order components

Uma higher-order function é uma função que recebe e / ou retorna uma função. Não é mais complicado do que isso. Então, o que é um High Order Component?

Se você já estiver usando componentes container, esses são apenas containers genéricos, envolvidos em uma função.

Vamos começar com nosso componente Ola .

const Ola = ({ name }) => {
  if (!name) {
    return <div>Conectando...</div>;
  }

  return <div>Olá {name}!</div>;
};

Se obtiver props.name, ele renderizará esses dados. Caso contrário, irá renderizar "Conectando ...".

Agora, para o dado de ordem superior.

const Connect = (ComposedComponent) => {
  const [name, setName] = useState("");

  useEffect(() => {
    // Isso seria um "fetch" ou uma conexão com a "store"
    setName("Michael");
  }, []);

  return <ComposedComponent {...props} name={name} />;
};

Esta é apenas uma função que retorna o componente que renderiza o componente que passamos como um argumento.

Última etapa, precisamos envolver nosso componente Ola em Connect.

const ConnectedMyComponent = Connect(Ola);

Este é um padrão poderoso para fazer requisições ( fetch ) e fornecer dados para qualquer número de componentes funcionais.

Elevando o state (state hoisting)

Aqui temos um componente contador, que vai passar seu state para o componente pai

import React, { useState } from "react";

function Counter(props) {
  const {
    count: [count, setCount],
  } = {
    count: useState(0),
    ...(props.state || {}),
  };

  return (
    <div>
      <h3>{count}</h3>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

na nossa função App, escutamos o state através da props state do componente Counter

function App() {
  const [count, setCount] = useState(0);

  return (
    <div className="App">
      <h2>Estado</h2>
      <Counter state={{ count: [count, setCount] }} />
    </div>
  );
}

na teoria poderiamos passar esse estado do componente filho, para qualquer outro componente irmão dele.

Inputs Controlados

É difícil falar sobre inputs controlados em abstrato. Vamos começar com um input não controlado (normal) e partir daí.

<input type="text" />

Quando você mexe com esse input no navegador, você vê suas alterações.

Isto é o normal.

Um input controlado desabilita as mutações do DOM que tornam isso possível. Você seta o value do input no escopo do componente e ele não altera no escopo do DOM.

<input type="text" value="Isso não será alterado. Tente." />

Obviamente, os inputs estáticos não são muito úteis para seus usuários. Então derivamos o value do state.


function ControlledNameInput () {

  const [name, setName] = useState("");

  return <input type="text" value={name} />;
}

Então, mudar o input é uma questão de mudar o estado do componente.

return (
  <input type="text" value={name} onChange={(e) => setName(e.target.value)} />
);

Este é um input controlado. Ele apenas atualiza o DOM quando o estado é alterado em nosso componente. Isso é inestimável ao criar interfaces de usuário consistentes.

Se está usando componentes funcionais para elementos de form, leia sobre state hoisting para mover o state do componente acima no tree.