109 votes

Quand utiliser useImperativeHandle, useLayoutEffect et useDebugValue ?

Je ne comprends pas pourquoi useImperativeHandle , useLayoutEffect et useDebugValue Les crochets sont nécessaires, pouvez-vous donner des exemples où ils peuvent être utilisés, mais pas des exemples de la documentation s'il vous plaît.

178voto

Chris Points 3970

Permettez-moi de préfacer cette réponse en affirmant que tous ces crochets sont très rarement utilisés. Dans 99 % des cas, vous n'en aurez pas besoin. Ils sont uniquement destinés à couvrir quelques rares cas de figure.


useImperativeHandle

Habituellement, lorsque vous utilisez useRef on vous donne la valeur d'instance du composant que la ref est attaché. Cela vous permet d'interagir directement avec l'élément DOM.

useImperativeHandle est très similaire, mais il vous permet de faire deux choses :

  1. Il vous permet de contrôler la valeur qui est renvoyée. Au lieu de renvoyer l'élément d'instance, vous indiquez explicitement quelle sera la valeur de retour (voir l'extrait ci-dessous).
  2. Il vous permet de remplacer les fonctions natives (telles que blur , focus ) avec des fonctions qui vous sont propres, permettant ainsi des effets secondaires au comportement normal, ou un comportement tout à fait différent. Cependant, vous pouvez appeler la fonction comme vous le souhaitez.

Vous pouvez avoir de nombreuses raisons de vouloir faire l'une ou l'autre de ces choses ; vous ne voulez peut-être pas exposer les propriétés natives au parent ou vous voulez peut-être changer le comportement d'une fonction native. Les raisons peuvent être nombreuses. Cependant, useImperativeHandle est rarement utilisé.

useImperativeHandle personnalise la valeur d'instance qui est exposée aux composants parents lors de l'utilisation de l'option ref

Exemple

Dans cet exemple, la valeur que nous obtiendrons de l'option ref ne contiendra que la fonction blur que nous avons déclaré dans notre useImperativeHandle . Il ne contiendra pas d'autres propriétés ( J'enregistre la valeur pour démontrer ceci ). La fonction elle-même est également "personnalisée" pour se comporter différemment de ce que vous attendez normalement. Ici, elle définit document.title et brouille l'entrée lorsque blur est invoquée.

const MyInput = React.forwardRef((props, ref) => {
  const [val, setVal] = React.useState('');
  const inputRef = React.useRef();

  React.useImperativeHandle(ref, () => ({
    blur: () => {
      document.title = val;
      inputRef.current.blur();
    }
  }));

  return (
    <input
      ref={inputRef}
      val={val}
      onChange={e => setVal(e.target.value)}
      {...props}
    />
  );
});

const App = () => {
  const ref = React.useRef(null);
  const onBlur = () => {
    console.log(ref.current); // Only contains one property!
    ref.current.blur();
  };

  return <MyInput ref={ref} onBlur={onBlur} />;
};

ReactDOM.render(<App />, document.getElementById("app"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useLayoutEffect

Bien que similaire dans une certaine mesure à useEffect() Il diffère en ce qu'il s'exécute après que React ait effectué les mises à jour du DOM. Utilisé dans de rares cas où vous devez calculer la distance entre les éléments après une mise à jour ou faire d'autres calculs post-mise à jour / effets secondaires.

La signature est identique à useEffect mais il se déclenche de manière synchrone après toutes les mutations du DOM. Utilisez cette fonction pour lire la mise en page du DOM et effectuer un nouveau rendu de manière synchrone. Les mises à jour programmées dans useLayoutEffect sera vidée de manière synchrone, avant que le navigateur ait la possibilité de peindre .

Exemple

Supposons que vous ayez un élément positionné de manière absolue dont la hauteur peut varier et que vous souhaitiez positionner un autre élément de type div en dessous. Vous pouvez utiliser getBoundingCLientRect() pour calculer les propriétés height et top du parent, puis les appliquer à la propriété top de l'enfant.

Ici, vous voudriez utiliser useLayoutEffect plutôt que useEffect . Voyez pourquoi dans les exemples ci-dessous :

Avec useEffect : (remarquez le comportement sautillant)

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Avec useLayoutEffect :

const Message = ({boxRef, children}) => {
  const msgRef = React.useRef(null);
  React.useLayoutEffect(() => {
    const rect = boxRef.current.getBoundingClientRect();
    msgRef.current.style.top = `${rect.height + rect.top}px`;
  }, []);

  return <span ref={msgRef} className="msg">{children}</span>;
};

const App = () => {
  const [show, setShow] = React.useState(false);
  const boxRef = React.useRef(null);

  return (
    <div>
      <div ref={boxRef} className="box" onClick={() => setShow(prev => !prev)}>Click me</div>
      {show && <Message boxRef={boxRef}>Foo bar baz</Message>}
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("app"));

.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background: green;
  color: white;
}

.msg {
  position: relative;
  border: 1px solid red;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>

useDebugValue

Il peut arriver que vous souhaitiez déboguer certaines valeurs ou propriétés, mais cela peut nécessiter des opérations coûteuses susceptibles d'affecter les performances.

useDebugValue n'est appelé que lorsque les React DevTools sont ouverts et que le hook correspondant est inspecté, ce qui évite tout impact sur les performances.

useDebugValue peut être utilisé pour afficher une étiquette pour les crochets personnalisés dans React DevTools.

Je n'ai personnellement jamais utilisé ce crochet. Peut-être que quelqu'un dans les commentaires peut donner un aperçu avec un bon exemple.

15voto

OliverRadini Points 2956

useImperativeHandle

useImperativeHandle vous permet de déterminer les propriétés qui seront exposées sur une référence. Dans l'exemple ci-dessous, nous avons un composant de type bouton, et nous souhaitons exposer la propriété someExposedProperty sur cette référence :

[index.tsx]

import React, { useRef } from "react";
import { render } from "react-dom";
import Button from "./Button";

import "./styles.css";

function App() {
  const buttonRef = useRef(null);

  const handleClick = () => {
    console.log(Object.keys(buttonRef.current)); // ['someExposedProperty']
    console.log("click in index.tsx");
    buttonRef.current.someExposedProperty();
  };

  return (
    <div>
      <Button onClick={handleClick} ref={buttonRef} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

[Button.tsx]

import React, { useRef, useImperativeHandle, forwardRef } from "react";

function Button(props, ref) {
  const buttonRef = useRef();
  useImperativeHandle(ref, () => ({
    someExposedProperty: () => {
      console.log(`we're inside the exposed property function!`);
    }
  }));
  return (
    <button ref={buttonRef} {...props}>
      Button
    </button>
  );
}

export default forwardRef(Button);

Disponible ici.

useLayoutEffect

C'est la même chose que useEffect mais ne se déclenche que lorsque toutes les mutations DOM sont terminées. Cet article De Kent C. Dodds explique la différence aussi bien que quiconque, concernant ces deux, il dit :

99% du temps [ useEffect ] est ce que vous voulez utiliser.

Je n'ai pas vu d'exemples qui illustrent particulièrement bien ce point, et je ne suis pas sûr que je serais capable de créer quoi que ce soit non plus. Il est probablement préférable de dire qu'il faut utiliser uniquement useLayoutEffect quand useEffect a des problèmes.

useDebugValue

J'ai l'impression les docs fait un très bon exemple pour expliquer celui-ci. Si vous avez un hook personnalisé et que vous souhaitez l'étiqueter dans React DevTools, c'est ce que vous devez utiliser.

Si vous avez des problèmes spécifiques à ce sujet, il serait probablement préférable de commenter ou de poser une autre question, parce que j'ai l'impression que tout ce que les gens mettent ici ne fera que répéter la documentation, au moins jusqu'à ce que nous ayons un problème plus spécifique.

7voto

Yamo93 Points 121

Le site useImperativeHandle hook m'a beaucoup aidé dans un de mes cas d'utilisation.

J'ai créé un composant de grille qui utilise un composant de bibliothèque tiers. La bibliothèque elle-même possède une couche de données volumineuses avec une fonctionnalité intégrée qui peut être utilisée en accédant à l'instance de l'élément.

Cependant, dans mon propre composant de grille, je veux l'étendre avec des méthodes qui effectuent des actions sur la grille. En outre, je veux également pouvoir exécuter ces méthodes depuis l'extérieur de mon composant de grille.

Cela est facilement réalisable en ajoutant les méthodes à l'intérieur de l'élément useImperativeHandle et ils seront alors exposés et utilisables par son parent.

Mon composant de grille ressemble à ceci :

import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import ThirdPartyGrid from 'some-library';

export default forwardRef((props, forwardedRef) => {
    const gridRef = useRef(null);
    useImperativeHandle(forwardedRef,
        () => ({

            storeExpandedRecords () {
                // Some code
            },

            restoreExpandedRecords () {
                // Some code
            },

        }));

    return (
        <div ref={forwardedRef}>
            <ThirdPartyGrid 
                ref={gridRef}
                {...props.config}
            />
        </div>
    );
});

Et ensuite dans mon parent, je peux exécuter ces méthodes :

import React, { useRef, useEffect } from 'react';

export default function Parent () {
    const gridRef = useRef(null);

    const storeRecords = () => {
        gridRef.current.storeExpandedRecords();
    };

    useEffect(() => {
        storeRecords();
    }, []);

    return <GridWrapper ref={gridRef} config={{ something: true }} />
};

2voto

useImperativeHandle

généralement, le crochet expose les méthodes et les propriétés de votre composant fonctionnel à d'autres composants en plaçant le composant fonctionnel dans le forwardRef. exemple

const Sidebar=forwardRef((props,ref)=>{

       const [visibility,setVisibility]=useState(null)

       const opensideBar=()=>{
         setVisibility(!visibility)
       }
        useImperativeHandle(ref,()=>({

            opensideBar:()=>{
               set()
            }
        }))

       return(
        <Fragment>
         <button onClick={opensideBar}>SHOW_SIDEBAR</button>
         {visibility==true?(
           <aside className="sidebar">
              <ul className="list-group ">
                <li className=" list-group-item">HOME</li>
                <li className=" list-group-item">ABOUT</li>
                <li className=" list-group-item">SERVICES</li>
                <li className=" list-group-item">CONTACT</li>
                <li className=" list-group-item">GALLERY</li>
             </ul>
          </aside>
         ):null}
        </Fragment>
       )
     }

    //using sidebar component 

class Main extends Component{

     myRef=createRef();

     render(){
      return(
       <Fragment>
         <button onClick={()=>{
                     ///hear we calling sidebar component
                     this.myRef?.current?.opensideBar()
                    }}>
          Show Sidebar
        </button>
        <Sidebar ref={this.myRef}/>
       </Fragment>
      )
     }
 }

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