86 votes

Gestionnaires d'événements dans les composants React Stateless

Essayer 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 (
        Cliquez-moi
    );
}

L'inconvénient ici étant qu'à 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], ); Renvoie un rappel optimisé.

63voto

Wintamute Points 383

Applying handlers to elements in function components should generally just look like this:

const f = props => 

If you need to do anything much more complex it's a sign that either a) the component shouldn't be stateless (use a class, or hooks), or b) you should be creating the handler in an outer stateful container component.

As an aside, and undermining my first point slightly, unless the component is in a particularly intensively re-rendered part of the app there's no need to worry about creating arrow functions in render().

2 votes

Comment éviter de créer une fonction à chaque fois que le composant sans état est rendu?

1 votes

Le code exemple ci-dessus montre simplement un gestionnaire étant 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(() => {}, []) ou this.onClick = this.onClick.bind(this), alors le composant obtiendrait la même référence de gestionnaire à chaque rendu également, ce qui pourrait aider avec l'utilisation de React.memo ou shouldComponentUpdate (mais cela n'est pertinent qu'avec des composants intensivement rendus de manière nombreuse/complexe).

52voto

ryanVincent Points 90

En utilisant la nouvelle fonctionnalité des hooks React, cela pourrait ressembler à ceci :

const HelloWorld = ({ dispatch }) => {
  const handleClick = useCallback(() => {
    dispatch(something())
  })
  return 

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

Cependant, cela est toujours au stade de proposition.

7 votes

Les Hooks React ont été publiés dans React 16.8 et font désormais partie intégrante de React. Ainsi, cette réponse fonctionne parfaitement.

4 votes

Il convient de noter que la règle exhaustive-deps recommandée dans le cadre du package eslint-plugin-react-hooks indique : "Le Hook React useCallback ne fait rien lorsqu'il est appelé avec un seul argument.", donc, oui, dans ce cas, un tableau vide doit être passé en tant que deuxième argument.

2 votes

Dans votre exemple ci-dessus, il n'y a pas d'efficacité gagnée en utilisant useCallback - et vous générez toujours une nouvelle fonction fléchée à chaque rendu (l'argument passé à useCallback). useCallback n'est utile que lors de la transmission de rappels à des composants enfants optimisés qui dépendent de l'égalité de référence pour éviter des rendus inutiles. Si vous appliquez simplement le rappel à un élément HTML comme un bouton, n'utilisez pas useCallback.

16voto

Phi Nguyen Points 2307

Que diriez-vous de cette manière :

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

const myComponent = (props) => {
 return (
     myHandler(e,props)}>Cliquez-moi
  );
}

16 votes

Bonne pensée! Malheureusement, cela ne contourne pas le problème de créer une nouvelle fonction à chaque appel de rendu : github.com/yannickcr/eslint-plugin-react/blob/master/docs/ru‌​les/…

0 votes

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

4 votes

Avoir un composant régulier parent qui a la mise en œuvre du myHandler, puis simplement le passer au sous-composant

6voto

ryanjduffy Points 2441

Si le gestionnaire dépend de propriétés qui changent, vous devrez créer le gestionnaire à chaque fois car vous manquez d'une instance étatique sur laquelle le mettre en cache. Une autre alternative qui pourrait fonctionner serait de mémoriser le gestionnaire en fonction des props d'entrée.

Options d'implémentation en couple lodash._memoize R.memoize fast-memoize

4voto

Hasan Points 21

Solution one mapPropsToHandler and event.target.

functions are objects in js so its possible to attach them properties.

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

function Input(props) {
    onChange.list = props.list;
    return 
}

this function only bind once a property to a function.

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

I do get my props just like this.

export function InputCell({query_name, search, loader}) {
    mapPropsToHandler(onChange, {list, query_name, search, loader});
    return (

    );
}

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

    console.log(search)
}

this example combined both event.target and mapPropsToHandler. its better to attach functions to handlers only not numbers or strings. number and strings could be passed with help of DOM attribute like

rather than 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 (

            {translator('accepted')}
            {translator('pending')}
            {translator('rejected')}

    )
}

solution two. event delegation

see solution one. we can remove event handler from input and put it to its parent that holds other inputs too and by the help delegation technique we can be use event.traget and mapPropsToHandler function again.

2 votes

Mauvaise pratique ! Une fonction ne devrait servir qu'à son but, elle est destinée à exécuter de la logique sur certains paramètres et non à contenir des propriétés, juste parce que JavaScript permet de nombreuses façons créatives de faire la même chose ne signifie pas que vous devez vous permettre d'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