Skip to content

Instantly share code, notes, and snippets.

@soanvig
Last active December 16, 2021 18:58
Show Gist options
  • Save soanvig/6a02c7af0cd7bb36912c5409b07f93ef to your computer and use it in GitHub Desktop.
Save soanvig/6a02c7af0cd7bb36912c5409b07f93ef to your computer and use it in GitHub Desktop.
Vue reactivity underhood - simple example
// Dep is dependency tracker
// Each tracked value (property of some state object)
// should have it's own Dep object.
class Dep {
constructor () {
// In Set each object must be unique.
// Adding one thing as dependency multiple times
// adds it only once.
// As `dependency` we understand function that called tracked value.
this.dependencies = new Set();
}
depend () {
if (Dep.activeFunction) {
this.dependencies.add(Dep.activeFunction);
}
}
notify () {
// Call each function that originally
// depends on tracked value.
this.dependencies.forEach((f) => {
f();
});
}
}
// Because JS is one-threaded, only one function
// can be called at any time. Because of that - simplified - statement
// we can save that function in 'global' object.
Dep.activeFunction = null;
// To make any value interactive, we need to define setter&getter
// To do that we use Object.defineProperty (ES5.1)
function makeReactive (obj) {
Object.keys(obj).forEach(key =>{
// Because of JS clousure anything declared here
// will be applied only to that property.
// Create new dependency tracker for that value
const dep = new Dep();
// Save that value, because otherwise we wouldn't
// be able to use it in getter and setter.
let internalValue = obj[key];
Object.defineProperty(obj, key, {
get () {
// Save dependency
dep.depend();
return internalValue;
},
set (newValue) {
// Update value and call all dependent functions
internalValue = newValue;
dep.notify();
}
});
});
}
// Because we can't just 'get access' to 'already executing' function
// we need to create wrapper, that will save that for us.
// Any time `f` innerFunction will be executed
// it will register itself as currently executing function.
function watch (f) {
function innerFunction () {
Dep.activeFunction = innerFunction;
f();
Dep.activeFunction = null;
}
return innerFunction;
}
// Finally, use everything.
const state = {
a: 1,
b: 1
}
// Create render function, which of course will be watched.
const render = watch(() => {
console.log(state.a);
});
// Make state reactive
makeReactive(state);
// Call render for the first and only time
render();
// => log: 1
state.b += 1;
// => log: nothing - because updating 'b' shouldn't rerender - it is just not used.
state.a += 1;
// => log: 2 - automagically!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment