Tối ưu ứng dụng React bằng Code-Spliting


React hiện nay là thư viện rất được ưa chuộng bởi các lập trình viên khi xây
dựng những ứng dụng web dạng một trang (Single Page Application) nhờ việc dễ
học, dễ viết và nhiều công cụ hỗ trợ. Tuy nhiên khi ứng dụng của bạn ngày càng
trở nên lớn hơn thì các vấn đề cũng xuất hiện nhiều hơn, nhất là việc liên
quan tới tối ưu hiệu năng cho ứng dụng. Trong bài viết này, mình sẽ cùng các
bạn tìm hiểu về Code-Splitting – một kỹ thuật để giúp “giảm” dung lượng file
bundle nhằm cải thiện thời gian load website và nâng cao hiệu năng ứng dụng
của bạn.



Bundle JS Files là gì?



Trước tiên bạn cần hiểu khái niệm bundle file trong React là gì. Thông thường
khi tạo ra một ứng dụng thì chúng ta sẽ viết source code của mình vào nhiều
files khác nhau, trong đó có chứa nhiều các modules và thư viện bên thứ 3
(3rd-party libs). Khi tiến hành build ứng dụng của bạn, React sẽ thực hiện
việc chuyển đổi rất nhiều file source code mà bạn viết trở thành 1 file lớn
hơn để sử dụng nó đưa cho các trình duyệt web khi load ứng dụng. Những file đó
được gọi là bundle.





href="https://blogger.googleusercontent.com/img/a/AVvXsEhQh3qTuDtXNDuijDklQKv9PKOG3MCOg7qBjSigSGFi2vefJbEEIgsu9XMnJ0NOU011-hjS8LYQcsyqch0uK5go5YlZKeGznqOnXBPr-JHiV5e0wXEkoCLBsqf4hURCOYnaMm_6z4uF500_9HLRk0EU37dxFUX83ovCblNHTAudx627maYWWyx6324KZZw"
style="margin-left: 1em; margin-right: 1em;"
> alt="bundle file"
data-original-height="637"
data-original-width="650"
height="627"
src="https://blogger.googleusercontent.com/img/a/AVvXsEhQh3qTuDtXNDuijDklQKv9PKOG3MCOg7qBjSigSGFi2vefJbEEIgsu9XMnJ0NOU011-hjS8LYQcsyqch0uK5go5YlZKeGznqOnXBPr-JHiV5e0wXEkoCLBsqf4hURCOYnaMm_6z4uF500_9HLRk0EU37dxFUX83ovCblNHTAudx627maYWWyx6324KZZw=w640-h627"
title="bundle file"
width="640"
/>





Ban đầu ứng dụng  của bạn tạo ra những file bundle nhỏ, và việc trình
duyệt load chúng lên không thành vấn đề; sau một thời gian phát triển, với
việc import và sử dụng ngày càng nhiều các thư viện và module thì các files
bundle của bạn cũng ngày càng nặng thêm. Nếu không thực hiện tối ưu, kích
thước các file bundle có thể lên tới 40-50Mb là chuyện bình thường, điều đó
cũng đồng nghĩa với việc ứng dụng của bạn trở nên nặng nề khi load, user sẽ
cần chờ 1 khoảng thời gian khá lâu để có thể tương tác được với các phần tử
trên màn hình.



Vậy cách giải quyết cho vấn đề này là gì?



Code-Splitting là gì?



Rõ ràng để load một ứng dụng hay một màn hình cụ thể, ứng dụng của bạn không
cần phải “nạp” hết các module hay thư viện được import vào; vì thế để giải
quyết cho vấn đề bundle file size lớn, chúng ta cần một kỹ thuật để tách nó ra
thành 2 phần: phần cần thiết load để khởi động ứng dụng (hay màn hình) và phần
có thể load nạp vào sau khi ứng dụng đã được chạy. Và khi cần cần thiết load
để có thể khởi động ứng dụng càng nhỏ, thì thời gian tải ứng dụng của chúng ta
càng nhanh hơn. React đã cung cấp cho chúng ta tính năng này và gọi nó là
Code-Splitting.





