自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

React 19 出手解決了異步請求的競態(tài)問題,是好事還是壞事?

開發(fā) 前端
競態(tài)問題指的是,當(dāng)我們在交互過程中,由于各種原因?qū)е峦粋€接口短時間之內(nèi)連續(xù)發(fā)送請求,后發(fā)送的請求有可能先得到請求結(jié)果,從而導(dǎo)致數(shù)據(jù)渲染出現(xiàn)預(yù)期之外的錯誤。

是的,又是競態(tài)問題。

在客戶端開發(fā)中,這是一個老生常態(tài)的問題。一個有經(jīng)驗的前端工程師必定是對這個問題的情況與解決方案如數(shù)家珍。因此競態(tài)問題也經(jīng)常在面試的過程中被討論。

競態(tài)問題指的是,當(dāng)我們在交互過程中,由于各種原因?qū)е峦粋€接口短時間之內(nèi)連續(xù)發(fā)送請求,后發(fā)送的請求有可能先得到請求結(jié)果,從而導(dǎo)致數(shù)據(jù)渲染出現(xiàn)預(yù)期之外的錯誤。

有的地方也稱為競態(tài)條件

因為防止重復(fù)執(zhí)行可以有效的解決競態(tài)問題,因此許多時候面試官也會直接在面試中問我們?nèi)绾螌崿F(xiàn)防重。常用的方式就是取消上一次請求,或者設(shè)置狀態(tài)讓按鈕不能連續(xù)點擊,想必各位大佬對這些方案都已經(jīng)非常熟悉,我這里就不展開細說。當(dāng)然,這個問題雖然被經(jīng)常討論,但是要解決好確實需要一點技術(shù)功底。

React 19 結(jié)合 Suspense 也在競態(tài)問題上,提出了一個自己的解決方案。我們結(jié)合新的案例來探討一下這個問題,看完之后大家感受一下這種方式是好是壞。

一、案例

我們先來看一下本次案例要實現(xiàn)的交互效果。如下圖所示。每次點擊會新增一條數(shù)據(jù)到下方的列表中。

我們來實現(xiàn)一下這個效果,首先定義一個用于請求接口的 promise。

const getApi = async () => {
  const res = await fetch('https://api.chucknorris.io/jokes/random')
  return res.json()
}

然后和前面的案例一樣,我們將每次點擊的 api 作為狀態(tài)存儲起來,通過 api 的改變來觸發(fā)更新的執(zhí)行。

const [api, setApi] = useState(null)

與此同時,我們還需要一個數(shù)組作為狀態(tài)來管理列表。

const [list, setList] = useState([])

有了這個數(shù)組之后,我們需要遍歷這個數(shù)組渲染成 UI。

<div className="list">
  {list.map((item, index) => {
    return <div className='item' key={item}>{item}</div>
  })}
</div>

最后需要 loading 顯示的部分,我們使用 Suspense 來完成。

<Suspense fallback={<div>loading...</div>}>
  <Item api={api} setList={setList} />
</Suspense>

需要注意的是,我們這里把 setList 傳遞進入了子組件。這個細節(jié)需要仔細思考我的動因。

我們要考慮的問題是,當(dāng)我們在 Suspense 之外,需要知道請求成功的狀態(tài)和數(shù)據(jù)時,只有在 Suspense 的子組件內(nèi)部才可以獲取到。Suspense 子組件和外面的 Loading 是一個互斥的顯示關(guān)系。

因此,我們要在子組件內(nèi)部去獲取請求成功的數(shù)據(jù)結(jié)果。

const Item = ({api, setList}) => {
  const [show, setShow] = useState(true)
  const joke = api ? use(api) : {value: 'nothing'}

  useEffect(() => {
    if (!api) return
    setList((list) => {
      if (!list.includes(joke.value)) {
        return list.concat(joke.value)
      }
      return list
    })
    setShow(false)
  }, [])

  const __cls = show ? '_03_a_value show' : '_03_a_value'

  return (
    <div className={__cls}>{joke.value}</div>
  )
}

狀態(tài) show 是為了讓最后一條數(shù)據(jù)在列表中顯示,而不在這里顯示。

這里我們使用了 useEffect 來表示子組件渲染完成時需要執(zhí)行的邏輯。注意 React 19 雖然通過很多方式大幅度弱化了 useEffect 的存在感,但是偶爾在合適的時候使用也是必要的。

我在合并 list 的過程中,添加了一個判斷。

setList((list) => {
  if (!list.includes(joke.value)) {
    return list.concat(joke.value)
  }
  return list
})

這個細節(jié)在真實項目開發(fā)中尤其重要。因為 React 19 嚴格模式之下,組件會讓 useEffect 執(zhí)行兩次,以模擬生產(chǎn)環(huán)境的重復(fù)請求問題,因此,我這里做了一個判斷方式同樣的數(shù)據(jù)連續(xù)推送到數(shù)組里,從而導(dǎo)致線上 bug 的發(fā)生。

一個程序員是否經(jīng)驗豐富,是否成熟,都是體現(xiàn)在這些生產(chǎn)環(huán)境的細節(jié)中。

完整代碼如下:

const getApi = async () => {
  const res = await fetch('https://api.chucknorris.io/jokes/random')
  return res.json()
}

export default function Index() {
  const [api, setApi] = useState(null)
  const [list, setList] = useState([])

  function __clickToGetMessage() {
    setApi(getApi())
  }

  return (
    <div>
      <div id='tips'>點擊按鈕新增一條數(shù)據(jù),該數(shù)據(jù)從接口中獲取</div>
      <button onClick={__clickToGetMessage}>新增數(shù)據(jù)</button>
      <div className="content">
        <div className="list">
          {list.map((item, index) => {
            return <div className='item' key={item}>{item}</div>
          })}
        </div>
        
        <Suspense fallback={<div>loading...</div>}>
          <Item api={api} setList={setList} />
        </Suspense>
      </div>
    </div>
  )
}

const Item = ({api, setList}) => {
  const [show, setShow] = useState(true)
  const joke = api ? use(api) : {value: 'nothing'}

  useEffect(() => {
    if (!api) return
    setList((list) => {
      if (!list.includes(joke.value)) {
        return list.concat(joke.value)
      }
      return list
    })
    setShow(false)
  }, [])

  const __cls = show ? '_03_a_value show' : '_03_a_value'

  return (
    <div className={__cls}>{joke.value}</div>
  )
}

這樣之后,我們的目標(biāo)基本就完成了。接下來,我們需要觀察,當(dāng)我惡意重復(fù)點擊按鈕,會發(fā)生什么事情。

二、連續(xù)點擊

惡意連續(xù)點擊之前,我根據(jù)我以往的經(jīng)驗預(yù)測一下可能會發(fā)生什么事情。

首先,多次點擊會導(dǎo)致多次請求,因此數(shù)組中會新增大量的數(shù)據(jù)。

其次,由于請求太密集,那么點擊的先后順序,與請求成功的先后順序不一致,因此列表中的順序也會與點擊順序不同?!父倯B(tài)問題」

那么我們來試著操作一下,看看該案例會有什么反應(yīng)。演示結(jié)果如下,新增一條數(shù)據(jù)時,我連續(xù)點擊了 10 次。

圖片

結(jié)果我們發(fā)現(xiàn),點擊期間,并沒有新的數(shù)據(jù)渲染到頁面上,一直是 loading 的狀態(tài)。

再來看一下此時的請求情況。

請求的順序被嚴格控制了:上一個請求請求成功之后,下一個請求才開始發(fā)生。此時是一個串行的請求過程。

react 19 使用這種思路解決了競態(tài)問題。與此同時,反饋到數(shù)據(jù)上,雖然前面多次的請求已經(jīng)成功,但是對于組件狀態(tài)來說,這個中間過程中一直有請求在發(fā)生,此時 React 認為中間的請求產(chǎn)生的數(shù)據(jù)為無效數(shù)據(jù)。只會把最后一個請求成功的數(shù)據(jù)作為最終的返回結(jié)果。

三、是好是壞

很顯然,僅從 UI 結(jié)果上來說,這樣的處理方式確實是非常合理的,我們不需要過多的干涉數(shù)據(jù)的處理,非常的輕松。但問題是,每次請求都成功發(fā)生。

當(dāng)我點擊 10 次,就會有 10 次請求,由于使用串行的策略來解決競態(tài)問題,導(dǎo)致最后一次的請求結(jié)果需要等待很長實踐才會返回。這無疑極大的降低了開發(fā)體驗。

和取消上一次的請求相比,無論是從體驗上,還是從效率上來說,無疑都是更差的一種方案。

因此,我們可以簡單基于目前的代碼,使用禁用按鈕的方式,來防止重復(fù)請求。

在父組件中定義一個狀態(tài)用于控制按鈕的禁用狀態(tài)。

const [disabled, setDisabled] = useState(false)

并將其傳遞給按鈕 button 組件的 disabled 屬性。

<button 
  disabled={disabled} 
  onClick={__clickToGetMessage}
>新增數(shù)據(jù)</button>

點擊時,我們將其設(shè)置為 true,此時一個新的請求會發(fā)生。

function __clickToGetMessage() {
  setDisabled(true);
  setApi(getApi())
}

請求成功之后,我們在子組件的 useEffect 中,將其設(shè)置為 false。子組件代碼調(diào)整如下:

const Item = ({api, setList, setDisabled}) => {
  const [show, setShow] = useState(true)
  const joke = api ? use(api) : {value: 'nothing'}

  useEffect(() => {
    if (!api) return
+   setDisabled(false)
    setList((list) => {
      if (!list.includes(joke.value)) {
        return list.concat(joke.value)
      }
      return list
    })
    setShow(false)
  }, [])

  const __cls = show ? '_03_a_value show' : '_03_a_value'

  return (
    <div className={__cls}>{joke.value}</div>
  )
}

演示效果如下:

這種方式也可以比較合理的解決競態(tài)問題。

后續(xù)我們通過別的案例,再來演示通過取消上一次的接口請求方式是如何實現(xiàn)的。

責(zé)任編輯:姜華 來源: 這波能反殺
相關(guān)推薦

2015-08-28 09:18:24

火狐Chrome

2012-11-22 09:51:14

2022-11-11 15:49:09

前端JavaScript開發(fā)

2022-11-11 10:22:54

前端Promise

2023-06-27 13:46:00

前端競態(tài)promise

2022-01-26 08:00:55

軟件系統(tǒng)軟件開發(fā)

2022-06-13 06:20:42

setStatereact18

2010-08-24 16:16:52

OracleSun

2024-09-20 08:14:16

2022-06-08 08:01:20

useEffect數(shù)組函數(shù)

2023-03-14 08:01:53

Go開發(fā)原子操作

2020-10-05 22:05:10

Linux系統(tǒng)編程時序競態(tài)

2011-08-03 10:39:06

IOS程序 HTTP 請求

2023-10-20 08:01:08

2021-08-31 07:54:24

TCPIP協(xié)議

2018-03-05 11:24:11

2023-06-09 07:21:23

React前端框架

2023-09-28 08:51:58

Java數(shù)據(jù)

2023-05-31 07:32:37

2025-02-17 12:24:06

點贊
收藏

51CTO技術(shù)棧公眾號