14 consejos para escribir código de React limpio. Parte 1

Escribir código limpio es una habilidad que se vuelve obligatoria en una determinada etapa de la carrera de un programador. Esta habilidad es especialmente importante cuando el programador intenta encontrar su primer trabajo. Esto, en esencia, es lo que hace que el desarrollador sea un jugador de equipo, y algo que puede "llenar" la entrevista o ayudarlo a pasar con éxito. Los empleadores, al tomar decisiones de personal, miran el código escrito por sus empleados potenciales. El código que escribe el programador debe ser entendido no solo por las máquinas, sino también por las personas.



El material, la primera parte de la traducción que publicamos hoy, presenta consejos para escribir código limpio para aplicaciones React. La relevancia de estos consejos es cuanto mayor, mayor es el tamaño del proyecto en el que se aplican los principios establecidos en ellos. En proyectos pequeños, probablemente pueda hacerlo sin aplicar estos principios. Al decidir qué se necesita en cada situación particular, vale la pena guiarse por el sentido común.

1. Propiedades de desestructuración


Las propiedades de desestructuración (en la terminología React English se llaman "accesorios") es una buena manera de hacer que el código sea más limpio y mejorar sus capacidades de soporte. El hecho es que esto le permite expresar o declarar claramente lo que usa una entidad (como el componente Reaccionar). Sin embargo, este enfoque no obliga a los desarrolladores a leer la implementación del componente para descubrir la composición de las propiedades asociadas con él.

Las propiedades de desestructuración también permiten al programador establecer sus valores predeterminados. Esto es bastante común:

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 

Una de las consecuencias más agradables del uso de la desestructuración en JavaScript, que pude encontrar, es que le permite admitir varias opciones de parámetros.

Por ejemplo, tenemos una función de authenticate , que tomó como parámetro el token utilizado para autenticar a los usuarios. Más tarde fue necesario hacer que aceptara la entidad jwt_token . Esta necesidad fue causada por un cambio en la estructura de la respuesta del servidor. Gracias al uso de la desestructuración, puede organizar fácilmente el soporte para ambos parámetros sin tener que lidiar con la necesidad de cambiar la mayor parte del código de función:

 //   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  } } 

La jwt_token se evaluará cuando el código llegue al token . Como resultado, si jwt_token resulta ser un token válido y la entidad del token resulta ser undefined , el valor de jwt_token caerá en el token . Si en el token ya había algún valor que no era falso según las reglas de JS (es decir, algún token real), entonces en el token simplemente habrá lo que ya está allí.

2. Coloque los archivos componentes en una estructura de carpetas bien pensada


Eche un vistazo a la siguiente estructura de directorios:

  • src

    • componentes
    • Breadcrumb.js
    • CollapsedSeparator.js
  • De entrada

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Tarjeta

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

Las migas de pan pueden incluir separadores. El componente CollapsedSeparator se importa en el archivo Breadcrumb.js . Esto nos da conocimiento de que en la implementación del proyecto en cuestión están conectados. Sin embargo, alguien que no posee esta información puede sugerir que Breadcrumb y CollapsedSeparator son un par de componentes completamente independientes que no están conectados entre sí de ninguna manera. Especialmente: si el CollapsedSeparator no CollapsedSeparator signos claros de que este componente esté asociado con el componente Breadcrumb . Entre estos signos, por ejemplo, puede haber un prefijo Breadcrumb , usado en el nombre del componente, que puede convertir el nombre en algo así como BreadcrumbCollapsedSeparator.js .

Como sabemos que Breadcrumb y CollapsedSeparator están relacionados entre sí, podemos preguntarnos por qué no se colocan en una carpeta separada, como Input y Card . Al hacerlo, podemos comenzar a hacer varias suposiciones sobre por qué los materiales del proyecto tienen tal estructura. Digamos, aquí puede pensar en qué componentes se colocaron en el nivel superior del proyecto para ayudarlos a encontrarlos rápidamente, cuidando a quienes trabajarán con el proyecto. Como resultado, la relación entre las partes del proyecto parece bastante vaga para el nuevo desarrollador. El uso de técnicas de escritura de código limpio debería tener exactamente el efecto contrario. El punto es que gracias a ellos, el nuevo desarrollador tiene la oportunidad de leer el código de otra persona y comprender instantáneamente la esencia de la situación.

