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

Как должен реализовываться CRUD?

1.0 Junior🔥 161 комментариев
#Архитектура и паттерны

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

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

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

Реализация CRUD операций во frontend

CRUD (Create, Read, Update, Delete) — это основной паттерн для работы с данными в любом приложении. Вот как я реализую это на фронтенде:

1. Архитектурный подход

Я разделяю ответственность между слоями:

// services/api.ts — работа с HTTP
export async function fetchUsers() {
  const response = await fetch('/api/v1/users');
  if (!response.ok) throw new Error('Failed to fetch');
  return response.json();
}

export async function createUser(data) {
  return fetch('/api/v1/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  }).then(r => r.json());
}

export async function updateUser(id, data) {
  return fetch(`/api/v1/users/${id}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  }).then(r => r.json());
}

export async function deleteUser(id) {
  return fetch(`/api/v1/users/${id}`, { method: 'DELETE' });
}

2. READ (Получение данных)

Для списков:

function UsersList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUsers()
      .then(setUsers)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Для отдельного ресурса:

function UserDetail({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/v1/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, [userId]);

  return user ? <div>{user.name}</div> : null;
}

3. CREATE (Создание)

С формой и обработкой ошибок:

function CreateUserForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const newUser = await createUser(formData);
      setFormData({ name: '', email: '' });
      // Обновляем список или переходим на страницу пользователя
      window.location.href = `/users/${newUser.id}`;
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <input
        type="email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create'}
      </button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </form>
  );
}

4. UPDATE (Обновление)

function EditUserForm({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    fetch(`/api/v1/users/${userId}`)
      .then(r => r.json())
      .then(setUser);
  }, [userId]);

  const handleChange = (field, value) => {
    setUser({ ...user, [field]: value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    
    try {
      await updateUser(userId, user);
      alert('Updated successfully');
    } finally {
      setLoading(false);
    }
  };

  if (!user) return <div>Loading...</div>;

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={user.name}
        onChange={(e) => handleChange('name', e.target.value)}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}

5. DELETE (Удаление)

function UserRow({ user, onDeleted }) {
  const [loading, setLoading] = useState(false);

  const handleDelete = async () => {
    if (!window.confirm('Are you sure?')) return;
    
    setLoading(true);
    try {
      await deleteUser(user.id);
      onDeleted(user.id); // Обновляем список
    } finally {
      setLoading(false);
    }
  };

  return (
    <tr>
      <td>{user.name}</td>
      <td>
        <button onClick={handleDelete} disabled={loading}>
          {loading ? 'Deleting...' : 'Delete'}
        </button>
      </td>
    </tr>
  );
}

6. State Management для CRUD

Для больших приложений используется более структурированный подход:

interface CrudState<T> {
  items: T[];
  loading: boolean;
  error: string | null;
  selectedId: string | null;
}

interface CrudActions<T> {
  fetchAll: () => Promise<void>;
  create: (item: Omit<T, 'id'>) => Promise<void>;
  update: (id: string, item: Partial<T>) => Promise<void>;
  delete: (id: string) => Promise<void>;
}

function useCrud<T extends { id: string }>(
  apiPath: string
): [CrudState<T>, CrudActions<T>] {
  const [state, dispatch] = useReducer(crudReducer, initialState);

  const fetchAll = async () => {
    try {
      const items = await fetch(`/api/v1${apiPath}`).then(r => r.json());
      dispatch({ type: 'SET_ITEMS', payload: items });
    } catch (err) {
      dispatch({ type: 'SET_ERROR', payload: err.message });
    }
  };

  const create = async (data) => {
    const newItem = await fetch(`/api/v1${apiPath}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    }).then(r => r.json());
    
    dispatch({ type: 'ADD_ITEM', payload: newItem });
  };

  // ... update и delete аналогично

  return [state, { fetchAll, create, update, delete: delete_ }];
}

7. Лучшие практики

  • Оптимистичное обновление — обновляй UI немедленно, откатывай при ошибке
  • Кэширование — не загружай одни и те же данные несколько раз
  • Обработка ошибок — всегда показывай пользователю, что пошло не так
  • Loading состояния — блокируй кнопки и показывай spinner
  • Валидация — проверяй данные до отправки на сервер
  • Типизация — используй TypeScript для безопасности

Этот подход обеспечивает чистоту кода, повторяемость и легкость в тестировании.

Как должен реализовываться CRUD? | PrepBro