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

Hoy publicamos la segunda parte del material sobre la escritura de código limpio al desarrollar aplicaciones React. Aquí hay algunos consejos más útiles.



Leer la primera parte

8. Convertir elementos duplicados en componentes


La conversión de elementos duplicados en componentes adecuados para su reutilización puede denominarse "componente" de dichos elementos.

Cada desarrollador tiene sus propias razones por las cuales escribe código de React duplicado. Esto puede ser una acción deliberada, o puede ser un accidente.

Cualquiera sea el motivo de la aparición de los mismos fragmentos de código en la aplicación, el programador debe pensar en cómo mejorar la situación.

Por ejemplo, si alguien no se ha acostumbrado a deshacerse de los duplicados, lo más probable es que surjan en sus proyectos una y otra vez. ¿Qué tipo de jugador de equipo es el que hace esto? Simplemente complica la vida futura de sus colegas, que se confundirán cuando encuentren un código duplicado. Recibirán un "regalo" especial si tienen que editar fragmentos de código similares.

Eche un vistazo al siguiente ejemplo y piense en cómo mejorarlo:

const SomeComponent = () => (   <Body noBottom>     <Header center>Title</Header>     <Divider />     <Background grey>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.groupsHeader}>               <Header center>Groups</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.groups} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </Background>     <div>       <Section height={500}>         <Grid spacing={16} container>           <Grid xs={12} sm={6} item>             <div className={classes.labsHeader}>               <Header center>Labs</Header>             </div>           </Grid>           <Grid xs={12} sm={6} item>             <div>               <img src={photos.labs} alt="" className={classes.img} />             </div>           </Grid>         </Grid>       </Section>     </div>   </Body> ) 

Si ahora necesita cambiar los parámetros de la cuadrícula de xs={12} sm={6} a xs={12} sm={4} , entonces esta tarea no será particularmente agradable. El hecho es que para esto tienes que editar el código en cuatro lugares.

La belleza del enfoque de componentes es que le permite resolver problemas similares a los anteriores, cambiando el código en un solo lugar. En nuestro caso, este cambio se reflejará en todas partes donde se utilizan las redes:

 const SomeComponent = ({ classes, xs = 12, sm = 6, md, lg }) => {  const BodySection = ({ header, src }) => {    const gridSizes = { xs, sm, md, lg }    return (      <Section height={500}>        <Grid spacing={16} container>          <Grid {...gridSizes} item>            <div className={classes.groupsHeader}>              <Header center>{header}</Header>            </div>          </Grid>          <Grid {...gridSizes} item>            <div>              <img src={src} alt="" className={classes.img} />            </div>          </Grid>        </Grid>      </Section>    )  }  return (    <Body noBottom>      <Header center>Title</Header>      <Divider />      <Background grey>        <BodySection header="Groups" src={photos.groups} />      </Background>      <div>        <BodySection header="Labs" src={photos.labs} />      </div>    </Body>  ) } 

Incluso el nivel mínimo de conversión de código demostrado aquí hace que este código sea mucho más conveniente en términos de lectura y soporte. Él, al mismo tiempo, es una forma completamente adecuada de resolver la tarea que se le asignó.

9. Esfuércese por mantener sus componentes lo más simple posible.


Al trabajar en aplicaciones de ventas, a veces me encontré no con la necesidad de luchar por la simplicidad de los componentes, sino con la necesidad de evitar situaciones en las que los componentes se vuelven demasiado complejos.

Aquí hay un ejemplo de un componente que es innecesariamente complicado. Está representado por ConfirmAvailability.js :

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ export default class TimeZonePicker extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  render() {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return (      <React.Fragment>        <Grid container justify="space-between">          <div>            <Typography>Current time</Typography>            <Typography variant="h6" gutterBottom>              {Time.formatTime(this.state.time)}            </Typography>          </div>          <div>            <Typography>Set timezone</Typography>            <Select value={this.state.offset} onChange={this.handleChange}>              {timezones}            </Select>          </div>        </Grid>      </React.Fragment>    )  } } 

Este componente fue concebido como un mecanismo simple, pero dado que contiene una lógica altamente relacionada, es responsable de resolver varios problemas. En el momento en que se escribió este código, los ganchos React aún no se habían lanzado, pero React incluía tecnologías como componentes de orden superior y accesorios de renderizado. Esto significa que simplemente podemos usar uno de estos patrones para simplificar el componente. Esto nos permitirá demostrar un enfoque para simplificar componentes sin cambiar la funcionalidad existente.

