欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

前端必知必會的實現(xiàn)URL查詢參數(shù)的方法詳解

 更新時間:2025年02月12日 08:45:04   作者:evle  
URL?參數(shù)查詢是指在?URL?中使用問號(?)后面附加的鍵值對參數(shù),本文為大家詳細介紹了前端實現(xiàn)URL查詢參數(shù)的方法,希望對大家有所幫助

今天來給大家聊一聊 URL 查詢參數(shù)。什么是 URL 參數(shù)查詢?URL 參數(shù)查詢是指在 URL 中使用問號(?)后面附加的鍵值對參數(shù)。例如:example.com/search?keyw…

URL 查詢參數(shù)的發(fā)展歷程

URL 查詢參數(shù)的起源可以追溯到早期的互聯(lián)網(wǎng)。在萬維網(wǎng)誕生之初,為了實現(xiàn)簡單的數(shù)據(jù)傳遞和頁面交互,開發(fā)人員開始在 URL 中添加額外信息,以告訴服務器用戶的需求。

到了 Web 2.0 時代,用戶交互性增強,單頁應用(SPA)興起,URL 查詢參數(shù)成為了前端路由和狀態(tài)管理的關鍵工具。像 React Router 等路由庫,就大量利用查詢參數(shù)來管理頁面狀態(tài),實現(xiàn)無刷新頁面跳轉和數(shù)據(jù)傳遞。

在 SPA 時代如何實現(xiàn)

首先忘記 useSearchParams 這個 React Router 提供的 Hook,讓我們重新思考這個問題。

還是這個 Link 假設這是一個商品搜索頁面,以前分享個頁面內(nèi)容,那叫一個費勁。現(xiàn)在有了 URL 查詢參數(shù),簡單到飛起!用戶直接復制鏈接就能分享給他人。比如說你在 React 項目里做了個搜索功能,搜完后,鏈接里的查詢參數(shù)就包含了搜索關鍵詞 接收者打開鏈接,直接就能看到和你一模一樣的搜索結果,完全不用再重新輸入關鍵詞,是不是很方便!

https://example.com/search?keyword=手機&price=1000-2000&brand=apple

當訪問這個頁面時,我們需要從 URL 上取出這些參數(shù),然后根據(jù)這些參數(shù)去發(fā)送網(wǎng)絡請求查詢商品。

那我們要解決第一個問題:獲取 URL 參數(shù)的變化

瀏覽器 API

如果想獲取瀏覽器地址欄上的數(shù)據(jù),需要調(diào)用瀏覽器的 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 跳轉到了新的頁面。

當用戶點擊前后按鈕時會觸發(fā)一個事件 popstate, 我們監(jiān)聽這個事件就可以捕捉到頁面前進后退傳遞的變量或URL變化,現(xiàn)在可以打開掘進試試監(jiān)聽這個事件

window.addEventListener('popstate', (event) => {
  console.log('歷史記錄變化:', event);
});

那在juejin 點擊前后的時候,就能看到juejin 頁面跳轉時傳遞的一些參數(shù)和變化

但是要注意的是 popstate 事件只能監(jiān)聽瀏覽器的前進后退,無法監(jiān)聽到 pushState 產(chǎn)生的變化,調(diào)用 pushState 會改變當前地址欄的URL,并且頁面也不會重新加載。 這個特性在 SPA 中非常關鍵,也是路由切換的關鍵方法,如果想監(jiān)聽這個方法來實現(xiàn)監(jiān)聽地址欄 URL 變化的效果請繼續(xù)看。

路由守衛(wèi)

有了對 history API 的理解,我們就可以做很多場景了比如路由守衛(wèi),路由守衛(wèi)就是當用戶切換頁面時,會有一個守衛(wèi),來檢測當前用戶是否能夠訪問該頁面。

這里我們用一個圖說明一下我們實現(xiàn)的流程

+------------------------+     注冊     +-------------------------+
|                        |  -------->  |      路由守衛(wèi)映射        |
|  registerGuard('/admin')|            | Map<路徑, 守衛(wèi)函數(shù)>      |
|                        |            |                         |
+------------------------+            | '/admin' => checkAdmin  |
                                     | '/user'  => checkUser   |
                                     +-------------------------+
                                              |
                                              | 監(jiān)聽
                                              ↓
