23. Progressive Enhancement
Progressive Enhancement — философия, при которой базовая функциональность работает без JavaScript, а JS лишь улучшает опыт. Remix реализует это через <Form> и стандартные HTML формы.
Принцип работы
Заголовок раздела «Принцип работы»Без JavaScript: <Form method="post"> → обычная HTML форма → POST запрос → redirect
С JavaScript: <Form method="post"> → fetch запрос → обновление данных без перезагрузкиФорма, работающая без JS
Заголовок раздела «Форма, работающая без JS»// Эта форма работает в обоих случаяхexport async function action({ request }) { const formData = await request.formData(); const email = formData.get("email");
await subscribeUser(email); return redirect("/thank-you"); // работает и без JS!}
export default function Subscribe() { return ( <Form method="post"> <input type="email" name="email" required /> <button type="submit">Подписаться</button> </Form> );}Улучшение с useNavigation
Заголовок раздела «Улучшение с useNavigation»export default function Subscribe() { const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting";
return ( <Form method="post"> <input type="email" name="email" required /> <button type="submit" disabled={isSubmitting}> {/* Без JS: всегда "Подписаться" */} {/* С JS: меняется на "Подписка..." */} {isSubmitting ? "Подписка..." : "Подписаться"} </button> </Form> );}Условное улучшение
Заголовок раздела «Условное улучшение»import { useHydrated } from "remix-utils/use-hydrated";
function EnhancedButton() { const hydrated = useHydrated();
if (!hydrated) { // Серверный рендер — простой HTML элемент return <button type="submit">Сохранить</button>; }
// После гидратации — богатый UI return ( <button type="submit" onClick={handleClick} data-tooltip="Сохранить изменения" > 💾 Сохранить </button> );}Семантический HTML
Заголовок раздела «Семантический HTML»Progressive Enhancement начинается с правильного HTML:
// ❌ Неправильно<div onClick={handleSubmit}> <div>Отправить</div></div>
// ✅ Правильно<Form method="post"> <fieldset> <legend>Контактная форма</legend> <label htmlFor="name">Имя</label> <input id="name" name="name" type="text" required /> <button type="submit">Отправить</button> </fieldset></Form>Graceful Degradation для JS-компонентов
Заголовок раздела «Graceful Degradation для JS-компонентов»// Дата-пикер с fallback на input[type="date"]function DatePicker({ name, value }) { const [hydrated, setHydrated] = useState(false);
useEffect(() => setHydrated(true), []);
if (!hydrated) { return <input type="date" name={name} defaultValue={value} />; }
return <FancyDatePicker name={name} value={value} />;}