Сохранение конфигурации и ссылка
Оглавление серии «Форма» · Урок 8 из 10.
У конфигуратора появилось самостоятельное состояние: вариант плафона, основания и размера. Теперь позволим восстановить выбор после закрытия страницы и передать его ссылкой. Ожидаемый результат — локальное сохранение удобно для возвращения того же посетителя, а явно переданная ссылка открывает именно описанную комплектацию. Неверные или устаревшие значения не должны попадать напрямую в геометрию и расчёт. Все внешние данные проходят через нормализацию предыдущего урока.
Есть два разных источника восстановления. localStorage относится к конкретному браузеру и происхождению сайта. URL путешествует вместе со ссылкой. Если открыть присланную ссылку после собственного прошлого выбора, её параметры должны иметь приоритет. Иначе человек получит старую личную комплектацию вместо того, что ему хотели показать. Такой приоритет является правилом продукта; браузер сам не выбирает, какой источник важнее для вашего конфигуратора.
Полный модуль сохранения
Ниже полный storage.js. Он использует исходную конфигурацию и normalize из общего модуля. restore сначала проверяет наличие хотя бы одного предметного параметра URL. Если параметры присутствуют, пытается восстановить всю схему из ссылки. Если ссылка неполная или некорректная, выбирает известную исходную комплектацию и возвращает сообщение. В этом случае он не подменяет ошибочную ссылку личным сохранением: явное действие пользователя остаётся различимым.
import { defaults, normalize } from './config.js';
export function restore() {
const params = new URLSearchParams(location.search);
if (['v','shade','base','size'].some(key => params.has(key))) {
return {config:normalize({v:Number(params.get('v')),shade:params.get('shade'),base:params.get('base'),size:params.get('size')}) || {...defaults},message:'Открыта конфигурация из ссылки; неверные параметры заменены исходными.'};
}
try {
const saved = normalize(JSON.parse(localStorage.getItem('forma-config')));
return {config:saved || {...defaults},message:saved ? 'Восстановлен локальный выбор.' : 'Исходная комплектация.'};
} catch { return {config:{...defaults},message:'Локальное сохранение недоступно; выбор работает в памяти.'}; }
}
export function remember(config) {
try {localStorage.setItem('forma-config',JSON.stringify(config));return true;} catch {return false;}
}
export function shareUrl(config) {
const url = new URL(location.href); url.search='';
for(const [key,value] of Object.entries(config)) url.searchParams.set(key,String(value));
return url.href;
}
URLSearchParams разбирает значения после вопросительного знака. Версия преобразуется в число, остальные значения остаются строками. Нормализация требует точного набора допустимых вариантов. Параметр shade=purple не становится цветом только потому, что он похож на название оттенка; в предметной палитре есть violet. Неизвестные дополнительные параметры не участвуют в конфигурации. Не следует распространять любой ключ ссылки на свойство объекта Three.js.
При отсутствии предметных параметров пробуем прочитать сохранённый JSON. Операция может завершиться ошибкой: доступ запрещён настройками, значение повреждено или хранилище недоступно в данном окружении. Поэтому чтение и разбор находятся в try. Даже корректный JSON ещё не является корректной конфигурацией. normalize проверяет версию и варианты после синтаксического разбора. Если сохранения нет, JSON.parse(null) даст null, который нормализация спокойно отвергнет.
Схема должна переживать обновление
Поле v не украшает JSON. Оно обозначает версию договорённости о параметрах. Если позже удалим вариант или изменим смысл размера, старые данные потребуют явного преобразования либо отказа. Сейчас принимаем только версию один и возвращаем исходный выбор для остальных. В более развитом приложении можно добавить функцию миграции версии один в версию два, но она должна сохранять понятный предметный смысл, а не случайно копировать неизвестные поля.
Важен и момент сохранения. Обработчик поля сначала получает нормализованное состояние, обновляет изображение и резюме, затем пытается записать JSON. Если запись не удалась, выбранная комплектация продолжает работать в памяти. Пользователь получает сообщение о недоступном сохранении, а не потерю текущего выбора. Сохранение является дополнительной возможностью интерфейса. Оно не должно превращать любое ограничение браузера в запрет изменить цвет товара.
В localStorage нет срока жизни само по себе. Сохранение может остаться после обновления каталога и удаления продукта. Наша ключевая строка forma-config относится только к одной учебной лампе. Для нескольких продуктов нужен идентификатор товара и версия схемы в пространстве ключей. Не нужно считать один общий ключ универсальным хранилищем всего магазина: конфигурации разных предметов могут иметь несовместимые поля и разные правила доступности вариантов.
Ссылка описывает выбор, а не доверие
shareUrl создаёт объект URL из текущего адреса, удаляет прежнюю поисковую часть и добавляет предметные поля из нормализованного состояния. Ссылка отражает именно текущий выбор, а не мусор из случайного предыдущего адреса. Все значения превращаются в строки через стандартный механизм параметров. Результат выводится в поле с подписью, которое можно выделить и скопировать обычным способом. Для базового обмена не требуется разрешение на доступ к буферу обмена.
В ссылку не попадают координаты камеры, локальный путь файла, сообщение интерфейса или предполагаемый личный идентификатор. Здесь передаём только публичную комплектацию товара. Если в другом проекте параметр содержит персональные данные, URL может стать неподходящим местом: его видят история браузера, журналы и участники обмена. Следует выбирать способ передачи по природе данных. Учебные цвета и размер безопасно рассматривать как открытые названия вариантов, без обещания конфиденциальности.
Не включаем сумму в ссылку. Стоимость выводится функцией quote по текущим правилам приложения. Если цена изменится, прежняя ссылка должна по-прежнему выбирать товар, а не навязывать устаревший расчёт. Для настоящего заказа потребуется серверная проверка, как обсуждали в предыдущем уроке. URL является описанием выбора. Он не подтверждает оплату, наличие на складе или право получить заданную цену в будущем.
Приоритет и ошибочные значения
Рассмотрим три случая. При пустом URL и правильном сохранении восстанавливается прошлый выбор. При полной правильной ссылке используется её выбор, даже если сохранение другое. При неполной предметной ссылке приложение возвращается к исходной конфигурации и сообщает об этом. Последний случай можно спроектировать иначе, например дополнять отсутствующие поля исходными значениями. Но тогда это должно быть явным правилом схемы, чтобы разные версии приложения не трактовали один адрес непредсказуемо.
Наш модуль не записывает состояние автоматически сразу после чтения ссылки. Посетитель может просто рассмотреть присланный вариант. Запись произойдёт после собственного изменения поля. Это позволяет отделить просмотр чужого выбора от личного сохранения. При другом продуктном требовании можно сохранять сразу, но следует понимать последствия: открытие ссылки начнёт менять личное состояние браузера даже без действия в форме. Такое поведение нельзя считать технически нейтральным.
Самостоятельное наблюдение
Выберите песочный широкий плафон, скопируйте полученный URL и откройте его в другом окне. Ожидается та же предметная комплектация и соответствующий артикул. Затем измените параметр версии на неизвестное значение: ожидается исходный набор и сообщение о восстановлении из ссылки с заменой неверных данных. После удаления параметров URL должен снова действовать локальный источник. Поведение в процессе написания не выполнялось; это предлагаемые наблюдения по правилам модуля.
Механизмы параметров описаны в URLSearchParams, возможности и ограничения хранилища — в localStorage. Теперь конфигурация существует независимо от 3D и может восстанавливаться из ограниченных данных. На следующем шаге воспользуемся этим разделением: сначала загрузим обычную карточку с формой, а библиотеку и модель подключим только после явного запроса объёмного представления.
Исходники этого шага
Архив серии содержит полную папку lesson-08. Возьмите именно её; переносить файлы из соседних папок не требуется. Версии и самостоятельная подготовка описаны в README. Пример, установка зависимостей и его выполнение при написании не запускались; результаты в тексте ожидаются по разбору исходников. Материал сохранён локально как Markdown-черновик.