Dans un composant basé sur la classe React je fais quelque chose comme ceci:
class SomeComponent extends React.Component{
onChange(ev){
this.setState({text: ev.currentValue.text});
}
transformText(){
return this.state.text.toUpperCase();
}
render(){
return (
<input type="text" onChange={this.onChange} value={this.transformText()} />
);
}
}
Ceci est un peu un exemple artificiel pour simplifier mon point. Ce que je veux essentiellement faire, c'est maintenir une référence constante à la fonction onChange. Dans l'exemple ci-dessus, lorsque React restitue mon composant, il ne restituera pas l'entrée si la valeur d'entrée n'a pas changé.
Choses importantes à noter ici:
Maintenant, si je devais réécrire ce composant à l'aide de crochets:
function onChange(setText, ev) {
setText(ev.currentValue.text);
};
function transformText(text) {
return text.toUpperCase();
};
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange} value={transformText()} />
);
}
Le problème est maintenant que je dois passer les méthodes text
à transformText
et setText
à onChange
respectivement. Les solutions possibles auxquelles je peux penser sont:
Faire l'une ou l'autre de ces modifications modifiera la référence constante aux fonctions que je dois conserver afin de ne pas rendre le composant input
. Comment dois-je procéder avec des crochets? Est-ce même possible?
Veuillez noter qu'il s'agit d'un exemple très simplifié et artificiel. Mon cas d'utilisation réel est assez complexe et je ne veux absolument pas restituer inutilement les composants.
Edit: Ce n'est pas un doublon de Que fait useCallback dans React? parce que j'essaie de comprendre comment obtenir un effet similaire à ce qui était fait à la manière des composants de classe, et tout en useCallback
fournit une façon de le faire, ce n'est pas idéal pour des problèmes de maintenabilité.
Définissez les fonctions à l'intérieur de la fonction de composant et utilisez des fermetures pour transmettre la valeur. Ensuite, ce que vous recherchez est useCallback
pour éviter des rendus inutiles. (pour cet exemple, ce n'est pas très utile)
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = useCallback((ev) => {
setText(ev.target.value);
}, []);
function transformText(text) {
return text.toUpperCase();
};
return (
<input type="text" onChange={onChange} value={transformText(text)} />
);
}
En savoir plus ici
C'est là que vous pouvez construire votre propre crochet (Dan Abramov a exhorté à ne pas utiliser le terme "crochets personnalisés" car cela rend la création de votre propre crochet plus difficile/plus avancée qu'elle ne l'est, qui est juste un copier/coller votre logique) extraire la logique de transformation du texte
"Coupez" simplement le code commenté ci-dessous de réponse de Mohamed .
function SomeComponent(props) {
// const [text, setText] = React.useState("");
// const onChange = ev => {
// setText(ev.target.value);
// };
// function transformText(text) {
// return text.toUpperCase();
// }
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
Et collez-le dans une nouvelle fonction (préfixe avec "use *" par convention). Nommez l'état et le rappel à renvoyer (sous forme d'objet ou de tableau selon votre situation)
function useTransformedText(textTransformer = text => text.toUpperCase()) {
const [text, setText] = React.useState("");
const onChange = ev => {
setText(ev.target.value);
};
return { onChange, text: textTransformer(text) };
}
Comme la logique de transformation peut être transmise (mais utilise UpperCase par défaut), vous pouvez utiliser la logique partagée à l'aide de votre propre hook.
function UpperCaseInput(props) {
const { onChange, text } = useTransformedText();
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
function LowerCaseInput(props) {
const { onChange, text } = useTransformedText(text => text.toLowerCase());
return (
<input type="text" onChange={React.useCallback(onChange)} value={text} />
);
}
Vous pouvez utiliser les composants ci-dessus comme ci-dessous.
function App() {
return (
<div className="App">
To Upper case: <UpperCaseInput />
<br />
To Lower case: <LowerCaseInput />
</div>
);
}
Le résultat ressemblerait à ceci.
Je sais que c'est une mauvaise forme de répondre à ma propre question, mais sur la base de cette réponse , et cette réponse , il semble que je vais devoir créer mon propre crochet personnalisé pour ce faire .
J'ai essentiellement construit un hook qui lie une fonction de rappel avec les arguments donnés et la mémorise. Il ne lie à nouveau le rappel que si les arguments donnés changent.
Si quelqu'un trouve un besoin pour un crochet similaire, je l'ai ouvert en tant que projet séparé. Il est disponible sur Github et NPM .
Le cas n'est pas spécifique aux hooks, il en serait de même pour le composant classe et setState
dans le cas où transformText
et onChange
devraient être extraits d'une classe. Il n'est pas nécessaire d'extraire des fonctions unifilaires, on peut donc supposer que les fonctions réelles sont suffisamment complexes pour justifier l'extraction.
C'est parfaitement bien d'avoir une fonction de transformation qui accepte une valeur comme argument.
Quant au gestionnaire d'événements, il doit avoir une référence à setState
, cela limite les façons dont il peut être utilisé.
Une recette courante consiste à utiliser la fonction de mise à jour de l'état. S'il doit accepter une valeur supplémentaire (par exemple, une valeur d'événement), il doit s'agir d'une fonction d'ordre supérieur.
const transformText = text => text.toUpperCase();
const onChange = val => _prevState => ({ text: val });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={e => setText(onChange(e.currentValue.text)} value={transformText(text)} />
);
}
Cette recette ne semble pas utile dans ce cas car l'original onChange
ne fait pas grand-chose. Cela signifie également que l'extraction n'était pas justifiée.
Une manière spécifique aux hooks est que setText
peut être passé comme rappel, contrairement à this.setState
. Donc onChange
peut être une fonction d'ordre supérieur:
const transformText = text => text.toUpperCase();
const onChange = setState => e => setState({ text: e.currentValue.text });
function SomeComponent(props) {
const [text, setText] = useState('');
return (
<input type="text" onChange={onChange(setText)} value={transformText(text)} />
);
}
Si l'intention est de réduire le rendu des enfants provoqué par des changements dans la prop onChange
, onChange
doit être mémorisé avec useCallback
ou useMemo
. Cela est possible car la fonction de définition de useState
ne change pas entre les mises à jour des composants:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const memoizedOnChange = useMemo(() => onChange(setText), []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}
La même chose peut être obtenue en n'extrayant pas onChange
et en utilisant useCallback
:
...
function SomeComponent(props) {
const [text, setText] = useState('');
const onChange = e => setText({ text: e.currentValue.text });
const memoizedOnChange = useCallback(onChange, []);
return (
<input type="text" onChange={memoizedOnChange} value={transformText(text)} />
);
}