Last active
December 16, 2021 18:58
-
-
Save soanvig/6a02c7af0cd7bb36912c5409b07f93ef to your computer and use it in GitHub Desktop.
Vue reactivity underhood - simple example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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