Si usamos una estructura de directorios bien pensada en nuestro ejemplo, obtenemos algo como lo siguiente:

  • src

    • componentes
  • Pan rallado

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
  • De entrada

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Tarjeta

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

Ahora no importa cuántos componentes asociados con el componente Breadcrumb se crearán. Mientras sus archivos estén ubicados en el mismo directorio que Breadcrumb.js , sabremos que están asociados con el componente Breadcrumb :

  • src

    • componentes
  • Pan rallado

    • index.js
    • Breadcrumb.js
    • CollapsedSeparator.js
    • Expander.js
    • BreadcrumbText.js
    • Breadcrumbhothotog.js
    • Breadcrumbfishes.js
    • Breadcrumbleftft.js
    • Breadcrumbhead.js
    • Breadcrumbaddict.js
    • Breadcrumbdragon0814.js
    • Breadcrumbcontext.js
  • De entrada

    • index.js
    • Input.js
    • utils.js
    • focusManager.js
  • Tarjeta

    • index.js
    • Card.js
    • CardDivider.js
  • Button.js
  • Typography.js

Así es como se ve trabajar en estructuras similares en 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. Nombrar componentes usando convenciones de nomenclatura estándar


El uso de ciertos estándares al nombrar componentes facilita que alguien que no sea el autor del proyecto lea el código de este proyecto.

Por ejemplo, los nombres de componentes de orden superior (HOC) generalmente tienen el prefijo. Muchos desarrolladores están acostumbrados a estos nombres 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 

Supongamos que alguien decide retirarse de esta práctica y hacer esto:

 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 es un código JavaScript perfectamente funcional. Los nombres aquí están compuestos, desde un punto de vista técnico, correcto. Pero el prefijo de use se use habitualmente en otras situaciones, especialmente al nombrar ganchos React . Como resultado, si alguien escribe un programa que planean mostrar a otra persona, debe tener cuidado con los nombres de las entidades. Esto es especialmente cierto para aquellos casos en que alguien pide ver su código y ayudarlo a resolver un problema. El hecho es que alguien que lee el código de otra persona, posiblemente, ya está acostumbrado a un determinado esquema de denominación de entidades.

Las desviaciones de los estándares generalmente aceptados dificultan la comprensión del código de otra persona.

4. Evita las trampas booleanas


El programador debe actuar con extrema precaución en el caso de que alguna salida dependa de algunos valores lógicos primitivos, y algunas decisiones se tomen en base al análisis de estos valores. Esto sugiere la mala calidad del código. Esto obliga a los desarrolladores a leer el código para implementar componentes u otros mecanismos para tener una idea precisa de cuál es exactamente el resultado de estos mecanismos.

Supongamos que creamos un componente de Typography que puede aceptar las siguientes opciones: 'h1' , 'h2' , 'h3' , 'h4' , 'h5 ', 'h6' , 'title' , 'subheading' .

¿Qué afectará exactamente la salida de un componente si las opciones se le pasan de la siguiente forma?

 const App = () => (  <Typography color="primary" align="center" subheading title>    Welcome to my bio  </Typography> ) 

Aquellos que tienen experiencia con React (o, mejor dicho, con JavaScript) ya pueden asumir que la opción de title subheading opción de subheading debido a la forma en que funciona el sistema. La última opción sobrescribirá la primera.

Pero el problema aquí es que no podemos, sin mirar el código, decir exactamente en qué medida se aplicará la opción de title o la opción de subheading .

Por ejemplo:

 .title {  font-size: 1.2rem;  font-weight: 500;  text-transform: uppercase; } .subheading {  font-size: 1.1rem;  font-weight: 400;  text-transform: none !important; } 

Aunque el title gane, la regla de text-transform: uppercase CSS text-transform: uppercase no se aplicará. Esto se debe a la mayor especificidad de la text-transform: none !important Regla text-transform: none !important que existe en el subheading . Si no tiene precaución en tales situaciones, depurar tales errores en los estilos puede ser extremadamente difícil. Especialmente, en casos donde el código no muestra algunas advertencias o mensajes de error en la consola. Esto puede complicar la firma del componente.

Una posible solución a este problema es usar una versión más limpia del componente Typography :

 const App = () => <Typography variant="title">Welcome to my bio</Typography> 

Aquí está el código del componente de 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>  ) } 

Ahora, cuando en el componente App pasamos al componente Typography variant="title" , podemos estar seguros de que solo el title afectará la salida del componente. Esto nos ahorra tener que analizar el código del componente para entender cómo se verá este componente.

Para trabajar con propiedades, puede usar la if/else simple if/else :

 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 

Pero la principal fortaleza de este enfoque es que simplemente puede usar el siguiente diseño limpio de una sola línea y ponerle fin:

 const result = styles[variant] 

5. Use las funciones de flecha


Las funciones de flecha representan un mecanismo conciso y claro para declarar funciones en JavaScript (en este caso, sería más correcto hablar sobre la ventaja de las funciones de flecha sobre las expresiones funcionales).

Sin embargo, en algunos casos, los desarrolladores no usan funciones de flecha en lugar de expresiones funcionales. Por ejemplo, cuando es necesario organizar la elevación de funciones.

React utiliza estos conceptos de manera similar. Sin embargo, si un programador no está interesado en aumentar funciones, entonces, en mi opinión, tiene sentido usar la sintaxis de las funciones de flecha:

 //     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> ) 

Cabe señalar que, analizando este ejemplo, es difícil ver las fortalezas de las funciones de flecha. Su belleza se manifiesta completamente cuando se trata de diseños simples de una sola línea:

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

Estoy seguro de que tales diseños de una sola línea atraerán a todos.

6. Coloque funciones independientes fuera de sus propios ganchos


He visto cómo algunos programadores declaran funciones dentro de sus propios enlaces, pero estos enlaces no necesitan tales funciones en particular. Este tipo de "infla" ligeramente el código de enlace y complica su lectura. Las dificultades para leer el código surgen debido al hecho de que sus lectores pueden comenzar a hacer preguntas sobre si el gancho realmente depende de la función que está dentro de él. Si este no es el caso, es mejor mover la función fuera del gancho. Esto le dará al lector de código una comprensión clara de lo que depende el gancho y de lo que no.

Aquí hay un ejemplo:

 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 

Si analizamos este código, podemos entender que las funciones removeFalseyImages , de hecho, no tienen que estar presentes dentro del gancho; no interactúa con su estado, lo que significa que se puede colocar fuera del gancho y se puede llamar desde el gancho sin ningún problema.

7. Sea consistente al escribir código


Un enfoque consistente para escribir código es algo que a menudo se recomienda para aquellos que programan en JavaScript.

En el caso de React, vale la pena prestar atención a un enfoque coherente para el uso de los siguientes diseños:

  1. Equipos de importación y exportación.
  2. Nombramiento de componentes, ganchos, componentes de orden superior, clases.

Al importar y exportar componentes, a veces uso algo similar a lo siguiente:

 import App from './App' export { default as Breadcrumb } from './Breadcrumb' export default App 

Pero también me gusta la sintaxis:

 export { default } from './App' export { default as Breadcrumb } from './Breadcrumb' 

Independientemente de lo que elija el programador, debe usarlo de manera consistente en cada proyecto que cree. Esto simplifica el trabajo de este programador y la lectura de su código por parte de otras personas.

Es muy importante cumplir con las convenciones de nomenclatura de entidades.

Por ejemplo, si alguien le dio al gancho el nombre useApp , es importante que los nombres de otros ganchos se construyan de manera similar, usando el prefijo de uso. Por ejemplo, el nombre de otro gancho con este enfoque puede verse como useController .

Si no cumple con esta regla, el código de un proyecto, al final, puede ser algo como esto:

 //  #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,  } } 

Así es como se ve la importación de estos 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 

A primera vista, es completamente obvio que el basicController es un gancho, lo mismo que useApp . Esto obliga al desarrollador a leer el código de implementación de lo que importa. Esto se hace solo para comprender con qué se enfrenta exactamente el desarrollador. Si nos adherimos constantemente a la misma estrategia para nombrar entidades, entonces tal situación no surgirá. Todo quedará claro de un vistazo:

 const app = useApp() const controller = useBasicController() 

Continuará ...

Estimados lectores! ¿Cómo aborda el nombramiento de entidades en sus proyectos React?

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


All Articles