इस लेख में, मैं आपको दिखाऊंगा कि अधिकांश मामलों में उपयोग प्रभाव को बदलने के लिए रिएक्ट का उपयोग कैसे करें।
मैं डेविड खुर्सिड द्वारा "अलविदा, उपयोग प्रभाव" देख रहा हूं, और यह मेरे दिमाग को एक अच्छे तरीके से उड़ा देता है। मैं मानता हूं कि useEffect का इतना अधिक उपयोग किया गया है कि यह हमारे कोड को गंदा और बनाए रखने में कठिन बना देता है। मैं लंबे समय से useEffect का उपयोग कर रहा हूँ, और मैं इसका दुरुपयोग करने का दोषी हूँ। मुझे यकीन है कि रिएक्ट में ऐसी विशेषताएं हैं जो मेरे कोड को साफ और बनाए रखने में आसान बनाती हैं।
useEffect एक हुक है जो हमें फ़ंक्शन घटकों में साइड इफेक्ट करने की अनुमति देता है। यह एक एपीआई में कंपोनेंटडिडमाउंट, कंपोनेंटडिडअपडेट और कंपोनेंटविलअनमाउंट को जोड़ती है। यह एक सम्मोहक हुक है जो हमें बहुत कुछ करने में सक्षम करेगा। लेकिन यह एक बहुत ही खतरनाक हुक भी है जो बहुत सारे कीड़े पैदा कर सकता है।
आइए निम्नलिखित उदाहरण देखें:
import React, { useEffect } from 'react' const Counter = () => { const [count, setCount] = useState(0) useEffect(() => { const interval = setInterval(() => { setCount((c) => c + 1) }, 1000) return () => clearInterval(interval) }, []) return <div>{count}</div> }
यह एक साधारण काउंटर है जो हर सेकंड बढ़ता है। यह एक अंतराल सेट करने के लिए useEffect का उपयोग करता है। यह घटक के अनमाउंट होने पर अंतराल को साफ़ करने के लिए उपयोग प्रभाव का भी उपयोग करता है। उपरोक्त कोड स्निपेट उपयोग प्रभाव के लिए व्यापक उपयोग का मामला है।
यह सीधा उदाहरण है, लेकिन यह एक भयानक उदाहरण भी है।
इस उदाहरण के साथ समस्या यह है कि हर बार जब घटक फिर से प्रस्तुत करता है तो अंतराल सेट होता है। यदि घटक किसी भी कारण से फिर से प्रस्तुत होता है, तो अंतराल फिर से सेट हो जाएगा। अंतराल प्रति सेकंड दो बार कहा जाएगा। यह इस सरल उदाहरण के साथ कोई समस्या नहीं है, लेकिन अंतराल अधिक जटिल होने पर यह एक बड़ी समस्या हो सकती है। यह मेमोरी लीक का कारण भी बन सकता है।
इसे कैसे जोड़ेंगे?
इस समस्या को ठीक करने के कई तरीके हैं। अंतराल को स्टोर करने के लिए useRef का उपयोग करने का एक तरीका है।
import React, { useEffect, useRef } from 'react' const Counter = () => { const [count, setCount] = useState(0) const intervalRef = useRef() useEffect(() => { intervalRef.current = setInterval(() => { setCount((c) => c + 1) }, 1000) return () => clearInterval(intervalRef.current) }, []) return <div>{count}</div> }
उपरोक्त कोड पिछले उदाहरण की तुलना में बहुत बेहतर है। यह हर बार घटक के फिर से रेंडर होने पर अंतराल सेट नहीं करता है। लेकिन अभी इसमें सुधार की जरूरत है। यह अभी भी थोड़ा जटिल है। और यह अभी भी useEffect का उपयोग करता है, जो एक बहुत ही खतरनाक हुक है।
जैसा कि हम useEffect के बारे में जानते हैं, यह एक ही API में कंपोनेंटडिडमाउंट, कंपोनेंटडिडअपडेट और कंपोनेंटविलअनमाउंट को जोड़ती है। आइए इसके कुछ उदाहरण देते हैं:
useEffect(() => { // componentDidMount? }, [])
useEffect(() => { // componentDidUpdate? }, [something, anotherThing])
useEffect(() => { return () => { // componentWillUnmount? } }, [])
इसे समझना आसान है। useEffect का उपयोग साइड इफेक्ट करने के लिए किया जाता है जब घटक माउंट, अपडेट और अनमाउंट होता है। लेकिन इसका उपयोग न केवल साइड इफेक्ट करने के लिए किया जाता है। जब घटक फिर से प्रस्तुत होता है तो इसका उपयोग साइड इफेक्ट करने के लिए भी किया जाता है। जब घटक फिर से प्रस्तुत होता है तो साइड इफेक्ट करना एक अच्छा विचार नहीं है। यह बहुत सारे कीड़े पैदा कर सकता है। साइड इफेक्ट करने के लिए अन्य हुक का उपयोग करना बेहतर होता है जब घटक फिर से प्रस्तुत होता है।
useEffect एक जीवनचक्र हुक नहीं है।
import React, { useState, useEffect } from 'react' const Example = () => { const [value, setValue] = useState('') const [count, setCount] = useState(-1) useEffect(() => { setCount(count + 1) }) const onChange = ({ target }) => setValue(target.value) return ( <div> <input type="text" value={value} onChange={onChange} /> <div>Number of changes: {count}</div> </div> ) }
useEffect एक राज्य सेटर नहीं है
import React, { useState, useEffect } from 'react' const Example = () => { const [count, setCount] = useState(0) // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times` }) // <-- this is the problem, 😱 it's missing the dependency array return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ) }
मैं इस दस्तावेज को पढ़ने की सलाह देता हूं:https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
अनिवार्य : जब कुछ होता है, इस प्रभाव को क्रियान्वित करें।
घोषणात्मक : जब कुछ होता है, तो यह राज्य को बदलने और निर्भर करता है (निर्भरता सरणी) राज्य के किन हिस्सों में बदल जाता है, इस प्रभाव को निष्पादित किया जाना चाहिए, लेकिन केवल तभी जब कुछ शर्त सत्य हो। और रिएक्ट इसे बिना किसी कारण के समवर्ती प्रतिपादन के लिए फिर से निष्पादित कर सकता है।
संकल्पना :
useEffect(() => { doSomething() return () => cleanup() }, [whenThisChanges])
कार्यान्वयन :
useEffect(() => { if (foo && bar && (baz || quo)) { doSomething() } else { doSomethingElse() } // oops, I forgot the cleanup }, [foo, bar, baz, quo])
वास्तविक दुनिया का कार्यान्वयन :
useEffect(() => { if (isOpen && component && containerElRef.current) { if (React.isValidElement(component)) { ionContext.addOverlay(overlayId, component, containerElRef.current!); } else { const element = createElement(component as React.ComponentClass, componentProps); ionContext.addOverlay(overlayId, element, containerElRef.current!); } } }, [component, containerElRef.current, isOpen, componentProps]);
useEffect(() => { if (removingValue && !hasValue && cssDisplayFlex) { setCssDisplayFlex(false) } setRemovingValue(false) }, [removingValue, hasValue, cssDisplayFlex])
यह कोड लिखना डरावना है। इसके अलावा, यह हमारे कोडबेस में सामान्य और गड़बड़ होगा। 😱🤮
प्रतिक्रिया 18 माउंट पर दो बार प्रभाव डालता है (सख्त मोड में)। माउंट/प्रभाव (╯°□°)╯︵ ┻━┻ -> अनमाउंट (नकली)/क्लीनअप ┬─┬ /( º _ º /) -> रिमाउंट/प्रभाव (╯°□°)╯︵ ┻━┻
क्या इसे घटक के बाहर रखा जाना चाहिए? डिफ़ॉल्ट उपयोग प्रभाव? उह... अजीब। हम्म... 🤔 हम इसे रेंडर में नहीं डाल सके क्योंकि इसका कोई साइड-इफेक्ट नहीं होना चाहिए क्योंकि रेंडर गणित के समीकरण के दाहिने हाथ की तरह होता है। यह केवल गणना का परिणाम होना चाहिए।
तादात्म्य
useEffect(() => { const sub = createThing(input).subscribe((value) => { // do something with value }) return sub.unsubscribe }, [input])
useEffect(() => { const handler = (event) => { setPointer({ x: event.clientX, y: event.clientY }) } elRef.current.addEventListener('pointermove', handler) return () => { elRef.current.removeEventListener('pointermove', handler) } }, [])
Fire-and-forget Synchronized (Action effects) (Activity effects) 0 ---------------------- ----------------- - - - oo | A | oo | A | A oo | | | oo | | | | oo | | | oo | | | | oo | | | oo | | | | oo | | | oo | | | | oo | | | oo | | | | oo V | V oo V | V | o--------------------------------------------------------------------------------> Unmount Remount
इवेंट हैंडलर। प्रकार।
<form onSubmit={(event) => { // 💥 side-effect! submitData(event) }} > {/* ... */} </form>
बीटा React.js में उत्कृष्ट जानकारी है। मैं इसे पढ़ने की सलाह देता हूं। विशेष रूप से "क्या ईवेंट संचालकों के दुष्प्रभाव हो सकते हैं?" भाग ।
बिल्कुल! <u>ईवेंट हैंडलर साइड इफेक्ट के लिए सबसे अच्छी जगह हैं।</u>
एक और बढ़िया संसाधन जिसका मैं उल्लेख करना चाहता हूं वह है जहां आप दुष्प्रभाव पैदा कर सकते हैं
रिएक्ट में, <u>साइड इफेक्ट आमतौर पर ईवेंट हैंडलर्स के अंदर होते हैं।</u>
यदि आप अन्य सभी विकल्पों को समाप्त कर चुके हैं और अपने साइड इफेक्ट के लिए सही ईवेंट हैंडलर नहीं ढूंढ पा रहे हैं, तो भी आप इसे अपने कंपोनेंट में <u>useEffect</u> कॉल के साथ अपने लौटाए गए JSX से जोड़ सकते हैं। यह प्रतिक्रिया को बाद में निष्पादित करने के लिए कहता है, प्रतिपादन के बाद, जब साइड इफेक्ट की अनुमति होती है। <u> हालांकि, यह दृष्टिकोण आपका अंतिम उपाय होना चाहिए। </यू>
"प्रभाव रेंडरिंग के बाहर होते हैं" - डेविड खुर्सिड।
(state) => UI (state, event) => nextState // 🤔 Effects?
यूआई राज्य का एक कार्य है। जैसा कि सभी मौजूदा राज्यों का प्रतिपादन किया गया है, यह वर्तमान यूआई का उत्पादन करेगा। इसी तरह, जब कोई घटना घटती है, तो यह एक नया राज्य बनाती है। और जब राज्य बदलता है, तो यह एक नया यूआई बनाएगा। यह प्रतिमान रिएक्ट का मूल है।
मध्यस्थ? 🕵️ कॉलबैक? 🤙 सागस? 🧙♂️ प्रतिक्रियाएं? 🧪 डूब गया? 🚰 मोनाड्स(?) 🧙♂️ जब भी? 🤷♂️
राज्य संक्रमण। हमेशा।
(state, event) => nextState | V (state, event) => (nextState, effect) // Here
क्रिया प्रभाव कहाँ जाते हैं? इवेंट हैंडलर। राज्य संक्रमण।
जो एक ही समय में इवेंट हैंडलर के रूप में निष्पादित होते हैं।
हम useEffect का उपयोग कर सकते हैं क्योंकि हम नहीं जानते कि पहले से ही React का एक बिल्ट-इन API है जो इस समस्या को हल कर सकता है।
इस विषय के बारे में पढ़ने के लिए यहां एक उत्कृष्ट संसाधन है: आपको प्रभाव की आवश्यकता नहीं हो सकती है
useEffect ➡️ useMemo (भले ही हमें ज्यादातर मामलों में useMemo की आवश्यकता नहीं है)
const Cart = () => { const [items, setItems] = useState([]) const [total, setTotal] = useState(0) useEffect(() => { setTotal(items.reduce((total, item) => total + item.price, 0)) }, [items]) // ... }
🧐फिर से ध्यान से पढ़िए और सोचिए।
const Cart = () => { const [items, setItems] = useState([]) const total = useMemo(() => { return items.reduce((total, item) => total + item.price, 0) }, [items]) // ... }
कुल योग की गणना करने के लिए useEffect
का उपयोग करने के बजाय, हम कुल योग को याद करने के लिए useMemo
का उपयोग कर सकते हैं। यहां तक कि अगर वेरिएबल एक महंगी गणना नहीं है, तो हमें इसे याद रखने के लिए useMemo
का उपयोग करने की आवश्यकता नहीं है क्योंकि हम मूल रूप से मेमोरी के लिए व्यापार प्रदर्शन कर रहे हैं।
जब भी हम useEffect
को setState
में देखते हैं, यह एक चेतावनी संकेत है कि हम इसे सरल बना सकते हैं।
उपयोग प्रभाव ➡️ सिंक बाहरी स्टोर का उपयोग करें
❌गलत तरीका:
const Store = () => { const [isConnected, setIsConnected] = useState(true) useEffect(() => { const sub = storeApi.subscribe(({ status }) => { setIsConnected(status === 'connected') }) return () => { sub.unsubscribe() } }, []) // ... }
✅ सबसे अच्छा तरीका:
const Store = () => { const isConnected = useSyncExternalStore( // 👇 subscribe storeApi.subscribe, // 👇 get snapshot () => storeApi.getStatus() === 'connected', // 👇 get server snapshot true ) // ... }
useEffect ➡️ eventHandler
❌गलत तरीका:
const ChildProduct = ({ onOpen, onClose }) => { const [isOpen, setIsOpen] = useState(false) useEffect(() => { if (isOpen) { onOpen() } else { onClose() } }, [isOpen]) return ( <div> <button onClick={() => { setIsOpen(!isOpen) }} > Toggle quick view </button> </div> ) }
📈 बेहतर तरीका:
const ChildProduct = ({ onOpen, onClose }) => { const [isOpen, setIsOpen] = useState(false) const handleToggle = () => { const nextIsOpen = !isOpen; setIsOpen(nextIsOpen) if (nextIsOpen) { onOpen() } else { onClose() } } return ( <div> <button onClick={} > Toggle quick view </button> </div> ) }
कस्टम हुक बनाने का सबसे अच्छा तरीका है:
const useToggle({ onOpen, onClose }) => { const [isOpen, setIsOpen] = useState(false) const handleToggle = () => { const nextIsOpen = !isOpen setIsOpen(nextIsOpen) if (nextIsOpen) { onOpen() } else { onClose() } } return [isOpen, handleToggle] } const ChildProduct = ({ onOpen, onClose }) => { const [isOpen, handleToggle] = useToggle({ onOpen, onClose }) return ( <div> <button onClick={handleToggle} > Toggle quick view </button> </div> ) }
यूज इफेक्ट ➡️ JustCallIt
❌गलत तरीका:
const Store = () => { useEffect(() => { storeApi.authenticate() // 👈 This will run twice! }, []) // ... }
🔨 आइए इसे ठीक करें:
const Store = () => { const didAuthenticateRef = useRef() useEffect(() => { if (didAuthenticateRef.current) return storeApi.authenticate() didAuthenticateRef.current = true }, []) // ... }
➿ दूसरा तरीका:
let didAuthenticate = false const Store = () => { useEffect(() => { if (didAuthenticate) return storeApi.authenticate() didAuthenticate = true }, []) // ... }
🤔 कैसे अगर:
storeApi.authenticate() const Store = () => { // ... }
🍷 एसएसआर, हुह?
if (typeof window !== 'undefined') { storeApi.authenticate() } const Store = () => { // ... }
🧪 परीक्षण?
const renderApp = () => { if (typeof window !== 'undefined') { storeApi.authenticate() } appRoot.render(<Store />) }
जरूरी नहीं कि हमें हर चीज को एक कंपोनेंट के अंदर रखने की जरूरत है।
यूज़ इफेक्ट ➡️ रेंडर एज़ यू फ़ेच (एसएसआर) या यूज़ एसडब्ल्यूआर (सीएसआर)
❌गलत तरीका:
const Store = () => { const [items, setItems] = useState([]) useEffect(() => { let isCanceled = false getItems().then((data) => { if (isCanceled) return setItems(data) }) return () => { isCanceled = true } }) // ... }
💽 रीमिक्स तरीका:
import { useLoaderData } from '@renix-run/react' import { json } from '@remix-run/node' import { getItems } from './storeApi' export const loader = async () => { const items = await getItems() return json(items) } const Store = () => { const items = useLoaderData() // ... } export default Store
⏭️🧹 Next.js (appDir) सर्वर कंपोनेंट तरीके से async/प्रतीक्षा के साथ:
// app/page.tsx async function getData() { const res = await fetch('https://api.example.com/...') // The return value is *not* serialized // You can return Date, Map, Set, etc. // Recommendation: handle errors if (!res.ok) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } return res.json() } export default async function Page() { const data = await getData() return <main></main> }
⏭️💁 Next.js (appDir) क्लाइंट कंपोनेंट तरीके से useSWR के साथ:
// app/page.tsx import useSWR from 'swr' export default function Page() { const { data, error } = useSWR('/api/data', fetcher) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return <div>hello {data}!</div> }
⏭️🧹 Next.js (pageDir) SSR तरीके से:
// pages/index.tsx import { GetServerSideProps } from 'next' export const getServerSideProps: GetServerSideProps = async () => { const res = await fetch('https://api.example.com/...') const data = await res.json() return { props: { data, }, } } export default function Page({ data }) { return <div>hello {data}!</div> }
⏭️💁 नेक्स्ट.जेएस (पेजडीआईआर) सीएसआर तरीके से:
// pages/index.tsx import useSWR from 'swr' export default function Page() { const { data, error } = useSWR('/api/data', fetcher) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return <div>hello {data}!</div> }
🍃 प्रतिक्रिया प्रश्न (एसएसआर तरीका:
import { getItems } from './storeApi' import { useQuery } from 'react-query' const Store = () => { const queryClient = useQueryClient() return ( <button onClick={() => { queryClient.prefetchQuery('items', getItems) }} > See items </button> ) } const Items = () => { const { data, isLoading, isError } = useQuery('items', getItems) // ... }
⁉️ सच में ⁉️
हमें क्या उपयोग करना चाहिए? उपयोग प्रभाव? क्वेरी का उपयोग करें? एसडब्ल्यूआर का उपयोग करें?
या... बस उपयोग करें() 🤔
उपयोग () एक नया रिएक्ट फ़ंक्शन है जो वैचारिक रूप से प्रतीक्षा के समान एक वादा स्वीकार करता है। use() किसी फ़ंक्शन द्वारा लौटाए गए वादे को इस तरह से संभालता है जो घटकों, हुक और सस्पेंस के अनुकूल है। रिएक्ट RFC में उपयोग () के बारे में अधिक जानें।
function Note({ id }) { // This fetches a note asynchronously, but to the component author, it looks // like a synchronous operation. const note = use(fetchNote(id)) return ( <div> <h1>{note.title}</h1> <section>{note.body}</section> </div> ) }
🏃♂️ दौड़ की स्थिति
🔙 कोई तत्काल बैक बटन नहीं
🔍 कोई SSR या प्रारंभिक HTML सामग्री नहीं
🌊 झरने का पीछा करते हुए
- रेडिट, डैन अब्रामोव
डेटा लाने से लेकर अनिवार्य एपीआई से लड़ने तक, साइड इफेक्ट वेब ऐप के विकास में निराशा के सबसे महत्वपूर्ण स्रोतों में से एक हैं। और चलो ईमानदार रहें, सब कुछ उपयोग में लाना प्रभाव हुक केवल थोड़ी मदद करता है। शुक्र है, साइड इफेक्ट्स के लिए एक विज्ञान (अच्छी तरह से, गणित) है, राज्य मशीनों और स्टेटचार्ट्स में औपचारिक रूप से, जो हमें दृष्टिगत रूप से मॉडल बनाने में मदद कर सकता है और यह समझ सकता है कि प्रभावों को कैसे ऑर्केस्ट्रेट करना है, इससे कोई फर्क नहीं पड़ता कि वे घोषणात्मक रूप से कितने जटिल हैं।