溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React競態(tài)條件Race?Condition問題怎么解決

發(fā)布時間:2022-11-09 09:28:49 來源:億速云 閱讀:159 作者:iii 欄目:開發(fā)技術

本篇內(nèi)容主要講解“React競態(tài)條件Race Condition問題怎么解決”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“React競態(tài)條件Race Condition問題怎么解決”吧!

    競態(tài)條件

    Race Condition,中文譯為競態(tài)條件,旨在描述一個系統(tǒng)或者進程的輸出,依賴于不受控制事件的出現(xiàn)順序或者出現(xiàn)時機。

    舉個簡單的例子:

    if (x == 5) // The "Check"
    {
       y = x * 2; // The "Act"
       // 如果其他的線程在 "if (x == 5)" and "y = x * 2" 執(zhí)行之間更改了 x 的值
       // y 就可能不等于 10.
    }

    你可能想,JavaScript 是單線程,怎么可能出現(xiàn)這個問題?

    React 與競態(tài)條件

    確實如此,但前端有異步渲染,所以競態(tài)條件依然有可能出現(xiàn),我們舉個 React 中常見的例子。

    這是一個非常典型的數(shù)據(jù)獲取代碼:

    class Article extends Component {
      state = {
        article: null
      };
      componentDidMount() {
        this.fetchData(this.props.id);
      }
      async fetchData(id) {
        const article = await API.fetchArticle(id);
        this.setState({ article });
      }
      // ...
    }

    看起來沒什么問題,但這段代碼還沒有實現(xiàn)數(shù)據(jù)更新,我們再改一下:

    class Article extends Component {
      state = {
        article: null
      };
      componentDidMount() {
        this.fetchData(this.props.id);
      }
      componentDidUpdate(prevProps) {
        if (prevProps.id !== this.props.id) {
          this.fetchData(this.props.id);
        }
      }
      async fetchData(id) {
        const article = await API.fetchArticle(id);
        this.setState({ article });
      }
      // ...
    }

    當組件傳入新的 id 時,我們根據(jù)新的 id 請求數(shù)據(jù),然后 setState 最新獲取的數(shù)據(jù)。

    這時就可能出現(xiàn)競態(tài)條件,比如用戶選完立刻點擊下一頁,我們請求 id 為 1 的數(shù)據(jù),緊接著請求 id 為 2 的數(shù)據(jù),但因為網(wǎng)絡或者接口處理等原因,id為 2 的接口提前返回,便會先展示 id 為 2 的數(shù)據(jù),再展示 id 為 1 的數(shù)據(jù),這就導致了錯誤。

    我們可以想想遇到這種問題的場景,比如類似于百度的搜索功能,切換 tab 等場景,雖然我們也可以使用諸如 debounce 的方式來緩解,但效果還是會差點,比如使用 debounce,用戶在輸入搜索詞的時候,展示內(nèi)容會長期處于空白狀態(tài),對于用戶體驗而言,我們可以做的更好。

    那么我們該如何解決呢?一種是在切換的時候取消請求,還有一種是借助一個布爾值來判斷是否需要更新,比如這樣:

    function Article({ id }) {
      const [article, setArticle] = useState(null);
      useEffect(() => {
        let didCancel = false;
        async function fetchData() {
          const article = await API.fetchArticle(id);
          // 如果 didCancel 為 true 說明用戶已經(jīng)取消了
          if (!didCancel) {
            setArticle(article);
          }
        }
        fetchData();
        // 執(zhí)行下一個 effect 之前會執(zhí)行
        return () => {
          didCancel = true;
        };
      }, [id]);
      // ...
    }

    當然你也可以用 ahooks 中的 useRequest,它的內(nèi)部有一個 ref 變量記錄最新的 promise,也可以解決 Race Condition 的問題:

    function Article({ id }) {
      const { data, loading, error} = useRequest(() => fetchArticle(id), {
      	refreshDeps: [id]
      });
      // ...
    }

    效果演示

    問題復現(xiàn)

    為了方便大家自己測試這個問題,我們提供相對完整的代碼。以 《Avoiding Race Conditions when Fetching Data with React Hooks》中的例子為例,出現(xiàn) Race Condition 問題的代碼如下:

    const fakeFetch = person => {
      return new Promise(res => {
        setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
      });
    };
    const App = () => {
      const [data, setData] = useState('');
      const [loading, setLoading] = useState(false);
      const [person, setPerson] = useState(null);
      useEffect(() => {
        setLoading(true);
        fakeFetch(person).then(data => {
          	setData(data);
          	setLoading(false);
        });
      }, [person]);
        const handleClick = (name) => () => {
        	setPerson(name)
        }
      return (
        <Fragment>
          <button onClick={handleClick('Nick')}>Nick's Profile</button>
          <button onClick={handleClick('Deb')}>Deb's Profile</button>
          <button onClick={handleClick('Joe')}>Joe's Profile</button>
          {person && (
            <Fragment>
              <h2>{person}</h2>
              <p>{loading ? 'Loading...' : data}</p>
            </Fragment>
          )}
        </Fragment>
      );
    };

    我們實現(xiàn)了一個 fakeFetch函數(shù),用于模擬接口的返回,具體返回的時間為 Math.random() * 5000),用于模擬數(shù)據(jù)的隨機返回。

    布爾值解決

    現(xiàn)在,我們嘗試用一個 canceled 布爾值解決:

    const App = () => {
      const [data, setData] = useState('');
      const [loading, setLoading] = useState(false);
      const [person, setPerson] = useState(null);
      useEffect(() => {
        let canceled = false;
        setLoading(true);
        fakeFetch(person).then(data => {
          if (!canceled) {
            setData(data);
            setLoading(false);
          }
        });
        return () => (canceled = true);
      }, [person]);
      return (
        <Fragment>
          <button onClick={() => setPerson('Nick')}>Nick's Profile</button>
          <button onClick={() => setPerson('Deb')}>Deb's Profile</button>
          <button onClick={() => setPerson('Joe')}>Joe's Profile</button>
          {person && (
          <Fragment>
            <h2>{person}</h2>
            <p>{loading ? 'Loading...' : data}</p>
          </Fragment>
        )}
        </Fragment>
      );
    };

    實現(xiàn)效果如下:

    React競態(tài)條件Race?Condition問題怎么解決

    即便接口沒有按照順序返回,依然不影響最終顯示的數(shù)據(jù)。

    useRequest 解決

    我們也可以借助 ahooks 的 useRequest 方法,修改后的代碼如下:

    const App2 = () => {
      const [person, setPerson] = useState('Nick');
      const { data, loading} = useRequest(() => fakeFetch(person), {
        refreshDeps: [person],
      });
      const handleClick = (name) => () => {
        setPerson(name)
      }
      return (
        <Fragment>
          <button onClick={handleClick('Nick')}>Nick's Profile</button>
          <button onClick={handleClick('Deb')}>Deb's Profile</button>
          <button onClick={() => setPerson('Joe')}>Joe's Profile</button>
          {person && (
          <Fragment>
            <h2>{person}</h2>
            <p>{loading ? 'Loading...' : data}</p>
          </Fragment>
        )}
        </Fragment>
      );
    };

    代碼效果如上,就不重復錄制了。

    考慮到部分同學可能會對 useRequest 的使用感到困惑,我們簡單介紹一下 useRequest的使用:

    useRequest 的第一個參數(shù)是一個異步函數(shù),在組件初次加載時,會自動觸發(fā)該函數(shù)執(zhí)行。同時自動管理該異步函數(shù)的 loadingdataerror 等狀態(tài)。

    useRequest 同樣提供了一個 options.refreshDeps 參數(shù),當它的值變化后,會重新觸發(fā)請求。

    const [userId, setUserId] = useState('1');
    const { data, run } = useRequest(() => getUserSchool(userId), {
      refreshDeps: [userId],
    });

    上面的示例代碼,useRequest 會在初始化和 userId 變化時,觸發(fā)函數(shù)執(zhí)行。與下面代碼實現(xiàn)功能完全一致:

    const [userId, setUserId] = useState('1');
    const { data, refresh } = useRequest(() => getUserSchool(userId));
    useEffect(() => {
      refresh();
    }, [userId]);

    Suspense

    這篇之所以講 Race Condition,主要還是為了引入講解 Suspense,借助 Suspense,我們同樣可以解決 Race Condition:

    // 實現(xiàn)參考的 React 官方示例:https://codesandbox.io/s/infallible-feather-xjtbu
    function wrapPromise(promise) {
      let status = "pending";
      let result;
      let suspender = promise.then(
        r => {
          status = "success";
          result = r;
        },
        e => {
          status = "error";
          result = e;
        }
      );
      return {
        read() {
          if (status === "pending") {
            throw suspender;
          } else if (status === "error") {
            throw result;
          } else if (status === "success") {
            return result;
          }
        }
      };
    }
    const fakeFetch = person => {
      return new Promise(res => {
        setTimeout(() => res(`${person}'s data`), Math.random() * 5000);
      });
    };
    function fetchData(userId) {
      return wrapPromise(fakeFetch(userId))
    }
    const initialResource = fetchData('Nick');
    function User({ resource }) {
      const data = resource.read();
      return <p>{ data }</p>
    }
    const App = () => {
      const [person, setPerson] = useState('Nick');
      const [resource, setResource] = useState(initialResource);
      const handleClick = (name) => () => {
        setPerson(name)
        setResource(fetchData(name));
      }
      return (
        <Fragment>
          <button onClick={handleClick('Nick')}>Nick's Profile</button>
          <button onClick={handleClick('Deb')}>Deb's Profile</button>
    	    <button onClick={handleClick('Joe')}>Joe's Profile</button>
          <Fragment>
            <h2>{person}</h2>
            <Suspense fallback={'loading'}>
              <User resource={resource} />
            </Suspense>
          </Fragment>
        </Fragment>
      );
    };

    到此,相信大家對“React競態(tài)條件Race Condition問題怎么解決”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

    AI