Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánh

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánh

Tôi luôn quan tâm đến cách Habr được cấu trúc từ bên trong, cách cấu trúc quy trình làm việc, cách cấu trúc thông tin liên lạc, những tiêu chuẩn nào được sử dụng và cách viết mã nói chung ở đây. May mắn thay, tôi có được cơ hội như vậy vì gần đây tôi đã trở thành thành viên của nhóm habra. Sử dụng ví dụ về một lần tái cấu trúc nhỏ của phiên bản di động, tôi sẽ cố gắng trả lời câu hỏi: làm việc ở đây ở phía trước sẽ như thế nào. Trong chương trình: Node, Vue, Vuex và SSR với nước sốt từ những ghi chú về trải nghiệm cá nhân ở Habr.

Điều đầu tiên bạn cần biết về nhóm phát triển là có rất ít người trong chúng tôi. Không đủ - đây là ba mặt trận, hai mặt sau và người dẫn đầu kỹ thuật của tất cả Habr - Baxley. Tất nhiên, còn có một người thử nghiệm, một nhà thiết kế, ba Vadim, một cây chổi thần kỳ, một chuyên gia tiếp thị và những Bumburums khác. Nhưng chỉ có sáu người đóng góp trực tiếp cho nguồn thông tin của Habr. Điều này khá hiếm - một dự án có lượng khán giả trị giá hàng triệu đô la, nhìn từ bên ngoài trông giống như một doanh nghiệp khổng lồ, trên thực tế trông giống một công ty khởi nghiệp ấm cúng với cơ cấu tổ chức phẳng nhất có thể.

Giống như nhiều công ty CNTT khác, Habr tuyên bố các ý tưởng Agile, thực hành CI, v.v. Nhưng theo cảm nhận của tôi, Habr với tư cách là một sản phẩm đang phát triển theo từng đợt hơn là liên tục. Vì vậy, trong nhiều lần chạy nước rút liên tiếp, chúng tôi siêng năng viết mã thứ gì đó, thiết kế và thiết kế lại, phá vỡ thứ gì đó và sửa nó, giải quyết các yêu cầu và tạo yêu cầu mới, dẫm lên một cái cào và tự bắn vào chân mình, để cuối cùng đưa tính năng này vào sản xuất. Và sau đó sẽ có một khoảng thời gian tạm lắng nhất định, một thời kỳ tái phát triển, thời gian để làm những gì nằm trong góc phần tư “quan trọng-không khẩn cấp”.

Chính xác thì cuộc chạy nước rút “trái mùa” này sẽ được thảo luận dưới đây. Lần này nó bao gồm việc tái cấu trúc phiên bản di động của Habr. Nhìn chung, công ty đặt nhiều hy vọng vào nó và trong tương lai nó sẽ thay thế toàn bộ sở thú của các hóa thân của Habr và trở thành một giải pháp đa nền tảng phổ quát. Một ngày nào đó sẽ có bố cục thích ứng, PWA, chế độ ngoại tuyến, tùy chỉnh người dùng và nhiều điều thú vị khác.

Hãy đặt nhiệm vụ

Một lần, tại một buổi họp mặt thông thường, một người đứng đầu đã nói về các vấn đề trong cấu trúc của thành phần bình luận của phiên bản di động. Với suy nghĩ này, chúng tôi đã tổ chức một cuộc họp vi mô theo hình thức trị liệu tâm lý nhóm. Mọi người lần lượt nói chỗ đau, họ ghi lại mọi chuyện ra giấy, họ thông cảm, họ hiểu, trừ việc không có ai vỗ tay. Kết quả là một danh sách gồm 20 vấn đề cho thấy rõ rằng Habr di động vẫn còn một con đường dài và đầy chông gai để đến thành công.

Tôi chủ yếu quan tâm đến hiệu quả sử dụng tài nguyên và cái được gọi là giao diện mượt mà. Mỗi ngày, trên đường đi làm về nhà, tôi thấy chiếc điện thoại cũ của mình đang cố gắng hiển thị 20 dòng tiêu đề trong nguồn cấp dữ liệu. Nó trông giống như thế này:

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánhGiao diện Mobile Habr trước khi tái cấu trúc

