Difference between declarative and imperative in React.js?
Imagine a simple UI component, such as a "Like" button. When you tap it, it turns blue if it was previously grey, and grey if it was previously blue.
The imperative way of doing this would be:
if( user.likes() ) {
if( hasBlue() ) {
removeBlue();
addGrey();
} else {
removeGrey();
addBlue();
}
}
Basically, you have to check what is currently on the screen and handle all the changes necessary to redraw it with the current state, including undoing the changes from the previous state. You can imagine how complex this could be in a real-world scenario.
In contrast, the declarative approach would be:
return this.state.liked ? <blueLike /> : <greyLike />;
Because the declarative approach separates concerns, this part of it only needs to handle how the UI should look in a sepecific state, and is therefore much simpler to understand.
It is best to compare React (declarative) and JQuery (imperative) to show you the differences.
In React, you only need to describe the final state of your UI in the render()
method, without worrying about how to transition from previous UI state to the new UI state. E.g.,
render() {
const { price, volume } = this.state;
const totalPrice = price * volume;
return (
<div>
<Label value={price} className={price > 100 ? 'expensive' : 'cheap'} ... />
<Label value={volume} className={volume > 1000 ? 'high' : 'low'} ... />
<Label value={totalPrice} ... />
...
</div>
)
}
On the other hand, JQuery requires you to transition your UI state imperatively, e.g, selecting the label elements and update their text and CSS:
updatePrice(price) {
$("#price-label").val(price);
$("#price-label").toggleClass('expansive', price > 100);
$("#price-label").toggleClass('cheap', price < 100);
// also remember to update UI depending on price
updateTotalPrice();
...
}
updateVolume(volume) {
$("#volume-label").val(volume);
$("#volume-label").toggleClass('high', volume > 1000);
$("#volume-label").toggleClass('low', volume < 1000);
// also remember to update UI depending on volume
updateTotalPrice();
...
}
updateTotalPrice() {
const totalPrice = price * volume;
$("#total-price-label").val(totalPrice);
...
}
In the real world scenario, there will be many more UI elements to be updated, plus their attributes (e.g., CSS styles, and event listeners), etc. If you do this imperatively using JQuery, it will become complex and tedious; it is easy to forget to update some parts of the UI, or forget to remove old event handlers (cause memory leak or handler fires multiple times), etc. This is where bugs happen, i.e., UI state and the model state are out of sync.
States out of sync will never happen to React's declarative approach, because we only need to update the model state, and React is responsible to keep the UI and model states in sync.
- Under the hook, React will updates all changed DOM elements using imperative code.
You may also read my answer for What is the difference between declarative and imperative paradigm in programming?.
PS: from above jQuery example, you may think what if we put all the DOM manipulations in a updateAll()
method, and call it every time when any of our model state changes, and the UI will never be out of sync. You are correct, and this is effectively what React does, the only difference is that jQuery updateAll()
will cause many unnecessary DOM manipulations, but React will only update changed DOM elements using its Virtual DOM Diffing Algorithm.
A declarative style, like what react has, allows you to control flow and state in your application by saying "It should look like this". An imperative style turns that around and allows you to control your application by saying "This is what you should do".
The benefit of declarative is that you don't get bogged down in the implementation details of representing the state. You're delegating the organizational component of keeping your application views consistent so you just have to worry about state.
Imagine you have a butler, who is kind of a metaphor for a framework. And you would like to make dinner. In an imperative world, you would tell them step by step how to make dinner. You have to provide these instructions:
Go to the kitchen
Open fridge
Remove chicken from fridge
...
Bring food to the table
In a declarative world, you would simply describe what you want
I want dinner with chicken.
If your butler doesn't know how to make chicken, then you cannot operate in a declarative style. Just like if Backbone doesn't know how to mutate itself to do a certain task, you can't just tell it to do that task. React is able to be declarative because it "knows how to make chicken", for example. Compared to Backbone, which only knows how to interface with the kitchen.
Being able to describe the state reduces the surface area for bugs dramatically, which is a benefit. On the other hand, you might have less flexibility in how things occur because you're delegating or abstracting away how you implement the state.