Когда приложение разрастается, работа с состоянием через обычные глобальные переменные становится непрактичной и приводит к хаосу. Чтобы избежать этого, важно использовать более структурированные подходы, такие как паттерн Observer. Он помогает разделить логику обновления и реакцию на изменения, что делает код более гибким и легко масштабируемым.
Рассмотрим, как создать и использовать простую реализацию этого паттерна для управления состоянием.
✏️ Создание Observable
В отдельном файле создадим функцию, которая будет хранить состояние и управлять подписчиками. Назовем её createObservable:
// observable.js
export const createObservable = (initialState = []) => {
let state = initialState;
let observers = [];
// Метод для уведомления всех подписчиков об изменениях
const notify = () => {
observers.forEach((observer) => observer(state));
};
return {
// Метод для подписки на изменения
subscribe: (observer) => {
observers = [...observers, observer];
},
// Метод для отмены подписки
unsubscribe: (observer) => {
observers = observers.filter((obs) => obs !== observer);
},
// Метод для изменения состояния и уведомления подписчиков
setState: (newState) => {
state = newState;
notify();
},
// Метод для получения текущего состояния
getState: () => state,
};
};
💭 Создание хранилища (Store)
Теперь вместо использования обычного массива, создадим состояние для списка фильмов, используя наш createObservable. Это позволит нам централизованно управлять состоянием и уведомлять подписчиков об изменениях.
// store.js
import { createObservable } from './observable';
const moviesObservable = createObservable([]);
⌛️ Добавление данных в хранилище
Теперь предположим, что вы загружаете данные о фильмах с API:
const fetchMovies = async (query) => {
const response = await fetch(`https://api.example.com/movies?search=${query}`);
const movies = await response.json();
return movies;
};
// Получаем фильмы и обновляем состояние хранилища
const moviesFromApi = await fetchMovies('Inception');
moviesObservable.setState(moviesFromApi); // Теперь в хранилище у нас массив фильмов
✉️ Подписка на изменения
Когда состояние изменится, нам нужно автоматически отобразить новые данные. Например, создадим функцию displayMovies, которая будет выводить фильмы на экран, и подпишем её на наш moviesObservable:
const displayMovies = (movies) => {
console.log('Фильмы обновились:', movies);
};
// Подписываемся на изменения в состоянии хранилища
moviesObservable.subscribe(displayMovies);
Теперь каждый раз, когда состояние хранилища изменяется через setState, функция displayMovies будет автоматически вызываться с обновлёнными данными.
🔔 Получение текущего состояния
Для доступа к текущим данным из хранилища можно использовать метод getState:
const currentMovies = moviesObservable.getState();
console.log('Текущие фильмы:', currentMovies);
Преимущества использования паттерна Observer:
- Централизованное управление состоянием — вся логика управления данными сосредоточена в одном месте.
- Гибкость — вы можете легко добавлять новые подписки или отменять их при необходимости.
- Простота тестирования — можно отдельно тестировать логику хранилища и взаимодействие с подписчиками.
- Отделение состояния от представления — подписчики отвечают только за реакцию на изменения, а само хранилище не зависит от реализации компонентов интерфейса.
Этот паттерн можно легко расширить на более сложные состояния: добавить возможность частичного обновления состояния, реализовать логику редукторов для обновления состояния в зависимости от действий, подключить несколько таких хранилищ и организовать обмен данными между ними.
Использование паттерна Observer — это хорошая база для перехода к более сложным инструментам управления состоянием, таким как Redux или MobX.
#javascript #store #observer