DEV Community

Discussion on: Declarative vs Imperative - a Thoughtful Comparison

Collapse
 
marzelin profile image
Marc Ziel • Edited

Let's say you want to do business in China but the problem with this is that you don't speak Chinese. You can either learn Mandarin or use a translator.

The reason why you should use a declarative framework instead of talking directly to the imperative DOM API is the same as the reason why you don't write assembler code anymore but use higher-level languages - even if it adds some overhead it's still worth it because it's much easier to read, write and organize.

You probably program computers for years and still don't know the only true computer language: machine code and that's all right because it's for computers, not humans. If you're in the mountains you don't have go native and start sheep breeding; you can just stay yourself and do some rock climbing.

Collapse
 
efpage profile image
Eckehard

If I could only use Assembler or Haskell, maybe I would choose Haskell. But there are several opportunities. So we can decide.

We have tried to implement a clean object oriented design patter close to the DOM, and it comes out this fits well. You get minimal overhead, as you can tightly couple your objects to DOM elements. It seems, most of the problems you get with the declarative approach (e.g. correctly manage state transistions) do not exist in the class based approach.

I assume you can write efficient code using one or the other design pattern. But with a declarative approach on a stateful DOM it seems, 80% of the work is just done to solve problems you would just not have with an imperative approach.

Thread Thread
 
marzelin profile image
Marc Ziel

We can theoretisize all day about it or let the code talk.

Here is a simple Todo App created with a declarative framework.

Build an app with equivalent features in your framework and we'll compare strengths and weaknesses of both approaches in practice.

Thread Thread
 
efpage profile image
Eckehard

Nice competition, just the example already existed:

See the toDo example here

For the example page, the base is just procedural code. There is no clue using a class if you have only one object. In a real world application I would have used two classes to make the whole thing resuable:

  • the baseObj: to create the element and store data
  • the todoObj: to contain the task data

See the "markdown component" example for an example how this would look. Usually this is very simple to put all the "generating" code into the constructor and let the rest be class methods.

Thread Thread
 
marzelin profile image
Marc Ziel • Edited

It's interesting that you haven't implemented filtering todos in your example because that's the perfect case of why declarative is better than imperative.

At this point your todo app interacts with the DOM in two ways:

  • appends element at the end of some container
  • removes element from the DOM

These are relatively simple operations to carry out in DOM API. Shifting elements around is a bit more difficult. I don't have to worry about it because my declarative framework does it for me automatically.

If I have currently displayed a list of completed todos: [1, 3, 5] and want to see all todos [1, 2, 3, 4, 5] I don't have to move DOM elements around myself - I just pass the new list to a component and it gets done automatically. Can your imperative framework do it for you?

Same goes with your todo state. You're touching the DOM not only in constructor but also in setState so you have to remember that keep those two places in sync so that just created todo looks identical to todo that transitions from checked to !checked (initial) state. You played smart and put setState at the end of constructor instead of just setting the proper style directly in this.ta.

But this has its own problems too. In readData you first create a todo:

let todo = new todoObj(area, content);
Enter fullscreen mode Exit fullscreen mode

so you're calling setState once already in constructor. Then, you're calling it again explicitly the second time:

todo.setState();
Enter fullscreen mode Exit fullscreen mode

That's a code smell. You're also changing todo element styles there:

todo.ta.style.height = todo.ta.scrollHeight + "px";
Enter fullscreen mode Exit fullscreen mode

So now you're modifying the DOM element in three places: constructor, setState and readData. I'm smelling something... something like... spaghetti? ;)

In declarative approach things concerning how something looks are all located in just one place: a component.

Another problem with OO approach is serialization. Because your todos are custom objects you can't just stringify them but rather use some tinkering to get it done. Unfortunately, the logic don't properly clean up todos removed by the user so they could stay in user's localStorage forever.

In declarative approach data is just plain objects so JSON.stringify does the job.

All in all, declarative approach provides better code organization and less code to write and maintain since you only care about transitions from 0 -> x and 0 -> y. Transitions from x -> y and y -> x are worked out automatically.

Thread Thread
 
efpage profile image
Eckehard • Edited

Well, first let me thank you for your thoroughly analysis. I´m pretty sure it depends much on the task which approach plays better.

You should also see: The DML-library I´m using is just a wrapper to the HTML-DOM-API, so what you see is the whole machine, nothing much behind. There is no big framework in the background that does all the heavy lifting for me. Some will find this a disadvantage, but it make the usage very lightweight and compatible to almost anything.

The ToDo-App was ready, so the filter was just not implemented. This can be handeled by the base app, but as the todoObjects handel their state autonomously, i would prefer to let them control their visibility too. So, I just would send a broadcast to all toDos and let them decide. As DOM can hide elements, this would be the preferred method.

You are right, the solution for "readObj" is not very elegant, as setState is called twice. This would be simple to avoid, but usually we would have a better organized persitence class in our projects that does the job. It´s the kind of "better do it double than never" thing you will find sometimes, but that does not happen very often.

But, this has nothing to do with "spaghetti": As the objects are tightly coupled to the DOM (in fact, nobody else has access to the DOM elements than the todoObj), state management has to be done by the objects. As the DOM elements are owned by the JS-objects, there is nothing wrong about this.

With the serialization you are pretty right, we always need som "tools" to do the job. But that was never a topic I got headaches from.

Bottomline I would say, we can do the job using both approaches.

  • Possibly you would get some more help from SolidJS,
  • my approach can be used as a webComponent, combined with Solid, React, Angular or even Typo3 (which we have already done)

There are pro´s and con´s on every approach, so I think it is not worth trying to win the contest. We can just try to get the best of each world.