Fonctions dans les composants sans état?

90

J'essaie de convertir cette <canvas>animation cool que j'ai trouvée ici en un composant réutilisable React. Il semble que ce composant nécessite un composant parent pour le canevas et de nombreux composants enfants pour le function Ball().

Pour des raisons de performances, il serait probablement préférable de transformer le Ballsen composants sans état car il y en aura beaucoup. Je ne suis pas aussi familier avec la création de composants sans état et je me demandais où je devrais définir les fonctions this.update()et this.drawqui sont définies dans function Ball().

Les fonctions des composants sans état vont-elles à l'intérieur ou à l'extérieur du composant? En d'autres termes, laquelle des propositions suivantes est la meilleure?

1:

const Ball = (props) => {
    const update = () => {
        ...
    }

    const draw = () => {
        ...
    }

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

const Ball = (props) => {
    return (
       ...
    );
}

Quels sont les avantages / inconvénients de chacun et l'un d'entre eux est-il meilleur pour des cas d'utilisation spécifiques tels que le mien?

MarksCode
la source
Pouvez-vous publier le code existant pour que nous voyions comment il sera utilisé?
Scimonster
@Scimonster Je l'ai posté dans un lien intégré, peut-être que vous l'avez manqué. Voici le lien: codepen.io/awendland/pen/XJExGv
MarksCode

Réponses:

113

La première chose à noter est que les composants fonctionnels sans état ne peuvent pas avoir de méthodes, il ne faut pas compter sur un appel updateou drawsur un rendu Balls'il s'agit d'un composant fonctionnel sans état.

Dans la plupart des cas, vous devez déclarer les fonctions en dehors de la fonction du composant afin de les déclarer une seule fois et de toujours réutiliser la même référence. Lorsque vous déclarez la fonction à l'intérieur, chaque fois que le composant est rendu, la fonction sera de nouveau définie.

Dans certains cas, vous devrez définir une fonction à l'intérieur du composant pour, par exemple, l'affecter en tant que gestionnaire d'événements qui se comporte différemment en fonction des propriétés du composant. Mais vous pouvez toujours définir la fonction à l'extérieur Ballet la lier aux propriétés, ce qui rend le code beaucoup plus propre et rend les fonctions updateou drawréutilisables.

// You can use update somewhere else
const update (propX, a, b) => { ... };

const Ball = props => (
  <Something onClick={update.bind(null, props.x)} />
);

Si vous utilisez des hooks , vous pouvez utiliser useCallbackpour vous assurer que la fonction n'est redéfinie que lorsque l'une de ses dépendances ( props.xdans ce cas) change:

const Ball = props => {
  const onClick = useCallback((a, b) => {
    // do something with a, b and props.x
  }, [props.x]);

  return (
    <Something onClick={onClick} />
  );
}

C'est la mauvaise façon :

const Ball = props => {
  function update(a, b) {
    // props.x is visible here
  }

  return (
    <Something onClick={update} />
  );
}

Lors de l'utilisation useCallback, la définition de la updatefonction dans le useCallbackhook lui-même notre extérieur du composant devient une décision de conception plus que tout, vous devez prendre en compte si vous allez réutiliser updateet / ou si vous devez accéder à la portée de la fermeture du composant à, par exemple, lecture / écriture de l'état. Personnellement je choisis de le définir à l'intérieur du composant par défaut et de le rendre réutilisable uniquement si le besoin s'en fait sentir, pour éviter une sur-ingénierie dès le départ. En plus de cela, il est préférable de réutiliser la logique de l'application avec des hooks plus spécifiques, laissant les composants à des fins de présentation. La définition de la fonction en dehors du composant lors de l'utilisation de hooks dépend vraiment du degré de découplage de React que vous souhaitez pour la logique de votre application.

Marco Scabbiolo
la source
Merci Marco, ça clarifie un peu les choses. La chose qui me trouble dans mon cas est liée à la this.drawfonction à l'intérieur du fichier Ball. Il utilise le ctxde ce qui serait le parent <canvas>et utilise également le thismot - clé pour ce que serait le Ballcomposant enfant . Quelle serait la meilleure façon d'intégrer l'implémentation du composant sans état afin que ces deux propriétés soient accessibles?
MarksCode
il n'y en a pas thislors de l'utilisation de composants fonctionnels sans état, gardez cela à l'esprit. Pour le contexte du canevas, vous devriez le transmettre à tous Ball, cela ne sonne pas du tout.
Marco Scabbiolo
Donc, dans ce cas, il serait préférable de n'avoir aucun composant enfant que vous dites?
MarksCode
1
@MarcoScabbiolo non non, ce n'est pas mon cas, utilisant déjà les fonctions fléchées de manière native depuis assez longtemps, puisque le seul navigateur qui ne les supporte pas est IE. En fait, j'ai réussi à trouver ce commentaire dans un article où il est en fait affirmé que bindspécifiquement dans Chrome avant 59 était encore plus lent que les fonctions fléchées. Et dans Firefox, cela fait aussi longtemps qu'ils fonctionnent tous les deux à la même vitesse. Donc, je dirais que dans de tels cas, il n'y a aucune différence quelle est la méthode préférée :)
Vadim Kalinin
1
@ MauricioAvendaño fonctionne de toute façon, mais c'est une mauvaise pratique pour le Somethingcomposant de savoir qu'il y a un accessoire X dans son composant parent car il le rend conscient de son contexte. La même chose se produit pour la question que vous posez et l'exemple de code que j'ai écrit, cela dépend du contexte, qui est ignoré par souci de simplicité.
Marco Scabbiolo le
12

