In moderne React-ontwikkeling is die useImperativeHandle-haak 'n kragtige manier om 'n komponent se blootgestelde waarde te verpersoonlik en gee meer beheer oor sy interne metodes en eienskappe. As gevolg hiervan kan meer doeltreffende komponent-API's die produk se buigsaamheid en onderhoubaarheid verbeter.
In hierdie stuk duik die Social Discovery Group- span se insigte in die beste praktyke om useImperativeHandle effektief te gebruik om React-komponente te verbeter.
React verskaf baie hake (die amptelike dokumentasie beskryf 17 hake soos van hierdie skrywe) vir die bestuur van toestand, effekte en interaksies tussen komponente.
Onder hulle is useImperativeHandle 'n nuttige hulpmiddel vir die skep van 'n programmatiese koppelvlak (API) vir kinderkomponente, wat vanaf weergawe 16.8.0 by React gevoeg is.
useImperativeHandle laat jou toe om aan te pas wat sal teruggestuur word deur die ref wat na 'n komponent oorgedra word. Dit werk in tandem met forwardRef, wat toelaat dat 'n ref na 'n kind-komponent deurgegee kan word.
useImperativeHandle(ref, createHandle, [deps]);
Hierdie haak laat eksterne beheer van 'n komponent se gedrag toe, wat nuttig kan wees in sekere situasies, soos om met derdeparty-biblioteke te werk, komplekse animasies of komponente wat direkte toegang tot metodes vereis. Dit moet egter versigtig gebruik word, aangesien dit React se verklarende benadering verbreek.
Kom ons stel ons voor dat ons die DOM van 'n kinderkomponent moet manipuleer. Hier is 'n voorbeeld van hoe om dit te doen deur 'n ref.
import React, { forwardRef, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { // Use forwardRef to pass the ref to the input element return <input ref={ref} {...props} />; }); export default function App() { const inputRef = useRef(); const handleFocus = () => { inputRef.current.focus(); // Directly controlling the input }; const handleClear = () => { inputRef.current.value = ''; // Directly controlling the input value }; return ( <div> <CustomInput ref={inputRef} /> <button onClick={handleFocus}>Focus</button> <button onClick={handleClear}>Clear</button> </div> ); }
En hier is hoe dit bereik kan word met useImperativeHandle.
import React, { useImperativeHandle, forwardRef, useRef } from 'react'; const CustomInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ''; }, })); return <input ref={inputRef} {...props} />; }); export default function App() { const inputRef = useRef(); return ( <div> <CustomInput ref={inputRef} /> <button onClick={inputRef.current.focus}>Focus</button> <button onClick={inputRef.current.clear}>Clear</button> </div> ); }
Soos gesien in die voorbeelde hierbo, wanneer useImperativeHandle gebruik word, voorsien die kind-komponent die ouer van 'n stel metodes wat ons self definieer.
Die gebruik van useImperativeHandle in gevorderde scenario's, soos in voorbeelde met animasie, laat toe om komplekse gedrag binne 'n komponent te isoleer. Dit maak die ouerkomponent eenvoudiger en meer leesbaar, veral wanneer daar met animasie- of klankbiblioteke gewerk word.
import React, { useRef, useState, useImperativeHandle, forwardRef, memo } from "react"; import { Player } from '@lottiefiles/react-lottie-player' import animation from "./animation.json"; const AnimationWithSound = memo( forwardRef((props, ref) => { const [isAnimating, setIsAnimating] = useState(false); const animationRef = useRef(null); const targetDivRef = useRef(null); useImperativeHandle( ref, () => ({ startAnimation: () => { setIsAnimating(true); animationRef.current?.play() updateStyles("start"); }, stopAnimation: () => { animationRef.current?.stop() updateStyles("stop"); }, }), [] ); const updateStyles = (action) => { if (typeof window === 'undefined' || !targetDivRef.current) return; if (action === "start") { if (targetDivRef.current.classList.contains(styles.stop)) { targetDivRef.current.classList.remove(styles.stop); } targetDivRef.current.classList.add(styles.start); } else if (action === "stop") { if (targetDivRef.current.classList.contains(styles.start)) { targetDivRef.current.classList.remove(styles.start); } targetDivRef.current.classList.add(styles.stop); } }; return ( <div> <Player src={animation} loop={isAnimating} autoplay={false} style={{width: 200, height: 200}} ref={animationRef} /> <div ref={targetDivRef} className="target-div"> This div changes styles </div> </div> ); }) ); export default function App() { const animationRef = useRef(); const handleStart = () => { animationRef.current.startAnimation(); }; const handleStop = () => { animationRef.current.stopAnimation(); }; return ( <div> <h1>Lottie Animation with Sound</h1> <AnimationWithSound ref={animationRef} /> <button onClick={handleStart}>Start Animation</button> <button onClick={handleStop}>Stop Animation</button> </div> ); }
In hierdie voorbeeld gee die kind-komponent die metodes startAnimation en stopAnimation terug, wat komplekse logika binne hulself omsluit.
Die fout is nie altyd dadelik opmerklik nie. Byvoorbeeld, die ouerkomponent kan gereeld rekwisiete verander, en jy kan 'n situasie teëkom waar 'n verouderde metode (met verouderde data) steeds gebruik word.
Voorbeeld fout:
https://use-imperative-handle.levkovich.dev/deps-is-not-correct/wrong
const [count, setCount] = useState(0); const increment = useCallback(() => { console.log("Current count in increment:", count); // Shows old value setCount(count + 1); // Are using the old value of count }, [count]); useImperativeHandle( ref, () => ({ increment, // Link to the old function is used }), [] // Array of dependencies do not include increment function );
Die regte benadering:
const [count, setCount] = useState(0); useImperativeHandle( ref, () => ({ increment, }), [increment] // Array of dependencies include increment function );
2. Ontbrekende afhanklikheidskikking
As die afhanklikheidskikking nie verskaf word nie, sal React aanvaar dat die objek in useImperativeHandle op elke weergawe herskep moet word. Dit kan aansienlike prestasieprobleme veroorsaak, veral as "swaar" berekeninge binne die haak uitgevoer word.
Voorbeeld fout:
useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }); // Array of dependencies is missing
Die regte benadering:
useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }, []); // Array of dependencies is correct
Direkte wysiging van ref.stroom ontwrig React se gedrag. As React probeer om die ref op te dateer, kan dit tot konflikte of onverwagte foute lei.
Voorbeeld fout:
useImperativeHandle(ref, () => { // ref is mutated directly ref.current = { customMethod: () => console.log("Error") }; });
Die regte benadering:
useImperativeHandle(ref, () => ({ customMethod: () => console.log("Correct"), }));
Oproepmetodes wat deur useImperativeHandle van useEffect of gebeurtenishanteerders verskaf word, met die veronderstelling dat die ref reeds beskikbaar is, kan tot foute lei - kyk altyd na die stroom voordat u die metodes daarvan oproep.
Voorbeeld fout:
const increment = useCallback(() => { childRef.current.increment(); }, [])
Die regte benadering:
const increment = useCallback(() => { if (childRef.current?.increment) { childRef.current.increment() } }, [])
As useImperativeHandle metodes terugstuur wat sinchronies van toestand verander (bv. om 'n animasie te begin en terselfdertyd style te wysig), kan dit 'n "gaping" tussen die visuele toestand en interne logika veroorsaak. Verseker konsekwentheid tussen toestand en visuele gedrag, byvoorbeeld deur effekte (useEffect) te gebruik.
Voorbeeld fout:
useImperativeHandle(ref, () => ({ startAnimation: () => { setState("running"); // Animation starts before the state changes lottieRef.current.play(); }, stopAnimation: () => { setState("stopped"); // Animation stops before the state changes lottieRef.current.stop(); }, }));
Die regte benadering:
useEffect(() => { if (state === "running" && lottieRef.current) { lottieRef.current.play(); } else if (state === "stopped" && lottieRef.current) { lottieRef.current.stop(); } }, [state]); // Triggered when the state changes useImperativeHandle( ref, () => ({ startAnimation: () => { setState("running"); }, stopAnimation: () => { setState("stopped"); }, }), [] );
Die gebruik van useImperativeHandle is geregverdig in die volgende situasies:
Beheer van kinderkomponentgedrag: Om byvoorbeeld 'n fokus- of herstelmetode vir 'n komplekse insetkomponent te verskaf.
Versteek implementeringsbesonderhede: Die ouerkomponent ontvang slegs die metodes wat dit benodig, nie die hele ref-voorwerp nie.
Voordat u useImperativeHandle gebruik, vra uself hierdie vrae:
Deur die useImperativeHandle-haak te bemeester, kan React-ontwikkelaars doeltreffender en onderhoubare komponente skep deur metodes selektief bloot te stel. Die tegnieke wat deur die Social Discovery Group- span uiteengesit is, kan ontwikkelaars help om hul buigsaamheid te verbeter, hul komponent-API's vaartbelyn te maak en algehele programprestasie te verbeter.
Geskryf deur Sergey Levkovich, Senior sagteware-ingenieur by Social Discovery Group