paint-brush
Master React՝ նախագծելով արդյունավետ API-ներ useImperativeHandle Hook-ովկողմից@socialdiscoverygroup
Նոր պատմություն

Master React՝ նախագծելով արդյունավետ API-ներ useImperativeHandle Hook-ով

կողմից Social Discovery Group11m2024/12/23
Read on Terminal Reader

Չափազանց երկար; Կարդալ

UseImperativeHandle մանգաղը React-ում թույլ է տալիս ծրագրավորողներին հարմարեցնել բաղադրիչի կողմից բացահայտված մեթոդներն ու հատկությունները՝ բարձրացնելով ճկունությունն ու պահպանելիությունը: Այն աշխատում է forwardRef-ի հետ՝ երեխայի բաղադրիչների համար ծրագրային ինտերֆեյս ապահովելու համար՝ հնարավորություն տալով անմիջականորեն վերահսկել նրանց վարքագիծը: Լավագույն փորձը ներառում է երեխայի տրամաբանության մեկուսացումը, երրորդ կողմի գրադարանների հետ ինտեգրման պարզեցումը և սովորական որոգայթներից խուսափելը, ինչպիսիք են սխալ կախվածության զանգվածները: Արդյունավետ օգտագործելով այս կեռիկը, մշակողները կարող են ստեղծել ավելի արդյունավետ բաղադրիչներ և բարելավել հավելվածի ընդհանուր կատարումը:
featured image - Master React՝ նախագծելով արդյունավետ API-ներ useImperativeHandle Hook-ով
Social Discovery Group HackerNoon profile picture
0-item
1-item

Ժամանակակից React-ի մշակման մեջ useImperativeHandle կեռիկը բաղադրիչի բացված արժեքը անհատականացնելու հզոր միջոց է և ավելի շատ վերահսկողություն է տալիս դրա ներքին մեթոդների և հատկությունների վրա: Արդյունքում, ավելի արդյունավետ Component API-ները կարող են բարելավել արտադրանքի ճկունությունն ու սպասունակությունը:


Այս հատվածում Social Discovery Group-ի թիմի պատկերացումներն ուսումնասիրում են React բաղադրիչները բարելավելու համար useImperativeHandle-ն արդյունավետ օգտագործելու լավագույն փորձը:


React-ը տրամադրում է բազմաթիվ կեռիկներ (պաշտոնական փաստաթղթերը նկարագրում են 17 կեռիկներ այս գրելու պահի դրությամբ) բաղադրիչների վիճակի, էֆեկտների և փոխազդեցությունների կառավարման համար:


Դրանց թվում useImperativeHandle-ը օգտակար գործիք է մանկական բաղադրիչների համար ծրագրային ինտերֆեյս (API) ստեղծելու համար, որն ավելացվել է React-ին 16.8.0 տարբերակից սկսած:


useImperativeHandle-ը թույլ է տալիս հարմարեցնել այն, ինչ կվերադարձվի բաղադրիչին փոխանցված ռեֆերատի կողմից: Այն աշխատում է forwardRef-ի հետ միասին, որը թույլ է տալիս ռեֆերատը փոխանցել երեխայի բաղադրիչին:


 useImperativeHandle(ref, createHandle, [deps]);
  • ref - բաղադրիչին փոխանցված հղում:
  • createHandle — ֆունկցիա, որը վերադարձնում է օբյեկտ, որը հասանելի կդառնա ref-ի միջոցով:
  • 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-ի առավելությունները՝ համեմատած ref


  1. Մեկուսացնում է երեխայի բաղադրիչի տրամաբանությունը. թույլ է տալիս տրամադրել մայր բաղադրիչներ միայն պահանջվող մեթոդներով:
  2. Պարզեցնում է ինտեգրումը. հեշտացնում է React բաղադրիչների ինտեգրումը գրադարանների հետ, որոնք պահանջում են ուղղակի DOM մուտք, օրինակ՝ Lottie կամ Three.js:

Ընդլայնված սցենարներ

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 մեթոդները, որոնք իրենց մեջ ներառում են բարդ տրամաբանություն:


Ընդհանուր սխալներ և որոգայթներ

1. Սխալ լրացված կախվածության զանգված

Սխալը միշտ չէ, որ նկատելի է անմիջապես: Օրինակ, մայր բաղադրիչը կարող է հաճախակի փոխել հենակետերը, և դուք կարող եք հանդիպել մի իրավիճակի, երբ հնացած մեթոդը (հնացած տվյալներով) շարունակվում է օգտագործել:


Օրինակ սխալ.

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 );


Ճիշտ մոտեցում.

https://use-imperative-handle.levkovich.dev/deps-is-not-correct/correct

 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


Ճիշտ մոտեցում.

https://use-imperative-handle.levkovich.dev/deps-empty/correct

 useImperativeHandle(ref, () => { // There is might be a difficult task console.log("useImperativeHandle calculated again"); return { focus: () => {} } }, []); // Array of dependencies is correct


  1. Ուղեկցողի փոփոխում useImperativeHandle-ի ներսում

ref.current-ի ուղղակի փոփոխությունը խաթարում է React-ի վարքագիծը: Եթե React-ը փորձի թարմացնել ref-ը, դա կարող է հանգեցնել կոնֆլիկտների կամ անսպասելի սխալների:


Օրինակ սխալ.

https://use-imperative-handle.levkovich.dev/ref-modification/wrong


 useImperativeHandle(ref, () => { // ref is mutated directly ref.current = { customMethod: () => console.log("Error") }; });

Ճիշտ մոտեցում.

https://use-imperative-handle.levkovich.dev/ref-modification/correct


 useImperativeHandle(ref, () => ({ customMethod: () => console.log("Correct"), }));


  1. Մեթոդների օգտագործումը նախքան դրանց սկզբնավորումը


UseImperativeHandle-ի միջոցով տրվող զանգի մեթոդները useEffect-ի կամ իրադարձությունների մշակողների կողմից, ենթադրելով, որ ռեֆերանսն արդեն հասանելի է, կարող է հանգեցնել սխալների. միշտ ստուգեք ընթացիկը՝ նախքան դրա մեթոդները կանչելը:


Օրինակ սխալ.


https://use-imperative-handle.levkovich.dev/before-init/wrong

 const increment = useCallback(() => { childRef.current.increment(); }, [])


Ճիշտ մոտեցում.

https://use-imperative-handle.levkovich.dev/before-init/correct


 const increment = useCallback(() => { if (childRef.current?.increment) { childRef.current.increment() } }, [])


  1. Անիմացիաների և վիճակի միջև համաժամացման խնդիրներ

Եթե useImperativeHandle-ը վերադարձնում է մեթոդներ, որոնք սինխրոն կերպով փոխում են վիճակը (օրինակ՝ անիմացիան սկսելը և ոճերը միաժամանակ փոփոխելը), դա կարող է առաջացնել «անջատ» տեսողական վիճակի և ներքին տրամաբանության միջև։ Ապահովել վիճակի և տեսողական վարքի միջև հետևողականություն, օրինակ՝ օգտագործելով էֆեկտներ (useEffect):


Օրինակ սխալ.

https://use-imperative-handle.levkovich.dev/state-animation-sync/wrong


 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(); }, }));


Ճիշտ մոտեցում.


https://use-imperative-handle.levkovich.dev/state-animation-sync/correct

 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-ը:


Տիրապետելով useImperativeHandle կեռին, React մշակողները կարող են ստեղծել ավելի արդյունավետ և պահպանվող բաղադրիչներ՝ ընտրողաբար բացահայտելով մեթոդները: Social Discovery Group-ի թիմի կողմից ներկայացված տեխնիկան կարող է օգնել ծրագրավորողներին բարելավել իրենց ճկունությունը, պարզեցնել իրենց բաղադրիչ API-ները և բարելավել հավելվածների ընդհանուր կատարումը:


Գրել է Սերգեյ Լևկովիչը՝ Social Discovery Group-ի ավագ ծրագրային ինժեներ