前端必知必会的实现URL查询参数的方法详解
作者:evle
今天来给大家聊一聊 URL 查询参数。什么是 URL 参数查询?URL 参数查询是指在 URL 中使用问号(?)后面附加的键值对参数。例如:example.com/search?keyw…
URL 查询参数的发展历程
URL 查询参数的起源可以追溯到早期的互联网。在万维网诞生之初,为了实现简单的数据传递和页面交互,开发人员开始在 URL 中添加额外信息,以告诉服务器用户的需求。
到了 Web 2.0 时代,用户交互性增强,单页应用(SPA)兴起,URL 查询参数成为了前端路由和状态管理的关键工具。像 React Router 等路由库,就大量利用查询参数来管理页面状态,实现无刷新页面跳转和数据传递。
在 SPA 时代如何实现
首先忘记 useSearchParams
这个 React Router 提供的 Hook,让我们重新思考这个问题。
还是这个 Link 假设这是一个商品搜索页面,以前分享个页面内容,那叫一个费劲。现在有了 URL 查询参数,简单到飞起!用户直接复制链接就能分享给他人。比如说你在 React 项目里做了个搜索功能,搜完后,链接里的查询参数就包含了搜索关键词 接收者打开链接,直接就能看到和你一模一样的搜索结果,完全不用再重新输入关键词,是不是很方便!
https://example.com/search?keyword=手机&price=1000-2000&brand=apple
当访问这个页面时,我们需要从 URL 上取出这些参数,然后根据这些参数去发送网络请求查询商品。
那我们要解决第一个问题:获取 URL 参数的变化
浏览器 API
如果想获取浏览器地址栏上的数据,需要调用浏览器的 History APIwindow.location
对象 这是浏览器提供的核心 API,包含了当前 URL 的信息,假设当前 URL 是:https://example.com/path?search=test#hash
那么访问 window.location
则可以获得如下信息:
window.location.pathname // "/path" window.location.search // "?search=test" window.location.hash // "#hash"
另外一个核心的 对象是 window.histroy
这个对象是操作浏览器的跳转的,比如 history.back()
就是用户点击了下图左侧箭头的效果。history.pushState(state, '', '/new-path')
就是模拟用户修改 上面提到的 window.location.pathname
跳转到了新的页面。
当用户点击前后按钮时会触发一个事件 popstate
, 我们监听这个事件就可以捕捉到页面前进后退传递的变量或URL变化,现在可以打开掘进试试监听这个事件
window.addEventListener('popstate', (event) => { console.log('历史记录变化:', event); });
那在juejin 点击前后的时候,就能看到juejin 页面跳转时传递的一些参数和变化
但是要注意的是 popstate
事件只能监听浏览器的前进后退,无法监听到 pushState
产生的变化,调用 pushState
会改变当前地址栏的URL,并且页面也不会重新加载。 这个特性在 SPA 中非常关键,也是路由切换的关键方法,如果想监听这个方法来实现监听地址栏 URL 变化的效果请继续看。
路由守卫
有了对 history API 的理解,我们就可以做很多场景了比如路由守卫,路由守卫就是当用户切换页面时,会有一个守卫,来检测当前用户是否能够访问该页面。
这里我们用一个图说明一下我们实现的流程
+------------------------+ 注册 +-------------------------+ | | --------> | 路由守卫映射 | | registerGuard('/admin')| | Map<路径, 守卫函数> | | | | | +------------------------+ | '/admin' => checkAdmin | | '/user' => checkUser | +-------------------------+ | | 监听 ↓ +------------------------+ 触发 +-------------------------+ | 路由变化事件 | --------> | 路由守卫检查 | | 1. history.pushState | | 1. 获取当前路径 | | 2. popstate | | 2. 查找匹配的守卫函数 | | | | 3. 执行守卫逻辑 | +------------------------+ +-------------------------+ | | 结果 ↓ +-------------------------+ | 处理结果 | | true → 允许访问 | | false → 重定向到登录页 | +-------------------------+
先实现注册系统
const routeGuards = new Map(); // 用一个 map 将所有守卫维护起来 // 注册路由守卫 export function registerGuard(path, guard) { routeGuards.set(path, guard); }
下面我们给 /user
注册一个守卫,即:当用户访问 /user
时,运行一个检测逻辑验证用户是否可以访问。
registerGuard('/user', () => { const isLoggedIn = checkAuth(); if (!isLoggedIn) { alert('请先登录!'); return false; } return true; });
接下来我们就需要监听了,前面提到 history.pushState API 不产生任何事件,我们无法监听,那 workaround 的方法是重写这个方法,并在使在调用这个API的时候先执行我们的 guard 逻辑。
export function setupRouteGuard() { // 初始状态检查 checkCurrentRoute(); // 监听 popstate 事件(浏览器前进/后退) window.addEventListener('popstate', checkCurrentRoute); // 拦截 history.pushState const originalPushState = history.pushState; history.pushState = function(...args) { const result = originalPushState.apply(this, args); checkCurrentRoute(); return result; }; }
通过改写后,每当通过 pushState 跳转页面时,就会调 checkCurrentRoute
的方法, 该方法中
function checkCurrentRoute() { const currentPath = window.location.pathname; for (const [path, guard] of routeGuards) { if (currentPath.startsWith(path)) { // 执行用户注册的回调函数 const result = guard({ to: currentPath, from: document.referrer, }); // 如果回调返回 false,则中断导航,比如调到其他未授权页面,或者错误提示什么的 if (result === false) { return; } } } }
实现 useNavigate
我们发现上面那种通过重写 pushState 方法实现监听并不优雅,既然 pushState 不产生事件,我们就不调用这个方法,我们提供一个新的方法 navigate
来进行页面跳转并且发射事件。
这里补充一个知识,浏览器 API 可以发射自定义事件通过 new CustomEvent()
方法。
function useNavigate() { const navigate = useCallback((to, options = {}) => { // 更新URL, 添加新的历史记录 window.history.pushState(state, '', to); // 触发自定义事件通知路由变化 window.dispatchEvent(new CustomEvent('routechange', { detail: { pathname: window.location.pathname, search: window.location.search, hash: window.location.hash, state } })); }, []); return navigate; }
定义好之后直接引用就行
const navigate = useNavigate(); navigate(`/search`);
当用户执行 navigate('/search')
时,当前页面的地址栏就会变为 xxx/search
实现 useLocation
到目前为止我们只解决了 地址栏 URL 改变的问题,这时我们的程序其实是没办法知道 URL 变了的,因为 window.location.path
是一个普通变量,无法通知我们的程序,我们需要实现一个 useLocation Hook, 通过 setState 通知 React 框架来更新 UI。
function useLocation() { const [location, setLocation] = useState(() => ({ pathname: window.location.pathname, search: window.location.search, hash: window.location.hash, state: window.history.state })); useEffect(() => { // 监听 popstate 事件(浏览器前进/后退时触发) const handlePopState = () => { setLocation({ pathname: window.location.pathname, search: window.location.search, hash: window.location.hash, state: window.history.state }); }; // 监听自定义的路由变化事件 const handleRouteChange = (event) => { setLocation(event.detail); }; window.addEventListener('popstate', handlePopState); window.addEventListener('routechange', handleRouteChange); return () => { window.removeEventListener('popstate', handlePopState); window.removeEventListener('routechange', handleRouteChange); }; }, []); return location; }
回到例子
经历了一些 API 的理解,我们回到最初的例子,想支持 URL 查询参数,也就是用户粘贴过来带有参数的URL,我们可以把他变成筛选项并发起请求,
function ProductSearch() { const [searchParams] = useSearchParams(); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { // 从 URL 获取查询参数 const keyword = searchParams.get('keyword'); const price = searchParams.get('price'); const brand = searchParams.get('brand'); // 如果有查询参数,则发起请求 if (keyword || price || brand) { fetchProducts(); } }, [searchParams]); // 当 URL 参数变化时重新请求 const fetchProducts = async () => { try { setLoading(true); // 构建查询参数 const params = new URLSearchParams(searchParams); // 发起请求 const response = await fetch(`/api/products?${params}`); const data = await response.json(); setProducts(data); } catch (error) { console.error('查询失败:', error); } finally { setLoading(false); } }; return ( <div> <h2>搜索结果</h2> {loading ? ( <div>加载中...</div> ) : ( <div className="product-list"> {products.map(product => ( <div key={product.id} className="product-item"> <h3>{product.name}</h3> <p>价格: ¥{product.price}</p> <p>品牌: {product.brand}</p> </div> ))} </div> )} </div> ); }
这样实现的好处:
- URL 可分享,用户可以直接分享搜索结果
- 刷新页面不会丢失搜索条件
- 支持浏览器前进/后退操作
- 便于跟踪用户搜索行为
那关键点 useSearchParams
也没有那么神秘了,根据我们前面的理解 we can see how it works easily.
这里贴一个简化实现:
import { useState, useCallback, useEffect } from 'react'; function useSearchParams() { const [params, setParams] = useState(() => new URLSearchParams(window.location.search) ); const setSearchParams = useCallback((update) => { // 处理更新 const newParams = new URLSearchParams( typeof update === 'object' ? update : update(params) ); // 更新 URL 和状态 history.pushState(null, '', `?${newParams}`); setParams(newParams); }, [params]); // 简化历史监听 useEffect(() => { const syncParams = () => setParams(new URLSearchParams(location.search)); window.addEventListener('popstate', syncParams); return () => window.removeEventListener('popstate', syncParams); }, []); return [params, setSearchParams]; } export default useSearchParams;
使用 React Router 常见问题
在使用 React Router 时,我们经常需要通过 useLocation 获取路由传递的 state 数据:
为了监听 state的变化,我们可能会这么使用
const location = useLocation(); useEffect(()=>{ }, [location.state])
但是在 React 中,每次组件重新渲染时,组件内的代码都会重新执行,意味着 location.state 可能值没有变化,比如 state 是 'hello', 下次重新渲染即使还是 'hello', 也会导致 useEffect 会重复执行,因为 useEffect 对监听的这个 location.state 做了一个浅比较(也就是针对引用数据的类型比如对象,不比较具体的值,只比较引用),为什么浅比较呢,因为每个内容都比较慢呀。
React 的做法是将这个优化决定权交给了开发者。
解决方案:
使用 useMemo 优化 用来监听真正内容的变更:我们可以使用 useMemo 来记忆化 state 对象,只在真正需要更新的属性发生变化时才创建新的引用:
const memoizedState = useMemo(() => { return location.state; }, [location.state?.source, location.state?.timestamp]); // 只监听需要的属性 useEffect(() => { if (id) { handleProductDetail(id, memoizedState); } }, [id, memoizedState]);
使用 JSON.stringify
另一种方案是使用 JSON.stringify 来比较对象的值而不是引用(不优雅,也有局限性)
useEffect(() => { // 一些操作 }, [id, JSON.stringify(location.state)]);
场景
SPA 时代下,应用支持参数化查询已经成为一个标配。
如果没有会怎么样?
想象一下,当用户在使用一个复杂的数据筛选系统时,他们花费了大量时间调整各种参数,最终得到了理想的结果。如果这时他们想要分享这个结果给同事,传统方式可能需要写一份详细的操作说明。但有了合理的 URL 参数设计,用户只需要复制当前页面的链接,同事就能看到完全相同的结果。
对 URL查询参数的支持可以极大的提升用户体验,并且随处可见:
- 表格的状态保持
- 多步骤表单
- 文档阅读位置
- 多语言切换
- 视频播放状态
- 等等
在设计这些参数时,“美观程度” 也很重要:
- 直观易懂 - 参数名称要见名知义
- 简洁明确 - 避免冗余和歧义
- 可预测 - 参数的行为要符合用户预期
- 稳定可靠 - 确保参数在各种情况下都能正常工作
到此这篇关于前端必知必会的实现URL查询参数的方法详解的文章就介绍到这了,更多相关前端URL查询参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!