Skip to main content

Boas práticas de programação com ReactJS

Boas práticas de programação são diretrizes que ajudam a escrever código mais legível, eficiente e seguro. Elas podem ser aplicadas a qualquer linguagem de programação ou biblioteca de interface para o usuário, como React.

info

Escrevi um arquivo de Clean Code com Javascript, que pode ser encontrado Clean Code

Componentização:

Divida a interface do usuário em componentes reutilizáveis e independentes. Isso facilita a manutenção e promove a reutilização de código.

Cada componente precisa ter sua responsabilidade única e bem definida, evitando complexidade desnecessária, muitas condições dentro de um mesmo componente e funções muito extensas.

Componentes pequenos também são mais fáceis de testar, entender e evoluir.

// evite
function UserProfile({ user }) {
return (
<div>
<h1>{user.name}</h1> {/* Imagine aqui contendo outras informações */}
{user?.role && (
<>
<img src={user.role.avatar} alt={user.name} />
<p>{user.role.description}</p>
<p>{user.role.responsability}</p>
</>
)}
<p>{user.bio}</p> {/* Imagine aqui contendo outras informações */}
<button onClick={() => handleFollow(user.id)}>Follow</button> {/* Imagine aqui contendo outras ações */}
</div>
);
}

// prefira
function UserProfile({ user }) {
return (
<div>
<UserHeader user={user} />
{user?.role && <UserRole role={user.role} />}
<FollowButton userId={user.id} />
</div>
);
}

Com a separação acima é muito mais claro o que está sendo feito, possibilitando também o compartilhamento de componentes, que é uma característica muito importante do React.

State e props:

Gerencie o estado da aplicação de forma eficiente, utilizando o estado local (useState) e props para passar dados entre componentes. Evite o uso excessivo de estado global, a menos que seja necessário.

Imutabilidade:

Mantenha a imutabilidade do estado sempre que possível. Isso ajuda a evitar efeitos colaterais indesejados e simplifica o rastreamento de alterações de estado.

// evite
function handleAddItem() {
const items = this.state.items;
items.push("newItem");
this.setState({ items: items });
}

// prefira
function handleAddItem() {
this.setState((prevState) => ({
items: [...prevState.items, "newItem"],
}));
}

Controle de estado centralizado:

Considere o uso de um gerenciador de estado, como Zustand ou Context API, para centralizar o estado da aplicação e facilitar o compartilhamento de dados entre componentes.

Mas não utilize para todo os cenários, mantendo o compartilhamento de estados somente quando realmente necessário.

A utilização em demasia pode causar problemas para aplicação, efetiso colaterais e dificultade de manutenção.

Ciclo de vida do componente:

Entenda e utilize os métodos do ciclo de vida do componente de forma apropriada. Isso é crucial para execução de lógicas específicas em diferentes fases da vida do componente.

É importante entendre a fase de recebimento de propriedades e estados, como funcionam alguns hooks:

info
  • useState useState
  • useEffect useEffect // aqui explico como functiona o ciclo de vida do React

Performance:

Otimize o desempenho da aplicação, evitando renderizações desnecessárias. Utilize memoização para componentes funcionais quando apropriado.

Exemplos de memoização:

// utilizando memo
const MyComponent = (props) {
// ..do something
}

export default memo(MyComponent)
// utilizando memo com comparação de props
const MyComponent = (props) {
// ..do something
}

function areEqual(prevProps, nextProps) {
return prevProps.id === nextProps.id
}

export default memo(MyComponent, areEqual)
// utilizando useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Testes unitários:

Escreva testes unitários para os seus componentes e lógica de negócios. Ferramentas como Jest e Vitest são comumente utilizadas para testes em projetos React.

Testes unitários ajudam a criar uma "documentação" do código. É comum que não se lembre de como um código funciona, mas com testes unitários é possível entender o que o código faz. Além do que várias pessoas/times podem interagir com o código levando em consideração os testes. Isso ajuda bastante, sem contar é claro que aumenta a qualidade das entregas.

Boas práticas e padrões de desenvolvimento:

Adote um estilo consistente de código, seguindo guias de estilo como o Airbnb JavaScript Style Guide. Utilize ferramentas como ESLint para garantir a consistência e qualidade do código.

É comum que o time também tenha o seu padrão de desenvolvimento, pra facilitar que outros membros ingressem na equipe e até mesmo evolução do código adote documentações claras e objetivas e como as entregas devem ser feitas.

Ex.:

  • Estrura de pastas e camadas do projeto
  • Padrão de nomenclatura de variáveis, funções e componentes
  • Padrão de nomenclatura de commits, PRs e release
  • Padrão de nomenclatura de branches (muita das vezes linkados ao board utilizado pela equipe)

Tratamento de erros:

Implemente mecanismos robustos de tratamento de erros. Como a adição de mensagens de erro claras e informativas, e a utilização de ferramentas de monitoramento de erros, como DataDog, Sentry e outros.

Adicione ErrorBoundary no projeto, isso ajuda a capturar erros e exibir mensagens amigáveis para o usuário. Além é claro de evitar que a aplicação quebre com mensagens indesejáveis, sem que o usuário saiba o que aconteceu/fazer.

Tome cuidado com chamadas duplicadas, loops em hooks, warnings/erros estourando no console, etc.

Roteamento eficiente:

Se a sua aplicação React envolve navegação entre diferentes páginas, utilize uma biblioteca de roteamento eficiente, como React Router. Isso ajuda a manter uma navegação fluida e estruturada.

Lazy loading:

Aproveite o conceito de carregamento sob demanda (lazy loading) para otimizar o tempo de carregamento da aplicação, especialmente em projetos grandes. Isso implica carregar componentes apenas quando são necessários.

Ex.:

import React, { lazy, Suspense } from "react";

const OtherComponent = lazy(() => import("./OtherComponent"));

function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
);
}

Dessa maneira a aplicação carrega somente o que é necessário, evitando que o usuário espere por algo que não será utilizado.

Documentação adequada:

Mantenha uma documentação clara e abrangente para o seu projeto. Isso facilita a compreensão do código por outros desenvolvedores e ajuda na integração de novos membros na equipe. Considere o uso de ferramentas como o Storybook para documentar componentes.

Excesso de condições:

Evite o uso excessivo de condições aninhadas. Isso pode tornar o código difícil de ler e manter. Considere a utilização de operadores lógicos e funções para simplificar a lógica condicional.

// evite

const MyComponent = (props) => {
return (
<Fragment>
<div>
{props.isLoading ? (
<div>Loading...</div>
) : (
<div>
{props.error ? (
<div>Error: {props.error}</div>
) : (
<div>{props.data}</div>
)}
</div>
)}
</div>
</Fragment>
);
};

// prefira

const MyComponent = (props) => {
if (props.isLoading) {
return <div>Loading...</div>;
}

if (props.error) {
return <div>Error: {props.error}</div>;
}

return <div>{props.data}</div>;
};

Dessa maneira o código fica mais legível e melhora a manutenção. Um erro muito comum também é utilizar muitas condições sem o devido reaproveitamento da lógica, ex.:

// evite

const MyComponent = (props) => {
// ...fetch A
return (
<div>
<ComponentA
error={props.error}
isLoading={props.isLoading && fetchA.loading && !props.error}
/>
<ComponentB
error={props.error}
isLoading={props.isLoading && fetchB.loading && !props.error}
/>
</div>
)
}

// prefira

const MyComponent = (props) => {
// ...fetch A

const isLoading = props.isLoading && fetchA.loading && !props.error;

return (
<div>
<ComponentA
error={props.error}
isLoading={isLoading}
/>
<ComponentB
error={props.error}
isLoading={isLoading}
</div>
)
}

O ideal é que os componentes A e B não tenham lógica de tratamento de erro e loading, isso deve ser feito no componente pai. Mas aqui eu gostaria de trazer apenas um exemplo que é bastante comum e que pode ser evitado.

Conclusão

Espero que essas dicas possam ajudar a melhorar a qualidade do seu código e do seu projeto. Lembre-se que a qualidade do código é um esforço contínuo e que deve ser compartilhado por toda a equipe. A qualidade do código é um reflexo da qualidade do time.