Làm chủ useTransition trong React: Xây dựng một tìm kiếm hiệu suất cao cho trường hợp có 50k dữ liệu

0 0 0

Người đăng: Thái Thịnh

Theo Viblo Asia

Khi xây dựng các ứng dụng web hiện đại, đảm bảo sự tương tác mượt mà cho người dùng là điều cực kỳ quan trọng. React, với khả năng cập nhật giao diện khai báo mạnh mẽ và kiến trúc dựa trên thành phần, mang đến sự linh hoạt tuyệt vời.

Tuy nhiên, khi ứng dụng phát triển phức tạp, hiệu suất có thể giảm sút, đặc biệt khi xử lý các bộ dữ liệu lớn hoặc tương tác người dùng nặng.

Bài viết này sẽ làm nổi bật những thử thách mà chúng tôi gặp phải khi triển khai bộ lọc tìm kiếm đơn giản trong React và cách chúng tôi khắc phục các vấn đề hiệu suất bằng cách tối ưu hóa thành phần.

Vấn Đề: Tương tác người dùng với dữ liệu lớn

Hãy tưởng tượng một ứng dụng hiển thị một danh sách lớn người dùng, và người dùng có thể lọc danh sách này bằng cách gõ vào ô tìm kiếm. Nhìn qua, đây có vẻ là một tác vụ đơn giản, nhưng khi số lượng người dùng tăng lên, ngay cả những sự thiếu sót nhỏ cũng có thể gây ra vấn đề hiệu suất nghiêm trọng.

Ban đầu, tôi xây dựng một thành phần bộ lọc tìm kiếm cho phép người dùng lọc danh sách 5.000 người dùng theo tên hoặc email. Tương tác bao gồm việc gõ vào trường nhập liệu và lọc động danh sách khi người dùng gõ. Tuy nhiên, hiệu suất nhanh chóng trở thành vấn đề.

Cách tiếp cận ban đầu

Đây là phiên bản đầu tiên của thành phần của tôi:

import React, { useState, useTransition } from "react"; // 🔧 Fake user generator
const generateUsers = (count: number) => { const names = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Helen"]; return Array.from({ length: count }, (_, i) => ({ id: i, name: `${names[i % names.length]} ${i}`, email: `user${i}@example.com`, }));
}; const usersData = generateUsers(5000); export default function MassiveSearchFilter() { const [query, setQuery] = useState(""); const [filteredUsers, setFilteredUsers] = useState(usersData); const [isPending, startTransition] = useTransition(); const handleSearch = (e: { target: { value: any; }; }) => { const value = e.target.value; setQuery(value); startTransition(() => { const lower = value.toLowerCase(); const filtered = usersData.filter( (user) => user.name.toLowerCase().includes(lower) || user.email.toLowerCase().includes(lower) ); setFilteredUsers(filtered); }); }; const highlight = (text: string, query: string) => { if (!query) return text; const index = text.toLowerCase().indexOf(query.toLowerCase()); if (index === -1) return text; return ( <> {text.slice(0, index)} <mark className="bg-pink-200 text-black"> {text.slice(index, index + query.length)} </mark> {text.slice(index + query.length)} </> ); }; return ( <div className="min-h-screen bg-gray-100 p-6 font-sans"> <div className="max-w-3xl mx-auto bg-white p-6 rounded-2xl border-2 border-pink-200 shadow-md"> <h1 className="text-2xl font-bold text-pink-600 mb-4"> 👩‍💻 Massive Search Filter (5,000 Users) </h1> <input type="text" value={query} onChange={handleSearch} placeholder="Search name or email..." className="w-full p-3 mb-4 border-2 border-pink-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-400" /> {isPending && ( <div className="text-pink-500 italic mb-2 text-sm animate-pulse"> 🔄 Loading results... </div> )} <div className="max-h-[500px] overflow-y-auto divide-y divide-pink-100 border-t border-pink-100"> {filteredUsers.map((user) => ( <div key={user.id} className="p-3 hover:bg-pink-50 transition-colors duration-150" > <p className="font-medium text-gray-800 text-base"> {highlight(user.name, query)} </p> <p className="text-sm text-gray-500"> {highlight(user.email, query)} </p> </div> ))} </div> </div> </div> );
}