Anteriormente, todo el código se almacenaba en un solo archivo. Ahora lo dividimos en dos archivos. Aquí está el contenido del primer archivo: SelectTimeZone.js :

 import React from 'react' /** *     .    ,     , ,  ,  * ,    .        .     -     . * * :     Date().getTimezoneOffset().     : *   1.      . *   2.     UTC -     ,   UTC   .  ,       *     UTC. */ class SelectTimeZone extends React.Component {  state = {    time: new Date(),    offset: -(new Date().getTimezoneOffset() / 60),  }  componentDidMount() {    this.props.setOffset(this.state.offset)  }  handleChange = (event) => {    const d = new Date()    d.setTime(      d.getTime() +        d.getTimezoneOffset() * 60 * 1000 +        event.target.value * 3600 * 1000,    )    this.setState({      time: d,      offset: event.target.value,    })    this.props.setOffset(event.target.value)  }  getTimeZones = () => {    const timezones = []    for (let i = -12; i <= 14; i++) {      timezones.push(        <MenuItem key={i} value={i}>          {i > 0 ? '+' : null}          {i}        </MenuItem>,      )    }    return timezones  }  render() {    return this.props.render({      ...this.state,      getTimeZones: this.getTimeZones,    })  } } 

Así es como se ve el segundo archivo: TimeZonePicker.js :

 import React from 'react' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' import MenuItem from '@material-ui/core/MenuItem' import Select from '@material-ui/core/Select' import Time from 'util/time' const TimeZonePicker = () => (  <SelectTimeZone    render={({ time, offset, getTimeZones, handleChange }) => (      <Grid container justify="space-between">        <div>          <Typography>Current time</Typography>          <Typography variant="h6" gutterBottom>            {Time.formatTime(time)}          </Typography>        </div>        <div>          <Typography>Set timezone</Typography>          <Select value={offset} onChange={handleChange}>            {getTimeZones()}          </Select>        </div>      </Grid>    )}  /> ) export default TimeZonePicker 

Después del procesamiento, el código del proyecto resultó ser mucho más limpio que antes. Extrajimos la lógica de la parte de presentación del componente. Ahora, además, las pruebas unitarias del proyecto se simplificarán enormemente.

10. Use useReducer cuando complique useState


useState más fragmentos de estado tenga que procesar en un proyecto, más complicado useState el uso de useState .

