14 dicas para escrever um código React limpo. Parte 1

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:

 //   async function authenticate({ user_id, token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } //   async function authenticate({ user_id, jwt_token, token = jwt_token }) {  try {    const response = await axios.post('https://someapi.com/v1/auth/', {      user_id,      token,    })    console.log(response)    return response.data  } catch (error) {    console.error(error)    throw error  } } 

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

    • componentes
  • 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

    • componentes
  • 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:

 //     function Gallery({ title, images = [], ...otherProps }) {  return (    <CarouselContext.Provider>      <Carousel>        {images.map((src, index) => (          <img align="center" src={src} key={`img_${index}`} />        ))}      </Carousel>    </CarouselContext.Provider>  ) } //         const Gallery = ({ title, images = [], ...otherProps }) => (  <CarouselContext.Provider>    <Carousel>      {images.map((src, index) => (        <img align="center" src={src} key={`img_${index}`} />      ))}    </Carousel>  </CarouselContext.Provider> ) 

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:

 //     function GalleryPage(props) {  return <Gallery {...props} /> } //         const GalleryPage = (props) => <Gallery {...props} /> 

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:

  1. Equipes de importação e exportação.
  2. 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:

 //  #1 const useApp = ({ data: dataProp = null }) => {  const [data, setData] = React.useState(dataProp)  React.useEffect(() => {    setData(data)  }, [])  return {    data,  } } //  #2 const basicController = ({ device: deviceProp }) => {  const [device, setDevice] = React.useState(deviceProp)  React.useEffect(() => {    if (!device && deviceProp) {      setDevice(deviceProp === 'mobile' ? 'mobile' : 'desktop')    }  }, [deviceProp])  return {    device,  } } 

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?

Source: https://habr.com/ru/post/pt465811/


All Articles