Optimistic Updates
Optimistic Updates
Оптимістичне оновлення — це стратегія, коли ми оновлюємо інтерфейс так, ніби запит на сервер вже пройшов успішно. Це створює відчуття миттєвої реакції.
Якщо сервер поверне помилку, ми повинні "відкотити" (rollback) зміни назад.
Реалізація в RTK Query
RTK Query має спеціальний колбек onQueryStarted для мутацій, який дозволяє маніпулювати кешем.
updateBoard: builder.mutation<void, BoardData>({
query: (board) => ({
url: 'board',
method: 'POST',
body: board,
}),
async onQueryStarted(board, { dispatch, queryFulfilled }) {
// 1. Оптимістичне оновлення кешу 'getBoard'
const patchResult = dispatch(
apiSlice.util.updateQueryData('getBoard', undefined, (draft) => {
// draft - це поточний стан кешу. Ми замінюємо його на нові дані (board).
// Оскільки це Immer, ми можемо просто присвоїти або мутувати.
Object.assign(draft, board);
})
);
try {
// 2. Чекаємо відповіді сервера
await queryFulfilled;
} catch {
// 3. Якщо помилка — відкочуємо зміни (undo)
patchResult.undo();
alert('Не вдалося зберегти зміни! Синхронізація не вдалася.');
}
},
}),
Як це працює в нашому випадку?
У нас гібридна система:
- Локальний
boardSliceоновлюється миттєво через діспатчmoveTask. - Ми викликаємо мутацію
updateBoard(newState). - Завдяки
onQueryStarted, ми також оновлюємо кеш RTK Query.
Але чекайте... Якщо ми вже оновили boardSlice локально, навіщо нам оновлювати кеш RTK Query?
У нашому випадку, оскільки boardSlice є "джерелом правди" для UI, нам не обов'язково робити оптимістичне оновлення кешу RTK Query для відображення змін (бо ми і так їх бачимо з локального слайсу).
АЛЕ, це корисно для узгодженості даних. Якщо інші компоненти підписані на useGetBoardQuery, вони теж миттєво отримають оновлення.
Сценарій відкату (Rollback)
Якщо сервер впав:
queryFulfilledкидає помилку.patchResult.undo()відкочує кеш RTK Query.- Проблема: Наш локальний
boardSliceвсе ще має "невірні" дані (переміщену картку).
Нам потрібно синхронізувати локальний слайс назад з кешем.
Ми можемо додати extraReducers в boardSlice, щоб слухати apiSlice.endpoints.updateBoard.matchRejected.
// ...
builder.addMatcher(
apiSlice.endpoints.updateBoard.matchRejected,
(state, action) => {
// Якщо мутація впала — можливо, варто перезапитати дані з сервера
// або якось повідомити користувача.
// Найпростіший варіант відкату: ми не знаємо попередній стан тут легко.
// Стратегія: Позначити стан як "out of sync" і форсувати refetch.
}
);
Складність синхронізації клієнтського і серверного стану — це вічна проблема. Саме тому підхід "Тільки RTK Query (без локального слайсу)" часто простіший, але він не дозволяє синхронних оновлень для drag-and-drop.
У більшості випадків для DnD ми просто ігноруємо помилки або показуємо "Toast" повідомлення, і при наступному завантаженні сторінки дані синхронізуються.
Підсумок проекту
Ми створили потужну архітектуру:
- BoardSlice: для миттєвого UI (60 FPS).
- RTK Query: для фонової синхронізації.
- Normalization: для зручної роботи з даними.
- Typescript: для безпеки.
Це база, на якій будуються такі гіганти як Trello, Jira чи Asana.
Інтеграція з RTK Query
Тепер зробимо наш додаток "живим". Ми будемо завантажувати початковий стан з сервера і відправляти зміни назад.
Тестування Redux
Однією з головних переваг Redux є те, що його надзвичайно легко тестувати. Оскільки більшість логіки (редюсери, селектори) — це чисті функції, для їх тестування навіть не потрібно запускати React.