Esto, por ejemplo, podría verse así:

 import React from 'react' import axios from 'axios' const useFrogs = () => {  const [fetching, setFetching] = React.useState(false)  const [fetched, setFetched] = React.useState(false)  const [fetchError, setFetchError] = React.useState(null)  const [timedOut, setTimedOut] = React.useState(false)  const [frogs, setFrogs] = React.useState(null)  const [params, setParams] = React.useState({ limit: 50 })  const timedOutRef = React.useRef()  function updateParams(newParams) {    if (newParams != undefined) {      setParams(newParams)    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    const nextFrogs = {      ...frogs,      [name]: frog,    }    setFrogs(nextFrogs)  }  function removeFrog(name) {    const nextFrogs = { ...frogs }    if (name in nextFrogs) delete nextFrogs[name]    setFrogs(nextFrogs)  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      setFetching(true)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          setFetching(false)          setFetched(true)          if (timedOut) setTimedOut(false)          if (fetchError) setFetchError(null)          setFrogs(formatFrogs(response.data))        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          setFetching(false)          if (timedOut) setTimedOut(false)          setFetchError(error)        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

Será mucho más conveniente trabajar con todo esto si traduce este código para usar useReducer :

 import React from 'react' import axios from 'axios' const initialFetchState = {  fetching: false  fetched: false  fetchError: null  timedOut: false } const initialState = {  ...initialFetchState,  frogs: null  params: { limit: 50 } } const reducer = (state, action) => {  switch (action.type) {    case 'fetching':      return { ...state, ...initialFetchState, fetching: true }    case 'fetched':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'fetch-error':      return { ...state, ...initialFetchState, fetchError: action.error }    case 'set-timed-out':      return { ...state, ...initialFetchState, timedOut: true }    case 'set-frogs':      return { ...state, ...initialFetchState, fetched: true, frogs: action.frogs }    case 'add-frog':      return { ...state, frogs: { ...state.frogs, [action.name]: action.frog }}    case 'remove-frog': {      const nextFrogs = { ...state.frogs }      if (action.name in nextFrogs) delete nextFrogs[action.name]      return { ...state, frogs: nextFrogs }    }    case 'set-params':      return { ...state, params: { ...state.params, ...action.params } }      default:        return state  } } const useFrogs = () => {  const [state, dispatch] = React.useReducer(reducer, initialState)  const timedOutRef = React.useRef()  function updateParams(params) {    if (newParams != undefined) {      dispatch({ type: 'set-params', params })    } else {      console.warn(        'You tried to update state.params but the parameters were null or undefined',      )    }  }  function formatFrogs(newFrogs) {    const formattedFrogs = newFrogs.reduce((acc, frog) => {      const { name, age, size, children } = frog      if (!(name in acc)) {        acc[name] = {          age,          size,          children: children.map((child) => ({            name: child.name,            age: child.age,            size: child.size,          })),        }      }      return acc    }, {})    return formattedFrogs  }  function addFrog(name, frog) {    dispatch({ type: 'add-frog', name, frog })  }  function removeFrog(name) {    dispatch({ type: 'remove-frog', name })  }  React.useEffect(() => {    if (frogs === null) {      if (timedOutRef.current) clearTimeout(timedOutRef.current)      timedOutRef.current = setTimeout(() => {        setTimedOut(true)      }, 20000)      axios        .get('https://somefrogsaspi.com/api/v1/frogs_list/', { params })        .then((response) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          const frogs = formatFrogs(response.data)          dispatch({ type: 'set-frogs', frogs })        })        .catch((error) => {          if (timedOutRef.current) clearTimeout(timedOutRef.current)          console.error(error)          dispatch({ type: 'fetch-error', error })        })    }  }, [])  return {    fetching,    fetched,    fetchError,    timedOut,    frogs,    params,    addFrog,    removeFrog,  } } export default useFrogs 

Aunque este enfoque probablemente no sea más limpio que usar useState , como puede ver mirando el código, el nuevo código es más fácil de mantener. Esto se debe al hecho de que al usar useReducer programador no tiene que preocuparse por las actualizaciones de estado en diferentes partes del gancho, ya que todas estas operaciones se definen en un solo lugar dentro del reducer .

En la versión del código que usa useState , además de escribir lógica, necesitamos declarar funciones dentro del gancho para saber cuál debería ser la siguiente parte del estado. Y cuando use useReducer , no tiene que hacer esto. En cambio, todo cae en la función reducer . Solo necesitamos activar una acción del tipo apropiado, y eso, de hecho, es todo de lo que debemos preocuparnos.

11. Use declaraciones de funciones en situaciones controvertidas


Un buen ejemplo del uso de esta recomendación es crear un useEffect limpieza useEffect :

 React.useEffect(() => {  setMounted(true)  return () => {    setMounted(false)  } }, []) 

Un desarrollador experimentado de React sabe cuál es el papel de la función devuelta, comprenderá fácilmente este código. Pero si imagina que alguien que no está muy familiarizado con useEffect leerá este código, sería mejor expresar sus intenciones en el código de la manera más clara posible. Se trata de usar declaraciones de funciones a las que se les puede dar nombres significativos. Por ejemplo, este código se puede reescribir así:

 React.useEffect(() => {  setMounted(true)  return function cleanup() {    setMounted(false)  } }, []) 

Este enfoque nos permite describir claramente qué papel juega la función devuelta.

12. Use más bonito


Prettier ayuda a los desarrolladores y equipos individuales a mantener un enfoque coherente y coherente para el formato de código. Esta herramienta ayuda a ahorrar tiempo y esfuerzo. Simplifica la revisión de código al reducir la cantidad de razones para discutir el estilo de los programas. Prettier también alienta a los programadores a usar técnicas de escritura de código limpio. Las reglas aplicadas por esta herramienta son editables. Como resultado, resulta que todos pueden personalizarlo como mejor les parezca.

13. Esforzarse por usar la taquigrafía para las declaraciones de fragmentos


La esencia de esta recomendación se puede expresar en los siguientes dos ejemplos.

Aquí hay una versión abreviada de la declaración de fragmento:

 const App = () => (  <>    <FrogsTable />    <FrogsGallery />  </> ) 

Aquí está la versión completa:

 const App = () => (  <React.Fragment>    <FrogsTable />    <FrogsGallery />  </React.Fragment> ) 

14. Siga un cierto orden de colocación de elementos al escribir código.


Cuando escribo código, prefiero organizar algunos comandos en un cierto orden. Por ejemplo, hago esto al importar archivos (la excepción aquí es solo importar react ):

 import React from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' import FrogsGallery from './FrogsGallery' import FrogsTable from './FrogsTable' import Stations from './Stations' import * as errorHelpers from '../utils/errorHelpers' import * as utils from '../utils/' 

Al mirar este código, alguien podría pensar que aquí no se observa un pedido especial. Después de todo, las entidades importadas ni siquiera están ordenadas alfabéticamente. Pero organizar algo en orden alfabético es solo una parte del esquema de orden de comandos que uso.

Yo, luchando por la limpieza del código de mis proyectos, uso las siguientes reglas que se aplican en el orden en que se siguen:

  1. Importar reaccionar.
  2. Importar bibliotecas (en orden alfabético).
  3. Comandos absolutos para importar entidades de un proyecto (en orden alfabético).
  4. Comandos de importación relativos (en orden alfabético).
  5. Comandos del formulario import * as .
  6. Comandos del formulario import './<some file>.<some ext>' .

Y así es como prefiero organizar, por ejemplo, variables. Decir - propiedades de los objetos:

 const character = (function() {  return {    cry() {      //    },    eat() {      //    },    hop() {      //    },    jump() {      //    },    punch() {      //    },    run() {      //    },    scratch() {      //    },    scream() {      //    },    sleep() {      //    },    walk() {      //    },    yawn() {      //    },  } })() 

Si sigue ciertas reglas para ordenar entidades al escribir código, esto tendrá un efecto beneficioso sobre su pureza.

Resumen


Le proporcionamos consejos para escribir código de aplicación React limpio. Esperamos que encuentre algo entre ellos que le sea útil.

Estimados lectores! ¿Qué recomendaciones agregarías a los consejos presentados en este artículo?

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


All Articles