14 conseils pour écrire du code React propre. 2e partie

Aujourd'hui, nous publions la deuxième partie du matériel sur l'écriture de code propre lors du développement d'applications React. Voici quelques conseils plus utiles.



Lire la première partie

8. Convertissez les éléments en double en composants


La conversion d'éléments en double en composants pouvant être réutilisés peut être appelée «mise en composants» de ces éléments.

Chaque développeur a ses propres raisons pour lesquelles il écrit du code React en double. Il peut s'agir d'une action délibérée ou d'un accident.

Quelle que soit la raison de l'apparition des mêmes fragments de code dans l'application, le programmeur doit réfléchir à la manière d'améliorer la situation.

Par exemple, si quelqu'un n'a pas pris l'habitude de se débarrasser des doublons, ils apparaîtront très probablement encore et encore dans ses projets. Quel genre de joueur d'équipe est celui qui fait ça? Cela complique simplement la vie future de ses collègues, qui seront confus lorsqu'ils rencontreront du code en double. Ils recevront un «cadeau» spécial s'ils doivent modifier des fragments de code similaires.

Jetez un œil à l'exemple suivant et réfléchissez à la façon de l'améliorer:

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 vous devez maintenant modifier les paramètres de la grille de xs={12} sm={6} à xs={12} sm={4} , cette tâche ne sera pas particulièrement agréable. Le fait est que pour cela, vous devez modifier le code à quatre endroits.

La beauté de l'approche par composant est qu'elle vous permet de résoudre des problèmes similaires à ce qui précède, en changeant le code en un seul endroit. Dans notre cas, ce changement se reflétera partout où les grilles sont utilisées:

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

Même le niveau minimal de conversion de code démontré ici rend ce code beaucoup plus pratique en termes de lecture et de support. Il est en même temps un moyen tout à fait adéquat de résoudre la tâche qui lui est confiée.

9. Efforcez-vous de garder vos composants aussi simples que possible.


Lorsque je travaillais sur des applications de vente, je rencontrais parfois non pas la nécessité de rechercher la simplicité des composants, mais la nécessité d'éviter les situations dans lesquelles les composants deviennent trop complexes.

Voici un exemple de composant inutilement compliqué. Il est représenté par 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>    )  } } 

Ce composant a été conçu comme un mécanisme simple, mais comme il contient une logique fortement liée, il est responsable de la résolution de plusieurs problèmes. Au moment où ce code a été écrit, les crochets React n'étaient pas encore sortis, mais React incluait des technologies telles que des composants d'ordre supérieur et des accessoires de rendu. Cela signifie que nous pouvons simplement utiliser l'un de ces modèles pour simplifier le composant. Cela nous permettra de démontrer une approche pour simplifier les composants sans changer les fonctionnalités existantes.

Auparavant, tout le code était stocké dans un seul fichier. Maintenant, nous le divisons en deux fichiers. Voici le contenu du premier fichier - 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,    })  } } 

Voici à quoi ressemble le deuxième fichier - 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 

Après le traitement, le code du projet s'est avéré beaucoup plus propre qu'auparavant. Nous avons extrait la logique de la partie présentation du composant. Maintenant, en plus, les tests unitaires du projet seront grandement simplifiés.

10. Utilisez useReducer lorsque vous compliquez useState


Plus vous devez traiter de fragments d'état dans un projet, plus l'utilisation de useState .

Par exemple, cela pourrait ressembler à ceci:

 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 

Il deviendra beaucoup plus pratique de travailler avec tout cela si vous traduisez ce code pour utiliser 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 

Bien que cette approche ne soit probablement pas plus propre que l'utilisation de useState , comme vous pouvez le voir en regardant le code, le nouveau code est plus facile à maintenir. Cela est dû au fait que lors de l'utilisation de useReducer programmeur n'a pas à se soucier des mises à jour d'état dans différentes parties du crochet, car toutes ces opérations sont définies au même endroit à l'intérieur du reducer .

Dans la version du code qui utilise useState , en plus d'écrire la logique, nous devons déclarer des fonctions à l'intérieur du hook afin de savoir quelle devrait être la prochaine partie de l'état. Et lorsque vous utilisez useReducer vous n'avez pas à le faire. Au lieu de cela, tout tombe dans la fonction de reducer . Nous avons juste besoin de déclencher une action du type approprié, et c'est en fait tout ce dont nous devons nous inquiéter.

11. Utilisez des déclarations de fonctions dans des situations controversées


Un bon exemple d'utilisation de cette recommandation est de créer un useEffect nettoyage useEffect :

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

Un développeur React expérimenté sait quel est le rôle de la fonction retournée, il comprendra facilement ce code. Mais si vous imaginez que quelqu'un qui n'est pas très familier avec useEffect lira ce code, il serait préférable d'exprimer ses intentions dans le code aussi clairement que possible. Il s'agit d'utiliser des déclarations de fonctions auxquelles on peut donner des noms significatifs. Par exemple, ce code peut être réécrit comme ceci:

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

Cette approche nous permet de décrire clairement le rôle de la fonction retournée.

12. Utilisez Prettier


Prettier aide les développeurs individuels et les équipes à maintenir une approche cohérente et cohérente du formatage du code. Cet outil permet d'économiser du temps et des efforts. Il simplifie la révision du code en réduisant le nombre de raisons de discuter du style des programmes. Prettier encourage également les programmeurs à utiliser des techniques d'écriture de code propres. Les règles appliquées par cet outil sont modifiables. En conséquence, il s'avère que tout le monde peut le personnaliser comme bon lui semble.

13. Efforcez-vous d'utiliser la sténographie pour les déclarations de fragments


L'essence de cette recommandation peut être exprimée dans les deux exemples suivants.

Voici une version abrégée de la déclaration de fragment:

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

Voici la version complète:

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

14. Suivez un certain ordre de placement des éléments lors de l'écriture de code.


Lorsque j'écris du code, je préfère organiser certaines commandes dans un certain ordre. Par exemple, je fais cela lors de l'importation de fichiers (l'exception ici n'est que l'importation 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/' 

En regardant ce code, quelqu'un pourrait penser qu'une commande spéciale n'est pas observée ici. Après tout, les entités importées ne sont même pas triées par ordre alphabétique. Mais organiser quelque chose dans l'ordre alphabétique n'est qu'une partie du schéma d'ordonnancement des commandes que j'utilise.

Moi, soucieux de la propreté du code de mes projets, j'utilise les règles suivantes qui s'appliquent dans l'ordre où elles sont suivies:

  1. Importer Réagir.
  2. Importer des bibliothèques (par ordre alphabétique).
  3. Commandes absolues pour importer des entités d'un projet (par ordre alphabétique).
  4. Commandes d'importation relatives (par ordre alphabétique).
  5. Les commandes du formulaire import * as .
  6. Les commandes du formulaire import './<some file>.<some ext>' .

Et voici comment je préfère organiser, par exemple, les variables. Say - propriétés des objets:

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

Si vous suivez certaines règles pour commander des entités lors de l'écriture de code - cela aura un effet bénéfique sur sa pureté.

Résumé


Nous vous avons fourni des conseils pour écrire du code d'application React propre. Nous espérons que vous y trouverez quelque chose qui vous sera utile.

Chers lecteurs! Quelles recommandations ajouteriez-vous aux conseils présentés dans cet article?

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


All Articles