Ժամանակակից React-ի մշակման մեջ useImperativeHandle կեռիկը բաղադրիչի բացված արժեքը անհատականացնելու հզոր միջոց է և ավելի շատ վերահսկողություն է տալիս դրա ներքին մեթոդների և հատկությունների վրա: Արդյունքում, ավելի արդյունավետ Component API-ները կարող են բարելավել արտադրանքի ճկունությունն ու սպասունակությունը:
Այս հատվածում Social Discovery Group-ի թիմի պատկերացումներն ուսումնասիրում են React բաղադրիչները բարելավելու համար useImperativeHandle-ն արդյունավետ օգտագործելու լավագույն փորձը:
React-ը տրամադրում է բազմաթիվ կեռիկներ (պաշտոնական փաստաթղթերը նկարագրում են 17 կեռիկներ այս գրելու պահի դրությամբ) բաղադրիչների վիճակի, էֆեկտների և փոխազդեցությունների կառավարման համար:
Դրանց թվում useImperativeHandle-ը օգտակար գործիք է մանկական բաղադրիչների համար ծրագրային ինտերֆեյս (API) ստեղծելու համար, որն ավելացվել է React-ին 16.8.0 տարբերակից սկսած:
useImperativeHandle-ը թույլ է տալիս հարմարեցնել այն, ինչ կվերադարձվի բաղադրիչին փոխանցված ռեֆերատի կողմից: Այն աշխատում է forwardRef-ի հետ միասին, որը թույլ է տալիս ռեֆերատը փոխանցել երեխայի բաղադրիչին:
useImperativeHandle(ref, createHandle, [deps]);
Այս կեռիկը թույլ է տալիս արտաքին վերահսկել բաղադրիչի վարքագիծը, որը կարող է օգտակար լինել որոշակի իրավիճակներում, օրինակ՝ աշխատել երրորդ կողմի գրադարանների, բարդ անիմացիաների կամ բաղադրիչների հետ, որոնք պահանջում են ուղղակի մուտք դեպի մեթոդներ: Այնուամենայնիվ, այն պետք է զգույշ օգտագործվի, քանի որ խախտում է React-ի դեկլարատիվ մոտեցումը:
Եկեք պատկերացնենք, որ մենք պետք է շահարկենք երեխայի բաղադրիչի DOM-ը: Ահա մի օրինակ, թե ինչպես դա անել՝ օգտագործելով 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> ); }
Եվ ահա թե ինչպես կարելի է դրան հասնել 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> ); }
Ինչպես երևում է վերը նշված օրինակներում, useImperativeHandle-ն օգտագործելիս երեխայի բաղադրիչը ծնողին տրամադրում է մի շարք մեթոդներ, որոնք մենք ինքներս ենք սահմանում:
UseImperativeHandle-ի օգտագործումը առաջադեմ սցենարներում, ինչպես օրինակ անիմացիայի օրինակներում, թույլ է տալիս մեկուսացնել բարդ վարքագիծը բաղադրիչի ներսում: Սա ծնող բաղադրիչն ավելի պարզ և ընթեռնելի է դարձնում, հատկապես անիմացիայի կամ ձայնադարանների հետ աշխատելիս:
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> ); }
Այս օրինակում երեխա բաղադրիչը վերադարձնում է startAnimation և stopAnimation մեթոդները, որոնք իրենց մեջ ներառում են բարդ տրամաբանություն:
Սխալը միշտ չէ, որ նկատելի է անմիջապես: Օրինակ, մայր բաղադրիչը կարող է հաճախակի փոխել հենակետերը, և դուք կարող եք հանդիպել մի իրավիճակի, երբ հնացած մեթոդը (հնացած տվյալներով) շարունակվում է օգտագործել:
Օրինակ սխալ.
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 );
Ճիշտ մոտեցում.
const [count, setCount] = useState(0); useImperativeHandle( ref, () => ({ increment, }), [increment] // Array of dependencies include increment function );
2. Բացակայում է կախվածության զանգվածը
Եթե կախվածության զանգվածը տրամադրված չէ, React-ը կենթադրի, որ useImperativeHandle-ի օբյեկտը պետք է վերստեղծվի յուրաքանչյուր արտապատկերման վրա: Սա կարող է առաջացնել զգալի կատարողական խնդիրներ, հատկապես, եթե «ծանր» հաշվարկները կատարվում են կեռիկի ներսում:
Օրինակ սխալ.
useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }); // Array of dependencies is missing
Ճիշտ մոտեցում.
useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }, []); // Array of dependencies is correct
ref.current-ի ուղղակի փոփոխությունը խաթարում է React-ի վարքագիծը: Եթե React-ը փորձի թարմացնել ref-ը, դա կարող է հանգեցնել կոնֆլիկտների կամ անսպասելի սխալների:
Օրինակ սխալ.
useImperativeHandle(ref, () => { // ref is mutated directly ref.current = { customMethod: () => console.log("Error") }; });
Ճիշտ մոտեցում.
useImperativeHandle(ref, () => ({ customMethod: () => console.log("Correct"), }));
UseImperativeHandle-ի միջոցով տրվող զանգի մեթոդները useEffect-ի կամ իրադարձությունների մշակողների կողմից, ենթադրելով, որ ռեֆերանսն արդեն հասանելի է, կարող է հանգեցնել սխալների. միշտ ստուգեք ընթացիկը՝ նախքան դրա մեթոդները կանչելը:
Օրինակ սխալ.
const increment = useCallback(() => { childRef.current.increment(); }, [])
Ճիշտ մոտեցում.
const increment = useCallback(() => { if (childRef.current?.increment) { childRef.current.increment() } }, [])
Եթե useImperativeHandle-ը վերադարձնում է մեթոդներ, որոնք սինխրոն կերպով փոխում են վիճակը (օրինակ՝ անիմացիան սկսելը և ոճերը միաժամանակ փոփոխելը), դա կարող է առաջացնել «անջատ» տեսողական վիճակի և ներքին տրամաբանության միջև։ Ապահովել վիճակի և տեսողական վարքի միջև հետևողականություն, օրինակ՝ օգտագործելով էֆեկտներ (useEffect):
Օրինակ սխալ.
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(); }, }));
Ճիշտ մոտեցում.
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"); }, }), [] );
UseImperativeHandle-ի օգտագործումը արդարացված է հետևյալ իրավիճակներում.
Երեխայի բաղադրիչի վարքագծի վերահսկում. Օրինակ՝ տրամադրել ֆոկուս կամ վերակայման մեթոդ բարդ մուտքային բաղադրիչի համար:
Թաքցնել իրականացման մանրամասները. մայր բաղադրիչը ստանում է միայն իրեն անհրաժեշտ մեթոդները, այլ ոչ թե ամբողջ ref օբյեկտը:
Նախքան useImperativeHandle-ն օգտագործելը, ինքներդ ձեզ տվեք հետևյալ հարցերը.
Տիրապետելով useImperativeHandle կեռին, React մշակողները կարող են ստեղծել ավելի արդյունավետ և պահպանվող բաղադրիչներ՝ ընտրողաբար բացահայտելով մեթոդները: Social Discovery Group-ի թիմի կողմից ներկայացված տեխնիկան կարող է օգնել ծրագրավորողներին բարելավել իրենց ճկունությունը, պարզեցնել իրենց բաղադրիչ API-ները և բարելավել հավելվածների ընդհանուր կատարումը:
Գրել է Սերգեյ Լևկովիչը՝ Social Discovery Group-ի ավագ ծրագրային ինժեներ