- خپره شوې پر
سلام React, سلام useEffect (انشاء الله)
- لیکوالان
- نوم
- Imamuzzaki Abu Salam
- https://x.com/ImBIOS_Dev
په دې مقاله کې، زه به تاسو ته وښیم چې څنګه د اکثرو قضیو کې د useEffect ځای د React سره څنګه نیول کېږي.
زه د "Goodbye, useEffect" د David Khoursid لخوا لیدنه کوم، او دا 🤯 زما ذهن 🤯 په 😀 ښه توګه 🤯 ماتوي. زه موافق یم چې useEffect دومره ډیر کارول شوی چې زموږ کوډ کثیف او د ساتلو لپاره ستونزمن کوي. زه د اوږدې مودې لپاره د useEffect کارولې یم، او زه د ناسم کارولو ګناهګار یم. زه ډاډمن یم چې React داسې ځانګړتیاوې لري چې زما کوډ به پاک او د ساتلو لپاره اسانه کړي.
useEffect څه شی دی؟
useEffect یو هوک دی چې موږ ته اجازه راکوي چې په تابعي اجزاو کې د اړخ اغیزو ترسره کولو لپاره. دا د componentDidMount، componentDidUpdate او componentWillUnmount په یو API کې یوځای کوي. دا یو قوي هوک دی چې موږ ته به ډیر کارونه ترسره کولو اجازه راکوي. مګر دا هم یو ډیر خطرناک هوک دی چې کولی شي ډیرې خرابۍ رامنځ ته کړي.
ولې 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 کاروي. دا د اجزاو له ختمیدو وروسته د فاصلې پاکولو لپاره هم د useEffect کاروي. پورته کوډ ټوټه د 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 د اغیزو لپاره نه دی
لکه څنګه چې موږ د useEffect په اړه پوهیږو، دا د componentDidMount، componentDidUpdate او componentWillUnmount په یو API کې یوځای کوي. راځئ چې د هغې ځینې مثالونه ورکړو:
useEffect(() => {
// componentDidMount?
}, [])
useEffect(() => {
// componentDidUpdate?
}, [something, anotherThing])
useEffect(() => {
return () => {
// componentWillUnmount?
}
}, [])
دا د پوهیدو لپاره اسانه دی. 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
امرې vs اعلانې
امرې: کله چې یو څه پیښ شي، دا اغیزه اجرا کړئ.
اعلانې: کله چې یو څه پیښ شي، دا به د حالت بدلولو لامل شي او په کومې برخې کې د حالت بدلون (د انحصار لړۍ) پورې اړه لري، دا اغیزه باید اجرا شي، مګر یوازې که یو شرط ریښتیا وي. او React ممکن دا د هیڅ دلیل پرته د هممهاله رنډر لپاره بیا اجرا کړي.
مفهوم vs پلي کول
مفهوم:
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])
دا د دې کوډ لیکل ډارونکي دي. برسیره پردې، دا به په زموږ د کوډ بېس کې عادي او ګډوډ وي. 😱🤮
اغیزې چیرته ځي؟
React 18 په نصبولو کې دوه ځله اغیزې اجرا کوي (په سخت حالت کې). نصب/اثر (╯°□°)╯︵ ┻━┻ -> ختمول (مصنوعي)/پاکول ┬─┬ /( º _ º /) -> بیا نصب/اثر (╯°□°)╯︵ ┻━┻
ایا دا باید د اجزاو څخه بهر ځای پرځای شي؟ د ډیفالټ useEffect؟ اه... ناخوښه. Hmm... 🤔 موږ نشو کولی دا په رنډر کې واچوو ځکه چې دا باید هلته هیڅ اړخ اغیزه نه وي ځکه چې رنډر یوازې د ریاضي مساوات په څیر دی. دا باید یوازې د محاسبې پایله وي.
useEffect د څه لپاره دی؟
سنکرونایزیشن
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)
}
}, [])
د عمل اغیزې vs د فعالیت اغیزې
Fire-and-forget Synchronized
(Action effects) (Activity effects)
0 ---------------------- ----------------- - - -
o o | A | o o | A | A
o o | | | o o | | | |
o o | | | o o | | | |
o o | | | o o | | | |
o o | | | o o | | | |
o o | | | o o | | | |
o o V | V o o V | V |
o-------------------------------------------------------------------------------->
Unmount Remount
د عمل اغیزې چیرته ځي؟
د پیښې اداره کونکي. ډیر ډیر.
<form
onSubmit={(event) => {
// 💥 side-effect!
submitData(event)
}}
>
{/* ... */}
</form>
په بیټا React.js کې ښه معلومات شتون لري. زه سپارښتنه کوم چې یې ولولئ. په ځانګړې توګه "ایا د پیښې اداره کونکي کولی شي د اړخ اغیزې ولري؟" برخه.
البته! د پیښې اداره کونکي د اړخ اغیزو لپاره غوره ځای دی.
یو بل ښه سرچینه چې زه غواړم یې یادونه وکړم چیرته چې تاسو کولی شئ د اړخ اغیزې رامنځ ته کړئ
په React کې، د اړخ اغیزې معمولا د پیښې اداره کونکو په دننه کې وي.
که تاسو ټولې نورې لارې کارولې وي او د خپلې اړخ اغیزې لپاره سم د پیښې اداره کونکی ومومئ، تاسو لاهم کولی شئ دا ستاسو د اجزا له خوا راستنیدونکي JSX سره د خپل اجزا په دننه کې د useEffect کاله سره وصل کړئ. دا React ته وايي چې وروسته، د رنډر کولو وروسته، دا اجرا کړي کله چې د اړخ اغیزو اجازه وي. په هرصورت، دا چلند باید ستاسو وروستی چلند وي.
"اغیزې د رنډر کولو بهر پیښیږي" - David Khoursid.
(state) => UI
(state, event) => nextState // 🤔 Effects?
UI د حالت یوه وظیفه ده. ځکه چې ټول اوسني حالتونه رنډر شوي، نو دا به اوسني UI تولید کړي. په ورته ډول، کله چې یوه پیښه پیښ شي، نو دا به یو نوی حالت رامنځ ته کړي. او کله چې حالت بدل شي، نو دا به یو نوی UI جوړ کړي. دا نمونه د React اصلي برخه ده.
اغیزې کله پیښیږي؟
د حالت لیږدونه. تل.
(state, event) => nextState
|
V
(state, event) => (nextState, effect) // Here
![د رنډر تصویری تصویر](https://media.slid.es/uploads/174419/images/9663683/CleanShot_2022-06-22_at_20.24.08_2x.png align="left")
د عمل اغیزې چیرته ځي؟ د پیښې اداره کونکي. د حالت لیږدونه.
کوم چې د د پیښې اداره کونکو په ورته وخت کې اجرا کیږي.
ممکن موږ ته د اغیزو اړتیا نه وي
موږ کولی شو د useEffect کارولو ځکه چې موږ نه پوهیږو چې ایا د React څخه یو جوړ شوی API شتون لري چې کولی شي دا ستونزه حل کړي.
دلته د دې موضوع په اړه د لوستلو لپاره یو ښه سرچینه ده: تاسو ممکن د اغیزې اړتیا نه وي
موږ د د معلوماتو بدلولو لپاره د useEffect اړتیا نه لرو.
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
وینو، نو دا د خبردارۍ نښه ده چې موږ کولی شو یې ساده کړو.
useSyncExternalStore
د بهرني ذخیرې سره اغیزې؟useEffect ➡️ useSyncExternalStore
❌ غلطه لاره:
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 اړتیا نه لرو.
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>
)
}
موږ د د نړیوالو واحدونو د پیل کولو لپاره د useEft اړتیا نه لرو.
useEffect ➡️ 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 = () => {
// ...
}
🍷 SSR، هاه؟
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
const Store = () => {
// ...
}
🧪 ازموینه؟
const renderApp = () => {
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
appRoot.render(<Store />)
}
موږ ته اړتیا نه ده چې هرڅه په اجزا کې واچوو.
موږ د د معلوماتو راوړلو لپاره د useEffect اړتیا نه لرو.
useEffect ➡️ renderAsYouFetch (SSR) or useSWR (CSR)
❌ غلطه لاره:
const Store = () => {
const [items, setItems] = useState([])
useEffect(() => {
let isCanceled = false
getItems().then((data) => {
if (isCanceled) return
setItems(data)
})
return () => {
isCanceled = true
}
})
// ...
}
💽 د Remix لاره:
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/await سره په سرور اجزا لاره کې:
// 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 (pagesDir) په 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>
}
⏭️💁 Next.js (pagesDir) په CSR لاره کې:
// 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>
}
🍃 React Query (SSR لاره:
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)
// ...
}
⁉️ واقعاً ⁉️ موږ باید څه وکاروو؟ useEffect? useQuery? useSWR?
یا... یوازې use() 🤔
use() یو نوی React فعالیت دی چې په مفکوره کې د await سره ورته وعده مني. use() د یوې دندې څخه راغلي وعده په هغه ډول اداره کوي چې د اجزاو، هوکونو او Suspense سره مطابقت لري. د use() په اړه د React 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>
)
}
د useEffect په کارولو کې د راوړلو ستونزې
🏃♂️ د ریس شرطونه
🔙 هیڅ فوري بیرته تڼۍ نه
🔍 هیڅ SSR یا لومړني HTML محتوا نه
🌊 د اوبوي ښکار
- Reddit, Dan Abramov
پایله
د معلوماتو راوړلو څخه د امري API سره مبارزې پورې، د اړخ اغیزې د ویب ایپ پراختیا کې د نا امیدی ترټولو لوی سرچینې دي. او راځئ چې صادقانه ووایو، د هرڅه په useEffect هوکونو کې ایښودل یوازې یو څه مرسته کوي. په خوشبینۍ سره، د اړخ اغیزو لپاره یو ساینس (ښه، ریاضي) شتون لري، چې په حالت ماشینونو او حالت چارټونو کې رسمي شوی، کوم چې کولی شي موږ ته په بصري توګه د اغیزو د تنظیم کولو طریقه نمونه او پوه شي، هیڅ فرق نه کوي چې دوی څومره پیچلي دي.