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

React useEffect 異步的實現(xiàn)示例

 更新時間:2026年07月05日 15:10:06   作者:Csvn  
本文主要介紹了React useEffect 異步的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

問題場景

你在做一個搜索下拉組件,用戶輸入關(guān)鍵詞后自動請求后端接口:

// ? 常見寫法,埋了競態(tài)地雷
const SearchSuggest = ({ query }: { query: string }) => {
  const [suggestions, setSuggestions] = useState<string[]>([])

  useEffect(() => {
    fetch(`/api/suggest?q=${query}`)
      .then(res => res.json())
      .then(data => setSuggestions(data))
  }, [query])

  return <ul>{suggestions.map(s => <li key={s}>{s}</li>)}</ul>
}

看起來很對?測試一下:

用戶輸入 "rea" → 請求 A 發(fā)出(耗時 300ms) 用戶繼續(xù)輸入 "react" → 請求 B 發(fā)出(耗時 100ms) 用戶又輸入 "react h" → 請求 C 發(fā)出(耗時 200ms)

結(jié)果:

請求 B 先回來(100ms) → setSuggestions(["react"])
請求 A 才回來(300ms) → setSuggestions(["rea"])   ← ? 把正確的數(shù)據(jù)覆蓋了!
請求 C 回來(200ms) → setSuggestions(["react hook"])

用戶明明搜的是 "react hook",結(jié)果 2 秒內(nèi)頁面閃現(xiàn)三組數(shù)據(jù)后,最終顯示了根本不完整的 ["rea"]。這對用戶體驗是災(zāi)難性的。

原因分析

問題的本質(zhì)是 異步請求的響應(yīng)順序不可控。

  • 請求 B(最新)先返回 → 更新狀態(tài) ?
  • 請求 A(過期)后返回 → 覆蓋狀態(tài) ?

React 不會替你判斷"哪個響應(yīng)是新哪個響應(yīng)舊"。setSuggestions 是個純函數(shù),誰來調(diào)用它,它就被誰覆蓋。

這種現(xiàn)象叫做 Race Condition(競態(tài)條件)——多個異步操作競爭同一個狀態(tài)寫入權(quán),后發(fā)先至造成數(shù)據(jù)不一致。

不止是搜索場景,分頁列表切換、Tab 切換加載、詳情頁路由跳轉(zhuǎn)……只要組件 Props/State 變化觸發(fā)新請求,而舊請求還在 pending,就會出現(xiàn)競態(tài)。

解決方案(3 種,由簡到優(yōu))

方案一:useEffect Cleanup + 布爾守衛(wèi)(最基礎(chǔ))

useEffect(() => {
  let ignore = false  // ?? 關(guān)鍵守衛(wèi)變量

  fetch(`/api/suggest?q=${query}`)
    .then(res => res.json())
    .then(data => {
      if (!ignore) {  // ?? 組件未卸載/query未變化才寫入
        setSuggestions(data)
      }
    })

  return () => {
    ignore = true     // ?? 組件卸載或 query 變化時標(biāo)記過期
  }
}, [query])

原理: 每次 query 變化時,上一次的 cleanup 先執(zhí)行,把 ignore 置為 true。舊請求的 .then 即便執(zhí)行了也不會修改狀態(tài)。

?? 但這個方案只能阻止卸載后的回調(diào),如果舊請求超慢、新請求更快,舊請求仍然可能在新請求之前執(zhí)行 .then 并寫入。單純 ignore 不夠可靠。

方案二:AbortController 取消請求(推薦生產(chǎn)使用)

useEffect(() => {
  const controller = new AbortController()   // ?? 創(chuàng)建取消控制器

  fetch(`/api/suggest?q=${query}`, {
    signal: controller.signal                // ?? 綁定 signal
  })
    .then(res => res.json())
    .then(data => setSuggestions(data))
    .catch(err => {
      if (err.name !== 'AbortError') {       // ?? 忽略主動取消的錯誤
        console.error('請求失敗:', err)
      }
    })

  return () => {
    controller.abort()                       // ?? 組件卸載/query變化時取消上次請求
  }
}, [query])

為什么更好?

方案優(yōu)點缺點
ignore 守衛(wèi)簡單易懂請求仍在背后運行,浪費帶寬
AbortController實際取消 HTTP 請求,節(jié)省資源需要瀏覽器支持(>Chrome 66,無需擔(dān)心)

同時 AbortController 是 100% 可靠的——舊請求的 promise 被 reject,永遠(yuǎn)不可能再調(diào)用 setSuggestions。

用 fetch 默認(rèn)支持,不需要額外庫。如果你用 axios,等價的是 CancelToken(v0.22+ 用 AbortSignal)。

方案三:自定義 Hook 封裝(一勞永逸)

把競態(tài)處理封裝進(jìn) useFetch,全項目通用:

// useFetch.ts
import { useEffect, useState, useRef } from 'react'

function useFetch<T>(url: string): { data: T | null; loading: boolean; error: Error | null } {
  const [data, setData] = useState<T | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    let ignore = false
    const controller = new AbortController()
    setLoading(true)

    fetch(url, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        return res.json() as Promise<T>
      })
      .then(res => {
        if (!ignore) {
          setData(res as T)
          setError(null)
        }
      })
      .catch(err => {
        if (err.name === 'AbortError') return  // 靜默忽略
        if (!ignore) {
          setError(err)
          setData(null)
        }
      })
      .finally(() => {
        if (!ignore) setLoading(false)
      })

    return () => {
      ignore = true
      controller.abort()
    }
  }, [url])

  return { data, loading, error }
}

使用:

const SearchSuggest = ({ query }: { query: string }) => {
  const { data, loading } = useFetch<string[]>(`/api/suggest?q=${query}`)

  if (loading) return <p>加載中…</p>
  return <ul>{data?.map(s => <li key={s}>{s}</li>)}</ul>
}

進(jìn)一步優(yōu)化: 配合 lodash.debounce 或 useDeferredValue 做防抖,減少請求頻率,效果更佳。

實操代碼:完整搜索組件(含防抖 + AbortController)

import { useState, useEffect, useRef, useMemo } from 'react'
import { debounce } from 'lodash-es'

const SearchPage = () => {
  const [input, setInput] = useState('')
  const [query, setQuery] = useState('')
  const [suggestions, setSuggestions] = useState<string[]>([])
  const [loading, setLoading] = useState(false)

  // 防抖:用戶停止輸入 300ms 后設(shè)為正式查詢詞
  const debouncedSetQuery = useMemo(
    () => debounce((val: string) => setQuery(val), 300),
    []
  )

  useEffect(() => {
    debouncedSetQuery(input)
    return () => debouncedSetQuery.cancel()
  }, [input])

  // 請求競態(tài)保護(hù)
  useEffect(() => {
    if (!query) {
      setSuggestions([])
      return
    }

    const controller = new AbortController()
    setLoading(true)

    fetch(`/api/suggest?q=${encodeURIComponent(query)}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => {
        setSuggestions(data)
        setLoading(false)
      })
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err)
          setLoading(false)
        }
      })

    return () => controller.abort()
  }, [query])

  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        placeholder="輸入搜索關(guān)鍵詞…"
      />
      {loading && <span>搜索中…</span>}
      <ul>
        {suggestions.map((s, i) => (
          <li key={i}>{s}</li>
        ))}
      </ul>
    </div>
  )
}

要點總結(jié)

  • 競態(tài)的本質(zhì):異步請求后發(fā)先至,舊請求覆蓋新響應(yīng)
  • ignore 守衛(wèi)最低成本,適合簡單場景;AbortController 才是生產(chǎn)級方案,能真正取消網(wǎng)絡(luò)請求
  • 自定義 useFetch Hook 將競態(tài)處理集中封裝,團(tuán)隊統(tǒng)一使用避免重復(fù)踩坑
  • 配合 防抖(Debounce) 可以減少無效請求,請求量降低 80%+
  • 搜索/分頁/Tab 切換/路由跳轉(zhuǎn)——所有"頻繁變化參數(shù) + 異步請求"的場景都需要考慮競態(tài)處理
  • React 18 的 Suspense + useTransition 未來可能改變這個模式,但傳統(tǒng) useEffect 取數(shù)依然廣泛存在,務(wù)必掌握

到此這篇關(guān)于React useEffect 異步的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)React useEffect 異步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

藁城市| 巴马| 吉木乃县| 离岛区| 牙克石市| 乌拉特后旗| 天等县| 原阳县| 安溪县| 通州市| 龙山县| 万荣县| 临夏市| 渑池县| 汝南县| 义乌市| 巨鹿县| 黑河市| 湾仔区| 清苑县| 黎平县| 沙洋县| 西畴县| 拜城县| 甘泉县| 新巴尔虎左旗| 铜梁县| 云安县| 乌鲁木齐市| 股票| 富川| 土默特右旗| 始兴县| 鸡泽县| 车致| 永年县| 夏邑县| 长丰县| 荣昌县| 榆中县| 墨江|