iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
Modern Web

用React刻自己的投資Dashboard系列 第 10

用React刻自己的投資Dashboard Day10 - 用useCallback hook幫你記住函式

  • 分享至 

  • twitterImage
  •  
tags: 2021鐵人賽 React

在Day9說明了useEffect的用法,不過其實當Card.js渲染時,會發現console有錯誤訊息如下:

src\components\Charts\Card.js
Line 47:6: React Hook useEffect has missing dependencies: 'fetchData' and 'props.item.series_id'. Either include them or remove the dependency array react-hooks/exhaustive-deps

這個錯誤訊息是ESLint提醒少放了dependencies(以下簡稱deps),並建議將fetchData與props.item.series_id放入deps,而ESLint建議將這兩個物件放入deps的原因如下:

  • 針對fetchData來說:因為fetchData是定義在useEffect外面,所以React不知道這個函數是否有用到任何state或props,當state或props有更新的時候,可能導致fetchData這個函數沒有呼叫到,資料就會出問題。
  • 針對props.item.series_id:因為這個直接是用到props,所以也應該放入deps,當props有更新時才會去呼叫fetchData。

將fetchData與props放入deps

根據ESLint的建議,就把上述兩個物件放入deps,程式碼改成下面這樣:

其他程式碼不變,只改了useEffect內的deps。

const Card = (props) => {
  const [chartOption, setChartOption] = useState({
    ...
  });

  const fetchData = (series_id) => {
    ...
  };

  useEffect(() => {
    fetchData(props.item.series_id);
  }, [fetchData, props]);

  return (
    ...
  )
}

改完之後儲存,會再跳另外一個error code

src\components\Charts\Card.js
Line 19:9: The 'fetchData' function makes the dependencies of useEffect Hook (at line 50) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'fetchData' in its own useCallback() Hook react-hooks/exhaustive-deps

這個錯誤的原因是,每次render都會產生一個在記憶體上位址不同的fetchData物件(在JavaScript內,函數也是物件的一種),當JavaScript使用===(嚴格相等)判斷前後物件是否相同時,因為前後產生的fetchData位址不同,JavaScript就會回傳false,導致effect又啟動,又再次render,就又產生新的fetchData物件,造成無窮迴圈。

還好ESLint有告訴我們解法,就是wrap the definition of 'fetchData' in its own useCallback() Hook react-hooks/exhaustive-deps這句,白話一點講就是把這個function丟到useCallback裡面。

useCallback的功能是什麼,怎麼使用?

React官方範例

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

從上面的範例可以發現,用法跟useEffect很像,第一個參數是一個函式,第二個參數是deps,useCallback會回傳一個函式,如果deps沒有改變,就不會回傳新的函式,也就是說,React把這個函式記住了。

再次回到Card.js,並且用useCallback去記住fetchData這個函式,下面貼上完整程式碼:

Card.js

// import 加上 useCallback hook
import React, { useEffect, useState, useCallback } from 'react';
import styles from './Card.module.css';
import Highcharts from 'highcharts/highstock';
import HighchartsReact from 'highcharts-react-official';

const Card = (props) => {
  // 初始的state加上series的name,之後setState的時候可以直接帶入,不用再用props
  const [chartOption, setChartOption] = useState({
    title: {
      text: props.item.title
    },
    xAxis: {
      type: "datetime",
      title: {
        text: 'Date'
      }
    },
    series: [
      {
        name: props.item.title
      }
    ]
  });

  // 將fetchData用useCallback包起來
  const fetchData = useCallback((series_id) => {
    fetch(`${process.env.REACT_APP_PROXY_SERVER_URL}/series/observations?series_id=${series_id}&api_key=${process.env.REACT_APP_API_KEY}&file_type=json`, {
      headers: {
        'Target-URL': 'https://api.stlouisfed.org/fred'
      }
    })
      .then((response) => response.json())
      .then((data) => {
        let data1 = [];
        data.observations.forEach(ob => {
          data1.push([new Date(ob.date).getTime(), Number(ob.value)]);
        });
        setChartOption((prevOption) => {
          return {
            ...prevOption,
            series: [
              {
                name: prevOption.series[0].name,
                data: data1
              }
            ]
          }
        });
      });
  }, []);
  
  // 在deps內加入fetchData, props
  useEffect(() => {
    fetchData(props.item.series_id);
  }, [fetchData, props]);

  return (
    <div className={styles.chartFrame}>
      <HighchartsReact
        highcharts={Highcharts}
        constructorType={'stockChart'}
        options={chartOption}
      />
      <div className={styles.chartInfo}>
        <p className={styles.source}>source: {props.item.source}</p>
        <p className={styles.date}>updated: {props.item.updated}</p>
      </div>
      <div>
        <p className={styles.document}>{props.item.document}</p>
      </div>
    </div>
  )
}

export default Card;

回到console應該會發現error code消失了,代表React已經成功地記住fetchData這個函式。

小結

剛開始接觸useEffect與useCallback的時候,會覺得非常困惑,我自己是透過多次寫程式並且用React的開發者工具去修改props或是state的內容,看看React會怎麼反應,來增加我對於這個技術的認識,覺得還蠻有用的。

下一篇要再讓Card.js這個檔案的程式碼更精簡一點,會把fetchData的功能從Card.js抽出來,這樣功能會分離得更清楚一些。


上一篇
用React刻自己的投資Dashboard Day9 - useEffect hook
下一篇
用React刻自己的投資Dashboard Day11 - 分離UI元件與抓取數據元件
系列文
用React刻自己的投資Dashboard30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
julie000000
iT邦新手 5 級 ‧ 2023-05-04 16:35:44

謝謝分享,剛好解決了我的問題!

我要留言

立即登入留言