← Назад к вопросам
Как должен реализовываться 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 для безопасности
Этот подход обеспечивает чистоту кода, повторяемость и легкость в тестировании.