Redux часто вважають складною бібліотекою, але насправді він базується на кількох дуже простих ідеях. Складність виникає не з самої бібліотеки, а з нових термінів та обмежень, які вона накладає.
Але ці обмеження існують не просто так. Вони — це те, що робить ваш код передбачуваним.
Формально: Redux — це контейнер передбачуваного стану для JavaScript-додатків.
Це означає, що:
Історично Redux був створений Dan Abramov та Andrew Clark у 2015 році як реалізація архітектури Flux (від Facebook), але спрощена і покращена ідеями з мови Elm.
Уся "магія" Redux тримається на трьох китах. Якщо ви зрозумієте їх, ви зрозумієте Redux.
Весь стан вашого додатку зберігається в дереві об'єктів всередині одного Store.
На відміну від Flux, де могло бути багато Store-ів, у Redux Store завжди один. Це як гігантський JSON-об'єкт, що описує весь ваш додаток.
// Приклад того, як виглядає Store всередині
const state = {
user: {
id: 1,
name: 'Ivan',
role: 'admin',
},
todos: [
{ id: 1, text: 'Вивчити Redux', completed: false },
{ id: 2, text: 'Написати код', completed: true },
],
visibilityFilter: 'SHOW_ALL',
}
Переваги:
Єдиний спосіб змінити стан — це відправити (dispatch) Action (дію), об'єкт, що описує, що сталося.
Ви не можете написати state.user.name = 'Petro'. Це заборонено. Замість цього ви кажете системі: "Гей, користувач хоче змінити ім'я".
Це робиться через простий об'єкт:
// Action
{
type: 'UPDATE_USER_NAME',
payload: 'Petro'
}
Це гарантує, що ні потік з мережі, ні подія миші не можуть записати щось у стан безпосередньо. Усі зміни централізовані та відбуваються в строгому порядку.
Щоб описати, як саме стан змінюється у відповідь на actions, ви пишете Reducers (редюсери).
Reducer — це просто функція, яка бере попередній стан і дію, а повертає новий стан.
// (PreviousState, Action) => NewState
function userReducer(state, action) {
if (action.type === 'UPDATE_USER_NAME') {
return {
...state,
name: action.payload,
}
}
return state
}
Важливо: Редюсер не змінює існуючий об'єкт state. Він створює новий об'єкт з оновленими даними. Це і є принцип Іммутабельності, про який ми поговоримо детальніше в наступному уроці.
У Redux дані завжди рухаються в одному напрямку (Unidirectional Data Flow). Це відрізняє його від двостороннього зв'язування (Two-way data binding), яке було популярне в ранніх фреймворках (наприклад, AngularJS).
Життєвий цикл даних у Redux виглядає так:
dispatch({ type: 'DEPOSIT', payload: 10 })reducer({ balance: 0 }, { type: 'DEPOSIT', payload: 10 })return { balance: 10 }Цей цикл робить поведінку програми надзвичайно передбачуваною. Якщо ви бачите неправильні дані на екрані, ви точно знаєте, що це сталося через якийсь конкретний Action, який був неправильно оброблений Reducer-ом.
Але чому так важливо "не змінювати" стан, а "створювати новий"? Чому не можна просто зробити state.value++? Про це — у наступному розділі про чистоту функцій.
Вступ до State Management
Перш ніж пірнати в код Redux, давайте зупинимося і відповімо на головне питання: "Яку проблему ми взагалі намагаємося вирішити?".
Чисті функції та Іммутабельність
Це, мабуть, технічно найважливіший розділ для розуміння того, як писати Redux код правильно. Більшість багів у початківців виникають саме через порушення цих правил.