Перейти к содержанию

Что такое реактивность

Введение

Реактивность - это основа интерактивности приложений Solid. В этой статье мы рассмотрим основы реактивности и то, как она работает в Solid.

Реактивность - это шаблон программирования, позволяющий настроить поведение, которое автоматически "реагирует" на изменение данных.

Например, можно сделать корзину покупок с ярлыком, на котором отображается общая стоимость товаров. С помощью реактивности можно указать, чтобы эта метка автоматически обновлялась при добавлении или удалении товара.

Таким образом, вам придется управлять только данными (например, добавлять товар в корзину). Как только вы соедините элементы пользовательского интерфейса (например, метку) с данными, обновление пользовательского интерфейса (изменение метки) будет выполнено за вас.

Чтобы начать работу с Solid, необходимо иметь базовое представление о реактивных сигналах и эффектах. В этой статье мы расскажем вам, как они работают за кулисами. Более глубокое понимание реактивности поможет вам оптимизировать код, отлаживать проблемы и создавать собственные расширения реактивной системы.

Реактивные примитивы

Чтобы использовать реактивность, вы создаете реактивные примитивы. Наиболее важными примитивами являются сигналы и эффекты. В следующих разделах мы объясним, что такое сигналы и эффекты, а затем сами реализуем их простую версию.

Другие реактивные примитивы

Solid имеет и другие реактивные примитивы, но они являются производными от сигналов и эффектов. Например:

  • Хранилища - это деревья сигналов.
  • Память - это сигналы, которые обновляются подобно эффектам
  • Ресурсы - это сигналы, которые обновляются при получении данных с сервера
  • Эффекты рендеринга - это эффекты, которые первоначально запускаются раньше.

Сигналы

Сигнал представляет собой часть данных, которая может изменяться. Например, сигналом может быть имя пользователя или значение счетчика, которое мы можем увеличивать. Сигналы состоят из двух основных частей:

  • getter - функция, позволяющая получить доступ к текущему значению сигнала.
  • setter - функция, позволяющая установить текущее значение сигнала.

Мы создаем сигнал с помощью функции Solid createSignal. Она возвращает геттер и сеттер в виде двухэлементного массива. Обычно мы используем деструктуризацию массива, чтобы присвоить геттеру и сеттеру переменные с выбранными нами именами.

1
2
3
4
5
6
7
import { createSignal } from 'solid-js';

const [count, setCount] = createSignal(1);

console.log(count()); // prints "1"

setCount(0); // Sets the count value to 0

В этом примере count - это геттер, а setCount - сеттер.

Эффекты

Эффект представляет собой действие, которое мы хотим выполнить при изменении данных в одном или нескольких сигналах. Solid предоставляет функцию createEffect, которая сама принимает функцию. Solid запустит эту функцию, а затем повторно запустит ее всякий раз, когда любой сигнал внутри этой функции изменит значение.

Например, следующий эффект будет console.log подсчитывать счетчик при каждом его обновлении:

1
2
3
4
5
6
7
import { createSignal, createEffect } from 'solid-js';

const [count, setCount] = createSignal(0);

createEffect(() => {
    console.log(count());
});
react

Обратите внимание, что в Solid не нужно указывать массив зависимостей для эффекта. Это основное отличие Solid от React — Solid будет автоматически отслеживать зависимости, а React - нет. Поэтому при повторном рендеринге компонента React повторно запускает любой эффект, если только разработчик явно не укажет зависимости.

Создание реактивной системы

Один из лучших способов понять, как работает реактивность, - это реализовать ее самостоятельно. В этом разделе мы реализуем тот же реактивный паттерн, который использует Solid: паттерн наблюдатель. В паттерне наблюдателя данные (сигналы) хранят список своих подписчиков (эффектов). При изменении данных сигнал вызывает всех своих подписчиков.

Используем для нашей реализации те же имена, createSignal и createEffect:

1
2
3
4
5
6
7
8
9
function createSignal() {}

