LÀr dig implementera framstegsestimering och förutsÀgelse av slutförandetid med Reacts useFormStatus-hook för att förbÀttra anvÀndarupplevelsen i datatunga applikationer.
React useFormStatus Framstegsestimering: FörutsÀgelse av Slutförandetid
Reacts useFormStatus-hook, som introducerades i React 18, ger vĂ€rdefull information om statusen för en formulĂ€rinskickning. Ăven om den inte direkt erbjuder framstegsestimering kan vi utnyttja dess egenskaper och andra tekniker för att ge anvĂ€ndare meningsfull Ă„terkoppling under potentiellt lĂ„ngvariga formulĂ€rinskickningar. Det hĂ€r inlĂ€gget utforskar metoder för att estimera framsteg och förutsĂ€ga slutförandetid nĂ€r man anvĂ€nder useFormStatus, vilket resulterar i en mer engagerande och anvĂ€ndarvĂ€nlig upplevelse.
FörstÄelse för useFormStatus
Innan vi dyker in i framstegsestimering, lÄt oss snabbt sammanfatta syftet med useFormStatus. Denna hook Àr utformad för att anvÀndas inuti ett <form>-element som anvÀnder action-propen. Den returnerar ett objekt som innehÄller följande egenskaper:
pending: En boolean som indikerar om formulÀret för nÀrvarande skickas in.data: Datan som skickades med formulÀret (om inskickningen lyckades).method: HTTP-metoden som anvÀndes för formulÀrinskickningen (t.ex. 'POST', 'GET').action: Funktionen som skickades till formulÀretsaction-prop.error: Ett felobjekt om inskickningen misslyckades.
Ăven om useFormStatus talar om för oss om formulĂ€ret skickas in, ger den ingen direkt information om framstegen för inskickningen, sĂ€rskilt om action-funktionen involverar komplexa eller lĂ„ngvariga operationer.
Utmaningen med Framstegsestimering
KÀrnutmaningen ligger i det faktum att exekveringen av action-funktionen Àr ogenomskinlig för React. Vi vet inte i sig hur lÄngt processen har kommit. Detta gÀller sÀrskilt för operationer pÄ serversidan. Vi kan dock anvÀnda olika strategier för att övervinna denna begrÀnsning.
Strategier för Framstegsestimering
HÀr Àr flera tillvÀgagÄngssÀtt du kan anvÀnda, var och en med sina egna avvÀgningar:
1. Server-Sent Events (SSE) eller WebSockets
Den mest robusta lösningen Àr ofta att skicka framstegsuppdateringar frÄn servern till klienten. Detta kan uppnÄs med:
- Server-Sent Events (SSE): Ett enkelriktat (server-till-klient) protokoll som lÄter servern skicka uppdateringar till klienten över en enda HTTP-anslutning. SSE Àr idealiskt nÀr klienten bara behöver *ta emot* uppdateringar.
- WebSockets: Ett dubbelriktat kommunikationsprotokoll som ger en bestÄende anslutning mellan klient och server. WebSockets Àr lÀmpliga för realtidsuppdateringar i bÄda riktningarna.
Exempel (SSE):
Serversidan (Node.js):
const express = require('express');
const app = express();
app.get('/progress', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
let progress = 0;
const interval = setInterval(() => {
progress += 10;
if (progress > 100) {
progress = 100;
clearInterval(interval);
res.write(`data: {"progress": ${progress}, "completed": true}\n\n`);
res.end();
} else {
res.write(`data: {"progress": ${progress}, "completed": false}\n\n`);
}
}, 500); // Simulera framstegsuppdatering var 500:e ms
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
Klientsidan (React):
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const eventSource = new EventSource('/progress');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
setProgress(data.progress);
if (data.completed) {
eventSource.close();
}
};
eventSource.onerror = (error) => {
console.error('EventSource failed:', error);
eventSource.close();
};
return () => {
eventSource.close();
};
}, []);
return (
<div>
<p>Progress: {progress}%</p>
</div>
);
}
export default MyComponent;
Förklaring:
- Servern sÀtter de lÀmpliga headrarna för SSE.
- Servern skickar framstegsuppdateringar som
data:-hÀndelser. Varje hÀndelse Àr ett JSON-objekt som innehÄllerprogressoch encompleted-flagga. - React-komponenten anvÀnder
EventSourceför att lyssna pÄ dessa hÀndelser. - Komponenten uppdaterar sitt tillstÄnd (
progress) baserat pÄ de mottagna hÀndelserna.
Fördelar: Exakta framstegsuppdateringar, Äterkoppling i realtid.
Nackdelar: KrÀver Àndringar pÄ serversidan, mer komplex implementering.
2. Polling med en API-Ă€ndpunkt
Om du inte kan anvÀnda SSE eller WebSockets kan du implementera polling. Klienten skickar periodvis förfrÄgningar till servern för att kontrollera statusen pÄ operationen.
Exempel:
Serversidan (Node.js):
const express = require('express');
const app = express();
// Simulera en lÄngvarig uppgift
let taskProgress = 0;
let taskId = null;
app.post('/start-task', (req, res) => {
taskProgress = 0;
taskId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); // Generera ett unikt uppgifts-ID
// Simulera bakgrundsbearbetning
const interval = setInterval(() => {
taskProgress += 10;
if (taskProgress >= 100) {
taskProgress = 100;
clearInterval(interval);
}
}, 500);
res.json({ taskId });
});
app.get('/task-status/:taskId', (req, res) => {
if (req.params.taskId === taskId) {
res.json({ progress: taskProgress });
} else {
res.status(404).json({ message: 'Task not found' });
}
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
Klientsidan (React):
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [progress, setProgress] = useState(0);
const [taskId, setTaskId] = useState(null);
const startTask = async () => {
const response = await fetch('/start-task', { method: 'POST' });
const data = await response.json();
setTaskId(data.taskId);
};
useEffect(() => {
if (!taskId) return;
const interval = setInterval(async () => {
const response = await fetch(`/task-status/${taskId}`);
const data = await response.json();
setProgress(data.progress);
if (data.progress === 100) {
clearInterval(interval);
}
}, 1000); // Polla varje sekund
return () => clearInterval(interval);
}, [taskId]);
return (
<div>
<button onClick={startTask} disabled={taskId !== null}>Start Task</button>
{taskId && <p>Progress: {progress}%</p>}
</div>
);
}
export default MyComponent;
Förklaring:
- Klienten startar en uppgift genom att anropa
/start-taskoch tar emot etttaskId. - Klienten pollar sedan
/task-status/:taskIdperiodvis för att fÄ framstegen.
Fördelar: Relativt enkelt att implementera, krÀver inga bestÄende anslutningar.
Nackdelar: Kan vara mindre exakt Àn SSE/WebSockets, introducerar latens pÄ grund av pollningsintervallet, belastar servern pÄ grund av frekventa förfrÄgningar.
3. Optimistiska uppdateringar och heuristik
I vissa fall kan du anvÀnda optimistiska uppdateringar i kombination med heuristik för att ge en rimlig uppskattning. Om du till exempel laddar upp filer kan du spÄra antalet uppladdade bytes pÄ klientsidan och uppskatta framstegen baserat pÄ den totala filstorleken.
Exempel (Filuppladdning):
import React, { useState } from 'react';
function MyComponent() {
const [progress, setProgress] = useState(0);
const [file, setFile] = useState(null);
const handleFileChange = (event) => {
setFile(event.target.files[0]);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!file) return;
const formData = new FormData();
formData.append('file', file);
try {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const percentage = Math.round((event.loaded * 100) / event.total);
setProgress(percentage);
}
});
xhr.open('POST', '/upload'); // ErsÀtt med din uppladdnings-Àndpunkt
xhr.send(formData);
xhr.onload = () => {
if (xhr.status === 200) {
console.log('Upload complete!');
} else {
console.error('Upload failed:', xhr.status);
}
};
xhr.onerror = () => {
console.error('Upload failed');
};
} catch (error) {
console.error('Upload error:', error);
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="file" onChange={handleFileChange} />
<button type="submit" disabled={!file}>Upload</button>
</form>
<p>Progress: {progress}%</p>
</div>
);
}
export default MyComponent;
Förklaring:
- Komponenten anvÀnder ett
XMLHttpRequest-objekt för att ladda upp filen. progress-hÀndelselyssnaren pÄxhr.uploadanvÀnds för att spÄra uppladdningens framsteg.- Egenskaperna
loadedochtotalför hÀndelsen anvÀnds för att berÀkna den procentuella slutförandegraden.
Fördelar: Endast pÄ klientsidan, kan ge omedelbar Äterkoppling.
Nackdelar: Noggrannheten beror pÄ heuristikens tillförlitlighet, kanske inte Àr lÀmplig för alla typer av operationer.
4. Dela upp ÄtgÀrden i mindre steg
Om action-funktionen utför flera distinkta steg kan du uppdatera UI:t efter varje steg för att indikera framsteg. Detta krÀver att action-funktionen modifieras för att ge uppdateringar.
Exempel:
import React, { useState } from 'react';
async function myAction(setProgress) {
setProgress(10);
await someAsyncOperation1();
setProgress(40);
await someAsyncOperation2();
setProgress(70);
await someAsyncOperation3();
setProgress(100);
}
function MyComponent() {
const [progress, setProgress] = useState(0);
const handleSubmit = async () => {
await myAction(setProgress);
};
return (
<div>
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
<p>Progress: {progress}%</p>
</div>
);
}
export default MyComponent;
Förklaring:
myAction-funktionen accepterar ensetProgress-callback.- Den uppdaterar framstegstillstÄndet vid olika punkter under sin exekvering.
Fördelar: Direkt kontroll över framstegsuppdateringar.
Nackdelar: KrÀver modifiering av action-funktionen, kan vara mer komplex att implementera om stegen inte Àr lÀtta att dela upp.
FörutsÀga slutförandetid
NÀr du har framstegsuppdateringar kan du anvÀnda dem för att förutsÀga den uppskattade ÄterstÄende tiden. Ett enkelt tillvÀgagÄngssÀtt Àr att spÄra tiden det tar att nÄ en viss framstegsnivÄ och extrapolera för att uppskatta den totala tiden.
Exempel (förenklat):
import React, { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [progress, setProgress] = useState(0);
const [estimatedTimeRemaining, setEstimatedTimeRemaining] = useState(null);
const startTimeRef = useRef(null);
useEffect(() => {
if (progress > 0 && startTimeRef.current === null) {
startTimeRef.current = Date.now();
}
if (progress > 0) {
const elapsedTime = Date.now() - startTimeRef.current;
const estimatedTotalTime = (elapsedTime / progress) * 100;
const remainingTime = estimatedTotalTime - elapsedTime;
setEstimatedTimeRemaining(Math.max(0, remainingTime)); // SÀkerstÀll att det inte Àr negativt
}
}, [progress]);
// ... (resten av komponenten och framstegsuppdateringar som beskrivits i tidigare avsnitt)
return (
<div>
<p>Progress: {progress}%</p>
{estimatedTimeRemaining !== null && (
<p>Estimated Time Remaining: {Math.round(estimatedTimeRemaining / 1000)} seconds</p>
)}
</div>
);
}
export default MyComponent;
Förklaring:
- Vi lagrar starttiden nÀr framstegen först uppdateras.
- Vi berÀknar den förflutna tiden och anvÀnder den för att uppskatta den totala tiden.
- Vi berÀknar den ÄterstÄende tiden genom att subtrahera den förflutna tiden frÄn den uppskattade totala tiden.
Viktiga övervÀganden:
- Noggrannhet: Detta Àr en *mycket* förenklad förutsÀgelse. NÀtverksförhÄllanden, serverbelastning och andra faktorer kan pÄverka noggrannheten avsevÀrt. Mer sofistikerade tekniker, som att berÀkna medelvÀrden över flera intervaller, kan förbÀttra noggrannheten.
- Visuell Äterkoppling: Ange tydligt att tiden Àr en *uppskattning*. Att visa intervall (t.ex. "Uppskattad ÄterstÄende tid: 5-10 sekunder") kan vara mer realistiskt.
- Kantfall: Hantera kantfall dÀr framstegen Àr mycket lÄngsamma i början. Undvik att dividera med noll eller visa överdrivet stora uppskattningar.
Kombinera useFormStatus med framstegsestimering
Ăven om useFormStatus i sig inte ger information om framsteg, kan du anvĂ€nda dess pending-egenskap för att aktivera eller inaktivera framstegsindikatorn. Till exempel:
import React, { useState } from 'react';
import { useFormStatus } from 'react-dom';
// ... (Logik för framstegsestimering frÄn tidigare exempel)
function MyComponent() {
const [progress, setProgress] = useState(0);
const { pending } = useFormStatus();
const handleSubmit = async (formData) => {
// ... (Din logik för formulÀrinskickning, inklusive uppdateringar av framsteg)
};
return (
<form action={handleSubmit}>
<button type="submit" disabled={pending}>Submit</button>
{pending && <p>Progress: {progress}%</p>}
</form>
);
}
I detta exempel visas framstegsindikatorn endast medan formulÀret Àr vÀntande (d.v.s. medan useFormStatus.pending Àr true).
BÀsta praxis och övervÀganden
- Prioritera noggrannhet: VÀlj en teknik för framstegsestimering som Àr lÀmplig för den typ av operation som utförs. SSE/WebSockets ger generellt de mest exakta resultaten, medan heuristik kan vara tillrÀckligt för enklare uppgifter.
- Ge tydlig visuell Äterkoppling: AnvÀnd förloppsindikatorer, spinnare eller andra visuella ledtrÄdar för att visa att en operation pÄgÄr. MÀrk tydligt förloppsindikatorn och, om tillÀmpligt, den uppskattade ÄterstÄende tiden.
- Hantera fel pÄ ett elegant sÀtt: Om ett fel intrÀffar under operationen, visa ett informativt felmeddelande till anvÀndaren. Undvik att lÀmna förloppsindikatorn fast pÄ en viss procentandel.
- Optimera prestanda: Undvik att utföra berÀkningsmÀssigt tunga operationer i UI-trÄden, eftersom detta kan pÄverka prestandan negativt. AnvÀnd web workers eller andra tekniker för att avlasta arbete till bakgrundstrÄdar.
- TillgÀnglighet: SÀkerstÀll att förloppsindikatorer Àr tillgÀngliga för anvÀndare med funktionsnedsÀttningar. AnvÀnd ARIA-attribut för att ge semantisk information om operationens framsteg. AnvÀnd till exempel
aria-valuenow,aria-valueminocharia-valuemaxpÄ en förloppsindikator. - Lokalisering: NÀr du visar uppskattad ÄterstÄende tid, var medveten om olika tidsformat och regionala preferenser. AnvÀnd ett bibliotek som
date-fnsellermoment.jsför att formatera tiden korrekt för anvÀndarens locale. - Internationalisering: Felmeddelanden och annan text bör internationaliseras för att stödja flera sprÄk. AnvÀnd ett bibliotek som
i18nextför att hantera översÀttningar.
Slutsats
Ăven om Reacts useFormStatus-hook inte direkt tillhandahĂ„ller funktioner för framstegsestimering, kan du kombinera den med andra tekniker för att ge anvĂ€ndare meningsfull Ă„terkoppling under formulĂ€rinskickningar. Genom att anvĂ€nda SSE/WebSockets, polling, optimistiska uppdateringar eller genom att dela upp Ă„tgĂ€rder i mindre steg kan du skapa en mer engagerande och anvĂ€ndarvĂ€nlig upplevelse. Kom ihĂ„g att prioritera noggrannhet, ge tydlig visuell Ă„terkoppling, hantera fel pĂ„ ett elegant sĂ€tt och optimera prestanda för att sĂ€kerstĂ€lla en positiv upplevelse för alla anvĂ€ndare, oavsett deras plats eller bakgrund.