用React刻自己的投资Dashboard Day10 - 用useCallback hook帮你记住函式

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抽出来,这样功能会分离得更清楚一些。


<<:  Day10 - 如何查询委托单状态

>>:  [Day 20] 调整一下我们的函数架构,谈扩充函数和流畅介面

新新新手阅读 Angular 文件 - Component - Day23

本文内容 本文是阅读有关 Angular 的元件生命周期的 OnChanges 的笔记内容。 ngO...

Day6:class函数

半夜睡不着来更新XD Python属於「物件导向程序语言」(Object-oriented prog...

Day 29. End To End Testing

E2E Test with efficiency End To End Testing 是前端测试中...

Day 29 - Rancher Fleet Helm + Kustomize 应用程序部署

本文将於赛後同步刊登於笔者部落格 有兴趣学习更多 Kubernetes/DevOps/Linux 相...

30天轻松学会unity自制游戏-调整摄影机

现在会遇到Boss也会一直往前移动,(要测试可以先把摄影机跟player的这行注解掉)如果不做测试就...