Как работает реактивность под капотом и как реализовать её?
Этот вопрос направлен на проверку понимания внутренней работы реактивности в React и того, как она реализуется на практике.
Короткий ответ
Реактивность в React работает через использование состояния и хуков, таких как useState и useEffect. Когда состояние изменяется, React автоматически инициирует перерисовку компонентов, которые зависят от этого состояния, обеспечивая согласованность между данными и интерфейсом. Реактивность достигается через механизм подписки на изменения состояния и виртуальный DOM, который минимизирует обновления реального DOM.
Длинный ответ
Для реализации реактивности на чистом JavaScript, можно использовать несколько ключевых механизмов языка. Один из популярных подходов — использование геттеров и сеттеров в сочетании с паттерном "Наблюдатель" (Observer pattern). Давайте рассмотрим, как это можно сделать на продвинутом уровне.
Шаг 1: Создание реактивного объекта
Для начала, создадим реактивный объект, используя Proxy. Proxy позволяет перехватывать и определять пользовательское поведение для основных операций (например, чтение свойства, присваивание значения и т.д.).
function reactive(target) {
const handler = {
get(target, property, receiver) {
// Здесь можно добавить логику отслеживания зависимостей
console.log(`Чтение ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
// Здесь можно добавить логику уведомления наблюдателей
console.log(`Установка ${property}: ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });Шаг 2: Реализация паттерна "Наблюдатель"
Для уведомления подписчиков об изменениях, реализуем простую систему наблюдателей.
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeUpdate) {
// Регистрируем активное обновление как зависимость
this.subscribers.add(activeUpdate);
}
}
notify() {
// Уведомляем всех подписчиков об изменениях
this.subscribers.forEach(sub => sub());
}
}
let activeUpdate = null;
function autorun(update) {
function wrappedUpdate() {
activeUpdate = wrappedUpdate;
update();
activeUpdate = null;
}
wrappedUpdate();
}Шаг 3: Связывание реактивности и наблюдателей
Теперь, когда у нас есть реактивный объект и система наблюдателей, мы можем их связать. Для этого модифицируем handler в функции reactive, чтобы использовать Dep для отслеживания зависимостей и уведомления подписчиков.
function reactive(target) {
const depsMap = new Map();
const handler = {
get(target, property, receiver) {
let dep = depsMap.get(property);
if (!dep) {
dep = new Dep();
depsMap.set(property, dep);
}
dep.depend(); // Регистрируем зависимость
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
const result = Reflect.set(target, property, value, receiver);
let dep = depsMap.get(property);
if (dep) {
dep.notify(); // Уведомляем подписчиков
}
return result;
}
};
return new Proxy(target, handler);
}Шаг 4: Использование реактивного состояния
Теперь мы можем использовать нашу реактивную систему для автоматического отслеживания изменений и обновления DOM или выполнения других действий.
const state = reactive({ count: 0 });
autorun(() => {
console.log(`Счетчик: ${state.count}`);
});
state.count++; // Автоматически вызовет логирование "Счетчик: 1"