前言
在看 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 的時機
- 閉包運作原理
直接說明解決問題發生的順序
他的步驟是這樣的:
- 第一次渲染,useEffect 執行 call api,return function 會存起來
- 點擊 switch 按鈕,state 改變
- 執行上一次 render useEffect 記住的 return function
- 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