debounce, throttle và once - ba hàm thêm cách giải quyết logic người dùng!

debounce, throttle và once - ba hàm thêm cách giải quyết logic người dùng!

Tin ngắn hàng ngày dành cho bạn
  • Bắt đầu kỳ nghỉ tết rồi nên mình cũng không đăng bài nữa. Hẹn gặp lại các bạn qua tết nha 😁

    » Xem thêm
  • Tiếp tục về jj. Đang thắc mắc là nó mới thế liệu có ai làm mấy phần mềm dạng GUI cho dễ nhìn chưa. Kiểu giống như git thì có quá nhiều rồi không đếm xuể.

    May quá, tác giả tổng hợp lại luôn rồi Community-built tools around Jujutsu 🥳

    » Xem thêm
  • Turso thông báo rằng họ đang viết lại SQLite bằng Rust. Thế là lại có thêm một bằng chứng nữa cũng cố cho câu nói Rust đang "tái định nghĩa" lại nhiều thứ.

    Nhưng nguyên nhân sâu xa mới thú vị. Tại sao họ lại làm vậy? Ai cũng biết SQLite là nguồn mở, ai cũng có thể tạo bản sao (fork) để chỉnh sửa lại theo ý mình. Lẽ nào nhóm của Turso không thích hoặc không tin vào C - vốn là ngôn ngữ dùng để cấu thành SQLite.

    Mình xin kể chuyện một chút. Turso là một bên cung cấp dịch vụ máy chủ cơ sở dữ liệu dựa trên SQLite, họ đã thực hiện một vài tùy chỉnh trên bản sao của SQLite để phục vụ cho mục đích của mình, gọi nó là libSQL. Họ "hào phóng" cho cộng đồng đóng góp thoải mái.

    Quay trở lại SQLite là mã nguồn mở chứ không phải là đóng góp mở. Chỉ có một nhóm người đứng đằng sau duy trì mã nguồn này, và họ không tiếp nhận yêu cầu kéo (pull request) từ những người khác. Đồng nghĩa mọi thay đổi hoặc tính năng đều là của nhóm người này tạo ra. Có vẻ như SQLite rất phổ biến nhưng cộng đồng không thể làm điều mà họ muốn là đóng góp cho sự phát triển của nó.

    Chúng ta biết rằng hầu hết ứng dụng mã nguồn mở thường đi kèm với một thư mục "tests" với các bài kiểm tra rất nghiêm ngặt. Điều đó giúp cho sự cộng tác trong phát triển trở nên dễ dàng hơn. Nếu muốn chỉnh sửa hoặc thêm một tính năng mới, trước hết bạn cần phải đảm bảo sự thay đổi vượt qua được tất cả bài kiểm tra. Nhiều thông tin cho rằng SQLite không công khai bộ kiểm tra này. Điều này vô tình gây khó khăn cho những ai muốn chỉnh sửa mã nguồn. Vì họ không chắc chắn rằng liệu triển khai mới của mình có phù hợp với những tính năng cũ hay không.

    tursodatabase/limbo là dự án viết lại SQLite bằng Rust đã nhắc đến ở đầu bài. Họ nói rằng nó hoàn toàn tương thích với SQLite và nguồn mở hoàn toàn. limbo đang trong giai đoạn hoàn thiện. Chúng ta hãy chờ xem kết quả trong tương lai thế nào nhé. Bài viết chi tiết tại Introducing Limbo: A complete rewrite of SQLite in Rust.

    » Xem thêm

Vấn đề

Tìm ra cách giải quyết vấn đề là một trong những kỹ năng hết sức quan trọng đối với lập trình viên. Thông thường, khi chúng ta làm càng nhiều thì kinh nghiệm càng lên cao. Lúc này đối mặt với những bài toán tương tự mà có thể nhanh chóng tìm ra hướng giải quyết.

Có nhiều cách để học hỏi kinh nghiệm, một trong số đó là chịu khó tìm kiếm và đọc nhiều bài viết, tài liệu trên mạng. Nhiều khi kiến thức ta thu nạp được chưa cần thiết ngay lúc này, nhưng đến một lúc nào đó nó có thể phát huy tác dụng. Hoặc ít ra, đọc để trong đầu ta tự thốt lên: "À hóa ra với trường hợp này thì chúng ta nên xử lý thế này...".

debonce, throttleonce là ba hàm tiện ích mà qua đó có thể ứng dụng trong rất nhiều trường hợp. Đặc biệt là logic về UI/UX, trải nghiệm người dùng... Bài viết ngày hôm nay, tôi xin phép được trình bày về công dụng cũng như cách sử dụng chúng.

Debounce

Debounce là một kỹ thuật ngăn chặn một chuỗi sự kiện tương tự nhau diễn ra liên tiếp. Thay vào đó, sự kiện tiếp theo chỉ được gọi sau một khoảng thời gian hành động chấm dứt.

