虽然 useState hook 仍然是 React 开发者在函数组件中管理状态的宠儿,但它也有其局限性。特别是当应用规模扩大、更多组件需要数据时,我们经常需要在组件间传递数据(这种做法被亲切地称为** props 钻取**),或者求助于状态管理库。以下是一些值得思考的限制:
- 局部组件作用域: useState 的设计目的是处理特定组件内的状态。如果你需要在不同组件之间共享状态或处理全局状态,可以考虑使用 React 的 useContext hook 或采用 Redux-toolkit 等强大的状态管理工具 。
- SEO 优化不足: 当 URL 参数未能反映 useState 促成的状态调整时,可能会影响 SEO 效果 。
- 用户体验考虑: 在电商应用中,如果不使用 URL 参数可能导致用户体验欠佳,因为用户无法轻松地与他人分享他们的偏好设置。
- Props 钻取: 如果你依赖 useState 来管理全局状态,你会发现自己需要将状态和其设置函数作为 props 传递给每个需要的组件。
要克服这些限制,你可以考虑使用其他状态管理方案,如 useReducer hook、useContext hook,或者 Redux 和 MobX 等第三方解决方案。
为什么选择 URL 参数而不是 useState?
URL 参数(使用问号(?)附加在 URL 末尾的查询字符串)具有多种功能,例如通过搜索和分页等功能增强网页功能、提升页面 SEO,并经常用于跟踪营销活动。一个 URL 可以包含多个参数,用 & 符号分隔。
那么为什么要使用 URL 参数而不是 useState?
URL 参数在某些场景下可以胜过 useState,特别是当目标是以更灵活和可共享的方式管理应用状态时。以下是几个选择 URL 参数的令人信服的理由:
- 网页书签功能: URL 参数使你能够直接将状态信息编码到 URL 中,简化了用户为特定页面添加书签并与他人分享的过程。
- 增强的状态管理: 在具有搜索功能的网页中,通过 URL 参数保存搜索词可以确保即使用户刷新浏览器后也能保留。
- 简化组件逻辑: URL 参数提供了简化单个组件内部逻辑的途径。相比于依赖 useState 实现复杂的搜索功能,你可以让 URL 查询参数承担这个任务。
虽然 useState 在管理局部组件状态方面很强大,但将其与 URL 参数结合使用可以提供更健壮的状态管理方法。
理解 URL 参数查询模式
URL 参数由键值对组成,便于灵活的数据传输。键作为标识符,而值与之绑定,两者用等号(=)分隔。一个 URL 中可以包含多个参数,每个参数用 & 符号分隔。
例如:
https://www.example.com/search?q=mens+t-shirt&size=3xl&color=white&sort=asc
这个 URL 表示一个搜索路由,搜索词由键"q"指定。后续的参数如"size"、"color"和"sort"定义了额外的搜索条件,每个参数都在提升用户的浏览体验。
常见的 URL 参数用例
网站经常利用 URL 参数来处理高级状态,提升营销活动效果和页面 SEO。使用 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 参数是增强网页功能和提升各种在线平台用户体验的有力工具。
使用 URL 进行全局状态管理的优缺点
在管理 Web 应用状态时,利用 URL 可以带来诸多好处。它可以提升用户体验,便于跟踪营销活动,并增强页面 SEO。但如果使用不当,也会给网页带来挑战。以下是一些需要考虑的优缺点:
优点
- 可添加书签和分享的 URL: 用户可以为应用的特定 URL 状态添加书签或与他人分享,提升可用性和协作性。
- 深度链接: 开发者可以使用 URL 参数创建与查询字符串匹配的动态页面,改善应用状态的深度链接。
- 服务器端渲染(SSR)兼容性: 使用 Next.js 进行需要服务器端渲染的项目是理想的选择,因为 URL 参数可以在服务器和客户端之间传输状态数据。
缺点
- 安全隐患: 存储在 URL 参数中的敏感信息可能带来重大安全风险,因为它们对用户可见且可能被篡改。
- 重复内容: URL 参数使用不当可能导致多个令人困惑的 URL,并可能降低 SEO 引擎的页面排名。
- 复杂的 URL 结构: 复杂的查询参数往往会导致冗长、难以阅读的 URL,使用户不愿点击和信任你的链接,从而减少页面访问量。
在 Next.js 中实现 URL 参数
创建组件
创建两个组件。首先,创建一个处理将搜索和排序查询附加到 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,并使用搜索参数从 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>
);
在这一部分中,我们创建了输入字段供用户输入搜索查询。我们没有使用 useState 来处理输入的更新,而是将输入的 defaultValue 设置为我们现有的查询。这样,即使用户离开页面或刷新页面,他们的查询仍然会存在。这是使用 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()}`);
};
这个函数负责处理查询逻辑。我们不使用 useState 来管理输入,而是从表单中获取值。如果搜索输入有值,我们用键"q"和用户输入的值创建新查询。如果搜索输入为空,我们删除查询。排序也遵循相同的过程。最后,我们导航到 /search 路由并将查询添加到 URL。
return (
<div className="flex items-center space-x-4 mb-4">
// 其他代码...
<form onSubmit={handleSubmit}>
// 输入字段...
</form>
</div>
);
export default SearchSortInput;
要完成这个组件的逻辑,我们将 handleSubmit 函数绑定到表单并导出组件。
创建数据显示组件
首先,我们创建一个接受 data、q 和 sort 参数的函数。我们在顶部包含 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;
};
然后我们创建一个 filteredData 函数,它使用 JavaScript 内置的 filter 和 sort 方法来搜索和排序数据。如果没有搜索或排序查询,我们只返回完整数据。
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>
);
最后,我们遍历过滤后的数据并渲染它。
创建搜索页面
这是根据用户查询显示搜索结果的页面。我们使用之前创建的 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 中的实现。打开 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 路由获取数据。作为首页,我们显示搜索输入和 DisplayData 组件。我们只在这里渲染组件,而搜索和排序逻辑则在专门用于此目的的 /search 页面上实现。
总结
URL 查询参数是一个强大的工具,可以显著提升网站性能并丰富用户体验。令人欣慰的是,越来越多的开发者在构建实际应用时将查询参数整合到他们的代码库中。