Các phương pháp hay nhất về phần tử tuỳ chỉnh

Các phần tử tuỳ chỉnh cho phép bạn tạo thẻ HTML của riêng mình. Danh sách kiểm tra này bao gồm các phương pháp hay nhất để giúp bạn tạo các phần tử chất lượng cao.

Các phần tử tuỳ chỉnh cho phép bạn mở rộng HTML và xác định các thẻ của riêng bạn. Họ là đây là một tính năng vô cùng mạnh mẽ nhưng cũng ở cấp độ thấp, có nghĩa là luôn rõ ràng cách tốt nhất để triển khai yếu tố của riêng bạn.

Để giúp bạn tạo ra trải nghiệm tốt nhất có thể, chúng tôi đã tổng hợp danh sách kiểm tra. Công cụ này chia nhỏ tất cả những yếu tố mà chúng tôi cho rằng cần có để trở thành một hoạt động tốt.

Danh sách kiểm tra

DOM bóng

Tạo gốc đổ bóng để đóng gói các kiểu.

Tại sao? Việc đóng gói các kiểu trong gốc đổ bóng của phần tử đảm bảo rằng kiểu đó sẽ hoạt động bất kể nó được sử dụng ở đâu. Điều này đặc biệt quan trọng nếu nhà phát triển muốn đặt phần tử của bạn bên trong gốc bóng của một phần tử khác. Chiến dịch này áp dụng cho cả các phần tử đơn giản như hộp đánh dấu hoặc nút chọn. Có thể là trong trường hợp nội dung duy nhất bên trong thư mục gốc của bóng sẽ là kiểu chính họ.
Ví dụ Phần tử <howto-checkbox>.

Tạo gốc đổ bóng trong hàm khởi tạo.

Tại sao? Hàm khởi tạo là khi bạn có Kiến thức độc quyền về phần tử của mình. Đây là thời điểm tuyệt vời để thiết lập các chi tiết triển khai mà bạn không muốn không có nhiều yếu tố. Thực hiện công việc này trong lệnh gọi lại sau đó, chẳng hạn như connectedCallback, nghĩa là bạn cần đề phòng trong trường hợp phần tử của bạn bị tách rời và sau đó được đính kèm lại vào tài liệu.
Ví dụ Phần tử <howto-checkbox>.

Đặt mọi phần tử con mà phần tử tạo ra vào gốc bóng của nó.

Tại sao? Phần tử con do phần tử của bạn tạo là một phần trong quá trình triển khai và nên được riêng tư. Nếu không có sự bảo vệ của gốc bóng, JavaScript bên ngoài có thể vô tình can thiệp vào những trẻ em này.
Ví dụ Phần tử <howto-tabs>.

Sử dụng <slot> để chiếu các phần tử con DOM sáng vào DOM tối của bạn

Tại sao? Cho phép người dùng trong thành phần của bạn chỉ định nội dung trong thành phần vì thành phần con HTML sẽ giúp thành phần đó trở nên dễ kết hợp hơn. Khi trình duyệt không hỗ trợ các phần tử tuỳ chỉnh, nội dung lồng nhau vẫn có sẵn, hiển thị và truy cập được.
Ví dụ Phần tử <howto-tabs>.

Đặt kiểu hiển thị :host (ví dụ: block, inline-block, flex) trừ phi bạn thích giá trị mặc định là inline.

Tại sao? Các phần tử tuỳ chỉnh là display: inline theo mặc định, vì vậy, việc đặt phần tử width hoặc height sẽ không có hiệu lực. Tần suất này làm nhà phát triển ngạc nhiên và có thể gây ra các vấn đề liên quan đến bố trí trang. Nếu không muốn dùng màn hình inline, bạn có thể phải luôn đặt giá trị display mặc định.
Ví dụ Phần tử <howto-checkbox>.

Thêm kiểu hiển thị :host tuân theo thuộc tính ẩn.

Tại sao? Một phần tử tuỳ chỉnh có kiểu display mặc định, ví dụ: :host { display: block }, sẽ thay thế độ đặc hiệu thấp hơn tích hợp sẵn thuộc tính hidden. Bạn có thể ngạc nhiên nếu muốn đặt hidden trên phần tử của bạn để hiển thị nó display: none. Ngoài ra thành kiểu display mặc định, hãy thêm tính năng hỗ trợ cho hidden cùng với :host([hidden]) { display: none }.
Ví dụ Phần tử <howto-checkbox>.

Thuộc tính và thuộc tính

Không ghi đè các thuộc tính chung, do nhóm tác giả thiết lập.