+------------------------+     觸發(fā)     +-------------------------+
|    路由變化事件          | -------->  |     路由守衛(wèi)檢查          |
|  1. history.pushState  |            | 1. 獲取當前路徑           |
|  2. popstate          |            | 2. 查找匹配的守衛(wèi)函數(shù)      |
|                       |            | 3. 執(zhí)行守衛(wèi)邏輯           |
+------------------------+            +-------------------------+
                                              |
                                              | 結果
                                              ↓
                                    +-------------------------+
                                    |         處理結果          |
                                    | true  → 允許訪問         |
                                    | false → 重定向到登錄頁    |
                                    +-------------------------+

先實現(xiàn)注冊系統(tǒng)

const routeGuards = new Map(); // 用一個 map 將所有守衛(wèi)維護起來

// 注冊路由守衛(wèi)
export function registerGuard(path, guard) {
  routeGuards.set(path, guard);
}

下面我們給 /user 注冊一個守衛(wèi),即:當用戶訪問 /user 時,運行一個檢測邏輯驗證用戶是否可以訪問。

registerGuard('/user', () => {
  const isLoggedIn = checkAuth();
  if (!isLoggedIn) {
    alert('請先登錄!');
    return false;
  }
  return true;
});

接下來我們就需要監(jiān)聽了,前面提到 history.pushState API 不產(chǎn)生任何事件,我們無法監(jiān)聽,那 workaround 的方法是重寫這個方法,并在使在調(diào)用這個API的時候先執(zhí)行我們的 guard 邏輯。

