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

Как в React осуществляется доступ к данным?

2.3 Middle🔥 161 комментариев
#React#Архитектура и паттерны

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

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

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

Как в React осуществляется доступ к данным?

Доступ к данным в React — это центральная концепция, определяющая архитектуру приложения. React предоставляет множество подходов для управления и доступа к данным: от локального состояния до глобальных хранилищ и удаленных API. Выбор подхода зависит от сложности приложения и требований.

1. Локальное состояние компонента (useState)

// Самый простой способ управления данными
import { useState } from 'react';

function Counter() {
  // Состояние локально в этом компоненте
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// Использование:
// - Простые, локальные данные
// - Не нужны другим компонентам
// - UI состояние (открыто/закрыто, активный элемент)

2. Props — пробрасывание данных вниз

// Родительский компонент
function Parent() {
  const [user, setUser] = useState({ name: 'John', age: 30 });
  
  return (
    <div>
      {/* Передаем данные в дочерний компонент */}
      <Child user={user} onUpdate={setUser} />
    </div>
  );
}

// Дочерний компонент
function Child({ user, onUpdate }) {
  return (
    <div>
      <p>Name: {user.name}</p>
      <button onClick={() => onUpdate({ ...user, name: 'Jane' })}>
        Update Name
      </button>
    </div>
  );
}

// Использование:
// - Передача данных от родителя к потомкам
// - Однонаправленный поток данных
// - Простой и предсказуемый

3. Context API — глобальное состояние без библиотек

import { createContext, useContext, useState } from 'react';

// 1. Создаем Context
const UserContext = createContext();

// 2. Provider компонент
function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const login = async (email, password) => {
    setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ email, password })
      });
      const userData = await response.json();
      setUser(userData);
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <UserContext.Provider value={{ user, loading, login }}>
      {children}
    </UserContext.Provider>
  );
}

// 3. Custom hook для использования Context
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser должен быть внутри UserProvider');
  }
  return context;
}

// 4. Использование в компоненте
function LoginForm() {
  const { user, loading, login } = useUser();
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    await login(email, password);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {user && <p>Logged in as: {user.email}</p>}
      {loading && <p>Loading...</p>}
      {/* форма */}
    </form>
  );
}

// 5. Оборачиваем приложение в Provider
function App() {
  return (
    <UserProvider>
      <LoginForm />
    </UserProvider>
  );
}

4. Redux — предсказуемое глобальное хранилище

import { createSlice, configureStore } from '@reduxjs/toolkit';
import { useSelector, useDispatch } from 'react-redux';

// 1. Создаем slice (содержит reducer и actions)
const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null
  },
  reducers: {
    // Синхронные actions
    setUser: (state, action) => {
      state.data = action.payload;
    },
    clearUser: (state) => {
      state.data = null;
    }
  },
  extraReducers: (builder) => {
    // Для асинхронных действий используем thunk
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  }
});

// 2. Асинхронный action (thunk)
import { createAsyncThunk } from '@reduxjs/toolkit';

