56

Using JSX for your own lightweight declarative UI library

 4 years ago
source link: https://www.tuicool.com/articles/qQbInqZ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Using JSX for your own lightweight declarative UI library

Oct 8 ·6min read

Some times you have a problem that is best solved with a small footprint and naïve implementation of a UI library. But even if your requirements are simplicity and a few bytes, you can have declarative and functional UI components. In this post, we’ll see how we can use the JSX syntax with our own custom lightweight UI library.

naqiiuV.jpg!web

Pining for the fjords.

JSX is a syntax introduced with the React library to easier write declarative view components inside JavaScript. For many, it looks like a templating language, but there is a subtle but important difference: With JSX you move markup into JavaScript, but with templating, you move JavaScript into a markup language. It sounds like a trivial difference, but it has major practical implications. For instance, with JSX you have all of JavaScript at your disposal, which also means it can be less syntax to learn.

While JSX was introduced by React, it is built on previous work in the language standard specification called E4X (ECMAScript for XML) . The standard was later deprecated and is no longer supported. React isn’t the only library using JSX. Many others support it as well, such as Vue, preact, inferno, and more.

JSX isn’t strictly necessary to make declarative views, and in many cases, I would say using JavaScript and functions are just as good or even better, but there are some advantages to JSX. In my mind the three most prominent arguments are

  1. Interchangeability with HTML, meaning we can more easily convert back and forth.
  2. Easier communication with designers who write HTML.
  3. Making the syntax more distinct, making it easier to visually separate data transformation and UI output.

These advantages are handy even if you don’t use large frameworks or libraries. We can write a small UI library that can utilize JSX to be more declarative, but still with a very minimal footprint. This will build on many of the same concepts asa previous blog post where we create our own library, but it uses a different approach and isn’t required reading to understand this post.

u26773n.jpg!web

Another duck enjoying the beach life reflecting on the tradeoffs of JSX.

In practicality, this implementation will require a build step to transform our code, but we will only transform the JSX through Babel and use modern language features supported by all major modern browsers.

How does JSX work?

Personally, it took some time to get used to the idea of JSX, but one of the things that made it easier for me to accept it back then was that the conceptual distance between JSX and JavaScript is small. Also, the actual syntax isn’t that different. We could almost do a Levenshtein distance comparison and talk of single-digit numbers. Let us see how it works.

If we have a package.json with the following Babel dependencies and configuration:

We can from our root (where the package.json is located), build our index.js file and get the output in clean JavaScript:

npm run build

With this setup, we can investigate what the output of JSX is. Given our index.js :

It will output:

Interesting! We see the tag is passed as a string to a function defined on the React namespace, and the last argument is the child, which is, in this case, a string of Hello, World! . If we add some attributes (or props as it is often called with React), we can see what the second argument is:

The second argument is an object passed as properties to the createElement function. How about child elements?

createElement is a variadic function where all but the first two parameters are children. We can say that the signature looks like this:

We can create a function that matches this signature and output our custom code. But for that to be possible, we somehow need to have the output of the JSX compilation be something other than React.createElement . Great news! The Babel JSX plugin supports that.

We can use Babel plugin configuration to override the function:

Now our output would be:

Perfect! All we need to do now is create our own library.

QNnuMjn.jpg!web

This is a library creating duck.

Creating our own UI library

We know what signature we have to use:

Where tag is HTML tag, props is an object of attributes, and children is text content or other elements. Let's start with the first part, which is to create our DOM Elements with text content:

It works just as expected! We can also extend this to support other DOM Elements as children:

So far, so good! We already have a working library for outputting something looking like HTML — in our JavaScript. But we need attributes to add things like CSS classes, or button types, or even events for interacting with the elements.

So let’s make a function attrs which checks for what attributes we have and add them to the DOM Elements accordingly. For this example, we say we can have 3 different attributes: normal attributes, classes, and events.

With this we can enhance our elements:

And we should now have a working library with JSX support!

We are missing something really important, though. An important part of having declarative views is the ability to separate view blocks into different components. And JSX supports that. The first argument can be HTML tag string, or it can be a function, as illustrated by:

To support this we have to expand our createElement function for special cases where our first parameter is a function. Before we do that, though, we have to look at how the signature is for custom components. In React it is normal to specify function components such as:

So it’s a different signature than our createElement . The most notable difference is that the children array is a part of the props, not as variadic rest parameters. Knowing this, we can extend our library:

And this will get us a long way! But if we look closely, there is a subtle bug when doing this. In our Header component, we actually use children directly as an array. Meaning, when we iterate through all children we try to append nested arrays as children to a DOM Element. To fix this, one way is to flatten out our children objects before iterating through it:

(Note: Here we use .flat which requires a very modern browser or even polyfill.)

With some cleaning up and modularizing our final code looks like this:

Closing notes

This is by no means a perfect implementation. This is intended for special cases where you have a specific problem to solve and don’t need libraries to handle your view output. You can use this to create dynamic content with JavaScript and you can even use it to update based on some state. But keep in mind that it will replace the entire DOM tree and would not be performant when updating the state often. This isn’t for “applications”. It is for specialized web pages with dynamic content where you have to create some DOM nodes and want to do this in a declarative manner that is a bit more ergonomic than the imperative low-level DOM API.

This implementation isn’t for every use case, but in some cases, it can be very valuable. And in any case, it is a good exercise to learn more about JavaScript and how the JSX basic syntax work! For JSX there is also something called fragments, which this post doesn’t cover, but would be trivial to support using the same approach described.

2IbmeeR.jpg!web

Mother duck teaching the lessons of JSX to her mighty ducks.

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK