Réagissez à une méthode alternative de forage de support (inverse, de l'enfant au parent) pour traiter les formulaires
P粉419164700
P粉419164700 2023-09-01 19:45:14
0
2
595
<p>Je suis nouveau sur React et je l'apprends à travers quelques projets pratiques. Je travaille actuellement sur le traitement et la validation des formulaires. J'utilise le composant Form de React Router dans un SPA et, à l'intérieur du formulaire, j'ai un élément FormGroup, qui restitue les entrées d'étiquette et les messages d'erreur. J'utilise également mon propre composant d'entrée au sein du composant FormGroup pour séparer la logique et la gestion de l'état des entrées utilisées dans le formulaire. </p> <p>J'ai donc placé le composant Form et le composant FormGroup dans l'exemple de page de connexion comme ceci : </p> <p><em>pages/Login.js</em></p> <pre class="brush:js;toolbar:false;">import { useState } depuis 'react'; importer {Link, Form, useNavigate, useSubmit } depuis 'react-router-dom' ; importer FormGroup depuis '../components/UI/FormGroup' ; importer le bouton depuis '../components/UI/Button' ; importer la carte depuis '../components/UI/Card' ; importer './Login.scss'; fonction Page de connexion() { const naviguer = useNavigate(); const soumettre = useSubmit(); const [isLoginValid, setIsLoginValid] = useState(false); const [isPasswordValid, setIsPasswordValid] = useState(false); var réinitialiserLoginInput = null ; var réinitialiserPasswordInput = null ; soit isFormValid = false; if(isLoginValid && isPasswordValid) { estFormValid = vrai ; } fonction formSubmitHandler (événement) { event.preventDefault(); si(!isFormValid) { retour; } réinitialiserLoginInput(); réinitialiserPasswordInput(); soumettre (événement.currentTarget); } fonction loginValidityChangeHandler (isValid) { setIsLoginValid(isValid); } fonction mot de passeValidityChangeHandler(isValid) { setIsPasswordValid(isValid); } fonction resetLoginInputHandler (réinitialisation) { resetLoginInput = réinitialiser ; } fonction resetPasswordInputHandler (réinitialisation) { réinitialiserPasswordInput = réinitialiser ; } fonction switchToSignupHandler() { naviguer('/inscription'); } retour ( <div className="connexion"> <div className="login__logo"> Allez la Coupe </div> <p className="login__description"> Connectez-vous à votre compte Go Cup </p> <Bordure de la carte> <Form onSubmit={formSubmitHandler}> <Groupe de formulaires id="connexion" label="Nom d'utilisateur ou adresse e-mail" inputProps={{ tapez : "texte", nom : "login", validité : (valeur) => { valeur = valeur.trim(); si(!valeur) { return [false, 'Le nom d'utilisateur ou l'adresse e-mail est requis.'] } else if(value.length < 3 || value.length > 30) { return [false, 'Le nom d'utilisateur ou l'adresse e-mail doit contenir au moins 3 et au maximum 30 caractères'] ; } autre { retourner [vrai, nul] ; } }, onValidityChange : loginValidityChangeHandler, onReset : réinitialiserLoginInputHandler }} /> <Groupe de formulaires id="mot de passe" label="Mot de passe" sideLabelElement={ <Lien vers ="/password-reset"> Mot de passe oublié? </Lien> } inputProps={{ tapez : "mot de passe", nom : "mot de passe", validité : (valeur) => { valeur = valeur.trim(); si(!valeur) { return [false, 'Un mot de passe est requis.'] } else if(value.length < 4 || value.length > 1024) { return [false, 'Le mot de passe doit comporter au moins 4 ou au maximum 1 024 caractères.']; } autre { retourner [vrai, nul] ; } }, onValidityChange : mot de passeValidityChangeHandler, onReset : réinitialiserPasswordInputHandler }}/> <div className="text-center"> <Classe du boutonNom="w-100" type="soumettre"> Se connecter </Bouton> <span className="login__or"> ou </envergure> <Classe du boutonNom="w-100" onClick={switchToSignupHandler}> S'inscrire </Bouton> </div> </Formulaire> </Carte> </div> ); } exporter la page de connexion par défaut ; ≪/pré> <p>Comme vous pouvez le voir dans le code ci-dessus, j'utilise le composant FormGroup et je transmets les propriétés <code>onValidityChange</code> et <code>onReset</code> / La valeur mise à jour du code> Modifications et fonctions de réinitialisation pour réinitialiser la saisie après la soumission du formulaire, etc. Utilisez mon hook personnalisé useInput pour créer les fonctions <code>isValid</code> et <code>reset</code> Je transmets la valeur isValid lorsque la valeur change et je transmets la fonction de réinitialisation du composant d'entrée à l'aide des accessoires définis dans le composant FormGroup. J'utilise également les états <code>isLoginValid</code> et <code>isPasswordValid</code> dans la page de connexion pour stocker la valeur d'état mise à jour <code>isValid</code> composant. J'ai donc défini des états dans le composant d'entrée et les ai transmis au composant parent à l'aide d'accessoires et j'ai stocké leurs valeurs dans d'autres états créés dans ce composant parent. Le forage d’hélices qui se déroulait me mettait un peu mal à l’aise. </p> <p>L'état est géré à l'intérieur du composant d'entrée, j'ai ces états : </p> <ul> <li><strong>Valeur : </strong>Entrez la valeur de l'élément. ≪/li> <li><strong>isInputTouched</strong> : détermine si l'utilisateur a touché/concentré l'entrée pour déterminer s'il doit afficher un message d'erreur de validation, le cas échéant. ≪/li> </ul> <p>Je combine et applique certaines fonctions (telles que la fonction de validation transmise au composant d'entrée) à ces deux états pour créer d'autres valeurs de variable afin de collecter des informations sur l'entrée et sa validité, par exemple si la valeur est valide (isValid ), s'il y a une vérification du message (message), si l'entrée est valide (<code>isInputValid = isValid || !isInputTouched</code>) pour décider d'afficher le message de vérification.</p> <p>Ces états et valeurs sont gérés dans un hook personnalisé que j'ai créé, <code>useInput</code>, comme ceci : </p> <p><em>hooks/use-state.js</em></p> <pre class="brush:js;toolbar:false;">import { useState, useCallback } from 'react'; fonction useInput (validitéFn) { const [valeur, setValue] = useState(''); const [isInputTouched, setIsInputTouched] = useState(false); const [isValid, message] = typeof validateFn === 'function' validFn(value) : [true, null]; const isInputValid = isValid || !isInputTouched; const inputChangeHandler = useCallback(event => { setValue(event.target.value); si(!isInputTouched) { setIsInputTouched(true); } }, [isInputTouched]); const inputBlurHandler = useCallback(() => { setIsInputTouched(true); }, []); const reset = useCallback(() => { setValeur(''); setIsInputTouched(faux); }, []); retour { valeur, est valable, estInputValid, message, gestionnaire de changement d'entrée, gestionnaire de flou d'entrée, réinitialiser } ; } exporter useInput par défaut ; ≪/pré> <p>J'utilise actuellement ce hook personnalisé dans Input.js comme ceci : </p> <p><em>components/UI/Input.js</em></p> <pre class="brush:js;toolbar:false;">import { useEffect } depuis 'react'; importer useInput depuis '../../hooks/use-input' ; importer './Input.scss'; fonction Entrée (accessoires) { const { valeur, est valable, estInputValid, message, gestionnaire de changement d'entrée, gestionnaire de flou d'entrée, réinitialiser } = useInput(props.validity); const { onIsInputValidOrMessageChange, surValidityChange, surRéinitialiser } = accessoires ; laissez className = 'form-control'; si(!isInputValid) { className = `${className} form-control--invalid` ; } if(props.className) { nom de classe = `${nom de classe} ${props.nom de classe}` ; } useEffect(() => { if(onIsInputValidOrMessageChange && typeof onIsInputValidOrMessageChange === 'fonction') { onIsInputValidOrMessageChange(isInputValid, message); } }, [onIsInputValidOrMessageChange, isInputValid, message]); useEffect(() => { if(onValidityChange && typeof onValidityChange === 'fonction') { onValidityChange(isValid); } }, [onValidityChange, isValid]); useEffect(() => { if(onReset && typeof onReset === 'fonction') { onReset(réinitialisation); } }, [onReset, réinitialiser]); retour ( <entrée {...accessoires} nom de classe={nom de classe} valeur={valeur}onChange={inputChangeHandler} onBlur={inputBlurHandler} /> ); } exporter l'entrée par défaut ; ≪/pré> <p>Dans le composant d'entrée, j'utilise directement l'état <code>isInputValid</code> pour ajouter la classe CSS non valide à l'entrée. Mais je transmets également les fonctions <code>isInputValid</code>, <code>message</code>, <code>isValid</code> à utiliser dedans. Pour transmettre ces états et fonctions, j'utilise les fonctions <code>onIsInputValidOrMessageChange</code>, <code>onValidityChange</code>, <code>onReset</code> enfant à parent). </p> <p>Voici la définition du composant FormGroup et comment j'utilise l'état d'entrée à l'intérieur du FormGroup pour afficher le message de validation (le cas échéant) : </p> <p><em>components/UI/FormGroup.js</em></p> <pre class="brush:js;toolbar:false;">import { useState } depuis 'react'; importer l'entrée depuis './Input' ; importer './FormGroup.scss'; fonction FormGroup (accessoires) { const [message, setMessage] = useState(null); const [isInputValid, setIsInputValid] = useState(false); let className = 'form-group'; if(props.className) { className = `form-group ${props.className}` ; } laissez labelCmp = ( <label htmlFor={props.id}> {props.étiquette} </étiquette> ); if(props.sideLabelElement) { étiquetteCmp = ( <div className="form-label-group"> {labelCmp} {props.sideLabelElement} </div> ); } function isInputValidOrMessageChangeHandler (changedIsInputValid, changesMessage) { setIsInputValid(changedIsInputValid); setMessage(changedMessage); } retour ( <div className={className}> {labelCmp} <Entrée identifiant={props.id} onIsInputValidOrMessageChange={isInputValidOrMessageChangeHandler} {...props.inputProps} /> {!isInputValid && <p>{message}</p>} </div> ); } exporter le FormGroup par défaut ; ≪/pré> <p>Comme vous pouvez le voir dans le code ci-dessus, j'ai défini les états <code>message</code> et <code>isInputValid</code> <code>isInputValid</code> code> L'état transmis par le composant d'entrée. J'ai défini 2 états dans le composant d'entrée pour contenir ces valeurs, mais je dois définir 2 autres états dans ce composant pour stocker les valeurs mises à jour et transmises dans le composant d'entrée. C'est un peu bizarre et cela ne me semble pas être la meilleure solution. </p> <p><strong>La question est : </strong>Je pense que je peux utiliser React Context (useContext) ou React Redux pour résoudre le problème de forage d'accessoires ici. Mais je ne sais pas si ma gestion actuelle de l'état est mauvaise et peut être améliorée à l'aide de React Context ou React Redux. Parce que d'après ce que j'ai compris, React Context peut être terrible dans les situations où l'état change fréquemment, mais si le Context est utilisé à l'échelle de l'application, alors cela fonctionne. Ici, je peux créer un contexte pour stocker et mettre à jour l'intégralité du formulaire, permettant ainsi une expansion à l'échelle du formulaire. React Redux, en revanche, n'est peut-être pas la meilleure solution pour le silo et peut être un peu exagéré. Qu'en penses-tu? Quelle pourrait être une meilleure alternative dans cette situation particulière ? </p> <p><strong>Remarque : </strong>Étant donné que je suis nouveau sur React, je suis ouvert à toutes vos suggestions sur tout mon codage, des simples erreurs aux erreurs générales. Merci! </p>
P粉419164700
P粉419164700

répondre à tous(2)
P粉627136450

Il existe deux principales écoles de pensée en matière de gestion de l'État dans React : contrôlée et incontrôlée. Les formulaires contrôlés peuvent être contrôlés à l'aide d'un contexte React où les valeurs sont accessibles de n'importe où pour assurer la réactivité. Cependant, une saisie contrôlée peut entraîner des problèmes de performances, notamment lors de la mise à jour de l'intégralité du formulaire à chaque saisie. C’est là qu’interviennent les formes incontrôlées. Avec ce paradigme, toute gestion d'état doit exploiter les capacités natives du navigateur pour afficher l'état. Le principal problème de cette approche est que vous perdez l'aspect React du formulaire, vous devez collecter manuellement les données du formulaire lors de la soumission, et la gestion de plusieurs références pour cela peut être fastidieuse.

L'entrée contrôlée ressemble à ceci :

const [name, setName] = useState("");

return <input value={name} onChange={(e) => setName(e.currentTarget.value)} />

EDIT : Comme @Arkellys l'a souligné, vous n'avez pas nécessairement besoin de références pour collecter les données du formulaire, Voici un exemple utilisant FormData

et hors de contrôle :

const name = useRef(null);
const onSubmit = () => {
    const nameContent = name.current.value;
}
return <input ref={name} defaultValue="" />

Il ressort clairement des deux exemples que la maintenance de formulaires à plusieurs composants en utilisant l'une ou l'autre approche est fastidieuse, c'est pourquoi les bibliothèques sont souvent utilisées pour vous aider à gérer vos formulaires. Je recommande personnellement React Hook Form comme bibliothèque de formulaires testée, bien entretenue et facile à utiliser. Il prend une forme non contrôlée pour des performances optimales tout en vous permettant de visualiser une seule entrée pour un rendu réactif.

Que vous utilisiez Redux, React context ou tout autre système de gestion d'état, il n'y a généralement aucune différence en termes de performances, en supposant que vous l'implémentiez correctement. Si vous aimez l'architecture flux, utilisez certainement Redux, mais dans la plupart des cas, le contexte React est à la fois performant et suffisant.

Votre useInput 自定义挂钩看起来是解决问题 react-hook-formreact-final-form tentative courageuse mais malavisée de code > a été résolue. Vous créez une complexité inutile et des effets secondaires imprévisibles avec cette abstraction. De plus, vous mirror props a> qui est souvent un anti-modèle dans React.

Si vous souhaitez vraiment implémenter votre propre logique de formulaire (ce que je recommande de ne pas faire sauf à des fins éducatives), vous pouvez suivre ces directives :

  1. Gardez une seule source de vérité chez l'ancêtre commun le plus élevé
  2. Évitez la mise en miroir et la copie des états
  3. Utilisez useMemouseRef pour restituer le moins possible
P粉596191963

C'est un aspect simple que j'utilise pour choisir entre une bibliothèque de publication-abonnement comme Redux et la propagation de l'état via une arborescence de composants.

Si deux composants ont une relation parent-enfant et sont à au plus deux bords l'un de l'autre, propagez l'état enfant au parent

Parent -> enfant1-niveau1 -> enfant1-niveau2 ------ OK

Parent -> enfant1-niveau1 ------ OK

Parent -> enfant1-niveau1 -> enfant1-niveau2 -> enfant1-niveau3 --> Trop de voyages pour changer le statut de enfant1-niveau3 à parent

  • Si la distance entre les composants interactifs est supérieure à 2 bords, utilisez redux
  • Utilisez Redux pour les composants frères, c'est-à-dire les composants enfants qui partagent un composant parent et doivent communiquer entre eux (sélectionnez un élément de l'arborescence dans le panneau latéral et affichez les détails de l'élément sélectionné dans le composant principal)

Depuis la mise en œuvre

  • Je trouve que useInput est une refactorisation excessive, votre composant d'entrée devrait être suffisant pour gérer les opérations liées aux entrées, une meilleure validation abstraite, etc.
  • Vous pouvez déclencher toutes les validations lors de la soumission du formulaire, auquel cas vous n'avez pas besoin de saisie contrôlée (ajouter la validation à l'événement onSubmit du formulaire)
  • Cependant, si votre formulaire contient trop de champs (disons >5) et que vous souhaitez valider le champ avant de le soumettre, vous pouvez utiliser l'événement onBlur du champ de saisie, ou utiliser onInput avec une action anti-rebond (par exemple, action anti-rebond de lodash) Ou implémentez-le comme ça

function debounce(func, timeout = 300){
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => { func.apply(this, args); }, timeout);
  };
}
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal