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

React自定義Hook之如何優(yōu)雅管理復雜列表的篩選狀態(tài)

 更新時間:2026年05月14日 10:38:11   作者:皮蛋小精靈  
在Web開發(fā)中,渲染包含數千甚至上萬條數據的列表是常見需求,這篇文章主要介紹了React自定義Hook之如何優(yōu)雅管理復雜列表篩選狀態(tài)的相關資料,文中給出了詳細的代碼示例,需要的朋友可以參考下

前言

在 React/React Native 開發(fā)中,帶有復雜篩選條件的列表頁是一個極其常見的場景??此坪唵蔚?ldquo;改變條件 -> 重新請求數據”流程,在 React 異步更新機制的加持下,往往會衍生出諸多邊緣問題,例如請求參數滯后、重置信號丟失等。

今天我們通過實現一個專門用于管理列表篩選狀態(tài)的 Hook —— useFilters,來聊聊如何優(yōu)雅地解決這些痛點,并在實踐中規(guī)避 TypeScript 和 React State 機制的隱藏陷阱。

痛點分析:為什么單純的 useState 不夠用?

通常,我們習慣用 useState 來保存查詢條件:

const [filters, setFilters] = useState({ keyword: '', type: 1 });

const updateFilter = (key, value) => {
  setFilters(prev => ({ ...prev, [key]: value }));
  refresh(); // ? 這里拿到的依舊是舊 filters 參數
};

React 的 setState 是異步的,如果在更新狀態(tài)后立即觸發(fā)數據刷新,網絡請求內部讀到的依然是未更新的舊狀態(tài)。為了解決這個問題,許多開發(fā)者會采用 useEffect 監(jiān)聽狀態(tài)變化來觸發(fā)請求。但在復雜業(yè)務邏輯中(包含防抖、多種觸發(fā)源),過度依賴 useEffect 會讓數據流變得難以追蹤。

破局思路:State 負責 UI 渲染,Ref 負責邏輯讀取。

核心 API 設計:State 與 Ref 的雙重奏

為了兼顧“UI 響應式”和“隨時獲取最新參數”,我們在 useFilters 中引入了 useRef 來同步追蹤最新的篩選狀態(tài)。

import { useCallback, useRef, useState } from 'react';

export function useFilters<T extends object>(initial: T) {
  const [filters, setFiltersState] = useState<T>(initial);
  const filtersRef = useRef<T>(initial);

  const updateFilter = useCallback(<K extends keyof T>(key: K, value: T[K]) => {
    // 1. 立即更新同步的 Ref(供接口請求使用)
    filtersRef.current = { ...filtersRef.current, [key]: value };
    // 2. 觸發(fā)異步的 State 更新(供 UI 重新渲染)
    setFiltersState(filtersRef.current);
  }, []);

  // ...
  return { filters, filtersRef, updateFilter };
}

通過這一層封裝:

  • filters:響應式狀態(tài)對象。用于驅動 UI 渲染(如輸入框回顯、Picker 選中態(tài))。
  • filtersRef:同步引用對象。閉包或外部函數(如 fetcher)隨時可通過 filtersRef.current 獲取無延遲的最新參數,從此告別參數滯后。

深入細節(jié):TypeScript 聯(lián)合類型的類型收窄陷阱

在使用習慣上,我們希望 setFilters 能像原生的 setState 一樣,既支持直接傳入新對象,也支持傳入基于舊狀態(tài)計算新狀態(tài)的 updater 函數:

// 支持形態(tài) A
setFilters({ keyword: '張三' });
// 支持形態(tài) B
setFilters(prev => ({ ...prev, keyword: '張三' }));

這里入參的類型是聯(lián)合類型 T | ((prev: T) => T)。在實現時,我們需要區(qū)分它:

const setFilters = useCallback((next: T | ((prev: T) => T)) => {
  if (typeof next === 'function') {
    // ?? 注意這里的類型斷言
    filtersRef.current = (next as (prev: T) => T)(filtersRef.current);
  } else {
    filtersRef.current = next;
  }
  setFiltersState(filtersRef.current);
}, []);

為什么在 typeof next === 'function' 分支里還要寫 as (prev: T) => T?

理論上,TypeScript 應當能自動把 next 的類型收窄為函數。但實際上并不行。這是 TS 的一個已知限制:當聯(lián)合類型中同時包含「對象類型(T)」與「函數類型」時,因為函數本質上也是對象,TS 的類型收窄會趨于保守,導致 next 在分支內部依然被視為聯(lián)合類型,無法直接調用。

