Selecting a frontend framework can be a difficult decision for a developer because there are so many options. React is one of the most popular choices. It is well established and has an 84% satisfaction rating as of the 2021 State of JS Survey. Still, there are several other frameworks with interesting features and functionality that are worth investigating.
When selecting a frontend framework for your next project, consider the following questions:
- Does this framework have the features I require?
- How fast is this framework compared to others?
- How easy is this framework to learn and use?
- What size community uses this framework?
One alternative to React is Lit, which has a 77% satisfaction rating as of the 2021 State of JS Survey. Lit is easy to learn and use and its small footprint translates to fast loading times.
In this tutorial, we’ll compare React and Lit. We’ll also create a sample project in Lit.
Jump ahead:
- What’s new in Lit?
- Lit vs. React
- JSX and templating
- Components and props
- State and lifecycle methods
- Hooks
- Refs
- Creating a basic to-do project in Lit
- Should I switch from React to Lit?
Let’s get started!
What’s new in Lit?
Lit has several features that distinguish it from other frontend frameworks:
- LitElement base class is the convenient and versatile extension of the native HTMLElement. This class can be extended to define our components
- Expressive and declarative templates make it easy to define how a component should be rendered
- Reactive properties are the internal state of Lit’s components. Components automatically re-render when a reactive property changes
- Scoped styles help keep our CSS selectors simple, ensuring our component styles do not affect other contexts
- Supports Vanilla Javascript, TypeScript, and ergonomics (decorators and type declarations)
Lit vs. React
Lit’s core concepts and features are similar to those of React in many ways, but there are some significant differences. For example, React has been around since 2013, and is far more popular than Lit. At the time of this writing, React has around 15.9 million weekly downloads on npm compared to 127k weekly downloads on npm for Lit.
However, Lit is faster than React and also takes up less memory. A public benchmark comparison showed lit-html to be 8-10 percent faster than React’s VDOM. Lit has a minified memory size of 5kB, compared to 40kB for React.
These two frameworks offer other cool features, as well. Let’s see how they compare.
JSX and templating
JSX is a syntax extension to JavaScript that functions similarly to a templating language, but with the full power of JavaScript. React users can use JSX to easily write templates in JavaScript code. Lit templates serve a similar purpose, but express a component UI as a function of their state.
Here’s an example of JSX templating in React:
import 'react'; import ReactDOM from 'react-dom'; const name = 'World'; const el = ( <> <h1>Hello, {name}</h1> <div>How are you? </div> </> ); ReactDOM.render( el, mountNode );
Here’s an example of templating in Lit:
import {html, render} from 'lit'; const name = 'World'; const el = html` <h1>Hello, ${name}</h1> <div>How are you?</div>`; render( el, mountNode );
As we can see in the above examples, Lit does not need a React fragment to group multiple elements in its templates. instead, Lit templates are wrapped with an HTML tagged template literal.
Components and props
Components are self-contained, reusable pieces of code. They perform the same action as JavaScript functions, but they work independently and return HTML. React components are classified into two types: class components and functional components.
Class components
The Lit equivalent of a React class component is called LitElement.
Here’s an example of a class-based component in React:
import React from 'react'; import ReactDOM from 'react-dom'; class Welcome extends React.Component { constructor(props) { super(props); this.state = {name: ''}; } render() { return <h1>Hello, {this.props.name}</h1>; } } const el = <Welcome name="World"/> ReactDOM.render( el, mountNode );
Here’s the same example in Lit, using LitElement
:
import {LitElement, html} from 'lit'; class WelcomeBanner extends LitElement { static get properties() { return { name: {type: String} } } constructor() { super(); this.name = ''; } render() { return html`<h1>Hello, ${this.name}</h1>` } } customElements.define('welcome-banner', WelcomeBanner);
After defining and rendering the template for the LitElement component, we add the following to our HTML file:
<!-- index.html --> <head> <script type="module" src="./index.js"></script> </head> <body> <welcome-banner name="World"></welcome-banner> </body>
Now, let’s look at how functional components are created in these frameworks.
Functional components
Lit does not use JSX, so there’s no one-to-one correlation to a React functional component. However, it is simpler to write a function that takes in properties and then renders DOM based on those properties.
Here’s an example of a functional component in React:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; } const el = <Welcome name="World"/> ReactDOM.render( el, mountNode );
Here’s the same example in Lit:
import {html, render} from 'lit'; function Welcome(props) { return html`<h1>Hello, ${props.name}</h1>`; } render( Welcome({name: 'World}), document.body.querySelector('#root') );
State and lifecycle methods
state
is a React object that contains data or information about the component. The state
of a component can change over time. Whenever its state
changes, the component re-renders.
Lit’s reactive properties is a mix of React’s state
and props
. When changed, reactive properties can trigger the component lifecycle, re-rendering the component and optionally being read or written to attributes. Reactive properties come in two variants:
- Public reactive properties
- Internal reactive state
Reactive properties are implemented in React, like so:
import React from 'react'; class MyEl extends React.Component { constructor(props) { super(props) this.state = {name: 'there'} } componentWillReceiveProps(nextProps) { if (this.props.name !== nextProps.name) { this.setState({name: nextProps.name}) } } }
Reactive proeprtiers are implemented in Lit, like so:
import {LitElement} from 'lit'; import {property} from 'lit/decorators.js'; class MyEl extends LitElement { @property() name = 'there'; }
Internal reactive state refers to reactive properties that are not exposed to the component’s public API. These state properties lack corresponding attributes and are not intended to be used outside of the component. The internal reactive state of the component should be determined by the component itself.
React and Lit have a similar lifecycle, with some small but notable differences. Let’s take a closer look at some of the methods that these frameworks have in common.
constructor
The constructor
method is available in both React and Lit. It is automatically called when an object is created from a class
.
Here’s an example of the constructor
method in React:
import React from 'react'; import Chart from 'chart.js'; class MyEl extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; this._privateProp = 'private'; }
Here’s an example of the constructor
method in Lit:
class MyEl extends LitElement { static get properties() { return { counter: {type: Number} } } constructor() { this.counter = 0; this._privateProp = 'private'; }
render
The render
method is available in both React and Lit. It displays the code inside the specified element.
Here’s an example of the render
method in React:
render() { return <div>Hello World</div> }
Here’s an example of the render
method in Lit:
render() { return html`<div>Hello World</div>`; }
componentDidMount
vs. firstUpdated
and connectedCallback
The componentDidMount
function in React is similar to a combination of Lit’s firstUpdated
and connectedCallback
lifecycle callbacks. This function is invoked after a component is mounted.
Here’s an example of the componentDidMount
method in React:
componentDidMount() { this._chart = new Chart(this.chartElRef.current, {...}); } componentDidMount() { this.window.addEventListener('resize', this.boundOnResize); }
Here’s an example of the firstUpdated
and connectedCallback
lifecycle callbacks in Lit:
firstUpdated() { this._chart = new Chart(this.chartEl, {...}); } connectedCallback() { super.connectedCallback(); this.window.addEventListener('resize', this.boundOnResize); }
componentDidUpdate
vs. updated
The componentDidUpdate
function in React is equivalent to updated
in Lit. It is invoked after a change to the component’s props or state.
Here’s an example of the componentDidUpdate
method in React:
componentDidUpdate(prevProps) { if (this.props.title !== prevProps.title) { this._chart.setTitle(this.props.title); } }
Here’s an example of the updated
method in Lit:
updated(prevProps: PropertyValues<this>) { if (prevProps.has('title')) { this._chart.setTitle(this.title); } }
componentWillUnmount
vs.disconnectedCallback
The componentWillUnmount
function in React is equivalent to disconnectedCallback
in Lit. This function is invoked after a component is destroyed or is unmounted.
Here’s an example of the componentWillUnmount
method in React:
componentWillUnmount() { this.window.removeEventListener('resize', this.boundOnResize); } }
Here’s an example of the disconnectedCallback
method in Lit:
disconnectedCallback() { super.disconnectedCallback(); this.window.removeEventListener('resize', this.boundOnResize); } }
Hooks
Hooks are functions that allow React functional components to “hook into” React state and lifecycle features. Hooks do not work within classes, but they allow us to use React without classes.
Unlike React, Lit does not offer a way to create custom elements from a function, but LitElement does address most of the main issues with React class components by:
- Not taking arguments in the constructor
- Auto-binding all @event bindings (generally, to the custom element’s reference)
- Instantiating class properties as class members
Here’s an example of Hooks in React (at the time of making hooks):
import React from 'react'; import ReactDOM from 'react-dom'; class MyEl extends React.Component { constructor(props) { super(props); // Leaky implementation this.state = {count: 0}; this._chart = null; // Deemed messy } render() { return ( <> <div>Num times clicked {count}</div> <button onClick={this.clickCallback}>click me</button> </> ); } clickCallback() { // Errors because `this` no longer refers to the component this.setState({count: this.count + 1}); } }
Here’s the same example, using LitElement:
class MyEl extends LitElement { @property({type: Number}) count = 0; // No need for constructor to set state private _chart = null; // Public class fields introduced to JS in 2019 render() { return html` <div>Num times clicked ${count}</div> <button @click=${this.clickCallback}>click me</button>`; } private clickCallback() { // No error because `this` refers to component this.count++; } }
Refs
Refs are React functions that allow us to access the DOM element and any React elements that we’ve created. They are used when we want to change the value of a child component without using props.
In Lit, refs are created using the @query
and @queryAll
decorators. These decorators are nearly equivalent to querySelector
and querySelectorAll
, respectively, and render directly to the DOM.
Here’s an example of the refs function in React:
const RefsExample = (props) => { const inputRef = React.useRef(null); const onButtonClick = React.useCallback(() => { inputRef.current?.focus(); }, [inputRef]); return ( <div> <input type={"text"} ref={inputRef} /> <br /> <button onClick={onButtonClick}> Click to focus on the input above! </button> </div> ); };
Here’s the same example in Lit using the @query
decorator:
@customElement("my-element") export class MyElement extends LitElement { @query('input') // Define the query inputEl!: HTMLInputElement; // Declare the prop // Declare the click event listener onButtonClick() { // Use the query to focus this.inputEl.focus(); } render() { return html` <input type="text"> <br /> <!-- Bind the click listener --> <button @click=${this.onButtonClick}> Click to focus on the input above! </button> `; } }
Creating a basic to-do project in Lit
Let’s take a look at Lit in action by creating a sample to-do project.
To get started, run the command to clone the Lit starter JavaScript project:
git clone https://github.com/lit/lit-element-starter-js.git
Then, cd to the project folder and install the required packages using this command:
npm install
When the installation is complete, proceed to the lit-element-starter-js/my-element.js
file. Delete the boilerplates codes and create a Todo
component with the following code snippet:
import {LitElement, html, css} from 'lit'; class Todo extends LitElement { constructor() { super(); } render() { return html` <div class="todos-wrapper"> <h4>My Todos List</h4> <input placeholder="Add task..."/> <button>Add</button> <div class="list"> #Todo List </div> </div> `; } } customElements.define('my-element', Todo);
The above code creates a Todo
component with a constructor
method, where all reactive properties of the application will be defined, and a render
method, which renders JSX containing an input field and button.
Next, let’s define the properties of the application. Since this is a to-do application, we’ll need a TodosList
to store the tasks and an input
property to get user input.
Now, we’ll add the below code snippet to the Todos
class:
static properties = { TodosList: {type: Array}, input: {type: String}, };
Then, we’ll use the below code to assign initial values to the TodosList
and input
properties in the constructor
method:
this.TodosList = []; this.input = null;
Next, we’ll create a method to add and update a to-do task:
setInput(event) { this.input = event.target.value; } addTodo() { this.TodosList.push({ name: this.input, id: this.TodosList.length + 1, completed: false, }); this.requestUpdate(); } updateTodo(todo) { todo.completed = !todo.completed; this.requestUpdate(); }
We can see in the above code that the requestUpdate()
function was called in the addTodo
and updateTodo
methods after modifying the state. These methods were mutating the TodosList
property, so we called the requestUpdate()
function to update the component state.
Next, we’ll modify the render
method, to add event listeners to the methods created above and to display the to-do tasks.
render() { return html` <div class="todos-wrapper"> <h4>My Todos List</h4> <input placeholder="Add task..." @input=${this.setInput} /> <button @click=${this.addTodo}>Add</button> <div class="list"> ${this.TodosList.map( (todo) => html` <li @click=${() => this.updateTodo(todo)} class=${todo.completed && 'completed'} > ${todo.name} </li> ` )} </div> </div> `; }
Finally, let’s add some styling to make the application look more appealing:
static styles = css` .todos-wrapper { width: 35%; margin: 0px auto; background-color: rgb(236, 239, 241); padding: 20px; } .list { margin-top: 9px; } .list li { background-color: white; list-style: none; padding: 6px; margin-top: 3px; } .completed { text-decoration-line: line-through; color: #777; } input { padding: 5px; width: 70%; } button { padding: 5px; } `;
Now, let’s run the application:
npm run serve
Here’s our sample to-do project!
Should I switch from React to Lit?
Every framework has unique strengths and weaknesses. React powers the web applications of many large companies, such as Facebook, Twitter, and Airbnb. It also has an extensive community of developers and contributors.
If you are currently using React and are happy with that choice, then I see no reason for you to switch. However, if you are working on a project that requires really fast performance, then you might consider using Lit.
To learn more about Lit, see its official documentation.
The post Lit vs. React: A comparison guide appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/xtFk7X9
via Read more