React Best Practices





Você está desenvolvendo com React ou apenas se interessa por esta tecnologia? Então, seja bem-vindo ao meu novo projeto - Total React .



Introdução



Trabalho com o React há 5 anos, no entanto, quando se trata da estrutura do aplicativo ou de sua aparência (design), é difícil nomear qualquer abordagem universal.



Ao mesmo tempo, existem certas técnicas de codificação que permitem garantir o suporte de longo prazo e a escalabilidade de seus projetos.



Este artigo é uma espécie de conjunto de regras para o desenvolvimento de aplicativos React que provaram ser eficazes para mim e para as equipes com as quais trabalhei.



Essas regras cobrem componentes, estrutura do aplicativo, teste, estilo, gerenciamento de estado e recuperação de dados. Os exemplos fornecidos são intencionalmente simplificados a fim de se concentrar em princípios gerais em vez de implementação específica.



As abordagens propostas não são a verdade última. Esta é só minha opinião. Existem muitas maneiras diferentes de realizar a mesma tarefa.



Componentes



Componentes funcionais


Dê preferência a componentes funcionais - eles têm sintaxe mais simples. Eles não têm métodos de ciclo de vida, construtores e código clichê. Eles permitem que você implemente a mesma lógica dos componentes da classe, mas com menos esforço e de uma forma mais descritiva (o código do componente é mais fácil de ler).



Use componentes funcionais até que você precise de fusíveis. O modelo mental a ter em mente será muito mais simples.



//     ""
class Counter extends React.Component {
  state = {
    counter: 0,
  }

  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({ counter: this.state.counter + 1 })
  }

  render() {
    return (
      <div>
        <p> : {this.state.counter}</p>
        <button onClick={this.handleClick}></button>
      </div>
    )
  }
}

//       
function Counter() {
  const [counter, setCounter] = useState(0)

  handleClick = () => setCounter(counter + 1)

  return (
    <div>
      <p> : {counter}</p>
      <button onClick={handleClick}></button>
    </div>
  )
}

      
      





Componentes consistentes (sequenciais)


Mantenha o mesmo estilo ao criar componentes. Coloque as funções auxiliares no mesmo lugar, use a mesma exportação (por padrão ou por nome) e use a mesma convenção de nomenclatura para componentes.



Cada abordagem tem suas próprias vantagens e desvantagens.



Não importa como você exporta os componentes, na parte inferior ou na definição, apenas siga uma regra.



Nomes de componentes


Sempre nomeie os componentes. Isso ajuda a analisar o rastreamento de pilha de erros ao usar as ferramentas de desenvolvedor React.



Também ajuda a determinar qual componente você está desenvolvendo atualmente.



//    
export default () => <form>...</form>

//    
export default function Form() {
  return <form>...</form>
}

      
      





Funções secundárias


As funções que não requerem os dados armazenados no componente devem estar fora (fora) do componente. O local ideal para isso é antes da definição do componente, para que o código possa ser examinado de cima para baixo.



Isso reduz o "ruído" do componente - apenas o essencial é deixado nele.



//    
function Component({ date }) {
  function parseDate(rawDate) {
    ...
  }

  return <div> {parseDate(date)}</div>
}

//      
function parseDate(date) {
  ...
}

function Component({ date }) {
  return <div> {parseDate(date)}</div>
}

      
      





Deve haver um número mínimo de funções auxiliares dentro do componente. Coloque-os do lado de fora, passando valores do estado como argumentos.



Seguindo as regras para a criação de funções "limpas", é mais fácil rastrear erros e estender o componente.



//      ""    
export default function Component() {
  const [value, setValue] = useState('')

  function isValid() {
    // ...
  }

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid) {
            // ...
          }
        }}
      >
        
      </button>
    </>
  )
}

//          
function isValid(value) {
  // ...
}

export default function Component() {
  const [value, setValue] = useState('')

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid(value)) {
            // ...
          }
        }}
      >
        
      </button>
    </>
  )
}

      
      





Marcação estática (rígida)


Não crie marcação estática para navegação, filtros ou listas. Em vez disso, crie um objeto com configurações e faça um loop sobre ele.



Isso significa que você só precisa alterar a marcação e os elementos em um lugar, se necessário.



//     
function Filters({ onFilterClick }) {
  return (
    <>
      <p> </p>
      <ul>
        <li>
          <div onClick={() => onFilterClick('fiction')}> </div>
        </li>
        <li>
          <div onClick={() => onFilterClick('classics')}>
            
          </div>
        </li>
        <li>
          <div onClick={() => onFilterClick('fantasy')}></div>
        </li>
        <li>
          <div onClick={() => onFilterClick('romance')}></div>
        </li>
      </ul>
    </>
  )
}

//       
const GENRES = [
  {
    identifier: 'fiction',
    name: ' ',
  },
  {
    identifier: 'classics',
    name: '',
  },
  {
    identifier: 'fantasy',
    name: '',
  },
  {
    identifier: 'romance',
    name: '',
  },
]

function Filters({ onFilterClick }) {
  return (
    <>
      <p> </p>
      <ul>
        {GENRES.map(genre => (
          <li>
            <div onClick={() => onFilterClick(genre.identifier)}>
              {genre.name}
            </div>
          </li>
        ))}
      </ul>
    </>
  )
}

      
      





Dimensões do componente


Um componente é apenas uma função que pega adereços e retorna marcação. Eles seguem os mesmos princípios de design.



Se uma função realizar muitas tarefas, mova parte da lógica para outra função. O mesmo é verdade para componentes - se um componente contiver funcionalidade muito complexa, divida-o em vários componentes.



Se parte da marcação for complexa, incluir loops ou condições, extraia-a em um componente separado.



Conte com adereços e retornos de chamada para interação e recuperação de dados. O número de linhas de código nem sempre é um critério objetivo para sua qualidade. Lembre-se sempre de ser responsivo e abstrato.



Comentários em JSX


Se precisar de uma explicação sobre o que está acontecendo, crie um bloco de comentário e adicione as informações necessárias lá. A marcação faz parte da lógica, portanto, se você sentir que precisa comentar sobre uma parte, faça-o.



function Component(props) {
  return (
    <>
      {/*    ,       */}
      {user.subscribed ? null : <SubscriptionPlans />}
    </>
  )
}

      
      





Disjuntores


Um erro no componente não deve interromper a interface do usuário. Existem casos raros em que desejamos que um erro crítico resulte em uma falha ou redirecionamento do aplicativo. Na maioria dos casos, basta remover um determinado elemento da tela.



Em uma função que solicita dados, podemos ter qualquer número de blocos try / catch. Use fusíveis não apenas no nível superior de seu aplicativo, mas envolva todos os componentes que podem lançar uma exceção para evitar uma cascata de erros.



function Component() {
  return (
    <Layout>
      <ErrorBoundary>
        <CardWidget />
      </ErrorBoundary>

      <ErrorBoundary>
        <FiltersWidget />
      </ErrorBoundary>

      <div>
        <ErrorBoundary>
          <ProductList />
        </ErrorBoundary>
      </div>
    </Layout>
  )
}

      
      





Suportes de desestruturação


A maioria dos componentes são funções que recebem adereços e marcações de retorno. Em uma função normal, usamos argumentos passados ​​diretamente para ela, portanto, no caso de componentes, faz sentido seguir uma abordagem semelhante. Não há necessidade de repetir "adereços" em todos os lugares.



A razão para não usar a desestruturação pode ser a diferença entre os estados externo e interno. No entanto, em uma função normal, não há diferença entre argumentos e variáveis. Você não precisa complicar as coisas.



//    "props"   
function Input(props) {
  return <input value={props.value} onChange={props.onChange} />
}

//        
function Component({ value, onChange }) {
  const [state, setState] = useState('')

  return <div>...</div>
}

      
      





Número de adereços


A resposta à pergunta sobre o número de adereços é muito subjetiva. O número de adereços passados ​​para um componente é correlacionado com o número de variáveis ​​usadas pelo componente. Quanto mais adereços são passados ​​para o componente, maior sua responsabilidade (ou seja, o número de tarefas resolvidas pelo componente).



Um grande número de adereços pode indicar que o componente está fazendo muito.



Se mais de 5 adereços são passados ​​para um componente, penso na necessidade de dividi-lo. Em alguns casos, o componente só precisa de muitos dados. Por exemplo, um campo de entrada de texto pode precisar de muitos acessórios. Por outro lado, isso é um sinal claro de que parte da lógica precisa ser extraída em um componente separado.



