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
Conteúdo
- Traduções
- Elementos
- Componentes
- Fragmentos
- Expressões
- Props (Propriedades)
- defaultProps (Propriedades Padrão)
- Desestruturando props
- Atributos de spread JSX
- Mergeando props desestruturadas com outros valores
- Renderização Condicional
- Tipos de filhos (Children Types)
- Array como filho (Array as children)
- Função como filha (Function as children)
- Render prop
- Passando um Filho (Children)
- Componente Proxy
- Estilizando componentes
- Switch de Eventos
- Componente de Layout
- Container Components
- Higher-order components
- Elevando o state (state hoisting)
- Inputs Controlados
Traduções
Traduções não verificadas, e links não significam que são aprovadas.
Elementos
Elementos são tudo que está envolvido por <>.
<div></div>
<MeuComponente />
Componentes retornam Elementos.
Componentes
Um Componente é defindo 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 extendê-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.
function HorizontalSplit() {
return (
<FlexContainer>
<div>{this.props.startSide}</div>
<div>{this.props.endSide}</div>
</FlexContainer>
);
}
export default React.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
import React, { useEffect, useState } from "react";
function CommentListContainer() {
const [comments, setComments] = useState([]);
useEffect(() => {
$.ajax({
url: "/my-comments.json",
dataType: "json",
success: (myComments) => setComments(myComments),
});
}, []);
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 que é "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.