Antipatterns dans React ou Bad Tips pour les débutants

Salut, Habr.

Exactement un an s'est écoulé depuis que j'ai commencé à étudier React. Pendant ce temps, j'ai réussi à sortir plusieurs petites applications mobiles écrites en React Native, et à participer au développement d'une application web utilisant ReactJS. En résumant et en repensant à tous ces râteaux sur lesquels j'ai réussi à marcher, j'ai eu l'idée d'exprimer mon expérience sous la forme d'un article. Je ferai une réserve qu'avant de commencer l'étude de la réaction, j'avais 3 ans d'expérience en développement en c ++, python, ainsi que l'opinion qu'il n'y a rien de compliqué dans le développement frontal et qu'il ne sera pas difficile de tout comprendre. Par conséquent, dans les premiers mois, j'ai négligé de lire la littérature pédagogique et, fondamentalement, juste des exemples de code prêts à l'emploi pour Google. En conséquence, un développeur exemplaire qui étudie tout d'abord la documentation ne trouvera probablement rien de nouveau pour lui ici, mais je pense toujours que beaucoup de gens, lorsqu'ils étudient les nouvelles technologies, préfèrent le chemin de la pratique à la théorie. Donc, si cet article sauve quelqu'un d'un râteau, alors j'ai essayé en vain.

Astuce 1. Utilisation des formulaires


La situation classique: il existe un formulaire avec plusieurs champs dans lequel l'utilisateur saisit des données, puis clique sur le bouton, et les données saisies sont envoyées à une API externe / enregistrées dans l'état / affichées à l'écran - souligner le nécessaire.

Option 1. Comment ne pas faire


Dans React, vous pouvez créer un lien vers un nœud DOM ou un composant React.

this.myRef = React.createRef(); 

À l'aide de l'attribut ref, le lien créé peut être attaché au composant / nœud requis.

 <input id="data" type="text" ref={this.myRef} /> 

Ainsi, le problème ci-dessus peut être résolu en créant une référence pour chaque champ du formulaire, et dans le corps de la fonction appelée lorsque le bouton est cliqué, obtenez les données du formulaire en contactant les liens nécessaires.

 class BadForm extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); this.onClickHandler = this.onClickHandler.bind(this); } onClickHandler() { const data = this.myRef.current.value; alert(data); } render() { return ( <> <form> <label htmlFor="data">Bad form:</label> <input id="data" type="text" ref={this.myRef} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Comment le singe intérieur peut essayer de justifier cette décision:

  1. La chose principale qui fonctionne, vous avez encore 100 500 tâches et les émissions de télévision ne sont pas regardées; les billets ne sont pas fermés. Laissez comme ça, puis changez
  2. Découvrez le peu de code nécessaire pour traiter le formulaire. Ref déclaré et accédez aux données où vous le souhaitez.
  3. Si vous stockez la valeur dans l'état, chaque fois que vous modifiez les données d'entrée, l'application entière sera à nouveau rendue et vous n'avez besoin que des données finales. Donc, cette méthode s'avère également bonne pour l'optimisation, laissez-la ainsi.

Pourquoi le singe a tort:

L'exemple ci-dessus est l'anti-modèle classique de React, qui viole le concept d'un flux de données unidirectionnel. Dans ce cas, votre application ne pourra pas répondre aux modifications de données lors de la saisie, car elles ne sont pas stockées dans leur état.

Option 2. Solution classique


Pour chaque champ de formulaire, une variable est créée dans l'état dans lequel le résultat d'entrée sera stocké. L'attribut value est affecté à cette variable. L'attribut onhange se voit attribuer une fonction dans laquelle la valeur de la variable dans l'état est modifiée via setState (). Ainsi, toutes les données sont extraites de l'état et lorsque les données changent, l'état change et l'application est à nouveau rendue.

 class GoodForm extends React.Component { constructor(props) { super(props); this.state = { data: '' }; this.onChangeData = this.onChangeData.bind(this); this.onClickHandler = this.onClickHandler.bind(this); } onChangeData(event) { this.setState({ data: event.target.value }); } onClickHandler(event) { const { data } = this.state; alert(data); } render() { const { data } = this.state; return ( <> <form> <label htmlFor="data">Good form:</label> <input id="data" type="text" value={data} onChange={this.onChangeData} /> <input type="button" value="OK" onClick={this.onClickHandler} /> </form> </> ); } } 

Option 3. Avancé. Quand les formes deviennent nombreuses


La deuxième option présente un certain nombre d'inconvénients: une grande quantité de code standard, pour chaque champ, il est nécessaire de déclarer la méthode onhange et d'ajouter une variable à l'état. Lorsqu'il s'agit de valider les données saisies et d'afficher des messages d'erreur, la quantité de code augmente encore plus. Pour faciliter le travail avec les formulaires, il existe une excellente bibliothèque Formik qui s'occupe des problèmes liés à la maintenance des formulaires et facilite également l'ajout d'un schéma de validation.

 import React from 'react'; import { Formik } from 'formik'; import * as Yup from 'yup'; const SigninSchema = Yup.object().shape({ data: Yup.string() .min(2, 'Too Short!') .max(50, 'Too Long!') .required('Data required'), }); export default () => ( <div> <Formik initialValues={{ data: '' }} validationSchema={SigninSchema} onSubmit={(values) => { alert(values.data); }} render={(props) => ( <form onSubmit={props.handleSubmit}> <label>Formik form:</label> <input type="text" onChange={props.handleChange} onBlur={props.handleBlur} value={props.values.data} name="data" /> {props.errors.data && props.touched.data ? ( <div>{props.errors.data}</div> ) : null} <button type="submit">Ok</button> </form> )} /> </div> ); 

Astuce 2. Évitez les mutations


Envisagez une application de liste de tâches simple. Dans le constructeur, nous définissons dans l'état la variable dans laquelle la liste de tâches sera stockée. Dans la méthode render (), affichez le formulaire à travers lequel nous ajouterons des cas à la liste. Voyons maintenant comment changer d'état.

Mauvaise option conduisant à une mutation du tableau:

 this.state.data.push(item); 

Dans ce cas, le tableau a vraiment changé, mais React n'en sait rien, ce qui signifie que la méthode render () ne sera pas appelée et que nos modifications ne seront pas affichées. Le fait est qu'en JavaScript, lors de la création d'un nouveau tableau ou objet, le lien est enregistré dans la variable, et non l'objet lui-même. Ainsi, en ajoutant un nouvel élément au tableau de données, nous modifions le tableau lui-même, mais pas le lien vers celui-ci, ce qui signifie que la valeur des données stockées dans l'état ne changera pas.

Des mutations en JavaScript peuvent être rencontrées à chaque tour. Pour éviter les mutations de données, utilisez l'opérateur d'étalement pour les tableaux, les méthodes filter () et map (), et pour les objets, utilisez l'opérateur d'étalement ou la méthode assign ().

 const newData = [...data, item]; const copy = Object.assign({}, obj); 

Revenons à notre application, il vaut la peine de dire que l'option correcte pour changer d'état est d'utiliser la méthode setState (). N'essayez pas de changer directement l'État ailleurs que le constructeur, car cela contredit l'idéologie React.

Ne fais pas ça!

 this.state.data = [...data, item]; 

Évitez également la mutation d'état. Même si vous utilisez setState (), les mutations peuvent entraîner des bogues lors de la tentative d'optimisation. Par exemple, si vous passez un objet muté via des accessoires à un enfant PureComponent, ce composant ne pourra pas comprendre que les accessoires reçus ont changé et ne sera pas restitués.

Ne fais pas ça!

 this.state.data.push(item); this.setState({ data: this.state.data }); 

L'option correcte:

 const { data } = this.state; const newData = [...data, item]; this.setState({ data: newData }); 

Mais même l'option ci-dessus peut conduire à des bugs subtils. Le fait est que personne ne garantit que pendant le temps écoulé entre la réception de la variable de données de l'état et l'écriture de sa nouvelle valeur à l'état, l'état lui-même ne changera pas. Ainsi, vous risquez de perdre certaines des modifications apportées. Par conséquent, dans le cas où vous devez mettre à jour la valeur d'une variable dans l'état en utilisant sa valeur précédente, procédez comme suit:

L'option correcte, si la condition suivante dépend du courant:

 this.setState((state) => { return {data: [...state.data, item]}; }); 

Astuce 3. Émulation d'une application multipage


Votre application est en cours de développement et vous vous rendez compte à un moment donné que vous avez besoin de plusieurs pages. Mais que faire, car React est une application d'une seule page? À ce stade, l'idée folle suivante peut vous venir à l'esprit. Vous décidez de conserver l'identifiant de la page en cours dans l'état global de votre application, par exemple en utilisant le magasin redux. Pour afficher la page souhaitée, vous utiliserez le rendu conditionnel et basculerez entre les pages, appelant l'action avec la charge utile souhaitée, modifiant ainsi les valeurs dans le magasin redux.

App.js

 import React from 'react'; import { connect } from 'react-redux'; import './App.css'; import Page1 from './Page1'; import Page2 from './Page2'; const mapStateToProps = (state) => ({ page: state.page }); function AppCon(props) { if (props.page === 'Page1') { return ( <div className="App"> <Page1 /> </div> ); } return ( <div className="App"> <Page2 /> </div> ); } const App = connect(mapStateToProps)(AppCon); export default App; 

Page1.js

 import React from 'react'; import { connect } from 'react-redux'; import { setPage } from './redux/actions'; function mapDispatchToProps(dispatch) { return { setPageHandle: (page) => dispatch(setPage(page)), }; } function Page1Con(props) { return ( <> <h3> Page 1 </h3> <input type="button" value="Go to page2" onClick={() => props.setPageHandle('Page2')} /> </> ); } const Page1 = connect(null, mapDispatchToProps)(Page1Con); export default Page1; 

Pourquoi est-ce mauvais?

  1. Cette solution est un exemple de vélo primitif. Si vous savez fabriquer un tel vélo avec compétence et comprenez ce que vous cherchez, ce n'est pas à moi de vous le conseiller. Sinon, votre code sera implicite, déroutant et trop complexe.
  2. Vous ne pourrez pas utiliser le bouton de retour dans le navigateur, car l'historique des visites ne sera pas enregistré.

Comment résoudre ça?

Utilisez simplement react-router . Il s'agit d'un excellent package qui peut facilement transformer votre application en plusieurs pages.

Astuce 4. Où placer les demandes d'API


À un moment donné, vous deviez ajouter une demande à une API externe dans votre application. Et ici, vous vous demandez: où dans votre application avez-vous besoin d'exécuter la demande?
Pour le moment, lors du montage d'un composant React, son cycle de vie est le suivant:

  • constructeur ()
  • getDerivedStateFromProps statique ()
  • render ()
  • componentDidMount ()

Analysons toutes les options dans l'ordre.

Dans la méthode constructor (), la documentation ne recommande pas de faire autre chose que:

  • Initialisation de l'état interne via l'attribution de l'objet this.state.
  • Liaisons des gestionnaires d'événements à une instance.

Les appels à l'API ne sont pas inclus dans cette liste, alors passons à autre chose.

La méthode getDerivedStateFromProps () selon la documentation existe pour de rares situations où l'état dépend des changements d'accessoires. Encore une fois, pas notre cas.

L'erreur la plus courante est l'emplacement du code qui exécute les requêtes API dans la méthode render (). Cela conduit au fait que dès que votre demande sera exécutée avec succès, vous enregistrerez très probablement le résultat dans l'état du composant, et cela conduira à un nouvel appel à la méthode render (), dans laquelle votre demande à l'API sera à nouveau exécutée. Ainsi, votre composant se retrouvera dans un rendu sans fin, et ce n'est clairement pas ce dont vous avez besoin.

La méthode componentDidMount () est donc l'endroit idéal pour accéder à l'api externe.

Conclusion


Des exemples de code peuvent être trouvés sur github .

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


All Articles