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>
const [value, setValue] = createSignal(0)
setValue(value() + 1)
{
data() {
let count = 1
return {
get count() {
track()
return count
},
set count(newCount) {
count = newCount
trigger()
}
}
}
}
{
data() {
return {
count: 1
}
}
}Property Interception
>>
function ref(initialValue) {
let value = initialValue
return {
get value() {
track()
return value
},
set value(newValue) {
value = newValue
trigger()
}
}
}const count = ref(0)
count.value = count.value + 1refs
const [count, setCount] = createSignal(0)
setCount(count() + 1)
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
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)
function signal(initialValue) {
let value = initialValue
function getter() {
track()
return value
}
getter.set = function(newValue) {
value = newValue
trigger()
}
return getter
}
signals
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
}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)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();
});
<body>
<div id="count">0</div>
<button id="inc">Increment</button>
</body>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)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");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);
},
};
}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
}
}
}The complete code and reference project can be found at
This class was based on the Evan You Reactivity Deep Dive Workshop 2023 and code samples.
Compacted and simplified by Peter Cosemans
For Euricom DevCruise 2023