javascript - React: Unngå ikke-deterministisk første rendering (hack med «mounted»-tilstand)
TL;DR React gjengir noen ganger en lastetilstand og noen ganger ikke, uten endringer i brukergrensesnittet. Dette skyldes sannsynligvis batchede oppdateringer.
Jeg vil gjerne vite om problemet nedenfor skyldes batch-oppdateringer. Hvis svaret er "ja", vil jeg gjerne vite om det finnes en foretrukket måte å velge bort batch-oppdateringer i React for å få deterministisk rendering. Gå ned til "Eksperiment" hvis du vil hoppe over oppsettet.
Oppsett
Her er oppsettet, et diagram som tar lang tid å gjengi. Så lang tid at gjengivelsen blokkerer. Det er tre forskjellige måter å gjengi diagrammet på her:
- én er den vanlige måten
- en med en «montert» gjengivelseshack
- en med den samme «mounted»-renderhacket, men med en ekstra
setTimeout
Alternativ 2 og 3 har begge en liten useState for å sjekke om de er montert. Jeg gjør dette for å vise en «Laster»-tilstand betinget:
function ChartWithMountHack({ data }: { data: Data }) {
// initially not mounted
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
// "Now I've been mounted!"
setIsMounted(true);
}, []);
return !isMounted ? <p>Loading</p> : <Chart data={data} />;
}
Jeg gjorde dette fordi jeg vil vise en «Laster»-tilstand i stedet for en blokkerende rendering, slik at f.eks. sideskifter eller ternære renderinger (f.eks. hasData ? <p>Ingen data</p> : <Chart />) vises umiddelbart, i stedet for å blokkere. (Hvis det finnes bedre måter, gi meg beskjed!)
Eksperiment
Nå vil hver knapp vise ett av de tre alternativene/diagrammene. Igjen, det andre og tredje diagrammet har en liten løsning for å sjekke om de er montert eller ikke.
Prøv å klikke på den første knappen og den andre knappen frem og tilbake raskt. Du vil se at noen ganger vil "Chart with mount hack" ("korrrekt") gjengi "Laster"-tilstanden, men noen ganger gjengir det bare ikke "Laster" - i stedet blokkerer det gjengivelsen helt til diagrammet er ferdig med å gjengi (hopper over "Laster"-tilstanden).
Jeg tror dette skyldes render-sykluser og om du får de to oppdateringene i én syklus av batching. (først: isMounted === false -> deretter: isMounted === true)
Jeg kan egentlig ikke si hvordan man gjenskaper dette, derav «nondeterministic» i tittelen. Noen ganger må du også klikke på «Regenerate data» og klikke frem og tilbake etter det.
Kryssjekk
Alternativ 3 ("Diagram med mount-hack med timeout") gir meg ALLTID "Laster"-tilstanden, som er akkurat det jeg ønsker. Den eneste forskjellen fra alternativ 2 er bruk av en setTimeout i useEffect der isMounted settes til true. setTimeout brukes her for å bryte ut av oppdateringsbatching.
Finnes det en bedre måte å velge bort batchingen på, slik at isMounted alltid vil renderes med sin opprinnelige verdi (false)? Å bruke setTimeout her føles som en midlertidig løsning.
Svar #1
React har samtidige funksjoner for å håndtere denne typen ting, for eksempel React Suspense-merker, eller du kan bruke abonnementbiblioteker som Rxjs, hvor abonnementet bør gjøres i componentDidMount og componentWillUnmount for å avmelde dataene.
Da er isMounted bare en midlertidig løsning for et pågående problem, sannsynligvis fra biblioteket du bruker eller noen ganger bare verktøyet ditt for bundling/build som oppfører seg litt rart.
Til slutt, for å unngå unødvendig re-rendering, kan du bruke React-memoisering av komponenten ved å bruke React.Memo.
Vennligst les mer om disse.
