Odkryj strumieniowanie odpowiedzi w React Server Actions dla progresywnych odpowiedzi formularzy. Dowiedz się, jak tworzyć szybsze i bardziej responsywne formularze dla lepszego doświadczenia użytkownika.
Strumieniowanie odpowiedzi w React Server Actions: Progresywna odpowiedź formularza dla lepszego UX
React Server Actions wprowadzają potężną zmianę paradygmatu w sposobie, w jaki obsługujemy operacje po stronie serwera w naszych aplikacjach React. Jedną z najbardziej ekscytujących funkcji jest możliwość progresywnego strumieniowania odpowiedzi, co pozwala nam na dostarczanie natychmiastowej informacji zwrotnej użytkownikom, zanim cała operacja zostanie zakończona. Jest to szczególnie korzystne w przypadku formularzy, gdzie możemy stworzyć bardziej responsywne i angażujące doświadczenie użytkownika, aktualizując interfejs w miarę pojawiania się danych.
Zrozumienie React Server Actions
Server Actions to asynchroniczne funkcje uruchamiane na serwerze, inicjowane z komponentów React. Oferują one kilka zalet w porównaniu z tradycyjnymi wywołaniami API:
- Zwiększone bezpieczeństwo: Server Actions działają bezpośrednio na serwerze, zmniejszając ryzyko ujawnienia wrażliwych danych lub logiki klientowi.
- Mniej kodu szablonowego: Eliminują potrzebę tworzenia osobnych tras API i logiki pobierania danych po stronie klienta.
- Lepsza wydajność: Mogą wykorzystywać renderowanie po stronie serwera (SSR) i buforowanie, co przekłada się na szybsze czasy ładowania początkowego i lepszą wydajność.
- Bezpieczeństwo typów: W połączeniu z TypeScript, Server Actions zapewniają bezpieczeństwo typów od początku do końca, gwarantując spójność danych między klientem a serwerem.
Potęga strumieniowania odpowiedzi
Tradycyjne przesyłanie formularzy często polega na wysłaniu wszystkich danych na serwer, oczekiwaniu na odpowiedź, a następnie odpowiedniej aktualizacji interfejsu użytkownika. Może to prowadzić do odczuwalnego opóźnienia, szczególnie w przypadku złożonych formularzy lub wolnych połączeń sieciowych. Strumieniowanie odpowiedzi pozwala serwerowi wysyłać dane z powrotem do klienta w częściach, co umożliwia progresywną aktualizację interfejsu w miarę pojawiania się danych.
Wyobraź sobie formularz, który oblicza skomplikowaną cenę na podstawie danych wprowadzonych przez użytkownika. Zamiast czekać na zakończenie całego obliczenia, serwer może strumieniowo przesyłać wyniki pośrednie z powrotem do klienta, dostarczając użytkownikowi informacji zwrotnej w czasie rzeczywistym. Może to znacznie poprawić doświadczenie użytkownika i sprawić, że aplikacja będzie wydawać się bardziej responsywna.
Implementacja progresywnej odpowiedzi formularza za pomocą Server Actions
Przeanalizujmy przykład implementacji progresywnej odpowiedzi formularza za pomocą React Server Actions.
Przykład: Przelicznik walut w czasie rzeczywistym
Stworzymy prosty formularz przelicznika walut, który dostarcza aktualizacje kursów wymiany w czasie rzeczywistym, gdy użytkownik wpisuje kwotę.
1. Konfiguracja Server Action
Najpierw zdefiniujemy Server Action, która obsługuje przeliczanie walut.
// server/actions.ts
'use server';
import { unstable_cache } from 'next/cache';
async function getExchangeRate(fromCurrency: string, toCurrency: string): Promise<number> {
// Symulacja pobierania kursu wymiany z zewnętrznego API
console.log(`Fetching exchange rate for ${fromCurrency} to ${toCurrency}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Symulacja opóźnienia sieciowego
if (fromCurrency === 'USD' && toCurrency === 'EUR') return 0.92;
if (fromCurrency === 'EUR' && toCurrency === 'USD') return 1.09;
if (fromCurrency === 'USD' && toCurrency === 'JPY') return 145;
if (fromCurrency === 'JPY' && toCurrency === 'USD') return 0.0069;
throw new Error(`Exchange rate not found for ${fromCurrency} to ${toCurrency}`);
}
export const convertCurrency = async (prevState: any, formData: FormData) => {
const fromCurrency = formData.get('fromCurrency') as string;
const toCurrency = formData.get('toCurrency') as string;
const amount = Number(formData.get('amount'));
try {
if (!fromCurrency || !toCurrency || isNaN(amount)) {
return { message: 'Please provide valid input.' };
}
// Symulacja strumieniowania odpowiedzi
await new Promise(resolve => setTimeout(resolve, 250));
const exchangeRate = await unstable_cache(
async () => getExchangeRate(fromCurrency, toCurrency),
[`exchange-rate-${fromCurrency}-${toCurrency}`],
{ tags: [`exchange-rate-${fromCurrency}-${toCurrency}`] }
)();
await new Promise(resolve => setTimeout(resolve, 250));
const convertedAmount = amount * exchangeRate;
return { message: `Converted amount: ${convertedAmount.toFixed(2)} ${toCurrency}` };
} catch (e: any) {
console.error(e);
return { message: 'Failed to convert currency.' };
}
};
W tym przykładzie, Server Action convertCurrency
pobiera kurs wymiany (symulowany z opóźnieniem) i oblicza przeliczoną kwotę. Dodaliśmy sztuczne opóźnienia za pomocą setTimeout
, aby zasymulować opóźnienie sieciowe i zademonstrować efekt strumieniowania.
2. Implementacja komponentu React
Następnie tworzymy komponent React, który używa Server Action.
// app/page.tsx
'use client';
import { useState, useTransition } from 'react';
import { convertCurrency } from './server/actions';
import { useFormState } from 'react-dom';
export default function CurrencyConverter() {
const [fromCurrency, setFromCurrency] = useState('USD');
const [toCurrency, setToCurrency] = useState('EUR');
const [amount, setAmount] = useState('');
const [isPending, startTransition] = useTransition();
const [state, formAction] = useFormState(convertCurrency, { message: '' });
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
startTransition(() => {
formAction(new FormData(event.target as HTMLFormElement));
});
};
return (
<div>
<h2>Przelicznik walut w czasie rzeczywistym</h2>
<form action={handleSubmit}>
<label htmlFor="fromCurrency">Z:</label>
<select id="fromCurrency" name="fromCurrency" value={fromCurrency} onChange={(e) => setFromCurrency(e.target.value)}>
<option value="USD">USD</option>
<option value="EUR">EUR</option>
<option value="JPY">JPY</option>
</select>
<label htmlFor="toCurrency">Do:</label>
<select id="toCurrency" name="toCurrency" value={toCurrency} onChange={(e) => setToCurrency(e.target.value)}>
<option value="EUR">EUR</option>
<option value="USD">USD</option>
<option value="JPY">JPY</option>
</select>
<label htmlFor="amount">Kwota:</label>
<input
type="number"
id="amount"
name="amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<button type="submit" disabled={isPending}>
{isPending ? 'Przeliczanie...' : 'Przelicz'}
</button>
</form>
<p>{state.message}</p>
</div>
);
}
Kluczowe punkty:
- Używamy haka
useFormState
do zarządzania stanem formularza i wywoływania Server Action. - Stan
isPending
zuseTransition
wyłącza przycisk przesyłania i wyświetla komunikat "Przeliczanie...", gdy akcja jest w toku, dając użytkownikowi informację zwrotną. - Funkcja
formAction
zwrócona przezuseFormState
automatycznie obsługuje przesłanie formularza i aktualizuje stan odpowiedzią z Server Action.
3. Zrozumienie progresywnych aktualizacji
Gdy użytkownik przesyła formularz, wywoływana jest funkcja handleSubmit
. Tworzy ona obiekt FormData
z formularza i przekazuje go do funkcji formAction
. Następnie Server Action jest wykonywana na serwerze. Z powodu sztucznych opóźnień wprowadzonych w Server Action, zaobserwujesz następujące zjawiska:
- Przycisk przesyłania niemal natychmiast zmienia się na "Przeliczanie...".
- Po krótkim opóźnieniu (250ms), kod symuluje pobieranie kursu wymiany.
- Przeliczona kwota jest obliczana, a wynik jest odsyłany do klienta.
state.message
w komponencie React jest aktualizowany, wyświetlając przeliczoną kwotę.
To pokazuje, jak strumieniowanie odpowiedzi pozwala nam dostarczać użytkownikowi pośrednie aktualizacje w miarę pojawiania się danych, co prowadzi do bardziej responsywnego i angażującego doświadczenia użytkownika.
Zalety progresywnej odpowiedzi formularza
- Lepsze doświadczenie użytkownika: Zapewnia natychmiastową informację zwrotną użytkownikom, sprawiając, że aplikacja wydaje się bardziej responsywna i mniej ociężała.
- Zmniejszone odczuwalne opóźnienie: Pokazując wyniki pośrednie, użytkownicy postrzegają proces jako szybszy, nawet jeśli cała operacja zajmuje tyle samo czasu.
- Większe zaangażowanie: Utrzymuje zaangażowanie użytkowników poprzez dostarczanie aktualizacji w czasie rzeczywistym i zapobieganie porzucaniu formularza z powodu odczuwalnych opóźnień.
- Zwiększone współczynniki konwersji: Płynniejsze i bardziej responsywne doświadczenie użytkownika może prowadzić do wyższych współczynników konwersji, szczególnie w przypadku złożonych formularzy.
Zaawansowane techniki
1. Użycie `useOptimistic` do natychmiastowych aktualizacji UI
Hak useOptimistic
pozwala na optymistyczną aktualizację interfejsu użytkownika, zanim Server Action zostanie zakończona. Może to zapewnić jeszcze szybszy odczuwalny czas reakcji, ponieważ interfejs natychmiast odzwierciedla oczekiwany wynik.
import { useOptimistic } from 'react';
function MyComponent() {
const [optimisticState, addOptimistic] = useOptimistic(
initialState,
(state, newUpdate) => {
// Zwróć nowy stan na podstawie aktualizacji
return { ...state, ...newUpdate };
}
);
const handleClick = async () => {
addOptimistic({ someValue: 'optimistic update' });
await myServerAction();
};
return (
<div>
<p>{optimisticState.someValue}</p>
<button onClick={handleClick}>Aktualizuj</button>
</div>
);
}
W przykładzie z przelicznikiem walut można by optymistycznie zaktualizować przeliczoną kwotę na podstawie bieżącego kursu wymiany, dostarczając użytkownikowi natychmiastowy podgląd, zanim faktyczne obliczenie na serwerze zostanie zakończone. Jeśli serwer zwróci błąd, można wycofać optymistyczną aktualizację.
2. Implementacja obsługi błędów i mechanizmów zapasowych
Kluczowe jest wdrożenie solidnej obsługi błędów i mechanizmów zapasowych, aby radzić sobie w przypadkach, gdy Server Action zawiedzie lub połączenie sieciowe zostanie przerwane. Możesz użyć bloku try...catch
wewnątrz Server Action, aby przechwytywać błędy i zwracać odpowiedni komunikat o błędzie do klienta.
// server/actions.ts
export const convertCurrency = async (prevState: any, formData: FormData) => {
// ...
try {
// ...
} catch (error: any) {
console.error(error);
return { message: 'Wystąpił błąd podczas przeliczania waluty. Spróbuj ponownie później.' };
}
};
Po stronie klienta możesz wyświetlić użytkownikowi komunikat o błędzie i udostępnić opcje ponowienia operacji lub skontaktowania się z pomocą techniczną.
3. Buforowanie kursów wymiany dla wydajności
Pobieranie kursów wymiany z zewnętrznego API może stanowić wąskie gardło wydajności. Aby poprawić wydajność, można buforować kursy wymiany za pomocą mechanizmu buforowania, takiego jak Redis lub Memcached. Funkcja unstable_cache
z Next.js (użyta w przykładzie) zapewnia wbudowane rozwiązanie do buforowania. Pamiętaj o okresowym unieważnianiu pamięci podręcznej, aby zapewnić aktualność kursów wymiany.
4. Kwestie internacjonalizacji
Podczas tworzenia aplikacji dla globalnej publiczności ważne jest uwzględnienie internacjonalizacji (i18n). Obejmuje to:
- Formatowanie liczb: Używaj odpowiednich formatów liczb dla różnych lokalizacji (np. używając przecinków lub kropek jako separatorów dziesiętnych).
- Formatowanie walut: Wyświetlaj symbole i formaty walut zgodnie z lokalizacją użytkownika.
- Formatowanie daty i czasu: Używaj odpowiednich formatów daty i czasu dla różnych lokalizacji.
- Lokalizacja: Przetłumacz interfejs użytkownika na różne języki.
Biblioteki takie jak Intl
i react-intl
mogą pomóc w implementacji i18n w aplikacjach React.
Przykłady i zastosowania w świecie rzeczywistym
- E-commerce: Wyświetlanie w czasie rzeczywistym kosztów wysyłki i szacunkowych terminów dostawy, gdy użytkownik dodaje produkty do koszyka.
- Aplikacje finansowe: Dostarczanie notowań giełdowych i aktualizacji portfela w czasie rzeczywistym.
- Rezerwacje podróży: Pokazywanie cen i dostępności lotów w czasie rzeczywistym.
- Wizualizacja danych: Strumieniowanie aktualizacji danych do wykresów i grafów.
- Narzędzia do współpracy: Wyświetlanie aktualizacji dokumentów i projektów w czasie rzeczywistym.
Podsumowanie
Strumieniowanie odpowiedzi w React Server Actions oferuje potężny sposób na ulepszenie doświadczenia użytkownika w aplikacjach React. Dostarczając progresywne odpowiedzi formularzy, możesz tworzyć szybsze, bardziej responsywne i bardziej angażujące formularze, które utrzymują zaangażowanie użytkowników i poprawiają współczynniki konwersji. Łącząc strumieniowanie odpowiedzi z technikami takimi jak optymistyczne aktualizacje i buforowanie, możesz tworzyć naprawdę wyjątkowe doświadczenia użytkownika.
W miarę jak React Server Actions będą się rozwijać, możemy spodziewać się pojawienia jeszcze potężniejszych funkcji i możliwości, które jeszcze bardziej uproszczą tworzenie złożonych i dynamicznych aplikacji internetowych.
Dalsze materiały
Ten przewodnik stanowi kompleksowy przegląd strumieniowania odpowiedzi w React Server Actions i jego zastosowania w progresywnych odpowiedziach formularzy. Rozumiejąc przedstawione tu koncepcje i techniki, możesz wykorzystać tę potężną funkcję do tworzenia szybszych, bardziej responsywnych i bardziej angażujących aplikacji internetowych.