- منتشر شده در
سلام React, سلام useEffect (انشاء الله)
- نویسندگان
- نام
- Imamuzzaki Abu Salam
- https://x.com/ImBIOS_Dev
په دې مقاله کې، زه به تاسو ته وښیم چې څنګه د ری ایکټ په کارولو سره د useEffect په ډیرو مواردو کې ځای په ځای کړئ.
زه د داوود کورسید لخوا "Goodbye, useEffect" ګورم، او دا 🤯 زما ذهن په 😀 ښه لاره کې چاودوي. زه موافق یم چې د useEffect دومره ډیر کارول شوی چې زموږ کوډ کثیف او د ساتلو لپاره سخت کوي. زه د اوږدې مودې لپاره د useEffect کارول، او زه په ناسم ډول د هغې کارولو ګناهګار یم. زه ډاډمن یم چې ری ایکټ ځانګړتیاوې لري چې زما کوډ به پاک او د ساتلو لپاره اسانه کړي.
د 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 ډیکلریټیو
امپریټیو: کله چې یو څه پیښ شي، دا اغیز اجرا کړئ.
ډیکلریټیو: کله چې یو څه پیښ شي، دا به د حالت بدلولو لامل شي او د هغه پورې اړه لري (وابسته توب اری) چې د حالت کومې برخې بدل شوې، دا اغیز باید اجرا شي، مګر یوازې که یو څه شرط ریښتیا وي. او ری ایکټ ممکن دا بیا د هیڅ دلیل همزمان رینډر کولو لپاره اجرا کړي.
تصور 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])
دا د دې کوډ لیکلو لپاره ویره لرونکی دی. سربیره پردې، دا به زموږ په کوډ بیس کې عادي او خراب شي. 😱🤮
اغیزې چیرته ځي؟
ری ایکټ 18 په انسټال کولو کې دوه ځله اغیزې اجرا کوي (په سخت حالت کې). انسټال/اغیز (╯°□°)╯︵ ┻━┻ -> غیر انسټال (مصنوعي)/پاکول ┬─┬ /( º _ º /) -> بیا انسټال/اغیز (╯°□°)╯︵ ┻━┻
ایا دا باید د اجزاو څخه بهر ځای پرځای شي؟ د ډیفالټ useEffect؟ اه ... ګړندي. ھم... 🤔 موږ نشو کولی دا په رینډر کې واچوو ځکه چې دا باید هیڅ جانبي اغیزې نه وي ځکه چې رینډر یوازې د ریاضي معادلې ښیې لاس ته ورته دی. دا باید یوازې د محاسبې نتیجه وي.
د 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>
په بیټا ری ایکټ.js کې عالي معلومات شتون لري. زه سپارښتنه کوم چې دا ولولئ. په ځانګړي ډول "ایا د پیښو هینډلر کولی شي جانبي اغیزې ولري؟" برخه.
په بشپړ ډول! د پیښو هینډلر د جانبي اغیزو لپاره غوره ځای دی.
بله عالي سرچینه چې زه غواړم يادونه وکړم چیرې چې تاسو کولی شئ جانبي اغیزې ولري
په ری ایکټ کې، جانبي اغیزې معمولا د پیښو هینډلرونو دننه دي.
که تاسو ټولې نورې انتخابونه ختم کړي وي او د خپل جانبي اغیز لپاره مناسب د پیښو هینډلر ومومئ، تاسو کولی شئ دا لاهم د خپل اجزا کې د useEffect کولو سره ستاسو بیرته راستنیدلي JSX سره وصل کړئ. دا ری ایکټ ته وايي چې دا وروسته، د رینډر کولو وروسته اجرا کړي، کله چې جانبي اغیزې اجازه ولري. په هرصورت، دا چلند باید ستاسو وروستی انتخاب وي.
"اغیزې د رینډر کولو څخه بهر پیښیږي" - داوود کورسید.
(state) => UI
(state, event) => nextState // 🤔 Effects?
UI د حالت یوه فنکشن ده. ځکه چې ټول اوسني حالتونه رینډر شوي، نو دا به اوسني UI تولید کړي. په ورته ډول، کله چې یو پیښه رامنځته شي، نو دا به یو نوی حالت رامنځته کړي. او کله چې حالت بدل شي، نو دا به یو نوی UI جوړ کړي. دا پیرامیډ د ری ایکټ اساس دی.
اغیزې کله پیښیږي؟
د حالت بدلونونه. تل.
(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 کارولو لپاره ځکه چې موږ نه پوهیږو چې ری ایکټ څخه یو جوړ شوی 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 ➡️ د پیښو هینډلر
❌ غلطه لاره:
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
}
})
// ...
}
💽 د ریمیکس لاره:
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() د ری ایکټ یو نوی فعالیت دی چې په مفهوم کې د await سره ورته ژمنه مني. use() د اجزاو، هوکونو او Suspense سره مطابقت لرونکي طریقه کې د فنکشن لخوا بیرته راستنیدلي ژمنه اداره کوي. د 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>
)
}
د useEffect ستونزې په ترلاسه کولو کې
🏃♂️ د ریس شرایط
🔙 هیڅ فوري بیک بټن
🔍 هیڅ SSR یا لومړنی HTML محتوا
🌊 د اوبو تړاو تعقیب
- ریډیټ، ډان ابراموف
پایله
د ډیټا ترلاسه کولو څخه تر د امپریټیو APIs سره مبارزې پورې، جانبي اغیزې د ویب ایپ پراختیا کې د نا امیدی اصلي سرچینو څخه یو دي. او راځئ چې صادق واوسو، هرڅه په useEffect هوکونو کې ځای پرځای کول یوازې لږ مرسته کوي. مننه، د جانبي اغیزو لپاره یوه ساینس (ښه، ریاضی) شتون لري، په حالت ماشینونو او د حالت چارټونو کې رسمي شوې، چې کولی شي موږ ته په بصری ډول د اغیزو تنظیم کولو څرنګوالي په اړه مدل او پوه شي، پرته لدې چې دوی څومره پیچلي وي.