Skip to content

Instantly share code, notes, and snippets.

@devhammed
Last active February 22, 2024 22:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devhammed/f741fc0324cc7d3ad1061888e675d9b0 to your computer and use it in GitHub Desktop.
Save devhammed/f741fc0324cc7d3ad1061888e675d9b0 to your computer and use it in GitHub Desktop.
120-lines implementation of a reactive system in JavaScript.
const { reactive, effect,computed, ref, watch} = require('./reactive');
const x = ref(0)
const y = ref(0)
const state = reactive({
showSword: false,
message: "Hey young padawan!",
});
const sword = ref({
sharpness: 10,
durability: 100,
});
const computedMessage = computed(() => {
return state.showSword ? "You have a sword!" : "You have no sword!";
});
const effectToDispose = effect(() => {
console.log('tracking showSword but I will be disposed after the second change: ', state.showSword);
});
effect(() => {
console.log(computedMessage.value);
});
effect(() => {
console.log(state.message);
});
effect(() => {
console.log('Sword durability', sword.value.durability);
});
watch(() => state.showSword, (newVal, oldVal) => {
console.log("showSword changed from " + oldVal + " to " + newVal);
});
watch(computedMessage, (newVal, oldVal) => {
console.log("Computed message changed from '" + oldVal + "' to '" + newVal + "'");
});
watch(() => sword.value.sharpness, (newVal, oldVal) => {
console.log("Sword sharpness changed from " + oldVal + " to " + newVal);
});
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
x.value = 1;
y.value = 2;
state.showSword = true;
state.message = "Welcome to the dark side!";
effectToDispose();
state.showSword = false;
sword.value = {
...sword.value,
sharpness: 20,
}
y.value = 3;
x.value = 4;
let activeEffect = null;
let disposables = new Set();
let targetMap = new WeakMap();
function track(target, key) {
if (activeEffect) {
let depsMap = targetMap.get(target);
if ( ! depsMap) {
targetMap.set(target, depsMap = new Map());
}
let deps = depsMap.get(key);
if ( ! deps) {
depsMap.set(key, deps = new Set());
}
deps.add(activeEffect);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if ( ! depsMap) {
return;
}
const deps = depsMap.get(key);
if ( ! deps) {
return;
}
disposables.forEach(function (fx) {
if (deps.has(fx)) {
deps.delete(fx);
disposables.delete(fx);
}
});
deps.forEach((fx) => fx());
}
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
return () => disposables.add(fn);
}
function ref(value) {
const valueKey = 'value';
const refObject = {
get value() {
track(refObject, valueKey);
return value;
},
set value(newValue) {
value = newValue;
trigger(refObject, valueKey);
}
}
return refObject;
}
function computed(fn) {
let value = null;
let runner = () => value = fn();
effect(runner);
return {
get value() {
return runner();
}
}
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key);
}
})
}
function watch(selector, fn) {
let get = () => typeof selector === 'object' && 'value' in selector
? selector.value
: selector();
let value = get();
effect(() => {
let newValue = get();
if (value !== newValue) {
fn(newValue, value);
value = newValue;
}
});
}
module.exports = { reactive, ref, computed, effect, watch };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment