Developers need to optimize their apps for performance to ensure a satisfying user experience and retain their users. As Akamai’s research shows, speed matters; every extra second of loading time can cause a 7% decrease in conversions. In this article, we will explore the methods and reasons for optimizing app performance and when to apply them.
In this article, we’ll examine several methods for optimizing user-application and streamlining the development process for React developers.
Maintain Local Component State where Necessary
Using local component state can boost a React app's performance by making the Component independent and limiting the props and states passed down the component tree. This can reduce re-renders and enhance the app's performance.
Local component state is useful when a component has its own state that doesn't depend on the app's global state. The use case may vary, but a common example is a toggle button component that keeps track of its on or off state locally, instead of using props from a parent component.
When using local component state, it's important to use the setState method from React to update the state, as this will cause a re-render of the Component and its children. It's also important to control the state updates with proper validation and error handling, to keep the Component consistent and predictable.
Here is an example of maintaining a local component state in a React application using the useState
hook:
import React, { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
}
const handleDecrement = () => {
setCount(count - 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleIncrement}>Increment</button>
<button onClick={handleDecrement}>Decrement</button>
</div>
);
}
export default MyComponent;
This example uses the useState()
hook to create a piece
of local state called count. The initial value of the count is 0. Next,
the Component has two buttons, one to increment the count and one to
decrement the count. Finally, the handleIncrement
and handleDecrement
functions update the count by calling setCount
with the new value.
Here is an example of maintaining a local component state in a React application using the useState
hook and Class Component
import React, { Component } from "react";
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleIncrement = () => {
this.setState({ count: this.state.count + 1 });
};
handleDecrement = () => {
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div>
<p>Count:{this.state.count}</p>
<button onClick={this.handleIncrement}>Increment</button>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
export default MyComponent;
In this example, the Component has a constructor method that
initializes the Component’s state with a count property set to 0. The
Component has two methods, handleIncrement
and handleDecrement
, that update the count by calling setState
with the new value.
Maintaining a local component state can be a powerful tool for optimizing the performance of a React application. Therefore, it’s a good idea to use it when a component needs to maintain its internal state that is not directly related to the application’s global state. However, it’s essential to use it controlled and ensure that state updates are done correctly.
Making Use of Immutable Data Structures
Using immutable data structures in a React application can improve
performance by allowing the application to take advantage of reference
equality checks. When a component’s props or state are changed, React
checks if the new values are the same as the previous values by
comparing their references. React assumes that the Component does not
need to re-render
if the references are the same.
The reference will always be different when using mutable data structures, such as Javascript objects or arrays, even if the data is the same. This causes React to re-render the Component even though it doesn’t need to. By using immutable data structures, such as those provided by the immutable library, the reference will not change when the data is updated, allowing React to avoid unnecessary re-renders.
Using immutable data structures can also help debug and reason about the application’s state, as it is guaranteed that a particular state is not modified at a specific point in time, and the data is only added to a new one.
Another benefit of using immutable data structures is that they can make implementing features such as undo/redo or time-travel debugging easier. For example, with immutable data, you can keep a copy of the previous state and revert to it without worrying about the complexity of undoing all the individual mutations made to the data.
Using immutable data can also help improve your application’s performance when it comes to updating, as it does not require iterating and updating the whole object; it can only update the needed specific property.
However, it’s important to note that using immutable data structures does come with a performance cost. Creating new copies of data can be more expensive than mutating existing data structures. So it’s essential to weigh the benefits against the costs and decide whether or not to use immutable data structures based on the specific needs of your application.
Another essential thing to note is that you must change how you work with the data when using immutable data structures. Instead of modifying the data in place, you will have to create new copies of the data with the desired changes. This can add complexity to your code and make it harder to read and understand.
To mitigate this complexity, you can use libraries like immutable.js or immer.js which allows you to write code that looks like it is modifying the data in place but will create new copies of the data under the hood.
Here is an example of using an immutable data structure, specifically an immutable list implemented using the immutable.js
library, in a React application to improve performance:
import { List } from 'immutable';
Class MyComponent extends React.Component {
state = {
items: List([1, 2, 3])
}
handleAddItem = () => {
this.setState(prevState => ({
items: prevState.items.push(4)
}));
}
render() {
return (
<div>
<button onClick={this.handleAddItem}>Add Item</button>
<ul>
{this.state.items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
}
In this example, we’re using the List class from immutable.js to
create an immutable list of items stored in the Component’s state. When
the “Add Item” button is clicked, the Component’s handleAddItem
function is called.
Instead of modifying the existing state by directly pushing the new item to the list, the function creates a new state by concatenating the new item to the current list of items using the push method provided by the List class; this will prevent the Component from re-rendering the entire list, which can improve performance.
Using immutable data structures in a React application can improve performance by allowing the application to take advantage of reference equality checks and can also help with debugging and reasoning about the application’s state. However, it’s essential to weigh the benefits against the costs and decide whether or not to use immutable data structures based on the specific needs of your application.
React Components Should be Remembered to Avoid Needless Re-renders
There are several ways to optimize the performance of a React application, one of which is to avoid unnecessary re-renders of components. Re-rendering a component can be expensive in terms of performance, especially if the Component has many child components or performs complex calculations.
One way to avoid unnecessary re-renders is to use the shouldComponentUpdate
lifecycle method. This method lets you control when a component should
re-render by comparing the current and next props and state. If the
Component’s props and state have not changed, you can return false from
this method to prevent the Component from re-rendering.
Another way to avoid unnecessary re-renders is to use the React.memo
Higher-order component. This Component wraps your functional Component and only re-renders the Component if the props change.
Lastly, use the useEffect
hook with the proper dependency array to only re-render the Component when necessary.
Using these techniques, you can significantly improve the performance of your React application by avoiding unnecessary re-renders.
Files in Many Chunks
One way to optimize the performance of a React application is to split the files into many chunks, also known as code splitting. This technique allows the browser only to load the code needed for the current page rather than loading all of the code at once. This can improve the initial load time of the application, as well as the overall performance, by reducing the amount of JavaScript that needs to be parsed and executed.
An example of code splitting in a React application would be using the react-loadable, library to dynamically load components as they are needed.
Here is an example of how you might use react-loadable to code split a component:
import Loadable from react-loadable;
const MyComponent = Loadable({
loader: () => import('./MyComponent'),
loading: () => <div>Loading...</div>,
});
export default MyComponent;
In this example, MyComponent
will be loaded when needed,
at which point the loader function will be called, and the Component
will be imported. The loading component will be displayed while the
Component is being loaded.
Use “React.Fragments” to Reduce the Number of HTML Element Wrappers
React.Fragment
is a component that allows you to group a list of children without adding extra nodes to the DOM
. Using React.Fragment
can help reduce the number of HTML
element wrappers in your application and improve performance by reducing the amount of DOM
manipulation React has to do. This can be especially useful when you
have a component that needs to render multiple children elements, but
you want to leave the wrapper element out of them.
Using React.Fragment
instead of a wrapper element can
also make your code more readable and maintainable by making it clear
that the element is only being used for grouping purposes and not adding
any additional styling or functionality.
You can use React.Fragment
in two ways:
- Using
<React.Fragment>
<React.Fragment>
<Child1 />
<Child2 />
<Child3 />
</React.Fragment>
- Using the shorthand
<> </>
<>
<Child1 />
<Child2 />
<Child3 />
</>
It is important to note that React.Fragment
does not add any extra nodes to the DOM
and has no additional overhead. It is a simple component that is useful
for improving the readability and maintainability of your code.
React’s Windowing or List Virtualization
React’s windowing, or list virtualization, is a technique that improves the performance of large lists by only rendering the items currently visible on the screen rather than rendering the entire list at once. This is done by only rendering the items visible in the viewport and then updating the items as the user scrolls.
This approach can greatly improve the performance of a React application, especially when dealing with large lists of items, as it reduces the amount of work that the browser needs to do to render the list. It also saves memory and helps maintain a smooth scrolling experience for the user.
One popular library for implementing list virtualization in React is react-virtualized. This library provides a set of components that can quickly implement list virtualization in a React application. It also offers a collection of utilities for calculating the size and position of items in a list and handling scroll events.
Another library is react-window, a popular library for implementing windowing in React, providing a set of components for efficiently rendering large lists and tabular data.
When using list virtualization, it’s also essential to consider the overall architecture of the application and how it interacts with the virtualized list. For example, if the data for the list is being loaded from an external source, it’s essential to make sure that the data is being loaded in a way optimized for use with a virtualized list. This may include using pagination or loading data in chunks rather than all at once.
List virtualization is a technique that can significantly improve the performance of a React application when dealing with large lists of items. However, it can be implemented using libraries like react-virtualized and react-window, and it’s essential to consider the overall architecture of the application when implementing it.
React’s Use of Lazy Image Loading
Lazy loading images in a React application can improve performance by only loading images currently visible on the screen. This can reduce the page’s initial load time and the amount of data that needs to be transferred over the network. It improves your React application’s performance by reducing the amount of data that needs to be transferred over the network, which leads to a better user experience.
One way to implement lazy loading in a React application is to use the React lazy and Suspense components. These components allow you to dynamically load components and modules as they are needed rather than loading all components and modules upfront. This can be especially useful for images, as they are often large in file size and take a long time to load.
Another way to implement this technique is by using a library like react-lazyload
,
which allows you to easily lazy load images and other elements in your
React application. This can be useful if you want to avoid manually
implementing lazy loading or if you want to take advantage of additional
features like placeholder images and debouncing.
In addition to using React’s built-in lazy loading functionality or a library like react-lazyload
, there are a few other best practices that you can follow to optimize the performance of your React application’s images.
One of these best practices is compressing and optimizing your images
before using them in your application. This can reduce the file size of
the images and make them load faster. Many tools are available for
compressing and optimizing images, such as TinyPNG
or JPEGoptim
.
Another best practice is to use a Content Delivery Network
(CDN)
to serve your images. A CDN
can distribute your images across multiple servers, which can improve
the load time of your images by reducing the distance that the data
needs to travel to reach the user.
It is crucial to ensure that your images are correctly sized and scaled for the devices on which they will be displayed. Loading large images on small screens can slow down the performance of your application and negatively impact the user experience.
Conclusion
Optimizing a React app requires different optimization techniques because of various possible delays. However, we have covered some key points above, so you don't need a long list if you follow them strictly. Thank you for reading.
No comments:
Post a Comment