Masterclass
Signals
What is Reactivity?
let a = 3
let a = 3
let b = a * 10let a = 3
let b = a * 10
console.log(b) // 30let a = 3
let b = a * 10
console.log(b) // 30
a = 4
console.log(b) // 30
let a = 3
let b = a * 10
console.log(b) // 30
a = 4
console.log(b) // 30
b = a * 10
console.log(b) // 40
| A | B | |
|---|---|---|
| 1 | 4 | 40 |
(fx = A1 * 10)
// change tracker
whenAChanges(() => {
b = a * 10
})
| A | B | C | |
|---|---|---|---|
| 1 | 4 | 40 |
<template>
<span id="cell-b1"></span>
</template>
<script>
</script>| A | B | C | |
|---|---|---|---|
| 1 | 4 | 40 |
<template>
<span id="cell-b1"></span>
</template>
<script>
document
.getElementById('cell-b1')
.textContent = state.a * 10
</script>| A | B | C | |
|---|---|---|---|
| 1 | 4 | 40 |
<template>
<span id="cell-b1"></span>
</template>
<script>
whenStateChanges(() => {
document
.getElementById('cell-b1')
.textContent = state.a * 10
});
</script>| A | B | C | |
|---|---|---|---|
| 1 | 4 | 40 |
<template>
<span id="cell-b1" />
<button id="inc">Inc</button>
</template>
<script>
whenStateChanges(() => {
document
.getElementById('cell-b1')
.textContent = state.a * 10
});
</script>
<script>
const btn = document.getElementById('inc')
btn.addEventListener('click', () => {
state.a = 4;
});
</script>
Track & Trigger
Track & Trigger

Value set/get Interception
const [value, setValue] = createSignal(0)
setValue(value() + 1)

Value set/get interception (signal)
track (dependencies) & trigger
+
whenStateChanges (effect)
+
=
reactivity system
Flavors
{
data() {
let count = 1
return {
get count() {
track()
return count
},
set count(newCount) {
count = newCount
trigger()
}
}
}
}
Vue2
{
data() {
return {
count: 1
}
}
}Property Interception
>>
function ref(initialValue) {
let value = initialValue
return {
get value() {
track()
return value
},
set value(newValue) {
value = newValue
trigger()
}
}
}Vue3
const count = ref(0)
count.value = count.value + 1refs
const [count, setCount] = createSignal(0)
setCount(count() + 1)
Solid
function createSignal(initialValue) {
let _value = initialValue
function get() {
track()
return _value
};
function set(val) {
_value = val
trigger();
}
return [get, set]
}signals
const count = signal(0)
count.value = 1
Preact
function signal(initialValue) {
let value = initialValue
return {
get value() {
track()
return value
},
set value(newValue) {
value = newValue
trigger()
}
}
}
signals
const count = signal(0)
count.set(count() + 1)
Angular v16
function signal(initialValue) {
let value = initialValue
function getter() {
track()
return value
}
getter.set = function(newValue) {
value = newValue
trigger()
}
return getter
}
signals
Implementing
a Reactive System
let activeEffect
class Dep {
subscribers = new Set()
track() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
trigger() {
this.subscribers.forEach((sub) => sub())
}
}
function createEffect(cb) {
activeEffect = cb
cb()
activeEffect = null
}Dependency tracker
function createSignal(value) {
// use Dep to implement a signal
const dep = new Dep()
function get() {
dep.track()
return value
}
function set(newValue) {
value = newValue
dep.trigger()
}
return [get, set]
}
// usage
const [count, setCount] = createSignal(0)createSignal
Like Solid createSignal
const [count, setCount] = createSignal(0)
// attach button handler
const btn = document.getElementById('inc')
btn.addEventListener('click', () => {
setCount(count() + 1)
});
// response to changes
createEffect(() => {
const elem = document.getElementById('count')
elem.textContent = 'count is' + count();
});
Using signal
<body>
<div id="count">0</div>
<button id="inc">Increment</button>
</body>Implementing
Vue3
function ref(value) {
const dep = new Dep()
return {
get value() {
dep.track()
return value
},
set value(newValue) {
value = newValue
dep.trigger()
}
}
}
// usage
const count = ref(0)Vue3 ref
const appComponent = {
template: `
<h2 class="text-xl font-bold">Mini Vue</h2>
<div :class="count.value % 2 ? 'text-red-400' : 'text-green-600'">
count is: {{ count.value }}
</div>
<button @click="increment()" class="btn btn-primary">
Inc
</button>
`,
setup() {
const count = ref(0);
return {
count,
increment() {
count.value++;
},
};
},
};
const app = createApp(appComponent);
app.mount("#app");Vue3 app
function createApp(rootComponent) {
return {
mount(target) {
// get state
const state = rootComponent.setup();
// take the template string and set innerHTML
const container = document.querySelector(target);
container.innerHTML = rootComponent.template;
// walk the resulting DOM tree and look
// for directives or bindings like
// `@click` and `{{ }}`
render(container, state);
},
};
}Vue3 app
function render(root, state) {
for (const node of root.childNodes) {
if (node.nodeType === 1 /* ELEMENT */) {
for (const { name, value } of node.attributes) {
if (name === "@click") {
node.addEventListener("click", () => {
evaluateExp(value, state);
});
} else if (name === ":class") {
effect(() => {
node.className = evaluateExp(value, state);
});
}
}
}
else if (node.nodeType === 3 /* TEXT */) {
// handle text
}
}
}Vue3 app
The complete code and reference project can be found at
github

This class was based on the Evan You Reactivity Deep Dive Workshop 2023 and code samples.
Credits
Compacted and simplified by Peter Cosemans
For Euricom DevCruise 2023
MasterclassSignals
By Peter Cosemans
MasterclassSignals
- 68