有些開發(fā)者可能會直接寫 (next as any)(...) 強行通過編譯。但我們極力反對這么做——as any 會抹殺掉所有的類型檢查。如果未來函數簽名調整為 (prev: T) => Partial<T>,any 將無法拋出錯誤,造成運行時隱患。

經驗總結:精確斷言 as (prev: T) => Tas any 在運行時完全等價,但保留了嚴密的類型校驗。處理聯(lián)合類型里的函數分支時,請始終使用精確斷言。

進階場景:重置操作與“信號丟失”之謎

重置條件是一個非常特殊的動作:當我們點擊“重置”按鈕時,不僅要清空 UI 上的篩選框,還期望列表自動使用初始條件刷新一次

由于 updateFilter 不應與具體的請求邏輯(如 refresh)產生耦合,我們采用了一種**“埋點信號”**的機制:

const resetSignalRef = useRef(false);

const resetFilters = useCallback(() => {
  const fresh = { ...initialRef.current };
  filtersRef.current = fresh;
  setFiltersState(fresh);
  resetSignalRef.current = true; // 埋下信號
}, []);

const consumeResetSignal = useCallback(() => {
  if (resetSignalRef.current) {
    resetSignalRef.current = false; // 消費信號
    return true;
  }
  return false;
}, []);

在組件端,我們通過 useEffect 響應這個信號:

useEffect(() => {
  if (consumeResetSignal()) {
    refresh();
  }
}, [filters, refresh, consumeResetSignal]); // 依賴 filters 變化觸發(fā)

隱藏的 Bug:React 的狀態(tài)復用優(yōu)化

上述代碼曾經遭遇過一個隱蔽的 Bug:當用戶進入頁面后,沒有修改任何篩選條件,直接點擊“重置”按鈕,列表毫無反應。而且,在此之后的第一次正常搜索,會意外觸發(fā)兩次請求。

問題出在哪里?出在 React 的內置優(yōu)化上。

如果我們在 resetFilters 里直接賦值原引用:

setFiltersState(initialRef.current);

當前狀態(tài) filters 已經等于初始狀態(tài)時,Object.is(newState, currentState) 成立。React 判定狀態(tài)無實質改變,直接跳過了本次重新渲染(Bailout),關聯(lián)的 useEffect 也不會執(zhí)行。

這就導致了災難性的連鎖反應:

  1. resetSignalRef.current = true 被悄悄埋下。
  2. Effect 沒執(zhí)行,信號沒人消費,滯留在了內存里。
  3. 隨后用戶正常輸入搜索,filters 改變,Effect 執(zhí)行,讀到了這個滯留的信號,造成意外的 refresh 動作。

破局之道:0 成本的淺拷貝

解決辦法異常優(yōu)雅,只需保證每次重置都產生一個新引用:

// 淺拷貝產生新引用,打破 Object.is 判斷
const fresh = { ...initialRef.current };
setFiltersState(fresh);

為什么不用深拷貝(Deep Clone)?

  1. React 只看外層引用:觸發(fā)重繪只需最外層對象的內存地址改變。
  2. 保護下游性能優(yōu)化:淺拷貝復用了內層引用(如數組字段)。依賴內層字段的被 React.memo 包裹的子組件,不會發(fā)生無意義的重新計算,完美契合了 React 不可變(Immutable) 的設計哲學。

完整實戰(zhàn)演練

最后,來看看 useFilters 與分頁 Hook usePaginatedList 的絲滑配合:

import { useCallback, useEffect } from 'react';
import { useFilters, usePaginatedList } from '@/hooks';

const INITIAL = { keyword: '', type: 1 };

const MyList = () => {
  const { filters, filtersRef, updateFilter, resetFilters, consumeResetSignal } = useFilters(INITIAL);

  // 1. fetcher 內部統(tǒng)一通過 filtersRef 讀取參數
  const fetcher = useCallback((params) => {
    return queryApi({
      ...params,
      ...filtersRef.current,
    });
  }, []);

  const { refresh, data } = usePaginatedList({ fetcher });

  // 2. 統(tǒng)一響應重置信號
  useEffect(() => {
    if (consumeResetSignal()) {
      refresh();
    }
  }, [filters, refresh, consumeResetSignal]);

  return (
    <View>
      <SearchBar
        value={filters.keyword}
        onChange={(v) => updateFilter('keyword', v)}
        onSubmit={refresh}
        onReset={resetFilters}
      />
      {/* 列表渲染邏輯... */}
    </View>
  );
};