Những gì đang xảy ra ở đây? Nói tóm lại, máy chủ phân phối trang HTML tới mọi người theo cùng một cách, bất kể người dùng có đăng nhập hay không. Sau đó, JS máy khách được tải và yêu cầu lại dữ liệu cần thiết nhưng được điều chỉnh để cấp phép. Tức là chúng tôi thực sự đã làm cùng một công việc hai lần. Giao diện nhấp nháy và người dùng đã tải xuống thêm hàng trăm kilobyte. Về chi tiết, mọi thứ thậm chí còn trông đáng sợ hơn.

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánhSơ đồ SSR-CSR cũ. Việc ủy ​​quyền chỉ có thể thực hiện được ở các giai đoạn C3 và C4, khi Node JS không bận tạo HTML và có thể ủy quyền các yêu cầu tới API.

Kiến trúc của chúng tôi vào thời điểm đó đã được một trong những người dùng Habr mô tả rất chính xác:

Phiên bản di động thật tệ. Tôi đang kể nó như nó vậy. Một sự kết hợp khủng khiếp giữa SSR và CSR.

Chúng tôi phải thừa nhận điều đó, dù nó có buồn đến thế nào.

Tôi đã đánh giá các tùy chọn, tạo một yêu cầu trong Jira với mô tả ở mức độ “hiện tại rất tệ, hãy làm đúng” và phân tích nhiệm vụ theo các nét khái quát:

  • tái sử dụng dữ liệu,
  • giảm thiểu số lần vẽ lại,
  • loại bỏ các yêu cầu trùng lặp,
  • làm cho quá trình tải rõ ràng hơn.

Hãy tái sử dụng dữ liệu

Về lý thuyết, kết xuất phía máy chủ được thiết kế để giải quyết hai vấn đề: không gặp phải những hạn chế của công cụ tìm kiếm về mặt Lập chỉ mục SPA và cải thiện chỉ số FMP (chắc chắn sẽ xấu đi TTI). Trong một kịch bản cổ điển mà cuối cùng được xây dựng tại Airbnb vào năm 2013 năm (vẫn còn trên Backbone.js), SSR là ứng dụng JS đẳng cấu tương tự chạy trong môi trường Node. Máy chủ chỉ cần gửi bố cục được tạo dưới dạng phản hồi cho yêu cầu. Sau đó quá trình bù nước diễn ra ở phía máy khách và sau đó mọi thứ sẽ hoạt động mà không cần tải lại trang. Đối với Habr, cũng như nhiều tài nguyên khác có nội dung văn bản, kết xuất máy chủ là một yếu tố quan trọng trong việc xây dựng mối quan hệ thân thiện với các công cụ tìm kiếm.

Mặc dù thực tế đã hơn sáu năm trôi qua kể từ khi công nghệ này ra đời và trong thời gian này, rất nhiều nước đã thực sự trôi qua trong thế giới front-end, nhưng đối với nhiều nhà phát triển, ý tưởng này vẫn được giữ bí mật. Chúng tôi đã không đứng ngoài cuộc và triển khai ứng dụng Vue có hỗ trợ SSR vào sản xuất, thiếu một chi tiết nhỏ: chúng tôi đã không gửi trạng thái ban đầu cho khách hàng.

Tại sao? Không có câu trả lời chính xác cho câu hỏi này. Hoặc là họ không muốn tăng kích thước phản hồi từ máy chủ hoặc do một loạt vấn đề kiến ​​​​trúc khác hoặc đơn giản là nó không thành công. Bằng cách này hay cách khác, việc loại bỏ trạng thái và sử dụng lại mọi thứ mà máy chủ đã làm có vẻ khá phù hợp và hữu ích. Nhiệm vụ thực sự là tầm thường - trạng thái chỉ đơn giản là được tiêm vào ngữ cảnh thực thi và Vue tự động thêm nó vào bố cục được tạo dưới dạng biến toàn cục: window.__INITIAL_STATE__.

Một trong những vấn đề nảy sinh là không có khả năng chuyển đổi cấu trúc tuần hoàn thành JSON (tham chiếu vòng tròn); đã được giải quyết bằng cách thay thế các cấu trúc như vậy bằng các cấu trúc phẳng của chúng.

Ngoài ra, khi xử lý nội dung UGC, bạn nên nhớ rằng dữ liệu phải được chuyển đổi thành thực thể HTML để không làm hỏng HTML. Đối với những mục đích này chúng tôi sử dụng he.

Giảm thiểu việc vẽ lại

Như bạn có thể thấy từ sơ đồ trên, trong trường hợp của chúng tôi, một phiên bản Node JS thực hiện hai chức năng: SSR và “proxy” trong API, nơi diễn ra quá trình ủy quyền người dùng. Trường hợp này khiến không thể ủy quyền khi mã JS đang chạy trên máy chủ vì nút này là một luồng và chức năng SSR là đồng bộ. Nghĩa là, máy chủ không thể gửi yêu cầu đến chính nó trong khi callstack đang bận việc gì đó. Hóa ra là chúng tôi đã cập nhật trạng thái, nhưng giao diện không ngừng giật, vì dữ liệu trên máy khách phải được cập nhật có tính đến phiên người dùng. Chúng tôi cần hướng dẫn ứng dụng của mình đặt dữ liệu chính xác ở trạng thái ban đầu, có tính đến thông tin đăng nhập của người dùng.

Chỉ có hai giải pháp cho vấn đề:

  • đính kèm dữ liệu ủy quyền vào các yêu cầu trên nhiều máy chủ;
  • chia các lớp Node JS thành hai phiên bản riêng biệt.

Giải pháp đầu tiên yêu cầu sử dụng các biến toàn cục trên máy chủ và giải pháp thứ hai kéo dài thời hạn hoàn thành nhiệm vụ thêm ít nhất một tháng.

Làm thế nào để thực hiện một sự lựa chọn? Habr thường di chuyển theo con đường ít bị cản trở nhất. Một cách không chính thức, có một mong muốn chung là giảm chu trình từ ý tưởng đến nguyên mẫu xuống mức tối thiểu. Mô hình thái độ đối với sản phẩm phần nào gợi nhớ đến các định đề của booking.com, với điểm khác biệt duy nhất là Habr coi trọng phản hồi của người dùng hơn nhiều và tin tưởng bạn, với tư cách là nhà phát triển, sẽ đưa ra những quyết định như vậy.

Theo logic này và mong muốn giải quyết vấn đề nhanh chóng của bản thân, tôi đã chọn các biến toàn cục. Và, như thường lệ, sớm hay muộn bạn cũng phải trả tiền cho chúng. Chúng tôi thanh toán gần như ngay lập tức: chúng tôi làm việc vào cuối tuần, giải quyết hậu quả, viết khám nghiệm tử thi và bắt đầu chia máy chủ thành hai phần. Lỗi này rất ngu ngốc và lỗi liên quan đến nó không dễ tái tạo. Và vâng, thật đáng tiếc cho điều này, nhưng bằng cách này hay cách khác, vấp ngã và rên rỉ, PoC của tôi với các biến toàn cục vẫn được đưa vào sản xuất và hoạt động khá thành công trong khi chờ chuyển sang kiến ​​​​trúc “hai nút” mới. Đây là một bước quan trọng vì về mặt hình thức, mục tiêu đã đạt được - SSR đã học cách cung cấp một trang hoàn toàn sẵn sàng để sử dụng và giao diện người dùng trở nên êm dịu hơn nhiều.

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánhGiao diện Mobile Habr sau giai đoạn tái cấu trúc đầu tiên

Cuối cùng, kiến ​​trúc SSR-CSR của phiên bản di động dẫn đến bức tranh này:

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánhMạch SSR-CSR “hai nút”. API Node JS luôn sẵn sàng cho I/O không đồng bộ và không bị chức năng SSR chặn vì cái sau nằm trong một phiên bản riêng biệt. Chuỗi truy vấn số 3 là không cần thiết.

Loại bỏ các yêu cầu trùng lặp

Sau khi thực hiện các thao tác, kết xuất ban đầu của trang không còn gây ra chứng động kinh nữa. Nhưng việc tiếp tục sử dụng Habr ở chế độ SPA vẫn gây ra sự nhầm lẫn.

Vì cơ sở của luồng người dùng là sự chuyển đổi của biểu mẫu danh sách bài viết → bài viết → bình luận và ngược lại, điều quan trọng là phải tối ưu hóa việc tiêu thụ tài nguyên của chuỗi này ngay từ đầu.

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánhViệc quay lại nguồn cấp dữ liệu bài đăng sẽ kích hoạt một yêu cầu dữ liệu mới

