86 votes

Gestionnaires d'événements dans les composants apatrides de React

J'essaie de trouver un moyen optimal de créer des gestionnaires d'événements dans les composants React sans état. Je pourrais faire quelque chose comme ceci :

const myComponent = (props) => {
    const myHandler = (e) => props.dispatch(something());
    return (
        <button onClick={myHandler}>Click Me</button>
    );
}

L'inconvénient est que chaque fois que ce composant est rendu, une nouvelle fonction "myHandler" est créée. Existe-t-il un meilleur moyen de créer des gestionnaires d'événements dans des composants sans état qui peuvent toujours accéder aux propriétés du composant ?

0 votes

UseCallback - const memoizedCallback = useCallback( () => { doSomething(a, b) ; }, [a, b], ) ; Retourne un callback mémorisé.

63voto

Wintamute Points 383

L'application de gestionnaires aux éléments des composants de fonction devrait généralement ressembler à ceci :

const f = props => <button onClick={props.onClick}></button>

Si vous devez faire quelque chose de beaucoup plus complexe, c'est le signe que soit a) le composant ne doit pas être sans état (utilisez une classe ou des crochets), soit b) vous devez créer le gestionnaire dans un composant conteneur externe sans état.

Par ailleurs, et en dépit de mon premier point, à moins que le composant ne se trouve dans une partie de l'application où le rendu est particulièrement intensif, il n'est pas nécessaire de s'inquiéter de la création de fonctions de flèches dans le fichier render() .

2 votes

Comment cela évite-t-il de créer une fonction à chaque fois que le composant sans état est rendu ?

1 votes

L'exemple de code ci-dessus montre simplement un gestionnaire appliqué par référence, aucune nouvelle fonction de gestionnaire n'est créée lors du rendu de ce composant. Si le composant externe a créé le gestionnaire en utilisant useCallback(() => {}, []) o this.onClick = this.onClick.bind(this) le composant recevrait alors la même référence de gestionnaire à chaque rendu, ce qui pourrait faciliter l'utilisation de l'option React.memo o shouldComponentUpdate (mais cela ne concerne que les composants nombreux/complexes ayant fait l'objet d'un rendu intensif).

52voto

ryanVincent Points 90

En utilisant la nouvelle fonctionnalité React hooks, cela pourrait ressembler à quelque chose comme ceci :

const HelloWorld = ({ dispatch }) => {
  const handleClick = useCallback(() => {
    dispatch(something())
  })
  return <button onClick={handleClick} />
}

useCallback crée une fonction mémorisée, ce qui signifie qu'une nouvelle fonction ne sera pas régénérée à chaque cycle de rendu.

https://reactjs.org/docs/hooks-reference.html#usecallback

Toutefois, ce projet est encore à l'état de proposition.

7 votes

React Hooks a été publié dans React 16.8 et est maintenant une partie officielle de React. Cette réponse fonctionne donc parfaitement.

4 votes

Il est à noter que la règle recommandée de l'exhaustivité de l'analyse qui fait partie du package eslint-plugin-react-hooks dit : "React Hook useCallback ne fait rien lorsqu'il est appelé avec un seul argument", donc, oui, dans ce cas, un tableau vide devrait être passé comme deuxième argument.

2 votes

