Ứng dụng trang đơn (SPA) có thể cung cấp trải nghiệm người dùng tương tác, phong phú khi xử lý dữ liệu động theo thời gian thực. Nhưng chúng cũng có thể nặng, cồng kềnh và hoạt động kém. Trong bài viết này, chúng tôi sẽ hướng dẫn một số mẹo tối ưu hóa giao diện người dùng để giữ cho ứng dụng Vue của chúng tôi tương đối gọn nhẹ và chỉ cung cấp JS khi cần thiết.
Lưu ý: Giả sử bạn đã quen thuộc với Vue và API Composition, nhưng hy vọng sẽ có một số thông tin hữu ích bất kể bạn chọn khuôn khổ nào.
Là một nhà phát triển giao diện người dùng tại Ada Mode, công việc của tôi liên quan đến việc xây dựng Windscope, một ứng dụng web dành cho các nhà điều hành trang trại gió để quản lý và bảo trì đội tua-bin của họ. Do nhu cầu tiếp nhận dữ liệu theo thời gian thực và mức độ tương tác cao được yêu cầu, nên kiến trúc SPA đã được chọn cho dự án. Ứng dụng web của chúng tôi phụ thuộc vào một số thư viện JS nặng, nhưng chúng tôi muốn cung cấp trải nghiệm tốt nhất cho người dùng cuối bằng cách truy xuất dữ liệu và hiển thị nhanh chóng và hiệu quả nhất có thể.
Để minh họa cho các tối ưu hóa khác nhau, tôi đã xây dựng một ứng dụng Vue đơn giản để lấy dữ liệu từ API và hiển thị một số biểu đồ bằng D3.js.