Observe: quanto mais adereços um componente recebe, mais frequentemente ele é redesenhado.



Passando um objeto em vez de primitivos


Uma maneira de reduzir o número de adereços passados ​​é passar um objeto em vez de primitivos. Em vez de, por exemplo, transmitir o nome do usuário, seu endereço de e-mail, etc. um de cada vez, você pode agrupá-los. Isso também tornará mais fácil adicionar novos dados.



//      
<UserProfile
  bio={user.bio}
  name={user.name}
  email={user.email}
  subscription={user.subscription}
/>

//   ,  
<UserProfile user={user} />

      
      





Renderização condicional


Em alguns casos, o uso de cálculos curtos (o operador lógico AND &&) para renderização condicional pode resultar em 0 sendo exibido na IU. Para evitar isso, use o operador ternário. A única desvantagem dessa abordagem é um pouco mais de código.



O operador && reduz a quantidade de código, o que é ótimo. Ternarnik é mais "prolixo", mas sempre funciona corretamente. Além disso, torna-se menos demorado adicionar uma alternativa conforme necessário.



//     
function Component() {
  const count = 0

  return <div>{count && <h1>: {count}</h1>}</div>
}

//   ,   
function Component() {
  const count = 0

  return <div>{count ? <h1>: {count}</h1> : null}</div>
}

      
      





Operadores ternários aninhados


Operadores ternários tornam-se difíceis de ler após o primeiro nível de aninhamento. Embora os ternários economizem espaço, é melhor expressar suas intenções de forma explícita e óbvia.



//     
isSubscribed ? (
  <ArticleRecommendations />
) : isRegistered ? (
  <SubscribeCallToAction />
) : (
  <RegisterCallToAction />
)

//      
function CallToActionWidget({ subscribed, registered }) {
  if (subscribed) {
    return <ArticleRecommendations />
  }

  if (registered) {
    return <SubscribeCallToAction />
  }

  return <RegisterCallToAction />
}

function Component() {
  return (
    <CallToActionWidget
      subscribed={subscribed}
      registered={registered}
    />
  )
}

      
      





Listas


Loop através dos elementos de uma lista é uma tarefa comum, geralmente realizada com o método "map ()". No entanto, em um componente que contém muitas marcações, o recuo extra e a sintaxe "map ()" não melhoram a legibilidade.



Se você precisar iterar sobre os elementos, extraia-os em um componente separado, mesmo se a marcação for pequena. O componente pai não precisa de detalhes, ele só precisa "saber" que a lista está sendo renderizada em um determinado lugar.



A iteração pode ser deixada em um componente cujo único propósito é exibir a lista. Se a marcação da lista for complexa e longa, é melhor extraí-la em um componente separado.



//       
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      {articles.map(article => (
        <div>
          <h3>{article.title}</h3>
          <p>{article.teaser}</p>
          <img src={article.image} />
        </div>
      ))}
      <div>    {page}</div>
      <button onClick={onNextPage}></button>
    </div>
  )
}

//      
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      <ArticlesList articles={articles} />
      <div>    {page}</div>
      <button onClick={onNextPage}></button>
    </div>
  )
}

      
      





Adereços padrão


Uma maneira de definir props padrão é adicionar um atributo "defaultProps" ao componente. No entanto, com essa abordagem, a função do componente e os valores de seus argumentos estarão em locais diferentes.



Portanto, é mais preferível atribuir valores "padrão" ao desestruturar adereços. Isso torna o código mais fácil de ler de cima para baixo e mantém as definições e valores em um só lugar.



//          
function Component({ title, tags, subscribed }) {
  return <div>...</div>
}

Component.defaultProps = {
  title: '',
  tags: [],
  subscribed: false,
}

//      
function Component({ title = '', tags = [], subscribed = false }) {
  return <div>...</div>
}

      
      





Funções de renderização aninhadas


Se você precisar extrair lógica ou marcação de um componente, não a coloque em uma função no mesmo componente. Um componente é uma função. Isso significa que a parte extraída do código será representada como uma função aninhada.



Isso significa que a função aninhada terá acesso ao estado e aos dados da função externa. Isso torna o código menos legível - o que essa função faz (pelo que é responsável)?



