Как работают signals

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

Signal = Data + Update function + Subscribers

Как создать простой signal

Нам понадобиться 2 файла:

  • custom-signal.mjs – здесь будет сам кода для сигнала.
  • using-signals.mjs – здесь будем его использовать.

Вариант №1 (неоптимальный)

В файле custom-signal.mjs создадим функцию createSignal, содержащую в себе еще исходное значение и 2 функции – read и set. А также массив подписчиков. Возвращать основная функция будет эти две в виде массива.

export function createSignal(initialValue) {
    let value = initialValue;
    const subscribers = [];

    function set(newValue) {
        value = newValue;
        subscribers.forEach(sub => sub(value))
    }

    function read(sub) {
        subscribers.push(sub)
        return value;
    }

    return [read, set]
}

Теперь создадим файл using-signals.mjs:

import {createSignal} from "./custom-signal.mjs";

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

const subscriber = count(console.log);

console.log(subscriber); // reading
setTimeout(() => {
    setCount(30); //updating value
}, 1000)

Теперь, когда мы подписались, то передали функцию (в данном случае console.log), которая добавилась в массив subscribers. И каждый раз, когда будем сетить новое значение, в цикле forEach для подписчика будет выполняться эта функция. Когда в setTimeout мы засетим новое значение, то в консоли сразу оно выведется.

НО ЭТО неоптимальное решение! Так как нам в подписчике надо передавать каждый раз функцию.

Вариант №2 (усовершенствованный)

Для решения проблемы предыдущего вариант создадим отдельную функцию effect, которая будет использоваться для оборачивания кода, выполняющего чтение сигнала без передачи функции в метод read. Эта функция effect будет выполняться каждый раз при изменении значения сигнала.

Для достижения этого также создадим глобальную переменную current, которая будез за пределами обеих фунций (основной и новой effect). В этой переменной будет хранится функция, которая должна выполняться каждый раз, когда изменяется значение сигнала.

В effect функции мы сетим эту переменну аргументом, который пришел в эту функци. А затем, после помещения ее в массив subscribers, очищаем глобальную переменную.

custom-signal.mjs

let current;

export function createSignal(initialValue) {
    let value = initialValue;
    const subscribers = [];

    function set(newValue) {
        value = newValue;
        subscribers.forEach(sub => sub()); // Executing the subscribed effect function AFTER the signal value was updated
    }

    function read() {
        subscribers.push(current)
        return value;
    }

    return [read, set]
}

export function effect(fn) {
    current = fn;
    fn(); // this is a function that call the signal's "read" function inside of it
    current = null;
}

Т.е. теперь порядок действий следующий:

  1. Функция effect положила переданную в нее в качестве аргумента функцию в глобальную переменную current.
  2. createSignal в своем методе read положила из глобальной переменной эту функцию в массив subscribers, потому что ее метод read был передан в функцию effect в качестве аргумента передаваемого туда колбека, что видно в файле using-signals.mjs.
  3. Функция effect очистила глобальную переменную current, так как ее значение больше не нужно.
  4. Теперь каждый раз, когда мы сетим новое значение в сигнал, то будет отрабатывать функция из нашего массива – одна и та же, т.е. не надо  передавать, как в предыдущем примере, через метод read.

using-signals.mjs

import {createSignal, effect} from "./custom-signal.mjs";

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

effect(() => console.log(count())); //this is the function "fn" that's called inside of effect(). It's calling the signal's "read" function ("count()")

setCount(100); //updating value

Т.е. теперь мы можем использовать функцию effect для регистрации новых подписчиков, передавая в нее функции, которые будут выполняться каждый раз при изменении значения при помощи метода set.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *