2021铁人赛
React
既上一篇介绍完useState hook後,本篇就来介绍Day6也有用到的useEffect hook,在React官网有提到,如果使用者熟悉React class component的生命周期,那麽可以用下面这个提示来理解useEffect:
如果你熟悉 React class 的生命周期方法,你可以把 useEffect 视为 componentDidMount,componentDidUpdate 和 componentWillUnmount 的组合。
从下面这张React官方提供图可以看出React生命周期的三个阶段,Mounting(创建)、Updating(更新)、Unmounting(销毁),不过要完整地解释这张图会需要比较多的篇幅,这边只要先了解执行顺序为先是componentDidMount,再来是componentDidUpdate,最後是componentWillUnmount,再来就直接看官网的范例即可。
要知道用法上的差异,可以从React官网上的范例来理解,这个范例是用React去写一个计数器,并且在React对DOM进行变更後立即更新网页标题,下面就分别用class与hook去写,藉由范例认识其差异。
先将相同功能的程序码比较一下,使用hook的程序码比较短一些,也发现class版本需要用到componentDidMount与componentDidUpdate去实现这样的功能,而hook版本只需要一个useEffect就可以处理,当然并不是程序码短就是好,因此接下来分析一下功能差别。
因为我们想要在一进入网页的时候,就让网页的title显示"You clicked 0 times",因此需要在Mounting的时候呼叫一次,之後在点击按钮的时候,count这个state会被更新,为了要将网页的title也更新为"You clicked n times",因此需要呼叫componentDidUpdate。
可能有人会说,难道没有一个method可以让React在mounting及updating都去执行这行程序码吗?可惜React的class component刚好就是没有这个功能,因此就需要写两次。
将上图中的class版本程序码放大如下:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
相对於class版本的程序码,hook版本只写了一行useEffect就达成这个范例的要求,原因是useEffect会在每次重新render之後都执行一次,不管这个render是在mounting阶段或updating阶段所执行的,useEffect都会执行,因此当state变动,自然会重新render,也就启动useEffect内的程序,网页的title也就更新了,真的是非常方便。
然後还有一个小地方是,useEffect需要在component内呼叫,才能去抓到最新的state,这个是javascript closures的特点,而React巧妙地运用了这点。
将上图中的hook版本程序码放大如下:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在Card.js中,因为我希望在以下时间点去触发抓取数据的程序:
从上述需求可以发现,抓取数据这个动作,会在Mounting及Updating阶段执行,因此useEffect就符合这样的需求。所以在Card.js内写了一个fetchData函数,并在useEffect内呼叫它,即完成这个功能。
Card.js
const Card = (props) => {
// 定义state
const [chartOption, setChartOption] = useState({...});
// 抓取API数据
const fetchData = (series_id) => {...};
// effect
useEffect(() => {
fetchData(props.item.series_id);
}, []);
return (
...
)
}
在上面的程序码可以看到useEffect内有一个空的阵列,它的功能是避免程序陷入无限回圈中,原因如下图,在mount时执行render,完成之後effect就会启动,因此就变更了state,React发现state改变後又重新render,再来又触发effect,因此就形成了一个无限回圈。
这个无限回圈的问题,可以透过useEffect的dependencies来解决,例如官网的范例如下:
如果count没有改变,effect就不会执行。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
因此我在Card的useEffect放入一个空阵列当作dependencies,因为它一直都是空的,所以react只会执行一次effect,虽然这不是最正确的方式,不过算是暂时解决这个问题。
useEffect真的是有点复杂的功能,不过还蛮好用的,在这篇提到的无限回圈问题还没结束,下一篇要来研究到底useEffect中的dependencies到底要放一些什麽,同时也会提到useCallback这个hook。
>>: Day16 Grafana (Match Making)
昨天用 Vite 快速打造了输入信箱获取认证码的页面,但必须搭配发送认证码的 API 才能继续完成这...
不知道现在台湾的AI有没有已经做到癌细胞医学图片的特徵侦测圈选跟分类 目前看到云象科技与林口长庚医院...
不知道要打什麽,直接开始 题号:739 标题:Daily Temperatures 难度:Mediu...
Ruby中self的意思? Ruby Guides What is self, exactly? I...
昨天介绍了在Dart中非同步的基本概念,今天就要来讲到如何简单的控制非同步操作。 Future Fu...