React Hook 搞定 Race Condition

歡迎關注個人公衆號睿Talk,獲取我最新的文章:
clipboard.pngjavascript

1、前言

Race Condition 是開發中常常遇到的問題,好比查詢天氣的時候,先輸入「北京」,再輸入「深圳」,這時將發起 2 個請求。很可第一個請求花的時間比第二個請求長,若是不作處理,最終看到的是北京的天氣,而不是深圳。本文要討論的就是如何使用 React Hooks 解決這種問題。java

2、場景

假設有以下搜索的場景,當用戶輸入關鍵字的時候,系統根據關鍵字搜索,而後實時顯示搜索結果。代碼以下:react

// 模擬網絡請求,能夠指定延遲時間
function getData(data, delay) {
  return new Promise(resolve => {
    setTimeout(()=>{
      resolve(`${data} result`);
    }, delay);
  })
}

function App() {
  const [query, setQuery] = useState('react');
  const [result, setResult] = useState();

  useEffect(() => {
    const fetchData = async () => {
      // 搜索 react1 時(第一個請求),2 秒後返回,其他 500 毫秒後返回
      const delay = query === 'react1' ? 2000 : 500;
      
      const result = await getData(query, delay);
      
      setResult(result); 
    }

    fetchData();
  }, [query]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <div>
        result: <span>{result}</span>
      </div>
    </Fragment>
  );
}

當咱們輸入react12345時,能夠看到最終的結果是react1 result,而咱們指望看到的結果是react12345 resultsegmentfault

這現象的緣由是更新數據的時候,沒有對結果的有效性進行判斷,用過時的數據覆蓋了最新的數據。網絡

3、解決方案

解決方式很簡單,就是在更新數據前判斷其有效性,改造一下useEffect部分的代碼:async

useEffect(() => {
  // 有效性標識
  let didCancel = false;

  const fetchData = async () => {
    const delay = query === 'react1' ? 2000 : 500;
    const result = await getData(query, delay);

    // 更新數據前判斷有效性
    if (!didCancel) {
      setResult(result); 
    }
  }

  fetchData();

  return () => {
    // query 變動時設置數據失效
    didCancel = true;
  }
}, [query]);

這裏利用了useEffect數據清理的特性,當 query 發生變化時,將以前的數據請求設置爲失效。fetch

4、更好的方案

上面這種方案雖然解決了問題,但體驗並很差。在輸入數據的過程當中,並不能看到輸入過程當中返回的結果,只能看到最終的結果。咱們指望的效果是輸入過程當中能實時展現有效的結果。再改造下代碼:spa

// 請求序號
let seqenceId = 0;
// 上一個有效請求的序號
let lastId = 0;

function App() {
  const [query, setQuery] = useState('react');
  const [result, setResult] = useState();

  useEffect(() => {
    const fetchData = async () => {
      // 發起一個請求時,序號加 1
      const curId = ++seqenceId;

      const delay = query === 'react1' ? 2000 : 500;
      const result = await getData(query,delay);

      // 只展現序號比上一個有效序號大的數據
      if (curId > lastId) {
        setResult(result); 
        lastId = curId;
      } else {
        console.log(`discard ${result}`); 
      }
    }

    fetchData();
  }, [query]);

  return (
    ...
  );
}

這裏引入了 2 個變量,一個變量用來標識當前請求的序號,另外一個記錄上一個有效請求的序號。只有序號比上一個有效序號大的時候,才展現數據。這樣就保證了舊的請求不會覆蓋新的請求。code

最終的代碼能夠看這裏ip

5、總結

本文討論了開發過程當中常常遇到的 Race Condition 問題,結合 React Hooks 給出了 2 種解題思路。一種是隻展現最終的結果,隱藏過程結果;另外一種是將過程當中有效的結果也展現出來。能夠根據實際的應用場景按需選用。

相關文章
相關標籤/搜索