This content originally appeared on DEV Community and was authored by Siddharth
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:
const 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 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:
// Setup 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]));
}
}
const data = new Reactive({
foo: 'bar'
});
data.listen('foo', (change) => console.log('Change: ' + change));
data.contents.foo = 'baz';
Thanks for reading! In the next parts, we'll get a bit more into how we can enhance this.
This content originally appeared on DEV Community and was authored by Siddharth

Siddharth | Sciencx (2021-08-12T16:09:52+00:00) Implementing Reactivity from scratch. Retrieved from https://www.scien.cx/2021/08/12/implementing-reactivity-from-scratch/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.