export function setupRouteGuard() {
  // 初始狀態(tài)檢查
  checkCurrentRoute();
  
  // 監(jiān)聽 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 跳轉頁面時,就會調(diào) checkCurrentRoute 的方法, 該方法中

function checkCurrentRoute() {
  const currentPath = window.location.pathname;
  
  for (const [path, guard] of routeGuards) {
    if (currentPath.startsWith(path)) {
      // 執(zhí)行用戶注冊的回調(diào)函數(shù)
      const result = guard({
        to: currentPath,
        from: document.referrer,
      });
      
      // 如果回調(diào)返回 false,則中斷導航,比如調(diào)到其他未授權頁面,或者錯誤提示什么的
      if (result === false) {
        return;
      }
    }
  }
}

實現(xiàn) useNavigate

我們發(fā)現(xiàn)上面那種通過重寫 pushState 方法實現(xiàn)監(jiān)聽并不優(yōu)雅,既然 pushState 不產(chǎn)生事件,我們就不調(diào)用這個方法,我們提供一個新的方法 navigate來進行頁面跳轉并且發(fā)射事件。

這里補充一個知識,瀏覽器 API 可以發(fā)射自定義事件通過 new CustomEvent() 方法。

function useNavigate() {
  const navigate = useCallback((to, options = {}) => {
    // 更新URL, 添加新的歷史記錄
    window.history.pushState(state, '', to);

    // 觸發(fā)自定義事件通知路由變化
    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`);

當用戶執(zhí)行 navigate('/search')時,當前頁面的地址欄就會變?yōu)?xxx/search

實現(xiàn) useLocation

到目前為止我們只解決了 地址欄 URL 改變的問題,這時我們的程序其實是沒辦法知道 URL 變了的,因為 window.location.path 是一個普通變量,無法通知我們的程序,我們需要實現(xiàn)一個 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(() => {
    // 監(jiān)聽 popstate 事件(瀏覽器前進/后退時觸發(fā))
    const handlePopState = () => {
      setLocation({
        pathname: window.location.pathname,
        search: window.location.search,
        hash: window.location.hash,
        state: window.history.state
      });
    };

    // 監(jiān)聽自定義的路由變化事件
    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;
}

回到例子

經(jīng)歷了一些 API 的理解,我們回到最初的例子,想支持 URL 查詢參數(shù),也就是用戶粘貼過來帶有參數(shù)的URL,我們可以把他變成篩選項并發(fā)起請求,

function ProductSearch() {
  const [searchParams] = useSearchParams();
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // 從 URL 獲取查詢參數(shù)
    const keyword = searchParams.get('keyword');
    const price = searchParams.get('price');
    const brand = searchParams.get('brand');

    // 如果有查詢參數(shù),則發(fā)起請求
    if (keyword || price || brand) {
      fetchProducts();
    }
  }, [searchParams]); // 當 URL 參數(shù)變化時重新請求

  const fetchProducts = async () => {
    try {
      setLoading(true);
      // 構建查詢參數(shù)
      const params = new URLSearchParams(searchParams);
      
      // 發(fā)起請求
      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>
  );
}

這樣實現(xiàn)的好處:

  • URL 可分享,用戶可以直接分享搜索結果
  • 刷新頁面不會丟失搜索條件
  • 支持瀏覽器前進/后退操作
  • 便于跟蹤用戶搜索行為

那關鍵點 useSearchParams 也沒有那么神秘了,根據(jù)我們前面的理解 we can see how it works easily.

這里貼一個簡化實現(xiàn):

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 和狀態(tài)
    history.pushState(null, '', `?${newParams}`);
    setParams(newParams);
  }, [params]);

  // 簡化歷史監(jiān)聽
  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 時,我們經(jīng)常需要通過 useLocation 獲取路由傳遞的 state 數(shù)據(jù):

為了監(jiān)聽 state的變化,我們可能會這么使用

const location = useLocation();

useEffect(()=>{
}, [location.state])

但是在 React 中,每次組件重新渲染時,組件內(nèi)的代碼都會重新執(zhí)行,意味著 location.state 可能值沒有變化,比如 state 是 'hello', 下次重新渲染即使還是 'hello', 也會導致 useEffect 會重復執(zhí)行,因為 useEffect 對監(jiān)聽的這個 location.state 做了一個淺比較(也就是針對引用數(shù)據(jù)的類型比如對象,不比較具體的值,只比較引用),為什么淺比較呢,因為每個內(nèi)容都比較慢呀。

React 的做法是將這個優(yōu)化決定權交給了開發(fā)者。

解決方案:

使用 useMemo 優(yōu)化 用來監(jiān)聽真正內(nèi)容的變更:我們可以使用 useMemo 來記憶化 state 對象,只在真正需要更新的屬性發(fā)生變化時才創(chuàng)建新的引用:

const memoizedState = useMemo(() => {
  return location.state;
}, [location.state?.source, location.state?.timestamp]); // 只監(jiān)聽需要的屬性

useEffect(() => {
  if (id) {
    handleProductDetail(id, memoizedState);
  }
}, [id, memoizedState]);

使用 JSON.stringify

另一種方案是使用 JSON.stringify 來比較對象的值而不是引用(不優(yōu)雅,也有局限性)

useEffect(() => {
  // 一些操作
}, [id, JSON.stringify(location.state)]);

場景

SPA 時代下,應用支持參數(shù)化查詢已經(jīng)成為一個標配。

如果沒有會怎么樣?

想象一下,當用戶在使用一個復雜的數(shù)據(jù)篩選系統(tǒng)時,他們花費了大量時間調(diào)整各種參數(shù),最終得到了理想的結果。如果這時他們想要分享這個結果給同事,傳統(tǒng)方式可能需要寫一份詳細的操作說明。但有了合理的 URL 參數(shù)設計,用戶只需要復制當前頁面的鏈接,同事就能看到完全相同的結果。

對 URL查詢參數(shù)的支持可以極大的提升用戶體驗,并且隨處可見:

  • 表格的狀態(tài)保持
  • 多步驟表單
  • 文檔閱讀位置
  • 多語言切換
  • 視頻播放狀態(tài)
  • 等等

在設計這些參數(shù)時,“美觀程度” 也很重要:

  • 直觀易懂 - 參數(shù)名稱要見名知義
  • 簡潔明確 - 避免冗余和歧義
  • 可預測 - 參數(shù)的行為要符合用戶預期
  • 穩(wěn)定可靠 - 確保參數(shù)在各種情況下都能正常工作

到此這篇關于前端必知必會的實現(xiàn)URL查詢參數(shù)的方法詳解的文章就介紹到這了,更多相關前端URL查詢參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論