在 Next.js 中使用 URL 參數(shù)進行狀態(tài)管理
雖然 useState hook 仍然是 React 開發(fā)者在函數(shù)組件中管理狀態(tài)的寵兒,但它也有其局限性。特別是當應用規(guī)模擴大、更多組件需要數(shù)據(jù)時,我們經(jīng)常需要在組件間傳遞數(shù)據(jù)(這種做法被親切地稱為** props 鉆取**),或者求助于狀態(tài)管理庫。以下是一些值得思考的限制:
- 局部組件作用域: useState 的設計目的是處理特定組件內(nèi)的狀態(tài)。如果你需要在不同組件之間共享狀態(tài)或處理全局狀態(tài),可以考慮使用 React 的 useContext hook 或采用 Redux-toolkit 等強大的狀態(tài)管理工具 。
- SEO 優(yōu)化不足: 當 URL 參數(shù)未能反映 useState 促成的狀態(tài)調(diào)整時,可能會影響 SEO 效果 。
- 用戶體驗考慮: 在電商應用中,如果不使用 URL 參數(shù)可能導致用戶體驗欠佳,因為用戶無法輕松地與他人分享他們的偏好設置。
- Props 鉆?。?/strong> 如果你依賴 useState 來管理全局狀態(tài),你會發(fā)現(xiàn)自己需要將狀態(tài)和其設置函數(shù)作為 props 傳遞給每個需要的組件。
要克服這些限制,你可以考慮使用其他狀態(tài)管理方案,如 useReducer hook、useContext hook,或者 Redux 和 MobX 等第三方解決方案。
為什么選擇 URL 參數(shù)而不是 useState?
URL 參數(shù)(使用問號(?)附加在 URL 末尾的查詢字符串)具有多種功能,例如通過搜索和分頁等功能增強網(wǎng)頁功能、提升頁面 SEO,并經(jīng)常用于跟蹤營銷活動。一個 URL 可以包含多個參數(shù),用 & 符號分隔。
那么為什么要使用 URL 參數(shù)而不是 useState?
URL 參數(shù)在某些場景下可以勝過 useState,特別是當目標是以更靈活和可共享的方式管理應用狀態(tài)時。以下是幾個選擇 URL 參數(shù)的令人信服的理由:
- 網(wǎng)頁書簽功能: URL 參數(shù)使你能夠直接將狀態(tài)信息編碼到 URL 中,簡化了用戶為特定頁面添加書簽并與他人分享的過程。
- 增強的狀態(tài)管理: 在具有搜索功能的網(wǎng)頁中,通過 URL 參數(shù)保存搜索詞可以確保即使用戶刷新瀏覽器后也能保留。
- 簡化組件邏輯: URL 參數(shù)提供了簡化單個組件內(nèi)部邏輯的途徑。相比于依賴 useState 實現(xiàn)復雜的搜索功能,你可以讓 URL 查詢參數(shù)承擔這個任務。
雖然 useState 在管理局部組件狀態(tài)方面很強大,但將其與 URL 參數(shù)結合使用可以提供更健壯的狀態(tài)管理方法。
理解 URL 參數(shù)查詢模式
URL 參數(shù)由鍵值對組成,便于靈活的數(shù)據(jù)傳輸。鍵作為標識符,而值與之綁定,兩者用等號(=)分隔。一個 URL 中可以包含多個參數(shù),每個參數(shù)用 & 符號分隔。
例如:
https://www.example.com/search?q=mens+t-shirt&size=3xl&color=white&sort=asc
這個 URL 表示一個搜索路由,搜索詞由鍵"q"指定。后續(xù)的參數(shù)如"size"、"color"和"sort"定義了額外的搜索條件,每個參數(shù)都在提升用戶的瀏覽體驗。
常見的 URL 參數(shù)用例
網(wǎng)站經(jīng)常利用 URL 參數(shù)來處理高級狀態(tài),提升營銷活動效果和頁面 SEO。使用 URL 參數(shù)有很多好處:
- 排序和篩選: URL 參數(shù)使用戶能夠對網(wǎng)頁內(nèi)容進行排序和篩選,定制他們的瀏覽體驗。例如:https://www.example.com/dresses?sort=a-z
- 搜索查詢: 參數(shù)可以封裝用戶的搜索查詢,便于將來參考。例如:https://www.example.com/search?q=t-shirt
- 語言翻譯: URL 參數(shù)可以處理語言翻譯查詢,讓用戶以他們偏好的語言訪問網(wǎng)頁。例如:https://www.example.com/news?lang=fr
- 跟蹤營銷活動: 參數(shù)可以包含營銷活動查詢,幫助跟蹤點擊率和活動效果。例如:https://www.example.com/home?utm_campaign=fbid_newyearpromo&referrer_id=25jh8s
- 頁面分頁: URL 參數(shù)在網(wǎng)頁搜索結果分頁中發(fā)揮重要作用,確保導航流暢。例如:https://www.example.com/blog/articles?page=3
URL 參數(shù)是增強網(wǎng)頁功能和提升各種在線平臺用戶體驗的有力工具。
使用 URL 進行全局狀態(tài)管理的優(yōu)缺點
在管理 Web 應用狀態(tài)時,利用 URL 可以帶來諸多好處。它可以提升用戶體驗,便于跟蹤營銷活動,并增強頁面 SEO。但如果使用不當,也會給網(wǎng)頁帶來挑戰(zhàn)。以下是一些需要考慮的優(yōu)缺點:
優(yōu)點
- 可添加書簽和分享的 URL: 用戶可以為應用的特定 URL 狀態(tài)添加書簽或與他人分享,提升可用性和協(xié)作性。
- 深度鏈接: 開發(fā)者可以使用 URL 參數(shù)創(chuàng)建與查詢字符串匹配的動態(tài)頁面,改善應用狀態(tài)的深度鏈接。
- 服務器端渲染(SSR)兼容性: 使用 Next.js 進行需要服務器端渲染的項目是理想的選擇,因為 URL 參數(shù)可以在服務器和客戶端之間傳輸狀態(tài)數(shù)據(jù)。
缺點
- 安全隱患: 存儲在 URL 參數(shù)中的敏感信息可能帶來重大安全風險,因為它們對用戶可見且可能被篡改。
- 重復內(nèi)容: URL 參數(shù)使用不當可能導致多個令人困惑的 URL,并可能降低 SEO 引擎的頁面排名。
- 復雜的 URL 結構: 復雜的查詢參數(shù)往往會導致冗長、難以閱讀的 URL,使用戶不愿點擊和信任你的鏈接,從而減少頁面訪問量。
在 Next.js 中實現(xiàn) URL 參數(shù)
創(chuàng)建組件
創(chuàng)建兩個組件。首先,創(chuàng)建一個處理將搜索和排序查詢附加到 URL 的搜索輸入組件。
import { useRouter, useSearchParams } from "next/navigation";
首先,我們從 next/navigation 導入查詢 hooks。useRouter hook 使我們能夠在客戶端應用中導航到任何路由。另一方面,useSearchParams hook 允許我們操作 URL 中的查詢,如 get、set 和 delete 方法。
const SearchSortInput = () => {
const router = useRouter();
const searchParams = useSearchParams();
const query = searchParams?.get("q");
const sort = searchParams?.get("sort");
const newParams = URLSearchParams(searchParams.toString());
};
接下來,我們初始化 hooks,并使用搜索參數(shù)從 URL 中檢索現(xiàn)有查詢。這允許我們在輸入字段中保持任何查詢。
return (
<div className="flex items-center space-x-4 mb-4">
<button
onClick={() => router.push("/")}
className="border border-gray-300 p-2 rounded text-black border-black"
>
Home
</button>
<form
className="
flex items-center space-x-4 mb-4 mx-auto
"
>
<input
type="text"
placeholder="Search..."
name="search"
key={query || ""}
defaultValue={query || ""}
className="border border-gray-300 p-2 rounded text-black border-black"
/>
<button
type="submit"
className="border border-gray-300 p-2 rounded text-black border-black"
>
Search
</button>
<div className="flex gap-2 items-center">
<p>Sort by:</p>
<select
defaultValue={sort || "default"}
name="sort"
onChange={(e) => {
newParams.set("sort", e.target.value);
router.push(`/search?${newParams.toString()}`);
}}
className="border border-gray-300 p-2 rounded"
>
<option value="default">Default</option>
<option value="title">Name</option>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
<option value="a-z">A to Z</option>
</select>
</div>
</form>
</div>
);
在這一部分中,我們創(chuàng)建了輸入字段供用戶輸入搜索查詢。我們沒有使用 useState 來處理輸入的更新,而是將輸入的 defaultValue 設置為我們現(xiàn)有的查詢。這樣,即使用戶離開頁面或刷新頁面,他們的查詢?nèi)匀粫嬖凇_@是使用 URL 查詢參數(shù)的好處之一。
const handleSubmit = (event) => {
event.preventDefault();
const val = event.target;
const search = val.search;
const sortBy = val.sort;
if (search.value) {
newParams.set("q", search.value);
} else {
newParams.delete("q");
}
if (sortBy.value) {
newParams.set("sort", sortBy.value);
} else {
newParams.delete("sort");
}
router.push(`/search?${newParams.toString()}`);
};
這個函數(shù)負責處理查詢邏輯。我們不使用 useState 來管理輸入,而是從表單中獲取值。如果搜索輸入有值,我們用鍵"q"和用戶輸入的值創(chuàng)建新查詢。如果搜索輸入為空,我們刪除查詢。排序也遵循相同的過程。最后,我們導航到 /search 路由并將查詢添加到 URL。
return (
<div className="flex items-center space-x-4 mb-4">
// 其他代碼...
<form onSubmit={handleSubmit}>
// 輸入字段...
</form>
</div>
);
export default SearchSortInput;
要完成這個組件的邏輯,我們將 handleSubmit 函數(shù)綁定到表單并導出組件。
創(chuàng)建數(shù)據(jù)顯示組件
首先,我們創(chuàng)建一個接受 data、q 和 sort 參數(shù)的函數(shù)。我們在頂部包含 use client 以表明這是一個 Next.js 客戶端組件。
const filteredData = () => {
let newData = [...data];
if (q) {
newData = newData.filter(
(item) =>
item.name.toLowerCase().includes(q.toLowerCase()) ||
item.username.toLowerCase().includes(q.toLowerCase()),
);
}
if (sort) {
newData.sort((a, b) => {
if (sort === "name") {
return a.name.localeCompare(b.name);
} else if (sort === "a-z") {
return b.username.localeCompare(a.username);
} else if (sort === "asc") {
return a.id - b.id;
} else if (sort === "desc") {
return b.id - a.id;
} else {
return 0;
}
});
}
return newData;
};
然后我們創(chuàng)建一個 filteredData 函數(shù),它使用 JavaScript 內(nèi)置的 filter 和 sort 方法來搜索和排序數(shù)據(jù)。如果沒有搜索或排序查詢,我們只返回完整數(shù)據(jù)。
return (
<div className="flex flex-col items-center">
<h1
className="
text-4xl font-semibold text-center mb-4 mt-8 mx-auto
"
>
My Feed
</h1>
<ul className="grid grid-cols-4 mx-auto max-w-[1260px] gap-10"></ul>
{filteredData().map((item) => (
<ul
key={item.id}
className="flex border border-gray-300 p-4 rounded w-[600px] mb-4 gap-4"
>
<h3 className="text-lg font-semibold mb-2">{item.name}</h3>
<p className="text-gray-500">Username: {item.username}</p>
<p className="text-gray-500">Email: {item.email}</p>
</ul>
))}
</div>
);
最后,我們遍歷過濾后的數(shù)據(jù)并渲染它。
創(chuàng)建搜索頁面
這是根據(jù)用戶查詢顯示搜索結果的頁面。我們使用之前創(chuàng)建的 DisplayData 組件。打開 search 文件夾中的 page.js 文件并粘貼以下代碼片段。
"use client";
import { useSearchParams } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
import DisplayData from "../_components/DisplayData";
import SearchSortInput from "../_components/SearchInput";
export default function Search() {
const searchParams = useSearchParams();
const q = searchParams.get("q");
const sort = searchParams.get("sort");
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const searchParams = new URLSearchParams();
if (q) {
searchParams.append("q", q);
}
if (sort) {
searchParams.append("sort", sort);
}
const response = await fetch(`/api/users`);
const data = await response.json();
setData(data);
};
fetchData();
}, [q, sort]);
return (
<div className="m-12">
<SearchSortInput />
{q && (
<h3 className="text-2xl font-bold mb-4">搜索結果: {q}</h3>
)}
{sort && <p className="text-[14px] mb-4">排序方式: {sort}</p>}
<Suspense fallback={<div>加載中...</div>} key={q}>
<DisplayData data={data} sort={sort} q={q} />
</Suspense>
</div>
);
}
最后,要完成我們的 URL 查詢在 Next.js 中的實現(xiàn)。打開 App 文件夾中的 page.js 文件并粘貼以下代碼片段。
"use client";
import { Suspense, useEffect, useState } from "react";
import DisplayData from "./_components/DisplayData";
import SearchSortInput from "./_components/SearchInput";
export default function Home() {
const [data, setData] = useState([]);
const fetchPosts = async () => {
const res = await fetch("/api/users");
const data = await res.json();
setData(data);
};
useEffect(() => {
fetchPosts();
}, []);
return (
<div className="m-12">
<SearchSortInput />
<Suspense fallback={<div>加載中...</div>}>
<DisplayData data={data} />
</Suspense>
</div>
);
}
在這個文件中,我們的主要任務是從 API 路由獲取數(shù)據(jù)。作為首頁,我們顯示搜索輸入和 DisplayData 組件。我們只在這里渲染組件,而搜索和排序邏輯則在專門用于此目的的 /search 頁面上實現(xiàn)。
總結
URL 查詢參數(shù)是一個強大的工具,可以顯著提升網(wǎng)站性能并豐富用戶體驗。令人欣慰的是,越來越多的開發(fā)者在構建實際應用時將查詢參數(shù)整合到他們的代碼庫中。