ItIron2023
react
我們昨天再次看了一個React.memo的錯誤使用例子,你也了解到該如何正確地使用useCallback,我們今天繼續來看一個跟render有關的小玩意吧!這次的錯誤雖然不嚴重,但確實是一個不算少見的誤用。
請你先看一下今天的codesandbox以及下方的截圖。
今天的例子並沒有出什麼異常,我們順利地透過了useEffect抓取資料,之後我們再透過另一個useEffect在user變化時去set完整的地址,一切都如我們預期的運作!
我們今天換個方式玩玩看吧,請試著觀察以下的程式碼,並試著猜測整個組件會render幾次、為什麼會這樣以及是否可以避免。
const App = () => {
const [user, setUser] = useState(null);
const [fullAddress, setFullAddress] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
);
const data = await response.json();
setUser(data);
};
fetchData();
}, []);
useEffect(() => {
if (user) {
setFullAddress(
`${user.address.street}, ${user.address.suite}, ${user.address.city}, ${user.address.zipcode}`
);
}
}, [user]);
return (
<div>
<h1>useEffect to the rescue, or is it?</h1>
<h2>{user ? user.name : "Loading..."}</h2>
{fullAddress && <p>Full Address: {fullAddress}</p>}
</div>
);
};
export default App;
首先我們先回答簡單的部分吧,該組件一共會render兩次,原因也相當單純,第一次render時第一個useEffect會先發力,去改變了user state造成重新渲染
useEffect(() => {
const fetchData = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
);
const data = await response.json();
setUser(data); // 這裡觸發重新渲染
};
fetchData();
}, []);
同時這也會觸發第二個useEffect因為user在它的dependency array中,不過我們提過渲染的行為會batch,所以都會一次在下一個渲染完成,並不會額外再多觸發一次重新渲染,最終第一次的渲染加上state改變的重新渲染一共兩次。
useEffect(() => {
if (user) {
setFullAddress(
`${user.address.street}, ${user.address.suite}, ${user.address.city}, ${user.address.zipcode}`
);
}
}, [user]);
接著來回答比較麻煩的問題
是否可以避免這樣的情況?
首先你自然要釐清所謂這樣的情況是什麼意思,如果你是指兩次渲染的事情,那麼很遺憾的這不可能被達成,除非你今天在第一次渲染就完成了所有事情,但照目前的邏輯state一定會被改變而觸發重新渲染。不過這並不是世界末日,我強調過很多次,重新渲染是react很重要的一部分,以這個題目來說這是必然的行為,但你仍有辦法讓這份程式碼變得更好一些,那就是不要使用不必要的useEffect。 我們之前就有提過每一次的渲染都會有自己新的state、函數與變數,因此你並不需要去看user data是否有改變才能渲染出正確的完整地址,你大可以宣告一個變數或函數即可,下方是其中一種做法!
const App = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("https://api.example.com/user/1");
const data = await response.json();
setUser(data);
};
fetchData();
}, []);
const getFullAddress = (address) => {
return `${address.street}, ${address.suite}, ${address.city}, ${address.zipcode}`;
};
const fullAddress = user ? getFullAddress(user.address) : null; // 直接宣告一個變數來處理就行了
return (
<div>
<h1>{user ? user.name : "Loading..."}</h1>
{fullAddress && <p>Full Address: {fullAddress}</p>}
</div>
);
};
React雖然提供了很多方便的hook讓我們使用(但先聲明,我覺得useEffect爛透了,他造成的問題絕對不比他解決的問題少),但很多時候其實你並不需要那些hook也能達成你要的效果,希望今天的例子能給你一點啟發,說不定你的程式碼中已經默默藏了很久這樣不必要的useEffect使用! 我們明天見吧,再撐一下,我們很快就要到面試問題了。
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!