最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

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

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

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

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

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

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

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

瀏覽器 API

如果想獲取瀏覽器地址欄上的數(shù)據(jù),需要調(diào)用瀏覽器的 History APIwindow.location 對(duì)象 這是瀏覽器提供的核心 API,包含了當(dāng)前 URL 的信息,假設(shè)當(dāng)前 URL 是:https://example.com/path?search=test#hash

那么訪問 window.location 則可以獲得如下信息:

window.location.pathname  // "/path"
window.location.search   // "?search=test"
window.location.hash     // "#hash"

另外一個(gè)核心的 對(duì)象是 window.histroy 這個(gè)對(duì)象是操作瀏覽器的跳轉(zhuǎn)的,比如 history.back() 就是用戶點(diǎn)擊了下圖左側(cè)箭頭的效果。history.pushState(state, '', '/new-path') 就是模擬用戶修改 上面提到的 window.location.pathname 跳轉(zhuǎn)到了新的頁面。

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

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

那在juejin 點(diǎn)擊前后的時(shí)候,就能看到j(luò)uejin 頁面跳轉(zhuǎn)時(shí)傳遞的一些參數(shù)和變化

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

路由守衛(wèi)

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

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

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

先實(shí)現(xiàn)注冊(cè)系統(tǒng)

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

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

下面我們給 /user 注冊(cè)一個(gè)守衛(wèi),即:當(dāng)用戶訪問 /user 時(shí),運(yùn)行一個(gè)檢測(cè)邏輯驗(yàn)證用戶是否可以訪問。

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

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

export function setupRouteGuard() {
  // 初始狀態(tài)檢查
  checkCurrentRoute();
  
  // 監(jiān)聽 popstate 事件(瀏覽器前進(jìn)/后退)
  window.addEventListener('popstate', checkCurrentRoute);
  
  // 攔截 history.pushState
  const originalPushState = history.pushState;
  history.pushState = function(...args) {
    const result = originalPushState.apply(this, args);
    checkCurrentRoute();
    return result;
  };
}

通過改寫后,每當(dāng)通過 pushState 跳轉(zhuǎn)頁面時(shí),就會(huì)調(diào) checkCurrentRoute 的方法, 該方法中

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

實(shí)現(xiàn) useNavigate

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

這里補(bǔ)充一個(gè)知識(shí),瀏覽器 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`);

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

實(shí)現(xiàn) useLocation

到目前為止我們只解決了 地址欄 URL 改變的問題,這時(shí)我們的程序其實(shí)是沒辦法知道 URL 變了的,因?yàn)?window.location.path 是一個(gè)普通變量,無法通知我們的程序,我們需要實(shí)現(xiàn)一個(gè) 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 事件(瀏覽器前進(jìn)/后退時(shí)觸發(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,我們可以把他變成篩選項(xiàng)并發(fā)起請(qǐng)求,

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ā)起請(qǐng)求
    if (keyword || price || brand) {
      fetchProducts();
    }
  }, [searchParams]); // 當(dāng) URL 參數(shù)變化時(shí)重新請(qǐng)求

  const fetchProducts = async () => {
    try {
      setLoading(true);
      // 構(gòu)建查詢參數(shù)
      const params = new URLSearchParams(searchParams);
      
      // 發(fā)起請(qǐng)求
      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>搜索結(jié)果</h2>
      {loading ? (
        <div>加載中...</div>
      ) : (
        <div className="product-list">
          {products.map(product => (
            <div key={product.id} className="product-item">
              <h3>{product.name}</h3>
              <p>價(jià)格: ¥{product.price}</p>
              <p>品牌: {product.brand}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

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

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

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

這里貼一個(gè)簡(jiǎn)化實(shí)現(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)化歷史監(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 時(shí),我們經(jīng)常需要通過 useLocation 獲取路由傳遞的 state 數(shù)據(jù):

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

const location = useLocation();

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

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

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

解決方案:

使用 useMemo 優(yōu)化 用來監(jiān)聽真正內(nèi)容的變更:我們可以使用 useMemo 來記憶化 state 對(duì)象,只在真正需要更新的屬性發(fā)生變化時(shí)才創(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 來比較對(duì)象的值而不是引用(不優(yōu)雅,也有局限性)

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

場(chǎng)景

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

如果沒有會(huì)怎么樣?

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

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

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

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

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

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

相關(guān)文章

最新評(píng)論

项城市| 繁昌县| 辽中县| 巴中市| 彝良县| 蓬安县| 简阳市| 韶山市| 太康县| 平和县| 广西| 武强县| 屯留县| 鄂温| 新竹县| 长武县| 古田县| 合作市| 新野县| 阿勒泰市| 河曲县| 渝北区| 牡丹江市| 黄大仙区| 荣成市| 噶尔县| 玉田县| 永城市| 旬邑县| 九龙城区| 临海市| 西充县| 旬邑县| 大化| 太原市| 抚顺县| 襄樊市| 枣阳市| 鹿邑县| 溧水县| 宁安市|