Dans votre exemple ci-dessus, il n'y a pas de gain d'efficacité en utilisant useCallback - et vous générez toujours une nouvelle fonction flèche à chaque rendu (l'argument passé à la fonction useCallback ). useCallback n'est utile que pour transmettre des rappels à des composants enfants optimisés qui s'appuient sur l'égalité des références pour éviter les rendus inutiles. Si vous appliquez simplement la fonction de rappel à un élément HTML comme un bouton, n'utilisez pas l'option useCallback .

16voto

Phi Nguyen Points 2307

Que diriez-vous de cette façon :

const myHandler = (e,props) => props.dispatch(something());

const myComponent = (props) => {
 return (
    <button onClick={(e) => myHandler(e,props)}>Click Me</button>
  );
}

16 votes

Bonne pensée ! Malheureusement, cela ne résout pas le problème de la création d'une nouvelle fonction à chaque appel de rendu : github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/

0 votes

@aStewartDesign une solution ou une mise à jour pour ce problème ? vraiment heureux de l'entendre, car je suis confronté au même problème.

4 votes

Avoir un composant régulier parent qui a l'implémentation de myHandler et ensuite simplement le passer au sous-composant

6voto

ryanjduffy Points 2441

Si le gestionnaire repose sur des propriétés qui changent, vous devrez créer le gestionnaire à chaque fois, car vous n'avez pas d'instance à état sur laquelle le mettre en cache. Une autre alternative qui pourrait fonctionner serait de mémoriser le gestionnaire basé sur les props d'entrée.

Couple d'options de mise en œuvre lodash._mémoire R.memoize mémorisation rapide

4voto

Hasan Points 21

Solution un mapPropsToHandler et event.target.

Les fonctions sont des objets en js, il est donc possible de leur attacher des propriétés.

function onChange() { console.log(onChange.list) }

function Input(props) {
    onChange.list = props.list;
    return <input onChange={onChange}/>
}

cette fonction ne lie qu'une seule fois une propriété à une fonction.

export function mapPropsToHandler(handler, props) {
    for (let property in props) {
        if (props.hasOwnProperty(property)) {
            if(!handler.hasOwnProperty(property)) {
                 handler[property] = props[property];
            }
        }
    }
}

Je reçois mes accessoires comme ça.

export function InputCell({query_name, search, loader}) {
    mapPropsToHandler(onChange, {list, query_name, search, loader});
    return (
       <input onChange={onChange}/> 
    );
}

function onChange() {
    let {query_name, search, loader} = onChange;

    console.log(search)
}

Cet exemple combine à la fois event.target et mapPropsToHandler. Il est préférable d'attacher des fonctions aux gestionnaires uniquement, et non à des nombres ou à des chaînes de caractères. Les nombres et les chaînes de caractères peuvent être passés à l'aide d'un attribut DOM comme

<select data-id={id}/>

plutôt que mapPropsToHandler

import React, {PropTypes} from "react";
import swagger from "../../../swagger/index";
import {sync} from "../../../functions/sync";
import {getToken} from "../../../redux/helpers";
import {mapPropsToHandler} from "../../../functions/mapPropsToHandler";

function edit(event) {
    let {translator} = edit;
    const id = event.target.attributes.getNamedItem('data-id').value;
    sync(function*() {
        yield (new swagger.BillingApi())
            .billingListStatusIdPut(id, getToken(), {
                payloadData: {"admin_status": translator(event.target.value)}
            });
    });
}

export default function ChangeBillingStatus({translator, status, id}) {
    mapPropsToHandler(edit, {translator});

    return (
        <select key={Math.random()} className="form-control input-sm" name="status" defaultValue={status}
                onChange={edit} data-id={id}>
            <option data-tokens="accepted" value="accepted">{translator('accepted')}</option>
            <option data-tokens="pending" value="pending">{translator('pending')}</option>
            <option data-tokens="rejected" value="rejected">{translator('rejected')}</option>
        </select>
    )
}

solution deux. délégation d'événements

voir la solution 1. nous pouvons enlever le gestionnaire d'événement de l'entrée et le mettre à son parent qui contient d'autres entrées aussi et par l'aide de la technique de délégation nous pouvons être utiliser event.traget et la fonction mapPropsToHandler à nouveau.

2 votes

Mauvaise pratique ! Une fonction ne doit servir qu'à son objectif, elle est destinée à exécuter une logique sur certains paramètres, pas à contenir des propriétés. Ce n'est pas parce que javascript permet de nombreuses façons créatives de faire la même chose que vous devez vous laisser aller à utiliser ce qui fonctionne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X