Escrever código limpo é uma habilidade que se torna obrigatória em um determinado estágio da carreira de um programador. Essa habilidade é especialmente importante quando o programador está tentando encontrar seu primeiro emprego. Isso é, em essência, o que faz do desenvolvedor um jogador da equipe e algo que pode “preencher” a entrevista ou ajudá-lo a passar com sucesso. Os empregadores, ao tomar decisões de pessoal, observam o código escrito por seus funcionários em potencial. O código que o programador escreve deve ser entendido não apenas pelas máquinas, mas também pelas pessoas.

O material, a primeira parte da tradução que publicamos hoje, apresenta dicas para escrever código limpo para aplicativos React. A relevância dessas dicas é maior, quanto maior o tamanho do projeto no qual os princípios estabelecidos nelas são aplicados. Em pequenos projetos, você provavelmente pode fazer isso sem aplicar esses princípios. Ao decidir o que é necessário em cada situação específica, vale a pena ser guiado pelo senso comum.
1. Desestruturar propriedades
As propriedades de reestruturação (na terminologia do React English, são chamadas de "adereços") são uma boa maneira de tornar o código mais limpo e melhorar seus recursos de suporte. O fato é que isso permite que você expresse ou declare claramente o que uma entidade usa (como o componente React). No entanto, essa abordagem não força os desenvolvedores a ler sobre a implementação do componente para descobrir a composição das propriedades associadas a ele.
As propriedades de reestruturação também permitem que o programador defina seus valores padrão. Isso é bastante comum:
import React from 'react' import Button from 'components/Button' const MyComponent = ({ placeholder = '', style, ...otherProps }) => { return ( <Button type="button" style={{ border: `1px solid ${placeholder ? 'salmon' : '#333'}`, ...style, }} {...otherProps} > Click Me </Button> ) } export default MyComponent
Uma das conseqüências mais agradáveis do uso da desestruturação no JavaScript, que eu pude encontrar, é que ela permite suportar várias opções de parâmetros.
Por exemplo, temos uma função
authenticate
, que tomou como parâmetro o
token
usado para autenticar usuários. Mais tarde, foi necessário fazê-lo aceitar a entidade
jwt_token
. Essa necessidade foi causada por uma alteração na estrutura da resposta do servidor. Graças ao uso da desestruturação, você pode organizar facilmente o suporte para ambos os parâmetros sem precisar lidar com a necessidade de alterar a maior parte do código de função:
A
jwt_token
será avaliada quando o código atingir o
token
. Como resultado, se
jwt_token
for um token válido e a entidade do
token
não estiver
undefined
, o valor de
jwt_token
cairá no
token
. Se no
token
já havia algum valor que não era falso pelas regras JS (ou seja, algum token real), então no
token
simplesmente haverá o que já está lá.
2. Coloque os arquivos de componentes em uma estrutura de pastas bem pensada
Dê uma olhada na seguinte estrutura de diretórios:
- src
- componentes
- Breadcrumb.js
- CollapsedSeparator.js
- Entrada
- index.js
- Input.js
- utils.js
- focusManager.js
- Cartão
- index.js
- Card.js
- CardDivider.js
- Button.js
- Typography.js
A farinha de rosca pode incluir separadores. O componente
CollapsedSeparator
é importado no arquivo
Breadcrumb.js
. Isso nos dá conhecimento de que, na implementação do projeto em questão, eles estão conectados. No entanto, alguém que não possui essas informações pode sugerir que o
Breadcrumb
e o
CollapsedSeparator
são um par de componentes completamente independentes que não são conectados de maneira alguma. Especialmente - se o
CollapsedSeparator
não
CollapsedSeparator
sinais claros de que esse componente está associado ao componente
Breadcrumb
. Entre esses sinais, por exemplo, pode haver um prefixo
Breadcrumb
, usado no nome do componente, que pode transformar o nome em algo como
BreadcrumbCollapsedSeparator.js
.
Como sabemos que
Breadcrumb
e
CollapsedSeparator
estão relacionados, podemos nos perguntar por que eles não são colocados em uma pasta separada, como
Input
e
Card
. Ao mesmo tempo, podemos começar a fazer várias suposições sobre por que os materiais do projeto têm exatamente essa estrutura. Digamos, aqui você pode pensar sobre o que esses componentes foram colocados no nível superior do projeto para ajudá-los a encontrar rapidamente esses componentes, cuidando daqueles que trabalharão com o projeto. Como resultado, o relacionamento entre as partes do projeto parece bastante vago para o novo desenvolvedor. O uso de técnicas de escrita de código limpo deve ter o efeito exatamente oposto. O ponto é que, graças a eles, o novo desenvolvedor tem a oportunidade de ler o código de outra pessoa e entender instantaneamente a essência da situação.
Se usarmos uma estrutura de diretórios bem pensada em nosso exemplo, obteremos algo como o seguinte:
- src
- Pão ralado
- index.js
- Breadcrumb.js
- CollapsedSeparator.js
- Entrada
- index.js
- Input.js
- utils.js
- focusManager.js
- Cartão
- index.js
- Card.js
- CardDivider.js
- Button.js
- Typography.js
Agora, não importa quantos componentes associados ao componente
Breadcrumb
serão criados. Desde que seus arquivos estejam localizados no mesmo diretório do
Breadcrumb.js
, saberemos que eles estão associados ao componente
Breadcrumb
:
- src
- Pão ralado
- index.js
- Breadcrumb.js
- CollapsedSeparator.js
- Expander.js
- BreadcrumbText.js
- Breadcrumbhothotog.js
- Breadcrumbfishes.js
- Breadcrumbleftft.js
- Breadcrumbhead.js
- Breadcrumbaddict.js
- Breadcrumbdragon0814.js
- Breadcrumbcontext.js
- Entrada
- index.js
- Input.js
- utils.js
- focusManager.js
- Cartão
- index.js
- Card.js
- CardDivider.js
- Button.js
- Typography.js
É assim que funciona o trabalho com estruturas semelhantes no código:
import React from 'react' import Breadcrumb, { CollapsedSeparator, Expander, BreadcrumbText, BreadcrumbHotdog, BreadcrumbFishes, BreadcrumbLeftOvers, BreadcrumbHead, BreadcrumbAddict, BreadcrumbDragon0814, } from '../../../../../../../../../../components/Breadcrumb' const withBreadcrumbHotdog = (WrappedComponent) => (props) => ( <WrappedComponent BreadcrumbHotdog={BreadcrumbHotdog} {...props} /> ) const WorldOfBreadcrumbs = ({ BreadcrumbHotdog: BreadcrumbHotdogComponent, }) => { const [hasFishes, setHasFishes] = React.useState(false) return ( <BreadcrumbDragon0814 hasFishes={hasFishes} render={(results) => ( <BreadcrumbFishes> {({ breadcrumbFishes }) => ( <BreadcrumbLeftOvers.Provider> <BreadcrumbHotdogComponent> <Expander> <BreadcrumbText> <BreadcrumbAddict> <pre> <code>{JSON.stringify(results, null, 2)}</code> </pre> </BreadcrumbAddict> </BreadcrumbText> </Expander> {hasFishes ? breadcrumbFishes.map((fish) => ( <> {fish} <CollapsedSeparator /> </> )) : null} </BreadcrumbHotdogComponent> </BreadcrumbLeftOvers.Provider> )} </BreadcrumbFishes> )} /> ) } export default withBreadcrumbHotdog(WorldOfBreadcrumbs)
3. Nomeie componentes usando convenções de nomenclatura padrão
O uso de certos padrões ao nomear componentes facilita a leitura de código para alguém que não é o autor do projeto.
Por exemplo, os nomes dos
componentes de ordem superior (HOCs) geralmente são prefixados. Muitos desenvolvedores estão acostumados a esses nomes de componentes:
import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const withFreeMoney = (WrappedComponent) => { class WithFreeMoney extends React.Component { giveFreeMoney() { return 50000 } render() { return ( <WrappedComponent additionalMoney={[ this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), ]} {...this.props} /> ) } } WithFreeMoney.displayName = `withFreeMoney(${getDisplayName( WrappedComponent, )}$)` hoistNonReactStatics(WithFreeMoney, WrappedComponent) return WithFreeMoney } export default withFreeMoney
Suponha que alguém decida se afastar dessa prática e faça o seguinte:
import React from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' import getDisplayName from 'utils/getDisplayName' const useFreeMoney = (WrappedComponent) => { class WithFreeMoney extends React.Component { giveFreeMoney() { return 50000 } render() { return ( <WrappedComponent additionalMoney={[ this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), this.giveFreeMoney(), ]} {...this.props} /> ) } } WithFreeMoney.displayName = `useFreeMoney(${getDisplayName( WrappedComponent, )}$)` hoistNonReactStatics(WithFreeMoney, WrappedComponent) return WithFreeMoney } export default useFreeMoney
Este é um código JavaScript perfeitamente funcional. Os nomes aqui são feitos, do ponto de vista técnico, à direita. Mas o prefixo de
use
é habitual em outras situações, nomeadamente ao nomear
ganchos React . Como resultado, se alguém escrever um programa que planeja mostrar a outra pessoa, ele deve ter cuidado com os nomes das entidades. Isso é especialmente verdade naqueles casos em que alguém pede para ver seu código e ajudá-lo a resolver um problema. O fato é que alguém que lê o código de outra pessoa, possivelmente, já está acostumado a um determinado esquema de nomeação de entidades.
Desvios dos padrões geralmente aceitos dificultam a compreensão do código de outra pessoa.
4. Evite armadilhas booleanas
O programador deve ter extrema cautela caso alguma saída dependa de alguns valores lógicos primitivos e algumas decisões sejam tomadas com base na análise desses valores. Isso sugere a má qualidade do código. Isso força os desenvolvedores a ler o código para implementar componentes ou outros mecanismos para ter uma idéia exata do resultado exato desses mecanismos.
Suponha que tenhamos criado um componente
Typography
que possa aceitar as seguintes opções:
'h1'
,
'h2'
,
'h3'
,
'h4'
,
'h5
',
'h6'
,
'title'
,
'subheading'
.
O que exatamente afetará a saída de um componente se as opções forem passadas para ele no seguinte formato?
const App = () => ( <Typography color="primary" align="center" subheading title> Welcome to my bio </Typography> )
Aqueles que têm alguma experiência com React (ou melhor, com JavaScript) já podem assumir que a opção de
title
subheading
opção de
subheading
devido à maneira como o sistema funciona. A última opção substituirá a primeira.
Mas o problema aqui é que, sem examinar o código, não podemos dizer exatamente até que ponto a opção de
title
ou a opção de
subheading
será aplicada.
Por exemplo:
.title { font-size: 1.2rem; font-weight: 500; text-transform: uppercase; } .subheading { font-size: 1.1rem; font-weight: 400; text-transform: none !important; }
Mesmo que o
title
ganhe, a regra CSS
text-transform: uppercase
não será aplicada. Isso se deve à maior especificidade da
text-transform: none !important
Regra
text-transform: none !important
que existe na
subheading
. Se você não tomar cuidado nessas situações, a depuração desses erros nos estilos pode se tornar extremamente difícil. Especialmente - nos casos em que o código não exibe alguns avisos ou mensagens de erro no console. Isso pode complicar a assinatura do componente.
Uma solução possível para esse problema é usar uma versão mais limpa do componente
Typography
:
const App = () => <Typography variant="title">Welcome to my bio</Typography>
Aqui está o código do componente
Typography
:
import React from 'react' import cx from 'classnames' import styles from './styles.css' const Typography = ({ children, color = '#333', align = 'left', variant, ...otherProps }) => { return ( <div className={cx({ [styles.h1]: variant === 'h1', [styles.h2]: variant === 'h2', [styles.h3]: variant === 'h3', [styles.h4]: variant === 'h4', [styles.h5]: variant === 'h5', [styles.h6]: variant === 'h6', [styles.title]: variant === 'title', [styles.subheading]: variant === 'subheading', })} > {children} </div> ) }
Agora, quando no componente
App
passamos para o componente
Typography
variant="title"
, podemos ter certeza de que apenas o
title
afetará a saída do componente. Isso evita que tenhamos que analisar o código do componente para entender como será esse componente.
Para trabalhar com propriedades, você pode usar a
if/else
simples:
let result if (variant === 'h1') result = styles.h1 else if (variant === 'h2') result = styles.h2 else if (variant === 'h3') result = styles.h3 else if (variant === 'h4') result = styles.h4 else if (variant === 'h5') result = styles.h5 else if (variant === 'h6') result = styles.h6 else if (variant === 'title') result = styles.title else if (variant === 'subheading') result = styles.subheading
Mas a principal força dessa abordagem é que você pode simplesmente usar o seguinte design de linha única limpa e acabar com ele:
const result = styles[variant]
5. Use as funções de seta
As funções de seta representam um mecanismo claro e conciso para declarar funções em JavaScript (nesse caso, seria mais correto falar sobre a vantagem das funções de seta sobre expressões funcionais).
No entanto, em alguns casos, os desenvolvedores não usam funções de seta em vez de expressões funcionais. Por exemplo, quando é necessário organizar a criação de funções.
O React usa esses conceitos de maneira semelhante. No entanto, se um programador não estiver interessado em aumentar funções, então, na minha opinião, faz sentido usar a sintaxe das funções de seta:
Note-se que, analisando este exemplo, é difícil ver os pontos fortes das funções das setas. Sua beleza se manifesta totalmente quando se trata de designs simples de linha única:
Estou certo de que esses designs de linha única agradarão a todos.
6. Coloque funções independentes fora de seus próprios ganchos
Vi como alguns programadores declaram funções dentro de seus próprios ganchos, mas esses ganchos não precisam particularmente dessas funções. Esse tipo de "infla" levemente o código do gancho e complica sua leitura. As dificuldades na leitura do código surgem devido ao fato de seus leitores começarem a fazer perguntas sobre se o gancho realmente depende da função que está dentro dele. Se não for esse o caso, é melhor mover a função para fora do gancho. Isso dará ao leitor de código uma compreensão clara do que o gancho depende e do que não depende.
Aqui está um exemplo:
import React from 'react' const initialState = { initiated: false, images: [], } const reducer = (state, action) => { switch (action.type) { case 'initiated': return { ...state, initiated: true } case 'set-images': return { ...state, images: action.images } default: return state } } const usePhotosList = ({ imagesList = [] }) => { const [state, dispatch] = React.useReducer(reducer, initialState) const removeFalseyImages = (images = []) => images.reduce((acc, img) => (img ? [...acc, img] : acc), []) React.useEffect(() => { const images = removeFalseyImages(imagesList) dispatch({ type: 'initiated' }) dispatch({ type: 'set-images', images }) }, []) return { ...state, } } export default usePhotosList
Se analisarmos esse código, podemos entender que as funções
removeFalseyImages
, de fato, não precisam estar presentes dentro do gancho; elas não interagem com seu estado, o que significa que ele pode ser colocado fora dele e pode ser chamado do gancho sem problemas.
7. Seja consistente ao escrever código
Uma abordagem consistente para escrever código é algo frequentemente recomendado para quem programa em JavaScript.
No caso do React, você deve prestar atenção a uma abordagem consistente ao uso das seguintes estruturas:
- Equipes de importação e exportação.
- Nomeação de componentes, ganchos, componentes de ordem superior, classes.
Ao importar e exportar componentes, às vezes uso algo semelhante ao seguinte:
import App from './App' export { default as Breadcrumb } from './Breadcrumb' export default App
Mas também gosto da sintaxe:
export { default } from './App' export { default as Breadcrumb } from './Breadcrumb'
Qualquer que seja a escolha do programador, ele deve usá-lo consistentemente em todos os projetos que cria. Isso simplifica o trabalho desse programador e a leitura de seu código por outras pessoas.
É muito importante aderir às convenções de nomenclatura das entidades.
Por exemplo, se alguém deu ao gancho o nome
useApp
, é importante que os nomes de outros ganchos sejam construídos de maneira semelhante - usando o prefixo de uso. Por exemplo, o nome de outro gancho com essa abordagem pode se parecer com
useController
.
Se você não aderir a esta regra, o código de um projeto, no final, pode ser algo como isto:
Aqui está a aparência da importação desses ganchos:
import React from 'react' import useApp from './useApp' import basicController from './basicController' const App = () => { const app = useApp() const controller = basicController() return ( <div> {controller.errors.map((errorMsg) => ( <div>{errorMsg}</div> ))} </div> ) } export default App
À primeira vista, é completamente óbvio que o
basicController
é um gancho, o mesmo que
useApp
. Isso força o desenvolvedor a ler o código de implementação do que ele importa. Isso é feito apenas para entender com o que exatamente o desenvolvedor está lidando. Se aderirmos consistentemente à mesma estratégia para nomear entidades, essa situação não ocorrerá. Tudo ficará claro de relance:
const app = useApp() const controller = useBasicController()
Para continuar ...
Caros leitores! Como você aborda a nomeação de entidades em seus projetos React?
