Reactivity in a framework is a declarative programming model that takes care of keeping the DOM(Document Object Model) in sync with the updates to current state
I know that it's hard to sip, let's get practical so that we solidify our mental models and get a good hold of it!
Let's code up a plain old counter by hand. With the advent of many javascript frameworks and libraries, it is a pretty easy task to accomplish. Will it be the same when developed with plain javascript?
Forget all the frameworks and libraries, Your only tool is javascript now and just get ready for the adventure!
index.html:
Our counter will be rendered into #app
.
index.js:
class Counter {
count = 0;
handleIncrement = () => {
this.count++;
};
handleDecrement = () => {
this.count--;
};
}
I'm defining a class Counter
, with a property count
that defaults to 0
and two methods handleIncrement
, handleDecrement
that handles increment and decrement actions respectively. Our current state is the count
property. Whenever the state is updated, our DOM should be synced up. It shouldn't be stale.
Since we are dealing with plain JS, we should be creating our increment and decrement buttons by hand right? That's what our next task is!
index.js:
setUpButton(action) {
const actionHash = {
Increment: this.handleIncrement,
Decrement: this.handleDecrement
};
const button = document.createElement("BUTTON");
button.textContent = action;
button.onclick = actionHash[action];
return button;
}
Our setupButton
method ensures that It creates a button and associates the respective onclick
handler according to the action
passed as an argument. So we are done with the functionality. Not bad till now. Let's get it into DOM
. We should be coding up our render
method now!
index.js:
render() {
const app = document.getElementById("app");
app.innerHTML = "";
const count = document.createElement("DIV");
count.textContent = this.count;
const elementsToAppend = [
count,
this.setUpButton("Increment"),
this.setUpButton("Decrement")
];
const fragment = document.createDocumentFragment();
elementsToAppend.forEach(element => {
fragment.append(element);
});
app.appendChild(fragment);
}
This is more of a straight-forward implementation of render
method. DOM should be kept in sync with our state count
. So we are clearing up any stale elements that were previously rendered at first by setting innerHTML
to an empty string
. We are creating a div
element that renders our count
value. Then we set up both our increment and decrement buttons and finally we append everything to the #app
element.
Hurray! we are done soon. Let's check whether it's working.
index.js:
new Counter().render();
Output π€―
Oops, it didn't work as expected π±
While checking our code we can find that, once we update our state we've failed to render our app again! That's the cause. Let's fix it π
index.js:
handleIncrement = () => {
this.count++;
this.render();
};
handleDecrement = () => {
this.count--;
this.render();
};
Finally π
The complete source code can be found here.
OMG! see how imperative our solution is π. What if we include a magic layer that takes care of these nitty-gritty things. That is, whenever our current state updates, our app should magically rerender declaratively. That's the way to go, right? What if we add another state in the future and failed to do the same? This solution is less maintainable and not future proof.
To the surprise, the modern javascript frameworks and libraries actually act as the magic layer underhood that takes care of these low-level tasks and makes you more productive by letting you concentrate entirely on the app business logic. The DOM will be in-sync with the state updates and that's a promise given by modern frameworks and libraries.
And also we can't simply rerender the whole app
for a single state change. These tools also ensure that they efficiently update the DOM
and only re-render
the parts that are only necessary.
These tools have their own ways of handling state management.
How does React handle it?
React achieves state tracking via the useState
API in functional components.
With useState
, now the solution is more maintainable and readable and less error-prone. Future updates can be done seamlessly.
useState
function imported from react
when invoked, returns an array. It contains two elements, the first one denotes the state variable itself, while the second element references a function that can be invoked to update that particular state variable. You can't simply use this.count++
or this.count--
directly as we do in plain JS. We should only use the respective state updater functions. This solution is more declarative than the previous one that we hand-coded with plain JS.
But what if I say that there is a more elegant way for achieving this?
Ember, a framework for ambitious web applications provides us some great APIs that are more natural-looking and syntactically very declarative. You can be free from using any state updater functions like this.setState()
. Just count++
or count--
is enough. This is how we do in javascript right?
Octane edition is the latest update in Ember
. This has amazed me with lots of cool new features and a more organized declarative programming model. If I had to pick one out of them, the new Reactivity model earns the medal, to be honest.
Let's see how our counter can be implemented with Ember
π€
Counter.js:
Counter.hbs:
I personally feel this approach to be more natural. You just tell Ember
which properties you wanna keep in the state. Ember
automatically tracks that particular property and keeps the DOM
in-sync on updates to it. Also, your markup is now split into a separate handlebars
file, so that your business logic now becomes less clunky and more readable π€©
This is a lot for now. Let me know your thoughts regarding our approach in the comments below.
Interested to know more about how @tracked
imported from @glimmer/tracking
achieves this complex work underhood?
Want to know how @tracked
keeps the track of different state properties and triggers rerender based on the updates on them?
Curious to know about their internals?
This is what exactly I'll be covering up in my next post. Can't wait for excitement! Meet you there again folks, bye! π€π»
Top comments (6)
Lol, I feel safe being a react devepoler as it comes to plain javascript, I wouldnot learn itπ;
Thank you great postπ
Thanks π€©
Frameworks and libraries make our lives easier.
It is also worth learning what they do under hood π
True, knowing how a tool works is beneficial.
i also wrote an article about it "Using a tool vs. knowing how a tool works internally"
That's an awesome post Anurag π€ Yeah, it is truly beneficial to learn the internals.
One wise person once told me that it's better to make libraries than to invent new freamwork from scratch, because everyone thinks differently and everyone has a different idea for new freamworks.
And chaos begins to grow, every freamwork works differently and you have to learn everything from scratch.
I use svelte and Rich Harris explains the best