href="https://blogger.googleusercontent.com/img/a/AVvXsEhL9EMCqtqMkRUkREDxwlIE-YA9wfvxmSnBeZuc1Y2afALp03VbLNfF79L7mxMFFKV0YCd0fqYUW3nFtg_DaWUhtfmC3lCaF4V0zduEpwWkURR00jBV73eQzKFafgK8t7Gst-fw4BRGeP33EqQ5zeZGk2F_ForYrURu4XqarHdP_XxeMcP74KkglpC4R9A"
style="margin-left: 1em; margin-right: 1em;"
> alt="Code-Splitting là gì"
data-original-height="359"
data-original-width="638"
height="360"
src="https://blogger.googleusercontent.com/img/a/AVvXsEhL9EMCqtqMkRUkREDxwlIE-YA9wfvxmSnBeZuc1Y2afALp03VbLNfF79L7mxMFFKV0YCd0fqYUW3nFtg_DaWUhtfmC3lCaF4V0zduEpwWkURR00jBV73eQzKFafgK8t7Gst-fw4BRGeP33EqQ5zeZGk2F_ForYrURu4XqarHdP_XxeMcP74KkglpC4R9A=w640-h360"
title="Code-Splitting là gì"
width="640"
/>





Có 3 kỹ thuật xử lý trong Code-Splitting thường được sử dụng, chúng ta cùng
lần lượt tìm hiểu và xem cách triển khai của chúng nhé.



Dynamic Import



Thông thường khi chúng ta cần import 1 module nào để sử dụng, câu lệnh import
sẽ được thực hiện như dưới đây:




style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"
>
style="line-height: 125%; margin: 0px;"
>import { add } from './math';

console.log(add(16, 26));






Đoạn khai báo trên sẽ import module math một cách đồng bộ – tức là sẽ import
vào luôn file bundle từ khởi tạo. Với Dynamic Import, chúng ta sẽ xử lý lại
đoạn code trên để chỉ import khi ứng dụng cần gọi đến phương thức add của
module math. Code sẽ được viết lại như sau:




style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"
>
style="line-height: 125%; margin: 0px;"
>import("./math").then(math => {
console.log(math.add(16, 26));
});






Phương thức dynamic import giúp việc import module, file một cách bất đồng bộ
bằng việc trả về 1 Promise. Nó hoạt động được cả ở server-side và client-side
giúp bạn có thể sử dụng trong các trường hợp load file, assets hay những
module, 3rd-party libs không cần thiết cho việc hiển thị ứng dụng, màn hình
lần đầu tiên.



Sử dụng React.lazy và Suspense



Phương thức React.lazy giúp bạn tạo ra 1 component ở dạng lazy-loading, nghĩa
là sẽ chỉ tạo ra component đó khi nó thực sự được gọi đến và cần hiển thị ra.
Hãy xem ví dụ dưới đây:




style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"
>
style="line-height: 125%; margin: 0px;"
>import React, { useState } from 'react';
import ProjectIntro from './projectIntro';
import ProjectDetails from './projectDetails';

export default function App() {
const [showDetails, setShowDetails] = useState(false);

return (
<>
<h1>Project List</h1>
<ProjectIntro />
<button onClick={() => setShowDetails(true)}>Show Details</button>
{showDetails ? <ProjectDetails /> : null }
</>
);
};






Ở đoạn code trên, component ProjectDetails mặc định sẽ không hiển thị, tuy
nhiên nó vẫn sẽ được load vào trong bundle file vì đã được import ngay trên
đầu. React.lazy giúp bạn dynamic import 1 component, kết hợp với Suspense bọc
bên ngoài cho phép chúng ta thêm xử lý hiệu loading component đó mà không làm
tăng đáng kể bundle file size. Source code cho việc triển khai React.lazy và
Suspense như sau:




style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"
>
style="line-height: 125%; margin: 0px;"
>import React, { useState, Suspense } from 'react';
import ProjectIntro from './projectIntro';

const ProjectDetails = React.lazy(() => import("./projectDetails"));

export default function App() {
const [showDetails, setShowDetails] = useState(false);

return (
<>
<h1>Project List</h1>
<ProjectIntro />
<button onClick={() => setShowDetails(true)}>Show Details</button>
<Suspense fallback={<div>Loading...</div>}>
{showDetails ? <ProjectDetails /> : null }
</Suspense>
</>
);
};






Có một lưu ý ở đây là React.lazy sẽ không thực hiện được khi render app ở
server-side. Các bạn có thể tham khảo cách sử dụng 1 thư viện lazy load khác
dành cho React như Loadable-components.



Route-based code splitting 



Ở hai kỹ thuật trên, chúng ta đã tìm cách giảm kích thước bundle file bằng
cách giảm những thành phần cần thiết để load ứng dụng trong 1 component. Với
phạm vi một ứng dụng, chúng ta có chứa nhiều màn hình, tại mỗi thời điểm sử
dụng thì người dùng sẽ chỉ tương tác với một hoặc một vài màn hình nhất định.
Vì thế việc code splitting hoàn toàn cần thiết để thực hiện trên phạm vi ứng
dụng.




Khi 1 ứng dụng React chạy, tương ứng với mỗi route sẽ có 1 component đảm nhiệm
việc hiển thị và tương tác với người dùng, điều đó cũng có nghĩa các component
khác không cần thiết phải được load lên ngay lúc đó. Chúng ta vẫn sẽ sử dụng
lazy load cho trường hợp này, code thực hiện sẽ như sau:




style="background: rgb(248, 248, 248); border-color: gray; border-image: initial; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"
>
style="line-height: 125%; margin: 0px;"
>import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from "./home";

const Profile = lazy(() => import('./profile'));
const ContactUs = lazy(() => import('./contact'));

const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
<Route path="/contact" element={<ContactUs />} />
</Routes>
</Suspense>
</Router>
);






Ở đoạn code trên, 2 components Profile và ContactUs được xử lý lazy loading,
chúng sẽ chỉ được load khi user đi đến route tương ứng. Điều đó giúp cho ứng
dụng của bạn không cần phải chờ load hết tất cả các component trong Router,
giảm kích thước bundle file ban đầu.



Lời kết



Như vậy chúng ta đã đi qua được các kỹ thuật Code-Splitting giúp tối ưu ứng
dụng React thông qua việc giảm bundle file size. Trong thực tế các project
React hiện nay, việc sử dụng lazy load là hết sức cần thiết khi có quá nhiều
component, các module, thư viện được sử dụng trên cùng 1 màn hình. Hãy cố gắng
tối ưu source code của bạn nhất có thể ngay từ ban đầu để tránh phải giải
quyết các vấn đề về hiệu năng cho việc mở rộng sau này. Hy vọng bài viết cung
cấp cho bạn kiến thức hữu ích cho các dự án React sắp tới, hẹn gặp lại mọi
người trong các biết tiếp theo của mình.



Bài viết liên quan:





  • href="https://www.hijublog.com/2023/08/top-10-git-repos-danh-cho-web-developer.html"
    target="_blank"
    >Top 10 Git Repositories mà Web Developer nào cũng nên biết
    >


  • href="https://www.hijublog.com/2023/08/mot-so-custom-hook-hay-su-dung-trong-react.html"
    target="_blank"
    >Một số custom hooks hay sử dụng cho React
    >


  • href="https://www.hijublog.com/2023/08/cach-su-dung-usememo-va-use-callback.html"
    target="_blank"
    >Khi nào nên sử dụng useMemo và useCallback trong React? 
    >



Post a Comment

Previous Post Next Post