Reactivity is at the heart of many web interfaces. It makes programming robust and interactive web apps much, much easier. Although most frameworks have reactivity as a built in feature, there will always be a point when you need reactivity in plain JavaScript. So, here I will show you how to implement reactivity in JavaScript.
Wait... What is reactivity?
There are a bunch of explanations out there, the best one so far being this. But here, I'll show you a code sample, which is easier to understand.
Suppose you have this:
let who = 'Siddharth';
document.querySelector('h1').innerText = who;
Later, you change who
:
who = 'Somebody';
But the content in the H1 does not change until we call document.querySelector('h1').innerText = who;
again. This is where reactivity comes in. It automatically reruns the code (in our case document.querySelector('h1').innerText = who;
) when the referred variables change. So, when we change the variable, the change is automatically reflected in the code.
The engine
Note: to keep this tutorial simple (and fun!), I won't implement error handling, objects, and all the boring checks. The next parts of this tutorial (if I write them!) will go in detail on some of them.
First, let's build an object which we need to react to:
let data = {
name: 'John Doe',
age: 25
};
One way to make it reactive would be to have setters/getters to listen for events, and react to that.
A quick note on setters/getters. Getters and setters are functions which are called when an object's property is called/set. Here's a simple example: |
---|
const obj = {
data: [],
get foo() {
return this.data.join(', ');
},
set foo(val) {
this.data.push(val);
}
}
obj.foo = 1;
obj.foo = 2;
obj.foo = 3;
obj.foo; //=> 1, 2, 3
Setters and getters are really helpful when building reactivity |
---|
So, we would need to change the object to be like this:
let data = {
name: 'John Doe',
get name () {
return this.name;
},
set name (val) {
this.name = name;
// TODO notify
}
};
And code using it would look like this:
const data = new Reactive({
name: 'John Doe',
age: 25
});
data.listen('name', val => console.log('name was changed to ' + val));
data.contents.name = 'Siddharth';
//=> name was changed to Siddharth
So, let's first build the Reactive
class:
class Reactive {
constructor(obj) {/* TODO */}
listen(prop) {/* TODO */}
}
the constructor is quite simple, just set the data and start observing:
constructor (obj) {
this.contents = obj;
this.listeners = {}; // Will be explained later
this.makeReactive(obj);
}
Now, we'll implement makeReactive
:
makeReactive(obj) {
Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop));
}
Now, we'll implement makePropReactive
:
makePropReactive(obj, key) {
let value = obj[key]; // Cache
Object.defineProperty(obj, key, {
get () {
return value;
},
set (newValue) {
value = newValue;
this.notify(key);
}
});
}
Here, we use Object.defineProperty
to set getters on an the object.
Next thing to do is set up a notifier and an listener. The listener is pretty simple:
listen(prop, handler) {
if (!this.listeners[prop]) this.listeners[prop] = [];
this.listeners[prop].push(handler);
}
Here, we set listeners on an object as values in an array.
Next, to notify:
notify(prop) {
this.listeners[prop].forEach(listener => listener(this.contents[prop]));
}
And that's the end! Here's the full code:
class Reactive {
constructor (obj) {
this.contents = obj;
this.listeners = {};
this.makeReactive(obj);
}
makeReactive(obj) {
Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop));
}
makePropReactive(obj, key) {
let value = obj[key];
// Gotta be careful with this here
const that = this;
Object.defineProperty(obj, key, {
get () {
return value;
},
set (newValue) {
value = newValue;
that.notify(key)
}
});
}
listen(prop, handler) {
if (!this.listeners[prop]) this.listeners[prop] = [];
this.listeners[prop].push(handler);
}
notify(prop) {
this.listeners[prop].forEach(listener => listener(this.contents[prop]));
}
}
Simple, isn't it? Here's a repl:
Thanks for reading! In the next parts, we'll get a bit more into how we can enhance this.