Reaktiivisten Efektien elinkaari
Efekteilla on eri elinkaari komponenteista. Komponentit voivat mountata, päivittyä, tai un-mountata. Efekti voi tehdä vain kaksi asiaa: aloittaa synkronoimaan jotain, ja myöhemmin lopettaa synkronointi. Tämä sykli voi tapahtua useita kertoja, jos Efekti riippuu propseista ja tilasta, jotka muuttuvat ajan myötä. React tarjoaa linter-säännön, joka tarkistaa, että olet määrittänyt Efektin riippuvuudet oikein. Tämä pitää Efektisi synkronoituna viimeisimpiin propseihin ja statukseen.
Tulet oppimaan
- Miten Efektin elinkaari eroaa komponentin elinkaaresta
- Miten ajatella jokaista yksittäistä Efektia erillään
- Milloin Efektisi täytyy synkronoida uudelleen ja miksi
- Miten Effektisi riippuvuudet määritellään
- Mitä tarkoittaa kun arvo on reaktiivinen
- Mitä tyhjä riippuvuuslista tarkoittaa
- Miten React tarkistaa rippuuksien oikeudellisuuden linterin avulla
- Mitä tehdä kun olet eri mieltä linterin kanssa
Efektin elinkaari
Jokainen React komponentti käy läpi saman elinkaaren:
- Komponentti mounttaa kun se lisätään näytölle.
- Komponentti päivittyy kun se saa uudet propsit tai tilan, yleensä vuorovaikutuksen seurauksena.
- Komponentti unmounttaa kun se poistetaan näytöltä.
Tämä on hyvä tapa ajatella komponentteja, mutta ei Efektejä. Sen sijaan, yritä ajatella jokaista Efektiä erillään komponentin elinkaaresta. Efekti kuvaa miten ulkoinen järjestelmä synkronoidaan nykyisten propsien ja tilan kanssa. Kun koodisi muuttuu, synkronointi täytyy tapahtua useammin tai harvemmin.
Kuvallistaaksemme tämän pointin, harkitse tätä Efektia, joka yhdistää komponenttisi chat-palvelimeen:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
Efektisi runko määrittää miten syknronointi aloitetaan:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
// ...
Efektisi palauttama siivousfunktio määrittelee miten synkronointi lopetetaan:
// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
// ...
Intuitiivisesti, saatat ajatella Reactin aloittavan synkronoinnin kun komponenttisi mountataan ja lopettavan synkronoinnin kun komponenttisi unmountataan. Tämä ei kuitenkaan ole tilanne! Joksus, saattaa olla tarpeellista aloittaa ja lopettaa synkronointi useita kertoja kun komponentti pysyy mountattuna.
Katsotaan miksi tämä on tarpeellista, milloin se tapahtuu_, ja miten voit hallita sen toimintaa.
Miksi synkronointi voi tapahtua useammin kuin kerran
Kuvittele, tämä ChatRoom
komponetti saa roomId
propin, jonka käyttäjä valitsee pudotusvalikosta. Oletetaan, että aluksi käyttäjä valitsee "general"
huoneen roomId
:ksi. Sovelluksesi näyttää "general"
chat-huoneen:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId /* "general" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}
Kun UI on näytetty, React suorittaa Efektisi aloittaakseen synkronoinnin. Se yhdistää "general"
huoneeseen:
function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Yhdistää "general" huoneeseen
connection.connect();
return () => {
connection.disconnect(); // Katkaisee yhteyden "general" huoneeseen
};
}, [roomId]);
// ...
Tähän asti kaikki hyvin.
Myöhemmin, käyttäjä valitsee eri huoneen pudotusvalikosta (esim. "travel"
). Ensin, React päivittää UI:n:
function ChatRoom({ roomId /* "travel" */ }) {
// ...
return <h1>Welcome to the {roomId} room!</h1>;
}
Ajattele mitä tulisi tapahtua seuraavaksi. Käyttäjä näkee, että "travel"
on valittu chat-huoneeksi UI:ssa. Kuitenkin, Efekti joka viimeksi suoritettiin on yhä yhdistetty "general"
huoneeseen. roomId
propsi on muuttunut, joten mitä Efektisi teki silloin (yhdisti "general"
huoneeseen) ei enää vastaa UI:ta.
Tässä vaiheessa, haluat Reactin tekevän kaksi asiaa:
- Lopettaa synkronoinnin vanhan
roomId
kanssa (katkaisee yhteyden"general"
huoneeseen) - Aloittaa synkronoinnin uuden
roomId
kanssa (yhdistää"travel"
huoneeseen)
Onneksi, olet jo opettanut Reactille miten teet molemmat näistä asioista! Efektisi runko määrittää miten aloitat synkronoinnin, ja siivousfunktio määrittää miten lopetat synkronoinnin. Kaikki mitä Reactin täytyy tehdä nyt on kutsua niitä oikeassa järjestyksessä ja oikeilla propseilla ja tilalla. Katsotaan mitä oikein tapahtuu.
Miten React uudelleen synkronisoi Efektisi
Muista, että ChatRoom
komponenttisi on saanut uuden arvon sen roomId
propsiksi. Se olu aluksi "general"
, ja se on nyt "travel"
. Reactin täytyy synkronoida Efektisi uudelleen yhdistääkseen sinut eri huoneeseen.
Lopettaaksesi synkronoinnin, React kutsuu siivousfunktiota, jonka Efektisi palautti yhdistettyään "general"
huoneeseen. Koska roomId
oli "general"
, siivousfunktio katkaisee yhteyden "general"
huoneeseen:
function ChatRoom({ roomId /* "general" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Yhdistää "general" huoneeseen
connection.connect();
return () => {
connection.disconnect(); // Katkaisee yhteyden "general" huoneeseen
};
// ...
React sitten kutsuu Efektiasi, jonka olet tarjonnut tämän renderöinnin aikana. Tällä kertaa, roomId
on "travel"
joten se aloittaa synkronoinnin "travel"
chat-huoneeseen (kunnes sen siivousfunktio kutsutaan):
function ChatRoom({ roomId /* "travel" */ }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Yhdistää "travel" huoneeseen
connection.connect();
// ...
Kiitos tämän, olet nyt yhdistetty samaan huoneeseen, jonka käyttäjä valitsi UI:ssa. Katastrofi vältetty!
Joka kerta kun komponenttisi renderöityy uudelleen eri roomId
:llä, Efektisi täytyy synkronoida uudelleen. Esimerkiksi, sanotaan että käyttäjä muuttaa roomId
:n arvosta "travel"
arvoon "music"
. Reactin täytyy taas lopettaa synkronointi Efektisi kanssa kutsumalla sen siivousfunktiota (katkaisemalla yhteys "travel"
huoneeseen). Sitten se taas aloittaa synkronoinnin suorittamalla Efektisi rungon uudella roomId
propsilla (yhdistämällä sinut "music"
huoneeseen).
Lopuksi, kun käyttäjä menee eri ruutuun, ChatRoom
unmounttaa. Sitten ei ole tarvetta pysyä ollenkaan yhteydessä. React lopettaa synkronoinnin Efektisi kanssa viimeisen kerran ja katkaisee yhteyden "music"
huoneeseen.
Ajattelu Efektin perspektiivistä
Käydään läpi kaikki mitä tapahtui ChatRoom
komponentin perspektiivissä:
ChatRoom
mounttasiroomId
arvolla"general"
ChatRoom
päivittyiroomId
arvolla"travel"
ChatRoom
päivittyiroomId
arvolla"music"
ChatRoom
unmounttasi
Jokaisen kohdan aikana komponentin elinkaaressa, Efektisi teki eri asioita:
- Efektisi yhdisti
"general"
huoneeseen - Efektisi katkaisi yhteyden
"general"
huoneesta ja yhdisti"travel"
huoneeseen - Efektisi katkaisi yhteyden
"travel"
huoneesta ja yhdisti"music"
huoneeseen - Efektisi katkaisi yhteyden
"music"
huoneesta
Ajatellaan mitä tapahtui Efektin perspektiivistä:
useEffect(() => {
// Efektisi yhdisti huoneeseen, joka määriteltiin roomId:lla...
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
// ...kunnes yhteys katkaistiin
connection.disconnect();
};
}, [roomId]);
Tämän koodin rakenne saattaa inspiroida sinua näkemään mitä tapahtui sekvenssinä ei-päällekkäisistä aikajaksoista:
- Efektisi yhdisti
"general"
huoneeseen (kunnes yhteys katkaistiin) - Efektisi yhdisti
"travel"
huoneeseen (kunnes yhteys katkaistiin) - Efektisi yhdisti
"music"
huoneeseen (kunnes yhteys katkaistiin)
Aiemmin, ajattelit komponentin perspektiivistä. Kun katsot sitä komponentin perspektiivistä, oli houkuttelevaa ajatella Efektejä “callbackeina” tai “elinkaari-tapahtumina”, jotka tapahtuvat tiettyyn aikaan kuten “renderöinnin jälkeen” tai “ennen unmounttaamista”. Tämä ajattelutapa monimutkaistuu nopeasti, joten on parempi välttää sitä.
Sen sijaan, keskity aina yksittäiseen alku/loppu sykliin kerralla. Sillä ei tulisi olla merkitystä mounttaako, päivittyykö, vai unmounttaako komponentti. Sinun täytyy vain kuvailla miten aloitat synkronoinnin ja miten lopetat sen. Jos teet sen hyvin, Efektisi on kestävä aloittamiselle ja lopettamiselle niin monta kertaa kuin tarpeellista.
Tämä saattaa muistuttaa sinua siitä, miten et ajattele mounttaako vai päivittyykö komponentti kun kirjoitat renderöintilogiikkaa, joka luo JSX:ää. Kuvailet mitä pitäisi olla näytöllä, ja React selvittää loput.
Miten React vahvistaa, että Efektisi voi synkronoitua uudelleen
Tässä on esimerkki, jota voit kokeilla. Paina “Open chat” mountataksesi ChatRoom
komponentin:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1>; } export default function App() { const [roomId, setRoomId] = useState('general'); const [show, setShow] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <button onClick={() => setShow(!show)}> {show ? 'Close chat' : 'Open chat'} </button> {show && <hr />} {show && <ChatRoom roomId={roomId} />} </> ); }
Huomaa, että kun komponentti mounttaa ensimmäisen kerran, näet kolme lokia:
✅ Connecting to "general" room at https://localhost:1234...
(vain-kehitysvaiheessa)❌ Disconnected from "general" room at https://localhost:1234.
(vain-kehitysvaiheessa)✅ Connecting to "general" room at https://localhost:1234...
Ensimmäiset kaksi ovat vain kehityksessä. Kehityksessä, React uudelleen mounttaa jokaisen komponentin kerran.
React vahvistaa, että Efektisi voi synkronoitua uudelleen pakottamalla sen tekemään se välittömästi kehityksessä. Tämä saattaa muistuttaa sinua oven avaamisesta ja sulkemisesta ylimääräisen kerran tarkistaaksesi, että lukko toimii. React aloittaa ja lopettaa Efektisi yhden ylimääräisen kerran kehityksessä tarkistaakseen, että olet toteuttanut siivousfunktion hyvin.
Pääsyy miksi Efektisi synkronoi uudelleen käytännössä on jos jokin data, jota se käyttää on muuttunut. Yllä olevassa hiekkalaatikossa, vaihda valittua chat-huonetta. Huomaa miten Efektisi synkronoituu uudelleen roomId
muuttuessa.
Kuitenkin, on myös epätavallisempia tapauksia, joissa uudelleen synkronointi on tarpeellista. Esimerkiksi, kokeile muokata serverUrl
:ää hiekkalaatikossa yllä kun chat on auki. Huomaa miten Efektisi synkronoituu uudelleen vastauksena koodin muokkaukseen. Tulevaisuudessa, React saattaa lisätä lisää ominaisuuksia, jotka nojaavat uudelleen synkronointiin.
Miten React tietää, että sen täytyy synkronoida Efekti uudelleen
Saatat miettiä miten React tiesi, että Efektisi täytyi synkronoida uudelleen roomId
:n muuttuessa. Se johtuu siitä, että kerroit Reactille koodin riippuvan roomId
:sta sisällyttämällä sen riippuvuuslistaan:
function ChatRoom({ roomId }) { // roomId propsi saattaa muuttua ajan kanssa
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Tämä Efekti lukee roomId:n
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]); // Joten kerrot Reactille, että tämä Efekti "riippuu" roomId:sta
// ...
Tässä miten tämä toimii:
- Tiesit
roomId
:n olevan propsi, joka tarkoittaa, että se voi muuttua ajan kanssa. - Tiesit, että Efektisi lukee
roomId
:n (joten sen logiikka riippuu arvosta, joka saattaa muuttua myöhemmin). - Tämä on miksi määritit sen Efektisi riippuvuudeksi (jotta se synkronoituu uudelleen kun
roomId
muuttuu).
Joka kerta kun komponenttisi renderöityy uudelleen, React katsoo riippuvuuslistaa, jonka olet määrittänyt. Jos mikään arvoista taulukossa on eri kuin arvo samassa kohdassa, jonka annoit edellisellä renderöinnillä, React synkronoi Efektisi uudelleen.
Esimerkiksi, jos välitit arvon ["general"]
ensimmäisen renderöinnin aikana, ja myöhemmin välitit ["travel"]
seuraavan renderöinnin aikana, React vertaa "general"
ja "travel"
arvoja. Nämä ovat eri arvoja (vertailtu Object.is
avulla), joten React synkronoi Efektisi uudelleen. Toisaalta, jos komponenttisi uudelleen renderöityy mutta roomId
ei ole muuttunut, Efektisi pysyy yhdistettynä samaan huoneeseen.
Kukin Efekti edustaa erillistä synkronointiprosessia
Vältä lisäämästä aiheesta poikkeavaa logiikkaa Efektiisi vain koska tämä logiikka täytyy suorittaa samaan aikaan kuin Efekti, jonka olet jo kirjoittanut. Esimerkiksi, oletetaan että haluat lähettää analytiikka tapahtuman kun käyttäjä vierailee huoneessa. Sinulla on jo Efekti, joka riippuu roomId
:sta, joten saatat tuntea houkutuksen lisätä analytiikka-kutsu sinne:
function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
Mutta kuvittele, että myöhemmin lisäät toisen riippuvuuden tähän Efektiin, joka täytyy alustaa yhteys uudelleen. Jos tämä Efekti synkronoituu uudelleen, se kutsuu myös logVisit(roomId)
samaan huoneeseen, jota et tarkoittanut. Vierailun kirjaaminen on erillinen prosessi yhdistämisestä. Kirjoita ne kahdeksi erilliseksi Efektiksi:
function ChatRoom({ roomId }) {
useEffect(() => {
logVisit(roomId);
}, [roomId]);
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
// ...
}, [roomId]);
// ...
}
Jokaisen Efektin koodissasi tulisi edustaa erillistä ja riippumatonta synkronointiprosessia.
Yllä olevassa esimerkissä, yhden Efektin poistaminen ei hajota toisen Efektin logiikkaa. Tämä on hyvä indikaatio siitä, että ne synkronoivat eri asioita, joten oli järkevää jakaa ne kahteen erilliseen Efektiin. Toisaalta, jos jaat yhtenäisen logiikan eri Efekteihin, koodi saattaa näyttää “puhtaammalta” mutta tulee vaikeammaksi ylläpitää. Tämän takia sinun tulisi ajatella ovatko prosessit samat vai erilliset, eivät sitä näyttääkö koodi puhtaammalta.
Efektit “reagoivat” reaktiivisiin arvoihin
Efektisi lukee kaksi muuttujaa (serverUrl
ja roomId
), mutta määritit vain roomId
:n riippuvuudeksi:
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}
Miksei serverUrl
tarvitse olla riippuvuus?
Tämä siksi, koska serverUrl
ei muutu koskaan uudelleen renderöinnin seurauksena. Se on aina sama riippumatta kuinka monta kertaa komponentti renderöityy ja miksi. Koska serverUrl
ei koskaan muutu, ei olisi järkevää määrittää sitä riippuvuudeksi. Loppujen lopuksi, riippuvuudet tekevät jotain vain kun ne muuttuvat ajan kanssa!
Toisella kädellä, roomId
saattaa olla eri uudelleen renderöinnin seurauksena. Propsit, tila, ja muut arvot, jotka on määritelty komponentin sisällä ovat reaktiivisia koska ne lasketaan renderöinnin aikana ja osallistuvat Reactin datavirtaan.
Jos serverUrl
olisi tilamuuttuja, se olisi reaktiivinen. Reaktiiviset arvot täytyy sisällyttää riippuvuuksiin:
function ChatRoom({ roomId }) { // Propsit muuttuvat ajan kanssa
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // Tila voi muuttua ajan kanssa
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Efektisi lukee propsin ja tilan
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // Joten kerrot Reactille, että tämä Efekti "riippuu" propsista ja tilasta
// ...
}
Sisällyttämällä serverUrl
riippuvuudeksi, varmistat että Efekti synkronoituu uudelleen sen muuttuessa.
Kokeile muuttaa valittua chat-huonetta tai muokata server URL:ää tässä hiekkalaatikossa:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId, serverUrl]); return ( <> <label> Server URL:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>Welcome to the {roomId} room!</h1> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Aina kun muutat reaktiivista arvoa kuten roomId
tai serverUrl
, Efekti yhdistää uudelleen chat-palvelimeen.
Mitä tyhjä riippuvuuslista tarkoittaa
Mitä tapahtuu jos siirrät molemmat serverUrl
ja roomId
komponentin ulkopuolelle?
const serverUrl = 'https://localhost:1234';
const roomId = 'general';
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ Kaikki riippuvuudet määritelty
// ...
}
Nyt Efektisi koodi ei käytä yhtään reaktiivista arvoa, joten sen riippuvuuslista voi olla tyhjä ([]
).
Ajattelu komponentin perspektiivista, tyhjä []
riippuvuuslista tarkoittaa, että tämä Efekti yhdistää chat-huoneeseen vain kun komponentti mounttaa, ja katkaisee yhteyden vain kun komponentti unmounttaa. (Pidä mielessä, että React silti synkronoi ylimääräisen kerran kehityksessä testatakseen logiikkaasi.)
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; const roomId = 'general'; function ChatRoom() { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, []); return <h1>Welcome to the {roomId} room!</h1>; } export default function App() { const [show, setShow] = useState(false); return ( <> <button onClick={() => setShow(!show)}> {show ? 'Close chat' : 'Open chat'} </button> {show && <hr />} {show && <ChatRoom />} </> ); }
Kuitenkin, jos ajattelet Efektin perspektiivista, sinun ei tarvitse ajatella mountaamista ja unmountaamista ollenkaan. Tärkeää on se, että olet määritellyt mitä Efektisi tarvitsee synkronoinnin aloittamiseen ja lopettamiseen. Tänään, sillä ei ole yhtään reaktiivista riippuvuutta. Mutta jos haluat koskaan käyttäjän muuttavan roomId
:n tai serverUrl
:n ajan kanssa (ja ne tulisivat reaktiivisiksi), Efektisi koodi ei muutu. Sinun täytyy vain lisätä ne riippuvuuksiin.
Kaikki muuttujat komponentin sisällä ovat reaktiivisia
Propsit ja tila eivät ole ainoita reaktiivisia arvoja. Arvot jotka niistä lasket ovat myös reaktiivisia. Jos propsit tai tila muuttuu, komponenttisi tulee renderöitymään uudelleen, ja niistä lasketut arvot myös muuttuvat. Tämän takia kaikki Efektin muuttujat tulisi lisätä Efektin riippuvuuslistalle.
Sanotaan, että käyttäjä voi valita chat-palvelimen pudotusvalikosta, mutta he voivat myös määrittää oletuspalvelimen asetuksissa. Oletetaan, että olet jo laittanut asetukset -tilan kontekstiin, joten luet settings
:in siitä kontekstista. Nyt lasket serverUrl
:n valitun palvelimen ja oletuspalvelimen perusteella:
function ChatRoom({ roomId, selectedServerUrl }) { // roomId on reaktiivinen
const settings = useContext(SettingsContext); // settings on reaktiivinen
const serverUrl = selectedServerUrl ?? settings.defaultServerUrl; // serverUrl on reaktiivinen
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // Efektisi lukee roomId ja serverUrl
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId, serverUrl]); // Joten sen täytyy synkronoida uudelleen kun jompi kumpi niistä muuttuu!
// ...
}
Tässä esimerkissä, serverUrl
ei ole propsi tai tilamuuttuja. Se on tavallinen muuttuja, jonka lasket renderöinnin aikana. Mutta se on laskettu renderöinnin aikana, jolloin se voi muuttua uudelleen renderöinnin seurauksena. Tämä on syy miksi se on reaktiivinen.
Kaikki komponentin sisällä olevat arvot (mukaan lukien propsit, tila, ja muuttujat komponenttisi sisällä) ovat reaktiivisia. Mikä tahansa reaktiivinen arvo voi muuttua uudelleen renderöinnin seurauksena, joten sinun täytyy sisällyttää reaktiiviset arvot Efektin riippuvuuslistalle.
Toisin sanoen, Efektisi “reagoi” kaikkii arvoihin komponentin sisällä.
Syväsukellus
Mutatoitavat arovot (mukaan lukien globaalit muttujat) eivät ole reaktiivisia.
Mutatoitava arvo kuten location.pathname
ei voi olla riippuvuus. Se on mutatoitavissa, joten se voi muuttua koska vain täysin Reactin renderöinnin datavirtauksen ulkopuolella. Sen muuttaminen ei käynnistäisi komponenttisi uudelleenrenderöintiä. Tämän takia, vaikka määrittelisit sen riippuvuuksissasi, React ei tietäisi synkronoida Efektiasi uudelleen sen muuttuessa. Tämä myös rikkoo Reactin sääntöjä, koska mutatoitavan datan lukeminen renderöinnin aikana (joka on kun lasket riippuvuuksia) rikkoo renderöinnin puhtauden. Sen sijaan, sinun tulisi lukea ja tilata ulkoisesta mutatoitavasta arvosta useSyncExternalStore
:n avulla.
Mutatoitava arvo kuten ref.current
tai siitä luettavat asiat eivät myöskään voi olla riippuvuuksia. useRef
:n palauttama ref-objekti voi olla riippuvuus, mutta sen current
-ominaisuus on tarkoituksella mutatoitava. Sen avulla voit pitää kirjaa jostain ilman, että se käynnistää uudelleenrenderöinnin. Mutta koska sen muuttaminen ei käynnistä uudelleenrenderöintiä, se ei ole reaktiivinen arvo, eikä React tiedä synkronoida Efektiasi uudelleen sen muuttuessa.
Kuten tulet oppimaan tällä sivulla, linter tulee myös tarkistamaan näitä ongelmia automaattisesti.
React tarkistaa, että olet määrittänyt jokaisen reaktiivisen arvon riippuvuudeksi
Jos linterisi on konfiguroitu Reactille, se tarkistaa, että jokainen reaktiivinen arvo, jota Efektisi koodi käyttää on määritelty sen riippuvuudeksi. Esimerkiksi, tämä on linterin virhe koska molemmat roomId
ja serverUrl
ovat reaktiivisia:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { // roomId on reaktiivinen const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl on reaktiivinen useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, []); // <-- Jokin on pielessä täällä! return ( <> <label> Server URL:{' '} <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /> </label> <h1>Welcome to the {roomId} room!</h1> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Tämä saattaa näyttää React-virheeltä, mutta oikeasti React näyttää bugin koodissasi. Molemmat roomId
sekä serverUrl
voivat muuttua ajan kanssa, mutta unohdat synkronoida Efektisi kun ne muuttuvat. Pysyt yhdistettynä alkuperäiseen roomId
ja serverUrl
vaikka käyttäjä valitsisi eri arvot käyttöliittymässä.
Korjataksesi bugin, seuraa linterin ehdotusta määrittääksesi roomId
ja serverUrl
Efektisi riippuvuuksiksi:
function ChatRoom({ roomId }) { // roomId on reaktiivinen
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // serverUrl on reaktiivinen
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]); // ✅ Kaikki riippuvuudet määritelty
// ...
}
Kokeile tätä korjausta hiekkalaatikossa yllä. Varmista, että linterin virhe on poistunut, ja chat yhdistyy kun se on tarpeellista.
Mitä tehdä kun et halua synkronoida uudelleen
Edellisessä esimerkissä, olet korjannut linter-virheen listaamalla roomId
ja serverUrl
riippuvuuksina.
Kuitenkin, voisit sen sijaan “todistaa” linterille, että nämä arvot eivät ole reaktiivisia arvoja, eli että ne eivät voi muuttua uudelleen renderöinnin seurauksena. Esimerkiksi, jos serverUrl
ja roomId
eivät riipu renderöinnistä ja ovat aina samoja arvoja, voit siirtää ne komponentin ulkopuolelle. Nyt niiden ei tarvitse olla riippuvuuksia:
const serverUrl = 'https://localhost:1234'; // serverUrl ei ole reaktiivinen
const roomId = 'general'; // roomId ei ole reaktiivinen
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ Kaikki riippuvuudet määritelty
// ...
}
Voit myös siirtää ne Efektin sisälle. Niitä ei lasketa renderöinnin aikana, joten ne eivät ole reaktiivisia:
function ChatRoom() {
useEffect(() => {
const serverUrl = 'https://localhost:1234'; // serverUrl ei ole reaktiivinen
const roomId = 'general'; // roomId ei ole reaktiivinen
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, []); // ✅ Kaikki riippuvuudet määritelty
// ...
}
Efektit ovat reaktiivisia koodinpalasia. Ne synkronoituvat uudelleen kun arvot, joita niiden sisällä luet muuttuvat. Toisin kuin tapahtumankäsittelijät, jotka suoritetaan vain kerran jokaista interaktiota kohden, Efektit suoritetaan aina kun synkronointi on tarpeellista.
Et voi “päättää” riippuvuuksiasi. Riippuvuutesi täytyy sisältää kaikki reaktiiviset arvot, joita luet Efektissa. Linter edellyttää tätä. Joskus tämä saattaa aiheuttaa ongelmia kuten loputtomia silmukoita ja Efektisi liian usein synkronoimisen. Älä korjaa näitä ongelmia hiljentämällä linter! Kokeile sen sijaan seuraavaa:
-
Tarkista, että Efektisi edustaa yksittäistä synkronointiprosessia. Jos Efektisi ei synkronoi mitään, se saattaa olla turha. Jos se synkronoi useita yksittäisiä asioita, jaa se osiin.
-
Jos haluat lukea propsien tai tilan arvon ilman “reagointia” tai Efektin synkronoimista uudelleen, voit jakaa Efektisi reaktiiviseen osaan (joka pidetään Efektissä) ja ei-reaktiiviseen osaan (joka erotetaan Efektin tapahtumaksi). Lue lisää tapahtumien erottamisesta Efekteistä.
-
Vältä riippuvuutta olioista ja funktioista. Jos luot olioita ja funktioita renderöinnin aikana ja luet niitä Efekteissä, ne ovat uusia joka renderöinnillä. Tämä aiheuttaa Efektisi synkronoimisen uudelleen joka kerta. Lue lisää tarpeettomien riippuvuuksien poistamisesta Efekteistä.
Kertaus
- Komponentit voivat mountata, päivittää, ja unmountata.
- Jokaisella Efektilla on erillinen elinkaari ympäröivästä komponentista.
- Jokainen Efekti kuvaa erillistä synkronointiprosessia, joka voi alkaa ja loppua.
- Kun kirjoitat ja luet Efekteja, ajattele jokaisen yksittäisen Efektin perspektiivistä (miten aloittaa ja lopettaa synkronointi) sen sijaan, että ajattelisit komponentin perspektiivistä (miten se mounttaa, päivittyy, tai unmounttaa).
- Komponentin sisällä määritellyt arvot ovat “reaktiivisia”.
- Reaktiivisten arvojen tulisi synkronoida Efekti uudelleen koska ne voivat muuttua ajan kanssa.
- Linter vahvistaa, että kaikki Efektin sisällä käytetyt reaktiiviset arvot on määritelty riippuvuuksiksi.
- Kaikki linterin virheet ovat todellisia. On aina tapa korjata koodi siten, ettei se riko sääntöjä.
Haaste 1 / 5: Korjaa yhdistäminen jokaisella näppäinpainalluksella
Tässä esimerkissä, ChatRoom
komponentti yhdistää chat-huoneeseen kun komponentti mounttaa, katkaisee yhteyden kun se unmounttaa, ja yhdistää uudelleen kun valitset eri chat-huoneen. Tämä käyttäytyminen on oikein, joten sinun täytyy pitää se toiminnassa.
Kuitenkin, on ongelma. Aina kun kirjoitat viestilaatikkoon alhaalla, ChatRoom
myös yhdistää chatiin uudelleen. (Voit huomata tämän tyhjentämällä konsolin ja kirjoittamalla viestilaatikkoon.) Korjaa ongelma niin, ettei näin tapahdu.
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }); return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }