Как Angular Signals работает внутри?
Angular signal — это новая красивая функция, которая поможет разработчикам делать приложения проще, быстрее и писать меньше кода. Но когда видишь это впервые, немного сложно понять, как это работает. Когда вы читаете статьи об «Angular Signals», вы можете найти много информации о том, как его использовать, но нет информации о том, как он работает. Я думаю, что самый простой способ освоить утилиту — это понять, как она работает изнутри. Итак, я хочу показать, как работает угловой сигнал изнутри, создавая простую версию сигналов.
Шаг 1. Сигнальная функция
Как мы знаем, сигнал — это просто функция, которая возвращает функцию, которая возвращает значение. Итак, давайте создадим его:
function signal(value) { function getter() { return value; } return getter; } const a = signal(10) console.log(a()) // console output: 10
Шаг 2. Обновление значений
Когда сигнал создан, нам нужен способ изменить их значение. Давайте расширим `getter` с помощью метода `set`:
function signal(value) { function getter() { return value; } getter.set = (newValue) => value = newValue; return getter; } const a = signal(10) console.log(a()) // console output: 10 a.set(11) console.log(a()) // console output: 11
Теперь у нас есть изменяемое значение.
Шаг 3. Функция эффекта
На первый взгляд
function signal(value) { function getter() { return value; } getter.set = (newValue) => value = newValue; return getter; } function effect(callback) { callback(); } const a = signal(10); effect(() => console.log(a())) // Console output: 10
Это работает, но как подключить эффект и сигнал меняется. Как мы можем уведомить сигнал о том, что он соответствует эффекту? Решение состоит в том, чтобы сделать флаг, который мы изменим в начале и в конце функции эффекта.
let inEffect = false; function signal(value) { function getter() { console.log(`getted from efffect: ${inEffect}`) return value; } getter.set = (newValue) => value = newValue; return getter; } function effect(callback) { inEffect = true; callback(); inEffect = false; } const a = signal(10); a(); // console output: getted from efffect: false effect(() => a()); // console output: getted from efffect: true
Шаг 4. Магия
Теперь мы заменим флаг функцией обратного вызова, и каждый раз, когда кто-то вызывает `getter`, мы будем проверять обратный вызов и сохранять его для каждого сигнала. И когда кто-то вызовет `set`, мы вызовем все сохраненные обратные вызовы;
let currentCallback; function signal(value) { const callbacks = []; function getter() { if (currentCallback) { callbacks.push(currentCallback) } return value; } getter.set = (newValue) => { value = newValue; callbacks.forEach(cb => cb()); } return getter; } function effect(callback) { currentCallback = callback; callback(); currentCallback = undefined; }
Давайте проверим это:
const a = signal(0); const b = signal('a'); effect(() => console.log(`This effect depends from a: ${a()}`)); // console: This effect depends from a: 0 effect(() => console.log(`This effect depends from b: ${b()}`)); // console: This effect depends from b: a effect(() => console.log(`This effect depends from a: ${a()} and b: ${b()}`)); // console: This effect depends from a: 0 and b: a a.set(1); // console: This effect depends from a: 1 // console: This effect depends from a: 1 and b: a b.set('b'); // console: This effect depends from b: b // console: This effect depends from a: 1 and b: b
Шаг 5. Расчет
Это просто комбинация сигнала и эффекта
function computed(fn) { const s = signal(null); effect(() => s.set(fn())) return s; }
и теперь мы можем сделать это
const num = signal(0); const isEven = computed(() => num() % 2 ? 'odd' : 'event') effect(() => console.log(`Number ${num()} is ${isEven()}`));
Заключение
Я надеюсь, что это поможет вам понять сигналы, и вам будет легче интегрировать их в свои проекты.
Полный код из этой статьи: github
Код оригинальных сигналов Angular: github