Где будешь хранить данные на клиенте?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где будешь хранить данные на клиенте
Выбор хранилища данных на клиенте — критическое решение, которое влияет на производительность, безопасность и функциональность приложения. Расскажу про все варианты и когда использовать каждый.
1. React State — для видимых данных
function ShoppingCart() {
const [items, setItems] = useState([]); // Видимые данные
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Эти данные отображаются в UI
return (
<div>
{loading && <p>Loading...</p>}
{error && <p>Error: {error}</p>}
{items.map(item => <Item key={item.id} {...item} />)}
</div>
);
}
Когда использовать:
- Данные, которые отображаются в UI
- Состояние формы (input, select, checkbox)
- UI состояние (открыто/закрыто меню, loading)
Плюсы:
- Автоматическая перерисовка при изменении
- Простая разработка
- Реактивность встроена
Минусы:
- Теряется при перезагрузке страницы
- Не может быть использован между сессиями
2. localStorage — для персистентных данных
function UserPreferences() {
const [theme, setTheme] = useState(() => {
// Инициализируем из localStorage
return localStorage.getItem('theme') || 'light';
});
const handleThemeChange = (newTheme) => {
setTheme(newTheme);
// Сохраняем в localStorage
localStorage.setItem('theme', newTheme);
};
return (
<div>
<button onClick={() => handleThemeChange('dark')}>Dark</button>
<button onClick={() => handleThemeChange('light')}>Light</button>
</div>
);
}
Когда использовать:
- Пользовательские настройки (тема, язык, предпочтения)
- Черновики формы (если форма не заполнена)
- Кэш данных (которые редко меняются)
Плюсы:
- Сохраняется после перезагрузки
- Просто использовать
- ~5-10MB лимит
Минусы:
- Синхронный (может замедлить UI)
- Видна в DevTools (не безопасна для секретов)
- Одна вкладка не видит другую
Важно: НЕ храни здесь:
- Токены авторизации (используй httpOnly cookies)
- Пароли
- Личные данные
3. sessionStorage — для временных данных
function FormWizard() {
const [step, setStep] = useState(() => {
return parseInt(sessionStorage.getItem('formStep')) || 1;
});
const handleNext = () => {
const nextStep = step + 1;
setStep(nextStep);
sessionStorage.setItem('formStep', nextStep);
};
const handleClear = () => {
sessionStorage.clear(); // Очистить всё
};
return (
<div>
<p>Step: {step}</p>
<button onClick={handleNext}>Next</button>
<button onClick={handleClear}>Clear</button>
</div>
);
}
Когда использовать:
- Данные, которые нужны только во время текущей сессии
- Шаги форм (wizard)
- Временные фильтры
Плюсы:
- Очищается при закрытии вкладки
- Один источник истины для одной вкладки
- ~5-10MB лимит
Минусы:
- Теряется при закрытии вкладки
- Синхронный
- Видна в DevTools
4. IndexedDB — для больших объёмов данных
function OfflineTodos() {
const [todos, setTodos] = useState([]);
// Открываем базу при загрузке
useEffect(() => {
const request = indexedDB.open('TodosDB', 1);
request.onerror = () => {
console.error('Failed to open IndexedDB');
};
request.onsuccess = (event) => {
const db = event.target.result;
// Получаем все todos из БД
const transaction = db.transaction(['todos'], 'readonly');
const store = transaction.objectStore('todos');
const getAllRequest = store.getAll();
getAllRequest.onsuccess = () => {
setTodos(getAllRequest.result);
};
};
}, []);
const handleAddTodo = async (text) => {
const request = indexedDB.open('TodosDB', 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(['todos'], 'readwrite');
const store = transaction.objectStore('todos');
const newTodo = { id: Date.now(), text, completed: false };
store.add(newTodo);
setTodos([...todos, newTodo]);
};
};
return (
<div>
{todos.map(todo => <p key={todo.id}>{todo.text}</p>)}
<button onClick={() => handleAddTodo('New')}>Add</button>
</div>
);
}
Когда использовать:
- Большие объёмы данных (> 10MB)
- Сложные структуры (графики, таблицы)
- Offline-first приложения
- Кэширование больших наборов данных
Плюсы:
- До 50GB+ данных
- Асинхронный (не блокирует UI)
- Структурированное хранилище (как БД)
- Поддерживает индексы и запросы
Минусы:
- Сложнее использовать (async API)
- Требует настройки scheme
- Видна в DevTools
5. Redux / Zustand — для глобального состояния
// Redux
const userSlice = createSlice({
name: 'user',
initialState: { data: null, loading: false },
reducers: {
setUser: (state, action) => {
state.data = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
}
}
});
// Zustand
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
loading: false,
setLoading: (loading) => set({ loading })
}));
function App() {
const { user, setUser } = useUserStore();
return (
<div>
{user && <p>Hello, {user.name}</p>}
<button onClick={() => setUser({ name: 'Alice' })}>Login</button>
</div>
);
}
Когда использовать:
- Глобальное состояние приложения
- Данные, которые нужны в нескольких компонентах
- Предотвращение prop drilling
Плюсы:
- Централизованное управление
- DevTools для отладки
- Time-travel debugging (Redux)
- Много middleware
Минусы:
- Boilerplate код
- Требует обучения
- Теряется при перезагрузке (нужна persisting)
6. Cookies — для авторизации
// Backend отправляет cookie
Set-Cookie: authToken=abc123; HttpOnly; Secure; SameSite=Strict
// Frontend НЕ может читать httpOnly cookies (это защита)
// Браузер автоматически отправляет их с запросами
function LoginComponent() {
const handleLogin = async () => {
const response = await fetch('/api/login', {
method: 'POST',
credentials: 'include', // Отправляем cookies
body: JSON.stringify({ email: 'user@example.com', password: '...' })
});
// Backend установит cookie в ответе
};
return <button onClick={handleLogin}>Login</button>;
}
Когда использовать:
- Токены авторизации (httpOnly cookies)
- Session ID
- CSRF tokens
Плюсы:
- Безопасны (httpOnly недоступна JS)
- Автоматически отправляются
- Кросс-домен возможен (с CORS)
Минусы:
- Frontend не может читать (если HttpOnly)
- Vulnerable к CSRF (если не защищено)
- Требуется правильная конфигурация
7. Memory (useRef) — для временных значений
function DataProcessor() {
const bufferRef = useRef([]);
const timerRef = useRef(null);
const processData = (data) => {
// Сохраняем в памяти, без перерисовки
bufferRef.current.push(data);
// Очищаем старый таймер
clearTimeout(timerRef.current);
// Устанавливаем новый
timerRef.current = setTimeout(() => {
console.log('Process buffer:', bufferRef.current);
bufferRef.current = []; // Очищаем
}, 1000);
};
return (
<button onClick={() => processData({ value: 1 })}>Add</button>
);
}
Когда использовать:
- Временные значения (буфер, очередь)
- Значения между renders
- Таймеры, интервалы
Плюсы:
- Очень быстро
- Синхронный
- Не вызывает перерисовку
Минусы:
- Теряется при перезагрузке
- Нет реактивности
- Только для текущего компонента
8. Query параметры URL — для шаринга состояния
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q') || '';
const page = searchParams.get('page') || '1';
const handleSearch = (q) => {
setSearchParams({ q, page: '1' });
// URL становится: /search?q=react&page=1
};
return (
<div>
<input value={query} onChange={(e) => handleSearch(e.target.value)} />
<p>Current query: {query}, page: {page}</p>
</div>
);
}
Когда использовать:
- Фильтры и сортировка
- Пагинация
- Когда нужно шарить состояние через URL
Плюсы:
- Шарится через URL
- Сохраняется в истории браузера
- Может быть забукмаркена
Минусы:
- Видна всем (не конфиденциально)
- Ограничена размером URL
- Требует парсинга
9. LocalStorage vs IndexedDB vs Redux
| Хранилище | Размер | Синхронность | Безопасность | Использование |
|---|---|---|---|---|
| localStorage | 5-10MB | Синхронный | Низкая | Настройки |
| sessionStorage | 5-10MB | Синхронный | Низкая | Временные данные |
| IndexedDB | 50GB+ | Асинхронный | Низкая | Большие данные |
| Redux/Zustand | RAM | Синхронный | N/A | Глобальное состояние |
| Cookies | 4KB | Синхронный (отправка) | Средняя | Авторизация |
| useRef | RAM | Синхронный | N/A | Временные значения |
| URL params | ~2KB | Синхронный | Видна всем | Фильтры/пагинация |
10. Мой систематический подход
Вопрос 1: Видимые данные в UI?
- ДА → React State
- НЕТ → Далее
Вопрос 2: Нужна ли персистентность?
- ДА → Далее
- НЕТ → Memory (useRef) или Redux/Zustand
Вопрос 3: Размер данных?
- < 10MB → localStorage
-
10MB → IndexedDB
Вопрос 4: Что это?
- Авторизация → httpOnly Cookies
- Глобальное состояние → Redux/Zustand
- Временная сессия → sessionStorage
- Шаринг через URL → Query параметры
Практический пример: E-commerce приложение
function EcommerceApp() {
// React State для видимых данных
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(false);
// Redux для глобального состояния
const { cart, user } = useSelector(state => state);
const dispatch = useDispatch();
// localStorage для пользовательских настроек
const [currency, setCurrency] = useState(() =>
localStorage.getItem('currency') || 'USD'
);
// URL params для фильтров
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
// Cookies для авторизации (автоматически отправляются)
// Это управляется backend
// IndexedDB для локального кэша больших данных
useEffect(() => {
// Загружаем избранные товары из IndexedDB
loadFavoritesFromIndexedDB();
}, []);
return (
<div>
<header>
<UserInfo user={user} /> {/* Redux */}
</header>
<ProductList products={products} category={category} /> {/* State + URL params */}
<Cart items={cart} /> {/* Redux */}
<Settings currency={currency} setCurrency={setCurrency} /> {/* localStorage */}
</div>
);
}
Ключевой вывод
Выбор хранилища зависит от цели:
- React State — для видимых данных и UI состояния
- localStorage — для пользовательских настроек
- sessionStorage — для временных данных в сессии
- IndexedDB — для больших объёмов данных
- Redux/Zustand — для глобального состояния
- Cookies — для авторизации (httpOnly)
- useRef — для временных значений
- URL params — для шаринга и фильтров
Правило: Используй самое простое хранилище, которое соответствует твоим требованиям.