Lưu ý: Vui lòng tham khảo kho lưu trữ GitHub mẫu để biết mã đầy đủ.
Chúng tôi đang sử dụng Parcel, một công cụ xây dựng cấu hình tối thiểu, để đóng gói ứng dụng của chúng tôi, nhưng tất cả các tối ưu hóa mà chúng tôi sẽ đề cập ở đây đều có thể áp dụng cho bất kỳ trình đóng gói nào bạn chọn.
Ngoài thu nhỏ, Parcel cũng sử dụng nâng phạm vi như một phần của quy trình sản xuất, có thể giúp thu nhỏ hiệu quả hơn nữa. Hướng dẫn chuyên sâu về nâng phạm vi nằm ngoài phạm vi (bạn thấy tôi đã làm gì ở đó không?) của bài viết này. Tuy nhiên, nếu chúng ta chạy quy trình xây dựng của Parcel trên ứng dụng ví dụ của mình với các cờ
Nhưng công việc không kết thúc ở đây. Ngay cả khi chúng ta đang vận chuyển một gói nhỏ hơn nói chung, trình duyệt vẫn cần thời gian để phân tích cú pháp và biên dịch JS của chúng ta, điều này có thể góp phần làm chậm trải nghiệm người dùng. Bài viết này về Tối ưu hóa kích thước gói của Calibre giải thích cách các gói JS lớn ảnh hưởng đến số liệu hiệu suất.
Hãy xem chúng ta có thể làm gì khác để giảm lượng công việc mà trình duyệt phải thực hiện.
Lưu ý: Bạn vẫn có thể sử dụng API Composition nếu đang sử dụng phiên bản Vue cũ hơn: API này đã được đưa ngược về Vue 2.7 và có một plugin chính thức dành cho các phiên bản cũ hơn.
Một trong những cách dễ nhất để giữ kích thước gói của chúng ta ở mức nhỏ nhất có thể là chỉ nhập các mô-đun chúng ta cần.
Hãy lấy hàm
Thay vì:
Chúng ta có thể nhập mô-đun khi chúng ta muốn sử dụng nó:
Điều này có nghĩa là mô-đun sẽ được chia thành một gói JS riêng biệt (hoặc "chunk"), chỉ được trình duyệt tải xuống nếu và khi cần. Ngoài ra, trình duyệt có thể lưu trữ đệm các phụ thuộc này, có thể thay đổi ít thường xuyên hơn so với mã cho phần còn lại của ứng dụng của chúng tôi.
Trong tệp
Mã cho tuyến đường ‘About’ sẽ chỉ được tải khi người dùng nhấp vào liên kết ‘About’ và điều hướng đến tuyến đường.
Điều này có nghĩa là mã cho thành phần KPI sẽ được nhập động, như chúng ta đã thấy trong ví dụ về bộ định tuyến. Chúng ta cũng có thể cung cấp một số thành phần để hiển thị khi nó đang ở trạng thái tải hoặc lỗi (hữu ích nếu chúng ta đang tải một tệp đặc biệt lớn).
Chúng tôi đã quyết định chia API thành nhiều điểm cuối và thực hiện một yêu cầu cho mỗi tiện ích. Mặc dù điều này có thể làm tăng thời gian phản hồi tổng thể, nhưng điều đó có nghĩa là ứng dụng sẽ có thể sử dụng được nhanh hơn nhiều vì người dùng sẽ thấy các phần của trang được hiển thị trong khi họ vẫn đang chờ các phần khác. Ngoài ra, bất kỳ lỗi nào có thể xảy ra sẽ được bản địa hóa trong khi phần còn lại của trang vẫn có thể sử dụng được.
Bạn có thể thấy sự khác biệt được minh họa ở đây:
[*] Trong ví dụ bên phải, người dùng có thể tương tác với một số thành phần trong khi những thành phần khác vẫn đang yêu cầu dữ liệu. Trang bên trái phải chờ phản hồi dữ liệu lớn trước khi có thể hiển thị và trở nên tương tác.
Để xử lý quy trình này cho mỗi thành phần, chúng tôi tạo ra một thành phần bậc cao hơn có tên là
Mẫu này có thể được mở rộng đến bất kỳ vị trí nào trong ứng dụng mà thành phần được hiển thị khi người dùng tương tác. Ví dụ, trong Windscope, chúng tôi chỉ tải thành phần bản đồ (và các thành phần phụ thuộc của nó) khi người dùng nhấp vào tab 'Bản đồ'. Điều này được gọi là Nhập khi tương tác.
Một cách nhanh chóng để làm cho mọi thứ trở nên mượt mà hơn đối với người dùng là đặt tỷ lệ khung hình trên tiện ích tương ứng với thành phần được hiển thị để người dùng không thấy sự thay đổi bố cục quá lớn. Chúng ta có thể truyền vào một prop cho việc này để tính đến các thành phần khác nhau, với giá trị mặc định để quay lại.
Chúng ta có thể sử dụng giao diện AbortController, cho phép chúng ta hủy bỏ các yêu cầu API theo ý muốn.
Trong hàm
Sau đó, chúng ta hủy yêu cầu trước khi thành phần được hủy gắn kết, bằng cách sử dụng hàm
Nếu bạn chạy dự án và điều hướng đến một trang khác trước khi các yêu cầu hoàn tất, bạn sẽ thấy các lỗi được ghi lại trong bảng điều khiển nêu rằng các yêu cầu đã bị hủy.
Stale-while-revalidate là một chiến lược vô hiệu hóa bộ đệm HTTP trong đó trình duyệt xác định có nên phục vụ phản hồi từ bộ đệm nếu nội dung đó vẫn còn mới hay "xác thực lại" và phục vụ từ mạng nếu phản hồi đã cũ.
Ngoài việc áp dụng tiêu đề cache-control cho phản hồi HTTP của chúng ta (nằm ngoài phạm vi của bài viết này, nhưng hãy đọc bài viết này từ Web.dev để biết thêm chi tiết), chúng ta có thể áp dụng một chiến lược tương tự cho trạng thái thành phần Vue của mình, bằng cách sử dụng thư viện SWRV.
Đầu tiên, chúng ta phải nhập composable từ thư viện SWRV:
Sau đó, chúng ta có thể sử dụng nó trong hàm
Chúng ta sẽ truyền phần này vào lệnh gọi hàm
Sau đó, chúng ta sẽ xóa các tùy chọn
Điều này có nghĩa là chúng ta sẽ cần đưa các thành phần
Chúng ta có thể cấu trúc lại thành phần có thể cấu hình riêng, giúp mã của chúng ta gọn gàng hơn một chút và cho phép chúng ta sử dụng ở bất kỳ đâu.
SPA có thể hoạt động tốt, nhưng chúng cũng có thể là nút thắt cổ chai về hiệu suất. Vì vậy, hãy cố gắng xây dựng chúng tốt hơn.
Lưu ý: Giả sử bạn đã quen thuộc với Vue và API Composition, nhưng hy vọng sẽ có một số thông tin hữu ích bất kể bạn chọn khuôn khổ nào.
Là một nhà phát triển giao diện người dùng tại Ada Mode, công việc của tôi liên quan đến việc xây dựng Windscope, một ứng dụng web dành cho các nhà điều hành trang trại gió để quản lý và bảo trì đội tua-bin của họ. Do nhu cầu tiếp nhận dữ liệu theo thời gian thực và mức độ tương tác cao được yêu cầu, nên kiến trúc SPA đã được chọn cho dự án. Ứng dụng web của chúng tôi phụ thuộc vào một số thư viện JS nặng, nhưng chúng tôi muốn cung cấp trải nghiệm tốt nhất cho người dùng cuối bằng cách truy xuất dữ liệu và hiển thị nhanh chóng và hiệu quả nhất có thể.
Chọn một Framework
Framework JS mà chúng tôi lựa chọn là Vue, một phần được chọn vì đây là framework mà tôi quen thuộc nhất. Trước đây, Vue có kích thước gói tổng thể nhỏ hơn so với React. Tuy nhiên, kể từ các bản cập nhật React gần đây, cán cân dường như đã chuyển sang có lợi cho React. Điều đó không nhất thiết quan trọng, vì chúng ta sẽ xem xét cách chỉ nhập những gì chúng ta cần trong quá trình viết bài viết này. Cả hai framework đều có tài liệu tuyệt vời và hệ sinh thái nhà phát triển lớn, đây là một cân nhắc khác. Svelte là một lựa chọn khả thi khác, nhưng nó đòi hỏi đường cong học tập dốc hơn do chưa quen thuộc và vì mới hơn nên nó có hệ sinh thái kém phát triển hơn.Để minh họa cho các tối ưu hóa khác nhau, tôi đã xây dựng một ứng dụng Vue đơn giản để lấy dữ liệu từ API và hiển thị một số biểu đồ bằng D3.js.

