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
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 Filho (Children Types)
- Array como filho (Array as children)
- Função como filha (Function as children)
- Render prop
- Passando um Filho (Children)
- Componente Proxy
- Component de estilo
- 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
Os Fragmentos permitem 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. Parecem tags vazias:
function MeuComponente() {
return (
<>
<div>Olá</div>
<div>Mundo</div>
</>
);
}
Expressões
Use chaves para Incorporar expressões no JSX.
function OlaUsuario() {
let 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",
};
Desestruturando props
Atribuição via desestruturação é um recurso do
Foi adicionado a linguagem no ES2015.
let usuario = { nome: "Ruben" };
let { nome } = usuario;
Funciona com Array também.
let numeros = ["um", "dois"];
let [um, dois] = numeros;
Atribuição via desestruturação (Destructuring assignment) é usado muito em componentes funcionais.
Essas declarações de componente são equivalentes.
function Hello(props) {
return <div>Olá {props.name}!</div>;
}
function Hello({ name }) {
return <div>Olá {name}!</div>;
}
Existe uma sintaxe para atribuir as props
restantes em um objeto.
Se chama Paramêtros e parece assim:
function Hello({ name, ...restProps }) {
return <div>Olá {name}!</div>;
}
Esses três pontos (...
) pegam todas a props que falam e atribuem ao paramêtro 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 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>
.
function Hello({ name, ...restProps }) {
return <div {...restProps}>Hi {name}!</div>;
}
Isso torna a função Hello
super flexível.
Podemos passar atributos DOM oara Hello
e que eles vão ser passados a nossa div
.
<Hello name="Fancy pants" className="fancy-greeting" id="user-greeting" />
Destructuring assignment é popular porque fornece uma maneira de separar props específicos 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 de 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 }) {
let 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 falsy
:
function MyButton({ className, ...props }) {
let 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 pode usar o operador ternário conditional (ternary) operator ou short-circuit are your friends.
if
{
!!condition && <span>Irá renderizar quando for `truthy`</span>;
}
Dica não utilize if dessa maneira:
{
condition && <span>Rendereiza quando `truthy`</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 falsy
unless
(ao menos que)
{
condition || <span>Renderizado quando `falsy`</span>;
}
if-else
(Operador Ternário)
{
condition ? (
<span>Renderizado quando for `truthy`</span>
) : (
<span>Renderizado quando for `falsy`</span>
);
}
Tipos de Filho (Children Types)
React consegue renderizar children
da maioria dos tipos.
Na maioria dos casos é um array
ou uma string
.
String
<div>Hello World!</div>
Array
<div>{["Hello ", <span>World</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>
{["first", "second"].map((item) => (
<li>{item}</li>
))}
</ul>
Esse é o equivalente a renderizar um array
literal.
<ul>{[<li>first</li>, <li>second</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 componete 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 function as children
.
<Width>{(width) => <div>window is {width}</div>}</Width>
Recebemos esse output.
<div>window é 500</div>
Com esta configuração, podemos usar essa prop width
para fazer decisões de render.
<Width>
{(width) => (width > 600 ? <div>min-width requirement met!</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.
class WindowWidth extends React.Component {
constructor() {
super();
this.state = { width: 0 };
}
componentDidMount() {
this.setState({ width: window.innerWidth }, () =>
window.addEventListener("resize", ({ target }) =>
this.setState({ width: target.innerWidth })
)
);
}
render() {
return this.props.children(this.state.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 user 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>
Component de estilo
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.
class HorizontalSplit extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
return (
<FlexContainer>
<div>{this.props.startSide}</div>
<div>{this.props.endSide}</div>
</FlexContainer>
);
}
}
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
class CommentListContainer extends React.Component {
constructor() {
super()
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: comments =>
this.setState({comments: comments});
})
}
render() {
return <CommentList comments={this.state.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 Hello
.
const Hello = ({ 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) =>
class extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
componentDidMount() {
// this would fetch or connect to a store
this.setState({ name: "Michael" });
}
render() {
return <ComposedComponent {...this.props} name={this.state.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 Hello
em Connect
.
const ConnectedMyComponent = Connect(Greeting);
Este é um padrão poderoso para fazer 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="This won't change. Try it." />
Obviamente, os inputs estáticos não são muito úteis para seus usuários.
Então derivamos o value
do state.
class ControlledNameInput extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
render() {
return <input type="text" value={this.state.name} />;
}
}
Então, mudar o input é uma questão de mudar o estado do componente.
return (
<input
value={this.state.name}
onChange={(e) => this.setState({ name: e.target.value })}
/>
);
Esta é 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.