- Publikuar më
Përshëndetje React, Përshëndetje useEffect (Inshallah)
- Autorët
- Emri
- Imamuzzaki Abu Salam
- https://x.com/ImBIOS_Dev
Në këtë artikull, do t'ju tregoj se si të përdorni React për të zëvendësuar useEffect në shumicën e rasteve.
Kam qenë duke parë "Goodbye, useEffect" nga David Khoursid, dhe 🤯 më ka frymëzuar në një mënyrë 😀 të mirë. Pajtohem që useEffect është përdorur aq shumë sa bën që kodi ynë të jetë i pista dhe i vështirë për t'u mirëmbajtur. Unë kam përdorur useEffect për një kohë të gjatë dhe jam fajtor për keqpërdorimin e tij. Jam i sigurt që React ka veçori që do ta bëjnë kodin tim më të pastër dhe më të lehtë për t'u mirëmbajtur.
Çfarë është useEffect?
useEffect është një hook që na lejon të kryejmë efekte anësore në komponentë funksionale. Ai kombinon componentDidMount, componentDidUpdate dhe componentWillUnmount në një API të vetme. Është një hook irheqës që do të na lejojë të bëjmë shumë gjëra. Por është gjithashtu një hook shumë i rrezikshëm që mund të shkaktojë shumë gabime.
Pse useEffect është i rrezikshëm?
Le të hedhim një vështrim në shembullin e mëposhtëm:
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>
}
Është një numërues i thjeshtë që rritet çdo sekondë. Ai përdor useEffect për të vendosur një interval. Ai gjithashtu përdor useEffect për të pastruar intervalin kur komponenti hiqet. Fragmenti i kodit më sipër është një rast i zakonshëm për përdorimin e useEffect. Është një shembull i thjeshtë, por është gjithashtu një shembull i tmerrshëm.
Problemi me këtë shembull është se intervali vendoset çdo herë që komponenti ri-renditet. Nëse komponenti ri-renditet për ndonjë arsye, intervali do të vendoset përsëri. Intervali do të thirret dy herë në sekondë. Nuk është një problem me këtë shembull të thjeshtë, por mund të jetë një problem i madh kur intervali është më kompleks. Ai gjithashtu mund të shkaktojë rrjedhje kujtese.
Si ta rregullojmë atë?
Ekzistojnë shumë mënyra për të rregulluar këtë problem. Një mënyrë është përdorimi i useRef për të ruajtur intervalin.
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>
}
Kodi i mësipërm është shumë më mirë se shembulli i mëparshëm. Ai nuk e vendos intervalin çdo herë që komponenti ri-renditet. Por ende ka nevojë për përmirësim. Ende është pak kompleks. Dhe ende përdor useEffect, që është një hook shumë i rrezikshëm.
useEffect nuk është për efekte
Siç e dimë për useEffect, ai kombinon componentDidMount, componentDidUpdate dhe componentWillUnmount në një API të vetme. Le të japim disa shembuj të tij:
useEffect(() => {
// componentDidMount?
}, [])
useEffect(() => {
// componentDidUpdate?
}, [something, anotherThing])
useEffect(() => {
return () => {
// componentWillUnmount?
}
}, [])
Është e lehtë për t'u kuptuar. useEffect përdoret për të kryer efekte anësore kur komponenti montohët, përditësohet dhe hiqet. Por nuk përdoret vetëm për të kryer efekte anësore. Ai përdoret gjithashtu për të kryer efekte anësore kur komponenti ri-renditet. Nuk është një ide e mirë të kryhen efekte anësore kur komponenti ri-renditet. Mund të shkaktojë shumë gabime. Është më mirë të përdoren hook-et e tjerë për të kryer efekte anësore kur komponenti ri-renditet.
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 nuk është një vendosës shteti
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>
)
}
Ju rekomandoj të lexoni këtë dokumentacion: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Imperativ vs Deklarativ
Imperativ: Kur ndodh diçka, ekzekutoni këtë efekt.
Deklarativ: Kur ndodh diçka, do të shkaktojë ndryshimin e shtetit dhe në varësi (varësia e vargjeve) të pjesëve të shtetit që ndryshuan, ky efekt duhet të ekzekutohet, por vetëm nëse disa kushte janë të vërteta. Dhe React mund ta ekzekutojë atë përsëri për asnjë arsye renditje konkurrente.
Koncept vs Zbatim
Koncept:
useEffect(() => {
doSomething()
return () => cleanup()
}, [whenThisChanges])
Zbatim:
useEffect(() => {
if (foo && bar && (baz || quo)) {
doSomething()
} else {
doSomethingElse()
}
// oops, I forgot the cleanup
}, [foo, bar, baz, quo])
Zbatim i botës reale:
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])
Është e frikshme të shkruhet ky kod. Për më tepër, do të jetë normale në bazën tonë të kodit dhe e prishur. 😱🤮
Ku shkojnë efektet?
React 18 i ekzekuton efektet dy herë gjatë montimit (në modin e rreptë). Montim / efekt (╯°□°)╯︵ ┻━┻ -> Hiqje (e simuluar) / pastrim ┬─┬ /( º _ º /) -> Rimontim / efekt (╯°□°)╯︵ ┻━┻
A duhet të vendoset jashtë komponentin? useEffect i paracaktuar? Uhh ... i çuditshëm. Hmm ... 🤔 Nuk mund ta vendosnim në renditje sepse duhet të ketë asnjë efekt anësor aty sepse renditja është vetëm si dora e djathtë e një ekuacioni matematik. Duhet të jetë vetëm rezultati i llogaritjes.
Për çfarë është useEffect?
Sinkronizim
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)
}
}, [])
Efekte veprimi vs efekte aktiviteti
Zjarri dhe harroje Sinkronizuar
(Efekte veprimi) (Efekte aktiviteti)
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-------------------------------------------------------------------------------->
Hiq Rimontim
Ku shkojnë efektet e veprimit?
Menaxhuesit e ngjarjeve. Më së shumti.
<form
onSubmit={(event) => {
// 💥 side-effect!
submitData(event)
}}
>
{/* ... */}
</form>
Ekziston informacion i shkëlqyer në Beta React.js. Ju rekomandoj ta lexoni. Sidomos pjesën "A mund të kenë menaxhuesit e ngjarjeve efekte anësore?".
Patjetër! Menaxhuesit e ngjarjeve janë vendi më i mirë për efektet anësore.
Një burim tjetër i shkëlqyer që dua të përmend është Ku ju mund të shkaktoni efekte anësore
Në React, efektet anësore zakonisht i përkasin brenda menaxhuesve të ngjarjeve.
Nëse keni shteruar të gjitha opsionet e tjera dhe nuk mund të gjeni menaxhuesin e duhur të ngjarjes për efektin tuaj anësor, mund ta bashkëngjitni atë në JSX-në tuaj të kthyer me një thirrje useEffect në komponentin tuaj. Kjo i thotë React ta ekzekutojë atë më vonë, pas renditjes, kur efektet anësore janë të lejuara. Sidoqoftë, ky qasje duhet të jetë zgjidhja juaj e fundit.
"Efektet ndodhin jashtë renditjes" - David Khoursid.
(state) => UI
(state, event) => nextState // 🤔 Efekte?
UI është një funksion i shtetit. Pasi të renditen të gjithë shtetet aktuale, do të prodhojë UI-në aktuale. Në mënyrë të ngjashme, kur ndodh një ngjarje, do të krijojë një shtet të ri. Dhe kur shteti ndryshon, do të ndërtojë një UI të re. Ky paradigmë është bërthama e React.
Kur ndodhin efektet?
Softver mesatar? 🕵️ Thirrjet e kthimit? 🤙 Sagas? 🧙♂️ Reagimet? 🧪 Lavamanet? 🚰 Monadët (?) 🧙♂️ Kurdo që? 🤷♂️
Tranzicionet e shtetit. Gjithmonë.
(state, event) => nextState
|
V
(state, event) => (nextState, effect) // Këtu
![Ilusion i renditjes së imazhit](https://media.slid.es/uploads/174419/images/9663683/CleanShot_2022-06-22_at_20.24.08_2x.png align="left")
Ku shkojnë efektet e veprimit? Menaxhuesit e ngjarjeve. Tranzicionet e shtetit.
Të cilat ndodhin të ekzekutohen në të njëjtën kohë me menaxhuesit e ngjarjeve.
Mund të mos kemi nevojë për një efekt
Ne mund të përdorim useEffect sepse nuk e dimë që ekziston tashmë një API i ndërtuar nga React që mund ta zgjidhë këtë problem.
Këtu është një burim i shkëlqyer për të lexuar për këtë temë: You Might Not Need an Effect
Ne nuk kemi nevojë për useEffect për transformimin e të dhënave.
useEffect ➡️ useMemo (edhe pse nuk kemi nevojë për useMemo në shumicën e rasteve)
const Cart = () => {
const [items, setItems] = useState([])
const [total, setTotal] = useState(0)
useEffect(() => {
setTotal(items.reduce((total, item) => total + item.price, 0))
}, [items])
// ...
}
Lexojeni dhe mendoni për të përsëri me kujdes 🧐.
const Cart = () => {
const [items, setItems] = useState([])
const total = useMemo(() => {
return items.reduce((total, item) => total + item.price, 0)
}, [items])
// ...
}
Në vend që të përdorim useEffect
për të llogaritur totalin, ne mund të përdorim useMemo
për të memorizuar totalin. Edhe nëse ndryshorja nuk është një llogaritje e shtrenjtë, nuk kemi nevojë të përdorim useMemo
për ta memorizuar atë sepse po bëjmë shkëmbim performancë për kujtesë.
Sa herë që shohim setState
në useEffect
, është një shenjë paralajmërimi që mund ta thjeshtojmë atë.
useSyncExternalStore
Efekte me dyqane të jashtme?useEffect ➡️ useSyncExternalStore
❌ Mënyra e gabuar:
const Store = () => {
const [isConnected, setIsConnected] = useState(true)
useEffect(() => {
const sub = storeApi.subscribe(({ status }) => {
setIsConnected(status === 'connected')
})
return () => {
sub.unsubscribe()
}
}, [])
// ...
}
✅ Mënyra më e mirë:
const Store = () => {
const isConnected = useSyncExternalStore(
// 👇 subscribe
storeApi.subscribe,
// 👇 get snapshot
() => storeApi.getStatus() === 'connected',
// 👇 get server snapshot
true
)
// ...
}
Ne nuk kemi nevojë për useEffect për komunikimin me prindërit.
useEffect ➡️ menaxhuesi i ngjarjes
❌ Mënyra e gabuar:
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>
)
}
📈 Mënyra më e mirë:
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>
)
}
✅ Mënyra më e mirë është të krijojmë një hook personalizuar:
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>
)
}
Ne nuk kemi nevojë për useEft për inicializimin e singletons globale.
useEffect ➡️ vetëmThirrja
❌ Mënyra e gabuar:
const Store = () => {
useEffect(() => {
storeApi.authenticate() // 👈 This will run twice!
}, [])
// ...
}
🔨 Le ta rregullojmë atë:
const Store = () => {
const didAuthenticateRef = useRef()
useEffect(() => {
if (didAuthenticateRef.current) return
storeApi.authenticate()
didAuthenticateRef.current = true
}, [])
// ...
}
➿ Një mënyrë tjetër:
let didAuthenticate = false
const Store = () => {
useEffect(() => {
if (didAuthenticate) return
storeApi.authenticate()
didAuthenticate = true
}, [])
// ...
}
🤔 Si nëse:
storeApi.authenticate()
const Store = () => {
// ...
}
🍷 SSR, po?
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
const Store = () => {
// ...
}
🧪 Testimi?
const renderApp = () => {
if (typeof window !== 'undefined') {
storeApi.authenticate()
}
appRoot.render(<Store />)
}
Nuk kemi domosdoshmërisht nevojë të vendosim gjithçka brenda një komponenti.
Ne nuk kemi nevojë për useEffect për marrjen e të dhënave.
useEffect ➡️ renditjaAshtuSiMerrni (SSR) ose useSWR (CSR)
❌ Mënyra e gabuar:
const Store = () => {
const [items, setItems] = useState([])
useEffect(() => {
let isCanceled = false
getItems().then((data) => {
if (isCanceled) return
setItems(data)
})
return () => {
isCanceled = true
}
})
// ...
}
💽 Mënyra e 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) me async/await në Server Component:
// 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) me useSWR në Client Component:
// 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) në 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) në 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)
// ...
}
⁉️ Vërtet ⁉️ Çfarë duhet të përdorim? useEffect? useQuery? useSWR?
ose ... vetëm use() 🤔
use() është një funksion i ri i React që pranon një premtim konceptualisht të ngjashëm me await. use() i trajton premtimet e kthyera nga një funksion në një mënyrë që është e pajtueshme me komponentët, hook-et dhe Suspense. Mësoni më shumë për use() në RFC të React.
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>
)
}
Problemet e marrjes së të dhënave në useEffect
🏃♂️ Kushte garuese
🔙 Asnjë buton i menjëhershëm i shpinës
🔍 Asnjë SSR ose përmbajtje fillestare HTML
🌊 Ndiqni ujëvarën
- Reddit, Dan Abramov
Përfundim
Që nga marrja e të dhënave deri te lufta me API-të imperativë, efektet anësore janë një nga burimet më të mëdha të frustrimit në zhvillimin e aplikacioneve të uebit. Dhe le të jemi të sinqertë, vendosja e gjithçkaje në hook-et useEffect ndihmon vetëm pak. Përfundimisht, ka një shkencë (mirë, matematikë) për efektet anësore, të formalizuara në makina shteti dhe shtetet, që mund të na ndihmojnë të modelojmë dhe kuptojmë vizualisht se si të organizojmë efektet, pavarësisht nga kompleksiteti që marrin, në mënyrë deklarative.