Lưu ý: Vui lòng tham khảo kho lưu trữ GitHub mẫu để biết mã đầy đủ.
Chúng tôi đang sử dụng Parcel, một công cụ xây dựng cấu hình tối thiểu, để đóng gói ứng dụng của chúng tôi, nhưng tất cả các tối ưu hóa mà chúng tôi sẽ đề cập ở đây đều có thể áp dụng cho bất kỳ trình đóng gói nào bạn chọn.
Rung cây, nén và thu nhỏ bằng công cụ xây dựng
Thực hành tốt là chỉ gửi mã bạn cần và ngay khi cài đặt, Parcel sẽ xóa mã Javascript không sử dụng trong quá trình xây dựng (rung cây). Nó cũng thu nhỏ kết quả và có thể được định cấu hình để nén đầu ra bằng Gzip hoặc Brotli.Ngoài thu nhỏ, Parcel cũng sử dụng nâng phạm vi như một phần của quy trình sản xuất, có thể giúp thu nhỏ hiệu quả hơn nữa. Hướng dẫn chuyên sâu về nâng phạm vi nằm ngoài phạm vi (bạn thấy tôi đã làm gì ở đó không?) của bài viết này. Tuy nhiên, nếu chúng ta chạy quy trình xây dựng của Parcel trên ứng dụng ví dụ của mình với các cờ
--no-optimize
và --no-scope-hoist
, chúng ta có thể thấy gói kết quả là 510kB — cao hơn khoảng 5 lần so với phiên bản được tối ưu hóa và thu nhỏ. Vì vậy, bất kể bạn đang sử dụng bundler nào, có thể nói rằng bạn có thể muốn đảm bảo rằng nó đang thực hiện càng nhiều tối ưu hóa càng tốt.Nhưng công việc không kết thúc ở đây. Ngay cả khi chúng ta đang vận chuyển một gói nhỏ hơn nói chung, trình duyệt vẫn cần thời gian để phân tích cú pháp và biên dịch JS của chúng ta, điều này có thể góp phần làm chậm trải nghiệm người dùng. Bài viết này về Tối ưu hóa kích thước gói của Calibre giải thích cách các gói JS lớn ảnh hưởng đến số liệu hiệu suất.
Hãy xem chúng ta có thể làm gì khác để giảm lượng công việc mà trình duyệt phải thực hiện.
Vue Composition API
Vue 3 đã giới thiệu Composition API, một bộ API mới để tạo thành phần thay thế cho Options API. Bằng cách chỉ sử dụng Composition API, chúng ta chỉ có thể nhập các hàm Vue mà chúng ta cần thay vì toàn bộ gói. Nó cũng cho phép chúng ta viết nhiều mã có thể tái sử dụng hơn bằng cách sử dụng composable. Mã được viết bằng API Composition thích hợp hơn với việc thu nhỏ và toàn bộ ứng dụng dễ bị rung cây hơn.Lưu ý: Bạn vẫn có thể sử dụng API Composition nếu đang sử dụng phiên bản Vue cũ hơn: API này đã được đưa ngược về Vue 2.7 và có một plugin chính thức dành cho các phiên bản cũ hơn.
Nhập phụ thuộc
Một mục tiêu chính là giảm kích thước của gói JS ban đầu do máy khách tải xuống. Windscope sử dụng rộng rãi D3 để trực quan hóa dữ liệu, một thư viện lớn và có phạm vi rộng. Tuy nhiên, Windscope chỉ cần một phần của nó (có toàn bộ các mô-đun trong thư viện D3 mà chúng ta không cần đến). Nếu chúng ta kiểm tra toàn bộ gói D3 trên Bundlephobia, chúng ta có thể thấy rằng ứng dụng của chúng ta sử dụng ít hơn một nửa số mô-đun khả dụng và thậm chí có thể không phải tất cả các chức năng trong các mô-đun đó.Một trong những cách dễ nhất để giữ kích thước gói của chúng ta ở mức nhỏ nhất có thể là chỉ nhập các mô-đun chúng ta cần.
Hãy lấy hàm
selectAll
của D3. Thay vì sử dụng lệnh import mặc định, chúng ta chỉ cần import hàm cần thiết từ module d3-selection
:
Mã:
// Trước đó:import * as d3 from 'd3'// Thay vào đó:import { selectAll } from 'd3-selection'
Phân tách mã bằng lệnh import động
Có một số gói được sử dụng ở nhiều nơi trong Windscope, chẳng hạn như thư viện xác thực AWS Amplify, cụ thể là phương thứcAuth
. Đây là một phụ thuộc lớn góp phần đáng kể vào kích thước gói JS của chúng tôi. Thay vì nhập mô-đun tĩnh ở đầu tệp, nhập động cho phép chúng ta nhập mô-đun chính xác vào nơi chúng ta cần trong mã của mình.Thay vì:
Mã:
import { Auth } from '@aws-amplify/auth'const user = Auth.currentAuthenticatedUser()
Mã:
import('@aws-amplify/auth').then(({ Auth }) => { const user = Auth.currentAuthenticatedUser()})
Tải chậm các tuyến đường bằng Vue Router
Ứng dụng của chúng tôi sử dụng Vue Router để điều hướng. Tương tự như nhập động, chúng tôi có thể tải chậm các thành phần tuyến đường của mình, do đó chúng sẽ chỉ được nhập (cùng với các phụ thuộc liên quan) khi người dùng điều hướng đến tuyến đường đó.Trong tệp
index/router.js
của chúng tôi:
Mã:
// Trước đó:import Home from "../routes/Home.vue";import About = "../routes/About.vue";// Tải chậm các thành phần tuyến đường thay thế:const Home = () => import("../routes/Home.vue");const About = () => import("../routes/About.vue");const routes = [ { name: "home", path: "/", component: Home, }, { name: "about", path: "/about", component: About, },];
Thành phần bất đồng bộ
Ngoài việc tải chậm từng tuyến đường, chúng ta cũng có thể tải chậm từng thành phần bằng phương thứcdefineAsyncComponent
của Vue.
Mã:
const KPIComponent = defineAsyncComponent(() => import('../components/KPI.vue))
Mã:
const KPIComponent = defineAsyncComponent({ loader: () => import('../components/KPI.vue), loadingComponent: Loader, errorComponent: Error, delay: 200, timeout: 5000,});
Chia tách các yêu cầu API
Ứng dụng của chúng tôi chủ yếu liên quan đến trực quan hóa dữ liệu và phụ thuộc nhiều vào việc lấy lượng lớn dữ liệu từ máy chủ. Một số yêu cầu này có thể khá chậm vì máy chủ phải thực hiện một số phép tính trên dữ liệu. Trong nguyên mẫu ban đầu, chúng tôi đã thực hiện một yêu cầu duy nhất tới REST API cho mỗi tuyến đường. Thật không may, chúng tôi thấy rằng điều này khiến người dùng phải đợi rất lâu — đôi khi lên đến 10 giây, xem trình quay vòng tải trước khi ứng dụng nhận được dữ liệu thành công và có thể bắt đầu hiển thị trực quan hóa.Chúng tôi đã quyết định chia API thành nhiều điểm cuối và thực hiện một yêu cầu cho mỗi tiện ích. Mặc dù điều này có thể làm tăng thời gian phản hồi tổng thể, nhưng điều đó có nghĩa là ứng dụng sẽ có thể sử dụng được nhanh hơn nhiều vì người dùng sẽ thấy các phần của trang được hiển thị trong khi họ vẫn đang chờ các phần khác. Ngoài ra, bất kỳ lỗi nào có thể xảy ra sẽ được bản địa hóa trong khi phần còn lại của trang vẫn có thể sử dụng được.
Bạn có thể thấy sự khác biệt được minh họa ở đây:
[*] Trong ví dụ bên phải, người dùng có thể tương tác với một số thành phần trong khi những thành phần khác vẫn đang yêu cầu dữ liệu. Trang bên trái phải chờ phản hồi dữ liệu lớn trước khi có thể hiển thị và trở nên tương tác.
Tải thành phần có điều kiện
Bây giờ chúng ta có thể kết hợp điều này với các thành phần bất đồng bộ để chỉ tải thành phần khi chúng ta nhận được phản hồi thành công từ máy chủ. Ở đây chúng ta đang truy xuất dữ liệu, sau đó nhập thành phần khi hàmfetch
của chúng ta trả về thành công:
Mã:
import { defineComponent, ref, defineAsyncComponent,} from "vue";nhập Loader từ "./Loader";nhập Lỗi từ "./Error";export default defineComponent({ components: { Loader, Error }, setup() { const data = ref(null); const loadComponent = () => { return fetch('https://api.npoint.io/ec46e59905dc0011b7f4') .then((response) => response.json()) .then((response) => (data.value = response)) .then(() => import("../components/KPI.vue") // Nhập thành phần .catch((e) => console.error(e)); }; const KPIComponent = defineAsyncComponent({ loader: loadComponent, loadingComponent: Loader, errorComponent: Error, delay: 200, timeout: 5000, }); return { data, KPIComponent }; }}
WidgetLoader
, bạn có thể thấy trong kho lưu trữ.Mẫu này có thể được mở rộng đến bất kỳ vị trí nào trong ứng dụng mà thành phần được hiển thị khi người dùng tương tác. Ví dụ, trong Windscope, chúng tôi chỉ tải thành phần bản đồ (và các thành phần phụ thuộc của nó) khi người dùng nhấp vào tab 'Bản đồ'. Điều này được gọi là Nhập khi tương tác.
CSS
Nếu bạn chạy mã ví dụ, bạn sẽ thấy rằng nhấp vào liên kết điều hướng 'Vị trí' sẽ tải thành phần bản đồ. Cũng như việc nhập động mô-đun JS, việc nhập phụ thuộc trong khối
của thành phần cũng sẽ tải chậm CSS:
Mã:
// Trong MapView.vue@import "../../node_modules/leaflet/dist/leaflet.css";.map-wrapper { aspect-ratio: 16 / 9;}
Tinh chỉnh trạng thái tải
Lúc này, chúng ta có các yêu cầu API chạy song song, với các thành phần được hiển thị tại các thời điểm khác nhau. Một điều chúng ta có thể nhận thấy là trang có vẻ không ổn định, vì bố cục sẽ bị dịch chuyển khá nhiều.Một cách nhanh chóng để làm cho mọi thứ trở nên mượt mà hơn đối với người dùng là đặt tỷ lệ khung hình trên tiện ích tương ứng với thành phần được hiển thị để người dùng không thấy sự thay đổi bố cục quá lớn. Chúng ta có thể truyền vào một prop cho việc này để tính đến các thành phần khác nhau, với giá trị mặc định để quay lại.
Mã:
// WidgetLoader.vue import { defineComponent, ref, onBeforeMount, onBeforeUnmount } from "vue";import Loader from "./Loader";import Error from "./Error";export default defineComponent({ components: { Loader, Error }, props: { aspectRatio: { type: String, default: "5 / 3", // define a default value }, url: String, importFunction: Function, }, setup(props) { const data = ref(null); const loading = ref(true); const loadComponent = () => { return fetch(url) .then((response) => response.json()) .then((response) => (data.value = response)) .then(importFunction .catch((e) => console.error(e)) .finally(() => (loading.value = false)); // Đặt trạng thái tải thành false }; /* ...Phần còn lại của mã thành phần */ return { data, aspectRatio, loading }; },});
Hủy bỏ các yêu cầu API
Trên một trang có nhiều yêu cầu API, điều gì sẽ xảy ra nếu người dùng điều hướng đi trước khi tất cả các yêu cầu được hoàn tất? Có lẽ chúng ta không muốn những yêu cầu đó tiếp tục chạy ở chế độ nền, làm chậm trải nghiệm của người dùng.Chúng ta có thể sử dụng giao diện AbortController, cho phép chúng ta hủy bỏ các yêu cầu API theo ý muốn.
Trong hàm
setup
, chúng ta tạo một bộ điều khiển mới và truyền tín hiệu của nó vào các tham số yêu cầu tìm nạp của chúng ta:
Mã:
setup(props) { const controller = new AbortController(); const loadComponent = () => { trả về fetch(url, { tín hiệu: controller.signal }) .then((phản hồi) => response.json()) .then((phản hồi) => (dữ liệu.giá trị = phản hồi)) .then(importFunction) .catch((e) => console.error(e)) .finally(() => (loading.giá trị = false)); };}
onBeforeUnmount
của Vue:
Mã:
onBeforeUnmount(() => controller.abort());
Stale While Revalidate
Cho đến nay, chúng ta đã tối ưu hóa ứng dụng của mình khá tốt. Nhưng khi người dùng điều hướng đến chế độ xem thứ hai rồi quay lại chế độ xem trước đó, tất cả các thành phần sẽ được gắn lại và trở về trạng thái đang tải của chúng, và chúng ta phải chờ phản hồi yêu cầu một lần nữa.Stale-while-revalidate là một chiến lược vô hiệu hóa bộ đệm HTTP trong đó trình duyệt xác định có nên phục vụ phản hồi từ bộ đệm nếu nội dung đó vẫn còn mới hay "xác thực lại" và phục vụ từ mạng nếu phản hồi đã cũ.
Ngoài việc áp dụng tiêu đề cache-control cho phản hồi HTTP của chúng ta (nằm ngoài phạm vi của bài viết này, nhưng hãy đọc bài viết này từ Web.dev để biết thêm chi tiết), chúng ta có thể áp dụng một chiến lược tương tự cho trạng thái thành phần Vue của mình, bằng cách sử dụng thư viện SWRV.
Đầu tiên, chúng ta phải nhập composable từ thư viện SWRV:
Mã:
import useSWRV from "swrv";
setup
của mình. Chúng ta sẽ đổi tên hàm loadComponent
thành fetchData
, vì nó sẽ chỉ xử lý việc truy xuất dữ liệu. Chúng ta sẽ không còn nhập thành phần của mình vào hàm này nữa, vì chúng ta sẽ xử lý riêng phần đó.Chúng ta sẽ truyền phần này vào lệnh gọi hàm
useSWRV
làm đối số thứ hai. Chúng ta chỉ cần thực hiện điều này nếu cần một hàm tùy chỉnh để truy xuất dữ liệu (có thể chúng ta cần cập nhật một số phần trạng thái khác). Vì chúng ta đang sử dụng Abort Controller, chúng ta sẽ thực hiện điều này; nếu không, đối số thứ hai có thể bị bỏ qua và SWRV sẽ sử dụng Fetch API:
Mã:
// Trong setup()const { url, importFunction } = props;const controller = new AbortController();const fetchData = () => { return fetch(url, { signal: controller.signal }) .then((response) => response.json()) .then((response) => (data.value = response)) .catch((e) => (error.value = e));};const { data, isValidating, error } = useSWRV(url, fetchData);
loadingComponent
và errorComponent
khỏi định nghĩa thành phần bất đồng bộ của mình, vì chúng ta sẽ sử dụng SWRV để xử lý lỗi và trạng thái tải.
Mã:
// Trong setup()const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000,});
Loader
và Error
vào mẫu của mình và hiển thị hoặc ẩn chúng tùy thuộc vào trạng thái. Giá trị trả về isValidating
cho chúng ta biết liệu có yêu cầu hoặc xác thực lại đang diễn ra hay không.
Mã:
import { defineComponent, defineAsyncComponent,} từ "vue";import useSWRV từ "swrv";xuất mặc định defineComponent({ components: { Error, Loader, }, props: { url: String, importFunction: Function, }, setup(props) { const { url, importFunction } = props; const controller = new AbortController(); const fetchData = () => { return fetch(url, { signal: controller.signal }) .then((response) => response.json()) .then((response) => (data.value = response)) .catch((e) => (error.value = e)); }; const { data, isValidating, error } = useSWRV(url, fetchData); const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000, }); onBeforeUnmount(() => controller.abort()); return { AsyncComponent, isValidating, data, error, }; },});
Mã:
// composables/lazyFetch.jsimport { onBeforeUnmount } from "vue";import useSWRV from "swrv";export function useLazyFetch(url) { const controller = new AbortController(); const fetchData = () => { return fetch(url, { signal: controller.signal }) .then((response) => response.json()) .then((response) => (data.value = response)) .catch((e) => (error.value = e)); }; const { data, isValidating, error } = useSWRV(url, fetchData); onBeforeUnmount(() => controller.abort()); return { isValidating, data, error, };}
Mã:
// WidgetLoader.vueimport { defineComponent, defineAsyncComponent, computed } from "vue";import Loader from "./Loader";import Error from "./Error";import { useLazyFetch } from "../composables/lazyFetch";export default defineComponent({ components: { Error, Loader, }, props: { aspectRatio: { type: String, default: "5 / 3", }, url: String, importFunction: Function, }, setup(props) { const { aspectRatio, url, importFunction } = props; const { data, isValidating, error } = useLazyFetch(url); const AsyncComponent = defineAsyncComponent({ loader: importFunction, delay: 200, timeout: 5000, }); return { aspectRatio, AsyncComponent, isValidating, data, error, }; },});
Chỉ báo cập nhật
Có thể hữu ích nếu chúng ta có thể hiển thị chỉ báo cho người dùng trong khi yêu cầu của chúng ta đang xác thực lại để họ biết ứng dụng đang kiểm tra dữ liệu mới. Trong ví dụ, tôi đã thêm một chỉ báo tải nhỏ ở góc của thành phần, chỉ hiển thị nếu đã có dữ liệu nhưng thành phần đang kiểm tra các bản cập nhật. Tôi cũng đã thêm một chuyển tiếp mờ dần đơn giản trên thành phần (sử dụng thành phầnTransition
tích hợp của Vue), do đó không có sự nhảy đột ngột như vậy khi thành phần được hiển thị.
Mã:
[TR]
Kết luận
Việc ưu tiên hiệu suất khi xây dựng ứng dụng web của chúng tôi sẽ cải thiện trải nghiệm của người dùng và giúp đảm bảo rằng chúng có thể được nhiều người sử dụng nhất có thể. Chúng tôi đã sử dụng thành công các kỹ thuật trên tại Ada Mode để làm cho ứng dụng của mình nhanh hơn. Tôi hy vọng bài viết này đã cung cấp một số gợi ý về cách làm cho ứng dụng của bạn hiệu quả nhất có thể — cho dù bạn chọn triển khai chúng toàn bộ hay một phần.SPA có thể hoạt động tốt, nhưng chúng cũng có thể là nút thắt cổ chai về hiệu suất. Vì vậy, hãy cố gắng xây dựng chúng tốt hơn.
Đọc thêm về Smashing Magazine
- Tái cấu trúc CSS (Phần 1–3)
- Năm mẫu tải dữ liệu để tăng hiệu suất web
- Công cụ thay đổi hiệu suất: Bộ đệm chuyển tiếp/quay lại của trình duyệt
- Giảm lượng khí thải carbon của web: Tối ưu hóa nhúng phương tiện truyền thông xã hội