Tại sao? Thuộc tính chung là những thuộc tính có trên tất cả các phần tử HTML. Hơi nhiều Ví dụ: tabindexrole. Phần tử tuỳ chỉnh có thể cần đặt tabindex ban đầu thành 0 để thiết bị này trở thành bàn phím có thể làm tâm điểm. Nhưng bạn phải luôn kiểm tra trước để xem liệu nhà phát triển đang sử dụng phần tử của bạn đã đặt giá trị này thành một giá trị khác. Ví dụ: nếu họ đặt tabindex thành -1, đó là tín hiệu cho thấy họ không muốn tương tác.
Ví dụ Phần tử <howto-checkbox>. Điều này được giải thích thêm trong Đừng ghi đè tác giả trang.

Luôn chấp nhận dữ liệu gốc (chuỗi, số, boolean) làm thuộc tính hoặc thuộc tính.

Tại sao? Bạn nên định cấu hình các phần tử tuỳ chỉnh (như các phần tử tương đương được tích hợp sẵn). Cấu hình có thể được chuyển vào theo cách khai báo, thông qua các thuộc tính hoặc bắt buộc thông qua các thuộc tính JavaScript. Tốt nhất là bạn cũng nên liên kết mọi thuộc tính với một thuộc tính tương ứng.
Ví dụ Phần tử <howto-checkbox>.

Cố gắng đồng bộ hoá các thuộc tính và thuộc tính của dữ liệu gốc, phản ánh từ vào thuộc tính đó và ngược lại.

Tại sao? Bạn không bao giờ biết cách người dùng sẽ tương tác với phần tử của mình. Chúng có thể đặt một thuộc tính trong JavaScript, sau đó yêu cầu đọc giá trị đó bằng cách sử dụng một API như getAttribute(). Nếu mỗi thuộc tính có một thuộc tính tương ứng và cả hai đều phản ánh, điều này sẽ giúp để người dùng làm việc với phần tử của bạn. Nói cách khác, việc gọi điện setAttribute('foo', value) cũng phải đặt giá trị tương ứng thuộc tính foo và ngược lại. Tất nhiên, vẫn có những trường hợp ngoại lệ đối với quy tắc này. Bạn không nên phản ánh các thuộc tính tần suất cao, ví dụ: currentTime trong trình phát video. Hãy suy xét thật kỹ lưỡng. Nếu có vẻ như người dùng sẽ tương tác với một thuộc tính hoặc thuộc tính và việc phản ánh điều đó không hề khó chịu, rồi bạn hãy làm như vậy.
Ví dụ Phần tử <howto-checkbox>. Điều này được giải thích thêm trong Tránh các vấn đề về tỷ lệ người dùng quay lại.

Cố gắng chỉ chấp nhận dữ liệu đa dạng thức (đối tượng, mảng) làm thuộc tính.

Tại sao? Nói chung, không có ví dụ nào về các phần tử HTML tích hợp sẵn chấp nhận dữ liệu đa dạng thức (đối tượng và mảng JavaScript thuần tuý) thông qua . Dữ liệu đa dạng thức được chấp nhận thông qua lệnh gọi phương thức hoặc các thuộc tính. Có một vài nhược điểm rõ ràng khi chấp nhận dữ liệu đa dạng thức như thuộc tính: có thể tốn kém khi chuyển đổi tuần tự một đối tượng lớn thành một chuỗi và mọi tham chiếu đối tượng đều sẽ bị mất trong quá trình chuỗi này. Cho ví dụ: nếu bạn xâu chuỗi một đối tượng có tham chiếu đến một đối tượng khác, hoặc có thể là nút DOM, các tham chiếu đó sẽ bị mất.

Đừng phản ánh các thuộc tính dữ liệu đa dạng thức vào các thuộc tính.

Tại sao? Việc phản ánh các thuộc tính dữ liệu đa dạng thức cho các thuộc tính là không cần thiết, yêu cầu chuyển đổi tuần tự và giải tuần tự các đối tượng JavaScript giống nhau. Trừ phi bạn có một trường hợp sử dụng chỉ có thể giải quyết bằng tính năng này, tốt nhất là nên tránh việc đó.

Cân nhắc kiểm tra các thuộc tính có thể đã được đặt trước phần tử này đã nâng cấp.

