The curious trouble of extending Immutable.js
It’s not “class X extends Immutable.Map”?
Wouldn’t it be cool to be able to build on Immutable.js’ data structures by adding new methods, behaviour or functionality? Apparently that’s not a new idea and there’s a closed issue floating around on GitHub, where various people discuss the advantages of such a feature:
Unfortunately — as it’s being said in the aforementioned issue — this is not part of Immutable.js’ scope, and they have no interest in sacrificing performance for supporting this.
I’m going to officially close this issue since we decided a while back that it wasn’t worth the serious performance costs. We would rather have performant data structures rather than subclassable ones. — Lee Byron
I do agree that it shouldn’t be part of the base classes themselves, but I think that it’s possible to build a subclass of every Immutable.js structure, that is itself subclassable!
So after looking into what prevents Immutable.js from being subclassable, and how it constructs new data structures, I’ve gone ahead and built “Extendable Immutable.js”.
import Extendable from "extendable-immutable";class User extends Extendable.Map {
fullName() {
return this.get('firstName') + ' ' + this.get('lastName')
}
}
This, for instance, is no problem anymore! 🎊 🎉
How does this sorcery work?!
In a nutshell, what Extendable Immutable.js does is wrapping around the Immutable.js classes. Simplified it only does a few things:
- It creates a new class, that inherits from the original data structure
2. It wraps around every method and passes results, that need to be wrapped into a new method, “__wrapImmutable”.
3. “__wrapImmutable” copies all attributes over to an instance of the subclass.
This is more or less the whole thing, and due to it inheriting the original structure, all the “instanceof” checks still pass.
What about the performance implications?
They shouldn’t actually be bad at all! It just adds the usual cost of the prototype chain, and when calling certain operations it has to copy over all properties.
In my opinion this is luckily a reasonably low cost. All methods essentially call “__wrapImmutable”, which does:
// `val` is the original Immutable.js data structure
copy(Object.create(prototype), val)
(It also makes sure that the empty structure has a constant reference, but we can neglect this here)
I don’t have any fancy graphs, showing the performance cost of using this (yet). But rest assured that it’s just a constant cost.
Bonus! We can now easily add new functionality
What if for example you wanted to add type checking (like React’s PropTypes) to your subclass of an Immutable.js sturcture? Since there’s “__wrapImmutable”, you just have to write a custom method for that:
class TypedMap extends Extendable.Map {
__wrapImmutable(...args) {
const res = super.__wrapImmutable(...args); // Do your checks here? return res;
}
}
Since this is called whenever you create a new data structure (on operations, on construction, etc.) this is called whenever the data structure is created or “changes”.
Similarly you can change the constructor as well.
What’s next?
I’m interested to see, what you can build with this! This will hopefully lead to a more active ecosystem around Immutable.js
I’d love to see what you are building using this. Don’t hesitate to reach out here on Medium, on Twitter, or on GitHub.