[Component 101] Bài 2 – Bài toán nút delete

Tính năng delete là một ví dụ điển hình về sử dụng sai Component trong ứng dụng. Việc sử dụng sai tính năng Delete trong bảng và trong màn hình dẫn đến việc Component bị phình ra, đảm nhiệm nhiều chức năng cũng như copy / paste code giữa các Component.

Bài toán

Giả sử chúng ta có màn hình danh sách sau:

Khi chúng ta nhấn nút delete trên một row, màn hình hiện ra hộp thoại

Nếu nhấn nút delete, hệ thống sẽ gọi đến API delete item vừa chọn, hộp thoại biến mất và danh sách ngoài được reload.

Phương pháp cổ điển: để tất cả trong một màn

Về cơ bản, khi gặp bài toán trên, mọi người sẽ làm như sau:

fetchList() { ... }

deleteItem(id) { ... }

didSelectDelete(id) {
  this.setState({
    showModelDelete: true,
    selectedId: id,
  });
}

render() {
  return (
    <>
      <List />
      <DeletePermissionModal  />
    </>
  );
}

Chuyện không có gì đáng nói đúng ko? Hãy vậy thêm một hộp thoại active / deactive, gọi đến 2 api active / deactive khác nhau xem nào.

fetchList() { ... }

deleteItem(id) { ... }

activeItem(id) { ... }

inactiveItem(id) { ... }

didSelectDelete(id) { ... }

didSelectActive(id) { ... }

didSelectDeactive(id) { ... }

render() {
  return (
    <>
      <List />
      <DeletePermissionModal  />
      <ActiveModal />
      <DeactiveModal />
    </>
  );
}

Dĩ nhiên bạn có thể nói chúng ta có thể dùng active / deactive vào cùng một hàm, 1 api, một modal, tuy nhiên điều tôi muốn nói ở đây là code của component cha sẽ phình ra rất nhanh khi các chức năng được add thêm càng nhiều vào màn hình. Đến một thời điểm bạn sẽ thấy một file dài 1000 dòng trong code của mình.

Vậy chúng ta nên làm thế nào?

Single-responsibility principle

Một trong những nguyên lý cơ bản nhất của Class nói chung và Component nói riêng chính là single-responsibility principle. Nguyên lý này rất đơn giản, nói rằng một class chỉ nên làm 1 nhiệm vụ. Tương tự với Component.

Trong ví dụ kể trên, Component cha đang làm rất nhiều nhiệm vụ, cụ thể như sau:

  • Lấy thông tin trong list
  • Ẩn hiện các modal
  • Thực hiện các hành động delete / inactive / active

và càng thêm các action mới vào trong list, trách nhiệm của component cha lại càng nhiều lên.

Giải quyết bài toán này bằng single-responsibility principle chúng ta sẽ làm như sau:

  • Thiết lập 4 component độc lập
<List />
<DeleteModal />
<ActiveModal />
<InactiveModal />
  • Đưa các hàm xử lý API (deleteItem, activeItem, inactiveItem) vào bên trong các modal, callback ra ngoài khi hoàn thiện được action
class DeleteModal extends Component {
  didPressDelete() {
    try {
      const { id, didDeleteItem } = this.props;
      await API.permission.delete({ id });
      // other process then callback
      if (didDeleteItem) {
        didDeleteItem();
      }
    }
    catch (error) { ... }
  }

  render() { ... }
}
  • Khi nhận sự kiện từ click từ list, truyền selectedId này vào trong chính Modal để modal tự xử lý

Bằng các cách trên, khi phát sinh các yêu cầu nghiệp vụ mới, chủ yếu chúng ta sẽ phát sinh component mới chứ không làm phình ra component cũ, ngoài ra việc dùng lại các nghiệp vụ sẽ đơn giản hơn và hiệu quả hơn, thay vì đi copy / paste các hàm / jsx thì chỉ cần import các modal nghiệp vụ.

Kết luận

Việc suy nghĩ JSX như một “view” sẽ khiến cho component không phát huy được hết tác dụng của nó, code rất xấu và khó maintain. Chỉ cần thay đổi phương pháp đặt vấn đề, suy nghĩ sâu xa hơn về các tính chất của component, ngay lập tức bài toán đã trở nên mạch lạc hơn và ngắn gọn hơn.

Trong phần sau chúng ta tiếp tục xử lý những vấn đề khác của component, tập trung vào component dạng bảng.

Leave a Reply

Your email address will not be published. Required fields are marked *