Mova a função aninhada para um componente separado, dê a ela um nome e conte com adereços em vez de fechamentos.



//       
function Component() {
  function renderHeader() {
    return <header>...</header>
  }
  return <div>{renderHeader()}</div>
}

//      
import Header from '@modules/common/components/Header'

function Component() {
  return (
    <div>
      <Header />
    </div>
  )
}

      
      





Gestão do estado



Caixas de câmbio


Às vezes, precisamos de uma maneira mais poderosa de definir e gerenciar o estado do que "useState ()". Tente usar "useReducer ()" antes de usar bibliotecas de terceiros. É uma ótima ferramenta para gerenciar estados complexos sem a necessidade de dependências.



Combinado com contexto e TypeScript, useReducer () pode ser muito poderoso. Infelizmente, não é usado com muita frequência. As pessoas preferem usar bibliotecas especiais.



Se você precisar de várias peças de estado, mova-as para o redutor:



//       
const TYPES = {
  SMALL: 'small',
  MEDIUM: 'medium',
  LARGE: 'large'
}

function Component() {
  const [isOpen, setIsOpen] = useState(false)
  const [type, setType] = useState(TYPES.LARGE)
  const [phone, setPhone] = useState('')
  const [email, setEmail] = useState('')
  const [error, setError] = useSatte(null)

  return (
    // ...
  )
}

//      
const TYPES = {
  SMALL: 'small',
  MEDIUM: 'medium',
  LARGE: 'large'
}

const initialState = {
  isOpen: false,
  type: TYPES.LARGE,
  phone: '',
  email: '',
  error: null
}

const reducer = (state, action) => {
  switch (action.type) {
    ...
    default:
      return state
  }
}

function Component() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    // ...
  )
}

      
      





Ganchos versus HOCs e adereços de renderização


Em alguns casos, precisamos "proteger" um componente ou fornecer a ele acesso a dados externos. Existem três maneiras de fazer isso - componentes de ordem superior (HOCs), renderização via adereços e ganchos.



A maneira mais eficaz é usar ganchos. Eles estão em total conformidade com a filosofia de que um componente é uma função que usa outras funções. Os ganchos permitem que você acesse fontes múltiplas contendo funcionalidade externa sem a ameaça de um conflito entre essas fontes. O número de ganchos não importa, nós sempre sabemos de onde obtemos o valor.



HOCs recebem valores como adereços. Nem sempre é óbvio de onde vêm os valores, do componente pai ou de seu invólucro. Além disso, o encadeamento de vários adereços é uma fonte bem conhecida de bugs.



Usar adereços de renderização leva a um aninhamento profundo e baixa legibilidade. Colocar vários componentes com acessórios de renderização na mesma árvore agrava ainda mais a situação. Além disso, eles usam apenas valores na marcação, então a lógica para obter os valores deve ser escrita aqui ou recebida de fora.



No caso de ganchos, estamos trabalhando com valores simples que são fáceis de rastrear e não se misturam com JSX.



//    -
function Component() {
  return (
    <>
      <Header />
        <Form>
          {({ values, setValue }) => (
            <input
              value={values.name}
              onChange={e => setValue('name', e.target.value)}
            />
            <input
              value={values.password}
              onChange={e => setValue('password', e.target.value)}
            />
          )}
        </Form>
      <Footer />
    </>
  )
}

//   
function Component() {
  const [values, setValue] = useForm()

  return (
    <>
      <Header />
        <input
          value={values.name}
          onChange={e => setValue('name', e.target.value)}
        />
        <input
          value={values.password}
          onChange={e => setValue('password', e.target.value)}
        />
      <Footer />
    </>
  )
}

      
      





Bibliotecas para obter dados


Freqüentemente, os dados do estado "vêm" da API. Precisamos armazená-los na memória, atualizá-los e recebê-los em vários lugares.



Bibliotecas modernas como React Query fornecem uma boa quantidade de ferramentas para manipular dados externos. Podemos armazenar dados em cache, excluí-los e solicitar novos. Essas ferramentas também podem ser usadas para enviar dados, acionar uma atualização de outro dado, etc.



Trabalhar com dados externos é ainda mais fácil se você usar um cliente GraphQL como o Apollo . Ele implementa o conceito de estado do cliente fora da caixa.



