- Published on
巧用 Next.js URL 参数,优化状态管理
- Authors
- Name
- Zhanxin
本文共有 2024 字 · 预计阅读时长≈ 11 min
在 React 和 Next.js 的开发世界中,状态管理是构建高效应用程序的关键环节。通常,useState 是管理应用程序状态的常用方法,但随着应用的扩展,其局限性也逐渐显现。
本文将深入探讨在 Next.js 中使用 URL 参数进行状态管理的优势、用例以及实现方法,并与useState进行比较。
useState的局限性
组件范围
useState 主要用于处理特定组件范围内的状态。当需要在多个组件之间共享状态或进行全局状态管理时,可能会导致 “props drilling”(属性透传)问题,此时需要借助状态管理库,如 Redux-toolkit 或集成 useContext 钩子。
未针对 SEO优化
当 URL 参数无法反映 useState 所管理的状态调整时,可能会对 SEO 产生不利影响。
用户体验考虑
在电子商务等应用中,不使用 URL 参数可能导致用户体验不佳,因为用户无法轻松分享他们的偏好。
URL 参数的优势
网页书签
URL 参数可以将状态信息直接编码到 URL 中,方便用户为特定页面添加书签并与他人分享。
增强的状态管理
在具有搜索功能的网页中,URL 参数可以保留搜索词,即使在用户刷新浏览器后也能保持状态。
简化组件逻辑
URL 参数为各个组件提供了一种简化逻辑的方法,无需依赖useState来实现复杂的搜索功能。
理解 URL 参数查询模式
URL 参数由键值对组成,多个参数用 “&” 符号分隔。例如:
https://www.example.com/search?q=mens+t-shirt&size=3xl&color=white&sort=asc
其中,“q” 表示搜索词,后续参数如 “size”、“color” 和 “sort” 描述了其他搜索条件,增强了用户的浏览体验。
常见的 URL 参数用例
排序和过滤
使用 URL 参数,用户可以对网页内容进行排序和过滤,定制浏览体验。例如:
https://www.example.com/dresses?sort=a-z。
搜索查询
参数可以封装用户搜索查询,便于用户为搜索结果添加书签以供将来参考。例如:
https://www.example.com/search?q=t-shirt。
语言翻译
URL 参数有助于语言翻译查询,使用户能够以首选语言访问网页。例如:
https://www.example.com/news?lang=fr。
跟踪营销活动
参数可以包括活动查询,有助于跟踪点击率和活动效果。例如:
https://www.example.com/home?utm_campaign=fbid_newyearpromo&referrer_id=25jh8s。
页面分页
URL 参数有助于对网页搜索结果进行分页,确保无缝导航。例如:
https://www.example.com/blog/articles?page=3。
带有 URL 的全局状态 — 优点和缺点
在管理 Web 应用程序的状态时,利用 URL 可以产生很多好处。它可以提升用户体验,促进营销活动的跟踪,并支持页面 SEO。但是,如果不小心使用,它也会给网页带来挑战。以下是一些需要考虑的优点和缺点:
优点
可添加书签和可共享的 URL:用户可以为应用程序的特定 URL 状态添加书签或与他人分享,增强了可用性和协作性。
深度链接:开发人员可以使用 URL 参数创建与查询字符串匹配的动态页面,改进应用程序状态的深度链接。
服务器端渲染 (SSR) 兼容性:对于需要服务器端渲染的项目,Next.js 与 URL 参数的结合非常理想,因为 URL 参数可以在服务器和客户端之间传输状态数据。
缺点
安全问题:存储在 URL 参数中的敏感信息可能会带来安全风险,因为它们可能对用户可见并可能被篡改。
重复内容:滥用 URL 参数会导致多个令人困惑的 URL,可能降低 SEO 引擎的页面排名。
复杂的 URL 结构:复杂的查询参数通常会导致 URL 长而难以阅读,从而阻止用户点击和信任链接,减少页面访问。
如何在 Next.js 中实现 URL 参数
创建组件
创建一个搜索输入组件 SearchSortInput,用于处理将搜索和排序查询附加到 URL。
从 next/navigation
导入 useRouter 和 useSearchParams 钩子。useRouter 钩子用于在客户端应用程序中进行导航,useSearchParams 钩子允许操作来自 URL 的查询。
import { useRouter, useSearchParams } from "next/navigation";
初始化钩子,并从 URL 中检索现有查询,以便在输入字段中保留任何查询。
const SearchSortInput = () => {
const router = useRouter();
const searchParams = useSearchParams();
const query = searchParams?.get("q");
const sort = searchParams?.get("sort");
const newParams = URLSearchParams(searchParams.toString());
};
为用户创建表单以输入搜索查询,将输入的 defaultValue 设置为现有查询,利用 URL 查询参数的优势,即使用户离开或刷新页面,查询仍将存在。
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>
);
查询处理逻辑,从表单中获取输入值。如果搜索输入有值,创建一个新查询;如果搜索输入为空,删除查询。对排序参数重复相同的过程。
最后,导航到 /search
路由并将查询参数添加到 URL。
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()}`);
};
将 handleSubmit 函数添加到表单事件并导出组件
return (
<div className="flex items-center space-x-4 mb-4">
// other codes here...
<form onSubmit={handleSubmit}>
// inputs here...
</form>
</div>
);
export default SearchSortInput;
创建数据显示组件
创建一个接受 data、q 和 sort 参数的函数,在顶部添加 use client
指示这是一个 Next.js 客户端组件。
'use client'
const DisplayData = ({data, q, sort}) => {
// ...
}
export default DisplayData;
创建 filteredData 函数,利用 JavaScript 的内置 filter 和 sort 方法来搜索和排序数据。如果没有搜索或排序查询,返回完整的数据。
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;
};
映射过滤后的数据并显示
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>
);
创建搜索页面
在 search 文件夹中的 page.js 文件中,根据用户查询显示搜索结果。使用之前创建的 DisplayData 组件, 从 useSearchParams 获取查询参数,根据查询参数从 API 中获取数据,并使用 Suspense 处理加载状态
"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">Search results for: {q}</h3>
)}
{sort && <p className="text-[14px] mb-4">Sorted by: {sort}</p>}
<Suspense fallback={<div>Loading...</div>} key={q}>
<DisplayData data={data} sort={sort} q={q} />
</Suspense>
</div>
);
}
在主页的 page.js 文件中,显示 SearchInput 和 DisplayData 组件,从 API 路由中获取数据并显示组件。
"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>Loading...</div>}>
<DisplayData data={data} />
</Suspense>
</div>
);
}
通过以上步骤,在 Next.js 中实现了使用 URL 参数进行状态管理。