Как работают 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; }
Т.е. теперь порядок действий следующий:
- Функция effect положила переданную в нее в качестве аргумента функцию в глобальную переменную current.
- createSignal в своем методе read положила из глобальной переменной эту функцию в массив subscribers, потому что ее метод read был передан в функцию effect в качестве аргумента передаваемого туда колбека, что видно в файле using-signals.mjs.
- Функция effect очистила глобальную переменную current, так как ее значение больше не нужно.
- Теперь каждый раз, когда мы сетим новое значение в сигнал, то будет отрабатывать функция из нашего массива — одна и та же, т.е. не надо передавать, как в предыдущем примере, через метод 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.