2020/09/04

理解 React useEffect 01


React 中,取得資料並呈現到畫面上一直是個難題,useEffect 就是讓元件呈現【副作用 (Side Effects) 效果】一樣,對我們造成了很大的【副作用】的【效果】。

useEffect 要解決什麼樣的問題?

useEffect 主要在呈現 (Render) 後觸發此 Hook 事件,並將取得的資料後再呈現到元件上。

在【How to fetch data with React Hook?】一文中卻提到:

Note: In the future, React Hooks are not be intended for data fetching in React. Instead, a feature called Suspense will be in charge for it.
這句話中文大概的意思是:

未來 React Hooks 不會用在資料取得的場合,會改由 Suspense 取代。

可是 Suspense 也說明它不會取代 fetch、axios 等取得資料的 API,在目前有限的資料只知道未來還會變化,所以現階段學習 useEffect 是必須的,待情勢明朗後再來調整也來得及。


取得資料的基本應用 - 一切的起點

以下範例源自【How to fetch data with React Hooks?】:

import React, { useState } from 'react';
 
function App() {
  const [data, setData] = useState({ hits: [] });
 
  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
 
export default App;

這個應用程式為【黑客新聞】,畫面裡有一清單。useState 為此清單的內部狀態,為取得新聞資料存在,預設內容為帶有 hits 屬性的空陣列物件;陣例裡的物件會有【objectID】、【url】和【title】屬性。 ( Step1 CodeSandbox )

取得資料的基本應用 - 引入 axios 函式庫

取得 WebAPI 資料時,我們會考慮 Promise fetch,另一個選擇是封裝 fetch 的【axios】函式庫,你會發現,axios 更好用。

接下來我們要使用【作用掛勾 (effect hook)】來看看 axios 取得資料後為 App 物件進行什麼樣的副作用:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
 
  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
 
    setData(result.data);
  });
 
  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
 
export default App;

 

 

然後就出錯了。

 

我在 CodeSandbox 輸入上述內容後會得到【邊界錯誤】。

因為 useEffect 在引數內使用 async 是錯誤的,它不能在引數內回傳 Promise 物件。

useEffect 內容要保持簡單,Promise 要包裝在 useEffect 內做一個 Closure function,所以要調整如下:

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";

export default function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    async function fetchData() {
      const result = await axios(
        "https://hn.algolia.com/api/v1/search?query=redux",
      );
      setData(result.data);
    }
    console.log("run useEffect")
    fetchData()
  });

  return (
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

完成修改後,專案就不會有警告和錯誤, 但卻會不停的向 WebAPI 取資料,達成 DDOS 的目標

這是因為 setData 觸發狀態改變,而進行繪製 (Render) 事件,而繪製後又會再進行【作用】處理,作用中向 WebAPI 拉資料後產生副作用,副作用又觸發狀態改變,如此無限循環,這是錯誤的。

此時,就需要用到 useEffect 的第二個參數:依賴 dependency。

只有在【初次繪製】和【依賴變更】時,元件才需要產生副作用,除此之外,就不要再繪製了。

以這個案例來說,並不用依賴清單 (data) 異動來進行繪製,案例中也沒有需要依賴產生副作用的 props 或 state,所以給予空陣列【[]】即可。

最終會得到以下的程式:【Step2 CodeSandbox

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";

export default function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    async function fetchData() {
      const result = await axios(
        "https://hn.algolia.com/api/v1/search?query=redux"
      );
      setData(result.data);
    }
    console.log("hi")
    fetchData();
  }, []);

  return (
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}


useEffect 最基礎的內容就先介紹到這裡。 

目標是 useEffect 內如何重用自己寫的下載函式,我們下次見!

See also

沒有留言:

張貼留言