const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, { rejectWithValue }) => {
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Network error');
      return response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

// 3. Создаем store
const store = configureStore({
  reducer: {
    user: userSlice.reducer
  }
});

// 4. Используем в компонентах
function UserProfile({ userId }) {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector(state => state.user);
  
  React.useEffect(() => {
    dispatch(fetchUser(userId));
  }, [userId, dispatch]);
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  
  return (
    <div>
      <h1>{data?.name}</h1>
      <p>{data?.email}</p>
    </div>
  );
}

// 5. Оборачиваем приложение
import { Provider } from 'react-redux';

function App() {
  return (
    <Provider store={store}>
      <UserProfile userId={1} />
    </Provider>
  );
}

5. React Query / TanStack Query — управление серверным состоянием

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function UserList() {
  // Автоматическое кеширование, refetching, и sync
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],  // Уникальный ключ
    queryFn: async () => {
      const response = await fetch('/api/users');
      return response.json();
    },
    staleTime: 5 * 60 * 1000,  // 5 минут
  });
  
  // Mutation для обновления
  const queryClient = useQueryClient();
  const { mutate: createUser } = useMutation({
    mutationFn: (newUser) =>
      fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(newUser)
      }).then(r => r.json()),
    onSuccess: () => {
      // Инвалидируем кеш
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
  
  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  
  return (
    <div>
      <ul>
        {users?.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <button onClick={() => createUser({ name: 'New User' })}>
        Add User
      </button>
    </div>
  );
}

// Использование:
// - Серверное состояние (API данные)
// - Автоматическое кеширование
// - Синхронизация
// - Обновления в фоне (background refetch)

6. Zustand — легкое альтернативное хранилище

import { create } from 'zustand';

const useStore = create((set) => ({
  // Состояние
  user: null,
  loading: false,
  
  // Actions
  fetchUser: async (userId) => {
    set({ loading: true });
    try {
      const response = await fetch(`/api/users/${userId}`);
      const userData = await response.json();
      set({ user: userData, loading: false });
    } catch (error) {
      set({ loading: false });
    }
  },
  
  clearUser: () => set({ user: null })
}));

// Использование в компонентах
function UserProfile() {
  const user = useStore(state => state.user);
  const fetchUser = useStore(state => state.fetchUser);
  
  return (
    <div>
      {user && <h1>{user.name}</h1>}
      <button onClick={() => fetchUser(1)}>Load User</button>
    </div>
  );
}

7. Fetch с useEffect — старый способ (не рекомендуется)

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let isMounted = true;  // Предотвращение утечек памяти
    
    const fetchData = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        
        if (isMounted) {
          setUser(data);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    return () => {
      isMounted = false;  // Cleanup
    };
  }, [userId]);
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  
  return <div>{user?.name}</div>;
}

// Проблемы:
// - Race conditions
// - Memory leaks
// - Сложно обработать ошибки
// - Нет кеширования
// Используй React Query вместо этого!

Сравнение подходов

┌─────────────────┬──────────────┬──────────────┬──────────────┐
│ Подход          │ Сложность    │ Масштаб      │ Когда выбр.   │
├─────────────────┼──────────────┼──────────────┼──────────────┤
│ useState        │ Очень низкая │ Один компон. │ Простые UI   │
│ Props           │ Низкая       │ Древо компон.│ Дочерним     │
│ Context API     │ Средняя      │ Всё прилож.  │ Малые прил.  │
│ Redux           │ Высокая      │ Сложные      │ Большие прил.│
│ React Query     │ Средняя      │ Серверные    │ API данные   │
│ Zustand         │ Низкая       │ Всё прилож.  │ Легкая альт. │
│ useEffect fetch │ Низкая       │ Один компон. │ НЕ ИСПОЛЬЗ.  │
└─────────────────┴──────────────┴──────────────┴──────────────┘

Лучшие практики доступа к данным

1. Разделение серверного и клиентского состояния

// Серверное состояние (React Query)
const { data: users } = useQuery(['users'], fetchUsers);

// Клиентское состояние (Zustand/Redux)
const theme = useStore(state => state.theme);
const sidebarOpen = useStore(state => state.sidebarOpen);

2. Нормализация данных

// ПЛОХО: вложенные данные
const user = {
  id: 1,
  name: 'John',
  posts: [
    { id: 1, title: 'Post 1' },
    { id: 2, title: 'Post 2' }
  ]
};

// ХОРОШО: нормализованные данные
const state = {
  users: { 1: { id: 1, name: 'John' } },
  posts: {
    1: { id: 1, title: 'Post 1', userId: 1 },
    2: { id: 2, title: 'Post 2', userId: 1 }
  }
};

3. Мемоизация селекторов

import { useMemo } from 'react';

function Component() {
  const users = useSelector(state => state.users);
  
  // ПЛОХО: новый объект каждый render
  const activeUsers = users.filter(u => u.active);
  
  // ХОРОШО: мемоизация
  const activeUsers = useMemo(
    () => users.filter(u => u.active),
    [users]
  );
}

Заключение

Доступ к данным в React имеет множество подходов:

  1. useState — для локального состояния
  2. Props — для пробрасывания между компонентами
  3. Context API — для глобального состояния без библиотек
  4. Redux — для сложных приложений с предсказуемым потоком
  5. React Query — для синхронизации с сервером
  6. Zustand — как легкая альтернатива Redux
  7. useEffect + fetch — ИЗБЕГАТЬ

Выбор инструмента зависит от масштаба приложения, сложности состояния и требований к производительности. Начинай с простого (useState/Props), добавляй абстракции по мере необходимости.

Как в React осуществляется доступ к данным? | PrepBro