This content originally appeared on Bits and Pieces - Medium and was authored by Fernando Doglio
Let’s take a peek behind the curtain of JavaScript
React and Vue.js and all those frameworks that call themselves “reactive” work around the concept of one-way data binding. In other words, they give developers the ability to bind their models (the data) to the representation (i.e the view). And on top of that, they let you modify the view automatically when you modify the data, hence the name of one-way data binding.
Now, let’s face it, to the untrained eye, that looks like magic. The moment you write something like this:
You feel all-powerful, like you can do anything. But that magic has an explanation and sometimes it’s good to understand it, so let’s take a look at how could you possibly implement something similar without having your head explode in the process.
Warning: understanding this might cause your god-like feelings to go away, so read under your own risk.
Disclaimer: The solutions shown below will depict a not very clean and performant way of tackling the problem. These are meant to show you the ropes, not to act as a final solution ready for production.
Building a basic example
For the first example I want to keep it simple, so following along with the idea of a counter that we can update at will, I want to be able to write the following code and have it working:
A few things to note here:
- I’m not manually binding my counter variable to the span with an id of “counter”.
- I’m not manually updating the content of my view.
- Heck, I’m not even manually creating my variable. After calling the createBindedVariable it just appears on my global scope.
- All I’m doing is reacting to a click and updating a variable in my JS code.
Would this work out of the box?
Of course not, JavaScript does not understand that there is a correlation between the code and the HTML element. That’s not how things work. So we have to do something about it, but the main objective is to keep the above code intact.
Can we do that?
Heck yes! And we don’t really need a lot, just to know a few lesser-known methods of the Object global object.
You see, when we usually define a property of an object, we just define it, and forget about it. However, when we do that, JavaScript by default creates a property descriptor for us. Inside this descriptor, you have things like whether or not you can assign values to it, whether or not you can change its type during runtime and a few other facts about it.
Two very interesting things you can set, are a setter and a getter function. These functions will be called whenever you either assign it a value, or read the value from it. They get called by the runtime, and you don’t really have to change the way you interact with your property. That is fantastic for us, because if we handle it correctly, we can define a setter function that also updates the HTML bound element.
And just like that, you have one-way data-binding!
As you can probably guess, that “magic” happens inside our createBindedVariable function, so let’s take a look at it:
OK, so let’s go over it in detail:
- We’re using the defineProperty method on the window object. Why? Because there is no way for us to define a variable outside the scope of an object and have its descriptor overridden. So we’re playing with the window object, which is used as the global scope on our browser.
- The setter function is saving the new value inside the newVal local variable. Since we’re defining a function here, the closure will keep a reference to this local variable even after the function is done executing. We’re also, as already mentioned, taking the opportunity to update the HTML element with the same id.
- The getter function is just returning the value inside our local variable. Nothing fancy here.
Clearly, this is not an ideal solution, because it only creates global variables, but it’s a great starting point. If you copy & paste the createBindedVariable on your project, you won’t have to worry about manually updating your DOM again.
But we can do one better, let’s try to go for a component-based approach, like, you know, React.
Moving up a notch
Let’s think about components, we want to create some kind of code that is scoped within the confines of a single element. And we want its HTML representation to be React-like in the sense that we want a custom HTML element named like our component.
So an HTML like this would be ideal:
It should render into this:
And we need some kind of code that correlates to the counter-component, that creates the above representation.
Something like this:
OK, that’s far from a React component, but bear with me, I’m trying to keep things simple. Let’s analyze the code, you’ll see this is not really that complex.
- We’re defining a class that extends the CustomComponent class. We’ll take a look at it in a minute.
- We’re defining a static property called observedAttributes , which contains a single string: current-counter . If you look at the HTML representation, you’ll notice it’s one of the properties I defined. That’s going to be where we keep our counter value and this is going to be our “reactive” variable.
- The constructor calls the super method from its parent and defines the styles inside the customStyle property. Here is where we define the styles for our component.
- We then have 2 methods, handleUpClick and handleDownClick they just update a property called currentCounter (notice how it’s the camel-cased version of HTML property?).
- Finally, we have the render method, which returns the HTML representation of the element. We’re clearly not using JSX here, because that’s not something JS supports natively. That said, you could add support for it and return it from within this method. This is pretty standard vanilla JS HTML creation code. We’re defining multiple elements and then setting up 2 event listeners, one for each button.
Take another look at the code, the dynamic part, the actual counter, is updated with just one line of code. Either line 22 or 25 from the above snippet will get the value to update into the HTML without you doing anything.
How is that possible?
We need to look at one last line, the last line of the file, line 58. Sadly I wasn’t able to hide that part of the magic. The customElements object lets you define exactly that, custom HTML elements that are associated with a class. There is where the CustomComponent class comes into play. It takes care of implementing all the lifecycle methods required by the Custom Element Standard.
So let’s take a look at that to understand how the update of a property we never directly defined, can trigger the update of a DOM element.
If you liked what you’ve read so far, consider subscribing to my FREE newsletter “The rambling of an old developer” and get regular advice about the IT industry directly in your inbox.
The CustomComponent class
This class is where the actual magic for this version of the component happens. It takes care of implementing the required lifecycle methods that a custom element requires.
If you’d like to know more about how to create custom elements on your own, you can start with the MDN docs, which are great and filled with details.
With that said, our class is an attempt to abstract the requirements imposed by the standard and provide something that can be re-used into creating other components. I could’ve perfectly implemented my component inside this class, however, but the code would not have been as easy to understand.
Not that big, but still, we need to cover some details:
- The class extends HTMLElement , that’s the native class our components need to extend. It’ll provide us with the internal behavior and lifecycle methods to implement.
- The constructor is nothing fancy, but it does call the super method, like before. And it also takes care of creating a shadow root node, which is where everything will be attached. We will work on this node, and the browser will take care of attaching that shadow node into the DOM for us.
- The first method to look at should be the connectedCallback . This is a method provided by the HTMLElement class that we have to re-implement. It gets called when the shadow node gets inserted into the DOM and it’s when we can begin inspecting our element and querying its attributes. That last part is very important, because this can’t be done inside the constructor, since at that time the element is not yet fully ready. This method takes care of setting up some internal accessors and calling the display method (which in turn calls the render method of our component).
- The setUpAccessors method does a little bit of meta-programming and adds, on runtime, one property for each attribute found on the HTML representation of our element. This is important, because as you can see, we’re re-defining their getters and setters on their descriptors. This is needed because this way when we change the value of this.currentCounter we’ll be actually changing the value of the HTML attribute current-counter , which as you already saw, was defined as an observed attribute on the child class. The fact that is inside that array will make the HTMLElement class trigger the attributeChangedCallback method whenever we change it.
- And while we’re on that subject, the attributeChangedCallback method receives the name of the attribute changed, its new and previous values. This could be useful if my logic was a bit more complex, but for the sake of simplicity whenever we see a property being updated, we re-render the whole component. This means we call the display method gain.
- The display method, in turn, takes care of removing the existing representation from the DOM and re-creating it by calling the render method, which if you remember, is the one we re-implemented in our custom class.
And that is it, that’s how the data flows. Whenever we trigger an update on our data model (in our example, the currentCounter property) the UI gets updated instantly.
As you can see, this is far from magic coding, but it’s also far from being the complex monster some people make it out to be. Granted, React is not implemented like this, the team behind it has done an incredible job abstracting this API even further, especially with the hooks API.
That said, these are the building blocks I would use if I wanted to build something similar to React, Vue or any of the other alternatives around. And this is also the reason why even though they all may provide different solutions, the underlying philosophy is the same.
This is not the case for others like Svelte, who go out of their way to provide a truly different and radical approach.
Note, If you’d like to read the full code and see it in action, you can check it out from this Github repo.
Have you done anything with these APIs in the past? Did you know they existed? Have I triggered the need in you to create the next React? Leave a comment and let’s chat!
Build composable web applications
Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favorite frameworks like React or Node. Build scalable and modular applications with a powerful and enjoyable dev experience.
Bring your team to Bit Cloud to host and collaborate on components together, and speed up, scale, and standardize development as a team. Try composable frontends with a Design System or Micro Frontends, or explore the composable backend with serverside components.
Learn More
- How We Build Micro Frontends
- How we Build a Component Design System
- The Composable Enterprise: A Guide
- 7 Tools for Faster Frontend Development in 2022
Demystifying React: Create One-way Data Binding with Vanilla JS was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Fernando Doglio
Fernando Doglio | Sciencx (2022-03-28T08:15:04+00:00) Demystifying React: Create One-way Data Binding with Vanilla JS. Retrieved from https://www.scien.cx/2022/03/28/demystifying-react-create-one-way-data-binding-with-vanilla-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.