網路上優良的使用者體驗與平庸的使用者體驗之間的差異,在於開發者如何藉由在網路密集操作期間提供視覺提示,來實現網路感知的使用者介面回饋。主要有三種待處理的 UI 類型:忙碌指示器、樂觀 UI 和骨架回溯。本文檔提供了根據特定情境選擇和實作適當的回饋機制的指南。
忙碌指示器:忙碌指示器在伺服器處理動作時,會向使用者顯示視覺提示。當應用程式無法預測動作的結果,且必須等待伺服器的回應才能更新 UI 時,會使用此回饋機制。
樂觀 UI:樂觀 UI 藉由在收到伺服器的回應之前,立即使用預期的狀態更新 UI,來增強感知速度和回應能力。當應用程式可以根據內容和使用者輸入來預測動作的結果時,會使用此方法,以便立即回應動作。
骨架回溯:骨架回溯用於 UI 初始載入時,為使用者提供視覺佔位符,概述即將出現的內容結構。此回饋機制在儘快呈現有用的內容時特別有用。
使用樂觀 UI
使用忙碌指示器
使用骨架回溯
忙碌指示器:您可以使用 useNavigation
指示使用者正在導覽至新頁面。
import { useNavigation } from "@remix-run/react";
function PendingNavigation() {
const navigation = useNavigation();
return navigation.state === "loading" ? (
<div className="spinner" />
) : null;
}
忙碌指示器:您可以使用 <NavLink className>
回呼,在導覽連結本身上指示使用者正在導覽至該連結。
import { NavLink } from "@remix-run/react";
export function ProjectList({ projects }) {
return (
<nav>
{projects.map((project) => (
<NavLink
key={project.id}
to={project.id}
className={({ isPending }) =>
isPending ? "pending" : null
}
>
{project.name}
</NavLink>
))}
</nav>
);
}
或者藉由檢查參數在旁邊新增一個旋轉符號
import { useParams } from "@remix-run/react";
export function ProjectList({ projects }) {
const params = useParams();
return (
<nav>
{projects.map((project) => (
<NavLink key={project.id} to={project.id}>
{project.name}
{params.projectId === project.id ? (
<Spinner />
) : null}
</NavLink>
))}
</nav>
);
}
雖然連結上的局部指示器很好,但它們並不完整。還有許多其他方式可以觸發導覽:表單提交、瀏覽器中的上一步和下一步按鈕點擊、動作重新導向和必要的 navigate(path)
呼叫,因此您通常需要一個全域指示器來捕捉所有內容。
忙碌指示器:由於在完成之前,ID 和其他欄位之類的東西都是未知的,因此通常最好等待建立記錄,而不是使用樂觀 UI。另請注意,此動作會從動作重新導向至新的記錄。
import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { redirect } from "@remix-run/node"; // or cloudflare/deno
import { useNavigation } from "@remix-run/react";
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData();
const project = await createRecord({
name: formData.get("name"),
owner: formData.get("owner"),
});
return redirect(`/projects/${project.id}`);
}
export default function CreateProject() {
const navigation = useNavigation();
// important to check you're submitting to the action
// for the pending UI, not just any action
const isSubmitting =
navigation.formAction === "/create-project";
return (
<Form method="post" action="/create-project">
<fieldset disabled={isSubmitting}>
<label>
Name: <input type="text" name="projectName" />
</label>
<label>
Owner: <UserSelect />
</label>
<button type="submit">Create</button>
</fieldset>
{isSubmitting ? <BusyIndicator /> : null}
</Form>
);
}
您可以使用 useFetcher
執行相同的操作,如果您沒有變更 URL(可能將記錄新增至清單),這會很有用。
import { useFetcher } from "@remix-run/react";
function CreateProject() {
const fetcher = useFetcher();
const isSubmitting = fetcher.state === "submitting";
return (
<fetcher.Form method="post" action="/create-project">
{/* ... */}
</fetcher.Form>
);
}
樂觀 UI:當 UI 僅更新記錄上的欄位時,樂觀 UI 是很好的選擇。如果不是大多數,那麼網頁應用程式中大多數的使用者互動往往是更新,因此這是一種常見的模式。
import { useFetcher } from "@remix-run/react";
function ProjectListItem({ project }) {
const fetcher = useFetcher();
const starred = fetcher.formData
? // use optimistic value if submitting
fetcher.formData.get("starred") === "1"
: // fall back to the database state
project.starred;
return (
<>
<div>{project.name}</div>
<fetcher.Form method="post">
<button
type="submit"
name="starred"
// use optimistic value to allow interruptions
value={starred ? "0" : "1"}
>
{/* 👇 display optimistic value */}
{starred ? "★" : "☆"}
</button>
</fetcher.Form>
</>
);
}
骨架回溯:當資料延遲時,您可以使用 <Suspense>
新增回溯。這允許 UI 無需等待資料載入即可呈現,從而加快應用程式的感知和實際效能。
import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { defer } from "@remix-run/node"; // or cloudflare/deno
import { Await } from "@remix-run/react";
import { Suspense } from "react";
export async function loader({
params,
}: LoaderFunctionArgs) {
const reviewsPromise = getReviews(params.productId);
const product = await getProduct(params.productId);
return defer({
product: product,
reviews: reviewsPromise,
});
}
export default function ProductRoute() {
const { product, reviews } =
useLoaderData<typeof loader>();
return (
<>
<ProductPage product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Await resolve={reviews}>
{(reviews) => <Reviews reviews={reviews} />}
</Await>
</Suspense>
</>
);
}
建立骨架回溯時,請考量下列原則
<Link prefetch="intent">
通常可以完全跳過回溯。當使用者將滑鼠游標懸停在連結上或將焦點放在連結上時,此方法會預先載入所需的資料,讓網路有短暫的時間在使用者按一下之前提取內容。這通常會立即導覽至下一個頁面。藉由在需要網路互動的動作期間顯示視覺提示,透過忙碌指示器、樂觀 UI 和骨架回溯建立網路感知 UI 可顯著改善使用者體驗。擅長此方面是建置使用者信任的應用程式的最佳方式。