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

Все ли будешь хранить в state

1.3 Junior🔥 302 комментариев
#React#State Management

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

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

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

Что хранить в state

Нет, не всё. Хранение всего в state — это anti-pattern, который делает приложение медленным и сложным. Нужно критически подходить к тому, что живёт в state.

Правило 80/20

В state хранить только:

  • Данные, которые используют несколько компонентов
  • Данные, изменяющиеся часто (и требующие перерендеринга)
  • UI состояние (opened, selected, loading, error)

НЕ хранить в state:

  • Статические данные (конфиг, константы)
  • Данные, нужные одному компоненту (local state)
  • Производные данные (computed/selector)
  • Кэширующиеся данные (используй хуки типа useMemo)
  • Временные значения (в процессе редактирования)

Примеры НЕправильного подхода

// ПЛОХО — всё в Redux
const store = {
  users: [...],
  posts: [...],
  comments: [...],
  currentUser: {...},
  currentPost: {...},
  currentComment: {...},
  theme: 'light',
  language: 'en',
  isLoading: false,
  error: null,
  isMenuOpen: false,
  isModalOpen: false,
  isDropdownOpen: false,
  notifications: [...],
  unreadCount: 0,
  lastUpdated: 1234567890,
  // ... ещё 50 полей
};

Проблемы:

  • Redux store слишком большой
  • Любое изменение = перерендеринг всех компонентов
  • Сложно отследить, что где используется
  • Медленная девелопер опытность

Правильный подход

// Redux/Vuex — только shared state
const store = {
  users: { byId: {...}, allIds: [...] }, // Используют несколько компонентов
  currentUserId: '123',                    // Нужен многим компонентам
  theme: 'light',                          // Глобальное состояние
  isAuthenticated: true,                   // Критично для приложения
};

// Компонент со своим state
function UserProfile() {
  const [editMode, setEditMode] = useState(false); // Локальное состояние
  const [formData, setFormData] = useState({});    // Локальное
  
  const currentUser = useSelector(state => 
    state.users.byId[state.currentUserId] // Только читаем из store
  );
  
  return (
    <div>
      {editMode ? (
        <EditForm data={formData} /> // formData НЕ в store
      ) : (
        <ProfileCard user={currentUser} />
      )}
    </div>
  );
}

Иерархия где хранить данные

1. Локальный state (useState) — 70% данных

function SearchForm() {
  const [query, setQuery] = useState(''); // Не нужен другим компонентам
  const [filters, setFilters] = useState({}); // Локальное
  
  const handleSearch = () => {
    // Отправляем на сервер или в Redux ТОЛЬКО результат
  };
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

2. Контекст (Context API) — 20% данных

// Для данных используемых всем поддеревом
const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Используется:
function DarkModeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle {theme}
    </button>
  );
}

3. Redux/Vuex/Pinia — 10% данных

// Только действительно глобальные данные
const store = {
  auth: {
    user: {...},
    isAuthenticated: true,
    token: '...',
  },
  notifications: [...],
};

Производные данные — НЕ в state

// ПЛОХО
const store = {
  users: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}],
  userCount: 2, // Это производное, не храни!
  userNames: ['John', 'Jane'], // Тоже производное
};

// ХОРОШО
const users = useSelector(state => state.users);
const userCount = useMemo(() => users.length, [users]); // Вычисляем
const userNames = useMemo(() => users.map(u => u.name), [users]);

Сложные состояния компонента

// ПЛОХО — в Redux
const store = {
  isDropdownOpen: false,
  isTooltipOpen: false,
  isMenuOpen: false,
  // ... 10 булевых флагов
};

// ХОРОШО — в компоненте
function Dropdown() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && <Menu />}
    </div>
  );
}

Временные данные при редактировании

// ПЛОХО
const store = {
  user: {...},
  editingUserData: {...}, // Временное, меняется каждый keystroke
};

// ХОРОШО
function UserEditForm() {
  const user = useSelector(state => state.user); // Из store
  const [formData, setFormData] = useState(user); // Локальное копирование
  
  const handleSave = async () => {
    await updateUser(formData);
    // dispatch(setUser(formData)); // Потом синхронизируем
  };
  
  return (
    <form>
      <input 
        value={formData.name}
        onChange={(e) => setFormData({...formData, name: e.target.value})}
      />
      <button onClick={handleSave}>Save</button>
    </form>
  );
}

Оптимизация: selectors

// Для больших деревьев state, используй selectors
const selectCurrentUser = state => 
  state.users.byId[state.currentUserId];

const selectCurrentUserComments = state => {
  const user = selectCurrentUser(state);
  return state.comments.filter(c => c.userId === user.id);
};

// Или с Reselect для memoization
import { createSelector } from 'reselect';

const selectUsers = state => state.users;
const selectUserIds = state => state.userIds;

const selectUsersByIdSorted = createSelector(
  [selectUsers, selectUserIds],
  (users, ids) => ids.map(id => users[id])
);

Кэширование API запросов

// НЕ храни всё в state, используй custom hook
function useUser(userId) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const cacheRef = useRef({});

  useEffect(() => {
    if (cacheRef.current[userId]) {
      setData(cacheRef.current[userId]);
      return;
    }

    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(user => {
        cacheRef.current[userId] = user;
        setData(user);
      })
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);

  return { data, loading, error };
}

Правило большого пальца

Спроси себя перед добавлением в state:

  1. Это нужно нескольким компонентам? Если нет → локальный state
  2. Это меняется часто? Если редко → переменная, не state
  3. Это производное от другого state? Если да → useMemo, не отдельный state
  4. Это действительно глобальное? Если нет → контекст вместо Redux
  5. Это влияет на UX? Если нет → не в state

Вывод

Правило 80/20:

  • 80% → Локальный state в компонентах
  • 15% → Context для локальных деревьев
  • 5% → Redux/Vuex для действительно глобального state

Преимущества такого подхода:

  • Быстрое приложение (меньше перерендеринга)
  • Простой и понятный код
  • Легко отследить где что используется
  • Меньше багов (меньше синхронизации)
Все ли будешь хранить в state | PrepBro