function createEffect() {}

const [count, setCount] = createSignal(0);

createEffect(() => {
    console.log('The count is ' + count());
});

Сначала разберемся с основами функции createSignal. Она должна:

  • инициализировать значение count до 0 (аргумент, передаваемый функции createSignal)
  • возвращать двухэлементный массив, состоящий из getter и setter функции
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function createSignal(initialValue) {
    let value = initialValue;

    function getter() {
        return value;
    }

    function setter(newValue) {
        value = newValue;
    }

    return [getter, setter];
}

Теперь мы можем получить текущее значение нашего сигнала, вызвав геттер, и установить его, используя сеттер. Это замечательно, но реактивности пока нет.

Далее давайте настроим createEffect. Мы знаем, что он берет функцию и запускает ее:

1
2
3
function createEffect(fn) {
    fn();
}

Ключом к реактивности является установление взаимосвязи между createSignal и createEffect.

Для этого мы даем каждому сигналу список подписчиков. Когда мы даем createEffect функцию и запускаем ее, мы хотим найти способ сказать всем сигналам, которые будут вызваны по пути, добавить эту функцию в их список подписчиков. Наши следующие шаги:

  • Создать глобальный currentSubscriber, который будет следить за функцией, передаваемой в createEffect.
  • Зарегистрировать функцию, которую мы передаем в createEffect, в качестве текущего подписчика
  • Когда мы обращаемся к сигналу, добавляем текущего слушателя в список подписчиков
  • Когда мы устанавливаем сигнал в новое значение, запускаем всех подписчиков
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
let currentSubscriber = null;

function createSignal(initialValue) {
    let value = initialValue;
    // Maintain a list of a signal's own subscribers
    const subscribers = new Set();

    function getter() {
        // Add to subscriber list
        if (currentSubscriber) {
            subscribers.add(currentSubscriber);
        }

        return value;
    }

    function setter(newValue) {
        value = newValue;
        // Notify all subscribers of the value change
        for (const subscriber of subscribers) {
            subscriber();
        }
    }

    return [getter, setter];
}

function createEffect(fn) {
    // Add function as subscriber in global scope
    currentSubscriber = fn;
    // Run function to trigger the accessor of any signals that are called
    fn();
    // Remove the function as the current subscriber
    currentSubscriber = null;
}

Это весь код, необходимый для создания базовой реактивной системы. Мы можем продемонстрировать, что она работает, увеличивая значение count каждую секунду и наблюдая за консолью.

1
2
3
4
5
6
7
8
9
const [count, setCount] = createSignal(0);

createEffect(() => {
    console.log('The count is ' + count());
});

setInterval(() => {
    setCount(count() + 1);
}, 1000);

Incrementing counter

Мы только что реализовали самую базовую форму системы реактивности Solid!

Отслеживание эффектов синхронно

Следует отметить, что наша система реактивности является синхронной. Она глобально регистрирует подписчика, запускает эффект и снимает подписчика с регистрации.

Что же произойдет, если наша функция createEffect будет выглядеть следующим образом?

1
2
3
4
5
createEffect(() => {
    setTimeout(() => {
        console.log(count());
    }, 1000);
});

Наша реализация createEffect не ждет выполнения этого обратного вызова setTimeout, поэтому к моменту вызова нашего геттера count в глобальной области видимости нет подписчика. Сигнал count не зарегистрирует этот обратный вызов в качестве одного из своих подписчиков.

Работа с асинхронными эффектами

Solid предоставляет некоторые возможности для работы с асинхронными эффектами. Например, можно использовать функцию on для ручного указания зависимостей эффектов.

Более подробно о механизмах отслеживания, специфичных для Solid, можно узнать из документации Tracking Concept documentation.

Узнать больше

Мы надеемся, что вам понравилось это введение в реактивность! Если вы хотите углубиться в эту тему, ознакомьтесь со следующими ресурсами:

Ссылки

Комментарии