Локальное хранение настроек
Применённая тема исчезает после закрытия страницы, если хранится только в переменной. Для небольших предпочтений можно использовать localStorage. Но чтение может быть запрещено, запись может не удаться, а прежнее содержимое может иметь неверный формат. Интерфейс должен продолжать работать с понятными начальными значениями.
Возьмём независимый снимок advanced/lesson-27. Он рассматривает только тему и порядок, без личных целей, токенов или содержимого статей. Данные находятся в браузере данного происхождения; сервер не получает готовую синхронизацию предпочтений. Пример пока не запускался, поэтому восстановление и отказ описываются как ожидаемые результаты.
Ключ и версия формата
Хранилище сопоставляет строковые ключи со строковыми значениями. Наш ключ catalog-lab.preferences.v1 обозначает одну запись настроек. Внутри JSON также содержится версия, чтобы чтение не приняло произвольный старый объект за текущий договор.
{
"version": 1,
"topic": "frontend",
"sort": "lessons"
}
Это пример сохраняемой записи. Наличие корректного JSON не доказывает, что тема разрешена модели. После разбора проверяем версию и допустимые значения обоих полей. Нет записи — используем all/original; повреждена запись — используем те же значения, не показывая пустую страницу.
Не называйте ключ только settings, если в одном происхождении работают несколько приложений. Ясное пространство имён уменьшает риск случайного пересечения. Также не очищайте всё хранилище ради сброса каталога: там могут находиться независимые данные других частей сайта. Сброс должен удалить конкретный известный ключ.
Чтение с безопасным исходом
Полная функция чтения принимает необязательный объект хранилища. Обычный вызов использует браузерный localStorage, но доступ к нему происходит внутри try:
const key = 'catalog-lab.preferences.v1';
const fallback = { topic: 'all', sort: 'original' };
export function readPreferences(storage) {
try {
const source = storage ?? localStorage;
const raw = source.getItem(key);
if (raw === null) return { ...fallback };
const value = JSON.parse(raw);
if (value?.version !== 1 ||
!['all', 'frontend', 'publishing'].includes(value.topic) ||
!['original', 'lessons'].includes(value.sort)) {
return { ...fallback };
}
return { topic: value.topic, sort: value.sort };
} catch (error) {
console.warn('Настройки не прочитаны:', error);
return { ...fallback };
}
}
Доступ к самому свойству хранилища может вызвать отказ в некоторых условиях. Поэтому запись параметра storage = localStorage до тела функции была бы неудобна: вычисление значения по умолчанию произошло бы вне нашего внутреннего try. Явное получение внутри защищённой границы делает политику отказа понятной.
Начальные настройки копируются в новый объект. Вызывающий код может дальше хранить собственное состояние, не изменяя общий fallback через ссылку. Такой небольшой приём сохраняет знакомую границу между договором по умолчанию и текущим выбором.
Основное поведение происхождения и возможные ограничения рассматриваются в MDN о localStorage. При открытии по file:// нельзя рассчитывать на тот же договор, что при HTTP; наш снимок обслуживается локальным сервером вместе с остальными уроками.
Запись не означает гарантированное сохранение
Функция возвращает boolean, сообщая, удалось ли выполнить запись:
export function savePreferences(value) {
try {
localStorage.setItem(key, JSON.stringify({
version: 1,
topic: value.topic,
sort: value.sort,
}));
return true;
} catch (error) {
console.warn('Настройки не сохранены:', error);
return false;
}
}
Здесь вход уже должен соответствовать известным кодам модели. Если сохранение применяется к внешнему неизвестному объекту, нужна дополнительная проверка до записи. JSON-сериализация не является такой проверкой; она лишь получает строковое представление данных.
Вызывающий интерфейс может применить выбор в памяти даже при неудачной записи. Тогда сообщение должно отличать «настройки применены» от «настройки сохранены для следующего посещения». Не превращайте отказ дополнительного удобства в невозможность пользоваться каталогом.
Неудача не обязана означать поломку программы. Политика браузера, доступный объём и пользовательские настройки могут ограничивать хранилище. Поэтому catch здесь является частью обычного договора интеграции, а не способом спрятать любой ошибочный код приложения.
Приоритет нескольких источников
Уже есть состояние в URL. Если адрес явно задаёт тему, оно должно иметь понятный приоритет перед сохранённым предпочтением. Иначе отправленная ссылка на публикацию могла бы открыть фронтенд из чужих настроек и перестала бы выражать выбранное состояние.
В этой независимой странице только демонстрируется чтение, а соединение источников можно выполнить отдельно: сначала определить, присутствует ли явный параметр адреса, затем использовать допустимое значение, затем настройку и, наконец, начальный вариант. Не используйте одно || вместо этого порядка, потому что оно скрывает причины выбора.
Также различайте сохранённую тему и номер страницы. Номер зависит от конкретного размера набора и легко устаревает. Для предпочтений удобно хранить общий способ просмотра, а адрес конкретного состояния — в URL. Наш JSON намеренно не сохраняет все временные флаги загрузки и объекты DOM.
Проверка повреждённого значения
Для ручного опыта в исходнике можно временно вызвать savePreferences({ topic: 'frontend', sort: 'lessons' }), затем убрать этот вызов и обновить страницу. Ожидается восстановление сохранённого выбора. Не оставляйте автоматическую запись в начале приложения: она перезаписала бы прежнее предпочтение ещё до чтения.
Через инструменты браузера можно поставить в известный ключ неверный JSON или неизвестную версию. Ожидается исходный выбор без отказа интерфейса. Запись чужого ключа не должна влиять на наш результат. Эти наблюдения не выполнены при подготовке, поэтому важны как план ручной проверки, а не как доказательство.
Временное хранилище sessionStorage имеет другой жизненный цикл; постоянное восстановление между посещениями нельзя обещать его именем. И сам localStorage не является резервной копией: пользователь способен очистить данные, разные устройства не разделяют их автоматически. Храните здесь восстанавливаемые предпочтения, а не единственный экземпляр важного материала.
Изменения между вкладками и запасной результат
Настройки хранятся для происхождения, а не для отдельной папки урока. Все снимки на одном локальном адресе могут читать тот же именованный ключ, если используют этот модуль. Поэтому во время опыта ясно отмечайте, какая вкладка выполняет запись. Другая вкладка не заменит свой объект настроек в памяти автоматически лишь потому, что строка хранилища изменилась. Для живой синхронизации потребовалась бы отдельная обработка изменений; нынешний снимок читает значение при запуске.
HTTP и HTTPS, а также иной порт образуют разные условия происхождения. Переход с локального адреса на опубликованный сайт не переносит записанный выбор как миграцию. Этим отличается браузерное предпочтение от данных учётной записи. Не обещайте сохранение на другом устройстве или домене без действующего сервиса переноса.
Возвращение запасного объекта позволяет продолжить работу, но не сообщает точную причину каждому вызывающему участку. В нашем договоре интерфейсу достаточно допустимых значений, а диагностическое сообщение направлено в консоль. Если продукту нужно показать различие отсутствующей записи, старой версии и запрета доступа, функция могла бы вернуть отдельный результат с причиной. Такое усложнение имеет смысл только при понятном действии пользователя, например повторении записи или сбросе своего ключа.
Неверная версия сейчас не мигрируется. Это сознательный выбор небольшого примера: старый формат игнорируется, а данные предпочтений восстанавливаемы. Для важной пользовательской информации такое поведение было бы недостаточным. Там потребуются план перехода формата и сохранение исходной записи до успешной миграции, а не молчаливое объявление нового объекта.
Наконец, синхронное чтение небольшого JSON удобно для нескольких кодов. Не используйте тот же приём как склад тысяч статей или крупных ответов. Размер данных и частота обращения являются отдельными характеристиками приложения. В этом уроке результатом остаётся устойчивое восстановление двух предпочтений, которое не ломает просмотр при отказе дополнительного хранилища.
Следующие уроки будут оформлять данные для человека. Сначала разберём даты: календарный день и момент времени требуют разных правил, особенно когда читатель находится в другом часовом поясе.