Không cần phải đào sâu. Trong ảnh chụp màn hình ở trên, bạn có thể thấy ứng dụng yêu cầu lại danh sách các bài viết khi vuốt ngược lại và trong quá trình yêu cầu chúng ta không nhìn thấy các bài viết, điều đó có nghĩa là dữ liệu trước đó sẽ biến mất ở đâu đó. Có vẻ như thành phần danh sách bài viết sử dụng trạng thái cục bộ và mất trạng thái đó khi hủy. Trên thực tế, ứng dụng đã sử dụng trạng thái toàn cục, nhưng kiến ​​trúc Vuex được xây dựng trực tiếp: các mô-đun được gắn với các trang, sau đó các trang này được gắn với các tuyến đường. Hơn nữa, tất cả các mô-đun đều là "dùng một lần" - mỗi lần truy cập trang tiếp theo sẽ viết lại toàn bộ mô-đun:

ArticlesList: [
  { Article1 },
  ...
],
PageArticle: { ArticleFull1 },

Tổng cộng, chúng tôi đã có một mô-đun Danh sách bài viết, chứa các đối tượng thuộc loại Bài báo và mô-đun TrangBài viết, đây là phiên bản mở rộng của đối tượng Bài báo, loại Bài viếtĐầy đủ. Nhìn chung, việc triển khai này không có gì khủng khiếp - nó rất đơn giản, thậm chí có thể nói là ngây thơ, nhưng cực kỳ dễ hiểu. Nếu bạn đặt lại mô-đun mỗi khi thay đổi tuyến đường, thì bạn thậm chí có thể chấp nhận nó. Tuy nhiên, việc di chuyển giữa các nguồn cấp dữ liệu bài viết chẳng hạn /nguồn cấp dữ liệu → /tất cả, được đảm bảo sẽ vứt bỏ mọi thứ liên quan đến nguồn cấp dữ liệu cá nhân, vì chúng tôi chỉ có một Danh sách bài viết, vào đó bạn cần đưa dữ liệu mới vào. Điều này một lần nữa dẫn chúng ta đến việc trùng lặp các yêu cầu.

Sau khi thu thập mọi thứ mà tôi có thể tìm hiểu về chủ đề này, tôi đã xây dựng một cấu trúc trạng thái mới và trình bày nó với các đồng nghiệp của mình. Các cuộc thảo luận kéo dài nhưng cuối cùng những lập luận ủng hộ đã lấn át những nghi ngờ và tôi bắt đầu thực hiện.

Logic của một giải pháp được thể hiện tốt nhất qua hai bước. Đầu tiên, chúng tôi cố gắng tách mô-đun Vuex khỏi các trang và liên kết trực tiếp với các tuyến đường. Có, sẽ có thêm một chút dữ liệu trong cửa hàng, getters sẽ phức tạp hơn một chút, nhưng chúng tôi sẽ không tải bài viết hai lần. Đối với phiên bản di động, đây có lẽ là lập luận mạnh mẽ nhất. Nó sẽ trông giống như thế này:

ArticlesList: {
  ROUTE_FEED: [ 
    { Article1 },
    ...
  ],
  ROUTE_ALL: [ 
    { Article2 },
    ...
  ],
}

Nhưng điều gì sẽ xảy ra nếu danh sách bài viết có thể chồng chéo giữa nhiều tuyến đường và nếu chúng ta muốn sử dụng lại dữ liệu đối tượng thì sao? Bài báo để hiển thị trang bài đăng, biến nó thành Bài viếtĐầy đủ? Trong trường hợp này, sẽ hợp lý hơn nếu sử dụng cấu trúc như vậy:

ArticlesIds: {
  ROUTE_FEED: [ '1', ... ],
  ROUTE_ALL: [ '1', '2', ... ],
},
ArticlesList: {
  '1': { Article1 }, 
  '2': { Article2 },
  ...
}

Danh sách bài viết ở đây nó chỉ là một loại kho lưu trữ các bài viết. Tất cả các bài viết đã được tải xuống trong phiên người dùng. Chúng tôi xử lý chúng một cách hết sức cẩn thận, vì đây là lưu lượng truy cập có thể đã được tải xuống do sự cố ở đâu đó trong tàu điện ngầm giữa các ga và chúng tôi chắc chắn không muốn gây ra nỗi đau này cho người dùng một lần nữa bằng cách buộc anh ta tải dữ liệu mà anh ta đã tải xuống. đã tải xuống. Một đối tượng Id bài viết chỉ đơn giản là một mảng ID (như thể “liên kết”) tới các đối tượng Bài báo. Cấu trúc này cho phép bạn tránh trùng lặp dữ liệu chung cho các tuyến đường và sử dụng lại đối tượng Bài báo khi hiển thị một trang bài đăng bằng cách hợp nhất dữ liệu mở rộng vào đó.

Đầu ra của danh sách các bài viết cũng trở nên minh bạch hơn: thành phần iterator lặp qua mảng với các ID bài viết và rút ra thành phần giới thiệu bài viết, chuyển Id làm chỗ dựa và thành phần con lần lượt lấy dữ liệu cần thiết từ Danh sách bài viết. Khi bạn truy cập trang xuất bản, chúng tôi nhận được ngày hiện có từ Danh sách bài viết, chúng tôi đưa ra yêu cầu lấy dữ liệu còn thiếu và chỉ cần thêm dữ liệu đó vào đối tượng hiện có.

Tại sao cách tiếp cận này tốt hơn? Như tôi đã viết ở trên, cách tiếp cận này nhẹ nhàng hơn đối với dữ liệu đã tải xuống và cho phép bạn sử dụng lại dữ liệu đó. Nhưng bên cạnh đó, nó mở đường cho một số khả năng mới hoàn toàn phù hợp với một kiến ​​trúc như vậy. Ví dụ: bỏ phiếu và tải các bài viết vào nguồn cấp dữ liệu khi chúng xuất hiện. Chúng ta có thể chỉ cần đặt các bài đăng mới nhất vào “bộ lưu trữ” Danh sách bài viết, hãy lưu một danh sách ID mới riêng biệt vào Id bài viết và thông báo cho người dùng về nó. Khi nhấp vào nút “Hiển thị ấn phẩm mới”, chúng tôi sẽ chỉ cần chèn Id mới vào đầu mảng danh sách bài viết hiện tại và mọi thứ sẽ hoạt động gần như kỳ diệu.

Làm cho việc tải xuống thú vị hơn

Điều quan trọng nhất trong quá trình tái cấu trúc là khái niệm về bộ xương, giúp quá trình tải xuống nội dung trên Internet chậm bớt khó chịu hơn một chút. Không có cuộc thảo luận nào về vấn đề này, con đường từ ý tưởng đến nguyên mẫu mất khoảng hai giờ. Thiết kế thực tế đã tự vẽ ra và chúng tôi đã hướng dẫn các thành phần của mình hiển thị các khối div đơn giản, hầu như không nhấp nháy trong khi chờ dữ liệu. Về mặt chủ quan, cách tiếp cận tải này thực sự làm giảm lượng hormone gây căng thẳng trong cơ thể người dùng. Bộ xương trông như thế này:

Nhật ký nhà phát triển giao diện người dùng Habr: tái cấu trúc và phản ánh
Đang tải Habra

Phản ánh

Tôi đã làm việc ở Habré được sáu tháng và bạn bè tôi vẫn hỏi: bạn thấy ở đó thế nào? Được rồi, thoải mái - vâng. Nhưng có điều gì đó khiến công việc này khác biệt với những công việc khác. Tôi làm việc trong những nhóm hoàn toàn thờ ơ với sản phẩm của họ, không biết hoặc hiểu người dùng của họ là ai. Nhưng ở đây mọi thứ đều khác. Ở đây bạn cảm thấy có trách nhiệm với những gì bạn làm. Trong quá trình phát triển một tính năng, bạn một phần trở thành chủ sở hữu của nó, tham gia vào tất cả các cuộc họp sản phẩm liên quan đến chức năng của bạn, đưa ra đề xuất và tự mình đưa ra quyết định. Việc tự mình tạo ra một sản phẩm mà bạn sử dụng hàng ngày là một điều rất thú vị, nhưng viết mã cho những người có thể giỏi về nó hơn bạn chỉ là một cảm giác khó tin (không hề mỉa mai).

Sau khi phát hành tất cả những thay đổi này, chúng tôi đã nhận được phản hồi tích cực và nó rất rất tuyệt. Nó đầy cảm hứng. Cảm ơn! Viết nhiều hơn.

Hãy để tôi nhắc bạn rằng sau các biến toàn cục, chúng tôi đã quyết định thay đổi kiến ​​trúc và phân bổ lớp proxy thành một phiên bản riêng biệt. Kiến trúc “hai nút” đã được phát hành dưới dạng thử nghiệm beta công khai. Giờ đây, bất kỳ ai cũng có thể chuyển sang sử dụng nó và giúp chúng tôi cải thiện Habr dành cho thiết bị di động. Đó là tất cả cho ngày hôm nay. Tôi sẽ sẵn lòng trả lời tất cả các câu hỏi của bạn trong phần bình luận.

Nguồn: www.habr.com

Thêm một lời nhận xét