Trong các trang web có nhiều dữ liệu, thì chúng ta thường sử dụng load more để tải nội dung phần còn lại khi người dùng cuộn trang xuống dưới.
Trong bài viết này, chúng ta sẽ hướng dẫn cách sử dụng API của trình duyệt là IntersectionObserver
để xây dựng tính năng load more khi scroll.
Bước 1: Chuẩn bị dữ liệu
Để làm ví dụ này, chúng ta sẽ sử dụng một API đơn giản để lấy các bài viết và hiển thị chúng trên trang web. Đầu tiên, chúng ta cần chuẩn bị một số dữ liệu mẫu.
Trong ví dụ này, chúng ta sẽ tạo một đối tượng data
với các thuộc tính posts
, perPage
, và page
. posts
sẽ chứa tất cả các bài viết, perPage
sẽ cho biết số bài viết được hiển thị trên mỗi trang, và page
sẽ cho biết trang hiện tại.
const data = {
posts: [],
perPage: 5,
page: 1
};
Sau đó, chúng ta sẽ viết một hàm getPosts()
để lấy danh sách bài viết từ API và lưu trữ chúng trong data.posts
. Trong ví dụ này, chúng ta sử dụng thư viện axios
để gửi yêu cầu GET đến API và xử lý kết quả trả về.
import axios from 'axios';
async function getPosts(page, perPage) {
const response = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=${perPage}`);
return response.data;
}
Bước 2: Hiển thị danh sách bài viết ban đầu
Sau khi có dữ liệu mẫu, chúng ta sẽ hiển thị danh sách bài viết ban đầu trên trang web. Trong ví dụ này, chúng ta sẽ sử dụng một function component để hiển thị danh sách bài viết và chạy hàm getPosts()
trong useEffect()
để lấy danh sách bài viết ban đầu.
import { useState, useEffect } from 'react';
function App() {
const [posts, setPosts] = useState([]);
useEffect(() => {
async function fetchData() {
const data = await getPosts(1, data.perPage);
setPosts(data);
}
fetchData();
}, []);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
}
export default App;
Bước 3: Thêm nút Load more
Sau khi hiển thị danh sách bài viết ban đầu, chúng ta sẽ thêm một nút Load more vào phía dưới danh sách bài viết. Khi người dùng click vào nút này, chúng ta sẽ tải thêm các bài viết mới và thêm chúng vào danh sách hiện có.
import { useState, useEffect } from 'react';
function App() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
useEffect(() => {
async function fetchData() {
const data = await getPosts(1, data.perPage);
setPosts(data);
}
fetchData();
}, []);
async function loadMore() {
const nextPage = page + 1;
const data = await getPosts(nextPage, data.perPage);
setPosts([...posts, ...data]);
setPage(nextPage);
}
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
<button onClick={loadMore}>Load more</button>
</div>
);
}
export default App;
Bước 4: Tải thêm bài viết khi scroll
Bước cuối cùng là thêm tính năng tự động tải thêm bài viết khi người dùng cuộn trang xuống dưới. Trong ví dụ này, chúng ta sử dụng api IntersectionObserver
để kiểm tra khi nào người dùng đã cuộn đến cuối trang. Nếu người dùng đã cuộn đến cuối trang, chúng ta sẽ tải thêm bài viết mới và thêm chúng vào danh sách hiện có.
import { useState, useEffect, useRef } from 'react';
function App() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(0);
const [count, setCount] = useState(0);
const loaderRef = useRef(null);
async function handleObserver([entries]) {
if (entries.isIntersecting) {
setPage(p => p + 1);
}
}
useEffect(() => {
const observer = new IntersectionObserver(handleObserver, {
root: null,
rootMargin: '20px',
threshold: 1.0
});
if (loaderRef && loaderRef.current) {
observer.observe(loaderRef.current);
}
return () => observer.disconnect();
}, []);
useEffect(() => {
// get initial data
async function fetchData() {
const { data = [], total = 0 } = await getPosts(1, data.perPage);
setPosts(data);
setCount(total)
}
fetchData();
}, []);
useEffect(() => {
// load more data every page change > 1 and post length < total data
async function fetchData() {
const { data = [] } = await getPosts(page, data.perPage);
setPosts([...posts, ...data]);
}
if(page > 1 && posts.length < count) fetchData();
}, [page]);
return (
<div>
{posts.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
<div ref={loaderRef}></div>
</div>
);
}
export default App;
Ở đây, chúng ta sử dụng useRef()
để tạo một tham chiếu đến phần tử cuối cùng trong danh sách bài viết. Sau đó, chúng ta sử
dụng useEffect()
và IntersectionObserver
để theo dõi phần tử này. IntersectionObserver
sẽ gọi hàm handleObserver
của chúng ta mỗi khi phần tử này được hiển thị trên màn hình. Trong hàm này, chúng ta kiểm tra xem phần tử đã hiển thị trên màn hình hay chưa bằng cách sử dụng isIntersecting
của đối tượng entries
. Nếu phần tử đã hiển thị trên màn hình, chúng ta sẽ tải thêm bài viết mới và thêm chúng vào danh sách hiện có.
Với việc thêm tính năng tự động tải thêm bài viết khi cuộn trang xuống dưới, chúng ta đã hoàn thành một ví dụ đơn giản về cách sử dụng React để tải dữ liệu từ một API và hiển thị nó trên trang web của bạn.
IntersectionObserver
IntersectionObserver
là một API mới trong trình duyệt, cho phép chúng ta theo dõi khi một phần tử (element) xuất hiện hoặc biến mất khỏi vùng nhìn thấy của người dùng (viewport), thay vì theo dõi sự kiện cuộn trang (scroll) như cách truyền thống. API này rất hữu ích trong việc tạo ra các tính năng tải dữ liệu khi cuộn trang, phát hiện quảng cáo, hiển thị hình ảnh chỉ khi cần thiết, hoặc bất kỳ trường hợp nào liên quan đến việc tương tác của người dùng với các phần tử trên trang web.
IntersectionObserver
cho phép chúng ta quan sát các thay đổi trong vị trí của một phần tử, và gọi một hàm xử lý tương ứng mỗi khi phần tử xuất hiện hoặc biến mất khỏi vùng nhìn thấy của người dùng. API này rất hiệu quả và tiết kiệm tài nguyên hơn so với việc sử dụng các sự kiện cuộn trang truyền thống, bởi vì nó chỉ gọi hàm xử lý khi có sự thay đổi vị trí của phần tử, thay vì phải kiểm tra liên tục trong quá trình cuộn trang.
EventListener Scroll với IntersectionObserver
Ngoài việc sử dụng API của browser là IntersectionObserver, chúng ta cũng có thể sử dụng eventListener scroll để bắt sự kiện khi scroll xuống bottom. Dưới đây là một vài so sánh về cả 2 cách làm này.
-
Tính hiệu quả: Khi sử dụng
EventListener Scroll
, mỗi lần cuộn trang sẽ gọi hàm xử lý dữ liệu tương ứng. Điều này có thể làm cho ứng dụng của bạn chậm và lag, đặc biệt nếu dữ liệu được tải là rất lớn.IntersectionObserver
, bằng cách so sánh vị trí của phần tử và vùng nhìn thấy của người dùng, chỉ gọi hàm xử lý khi phần tử xuất hiện hoặc biến mất khỏi vùng nhìn thấy. Do đó, nó tiết kiệm tài nguyên hơn và giúp ứng dụng của bạn chạy nhanh hơn. -
Khả năng tùy chỉnh: Khi sử dụng
EventListener Scroll
, bạn cần phải kiểm tra vị trí của người dùng trong danh sách dữ liệu và tính toán để xác định khi nào cần tải thêm dữ liệu mới. Trong khi đó,IntersectionObserver
cho phép bạn quan sát trực tiếp vị trí của phần tử và vùng nhìn thấy của người dùng, giúp bạn dễ dàng xác định khi nào cần tải thêm dữ liệu. -
Hỗ trợ trình duyệt:
IntersectionObserver
được hỗ trợ rộng rãi trên các trình duyệt hiện đại, bao gồm cả Chrome, Firefox, Safari và Edge. Trong khi đó,EventListener Scroll
có thể không hoạt động tốt trên một số trình duyệt cũ hơn. -
Theo nhiều bài kiểm tra và đánh giá thực nghiệm,
IntersectionObserver
có hiệu suất cao hơn so vớiEventListener Scroll
khi xử lý việc tải thêm dữ liệu khi cuộn trang. Lý do làIntersectionObserver
sử dụng một cơ chế tối ưu hóa hơn để giảm thiểu các lần gọi hàm khi cuộn trang, và giảm thiểu tải nặng cho CPU.EventListener Scroll
hoạt động bằng cách gắn một sự kiện cuộn vào cửa sổ trình duyệt, và mỗi khi người dùng cuộn trang, sự kiện sẽ được kích hoạt và xử lý, có thể dẫn đến việc gọi nhiều lần hàm, đặc biệt là trong các ứng dụng phức tạp với nhiều thành phần trên trang.Trong khi đó,
IntersectionObserver
theo dõi sự tương tác giữa các phần tử HTML, tức là khi một phần tử xuất hiện trong viewport, sự kiện sẽ được kích hoạt. Như vậy,IntersectionObserver
chỉ kích hoạt sự kiện một lần khi một phần tử xuất hiện trong viewport, giúp giảm thiểu các lần gọi hàm và tối ưu hóa hiệu suất cho trang web.
Tính năng | EventListener Scroll | IntersectionObserver |
---|---|---|
Tính hiệu quả | Thấp | Cao |
Hiệu suất | Thấp | Cao |
Khả năng tùy chỉnh | Khó | Dễ |
Hỗ trợ trình duyệt | Hỗ trợ tốt trên các trình duyệt cũ | Hỗ trợ trên các trình duyệt hiện đại |