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

Hoje estamos publicando a segunda parte do material sobre como escrever código limpo ao desenvolver aplicativos React. Aqui estão algumas dicas mais úteis.



Leia a primeira parte

8. Converta elementos duplicados em componentes


A conversão de elementos duplicados em componentes adequados para reutilização pode ser chamada de "componentização" desses elementos.

Cada desenvolvedor tem seus próprios motivos pelos quais ele escreve um código React duplicado. Isso pode ser uma ação deliberada ou um acidente.

Qualquer que seja o motivo da aparência dos mesmos fragmentos de código no aplicativo, o programador deve pensar em como melhorar a situação.

Por exemplo, se alguém não criou o hábito de se livrar de duplicatas, é provável que elas surjam nos projetos dele repetidamente. Que tipo de jogador de equipe é quem faz isso? Simplesmente complica a vida futura de seus colegas, que ficarão confusos quando encontrarem código duplicado. Eles receberão um "presente" especial se tiverem que editar fragmentos de código semelhantes.

Dê uma olhada no exemplo a seguir e pense em como melhorá-lo:

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

Se você agora precisar alterar os parâmetros da grade de xs={12} sm={6} para xs={12} sm={4} , essa tarefa não será particularmente agradável. O fato é que, para isso, é necessário editar o código em quatro locais.

A vantagem da abordagem de componentes é que ela permite solucionar problemas semelhantes ao acima, alterando o código em apenas um lugar. No nosso caso, essa alteração será refletida em todos os lugares em que as grades são usadas:

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

Mesmo o nível mínimo de conversão de código demonstrado aqui torna esse código muito mais conveniente em termos de leitura e suporte. Ele, ao mesmo tempo, é uma maneira completamente adequada para resolver a tarefa que lhe foi confiada.

9. Tente manter seus componentes o mais simples possível.


Ao trabalhar em aplicativos de vendas, às vezes me deparei com a necessidade de se esforçar pela simplicidade dos componentes, mas a necessidade de evitar situações nas quais os componentes se tornam muito complexos.

Aqui está um exemplo de um componente desnecessariamente complicado. É 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>    )  } } 

Esse componente foi concebido como um mecanismo simples, mas como contém uma lógica altamente relacionada, é responsável por resolver vários problemas. No momento em que esse código foi escrito, os ganchos do React ainda não haviam sido liberados, mas o React incluía tecnologias como componentes de ordem superior e acessórios de renderização. Isso significa que podemos simplesmente usar um desses padrões para simplificar o componente. Isso nos permitirá demonstrar uma abordagem para simplificar componentes sem alterar a funcionalidade existente.

Anteriormente, todo o código era armazenado em um único arquivo. Agora dividimos em dois arquivos. Aqui está o conteúdo do primeiro arquivo - 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,    })  } } 

Aqui está a aparência do segundo arquivo - 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 

Após o processamento, o código do projeto ficou muito mais limpo do que antes. Extraímos a lógica da parte da apresentação do componente. Agora, além disso, o teste de unidade do projeto será bastante simplificado.

10. Use useReducer ao complicar useState


Quanto mais fragmentos de estado você precisar processar em um projeto, mais complicado será o uso de useState .

Isso, por exemplo, pode ficar assim:

 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 

useReducer muito mais conveniente trabalhar com tudo isso se você traduzir esse 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 

Embora essa abordagem provavelmente não seja mais limpa do que usar useState , como você pode ver, observando o código, o novo código é mais fácil de manter. Isso se deve ao fato de que, ao usar useReducer programador não precisa se preocupar com atualizações de estado em diferentes partes do gancho, pois todas essas operações são definidas em um local dentro do reducer .

Na versão do código que usa useState , além de escrever a lógica, precisamos declarar funções dentro do gancho para descobrir qual deve ser a próxima parte do estado. E ao usar useReducer , você não precisa fazer isso. Em vez disso, tudo cai na função reducer . Nós apenas precisamos desencadear uma ação do tipo apropriado, e isso, de fato, é tudo o que precisamos nos preocupar.

11. Use declarações de função em situações controversas


Um bom exemplo do uso desta recomendação é criar um useEffect limpeza useEffect :

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

Um desenvolvedor experiente do React sabe qual é o papel da função retornada e entenderá facilmente esse código. Mas se você imaginar que alguém que não esteja familiarizado com o useEffect lerá esse código, seria melhor expressar suas intenções no código da maneira mais clara possível. Trata-se de usar declarações de função que podem receber nomes significativos. Por exemplo, esse código pode ser reescrito assim:

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

Essa abordagem nos permite descrever claramente qual o papel da função retornada.

12. Use mais bonito


O Prettier ajuda os desenvolvedores e equipes individuais a manter uma abordagem consistente e consistente à formatação do código. Essa ferramenta ajuda a economizar tempo e esforço. Simplifica a revisão de código, reduzindo o número de razões para discutir o estilo dos programas. Prettier também incentiva os programadores a usar técnicas de escrita de código limpas. As regras aplicadas por esta ferramenta são editáveis. Como resultado, acontece que todos podem personalizá-lo como entenderem.

13. Esforce-se para usar a abreviação de declarações de fragmentos


A essência desta recomendação pode ser expressa nos dois exemplos a seguir.

Aqui está uma versão abreviada da declaração do fragmento:

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

Aqui está a versão completa:

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

14. Siga uma certa ordem de posicionamento dos elementos ao escrever o código.


Quando escrevo código, prefiro organizar alguns comandos em uma determinada ordem. Por exemplo, faço isso ao importar arquivos (a exceção aqui é apenas a importação de 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/' 

Observando esse código, alguém pode pensar que um pedido especial não é observado aqui. Afinal, as entidades importadas nem são classificadas em ordem alfabética. Mas organizar algo em ordem alfabética é apenas parte do esquema de ordenação de comandos que eu uso.

Eu, buscando a pureza do código dos meus projetos, uso as seguintes regras que se aplicam na ordem em que são seguidas:

  1. Importar reagir.
  2. Importar bibliotecas (em ordem alfabética).
  3. Comandos absolutos para importar entidades de um projeto (em ordem alfabética).
  4. Comandos de importação relativa (em ordem alfabética).
  5. Comandos do formulário import * as .
  6. Comandos do formulário import './<some file>.<some ext>' .

E aqui está como eu prefiro organizar, por exemplo, variáveis. Diga - propriedades dos objetos:

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

Se você seguir certas regras para solicitar entidades ao escrever um código, isso terá um efeito benéfico em sua pureza.

Sumário


Fornecemos dicas para escrever um código de aplicativo React limpo. Esperamos que você encontre algo útil entre eles.

Caros leitores! Que recomendações você adicionaria às dicas apresentadas neste artigo?

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


All Articles