Những thử thách đã gặp phải

Lúc đầu, mọi thứ có vẻ ổn. Tìm kiếm nhanh và kết quả xuất hiện gần như ngay lập tức. Tuy nhiên, khi tôi bắt đầu thử nghiệm với các bộ dữ liệu lớn hơn (ví dụ, hơn 5.000 người dùng), các vấn đề sau đây nhanh chóng xuất hiện:

1. Tắc nghẽn hiệu suất

  • Thành phần sẽ chậm đi đáng kể khi người dùng gõ nhanh trong ô tìm kiếm.
  • React sẽ vẽ lại toàn bộ danh sách 5.000 người dùng mỗi khi người dùng gõ, gây ra độ trễ trong việc cập nhật giao diện.

2. Đóng băng giao diện người dùng

Vì bộ lọc tìm kiếm liên kết trực tiếp với trường nhập liệu và tương tác người dùng, việc gõ hoặc xóa văn bản nhanh chóng khiến giao diện người dùng bị đóng băng, vì React bị quá tải với việc lọc và vẽ lại hàng ngàn node DOM.

3. Quá phụ thuộc vào việc lọc

Mỗi thay đổi trong ô tìm kiếm đều kích hoạt một lần lọc lại toàn bộ danh sách, ngay cả khi người dùng vẫn đang gõ, điều này rất không hiệu quả.

Quy trình tối ưu hóa

Sau khi xác định các vấn đề, tôi đã áp dụng một số kỹ thuật để cải thiện hiệu suất và trải nghiệm người dùng:

Bước 1: Sử dụng react-window để ảo hóa

Việc vẽ 5.000+ mục là một ví dụ điển hình cho ảo hóa. Tôi quyết định sử dụng react-window, cho phép React chỉ vẽ những mục đang hiển thị trong cửa sổ xem. Điều này có nghĩa là ngay cả với bộ dữ liệu lớn, tôi không cần vẽ toàn bộ danh sách, giảm đáng kể tải cho trình duyệt.

Bước 2: Ghi nhớ kết quả lọc

Bằng cách sử dụng hook useMemo, tôi ghi nhớ kết quả lọc dựa trên truy vấn. Điều này đảm bảo rằng việc tính toán lọc chỉ xảy ra khi truy vấn thay đổi, thay vì mỗi lần vẽ lại.

Bước 3: Triển khai startTransition để cập nhật giao diện mượt mà

Hook useTransition cho phép tôi ưu tiên cập nhật trường nhập liệu trong khi hoãn việc tính toán lọc. Điều này đảm bảo rằng giao diện người dùng vẫn mượt mà trong khi ứng dụng thực hiện tác vụ tốn kém hơn là lọc dữ liệu.

Thành phần được tối ưu hóa

Sau đây là phiên bản tối ưu của thành phần sau những thay đổi này:

