Tuần trước, chúng ta đã xem xét những lợi ích và chi phí khác nhau khi sử dụng các khuôn khổ, bắt đầu từ quan điểm về các vấn đề cốt lõi mà chúng đang cố gắng giải quyết, tập trung vào lập trình khai báo, liên kết dữ liệu, phản ứng, danh sách và điều kiện. Hôm nay, chúng ta sẽ xem liệu một giải pháp thay thế có thể xuất hiện từ chính nền tảng web hay không.
Khi lướt qua tài liệu của các framework phổ biến, tôi tìm thấy các tính năng được mô tả trong Phần 1 ngay lập tức. Khi đọc tài liệu nền tảng web (ví dụ: trên MDN), tôi thấy nhiều kiểu mẫu khó hiểu về cách thực hiện mọi việc, mà không có biểu diễn thuyết phục về liên kết dữ liệu, đồng bộ hóa danh sách hoặc khả năng phản ứng. Tôi sẽ cố gắng đưa ra một số hướng dẫn về cách tiếp cận các vấn đề này trên nền tảng web, mà không cần đến một khuôn khổ (nói cách khác, bằng cách sử dụng vanilla).
Nhưng nếu chúng ta không có mã đó và thay vào đó, chúng ta sử dụng CSS để ẩn và hiển thị nhãn lỗi thì sao?
Trong trường hợp này, phản ứng được xử lý trong trình duyệt — sự thay đổi lớp của ứng dụng sẽ truyền đến các lớp con của nó cho đến khi cơ chế nội bộ trong trình duyệt quyết định có hiển thị nhãn hay không.
Kỹ thuật này có một số ưu điểm:
Do được sử dụng rộng rãi và có lịch sử lâu đời, các API biểu mẫu đã tích lũy được một số nugget ẩn khiến chúng hữu ích cho các vấn đề mà theo truyền thống, người ta không nghĩ rằng có thể giải quyết bằng biểu mẫu.
Trong ví dụ nhãn lỗi từ phần trước, chúng tôi đã chỉ ra cách hiển thị và ẩn thông báo lỗi theo phản ứng. Đây là cách chúng tôi cập nhật văn bản thông báo lỗi trong React (và tương tự trong SolidJS):
Khi chúng ta có DOM ổn định và các biểu mẫu cây ổn định và các phần tử biểu mẫu, chúng ta có thể thực hiện các thao tác sau:
Điều này trông khá dài dòng ở dạng thô, nhưng nó cũng rất ổn định, trực tiếp và cực kỳ hiệu quả.
Đây sẽ là một ví dụ quen thuộc (được viết bằng Typescript để dễ đọc):
Trong mã khung, việc tạo đối tượng
Bằng cách sử dụng các đầu vào ẩn và lớp
Lưu ý trong ví dụ này là không sử dụng lớp nào — chúng ta phát triển hành vi của DOM và kiểu từ dữ liệu của biểu mẫu, thay vì thay đổi thủ công các lớp phần tử.
Tôi không thích sử dụng quá nhiều lớp CSS làm bộ chọn JavaScript. Tôi nghĩ chúng nên được sử dụng để nhóm các phần tử có kiểu tương tự lại với nhau, chứ không phải là cơ chế bắt buộc để thay đổi kiểu thành phần.
Vấn đề chính với danh sách quan sát có mục đích chung là chúng có mục đích chung. Điều này làm tăng thêm sự tiện lợi với chi phí hiệu suất và cũng yêu cầu các công cụ dành riêng cho nhà phát triển để gỡ lỗi các hành động phức tạp mà các thư viện đó thực hiện ở chế độ nền.
Sử dụng các thư viện đó và hiểu chức năng của chúng là ổn và chúng có thể hữu ích bất kể lựa chọn khuôn khổ UI nào, nhưng sử dụng giải pháp thay thế có thể không phức tạp hơn và có thể ngăn ngừa một số cạm bẫy xảy ra khi bạn cố gắng tạo mô hình của riêng mình.
Giao diện của ChaCha thường có thể được lấy từ thông số kỹ thuật của ứng dụng, mà không cần bất kỳ mã UI nào.
Ví dụ: một ứng dụng cho phép bạn thêm và xóa danh bạ và tải danh sách ban đầu từ máy chủ (có tùy chọn làm mới) có thể có ChaCha trông như thế này:
Lưu ý rằng tất cả các hàm trong hai giao diện đều là void và chỉ nhận các đối tượng thuần túy. Đây là cố ý. ChaCha được xây dựng giống như một kênh có hai cổng để gửi tin nhắn, cho phép nó hoạt động trong
Điểm tuyệt vời của ChaCha là chúng dễ kiểm tra: Bạn gửi các hành động và mong đợi các lệnh gọi cụ thể đến trình quan sát để đáp lại.
Khi chúng ta sử dụng phần tử
Sau đây sẽ thêm tên vào danh sách bằng cách sử dụng
Bằng cách sử dụng phần tử
Bạn có thể chơi với kết quả trong kho lưu trữ GitHub và mã nguồn đầy đủ có sẵn.
Các hàm trong mô hình tác vụ được lấy trực tiếp từ đặc tả và những gì người dùng có thể làm (xóa các tác vụ đã hoàn thành, đánh dấu tất cả là đã hoàn thành hoặc đang hoạt động, lấy số lượng đang hoạt động và đã hoàn thành).
Lưu ý rằng mô hình này tuân theo các hướng dẫn của ChaCha:
Mô hình này rất đơn giản và không liên quan nhiều đến cuộc thảo luận về khuôn khổ UI. Nó lưu vào
Làm sao để biết liệu một thứ gì đó có cần phải là phần tử biểu mẫu hay không? Theo nguyên tắc chung, nếu nó liên kết với dữ liệu từ mô hình, thì nó phải là phần tử biểu mẫu.
tệp HTML đầy đủ có sẵn, nhưng sau đây là phần chính của nó:
HTML này bao gồm những nội dung sau:
HTML này không biết cách tạo kiểu hoặc chính xác dữ liệu nào mà nó được liên kết. Hãy để CSS và JavaScript hoạt động cho HTML của bạn, thay vì HTML của bạn hoạt động cho một cơ chế tạo kiểu cụ thể. Điều này sẽ giúp bạn dễ dàng thay đổi thiết kế hơn khi bạn thực hiện.
Sau đây là một phiên bản, có giải thích cho từng phần:
Ở trên, chúng ta tạo một mô hình mới.
Khi một mục được thêm vào mô hình, chúng tôi tạo mục danh sách tương ứng của mục đó trong UI.
Ở trên, chúng tôi sao chép nội dung của mục
Lưu ý rằng hàm này, cùng với
Khi một mục được cập nhật, chúng tôi đặt các giá trị
Khi một mục bị xóa khỏi mô hình, chúng tôi sẽ xóa mục danh sách tương ứng của mục đó khỏi chế độ xem.
Trong mã trên, khi số lượng mục đã hoàn thành hoặc đang hoạt động thay đổi, chúng tôi đặt các đầu vào thích hợp để kích hoạt phản ứng CSS và chúng tôi định dạng đầu ra hiển thị số lượng.
Và chúng tôi cập nhật bộ lọc từ đoạn
Tại đây, chúng ta đảm bảo rằng chúng ta không tải lại trang khi biểu mẫu được gửi. Đây là dòng biến ứng dụng này thành SPA.
Và điều này xử lý các hành động chính (tạo, đánh dấu tất cả, xóa đã hoàn thành).
CSS xử lý rất nhiều yêu cầu của thông số kỹ thuật (với một số sửa đổi để ưu tiên khả năng truy cập). Hãy cùng xem một số ví dụ.
Theo thông số kỹ thuật, nút "X" (
Liên kết
Lưu ý rằng chúng ta có thể sử dụng
Chúng ta cũng sử dụng bộ chọn
Kiểu xem và chỉnh sửa của đầu vào
Việc lọc (tức là chỉ hiển thị các tác vụ đang hoạt động và đã hoàn thành) được thực hiện bằng bộ chọn:
Đoạn mã trên có vẻ hơi dài dòng và có lẽ sẽ dễ đọc hơn khi sử dụng bộ xử lý CSS như Sass. Nhưng những gì nó thực hiện thì rất đơn giản: Nếu bộ lọc
Tôi đã chọn triển khai bộ lọc đơn giản này trong CSS để cho thấy phạm vi mà bộ lọc này có thể đạt được, nhưng nếu nó bắt đầu trở nên phức tạp, thì việc chuyển nó vào mô hình sẽ hoàn toàn hợp lý.
Nhưng hãy nhớ rằng có những mẫu thay thế, thường có chi phí thấp hơn và không phải lúc nào cũng cần ít kinh nghiệm của nhà phát triển hơn. Hãy cho phép bản thân tò mò với những mẫu đó, ngay cả khi bạn quyết định chọn lọc chúng trong khi sử dụng một khuôn khổ.
Tự tạo khuôn khổ của riêng bạn?
Một kết quả có vẻ như không thể tránh khỏi khi khám phá cuộc sống mà không có một trong các khuôn khổ, là tự tạo khuôn khổ của riêng bạn để liên kết dữ liệu phản ứng. Sau khi đã thử cách này trước đây và thấy rằng nó có thể tốn kém như thế nào, tôi quyết định làm việc với một hướng dẫn trong quá trình khám phá này; không phải để tự tạo khuôn khổ của riêng mình, mà thay vào đó là để xem liệu tôi có thể sử dụng trực tiếp nền tảng web theo cách khiến các khuôn khổ ít cần thiết hơn hay không. Nếu bạn cân nhắc việc tự triển khai framework của riêng mình, hãy lưu ý rằng có một số chi phí không được thảo luận trong bài viết này.Vanilla Choices
Nền tảng web đã cung cấp một cơ chế lập trình khai báo ngay khi xuất xưởng: HTML và CSS. Cơ chế này đã hoàn thiện, được thử nghiệm kỹ lưỡng, phổ biến, được sử dụng rộng rãi và có tài liệu hướng dẫn. Tuy nhiên, nó không cung cấp các khái niệm tích hợp rõ ràng về liên kết dữ liệu, kết xuất có điều kiện và đồng bộ hóa danh sách, và khả năng phản ứng là một chi tiết tinh tế trải rộng trên nhiều tính năng nền tảng.Khi lướt qua tài liệu của các framework phổ biến, tôi tìm thấy các tính năng được mô tả trong Phần 1 ngay lập tức. Khi đọc tài liệu nền tảng web (ví dụ: trên MDN), tôi thấy nhiều kiểu mẫu khó hiểu về cách thực hiện mọi việc, mà không có biểu diễn thuyết phục về liên kết dữ liệu, đồng bộ hóa danh sách hoặc khả năng phản ứng. Tôi sẽ cố gắng đưa ra một số hướng dẫn về cách tiếp cận các vấn đề này trên nền tảng web, mà không cần đến một khuôn khổ (nói cách khác, bằng cách sử dụng vanilla).
Phản ứng với cây DOM ổn định và xếp tầng
Chúng ta hãy quay lại ví dụ về nhãn lỗi. Trong ReactJS và SolidJS, chúng ta tạo mã khai báo được dịch thành mã bắt buộc để thêm nhãn vào DOM hoặc xóa nhãn. Trong Svelte, mã đó được tạo ra.Nhưng nếu chúng ta không có mã đó và thay vào đó, chúng ta sử dụng CSS để ẩn và hiển thị nhãn lỗi thì sao?
Mã:
label.error { display: none; } .app.has-error label.error {display: block; }Message app.classList.toggle('has-error', true);
Kỹ thuật này có một số ưu điểm:
- Kích thước gói là 0.
- Không có bước xây dựng nào.
- Sự truyền thay đổi được tối ưu hóa và được thử nghiệm tốt trong mã trình duyệt gốc và tránh các hoạt động DOM tốn kém không cần thiết như
append
vàremove
. - Các bộ chọn ổn định. Trong trường hợp này, bạn có thể tin tưởng vào phần tử nhãn ở đó. Bạn có thể áp dụng hoạt ảnh cho nó mà không cần dựa vào các cấu trúc phức tạp như "nhóm chuyển tiếp". Bạn có thể giữ tham chiếu đến nó trong JavaScript.
- Nếu nhãn được hiển thị hoặc ẩn, bạn có thể thấy lý do trong bảng điều khiển kiểu của công cụ dành cho nhà phát triển, hiển thị cho bạn toàn bộ chuỗi, chuỗi quy tắc kết thúc trong nhãn có thể nhìn thấy (hoặc ẩn).
“Liên kết dữ liệu” hướng biểu mẫu
Trước kỷ nguyên của các ứng dụng một trang (SPA) nặng về JavaScript, biểu mẫu là cách chính để tạo các ứng dụng web bao gồm đầu vào của người dùng. Theo truyền thống, người dùng sẽ điền vào biểu mẫu và nhấp vào nút “Gửi” và mã phía máy chủ sẽ xử lý phản hồi. Biểu mẫu là phiên bản ứng dụng nhiều trang về liên kết dữ liệu và tương tác. Không có gì ngạc nhiên khi các phần tử HTML có tên cơ bản làinput
và output
là các phần tử biểu mẫu.Do được sử dụng rộng rãi và có lịch sử lâu đời, các API biểu mẫu đã tích lũy được một số nugget ẩn khiến chúng hữu ích cho các vấn đề mà theo truyền thống, người ta không nghĩ rằng có thể giải quyết bằng biểu mẫu.
Biểu mẫu và phần tử biểu mẫu như các bộ chọn ổn định
Biểu mẫu có thể truy cập theo tên (sử dụng document.forms
) và mỗi phần tử biểu mẫu có thể truy cập theo tên của nó (sử dụng form.elements
). Ngoài ra, biểu mẫu liên kết với một phần tử có thể truy cập được (sử dụng thuộc tính form
). Điều này bao gồm không chỉ các phần tử đầu vào mà còn các phần tử biểu mẫu khác như output
, textarea
và fieldset
, cho phép truy cập lồng nhau vào các phần tử trong một cây.Trong ví dụ nhãn lỗi từ phần trước, chúng tôi đã chỉ ra cách hiển thị và ẩn thông báo lỗi theo phản ứng. Đây là cách chúng tôi cập nhật văn bản thông báo lỗi trong React (và tương tự trong SolidJS):
Mã:
const [errorMessage, setErrorMessage] = useState(null);return {errorMessage}
Mã:
function setErrorMessage(message) { document.forms.contactForm.elements.email.elements.error.value = message; }
Biểu mẫu để nhập
Thông thường, khi chúng ta xây dựng một SPA, chúng ta có một số loại API giống JSON mà chúng ta sử dụng để cập nhật máy chủ của mình hoặc bất kỳ mô hình nào chúng ta sử dụng.Đây sẽ là một ví dụ quen thuộc (được viết bằng Typescript để dễ đọc):
Mã:
interface Contact { id: string; name: string; email: string; subscriber: boolean;}function updateContact(contact: Contact) { … }
Contact
này bằng cách chọn các phần tử đầu vào và xây dựng đối tượng từng phần là điều phổ biến. Với việc sử dụng đúng biểu mẫu, có một giải pháp thay thế ngắn gọn:
Mã:
updateContact(Object.fromEntries( new FormData(document.forms.contactForm));
FormData
hữu ích, chúng ta có thể chuyển đổi liền mạch các giá trị giữa đầu vào DOM và các hàm JavaScript.Kết hợp các biểu mẫu và khả năng phản ứng
Bằng cách kết hợp tính ổn định của bộ chọn hiệu suất cao của các biểu mẫu và khả năng phản ứng của CSS, chúng ta có thể đạt được logic UI phức tạp hơn:
Mã:
hàm setErrorMessage(phần, thông báo) { document.forms.contactForm.elements[phần].elements.error.value = thông báo; } hàm setShowErrors(hiển thị) { document.forms.contactForm.elements.showErrors.checked = hiển thị; } input[name="showErrors"]:not(:checked) ~ * output[name="error"] { display: none; }
Tôi không thích sử dụng quá nhiều lớp CSS làm bộ chọn JavaScript. Tôi nghĩ chúng nên được sử dụng để nhóm các phần tử có kiểu tương tự lại với nhau, chứ không phải là cơ chế bắt buộc để thay đổi kiểu thành phần.
Ưu điểm của biểu mẫu
- Giống như với việc xếp tầng, các biểu mẫu được tích hợp vào nền tảng web và hầu hết các tính năng của chúng đều ổn định. Điều đó có nghĩa là ít JavaScript hơn nhiều, ít phiên bản khung không khớp hơn và không có "bản dựng".
- Biểu mẫu có thể truy cập theo mặc định. Nếu ứng dụng của bạn sử dụng biểu mẫu đúng cách, sẽ ít cần đến các thuộc tính ARIA, "phần bổ trợ trợ năng" và các cuộc kiểm tra vào phút cuối hơn. Biểu mẫu thích hợp với điều hướng bằng bàn phím, trình đọc màn hình và các công nghệ hỗ trợ khác.
- Biểu mẫu đi kèm với các tính năng xác thực đầu vào tích hợp: xác thực theo mẫu biểu thức chính quy, phản ứng với các biểu mẫu không hợp lệ và hợp lệ trong CSS, xử lý bắt buộc so với tùy chọn, v.v. Bạn không cần thứ gì đó trông giống biểu mẫu để tận hưởng các tính năng này.
- Sự kiện
submit
của biểu mẫu cực kỳ hữu ích. Ví dụ, nó cho phép bắt phím "Enter" ngay cả khi không có nút gửi và cho phép phân biệt nhiều nút gửi bằng thuộc tínhsubmitter
(như chúng ta sẽ thấy trong ví dụ TODO sau). - Các phần tử được liên kết với biểu mẫu chứa chúng theo mặc định nhưng có thể được liên kết với bất kỳ biểu mẫu nào khác trong tài liệu bằng thuộc tính
form
. Điều này cho phép chúng ta thử nghiệm với liên kết biểu mẫu mà không tạo ra sự phụ thuộc vào cây DOM. - Sử dụng bộ chọn ổn định giúp tự động hóa thử nghiệm UI: Chúng ta có thể sử dụng API lồng nhau như một cách ổn định để móc vào DOM bất kể bố cục và phân cấp của nó. Phân cấp phần tử
form > (fieldsets) >
có thể đóng vai trò là bộ khung tương tác của tài liệu của bạn.
ChaCha And HTML Template
Các khuôn khổ cung cấp cách riêng để thể hiện danh sách có thể quan sát được. Nhiều nhà phát triển ngày nay cũng dựa vào các thư viện không phải framework cung cấp tính năng này, chẳng hạn như MobX.Vấn đề chính với danh sách quan sát có mục đích chung là chúng có mục đích chung. Điều này làm tăng thêm sự tiện lợi với chi phí hiệu suất và cũng yêu cầu các công cụ dành riêng cho nhà phát triển để gỡ lỗi các hành động phức tạp mà các thư viện đó thực hiện ở chế độ nền.
Sử dụng các thư viện đó và hiểu chức năng của chúng là ổn và chúng có thể hữu ích bất kể lựa chọn khuôn khổ UI nào, nhưng sử dụng giải pháp thay thế có thể không phức tạp hơn và có thể ngăn ngừa một số cạm bẫy xảy ra khi bạn cố gắng tạo mô hình của riêng mình.
Kênh thay đổi (hay ChaCha)
ChaCha — hay còn gọi là Kênh thay đổi — là luồng hai chiều có mục đích là thông báo các thay đổi theo hướng ý định và hướng quan sát.- Theo hướng ý định, UI thông báo cho mô hình về các thay đổi mà người dùng dự định.
- Theo hướng quan sát, mô hình thông báo cho UI về các thay đổi đã được thực hiện đối với mô hình và cần hiển thị cho người dùng.
MessagePort
). Trong trường hợp này, chúng tôi đang tạo một luồng hai chiều có mục đích cụ thể: báo cáo các thay đổi mô hình thực tế cho UI và các ý định cho mô hình.Giao diện của ChaCha thường có thể được lấy từ thông số kỹ thuật của ứng dụng, mà không cần bất kỳ mã UI nào.
Ví dụ: một ứng dụng cho phép bạn thêm và xóa danh bạ và tải danh sách ban đầu từ máy chủ (có tùy chọn làm mới) có thể có ChaCha trông như thế này:
Mã:
interface Contact { id: string; name: string; email: string;}// Hướng "Observe"interface ContactListModelObserver { onAdd(contact: Contact); onRemove(contact: Contact); onUpdate(contact: Contact);}// Hướng "Intent"interface ContactListModel { add(contact: Contact); remove(contact: Contact); reloadFromServer();}
EventSource
, MessageChannel
HTML, một trình làm việc dịch vụ hoặc bất kỳ giao thức nào khác.Điểm tuyệt vời của ChaCha là chúng dễ kiểm tra: Bạn gửi các hành động và mong đợi các lệnh gọi cụ thể đến trình quan sát để đáp lại.
Phần tử mẫu HTML cho các mục danh sách
Các mẫu HTML là các phần tử đặc biệt có trong DOM nhưng không được hiển thị. Mục đích của chúng là tạo ra các phần tử động.Khi chúng ta sử dụng phần tử
template
, chúng ta có thể tránh tất cả các mã mẫu để tạo các phần tử và điền chúng vào JavaScript.Sau đây sẽ thêm tên vào danh sách bằng cách sử dụng
template
:
Mã:
[LIST]
[*] [/LIST] function addName(name) { const list = document.querySelector('#names'); const item = list.querySelector('template').content.cloneNode(true).firstElementChild; item.querySelector('label').innerText = name; list.appendChild(item); }
template
cho các mục danh sách, chúng ta có thể thấy mục danh sách trong HTML gốc của mình — mục này không được "hiển thị" bằng JSX hoặc một ngôn ngữ nào khác. Tệp HTML của bạn hiện chứa tất cả mã HTML của ứng dụng — các phần tĩnh là một phần của DOM được hiển thị và các phần động được thể hiện trong các mẫu, sẵn sàng để sao chép và thêm vào tài liệu khi cần.Tổng hợp tất cả: TodoMVC
TodoMVC là thông số kỹ thuật ứng dụng của danh sách TODO đã được sử dụng để giới thiệu các khuôn khổ khác nhau. Mẫu TodoMVC đi kèm với HTML và CSS có sẵn để giúp bạn tập trung vào khuôn khổ.Bạn có thể chơi với kết quả trong kho lưu trữ GitHub và mã nguồn đầy đủ có sẵn.
Bắt đầu với ChaCha có nguồn gốc từ thông số kỹ thuật
Chúng ta sẽ bắt đầu với thông số kỹ thuật và sử dụng nó để xây dựng giao diện ChaCha:
Mã:
interface Task { title: string; completed: boolean;}giao diện TaskModelObserver { onAdd(key: số, giá trị: Nhiệm vụ); onUpdate(key: số, giá trị: Nhiệm vụ); onRemove(key: số); onCountChange(count: {active: số, hoàn thành: số});}giao diện TaskModel { constructor(observer: TaskModelObserver); createTask(nhiệm vụ: Nhiệm vụ): void; updateTask(key: số, nhiệm vụ: Nhiệm vụ): void; deleteTask(key: số): void; clearCompleted(): void; markAll(hoàn thành: boolean): void;}
Lưu ý rằng mô hình này tuân theo các hướng dẫn của ChaCha:
- Có hai giao diện, một giao diện hoạt động và một giao diện quan sát.
- Tất cả các kiểu tham số đều là nguyên thủy hoặc đối tượng thuần túy (có thể dễ dàng dịch sang JSON).
- Tất cả các hàm đều trả về void.
localStorage
làm phần phụ trợ.Mô hình này rất đơn giản và không liên quan nhiều đến cuộc thảo luận về khuôn khổ UI. Nó lưu vào
localStorage
khi cần và kích hoạt các lệnh gọi lại thay đổi tới trình quan sát khi có điều gì đó thay đổi, do hành động của người dùng hoặc khi mô hình được tải từ localStorage
lần đầu tiên.HTML tinh gọn, hướng biểu mẫu
Tiếp theo, tôi sẽ lấy mẫu TodoMVC và sửa đổi nó thành hướng biểu mẫu — một hệ thống phân cấp các biểu mẫu, với các phần tử đầu vào và đầu ra biểu diễn dữ liệu có thể thay đổi bằng JavaScript.Làm sao để biết liệu một thứ gì đó có cần phải là phần tử biểu mẫu hay không? Theo nguyên tắc chung, nếu nó liên kết với dữ liệu từ mô hình, thì nó phải là phần tử biểu mẫu.
tệp HTML đầy đủ có sẵn, nhưng sau đây là phần chính của nó:
Mã:
[HEADING=1]todos[/HEADING] [LIST]
[*] X [/LIST] 0 All Active Completed
- Chúng tôi có một biểu mẫu
main
, với tất cả các nút và đầu vào chung, và một biểu mẫu mới để tạo tác vụ mới. Lưu ý rằng chúng tôi liên kết các phần tử với biểu mẫu bằng thuộc tínhform
để tránh lồng các phần tử vào biểu mẫu. - Phần tử
template
biểu thị một mục danh sách và phần tử gốc của nó là một biểu mẫu khác biểu thị dữ liệu tương tác liên quan đến một tác vụ cụ thể. Biểu mẫu này sẽ được lặp lại bằng cách sao chép nội dung của mẫu khi các tác vụ được thêm vào. - Các đầu vào ẩn biểu thị dữ liệu không được hiển thị trực tiếp nhưng được sử dụng để tạo kiểu và chọn.
HTML này không biết cách tạo kiểu hoặc chính xác dữ liệu nào mà nó được liên kết. Hãy để CSS và JavaScript hoạt động cho HTML của bạn, thay vì HTML của bạn hoạt động cho một cơ chế tạo kiểu cụ thể. Điều này sẽ giúp bạn dễ dàng thay đổi thiết kế hơn khi bạn thực hiện.
Minimal Controller JavaScript
Bây giờ chúng ta đã có hầu hết khả năng phản ứng trong CSS và chúng ta có thể xử lý danh sách trong mô hình, phần còn lại là mã điều khiển — băng keo giữ mọi thứ lại với nhau. Trong ứng dụng nhỏ này, bộ điều khiển JavaScript khoảng 40 dòng.Sau đây là một phiên bản, có giải thích cho từng phần:
Mã:
import TaskListModel from './model.js';const model = new TaskListModel(new class {
Mã:
onAdd(key, value) { const newItem = document.querySelector('.todo-list template').content.cloneNode(true).firstElementChild; newItem.name = `task-${key}`; const save = () => model.updateTask(key, Object.fromEntries(new FormData(newItem))); newItem.elements.completed.addEventListener('change', save); newItem.addEventListener('submit', save); newItem.elements.title.addEventListener('dblclick', ({target}) => target.removeAttribute('readonly')); newItem.elements.title.addEventListener('blur', ({target}) => target.setAttribute('readonly', '')); newItem.elements.destroy.addEventListener('click', () => model.deleteTask(key)); this.onUpdate(key, value, newItem); document.querySelector('.todo-list').appendChild(newItem);}
Ở trên, chúng tôi sao chép nội dung của mục
template
, chỉ định trình lắng nghe sự kiện cho một mục cụ thể và thêm mục mới vào danh sách.Lưu ý rằng hàm này, cùng với
onUpdate
, onRemove
và onCountChange
, là các lệnh gọi lại sẽ được gọi từ model.
Mã:
onUpdate(key, {title, completed}, form = document.forms[`task-${key}`]) { form.elements.completed.checked = !!completed; form.elements.title.value = title; form.elements.title.blur();}
completed
và title
của mục đó, sau đó là blur
(để thoát khỏi chế độ chỉnh sửa).
Mã:
onRemove(key) { document.forms[`task-${key}`].remove(); }
Mã:
onCountChange({active, completed}) { document.forms.main.elements.completedCount.value = completed; document.forms.main.elements.toggleAll.checked = active === 0; document.forms.main.elements.totalCount.value = active + completed; document.forms.main.elements.activeCount.innerHTML = `[B]${active}[/b] item${active === 1 ? '' : 's'} left`;}
Mã:
const updateFilter = () => filter.value = location.hash.substr(2);window.addEventListener('hashchange', updateFilter);window.addEventListener('load', updateFilter);
hash
(và khi khởi động). Tất cả những gì chúng ta đang làm ở trên là thiết lập giá trị của một phần tử biểu mẫu — CSS sẽ xử lý phần còn lại.
Mã:
document.querySelector('.todoapp').addEventListener('submit', e => e.preventDefault(), {capture: true});
Mã:
document.forms.newTask.addEventListener('submit', ({target: {elements: {title}}}) => model.createTask({title: title.value}));document.forms.main.elements.toggleAll.addEventListener('change', ({target: {checked}})=> model.markAll(checked));document.forms.main.elements.clearCompleted.addEventListener('click', () => model.clearCompleted());
Phản ứng với CSS
Bạn có thể xem tệp CSS đầy đủ.CSS xử lý rất nhiều yêu cầu của thông số kỹ thuật (với một số sửa đổi để ưu tiên khả năng truy cập). Hãy cùng xem một số ví dụ.
Theo thông số kỹ thuật, nút "X" (
hủy
) chỉ hiển thị khi di chuột qua. Tôi cũng đã thêm một chút khả năng truy cập để làm cho nó hiển thị khi tác vụ được tập trung:
Mã:
.task:not(:hover, :focus-within) button[name="destroy"] { opacity: 0 }
filter
có đường viền màu đỏ khi đó là liên kết hiện tại:
Mã:
.todoapp input[name="filter"][value=""] ~ footer a[href$="#/"],nav a:target { border-color: #CE4646;}
href
của phần tử liên kết như một bộ chọn thuộc tính một phần — không cần JavaScript kiểm tra bộ lọc hiện tại và đặt lớp selected
trên phần tử thích hợp.Chúng ta cũng sử dụng bộ chọn
:target
, giúp chúng ta không phải lo lắng về việc có nên thêm bộ lọc hay không.Kiểu xem và chỉnh sửa của đầu vào
title
thay đổi dựa trên chế độ chỉ đọc của nó:
Mã:
.task input[name="title"]:read-only {…}.task input[name="title"]:not(:read-only) {…}
Mã:
input[name="filter"][value="active"] ~ * .task :is(input[name="completed"]:checked, input[name="completed"]:checked ~ *),input[name="filter"][value="completed"] ~ * .task :is(input[name="completed"]:not(:checked), input[name="completed"]:not(:checked) ~ *) { display: none;}
hoạt động
và hộp kiểm hoàn thành
được chọn hoặc ngược lại, thì chúng ta sẽ ẩn hộp kiểm và các hộp kiểm tương tự.Tôi đã chọn triển khai bộ lọc đơn giản này trong CSS để cho thấy phạm vi mà bộ lọc này có thể đạt được, nhưng nếu nó bắt đầu trở nên phức tạp, thì việc chuyển nó vào mô hình sẽ hoàn toàn hợp lý.
Kết luận và bài học kinh nghiệm
Tôi tin rằng các khuôn khổ cung cấp những cách thuận tiện để thực hiện các nhiệm vụ phức tạp và chúng có những lợi ích vượt ra ngoài phạm vi kỹ thuật, chẳng hạn như sắp xếp một nhóm nhà phát triển theo một phong cách và mẫu cụ thể. Nền tảng web cung cấp nhiều lựa chọn và việc áp dụng một khuôn khổ giúp mọi người ít nhất là cùng chung một trang đối với một số lựa chọn đó. Điều đó có giá trị. Ngoài ra, có một điều gì đó cần nói về sự thanh lịch của lập trình khai báo và tính năng lớn của thành phần hóa không phải là điều tôi đã giải quyết trong bài viết này.Nhưng hãy nhớ rằng có những mẫu thay thế, thường có chi phí thấp hơn và không phải lúc nào cũng cần ít kinh nghiệm của nhà phát triển hơn. Hãy cho phép bản thân tò mò với những mẫu đó, ngay cả khi bạn quyết định chọn lọc chúng trong khi sử dụng một khuôn khổ.
Tóm tắt mẫu
- Giữ cho cây DOM ổn định. Nó bắt đầu một phản ứng dây chuyền làm cho mọi thứ trở nên dễ dàng.
- Dựa vào CSS để phản ứng thay vì JavaScript, khi bạn có thể.
- Sử dụng các phần tử biểu mẫu làm cách chính để biểu diễn dữ liệu tương tác.
- Sử dụng phần tử
template
HTML thay vì các mẫu do JavaScript tạo ra. - Sử dụng luồng thay đổi hai chiều làm giao diện cho mô hình của bạn.
Đọc thêm
- Phân tích pháp y về các thành phần máy chủ React (RSC)
- Svelte 5 và tương lai của các khuôn khổ: Trò chuyện với Rich Harris
- Tạo biểu mẫu nhiều bước hiệu quả để có trải nghiệm người dùng tốt hơn
- Cách xây dựng trang web đa ngôn ngữ bằng Nuxt.js