If you worked with frameworks like Angular, React, Vue, SOLID, Svelte, you're already familiar with the Imperative-Reactive paradigm, because it's the one they all support.
Sometimes you may have been led to believe you were doing Functional-Reactive programming but chances are you were not.
Can you tell the difference? Does it matter?
Let's start with an example to help understand.
// react.js with hooks
const component = () => {
const [value, setValue] = useState(0);
return (
<div>
<span>{value}</span>
<button onClick={()=>setValue(()=>value +1)}</span>
</div>
);
}
And following we have a functional-reactive counterpart.
// rimmel.js with rxjs streams
const component = () => {
const count = new BehaviorSubject(0).pipe(
scan(x=>x+1)
);
return rml`
<div>
count: <span>${count}</span>
<button onClick=${count}</span>
</div>
`;
}
So, what's the difference and why does it matter?
First-off, given this example is trivial and overly simplistic, the difference is almost negligible.
As in the latter case you have BehaviorSubject
and scan
as potentially unfamiliar entities, the former case may easily appear simpler or more familiar to reason about: you handle an onclick
event and you set some state setValue(new value)
.
This is the essence of imperative programming, a paradigm in which the currently running code sets the value of something somewhere else.
In the latter case, though, it's the other way around.
If you ever used Excel or Google Sheets, you're already somewhat familiar with functional programming: you define the formula for each cell, like
=SUM(A1:A10) +AVG(B1:B10) +C1 *D2
.
With functional-reactive libraries like Rimmel.js you can do the same: define a formula for any part of your HTML in terms of where should the value be calculated from.
Where should the innerHTML, classes, styles, data-attributes come from? Another textbox? A dropdown? The last position of the mouse? The last message from a websocket?
The answer is "from a stream"
A stream is just a sequence of repeated events over time, like mouse clicks, mouse movements, keystrokes, messages from a websocket, etc.
The beauty and the power of observable streams is that you can transform them at a very high level using RxJS operators like map, reduce, filter, takeUntil, combineLatest and many, many others. They are almost like a streams-processing language, designed to combine and transform streams in a very efficient way.
Ok, it's interesting, but why?
There are a few compelling reasons for functional-reacvite programming and one is testability.
It's much easier to test functional-reactive code than imperative code.
Writing a smaller number of smaller unit tests you can achieve the same level of coverage.
The other case is more concise code, which means less code to write, a seriously smaller chance of introducing bugs (longer code ====== more bugs) and drastically smaller bundle sizes.
This is due to the fact that in imperative programming you don't think in terms of streams. You have a variable here, a variable there, an object up, another down, one is a number, one is a date, etc.
You can do whatever you want with them, which gives you not just more power you can handle, but also the power to hurt yourself and write and unmanageable codebase.
Everything as a stream
When treating everything as a stream, you break down all your problems into stream transformation problems. When some data comes in you transform it and the result goes out. No mocks, stubs, spies or other swiss-knife tools to artificially change your code and make it work for your unit tests without actually buying expensive stocks on the exchange or send out thousands of test emails.
The key feature of streams
The key feature is the stream operators used by RxJS and similar libraries which help you treat everything as a stream, so same operators, same way to combine them together, same way to wait for other events, etc.
Finally, you never have to test this high-level stream manipulation functionality. You don't test map, reduce, withLatestFrom, takeUntil, etc. They are a given, they are tested and they just work. In imperative programming you essentially have to reimplement each of these, somehow in the middle of your business logic. So your code will most likely be a mixture of business logic and stream-manipulation logic together, and the likelyhood of introducing bugs will be orders of magnitude higher.
Downsides?
Oh, yeah, there's no free lunch, breakfast, or snacks, whatever.
You need to spend some time learning how to use these stream-management operators, Observables, RxJS.
It's a bit like learning a new pseudo-language, or DSL.
Suppose you grew up writing everything in Assembly and had to print out some text. You'd have to allocate memory,
get pointers to a string, loop over the memory block until you find some zeros, set some CPU registers, call some interrupt along the way, etc. Then you switch to JavaScript and suddenly you just call console.log('Hello, world')
.
Switching to RxJS will bring similar benefits. You just have to learn that there are specific high-level functions like console.log
that you use to print out some text, rather than messing with CPU registers, allocating memory and calling interrupts.
Is this a downside? A little bit, initially.
In real life you subscribe to a stream, transform the emitted value into a new stream coming from somewhere else until another event happens and take the final value (lol, that's how you do drag'n'drop, btw).
So, it's not always as trivial as console.log
but still way better than reimplementing all this logic.
How does Rimmel.js come to the scene here?
It's the only UI library around that's specifically designed to treat everything as an observable stream.
With other libraries you need third-party adapters and you end up duplicating and complicating a lot of work, potentially sacrificing benefits such as better performance, testability, code quality.
Using observable streams with Rimmel guarantees you reduced code size, bundle size, number of bugs, and you'll get a performance boost along the way, as well.
Happy coding!
Top comments (0)