import React, { useState, useMemo, useTransition } from "react";
import { FixedSizeList as List } from "react-window"; const generateUsers = (count: number) => { const names = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Helen"]; return Array.from({ length: count }, (_, i) => ({ id: i, name: `${names[i % names.length]} ${i}`, email: `user${i}@example.com`, }));
}; const usersData = generateUsers(5000); export default function MassiveSearchFilter() { const [query, setQuery] = useState(""); const [isPending, startTransition] = useTransition(); const filteredUsers = useMemo(() => { const lower = query.toLowerCase(); return usersData.filter( (user) => user.name.toLowerCase().includes(lower) || user.email.toLowerCase().includes(lower) ); }, [query]); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; startTransition(() => setQuery(value)); }; const highlight = (text: string, query: string) => { if (!query) return text; const index = text.toLowerCase().indexOf(query.toLowerCase()); if (index === -1) return text; return ( <> {text.slice(0, index)} <mark className="bg-pink-200 text-black"> {text.slice(index, index + query.length)} </mark> {text.slice(index + query.length)} </> ); }; const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => { const user = filteredUsers[index]; return ( <div style={style} key={user.id} className="p-3 border-b border-pink-100 hover:bg-pink-50" > <p className="font-medium text-gray-800 text-base"> {highlight(user.name, query)} </p> <p className="text-sm text-gray-500">{highlight(user.email, query)}</p> </div> ); }; return ( <div className="min-h-screen bg-gray-100 p-6 font-sans"> <div className="max-w-3xl mx-auto bg-white p-6 rounded-2xl border-2 border-pink-200 shadow-md"> <h1 className="text-2xl font-bold text-pink-600 mb-4"> 👩‍💻 Massive Search Filter (Virtualized) </h1> <input type="text" value={query} onChange={handleSearch} placeholder="Search name or email..." className="w-full p-3 mb-4 border-2 border-pink-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-pink-400" /> {isPending && ( <div className="text-pink-500 italic mb-2 text-sm animate-pulse"> 🔄 Filtering... </div> )} {filteredUsers.length === 0 ? ( <div className="p-4 text-gray-500 italic text-center">No users found.</div> ) : ( <List height={500} itemCount={filteredUsers.length} itemSize={70} width="100%" className="border-t border-pink-100" > {Row} </List> )} </div> </div> );
}

Kết luận

Bằng cách áp dụng ảo hóa, ghi nhớ và các chiến lược ưu tiên giao diện người dùng, chúng tôi đã có thể cải thiện đáng kể hiệu suất của thành phần bộ lọc tìm kiếm. Tương tác người dùng hiện tại mượt mà và phản hồi nhanh, ngay cả khi phải lọc qua hàng ngàn mục.

Kinh nghiệm này làm nổi bật tầm quan trọng của việc tối ưu hóa hiệu suất khi xử lý dữ liệu lớn hoặc các tương tác người dùng phức tạp. Các hook tích hợp sẵn của React như useMemo, useTransition và các thư viện bên thứ ba như react-window có thể tạo ra sự khác biệt lớn về mức độ hiệu suất và sự mượt mà của ứng dụng đối với người dùng.

Hy vọng các thông tin vừa rồi sẽ giúp ích cho các bạn!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tìm hiểu về Redux Thunk

Chào mọi người, nếu bạn là người đã biết về React và đang làm quen với Redux chắc hẳn bạn đang rất mơ hồ về các khái niệm cơ bản của Redux như dispatch, store, action creator,... bạn còn đang vật lộn với đống document của Redux để hiểu những khái niệm đó và bạn nghe ai đó trong team nói về Redux Thu

0 0 407

- vừa được xem lúc

[React] Giới thiệu tổng quát về Redux Toolkit

1. Redux Toolkit (RTK) là gì và tại sao lại có nó. . .

0 0 6.7k

- vừa được xem lúc

Uống Pepsi code Vue đi - Uống Cocacola code React nha ;)

. (Nguồn ảnh: Internet). Chào các bạn, chào các bạn. Let's go . 1.

0 0 154

- vừa được xem lúc

Cài đặt taillwind css cho dự án React

Trong bài viết cùng mình tìm hiểu cách cài đặt tailwind css cho một dự án React sẵn có. .

0 0 154

- vừa được xem lúc

Formik vs React Hook Form (Phần 1)

Các lập trình viên Front End đều làm việc rất nhiều với form cùng sự phức tạp của ứng dụng. Do vậy chúng ta cần những thư viện form mạnh mẽ hỗ trợ quản lý các form state, form validation... Thành phần module. Formik bao gồm có 9 dependencies khác. . React Hook Form thì không có.

0 0 378

- vừa được xem lúc

Hướng dẫn React Redux cho người mới bắt đầu - Phần 1

Lời mởi đầu. Chào các bạn, ở thời điểm thực hiện bài viết này mình cũng là một người đang bắt đầu tìm hiểu và học với ReactJs và Redux, trong quá trình tìm hiểu đọc các tài liệu về thư viện này mình có tìm được một bài hướng dẫn khá hay nên đã quyết định chia sẻ với mọi người .

0 0 289