Tại sao? Nhà phát triển sử dụng phần tử của bạn có thể tìm cách đặt thuộc tính trên phần tử đó trước khi tải định nghĩa. Điều này đặc biệt đúng nếu nhà phát triển đang sử dụng một khung xử lý quá trình tải, đóng dấu các thành phần đó với trang rồi liên kết các thuộc tính của chúng với một mô hình.
Ví dụ Phần tử <howto-checkbox>. Giải thích thêm trong Đặt các thuộc tính thành lazy.

Không được tự áp dụng lớp học.

Tại sao? Các phần tử cần thể hiện trạng thái phải sử dụng các thuộc tính. Chiến lược phát hành đĩa đơn Thuộc tính class thường được coi là thuộc sở hữu của nhà phát triển đang sử dụng phần tử của bạn và tự bạn viết vào phần tử đó có thể vô tình dậm chân vào các lớp học lập trình.

Sự kiện

Điều phối các sự kiện để phản hồi hoạt động của thành phần nội bộ.

Tại sao? Thành phần của bạn có thể có các thuộc tính thay đổi để phản hồi hoạt động chỉ thành phần của bạn biết (chẳng hạn như bộ tính giờ hay ảnh động) hoàn tất hoặc tài nguyên tải xong. Sẽ hữu ích khi điều phối các sự kiện để phản hồi những thay đổi này nhằm thông báo cho máy chủ lưu trữ rằng trạng thái của thành phần là khác.

Không gửi các sự kiện để phản hồi chế độ cài đặt của máy chủ lưu trữ (giảm dần) luồng dữ liệu).

Tại sao? Việc điều phối một sự kiện để phản hồi chế độ cài đặt của người tổ chức sự kiện là không cần thiết (máy chủ lưu trữ biết trạng thái hiện tại vì chỉ cần đặt trạng thái đó). Sự kiện điều phối khi phản hồi chế độ cài đặt của máy chủ lưu trữ, một thuộc tính có thể tạo ra vòng lặp vô hạn với dữ liệu hệ thống liên kết.
Ví dụ Phần tử <howto-checkbox>.

Video giải thích

Không ghi đè tác giả trang

Có thể nhà phát triển đang sử dụng phần tử của bạn muốn ghi đè một số trạng thái ban đầu của nó. Ví dụ: thay đổi role ARIA hoặc khả năng lấy tiêu điểm bằng tabindex Kiểm tra xem các thuộc tính này và bất kỳ thuộc tính chung nào khác đã được thiết lập hay chưa, trước khi áp dụng các giá trị của riêng bạn.

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

Đặt thành lazy cho các thuộc tính

Nhà phát triển có thể tìm cách đặt một thuộc tính trên phần tử của bạn trước đã tải định nghĩa. Điều này đặc biệt đúng nếu nhà phát triển đang sử dụng khung xử lý việc tải các thành phần, chèn chúng vào trang và liên kết các thuộc tính của chúng với một mô hình.

Trong ví dụ sau, Angular được tuyên bố liên kết với mô hình của nó isChecked với thuộc tính checked của hộp đánh dấu. Nếu định nghĩa cho hộp kiểm howto-checkbox được tải từng phần, có thể Angular có thể tìm cách thiết lập thuộc tính được đánh dấu trước khi phần tử được nâng cấp.

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

Một phần tử tuỳ chỉnh nên xử lý trường hợp này bằng cách kiểm tra xem có cơ sở lưu trú nào đã được đặt trên thực thể của nó. <howto-checkbox> minh hoạ mẫu này bằng phương thức có tên là _upgradeProperty().

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

_upgradeProperty() ghi lại giá trị từ phiên bản chưa nâng cấp và xoá thuộc tính đó để nó không làm ẩn phương thức setter thuộc tính của riêng phần tử tuỳ chỉnh. Bằng cách này, khi định nghĩa của phần tử cuối cùng đã tải, nó có thể ngay lập tức để phản ánh chính xác trạng thái.

Tránh các vấn đề về tỷ lệ người dùng quay lại

Bạn nên sử dụng attributeChangedCallback() để phản ánh trạng thái cho một thuộc tính cơ bản, ví dụ:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

Nhưng điều này có thể tạo ra một vòng lặp vô hạn nếu phương thức setter thuộc tính cũng phản ánh thuộc tính đó.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

Một cách khác là cho phép phương thức setter thuộc tính phản ánh đến thuộc tính và để phương thức getter xác định giá trị dựa trên thuộc tính.

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

Trong ví dụ này, việc thêm hoặc xoá thuộc tính cũng sẽ đặt thuộc tính đó.

Cuối cùng, bạn có thể dùng attributeChangedCallback() để xử lý các hiệu ứng phụ như áp dụng các trạng thái ARIA.

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}