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.
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:
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.