Nous pouvons avoir des fonctions à l'intérieur de composants fonctionnels sans état, voici l'exemple:

 const Action = () => {
  function  handlePick(){
     alert("test");
  }
  return (
    <div>
      <input type="button" onClick={handlePick} value="What you want to do ?" />
    </div>
  );
}

Mais ce n'est pas une bonne pratique car la fonction handlePick()sera définie à chaque fois que le composant est appelé.

Ruchir Saxena
la source
11
Si ce n'est pas une bonne pratique, quelle sera la solution alternative?
John Samuel
@JohnSamuel L'alternative est de définir handlePick()au-dessus / en dehors du composant comme function handlePick() { ... }; const Action = () => { ... }le résultat étant que handlePick n'est défini qu'une seule fois. Si vous avez besoin de données du Actioncomposant, vous devez les transmettre en tant que paramètres à la handlePickfonction.
James Hay
11
si ce n'est pas une bonne pratique, vous auriez pu ajouter la bonne pratique avec la réponse? maintenant je dois chercher à nouveau la bonne pratique :(
Shamseer Ahammed
2

Nous pouvons utiliser le hook React useCallbackcomme ci-dessous dans un composant fonctionnel:

const home = (props) => {
    const { small, img } = props
    const [currentInd, setCurrentInd] = useState(0);
    const imgArrayLength = img.length - 1;
    useEffect(() => {
        let id = setInterval(() => {
            if (currentInd < imgArrayLength) {
                setCurrentInd(currentInd => currentInd + 1)
            }
            else {
                setCurrentInd(0)
            }
        }, 5000);
        return () => clearInterval(id);
    }, [currentInd]);
    const onLeftClickHandler = useCallback(
        () => {
            if (currentInd === 0) {

            }
            else {
                setCurrentInd(currentInd => currentInd - 1)
            }
        },
        [currentInd],
    );

    const onRightClickHandler = useCallback(
        () => {
            if (currentInd < imgArrayLength) {
                setCurrentInd(currentInd => currentInd + 1)
            }
            else {

            }
        },
        [currentInd],
    );
    return (
        <Wrapper img={img[currentInd]}>
            <LeftSliderArrow className={currentInd > 0 ? "red" : 'no-red'} onClick={onLeftClickHandler}>
                <img src={Icon_dir + "chevron_left_light.png"}></img>
            </LeftSliderArrow>
            <RightSliderArrow className={currentInd < imgArrayLength ? "red" : 'no-red'} onClick={onRightClickHandler}>
                <img src={Icon_dir + "chevron_right_light.png"}></img>
            </RightSliderArrow>
        </Wrapper>);
}

export default home;

Je reçois «img» de son parent et c'est un tableau.

Vishant Rastogi
la source
0

Si vous souhaitez utiliser des accessoires ou l'état du composant dans la fonction, cela doit être défini dans le composant avec useCallback.

function Component(props){
  const onClick=useCallback(()=>{
     // Do some things with props or state
  },[])
}

D'un autre côté, si vous ne voulez pas utiliser d'accessoires ou d'état dans la fonction, définissez cela en dehors du composant.

const computeSomethings=()=>{
   // Do some things with params or side effects
}

function Component(props){
  
}
Hossein Mohammadi
la source
3
pouvez-vous donner un exemple d'utilisation du crochet pour la méthode à l'intérieur du composant fonctionnel? (méthode non définie)
jake-ferguson