Một ví dụ thông dụng nhất mà chúng ta thường gặp đó là tính năng gõ từ khóa vào ô tìm kiếm. Khi nhập kí tự, kết quả tìm kiếm hoặc cụm từ gợi ý hiện ra. Đằng sau quá trình này là một cách xử lý nào đó, ví dụ như gọi API để lấy dữ liệu trước khi người dùng bấm vào nút tìm kiếm hoặc nhấn Enter. Nếu bắt sự kiện keypress thì cứ mỗi lần nhấn phím sẽ gọi một lần API. Như thế quả là lãng phí tài nguyên máy chủ bời vì tốc độ của người gõ là rất nhanh, chưa kể từ khóa họ nhập vào còn chưa đầy đủ. Để xử lý tối ưu trong trường hợp này là chờ một khoảng thời gian người dùng không nhấn nhím nữa thì hãy kích hoạt cuộc gọi API.

Đây là lúc debounce phát huy tác dụng. Một hàm debounce có thể trông giống như dưới đây:

function debounce(func, delay) {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), delay);
  };
}

Sau đó, sử dụng hàm trong sự kiện keypress. Ở đây mình lấy ví dụ trong vue:

<input @keypress="debounce(triggerCallAPI, 500)" />

Khi đó cứ mỗi 500ms mà người dùng không nhập gì nữa thì triggerCallAPI sẽ được gọi.

Một trường hợp phổ biến khác là sự kiện resize của trình duyệt. Trong quá trình thay đổi kích thước cửa sổ, có nhiều hàm cần chạy để xử lý logic. Nếu cứ gọi hàm liên tục khi resize chắc chắn hiệu năng sẽ bị ảnh hưởng. Lúc đó, chúng ta có thể sử dụng debounce để giải quyết vấn đề này.

Throttle

Throttle phần nào giống như Debounce. Nghĩa là nó cũng ngăn chặn sự kiện diễn ra liên tiếp, nhưng theo một cách khác so với Debounce.

Nếu Debounce chỉ kích hoạt sự kiện sau một khoảng thời gian mà sự kiện không xảy ra nữa thì Throttle lại chỉ kích hoạt sự kiện sau một khoảng thời gian mà bất kể sự kiện đó có diễn ra hay không. Hay nói ngắn gọn lại là Throttle chỉ cho phép chạy một sự kiện trong mỗi x giây. Sau x giây, lần chạy tiếp theo mới được thực thi nếu nó còn được kích hoạt (trigger).

Một hàm throttle có thể trông giống như dưới đây:

function throttle(func, delay) {
  let wait = false;

  return (...args) => {
    if (wait) {
        return;
    }

    func(...args);
    wait = true;
    setTimeout(() => {
      wait = false;
    }, delay);
  }
}

Bất cứ khi nào cần giới hạn thời gian giữa các lần kích hoạt sự kiện bạn có thể áp dụng Throttle. Ví dụ, ngăn chặn người dùng bấm quá nhanh vào một nút "Submit".

<input type="submit" @submit="throttle(triggerCallAPI, 500)" />

Nghĩa là triggerCallAPI chỉ được gọi sau mỗi 500ms bất kể người dùng có bấm vào nút bao nhiêu lần đi chăng nữa.

Once

Once chỉ cho phép sự kiện được gọi một lần duy nhất. Once hữu ích trong trường hợp bạn chỉ cho phép người dùng thực hiện một hành động trong phiên truy cập.

Một hàm once có thể giống giống như sau:

function once(func) {
  let ran = false;
  let result;
  return function() {
    if (ran) return result;
    result = func.apply(this, arguments);
    ran = true;
    return result;
  };
}

Ví dụ sử dụng trong trường hợp nút "Đánh dấu tất cả là đã đọc" của khung thông báo. Người dùng chỉ cần thực hiện bấm vào nút đó một lần trong mỗi phiên.

<button @click="once(triggerCallAPI)">Đánh dấu tất cả là đã đọc</button>

triggerCallAPI chỉ được gọi một lần duy nhất khi người dùng bấm vào button. Các lần tiếp theo, triggerCallAPI sẽ không được kích hoạt.

Tổng kết

Trên đây là 3 hàm debounce, throttleonce với mô tả kèm theo cách sử dụng. Chúng rất thông dụng trong xử lý logic về giao diện người dùng, đến nỗi nhiều thư viện tiện ích đều có mặt của 3 hàm này như là lodash. Việc biết đến cũng như biết cách sử dụng có thể giúp bạn giảm được thời gian giải quyết vấn đề trong những trường hợp tương tự trong bài viết.

Tài liệu tham khảo:

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...
Bấm hoặc cuộn mạnh để sang bài mới