Việc duy trì một ứng dụng web đơn giản có thể phức tạp đến không ngờ. Trong mô-đun này, bạn sẽ tìm hiểu cách API web hoạt động với việc phân luồng cũng như cách bạn có thể sử dụng API này cho các mẫu PWA phổ biến, chẳng hạn như quản lý trạng thái.
Tính đơn giản và phức tạp
Trong bài nói chuyện Simple Made Easy, Rich Hickey thảo luận về ưu điểm của sự đơn giản và phức tạp. Anh mô tả những thứ đơn giản thường tập trung vào:
"Một vai trò, một nhiệm vụ, một khái niệm hoặc một phương diện".
Tuy nhiên, hãy nhấn mạnh rằng sự đơn giản không quan trọng ở:
"Có một thực thể hoặc một thao tác."
Cho dù một điều đơn giản hay đơn giản là mối liên hệ giữa chúng.
Sự phức tạp đến từ việc ràng buộc, dệt vải hoặc sử dụng thuật ngữ của Rich, sự hoàn chỉnh mọi thứ lại với nhau. Bạn có thể tính độ phức tạp bằng cách đếm số lượng vai trò, nhiệm vụ, khái niệm hoặc phương diện do một quản lý nào đó quản lý.
Tính đơn giản là yếu tố cần thiết trong quá trình phát triển phần mềm vì mã đơn giản thường dễ hiểu và dễ bảo trì hơn. Tính đơn giản cũng cần thiết cho các ứng dụng web, vì điều này có thể giúp ứng dụng của chúng ta hoạt động nhanh chóng và có thể truy cập được trong mọi ngữ cảnh có thể.
Quản lý độ phức tạp của PWA
Tất cả JavaScript chúng tôi viết cho web đều chạm vào chuỗi chính tại một thời điểm. Tuy nhiên, luồng chính có rất nhiều phức tạp ngay từ đầu mà bạn, với tư cách là nhà phát triển, không thể kiểm soát được.
Luồng chính là:
- Chịu trách nhiệm vẽ trang. Bản thân trang này là một quy trình phức tạp, gồm nhiều bước liên quan đến việc tính toán kiểu, cập nhật và kết hợp các lớp cũng như tô màu lên màn hình.
- Chịu trách nhiệm lắng nghe và phản ứng với các sự kiện, bao gồm cả các sự kiện như cuộn.
- Chịu trách nhiệm tải và huỷ tải trang.
- Quản lý nội dung nghe nhìn, tính bảo mật và danh tính. Đó là tất cả trước khi bất kỳ mã nào bạn viết thậm chí có thể thực thi trên chuỗi đó, chẳng hạn như:
- Thao túng DOM.
- Truy cập vào các API nhạy cảm, chẳng hạn như các chức năng của thiết bị hoặc nội dung nghe nhìn/bảo mật/danh tính.
Như Surma đưa ra trong bài nói chuyện về Hội nghị Nhà phát triển Chrome năm 2019, luồng chính hiện đang được làm việc quá mức và bị chi trả quá thấp.
Chưa hết, hầu hết các mã xử lý ứng dụng cũng tồn tại trên luồng chính.
Tất cả những điều đó đều làm tăng thêm sự phức tạp của luồng chính. Luồng chính là luồng duy nhất mà trình duyệt có thể sử dụng để bố trí và hiển thị nội dung trên màn hình. Do đó, khi mã của bạn ngày càng đòi hỏi nhiều khả năng xử lý hơn để hoàn thành, chúng ta cần chạy mã nhanh chóng, vì mỗi giây cần để thực hiện logic ứng dụng là một giây mà trình duyệt không thể phản hồi với hoạt động đầu vào của người dùng hoặc vẽ lại trang.
Khi các lượt tương tác không kết nối được với đầu vào, khi khung hình giảm hay khi mất quá nhiều thời gian để sử dụng một trang web, người dùng sẽ cảm thấy thất vọng, họ cảm thấy ứng dụng bị hỏng và niềm tin của họ vào ứng dụng giảm đi.
Tin xấu? Việc thêm sự phức tạp vào luồng chính là một cách gần như chắc chắn để đạt được các mục tiêu này. Tin vui đây? Vì những gì luồng chính cần làm rất rõ ràng: bạn có thể sử dụng luồng này làm hướng dẫn để giảm sự phụ thuộc vào luồng cho phần còn lại của ứng dụng.
Tách biệt vấn đề
Ứng dụng web có rất nhiều loại tác vụ, nhưng nói chung, bạn có thể chia nhỏ thành các tác vụ trực tiếp chạm vào giao diện người dùng, còn các tác vụ không thì không. Giao diện người dùng là hoạt động:
- Trực tiếp chạm vào DOM.
- Sử dụng các API liên quan đến các tính năng của thiết bị, chẳng hạn như thông báo hoặc quyền truy cập vào hệ thống tệp.
- Chạm vào thông tin nhận dạng, ví dụ: cookie của người dùng, bộ nhớ cục bộ hoặc bộ nhớ phiên.
- Quản lý nội dung nghe nhìn, chẳng hạn như hình ảnh, âm thanh hoặc video.
- Có các vấn đề về bảo mật và cần người dùng can thiệp để phê duyệt, chẳng hạn như API nối tiếp trên web.
Tác vụ không phải giao diện người dùng có thể bao gồm các nội dung như:
- Tính toán thuần tuý.
- Truy cập dữ liệu (tìm nạp, IndexedDB, v.v.).
- Tiền mã hóa.
- Nhắn tin.
- Hành vi tạo hoặc thao túng hoạt động tạo hoặc phát trực tiếp trên màn hình hoặc sự kiện phát trực tiếp.
Tác vụ không phải giao diện người dùng thường được đặt trước tác vụ giao diện người dùng: người dùng nhấp vào nút kích hoạt yêu cầu mạng cho một API trả về kết quả được phân tích cú pháp mà sau đó được dùng để cập nhật DOM. Khi viết mã, chúng tôi thường xem xét trải nghiệm hai đầu này, nhưng mỗi phần của luồng đó thường không được xem xét. Ranh giới giữa công việc trên giao diện người dùng và công việc không liên quan đến giao diện người dùng cũng quan trọng như khi xem xét trải nghiệm hai đầu, vì đây là điểm đầu tiên bạn có thể giảm bớt độ phức tạp của các luồng chính.
Tập trung vào một nhiệm vụ duy nhất
Một trong những cách đơn giản nhất để đơn giản hoá mã là chia nhỏ các hàm để mỗi hàm tập trung vào một nhiệm vụ duy nhất. Bạn có thể xác định nhiệm vụ dựa trên các ranh giới được xác định bằng cách đi qua trải nghiệm toàn diện:
- Trước tiên, hãy phản hồi hoạt động đầu vào của người dùng. Đây là công việc liên quan đến giao diện người dùng.
- Tiếp theo, hãy tạo một yêu cầu API. Đây không phải là công việc giao diện người dùng.
- Tiếp theo, hãy phân tích cú pháp yêu cầu API. Một lần nữa, đây là công việc không phải giao diện người dùng.
- Tiếp theo, hãy xác định các thay đổi đối với DOM. Đây có thể là công việc liên quan đến giao diện người dùng hoặc nếu bạn đang sử dụng công cụ nào đó chẳng hạn như triển khai DOM ảo, thì đó có thể không phải là công việc liên quan đến giao diện người dùng.
- Cuối cùng, hãy thực hiện các thay đổi đối với DOM. Đây là công việc liên quan đến giao diện người dùng.
Ranh giới rõ ràng đầu tiên là giữa công việc trên giao diện người dùng và công việc không phải giao diện người dùng. Sau đó có những lệnh gọi đánh giá cần được đưa ra: liệu có thực hiện và phân tích cú pháp yêu cầu API trong một hoặc hai tác vụ không? Nếu các thay đổi DOM không phải là công việc giao diện người dùng, thì chúng có được đóng gói với công việc API không? Trong cùng một chuỗi? Trong một chuỗi khác? Mức độ phân tách phù hợp ở đây là yếu tố then chốt để đơn giản hoá cơ sở mã và khả năng di chuyển thành công các phần của mã đó ra khỏi luồng chính.
Khả năng kết hợp
Để chia nhỏ quy trình công việc lớn toàn diện thành các phần nhỏ hơn, bạn cần suy nghĩ về khả năng kết hợp của cơ sở mã. Lấy tín hiệu từ việc lập trình chức năng, hãy cân nhắc:
- Phân loại các loại công việc mà ứng dụng của bạn sẽ thực hiện.
- Xây dựng các giao diện đầu vào và đầu ra phổ biến cho các khoá đó.
Ví dụ: mọi tác vụ truy xuất API đều lấy điểm cuối API và trả về một mảng các đối tượng chuẩn. Tất cả các hàm xử lý dữ liệu sẽ nhận và trả về một mảng các đối tượng chuẩn.
JavaScript có một thuật toán sao chép có cấu trúc để sao chép các đối tượng JavaScript phức tạp. Web worker sử dụng lớp này khi gửi thông báo và IndexedDB sử dụng lớp này để lưu trữ các đối tượng. Việc chọn các giao diện mà bạn có thể sử dụng với thuật toán sao chép có cấu trúc sẽ giúp các giao diện này chạy linh hoạt hơn nữa.
Do đó, bạn có thể tạo thư viện chức năng có thể kết hợp bằng cách phân loại mã và tạo các giao diện I/O phổ biến cho các danh mục đó. Mã có khả năng kết hợp là một đặc điểm nổi bật của cơ sở mã đơn giản: các phần được kết nối lỏng lẻo, có thể thay thế cho nhau, có thể nằm "bên cạnh" nhau và xây dựng dựa vào nhau, trái ngược với mã phức tạp được kết nối sâu sắc với nhau và do đó không thể dễ dàng tách biệt. Và trên web, mã có thể kết hợp có thể là sự khác biệt giữa việc có quá nhiều hoạt động với luồng chính hay không.
Khi có sẵn mã có thể kết hợp, đã đến lúc xoá một phần mã đó ra khỏi luồng chính.
Sử dụng nhân viên web để giảm độ phức tạp
Web worker là một chức năng web thường ít được sử dụng nhưng có sẵn rộng rãi, cho phép bạn chuyển công việc ra khỏi luồng chính.
Nhân viên web cho phép PWA chạy (một số) JavaScript bên ngoài luồng chính.
Có 3 loại worker.
Nhân viên chuyên dụng (được nghĩ đến nhiều nhất khi mô tả nhân viên web) có thể được sử dụng bằng một tập lệnh trong một thực thể PWA đang chạy. Bất cứ khi nào có thể, công việc không tương tác trực tiếp với DOM nên được chuyển sang một nhân viên web để cải thiện hiệu suất.
Trình thực thi dùng chung cũng tương tự như trình thực thi chuyên dụng, ngoại trừ việc nhiều tập lệnh có thể chia sẻ chúng trên nhiều cửa sổ đang mở. Điều này mang lại lợi ích của một worker chuyên dụng nhưng có trạng thái dùng chung và ngữ cảnh nội bộ giữa các cửa sổ và tập lệnh.
Ví dụ: một worker dùng chung có thể quản lý quyền truy cập và giao dịch cho IndexedDB của PWA và thông báo kết quả giao dịch trên tất cả các tập lệnh gọi để chúng phản ứng với các thay đổi.
Trình chạy web cuối cùng cũng được đề cập chi tiết trong khoá học này: trình chạy dịch vụ, đóng vai trò là proxy cho các yêu cầu mạng và được dùng chung giữa mọi thực thể của PWA.
Thử ngay
Đến giờ lập trình rồi! Xây dựng một ứng dụng web tiến bộ (PWA) từ đầu dựa trên mọi kiến thức bạn đã tìm hiểu trong học phần này.