iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 17

30天React練功坊-攻克常見實務/面試問題 Day17: useEffect to the rescue, or is it?

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

我們昨天再次看了一個React.memo的錯誤使用例子,你也了解到該如何正確地使用useCallback,我們今天繼續來看一個跟render有關的小玩意吧!這次的錯誤雖然不嚴重,但確實是一個不算少見的誤用。

本日題目

請你先看一下今天的codesandbox以及下方的截圖。

day17-demo-image

今天的例子並沒有出什麼異常,我們順利地透過了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使用! 我們明天見吧,再撐一下,我們很快就要到面試問題了。

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
30天React練功坊-攻克常見實務/面試問題 Day16: React.memo not working corretly with function as props
下一篇
30天React練功坊-攻克常見實務/面試問題 Day18:UI flicker issue with useEffect
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言