Bibliotecas de gerenciamento estadual


Na grande maioria dos casos, não precisamos de nenhuma biblioteca para gerenciar o estado do aplicativo. Eles são necessários apenas em aplicativos muito grandes com estado muito complexo. Em tais situações, uso uma de duas soluções - Recoil ou Redux .



Modelos mentais componentes



Container e Representante


Normalmente, é comum dividir os componentes em dois grupos - representantes e recipientes ou "inteligente" e "estúpido".



O resultado final é que alguns componentes não contêm estado e funcionalidade. Eles são chamados apenas pelo componente pai com alguns adereços. O componente do contêiner, por sua vez, contém alguma lógica de negócios, envia solicitações para receber dados e gerencia o estado.



Este modelo mental realmente descreve o padrão de design MVC para aplicativos do lado do servidor. Ela funciona muito bem lá.



Mas em aplicativos clientes modernos, essa abordagem não se justifica. Colocar toda a lógica em vários componentes leva ao excesso de volume. Isso leva ao fato de que um componente resolve muitos problemas. O código para esse componente é difícil de manter. Conforme o aplicativo cresce, manter o código em um estado adequado torna-se quase impossível.



Componentes com e sem estado


Divida os componentes em componentes com e sem estado. O modelo mental mencionado acima sugere que um pequeno número de componentes deve conduzir a lógica de todo o aplicativo. Este modelo pressupõe a divisão da lógica no número máximo possível de componentes.



Os dados devem ser o mais próximo possível do componente em que são usados. Ao usar o cliente GrapQL, recebemos dados em um componente que exibe esses dados. Mesmo que não seja um componente de nível superior. Não pense em recipientes, pense na responsabilidade dos componentes. Determine o componente mais apropriado para manter uma parte do estado



Por exemplo, um componente <Form /> deve conter dados de formulário. O componente <Input /> deve receber valores e callbacks. O componente <Button /> deve notificar o formulário sobre o desejo do usuário de enviar dados para processamento, etc.



Quem é o responsável pela validação do formulário? Campo de entrada? Isso significa que esse componente é responsável pela lógica de negócios do aplicativo. Como informará o formulário sobre um erro? Como o estado de erro será atualizado? O formulário "saberá" sobre essa atualização? Caso ocorra algum erro, será possível enviar os dados para processamento?



Quando essas questões surgem, fica claro que há uma confusão de responsabilidades. Nesse caso, a "entrada" é melhor permanecer um componente sem estado e receber mensagens de erro do formulário.



Estrutura do aplicativo



Agrupamento por rota / módulo


O agrupamento por contêineres e componentes torna o aplicativo difícil de aprender. Determinar a qual parte de um aplicativo um componente específico pertence pressupõe uma familiaridade "próxima" com a base de código inteira.



Nem todos os componentes são iguais - alguns são usados ​​globalmente, outros são projetados para atender a necessidades específicas. Esta estrutura é adequada para pequenos projetos. No entanto, para projetos de médio a grande porte, essa estrutura é inaceitável.



//        
├── containers
|   ├── Dashboard.jsx
|   ├── Details.jsx
├── components
|   ├── Table.jsx
|   ├── Form.jsx
|   ├── Button.jsx
|   ├── Input.jsx
|   ├── Sidebar.jsx
|   ├── ItemCard.jsx

//     /
├── modules
|   ├── common
|   |   ├── components
|   |   |   ├── Button.jsx
|   |   |   ├── Input.jsx
|   ├── dashboard
|   |   ├── components
|   |   |   ├── Table.jsx
|   |   |   ├── Sidebar.jsx
|   ├── details
|   |   ├── components
|   |   |   ├── Form.jsx
|   |   |   ├── ItemCard.jsx

      
      





Desde o início, agrupe os componentes por rota / módulo. Essa estrutura permite suporte e expansão de longo prazo. Isso impedirá que o aplicativo supere sua arquitetura. Se você confiar na "arquitetura de componentes de contêiner", isso acontecerá muito rapidamente.



A arquitetura baseada em módulo é altamente escalável. Você simplesmente adiciona novos módulos sem aumentar a complexidade do sistema.



A "arquitetura do contêiner" não está errada, mas não é muito geral (abstrata). Ele não contará a ninguém que o aprender, a não ser que usa o React para desenvolver o aplicativo.



