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

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

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

前言

在 React/React Native 開(kāi)發(fā)中,帶有復(fù)雜篩選條件的列表頁(yè)是一個(gè)極其常見(jiàn)的場(chǎng)景??此坪?jiǎn)單的“改變條件 -> 重新請(qǐng)求數(shù)據(jù)”流程,在 React 異步更新機(jī)制的加持下,往往會(huì)衍生出諸多邊緣問(wèn)題,例如請(qǐng)求參數(shù)滯后、重置信號(hào)丟失等。

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

痛點(diǎn)分析:為什么單純的 useState 不夠用?

通常,我們習(xí)慣用 useState 來(lái)保存查詢(xún)條件:

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

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

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

破局思路:State 負(fù)責(zé) UI 渲染,Ref 負(fù)責(zé)邏輯讀取。

核心 API 設(shè)計(jì):State 與 Ref 的雙重奏

為了兼顧“UI 響應(yīng)式”和“隨時(shí)獲取最新參數(shù)”,我們?cè)?useFilters 中引入了 useRef 來(lái)同步追蹤最新的篩選狀態(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(供接口請(qǐng)求使用)
    filtersRef.current = { ...filtersRef.current, [key]: value };
    // 2. 觸發(fā)異步的 State 更新(供 UI 重新渲染)
    setFiltersState(filtersRef.current);
  }, []);

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

通過(guò)這一層封裝:

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

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

在使用習(xí)慣上,我們希望 setFilters 能像原生的 setState 一樣,既支持直接傳入新對(duì)象,也支持傳入基于舊狀態(tài)計(jì)算新?tīng)顟B(tài)的 updater 函數(shù):

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

這里入?yún)⒌念?lèi)型是聯(lián)合類(lèi)型 T | ((prev: T) => T)。在實(shí)現(xiàn)時(shí),我們需要區(qū)分它:

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

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

理論上,TypeScript 應(yīng)當(dāng)能自動(dòng)把 next 的類(lèi)型收窄為函數(shù)。但實(shí)際上并不行。這是 TS 的一個(gè)已知限制:當(dāng)聯(lián)合類(lèi)型中同時(shí)包含「對(duì)象類(lèi)型(T)」與「函數(shù)類(lèi)型」時(shí),因?yàn)楹瘮?shù)本質(zhì)上也是對(duì)象,TS 的類(lèi)型收窄會(huì)趨于保守,導(dǎo)致 next 在分支內(nèi)部依然被視為聯(lián)合類(lèi)型,無(wú)法直接調(diào)用。

有些開(kāi)發(fā)者可能會(huì)直接寫(xiě) (next as any)(...) 強(qiáng)行通過(guò)編譯。但我們極力反對(duì)這么做——as any 會(huì)抹殺掉所有的類(lèi)型檢查。如果未來(lái)函數(shù)簽名調(diào)整為 (prev: T) => Partial<T>any 將無(wú)法拋出錯(cuò)誤,造成運(yùn)行時(shí)隱患。

經(jīng)驗(yàn)總結(jié):精確斷言 as (prev: T) => Tas any 在運(yùn)行時(shí)完全等價(jià),但保留了嚴(yán)密的類(lèi)型校驗(yàn)。處理聯(lián)合類(lèi)型里的函數(shù)分支時(shí),請(qǐng)始終使用精確斷言。

進(jìn)階場(chǎng)景:重置操作與“信號(hào)丟失”之謎

重置條件是一個(gè)非常特殊的動(dòng)作:當(dāng)我們點(diǎn)擊“重置”按鈕時(shí),不僅要清空 UI 上的篩選框,還期望列表自動(dòng)使用初始條件刷新一次。

由于 updateFilter 不應(yīng)與具體的請(qǐng)求邏輯(如 refresh)產(chǎn)生耦合,我們采用了一種**“埋點(diǎn)信號(hào)”**的機(jī)制:

const resetSignalRef = useRef(false);

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

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

在組件端,我們通過(guò) useEffect 響應(yīng)這個(gè)信號(hào):

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

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

上述代碼曾經(jīng)遭遇過(guò)一個(gè)隱蔽的 Bug:當(dāng)用戶(hù)進(jìn)入頁(yè)面后,沒(méi)有修改任何篩選條件,直接點(diǎn)擊“重置”按鈕,列表毫無(wú)反應(yīng)。而且,在此之后的第一次正常搜索,會(huì)意外觸發(fā)兩次請(qǐng)求。

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

如果我們?cè)?resetFilters 里直接賦值原引用:

setFiltersState(initialRef.current);

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

這就導(dǎo)致了災(zāi)難性的連鎖反應(yīng):

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

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

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

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

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

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

完整實(shí)戰(zhàn)演練

最后,來(lái)看看 useFilters 與分頁(yè) 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 內(nèi)部統(tǒng)一通過(guò) filtersRef 讀取參數(shù)
  const fetcher = useCallback((params) => {
    return queryApi({
      ...params,
      ...filtersRef.current,
    });
  }, []);

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

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

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

總結(jié)

一個(gè)看似簡(jiǎn)單的 useFilters,內(nèi)部卻大有乾坤:

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

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

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

相關(guān)文章

  • 使用react的7個(gè)避坑案例小結(jié)

    使用react的7個(gè)避坑案例小結(jié)

    React是個(gè)很受歡迎的前端框架。今天我們探索下React開(kāi)發(fā)者應(yīng)該注意的七點(diǎn),感興趣的小伙伴們可以參考一下
    2021-05-05
  • React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法

    React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法

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

    react-router4 配合webpack require.ensure 實(shí)現(xiàn)異步加載的示例

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

    React中條件渲染的常見(jiàn)方法總結(jié)

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

    關(guān)于react-router中的Prompt組件使用心得

    這篇文章主要介紹了關(guān)于react-router中的Prompt組件學(xué)習(xí)心得,Prompt組件作用是,在用戶(hù)準(zhǔn)備離開(kāi)該頁(yè)面時(shí),?彈出提示,?返回true或者false,?如果為true,?則離開(kāi)頁(yè)面,?如果為false,?則停留在該頁(yè)面,本文結(jié)合示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • React Hooks如何主動(dòng)更新Hooks組件

    React Hooks如何主動(dòng)更新Hooks組件

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

    React類(lèi)組件更新的底層邏輯案例詳解

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

    react native 原生模塊橋接的簡(jiǎn)單說(shuō)明小結(jié)

    這篇文章主要介紹了react native 原生模塊橋接的簡(jiǎn)單說(shuō)明小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • 詳解前端路由實(shí)現(xiàn)與react-router使用姿勢(shì)

    詳解前端路由實(shí)現(xiàn)與react-router使用姿勢(shì)

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

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

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

最新評(píng)論

白山市| 江永县| 南丰县| 论坛| 濉溪县| 南昌市| 彭阳县| 深水埗区| 额尔古纳市| 河池市| 台湾省| 苏尼特左旗| 广宗县| 惠来县| 武隆县| 莎车县| 石渠县| 清镇市| 平武县| 垦利县| 建始县| 东乌珠穆沁旗| 通渭县| 潮安县| 鄂州市| 望都县| 通榆县| 梁平县| 固阳县| 炎陵县| 全椒县| 宁南县| 长岭县| 米脂县| 凌海市| 鄢陵县| 金沙县| 特克斯县| 西宁市| 鄢陵县| 游戏|