- Yayımlanma tarixi
Salam React, Salam useEffect (İnşallah)
- Müəllif(lər)
- Ad
- Imamuzzaki Abu Salam
- https://x.com/ImBIOS_Dev
Bu məqalədə sizə React-in əksər hallarda useEffect-i necə əvəz edəcəyini göstərəcəyəm.
David Khoursid tərəfindən "Vida, useEffect" adlı videoya baxırdım və bu, 🤯 məni 😀 yaxşı mənada heyrətə gətirdi. Mən də razıyam ki, useEffect o qədər çox istifadə olunur ki, kodumuzu çirkli və saxlanılması çətin edir. Uzun müddətdir ki, useEffect-dən istifadə edirəm və onu səhv istifadə etməkdə günahkaram. Əminəm ki, React-in kodumu daha təmiz və saxlanılması asanlaşdıracaq xüsusiyyətləri var.
useEffect nədir?
useEffect, funksiya komponentlərində yan təsirlər etməyə imkan verən bir hookdur. O, componentDidMount, componentDidUpdate və componentWillUnmount-u tək bir API-da birləşdirir. Bu, çox şey etməyimizə imkan verən güclü bir hookdur. Ancaq eyni zamanda çoxlu səhvə səbəb ola bilən çox təhlükəli bir hookdur.
Niyə useEffect təhlükəlidir?
Gəlin aşağıdakı nümunəyə nəzər salaq:
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>
}
Bu, hər saniyə artan sadə bir sayğacdır. O, bir interval təyin etmək üçün useEffect-dən istifadə edir. Həmçinin komponent söküldükdə intervalı təmizləmək üçün useEffect-dən istifadə edir. Yuxarıdakı kod parçası useEffect üçün geniş yayılmış bir istifadə halıdır. Bu, sadə bir nümunədir, amma eyni zamanda dəhşətli bir nümunədir.
Bu nümunənin problemi odur ki, interval komponent yenidən render edilərkən hər dəfə təyin edilir. Əgər komponent hər hansı bir səbəbdən yenidən render olunarsa, interval yenidən təyin ediləcək. Interval saniyədə iki dəfə çağırılacaq. Bu sadə nümunədə problem deyil, amma interval daha mürəkkəb olduqda böyük problem ola bilər. O, həmçinin yaddaş sızmalarına səbəb ola bilər.
Bunu necə düzəltməli?
Bu problemi düzəltməyin bir çox yolu var. Bunlardan biri intervalı saxlamaq üçün useRef-dən istifadə etməkdir.
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>
}
Yuxarıdakı kod əvvəlki nümunədən çox daha yaxşıdır. O, intervalı komponent yenidən render edilərkən hər dəfə təyin etmir. Amma hələ də yaxşılaşdırılmalıdr. Hələ də bir qədər mürəkkəbdir. Və hələ də çox təhlükəli bir hook olan useEffect-dən istifadə edir.
useEffect təsirlər üçün deyil
Bildiyimiz kimi useEffect, componentDidMount, componentDidUpdate və componentWillUnmount-u tək bir API-da birləşdirir. Gəlin bunun bəzi nümunələrini verək:
useEffect(() => {
// componentDidMount?
}, [])
useEffect(() => {
// componentDidUpdate?
}, [something, anotherThing])
useEffect(() => {
return () => {
// componentWillUnmount?
}
}, [])
Bu, başa düşmək asandır. useEffect, komponent montaj edildikdə, yeniləndikdə və söküldükdə yan təsirlər etmək üçün istifadə olunur. Amma o, yalnız yan təsirlər etmək üçün istifadə olunmur. O, həmçinin komponent yenidən render edilərkən yan təsirlər etmək üçün də istifadə olunur. Komponent yenidən render edilərkən yan təsirlər etmək yaxşı fikir deyil. Bu, çoxlu səhvə səbəb ola bilər. Komponent yenidən render edilərkən yan təsirlər etmək üçün digər hooklardan istifadə etmək daha yaxşıdır.
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>Dəyişiklik sayı: {count}</div>
</div>
)
}
useEffect, state setter deyil
import React, { useState, useEffect } from 'react'
const Example = () => {
const [count, setCount] = useState(0)
// componentDidMount və componentDidUpdate-yə oxşar:
useEffect(() => {
// Brauzer API-sini istifadə edərək sənədin başlığını yeniləyin
document.title = `Siz ${count} dəfə kliklədiniz`
}) // <-- problem budur, 😱 asılılıq massivi yoxdur
return (
<div>
<p>Siz {count} dəfə kliklədiniz</p>
<button onClick={() => setCount(count + 1)}>Mənə klikləyin</button>
</div>
)
}
Bu sənədi oxumağı məsləhət görürəm: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
İmperativ vs Deklarativ
İmperativ: Bir şey olduqda, bu təsiri yerinə yetirin.
Deklarativ: Bir şey olduqda, bu state-də dəyişikliklərə səbəb olacaq və state-in hansı hissələrinin dəyişdiyinə (asılılıq massivi) görə, bu təsir yalnız bəzi şərtlər doğrulandıqda icra olunacaq. Və React bunu heç bir səbəb olmadan eyni vaxtda render etmək üçün yenidən icra edə bilər.
Konsepsiya vs İcra
Konsepsiya:
useEffect(() => {
doSomething()
return () => cleanup()
}, [whenThisChanges])
İcra:
useEffect(() => {
if (foo && bar && (baz || quo)) {
doSomething()
} else {
doSomethingElse()
}
// üzr istəyirəm, təmizlənməni unutdum
}, [foo, bar, baz, quo])
Real dünya icra:
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])
Bu kodu yazmaq qorxuncudur. Bundan əlavə, bu, bizim kod bazamızda normal bir şey olacaq və qarışıq olacaq. 😱🤮
Təsirlər hara gedir?
React 18, montaj zamanı təsirləri iki dəfə işlədir (strict mode-da). Montaj/təsir (╯°□°)╯︵ ┻━┻ -> Sökülmə (simulyasiya olunmuş)/təmizləmə ┬─┬ /( º _ º /) -> Yenidən montaj/təsir (╯°□°)╯︵ ┻━┻
Bu, komponentin xaricində yerləşdirilməlidir? Standart useEffect? Uh... utancaqlıq. Hmm... 🤔 Biz bunu render-ə qoya bilməzdik, çünki render yalnız riyazi bərabərliyin sağ tərəfi kimi olmalıdır. Bu, yalnız hesablamanın nəticəsi olmalıdır.
useEffect nə üçündür?
Sinxronizasiya
useEffect(() => {
const sub = createThing(input).subscribe((value) => {
// dəyər ilə bir şey edin
})
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)
}
}, [])
Təsir effektləri vs Fəaliyyət effektləri
Atəş et və unut Sinxronlaşdırılmış
(Təsir effektləri) (Fəaliyyət effektləri)
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-------------------------------------------------------------------------------->
Sökülmə Yenidən montaj
Təsir effektləri hara gedir?
Hadisə işləyiciləri. Nədənsə.
<form
onSubmit={(event) => {
// 💥 yan təsir!
submitData(event)
}}
>
{/* ... */}
</form>
Beta React.js-də əla məlumatlar var. Bunu oxumağı məsləhət görürəm. Xüsusilə "Hadisə işləyicilərinin yan təsirləri ola bilərmi?" bölməsi.
Mütləq! Hadisə işləyiciləri yan təsirlər üçün ən yaxşı yerdir.
Xatırlatmaq istədiyim başqa bir əla resurs da Yan təsirlərə səbəb ola biləcəyiniz yerlərdir
React-də yan təsirlər, adətən, hadisə işləyicilərinin içərisində olur.
Əgər bütün digər seçimləri tükətdiniz və yan təsiriniz üçün uyğun hadisə işləyicisini tapa bilmirsinizsə, hələ də komponentinizdə useEffect zəngi ilə qaytarılan JSX-inizə bağlaya bilərsiniz. Bu, React-ə render etdikdən sonra, yan təsirlərə icazə verildikdə, onu daha sonra icra etməsini bildirir. Ancaq bu yanaşma son çarəniz olmalıdır.
"Təsirlər render etmənin xaricində baş verir" - David Khoursid.
(state) => UI
(state, event) => nextState // 🤔 Təsirlər?
UI, state-in bir funksiyasıdır. Bütün cari state-lər render edildiyi zaman, cari UI-ni yaradacaq. Eyni şəkildə, bir hadisə olduqda, yeni bir state yaradacaq. Və state dəyişdikdə, yeni bir UI qurulacaq. Bu paradigma, React-in əsasını təşkil edir.
Təsirlər nə zaman baş verir?
State keçid. Həmişə.
(state, event) => nextState
|
V
(state, event) => (nextState, effect) // Burada
![Yenidən render etmə təsviri](https://media.slid.es/uploads/174419/images/9663683/CleanShot_2022-06-22_at_20.24.08_2x.png align="left")
Təsir effektləri hara gedir? Hadisə işləyiciləri. State keçid.
Bunlar hadisə işləyiciləri ilə eyni vaxtda icra olunur.
Bizə Təsirə Ehtiyac Olmaya Bilər
Bu problemi həll edə biləcək React-dən daxili bir API olduğunu bilmədiyimiz üçün useEffect istifadə edə bilərdik.
Bu mövzu haqqında oxumaq üçün əla bir resurs var: Sizə Təsirə Ehtiyac Olmaya Bilər
Məlumatları transformasiya etmək üçün useEffect-ə ehtiyacımız yoxdur.
useEffect ➡️ useMemo (çox hallarda useMemo-ya ehtiyacımız olmasa da)
const Cart = () => {
const [items, setItems] = useState([])
const [total, setTotal] = useState(0)
useEffect(() => {
setTotal(items.reduce((total, item) => total + item.price, 0))
}, [items])
// ...
}
Yenidən diqqətlə 🧐 oxuyun və düşünün.
const Cart = () => {
const [items, setItems] = useState([])
const total = useMemo(() => {
return items.reduce((total, item) => total + item.price, 0)
}, [items])
// ...
}
Ümumi miqdarı hesablamaq üçün useEffect
-dən istifadə etmək əvəzinə, ümumi miqdarı memoize etmək üçün useMemo
-dan istifadə edə bilərik. Hətta dəyişən bahalı hesablama deyilsə də, useMemo
-dan istifadə edərək onu memoize etməyə ehtiyacımız yoxdur, çünki əsasən performansı yaddaş üçün dəyişdiririk.
Hər dəfə useEffect
-də setState
görəndə, onu sadələşdirə biləcəyimizə işarədir.
useSyncExternalStore
Xarici mağazalarla təsirlər?useEffect ➡️ useSyncExternalStore
❌ Səhv yol:
const Store = () => {
const [isConnected, setIsConnected] = useState(true)
useEffect(() => {
const sub = storeApi.subscribe(({ status }) => {
setIsConnected(status === 'connected')
})
return () => {
sub.unsubscribe()
}
}, [])
// ...
}
✅ Ən yaxşı yol:
const Store = () => {
const isConnected = useSyncExternalStore(
// 👇 abunə ol
storeApi.subscribe,
// 👇 anlık görüntüyü alın
() => storeApi.getStatus() === 'connected',
// 👇 server anlık görüntüsünü alın
true
)
// ...
}
Valideynlər ilə ünsiyyət qurmaq üçün useEffect-ə ehtiyacımız yoxdur.
useEffect ➡️ eventHandler
❌ Səhv yol:
const ChildProduct = ({ onOpen, onClose }) => {
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
if (isOpen) {
onOpen()
} else {
onClose()
}
}, [isOpen])
return (
<div>
<button
onClick={() => {
setIsOpen(!isOpen)
}}
>
Sürətli baxışı açıb bağla
</button>
</div>
)
}
📈 Daha yaxşı yol:
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={}
>
Sürətli baxışı açıb bağla
</button>
</div>
)
}
✅ Ən yaxşı yol isə xüsusi bir hook yaratmaqdır:
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}
>
Sürətli baxışı açıb bağla
</button>
</div>
)
}
Qlobal tək nöqtəli obyektləri başlatmaq üçün useEft-ə ehtiyacımız yoxdur.
useEffect ➡️ justCallIt
❌ Səhv yol:
const Store = () => {
useEffect(() => {
storeApi.authenticate() // 👈 Bu iki dəfə işləyəcək!
}, [])
// ...
}
🔨 Gəlin düzəldək:
const Store = () => {
const didAuthenticateRef = useRef()
useEffect(() => {
if (didAuthenticateRef.current) return
storeApi.authenticate()
didAuthenticateRef.current = true
}, [])
// ...
}
➿ Başqa bir yol:
let didAuthenticate = false
const Store = () => {
useEffect(() => {
if (didAuthenticate) return
storeApi.authenticate()
didAuthenticate = true
}, [])
// ...
}
🤔 Bəs əgər:
storeApi.authenticate()
const Store = () => {
// ...
}
🍷 SSR, eləmi?
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
const Store = () => {
// ...
}
🧪 Test etmə?
const renderApp = () => {
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
appRoot.render(<Store />)
}
Hər şeyi bir komponentin içərisinə yerləşdirməyə ehtiyacımız yoxdur.
Məlumat almaq üçün useEffect-ə ehtiyacımız yoxdur.
useEffect ➡️ renderAsYouFetch (SSR) or useSWR (CSR)
❌ Səhv yol:
const Store = () => {
const [items, setItems] = useState([])
useEffect(() => {
let isCanceled = false
getItems().then((data) => {
if (isCanceled) return
setItems(data)
})
return () => {
isCanceled = true
}
})
// ...
}
💽 Remix yolu:
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) Server Komponentində async/await ilə:
// app/page.tsx
async function getData() {
const res = await fetch('https://api.example.com/...')
// Qaytarılan dəyər seriallaşdırılmır
// Date, Map, Set, və s. qaytara bilərsiniz.
// Tövsiyə: səhvləri idarə edin
if (!res.ok) {
// Bu, ən yaxın `error.js` Error Boundary-ni aktivləşdirəcək
throw new Error('Məlumatları almaq mümkün olmadı')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
⏭️💁 Next.js (appDir) Client Komponentində useSWR ilə:
// app/page.tsx
import useSWR from 'swr'
export default function Page() {
const { data, error } = useSWR('/api/data', fetcher)
if (error) return <div>yüklənmədi</div>
if (!data) return <div>yüklənir...</div>
return <div>salam {data}!</div>
}
⏭️🧹 Next.js (pagesDir) SSR yolu ilə:
// 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>salam {data}!</div>
}
⏭️💁 Next.js (pagesDir) CSR yolu ilə:
// pages/index.tsx
import useSWR from 'swr'
export default function Page() {
const { data, error } = useSWR('/api/data', fetcher)
if (error) return <div>yüklənmədi</div>
if (!data) return <div>yüklənir...</div>
return <div>salam {data}!</div>
}
🍃 React Query (SSR yolu):
import { getItems } from './storeApi'
import { useQuery } from 'react-query'
const Store = () => {
const queryClient = useQueryClient()
return (
<button
onClick={() => {
queryClient.prefetchQuery('items', getItems)
}}
>
Əşyalara bax
</button>
)
}
const Items = () => {
const { data, isLoading, isError } = useQuery('items', getItems)
// ...
}
⁉️ Həqiqətən ⁉️ Nədən istifadə etməliyik? useEffect? useQuery? useSWR?
yoxsa... sadəcə use() 🤔
use(), konsepsiya olaraq await-ə bənzər bir vəd qəbul edən yeni bir React funksiyasıdır. use(), komponentlər, hook-lar və Suspense ilə uyğun olan bir şəkildə bir funksiyadan qaytarılan vədi idarə edir. use() haqqında daha çox məlumatı React RFC-də öyrənə bilərsiniz.
function Note({ id }) {
// Bu, qeydi asinxron olaraq alır, amma komponent müəllifı üçün bu
// sinxron əməliyyat kimi görünür.
const note = use(fetchNote(id))
return (
<div>
<h1>{note.title}</h1>
<section>{note.body}</section>
</div>
)
}
useEffect-də məlumat alma problemləri
🏃♂️ Yarış şərtləri
🔙 Anında geri düyməsi yoxdur
🔍 SSR və ya ilkin HTML məzmunu yoxdur
🌊 Şəlalənin dalınca getmək
- Reddit, Dan Abramov
Nəticə
Məlumat almaqdan imperativ API-larla mübarizəyə qədər yan təsirlər, veb tətbiq inkişafında ən böyük məyusluq mənbələrindən biridir. Və gəlin etiraf edək, hər şeyi useEffect hook-larına qoymaq yalnız bir qədər kömək edir. Şükürlər olsun ki, yan təsirlər üçün bir elm (yaxşı, riyaziyyat) var, state maşınlarında və statechart-larda rəsmiləşdirilib, bu, bizə yan təsirləri necə deklerativ olaraq təşkil etmək lazım olduğunu vizual olaraq modelləşdirməyə və başa düşməyə kömək edir, nə qədər mürəkkəb olursa olsunlar.