Módulos comuns


Componentes como botões, campos de entrada e cartões são onipresentes. Mesmo se você não estiver usando uma estrutura baseada em componentes, extraia-os em componentes compartilhados.



Dessa forma, você pode ver quais componentes comuns são usados ​​em sua aplicação, mesmo sem a ajuda de um Storybook . Isso evita a duplicação de código. Você não quer que cada membro de sua equipe crie sua própria versão do botão, não é? Infelizmente, isso geralmente ocorre devido à arquitetura de aplicativo deficiente.



Caminhos absolutos


As partes individuais do aplicativo devem ser alteradas o mais facilmente possível. Isso se aplica não apenas ao código do componente, mas também à sua localização. Caminhos absolutos significam que você não precisa alterar nada ao mover o componente importado para um local diferente. Também torna mais fácil localizar o componente.



//     
import Input from '../../../modules/common/components/Input'

//      
import Input from '@modules/common/components/Input'

      
      





Eu uso o prefixo "@" como um indicador do módulo interno, mas também vi exemplos de uso do caractere "~".



Envolvendo componentes externos


Tente não importar muitos componentes de terceiros diretamente. Ao criar um adaptador para esses componentes, podemos modificar sua API, se necessário. Também podemos alterar as bibliotecas usadas em um só lugar.



Isso se aplica a ambas as bibliotecas de componentes, como a IU e utilitários Semantic. A maneira mais simples é exportar novamente esses componentes do módulo compartilhado.



O componente não precisa saber qual biblioteca específica estamos usando.



//     
import { Button } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'

//       
import { Button, DatePicker } from '@modules/common/components'

      
      





Um componente - um diretório


Eu crio um diretório de componentes para cada módulo em meus aplicativos. Primeiro, crio um componente. Então, se houver necessidade de arquivos adicionais relacionados ao componente, como estilos ou testes, crio um diretório para o componente e coloco todos os arquivos nele.



É uma boa prática criar um arquivo "index.js" para reexportar o componente. Isso permite que você não altere os caminhos de importação e evite a duplicação do nome do componente - "import Form from 'components / UserForm / UserForm'". No entanto, você não deve colocar o código do componente no arquivo "index.js", pois isso tornará impossível encontrar o componente pelo nome da guia no editor de código.



//        
├── components
    ├── Header.jsx
    ├── Header.scss
    ├── Header.test.jsx
    ├── Footer.jsx
    ├── Footer.scss
    ├── Footer.test.jsx

//      
├── components
    ├── Header
        ├── index.js
        ├── Header.jsx
        ├── Header.scss
        ├── Header.test.jsx
    ├── Footer
        ├── index.js
        ├── Footer.jsx
        ├── Footer.scss
        ├── Footer.test.jsx

      
      





atuação



Otimização prematura


Antes de iniciar a otimização, certifique-se de que há motivos para isso. Seguir cegamente as práticas recomendadas é perda de tempo se não tiver impacto no aplicativo.



Claro, você precisa pensar em coisas como otimização, mas sua preferência deve ser desenvolver componentes legíveis e de fácil manutenção. Um código bem escrito é mais fácil de melhorar.



Se você notar um problema de desempenho do aplicativo, meça e determine a causa. Não faz sentido reduzir o número de re-renderizações com um tamanho de pacote enorme.



Depois de identificar os problemas, corrija-os em ordem de impacto no desempenho.



Tamanho da construção


A quantidade de JavaScript enviada ao navegador é um fator-chave no desempenho do aplicativo. O aplicativo em si pode ser muito rápido, mas ninguém saberá sobre ele se você tiver que pré-carregar 4 MB de JavaScript para executá-lo.



Não aponte para um pacote. Divida seu aplicativo no nível da rota e muito mais. Certifique-se de enviar a quantidade mínima de código ao navegador.



Carregue em segundo plano ou quando o usuário pretende obter outra parte do aplicativo. Se clicar em um botão iniciar um download de PDF, você pode começar a baixar a biblioteca correspondente no momento em que passar o mouse sobre o botão.



Re-renderização - retornos de chamada, matrizes e objetos


Você deve se esforçar para reduzir o número de reconstruções de componentes. Lembre-se disso, mas também de que renderizações desnecessárias raramente têm um impacto significativo no aplicativo.



