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.
Réponses
Trop de publicités?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 :
- 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).
- 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'optionref
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 dansuseLayoutEffect
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.
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);
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.
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 }} />
};
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>
)
}
}