總結

一個看似簡單的 useFilters,內部卻大有乾坤:

  1. State 渲染,Ref 獲取最新值,打破了 React 異步更新帶來的延遲限制。
  2. 面對聯(lián)合類型斷言,摒棄 as any,堅持精確的函數簽名斷言。
  3. 警惕 React setState 的**“引用相等跳過更新”機制**,利用一行淺拷貝巧妙消除跨渲染周期通信的隱藏 Bug。

良好的前端架構不僅僅在于使用高級的框架,更在于對框架底層的邊界情況有著深入且清晰的掌控。希望這個小小的自定義 Hook 實戰(zhàn)能給你帶來啟發(fā)!

到此這篇關于React自定義Hook之如何優(yōu)雅管理復雜列表的篩選狀態(tài)的文章就介紹到這了,更多相關React自定義Hook篩選狀態(tài)內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 使用react的7個避坑案例小結

    使用react的7個避坑案例小結

    React是個很受歡迎的前端框架。今天我們探索下React開發(fā)者應該注意的七點,感興趣的小伙伴們可以參考一下
    2021-05-05
  • React Native自定義標題欄組件的實現方法

    React Native自定義標題欄組件的實現方法

    今天講一下如何實現自定義標題欄組件,我們都知道RN有一個優(yōu)點就是可以組件化,在需要使用該組件的地方直接引用并傳遞一些參數就可以了,這種方式確實提高了開發(fā)效率。對React Native自定義標題欄組件的實現方法感興趣的朋友參考下
    2017-01-01
  • react-router4 配合webpack require.ensure 實現異步加載的示例

    react-router4 配合webpack require.ensure 實現異步加載的示例

    本篇文章主要介紹了react-router4 配合webpack require.ensure 實現異步加載的示例,非常具有實用價值,需要的朋友可以參考下
    2018-01-01
  • React中條件渲染的常見方法總結

    React中條件渲染的常見方法總結

    條件渲染在React開發(fā)中非常重要的功能,它允許開發(fā)人員根據條件控制渲染的內容,在創(chuàng)建動態(tài)和交互式用戶界面方面發(fā)揮著至關重要的作用,本文總結了常用的的條件渲染方法,需要的朋友可以參考下
    2024-01-01
  • 關于react-router中的Prompt組件使用心得

    關于react-router中的Prompt組件使用心得

    這篇文章主要介紹了關于react-router中的Prompt組件學習心得,Prompt組件作用是,在用戶準備離開該頁面時,?彈出提示,?返回true或者false,?如果為true,?則離開頁面,?如果為false,?則停留在該頁面,本文結合示例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-01-01
  • React Hooks如何主動更新Hooks組件

    React Hooks如何主動更新Hooks組件

    這篇文章主要介紹了React Hooks如何主動更新Hooks組件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • React類組件更新的底層邏輯案例詳解

    React類組件更新的底層邏輯案例詳解

    這篇文章主要介紹了React類組件的更新過程,包括組件初始化、更新和卸載的生命周期方法,以及如何使用setState和forceUpdate來控制視圖的更新,感興趣的朋友一起看看吧
    2025-01-01
  • react native 原生模塊橋接的簡單說明小結

    react native 原生模塊橋接的簡單說明小結

    這篇文章主要介紹了react native 原生模塊橋接的簡單說明小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-02-02
  • 詳解前端路由實現與react-router使用姿勢

    詳解前端路由實現與react-router使用姿勢

    本篇文章主要介紹了詳解前端路由和react-router使用姿勢,詳細的介紹了react-router的用法,有興趣的可以了解一下
    2017-08-08
  • React大屏可視化腳手架教程示例

    React大屏可視化腳手架教程示例

    這篇文章主要為大家介紹了React大屏可視化腳手架教程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06

最新評論

贵港市| 高清| 旅游| 荔波县| 黔西| 余干县| 长阳| 抚宁县| 临高县| 南涧| 顺昌县| 罗甸县| 杂多县| 新蔡县| 娄底市| 易门县| 鹿泉市| 台南市| 永靖县| 齐齐哈尔市| 甘孜县| 天津市| 宜州市| 南乐县| 黑龙江省| 农安县| 克东县| 海原县| 沅江市| 瓦房店市| 信阳市| 抚顺县| 营山县| 临桂县| 宜兴市| 博客| 万山特区| 池州市| 丰都县| 深泽县| 淮南市|