Почему localStorage по-прежнему приводит к сбою вашего сайта в 2023 году
29 января 2023 г.
Осенью 2022 года я решил перенести один из наших рабочих проектов с приложения Create React на NextJs. Цель этого состояла в том, чтобы повысить производительность нескольких страниц, отделить страницы для мобильных устройств от основного приложения и уменьшить количество сторонних лиц, имеющих доступ. Большинство наших целей были успешно достигнуты, но, по словам нашего уведомителя Airbrake, localStorage
всегда что-то ломало. Раньше я не думал, что localStorge
будет проблемой, так как я хранил там номера телефонов пользователей и ключи i18n и не мог использовать вместо этого внутренний API.
Итак, мне нужно было, чтобы localStorage работал, так как это был единственный вариант для меня.
Мои проблемы
Одной из проблем, с которыми я столкнулся, была ошибка во время компиляции.
ReferenceError: localStorage is not defined
Да, я определенно был новичком в NextJs и полностью упустил тот факт, что мои страницы в первую очередь выполняют серверную визуализацию. Это означает, что window
и localStorage
не будут доступны, пока страница не будет загружена на стороне клиента.
import { useEffect } from 'react';
useEffect(() => {
const phoneNumber = localStorage.getItem('phoneNumber');
}, []);
// and
const onChange = value => localStorage.setItem('phoneNumber', value);
Решение было довольно простым: поместить все, что с этим связано, в обработчики useEffect
и onChange
, чтобы убедиться, что это отражается в браузере.
Вторая и третья были моими самыми большими проблемами, потому что мне удалось решить их только после прочтения множества статей, сообщений и страниц документации.
SecurityError: The operation is insecure.
Мы получили эту ошибку только от браузера Safari, поэтому я решил копнуть немного дальше. Нашел интересный факт. Если все файлы cookie в настройках конфиденциальности и безопасности заблокированы пользователем, каждый раз, когда ваш веб-сайт вызывает localStorage
или sessionStorage
, это будет вызывать ошибку.
// Even calling
console.log(localStorage);
// or
localStorage?.getItem('phoneNumber');
Оборачивать все в try...catch
было не лучшим вариантом, потому что это увеличило бы существующую кодовую базу, что является плохой практикой. Следуя фрагменту кода на MDN, я решил написать служебный класс, охватывающий все жизненно важные компоненты, связанные с localStorage
.
class AppStorage {
getItem(key) {
if (this.storageAvailable()) {
return window.localStorage?.getItem(key);
}
return undefined;
}
setItem(key, value) {
if (this.storageAvailable()) {
window.localStorage?.setItem(key, value);
}
}
storageAvailable(type = 'localStorage') {
let storage;
try {
storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return (
e instanceof DOMException &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
storage &&
storage.length !== 0
);
}
}
}
// and inside our components
const phoneNumber = AppStorage.getItem('phoneNumber');
Выглядит весьма элегантно, не так ли?
У нас есть этот служебный класс в отдельном файле, и мы можем использовать его во всем приложении, применяя стратегию DRY. Кстати, этот служебный класс помог решить и последнюю проблему:
TypeError: Cannot read properties of null (reading 'setItem')
Airbrake прислал нам эту ошибку с Android в новых версиях Chrome. Я до сих пор не понял, чем это вызвано, потому что оператор .?
не помог, был определен window.localStorage
. Но это было null
.
Я очень надеюсь, что это будет исправлено в ближайшие месяцы или годы, и Browser API будет унифицирован для всех браузеров. Да, я мечтатель. И, конечно же, я верю, что эта статья и мой опыт помогут тем, кто борется со сбоями localStorage
.
Оригинал