前言

在看 yt 在介紹為啥不建議用 useEffect 去 fetch data 時

從官網他給的解法我 get 不到

稍微研究之後才發現我對 useEffect 不夠瞭解

所以稍微記錄一下

一般使用 useEffect 去 fetch data

Code 如下

import React, { useState, useEffect } from "react";

export function App(props) {
  const [person, setPerson] = useState(1);
  const [bio, setBio] = useState(null);

  const handlePeople = () => {
    setPerson(2);
  };

  const fakeFetchData = (num) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // 模擬成功返回數據
        const success = true; // 將其設為 false 以模擬錯誤
        if (success) {
          const db = {
            1: "Alice",
            2: "Bob",
          };
          resolve({ message: db[num] });
        } else {
          reject("Error fetching data");
        }
      }, 2000); // 模擬 2 秒的延遲
    });
  };

  useEffect(() => {
    setBio(null);
    fakeFetchData(person).then((result) => {
      setBio(result.message);
    });
  }, [person]);

  return (
    <div className="App">
      <h1>Hello React.</h1>
      <h2>{bio}</h2>
      <button onClick={handlePeople}>switch</button>
    </div>
  );
}

其實這個寫法非常不難懂也不難寫

以有在使用或是對 React 有些了解的人來說

不過他會有一個問題

就是如果第一次 call api 也就是第一次渲染還沒 response

我就點了 switch 按鈕所以會 call 第二次的 api 且返回得值會和第一次不一樣

結果出現第二次 api call 比第一次 api call 還要早 response

就會出現 state 和 response 資料對不起來的情況

就叫做 race condition

解法

Code 如下

(這裡有特意做一個第二次會比第一次 response 快的機制)

import React, { useState, useEffect } from "react";

export function App(props) {
  const [person, setPerson] = useState(1);
  const [bio, setBio] = useState(null);

  const handlePeople = () => {
    setPerson(2);
  };

  const fakeFetchData = (num, defer = 5000) => {
    return new Promise((resolve, reject) => {
      if (num === 2) defer = 1000;
      setTimeout(() => {
        // 模擬成功返回數據
        const success = true; // 將其設為 false 以模擬錯誤
        if (success) {
          const db = {
            1: "Alice",
            2: "Bob",
          };
          resolve({ message: db[num] });
        } else {
          reject("Error fetching data");
        }
      }, defer); // 模擬 2 秒的延遲
    });
  };

  useEffect(() => {
    let ignore = false;
    setBio(null);
    fakeFetchData(person).then((result) => {
      if (!ignore) {
        setBio(result.message);
      }
    });
    return () => {
      ignore = true;
    };
  }, [person]);

  return (
    <div className="App">
      <h1>Hello React.</h1>
      <h2>{bio}</h2>
      <button onClick={handlePeople}>switch</button>
    </div>
  );
}

簡單來說就是多了 ignore 這個 flag

原本很 confused 這個 ignore 為何可以預防這件事

其實涵蓋了兩個概念

  • useEffect return 的時機
  • 閉包運作原理

直接說明解決問題發生的順序

他的步驟是這樣的:

  1. 第一次渲染,useEffect 執行 call api,return function 會存起來
  2. 點擊 switch 按鈕,state 改變
  3. 執行上一次 render useEffect 記住的 return function
  4. useEffect 再次執行

沒錯,我才發現 useEffect 在以 state 為參數時 return 是這時候執行的 😅

另外一個概念是閉包

雖然 return function 才將 ignore 變為 true

但是這個 ignore 代表的還是第一次 api call 的那個 ignore 判斷

useEffect(() => {
  let ignore = false;
  setBio(null);
  fakeFetchData(person).then((result) => {
    if (!ignore) {
      setBio(result.message);
    }
  });
  return () => {
    ignore = true; // 這個會在有下一個 api call 的時候才會執行
  };
}, [person]);

簡單來說

在第一次 api response 之前,我在第二次的 useEffect 執行的上一個 return function

來擋住上一次的 api response state change