Não envie callbacks como adereços. Com essa abordagem, a função é recriada a cada vez, disparando uma nova renderização.



Se você tiver problemas de desempenho causados ​​por fechamentos, livre-se deles. Mas não torne seu código menos legível ou muito prolixo.



Passar matrizes ou objetos explicitamente se enquadra na mesma categoria de problemas. Eles são comparados por referência, portanto, não passam em uma verificação superficial e acionam uma nova renderização. Se você precisar passar um array estático, crie-o como uma constante antes de definir o componente. Isso permitirá que a mesma instância seja passada todas as vezes.



Testando



Teste de instantâneo


Certa vez, me deparei com um problema interessante ao fazer o teste de instantâneo: comparar “new Date ()” sem um argumento com a data atual sempre retornou “false”.



Além disso, os instantâneos só resultam em montagens com falha quando o componente é alterado. Um fluxo de trabalho típico é o seguinte: faça alterações no componente, falhe no teste, atualize o instantâneo e continue.



É importante entender que os instantâneos não substituem os testes de nível de componente. Pessoalmente, não uso mais esse tipo de teste.



Teste de renderização correta


O principal objetivo do teste é confirmar se o componente está funcionando conforme o esperado. Certifique-se de que o componente retorne a marcação correta com os adereços padrão e aprovados.



Além disso, certifique-se de que a função sempre retorne resultados corretos para entradas específicas. Verifique se tudo o que você precisa é exibido corretamente na tela.



Testando estado e eventos


Um componente com estado geralmente muda em resposta a um evento. Crie uma simulação de evento e verifique se o componente responde corretamente a ele.



Certifique-se de que os manipuladores sejam chamados e que os argumentos corretos sejam passados. Verifique a configuração correta do estado interno.



Testando casos extremos


Depois de cobrir o código com testes básicos, adicione alguns testes para verificar casos especiais.



Isso pode significar passar um array vazio para garantir que o índice não seja acessado sem verificação. Também pode significar chamar um erro em um componente (por exemplo, em uma solicitação de API) para verificar se ele foi tratado corretamente.



Teste de integração


O teste de integração significa testar uma página inteira ou um componente grande. Este tipo de teste significa testar o desempenho de uma determinada abstração. Ele fornece um resultado mais convincente de que o aplicativo está funcionando conforme o esperado.



Os componentes individuais podem passar nos testes de unidade com sucesso, mas as interações entre eles podem causar problemas.



Estilização



CSS-to-JS


Este é um assunto muito controverso. Pessoalmente, prefiro usar bibliotecas como Styled Components ou Emotion, que permitem escrever estilos em JavaScript. Um arquivo a menos. Não pense em coisas como nomes de classes.



O bloco de construção do React é um componente, então a técnica CSS-in-JS, ou mais precisamente, all-in-JS, é na minha opinião a técnica preferida.



Observe que outras abordagens de estilo (SCSS, módulos CSS, bibliotecas com estilos como Tailwind) não estão erradas, mas ainda recomendo usar CSS-in-JS.



Componentes estilizados


Normalmente, tento manter os componentes estilizados e o componente que os usa no mesmo arquivo.



No entanto, quando há muitos componentes estilizados, faz sentido movê-los para um arquivo separado. Já vi essa abordagem usada em alguns projetos de código aberto como o Spectrum.



Recebendo dados



Bibliotecas para trabalhar com dados


O React não fornece nenhuma ferramenta especial para obter ou atualizar dados. Cada equipe cria sua própria implementação, geralmente incluindo um serviço para funções assíncronas que interagem com a API.



Essa abordagem significa que é nossa responsabilidade rastrear o status do download e lidar com os erros de HTTP. Isso leva a verbosidade e muito código clichê.



Em vez disso, é melhor usar bibliotecas como React Query e SWR . Eles tornam as interações do servidor uma parte orgânica do ciclo de vida do componente de uma forma idiomática - usando ganchos.



Eles têm suporte integrado para armazenamento em cache, gerenciamento de estado de carga e tratamento de erros. Eles também eliminam a necessidade de bibliotecas de gerenciamento de estado para lidar com esses dados.



Obrigado pela atenção e um bom começo de semana de trabalho.



All Articles