← Назад к вопросам

Где будешь хранить данные на клиенте?

2.0 Middle🔥 271 комментариев
#State Management#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Где будешь хранить данные на клиенте

Выбор хранилища данных на клиенте — критическое решение, которое влияет на производительность, безопасность и функциональность приложения. Расскажу про все варианты и когда использовать каждый.

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

ХранилищеРазмерСинхронностьБезопасностьИспользование
localStorage5-10MBСинхронныйНизкаяНастройки
sessionStorage5-10MBСинхронныйНизкаяВременные данные
IndexedDB50GB+АсинхронныйНизкаяБольшие данные
Redux/ZustandRAMСинхронныйN/AГлобальное состояние
Cookies4KBСинхронный (отправка)СредняяАвторизация
useRefRAMСинхронный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>
  );
}

Ключевой вывод

Выбор хранилища зависит от цели:

  1. React State — для видимых данных и UI состояния
  2. localStorage — для пользовательских настроек
  3. sessionStorage — для временных данных в сессии
  4. IndexedDB — для больших объёмов данных
  5. Redux/Zustand — для глобального состояния
  6. Cookies — для авторизации (httpOnly)
  7. useRef — для временных значений
  8. URL params — для шаринга и фильтров

Правило: Используй самое простое хранилище, которое соответствует твоим требованиям.

Где будешь хранить данные на клиенте? | PrepBro