Add lit articles

This commit is contained in:
Corbin Crutchley
2021-12-02 16:39:28 -08:00
parent 563604e237
commit 3965be9a26
5 changed files with 822 additions and 2 deletions

View File

@@ -1,13 +1,15 @@
---
{
title: "Intro to Web Components: Vanilla JS",
title: "Web Components 101: Vanilla JS",
description: "",
published: '2021-07-15T22:12:03.284Z',
authors: ['crutchcorn'],
tags: ['javascript', 'html', 'web'],
attached: [],
license: 'coderpad',
originalLink: 'https://coderpad.io/blog/intro-to-web-components-vanilla-js/'
originalLink: 'https://coderpad.io/blog/intro-to-web-components-vanilla-js/',
series: "Web Components 101",
order: 1
}
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,252 @@
---
{
title: "Web Components 101: Framework Comparison",
description: "",
published: '2021-12-02T22:12:03.284Z',
authors: ['crutchcorn'],
tags: ['lit', 'vue', 'react', 'angular'],
attached: [],
license: 'coderpad',
originalLink: 'https://coderpad.io/blog/intro-to-web-components-vanilla-js/',
series: "Web Components 101",
order: 3
}
---
Alright alright, I know for a lot of the last article seemed like a big ad for Lit. That said, I promise Im not unable to see the advantages of other frameworks. Lit is a tool in a web developers toolbox. Like any tool, it has its pros and cons: times when its the right tool for the job, and other times when its less so.
That said, Id argue that using an existing framework is more often the better tool for the job than vanilla web components.
To showcase this, lets walk through some of these frameworks and compare and contrast them to home-growing web components.
# Pros and Cons of Vanilla Web Components
While web frameworks are the hot new jazz - its not like we couldnt make web applications before them. With the advent of W3C standardized web components (without Lit), doing so today is better than its ever been.
Here are some pros and cons of Vanilla JavaScript web components:
<table class="wp-block-table"> <tbody> <tr> <th> Pros </th> <th> Cons </th> </tr> <tr> <td> <ul> <li><span>No framework knowledge required</span></li> <li><span>Less reliance on framework</span></li> </ul> <ul> <li><span>Maintenance</span></li> <li><span>Bugs</span></li> <li><span>Security issues</span></li> </ul> <ul> <li><span>Smaller “hello world” size</span></li> <li><span>More control over render behavior</span></li> </ul> </td> <td> <ul> <li><span>Re-rendering un-needed elements is slow</span></li> <li><span>Handling event passing is tricky</span></li> <li><span>Creating elements can be overly verbose</span></li> <li><span>Binding to props requires element query</span></li> <li><span>Youll end up building Lit, anyway</span></li> </ul> </td> </tr> </tbody> </table>
To the vanilla way of doing things credit, theres a bit of catharsis knowing that youre relying on a smaller pool of upstream resources. Theres also a lessened likelihood of some bad push to NPM from someone on the Lit team breaking your build.
Likewise - for smaller apps - youre likely to end up with a smaller output bundle. Thats a huge win!
For smaller applications where performance is critical, or simply for the instances where you need to be as close to the DOM as possible, vanilla web components can be the way to go.
That said, its not all roses. After all, this series has already demonstrated that things like event passing and prop binding are verbose compared to Lit. Plus, things may not be as good as they seem when it comes to performance.
## Incremental Rendering
On top of the aforementioned issues with avoiding a framework like Lit, something we havent talked about much is incremental rendering. A great example of this would come into play if we had an array of items we wanted to render, and werent using Lit.
Every time we needed to add a single item to that list, our `innerHTML` trick would end up constructing a new element for every single item in the list. Whats worse is that every subelement would render as well!
This means that if you have an element like this:
```html
<li><a href=”https://example.com”><div class=”flex p-12 bg-yellow><span>Go to this location</span></div></a></li>
<li><a href=”https://example.com”><div class=”flex p-12 bg-yellow><span>Go to this location</span></div></a></li>
```
And only needed to update the text for a single item in the list, youd end up creating 4 more elements for the item you wanted to update… On top of recreating the 5 nodes (including the [Text Node](https://developer.mozilla.org/en-US/docs/Web/API/Text)) for every other item in the list.
## Building Your Own Framework
As a result of the downsides mentioned, many that choose to utilize vanilla web components often end up bootstrapping their own home-grown version of Lit.
Heres the problem with that: Youll end up writing Lit yourself, sure, but with none of the upsides of an existing framework.
This is the problem with diving headlong into vanilla web components on their own. Even in our small examples in the article dedicated to vanilla web components, we emulated many of the patterns found within Lit. Take this code from the article:
```html
<script>
class MyComponent extends HTMLElement {
todos = [];
connectedCallback() {
this.render();
}
// This function can be accessed in element query to set internal data externally
setTodos(todos) {
this.todos = todos;
this.clear();
this.render();
}
clear() {
for (const child of this.children) {
child.remove();
}
}
render() {
this.clear();
// Do logic
}
}
customElements.define('my-component', MyComponent);
</script>
<script> class MyComponent extends HTMLElement { todos = []; connectedCallback() { this.render(); } // This function can be accessed in element query to set internal data externally setTodos(todos) { this.todos = todos; this.clear(); this.render(); } clear() { for (const child of this.children) { child.remove(); } } render() { this.clear(); // Do logic } } customElements.define('my-component', MyComponent);</script>
```
Here, were writing our own `clear` logic, handling dynamic value updates, and more.
The obvious problem is that wed then have to copy and paste most of this logic in many components in our app. But lets say that we were dedicated to this choice, and broke it out into a class that we could then extend.
Heck, lets even add in some getters and setters to make managing state easier:
```html
<script>
// Base.js
class OurBaseComponent extends HTMLElement {
connectedCallback() {
this.doRender();
}
createState(obj) {
return Object.keys(obj).reduce((prev, key) => {
// This introduces bugs
prev["_" + key] = obj[key];
prev[key] = {
get: () => prev["_" + key],
set: (val) => this.changeData(() => prev["_" + key] = val);
}
}, {})
}
changeData(callback) {
callback();
this.clear();
this.doRender();
}
clear() {
for (const child of this.children) {
child.remove();
}
}
doRender(callback) {
this.clear();
callback();
}
}
</script>
<script> // Base.js class OurBaseComponent extends HTMLElement { connectedCallback() { this.doRender(); } createState(obj) { return Object.keys(obj).reduce((prev, key) => { // This introduces bugs prev["_" + key] = obj[key]; prev[key] = { get: () => prev["_" + key], set: (val) => this.changeData(() => prev["_" + key] = val); } }, {}) } changeData(callback) { callback(); this.clear(); this.doRender(); } clear() { for (const child of this.children) { child.remove(); } } doRender(callback) { this.clear(); callback(); } }</script>
```
Now our usage should look fairly simple!
```html
<script>
// MainFile.js
class MyComponent extends OurBaseComponent {
state = createState({todos: []});
render() {
this.doRender(() => {
this.innerHTML = `<h1>You have ${this.state.todos.length} todos</h1>`
})
}
}
customElements.define('my-component', MyComponent);
</script>
<script> // MainFile.js class MyComponent extends OurBaseComponent { state = createState({todos: []}); render() { this.doRender(() => { this.innerHTML = `<h1>You have ${this.state.todos.length} todos</h1>` }) } } customElements.define('my-component', MyComponent);</script>
```
Thats only 13 lines to declare a UI component!
Only now you have a bug with namespace collision of state with underscores, your `doRender` doesnt handle async functions, and you still have many of the downsides listed below!
You could work on fixing these, but ultimately, youve created a basis of what Lit looks like today, but now youre starting at square one. No ecosystem on your side, no upstream maintainers to lean on.
# Pros and Cons of Lit Framework
With the downsides (and upsides) of vanilla web components in mind, lets compare the pros and cons of what building components using Lit looks like:
<table class="wp-block-table"> <tbody> <tr> <th> Pros </th> <th> Cons </th> </tr> <tr> <td> <ul> <li><span>Faster re-renders* that are automatically handled</span></li> <li><span>More consolidated UI/logic</span></li> <li><span>More advanced tools after mastery</span></li> <li><span>Smaller footprint than other frameworks</span></li> </ul> </td> <td> <ul> <li><span>Framework knowledge required</span></li> <li><span>Future breaking changes</span></li> <li><span>Not as widely known/used as other frameworks (Vue, React, Angular)</span></li> </ul> <p><span></span></p> </td> </tr> </tbody> </table>
While there is some overlap between this list of pros and cons and the one for avoiding Lit in favor of home-growing, theres a few other items here.
Namely, this table highlights the fact that Lit isnt the only framework for building web components. Theres huge alternatives like React, Vue, and Angular. These ecosystems have wider adoption and knowledge than Lit, which may make training a team to use Lit more difficult.
However, Lit has a key advantage over them, ignoring being able to output to web components for a moment - well come back to that.
Even compared to other frameworks, Lit is uniquely lightweight.
Compare the bundle sizes of Vue - a lightweight framework in its own right - compared to Lit.
![Lit weighs in at 16.3 kilobytes while Vue weighs in at 91.9 kilobytes](./bundlephobia.png)
While tree shaking will drastically reduce the bundle size of Vue for smaller applications, Lit will still likely win out for a simple component system.
# Other Frameworks
Lit framework isnt alone in being able to output to web components, however. In recent years, other frameworks have explored and implemented various methods of writing code for a framework that outputs to web components.
For example, the following frameworks have official support for creating web components without changing implementation code:
- [Vue](https://v3.vuejs.org/guide/web-components.html#definecustomelement)
- [Angular](https://angular.io/guide/elements)
- [Preact](https://github.com/preactjs/preact-custom-element)
Vue 3, in particular, has made massive strides in improving the web component development experience for their users.
Whats more is that these tools tend to have significantly larger ecosystems. Take Vue for example.
Want the ability to change pages easily? [Vue Router](https://router.vuejs.org/)
Want a global store solution? [Vuex
](https://vuex.vuejs.org/)Prefer similar class based components? [Vue Class Component Library](https://class-component.vuejs.org/)
Prebuilt UI components? [Ant Design](https://www.antdv.com/docs/vue/introduce/)
While some ecosystem tools might exist in Lit, they certainly dont have the same breadth.
Thats not to say its all good in the general web component ecosystem. Some frameworks, like React, [have issues with Web Component interop](https://custom-elements-everywhere.com/), that may impact your ability to merge those tools together.
# Why Web Components?
You may be asking - if youre going to use a framework like Vue or React anyway, why even bother with web components? Couldnt you instead write an app in one of those frameworks, without utilizing web components?
You absolutely can, and to be honest - this is how most apps that use these frameworks are built.
But web components play a special role in companies that have multiple different projects: Consolidation.
Lets say that you work for BigCorp - the biggest corporation in Corpville.
BigCorp has dozens and dozens of full-scale applications, and not all of them are using the same frontend framework. This might sound irresponsible of BigCorps system architects, but in reality, sometimes a framework is better geared towards specific applications. Additionally, maybe some of the apps were part of an acquisition or merger that brought them into the company.
After all, the user doesnt care (or often, know) about what framework a tool is built with. You know what a user does care about? The fact that each app in a collection all have vastly different UIs and buttons.
![Two different apps, each with different text cutoff points in their button's text](./two_apps.png)
While this is clearly a bug, if both codebases implement the buttons on their own, youll inevitably end up with these types of problems; this being on top of the work-hours your teams have to spend redoing one-anothers work for their respective frameworks.
And thats all ignoring how difficult it can be to get designers to have consistency between different projects design components - like buttons.
Web Components solve this problem.
If you build a shared component system that exports web components, you can then use the same codebase across multiple frameworks.
Once the code is written and exported into web components, its trivial to utilize these new web components in your application. Like, it can be a [single line of code trivial.](https://v3.vuejs.org/guide/web-components.html#tips-for-a-vue-custom-elements-library)
From this point, youre able to make sure the logic and styling of these components are made consistent between applications - even if different frameworks.
# Conclusion
While web components have had a long time in the oven, they came out swinging! And while Lit isnt the only one at the table, theyve certainly found a strong foothold in capabilities.
Lits lightweightness, paired with web components abilities to integrate between multiple frameworks is an incredible one-two punch that makes it a strong candidate for any shared component system.
Whats more, the ability to transfer knowledge from other frameworks makes it an easy tool to place in your toolbox for usage either now or in the future.
Regardless; whether youre using Vue, React, Angular, Lit, Vanilla Web Components, or anything else, we wish you happy engineering!

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,566 @@
---
{
title: "Web Components 101: Lit Framework",
description: "",
published: '2021-11-04T22:12:03.284Z',
authors: ['crutchcorn'],
tags: ['web components', 'lit'],
attached: [],
license: 'coderpad',
originalLink: 'https://coderpad.io/blog/web-components-101-lit-framework/',
series: "Web Components 101",
order: 2
}
---
Recently we talked about [what web components are and how you can build a web app utilizing them with only vanilla JavaScript](https://coderpad.io/blog/intro-to-web-components-vanilla-js/).
While web components are absolutely usable with only vanilla JavaScript, more complex usage, especially pertaining to value binding, can easily become unwieldy.
One potential solution might be using a web component framework such as VueJS or React. However, web-standard components can still be a massive boon to development.
As such, theres a framework called [“Lit”](https://lit.dev/) that is developed specifically to leverage web components. With [Lit 2.0 recently launching as a stable release](https://lit.dev/blog/2021-09-21-announcing-lit-2/), we thought wed take a look at how we can simplify web component development.
# HTML
One of the greatest strengths of custom elements is the ability to contain multiple other elements. This makes it so that you can have custom elements for every scale: from a button to an entire page.
To do this in a vanilla JavaScript custom element, you can use `innerHTML` to create new child elements.
```html
<script>
class MyComponent extends HTMLElement {
connectedCallback() {
this.render();
}
render() {
this.innerHTML = '<p>Hello!</p>';
}
}
customElements.define('hello-component', MyComponent);
</script>
<hello-component></hello-component>
```
This initial example looks fairly similar to what the Lit counterpart of that code looks like:
```html
<script type="module">
import { html, LitElement } from "https://cdn.skypack.dev/lit";
export class HelloElement extends LitElement {
render() {
return html`
<p>Hello!</p>
`;
}
}
window.customElements.define('hello-component', HelloElement);
</script>
<hello-component></hello-component>
```
<iframe src="https://app.coderpad.io/sandbox?question_id=194516" width="640" height="480" loading="lazy"></iframe>
There are two primary differences from the vanilla JavaScript example. First, we no longer need to use the `connectedCallback` to call `render`. The LitElements `render` function is called by Lit itself whenever needed - such as when data changes or for an initial render - avoiding the need to manually re-call the render method.
That said, Lit components fully support the same lifecycle methods as a vanilla custom elements.
The second, easier-to-miss change from the vanilla JavaScript component to the Lit implementation, is that when we set our HTML, we dont simply use a basic template literal (`<p>test</p>`): we pass the function `html` to the template literal (`html\`<p>test</p>\``).
This leverages [a somewhat infrequently used feature of template literals called tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). Tagged templates allow a template literal to be passed to a function. This function can then transform the output based on the string input and expected interpolated placeholders.
Because tagged templates return a value like any other function, you can assign the return value of `html` to a variable.
```javascript
render {
const el = html`
<p>Hello!</p>
`;
return el;
}
```
If you were to `console.log` this value, youd notice that its not an [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). Instead, its a custom value that Lit utilizes to render to proper DOM nodes.
# Event Binding
“If the syntax is so similar, why would I add a framework to build custom elements?”
Well, while the Vanilla JavaScript and Lit custom element code look similar for a small demo: The story changes dramatically when you look to scale up.
For example, if you wanted to render a button and add a click event to the button with vanilla JavaScript, youd have to abandon the `innerHTML` element assignment method.
First, well create an element using `document.createElement`, then add events, and finally utilize [an element method like `append`](https://developer.mozilla.org/en-US/docs/Web/API/Element/append) to add the node to the DOM.
```html
<script>
class MyComponent extends HTMLElement {
connectedCallback() {
this.render();
}
sayHello() {
alert("Hi there!");
}
render() {
const button = document.createElement('button');
button.innerText = "Say Hello!";
button.addEventListener('click', this.sayHello);
this.append(button);
}
}
window.customElements.define('hello-component', MyComponent);
</script>
<hello-component></hello-component>
```
While this works for the initial render, it doesnt handle any of the edgecases that,at scale,can cause long-term damage to your apps maintainability & performance.
For example, future re-renders of the element will duplicate the button. To solve this, you must iterate through all of the elements [`children`](https://developer.mozilla.org/en-US/docs/Web/API/Element/children) and [`remove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) them one-by-one.
Further, once the element is removed from the DOM, the click listener is not implicitly removed in the background. Because of this, its never released from memory and is considered a memory leak. If this issue continued to occur during long-term usage of your app, it would likely bloat memory usage and eventually crash or hang.
To solve this, youd need to assign a variable for every `addEventListener` you had present. This may be simple for one or two events, but add too many and it can be difficult to keep track.
And all of this ignores the maintenance standpoint: What does that code do at a glance?
It doesn't look anything like HTML and as a result, requires you to consistently context shift between writing standard HTML in a string and using the DOM APIs to construct elements.
Luckily, Lit doesnt have these issues. Heres the same button construction and rendering to a custom element using Lit instead of vanilla JavaScript:
```html
<script type="module">
import { html, LitElement } from "https://cdn.skypack.dev/lit";
export class HelloElement extends LitElement {
sayHello() {
alert("Hi there!");
}
render() {
return html`
<button @click=${this.sayHello}>Say Hello!</button>
`;
}
}
window.customElements.define('hello-component', HelloElement);
</script>
<hello-component></hello-component>
```
<iframe src="https://app.coderpad.io/sandbox?question_id=194518" width="640" height="480" loading="lazy"></iframe>
Yup, thats all. Lit allows you to bind elements by using the `@` sign and passing the function as a placeholder to the `html` tagged template. Not only does this look much HTML-like, it handles event cleanup, re-rendering, and more.
# Attributes & Properties
As we learned before, there are two ways to pass values between and into components: attributes and values.
Previously, when we were using vanilla JavaScript, we had to define these separately. Moreover, we had to declare which attributes to dynamically listen to value changes of.
```javascript
class MyComponent extends HTMLElement {
connectedCallback() {
this.render();
}
static get observedAttributes() {
return ['message'];
}
attributeChangedCallback(name, oldValue, newValue) {
this.render();
}
render() {
const message = this.attributes.message.value || 'Hello world';
this.innerHTML = `<h1>${message}</h1>`;
}
}
```
In Lit, we declare attributes and properties using a static getter and treat them as normal values in any of our functions.
```javascript
import { html, LitElement } from "https://cdn.skypack.dev/lit";
export class HelloElement extends LitElement {
static get properties() {
return {
message: {type: String},
};
}
constructor() {
super();
this.message = 'Hello world';
}
render() {
return html`
<h1>${this.message}</h1>
`;
}
}
window.customElements.define('hello-component', HelloElement);
```
For starters, we no longer have to manually call “render” when a propertys value is changed. Lit will re-render when values are changed.
Thats not all, though: Keen eyed readers will notice that were declaring a type associated with the `message` property.
Unlike the [React ecosystems PropTypes](https://github.com/facebook/prop-types), the `type` subproperty doesnt do runtime type validation. Instead, it acts as an automatic type converter.
This can be of great help as the knowledge that attributes can only be strings can be difficult to remember while debugging.
For example, we can tell Lit to convert an attribute to a Number and it will migrate from a string that looks like a number to an actual JavaScript type number.
```html
<script type="module">
import { html, LitElement } from "https://cdn.skypack.dev/lit";
export class HelloElement extends LitElement {
static get properties() {
return {
val: {type: Number},
};
}
render() {
return html`
<h1>${this.val} is typeof ${typeof this.val}</h1>
`;
}
}
window.customElements.define('hello-component', HelloElement);
</script>
<!-- This will show "123 is typeof number" -->
<hello-component val="123"></hello-component>
<!-- This will show "NaN is typeof number" -->
<hello-component val="Test"></hello-component>
```
<iframe src="https://app.coderpad.io/sandbox?question_id=194519" width="640" height="480" loading="lazy"></iframe>
## Attribute Reactivity
One of the biggest benefits of not having to call `render` manually is that Lit is able to render contents when they need to update.
For example, given this example, the contents will render properly to update with new values.
```javascript
import { html, LitElement } from "lit";
export class ChangeMessageElement extends LitElement {
static get properties() {
return {
message: {type: String},
};
}
changeSelectedMsg() {
const newMsg = msgs[Math.floor(Math.random() * msgs.length)];
this.message = newMsg;
}
constructor() {
super();
this.message = 'Hello world';
}
render() {
return html`
<button @click="${this.changeSelectedMsg}">Toggle</button>
<hello-component message=${this.message}></hello-component>
`;
}
}
```
<iframe src="https://app.coderpad.io/sandbox?question_id=181069" width="640" height="480" loading="lazy"></iframe>
# Reactive Data Binding
This reactivity comes with its own set of limitations. While numbers and strings are able to be set fairly trivially, objects (and by extension arrays) are a different story.
This is because, in order for Lit to know what properties to update in render, an object must have a different reference value from one to another. [This is just how React and other frameworks detect changes in state as well.](https://www.coletiv.com/blog/dangers-of-using-objects-in-useState-and-useEffect-ReactJS-hooks/)
```javascript
export class FormElement extends LitElement {
constructor() { /* ... */ }
static get properties() {
return {
todoList: {type: Array},
inputVal: {type: String},
};
}
_onSubmit(e) {
e.preventDefault(); /* This works, because were changing the object reference */
this.todoList = [...this.todoList, this.inputVal]; /* But this would not, because we arent */
// this.todoList.push(this.inputVal); this.inputVal = '';
}
_onChange(e) {
this.inputVal = e.target.value;
}
render() {
return html`
<form @submit="${this._onSubmit}">
<input .value="${this.inputVal}" @change="${this._onChange}" type="text" />
<button type="submit">Add</button>
</form>
<todo-component todos=${this.todoList}></todo-component>
`;
}
}
```
<iframe src="https://app.coderpad.io/sandbox?question_id=181090" width="640" height="480" loading="lazy"></iframe>
You may also notice that were binding both the users input and output to set and reflect the state. [This is exactly how other frameworks like React also expect you to manage user state](https://coderpad.io/blog/master-react-unidirectional-data-flow/).
# Prop Passing with Lits Dot Synax
HTML attributes are not the only way to pass data to a web component. Properties on the element class are a way to pass more than just a string to an element.
While the `type` field can help solve this problem as well, youre still limited by serializability, meaning that things like functions wont be able to be passed by attributes.
While properties are a more robust method of data passing to web components, theyre seldomly used in vanilla JavaScript due to their complexity in coding.
For example, this is a simple demonstration of passing an array.
```html
<html>
<head>
<!-- Render object array as "ul", passing fn to checkbox change event -->
<script>
class MyComponent extends HTMLElement {
property = [];
connectedCallback() {
this.render();
}
render() {
this.innerHTML = `<h1>${this.property.length}</h1>`;
}
}
customElements.define('my-component', MyComponent);
</script>
<script>
function changeElement() {
const compEl = document.querySelector('#mycomp');
compEl.property = [
'Testing',
'Second',
'Another'
];
compEl.render();
}
</script>
</head>
<body>
<my-component id="mycomp"></my-component>
<button onclick="changeElement()">Change to 3</button>
</body>
</html>
```
First, you have to get a reference to the element using an API like `querySelector`. This means you need to introduce a new reference to the component and make sure the IDs match in both parts of code.
Then, just as is the case with updating attribute values, we need to manually call the “render” function in order to update the UI.
But those complaints aside, theres still one more: It places your data and component tags in two different areas. Because of this, it can be more difficult to debug or figure out what data is being passed to what component.
Lit takes a different approach. Within a Lit `html` tagged template, add a period before an attribute binding and suddenly it will pass as a property instead.
```html
<script type="module">
import { html, LitElement } from "https://cdn.skypack.dev/lit";
class MyElement extends LitElement {
static get properties() {
return {
property: {type: Array},
};
}
render() {
return html`
<h1>${this.property.length}</h1>
`;
}
}
window.customElements.define('my-component', MyElement);
class ChangeMessageElement extends LitElement {
static get properties() {
return {
array: {type: Array},
};
}
constructor() {
super();
this.array = [];
}
changeElement() {
this.array = [
'Testing',
'Second',
'Another'
];
}
render() {
return html`
<!-- If "property" didn't have a period, it would pass as attribute -->
<my-component .property=${this.array}></my-component>
<button @click=${this.changeElement}>Change to 3</button>
`;
}
}
window.customElements.define('change-message-component', ChangeMessageElement);
</script>
<change-message-component></change-message-component>
```
<iframe src="https://app.coderpad.io/sandbox?question_id=194520" width="640" height="480" loading="lazy"></iframe>
This works because properties and attributes are both created at the same time with Lit.
However, due to the period binding not being HTML standard, it comes with the side effect of having to use a Lit template in order to bind properties. This tends not to be a problem in applications - since many tend to use and compose components throughout their applications.
# Array Rendering
In our article about vanilla JavaScript web components, we built a simple todo list. Lets take another look at that example, but this time using Lit for our component code. Well get started with a parent `FormElement`, which will manage the data and user input.
```javascript
class FormElement extends LitElement {
static get properties() {
return {
todoList: {type: Array},
inputVal: {type: String},
};
}
_onSubmit(e) {
e.preventDefault();
this.todoList = [...this.todoList, {name: this.inputVal, completed: false}];
this.inputVal = '';
}
// ...
render() {
return html`
<button @click=${this.toggleAll}>Toggle all</button>
<form @submit=${this._onSubmit}>
<input .value=${this.inputVal} @change=${this._onChange} type="text" />
<button type="submit">Add</button>
</form>
<!-- Notice the period in ".todos" -->
<todo-component .todos=${this.todoList}></todo-component>
`;
}
}
```
Now that we have a form that contains an array, an important question arises: how do we iterate through an array in order to create individual elements for a list?
Well, while [React has `Array.map](https://reactjs.org/docs/lists-and-keys.html)` and [Vue has `v-for`](https://v3.vuejs.org/guide/list.html#mapping-an-array-to-elements-with-v-for), Lit uses a `repeat` function. Heres an example:
```javascript
class TodoElement extends LitElement {
// ...
render() {
return html`
<ul>
${repeat(this.todos, (todo) => html`
<li>
<input type="checkbox" .checked=${todo.completed}/>
${todo.name}
</li>
`)}
</ul>
`;
}
}
```
<iframe src="https://app.coderpad.io/sandbox?question_id=181092" width="640" height="480" loading="lazy"></iframe>
# Passing Functions
Before we step away from code to talk pros and cons about Lit itself (shh, spoilers!); lets take a look at a code sample that demonstrates many of the benefits over vanilla JavaScript web components weve talked about today.
Readers of the previous blog post will remember that when passing an array of objects to a web component, things looked pretty decent.
It wasnt until we tried binding event listeners to an array of objects that things got complex (and messy). Between needing to manually create elements using `document`, dealing with `querySelector` to pass properties, manually calling “render”, and needing to implement a custom “clear” method - it was a messy experience.
Lets see how Lit handles the job.
```javascript
class TodoElement extends LitElement {
// ...
render() {
const headerText = this.todos
.filter(todo => todo.completed).length;
return html`
<h1>${headerText}</h1>
<ul>
${repeat(this.todos, (todo) => html`
<li>
<input type="checkbox" @change=${todo.onChange} .checked=${todo.completed}/>
${todo.name}
</li>
`)}
</ul>
`;
}
}
```
<iframe src="https://app.coderpad.io/sandbox?question_id=181093" width="640" height="480" loading="lazy"></iframe>
You will notice that were using a `filter` within our `render` method. Because this logic is within the `render` method, it will run on every UI update. This is important to note in case you have expensive operations: you should avoid running those within the render method.
Outside of this, however - thats all there is! It reads just like HTML would (with the added benefit of cleanup and prop passing), handles dynamic data, and more!
# Conclusion
The ability to leverage Lit in an application makes maintaining and improving a project easier than rolling web components yourself.
Lit demonstrates significant growth in web components from the early days of [Polymer](http://polymer-project.org/). This growth is in no small part due to the Lit team themselves, either!
Before it was a fully fledged framework, the project started from the `lit-html` package, which was an offshoot of Polymer. The Polymer team was instrumental in standardizing the modern variant of web components.
The ability to use Lit can strongly enhance web component development, but there are other options out there. Next time, well talk about what the competitors are doing, what the pros and cons of each are, and how you can make the best choice for your applications.