有这么一个需求:需要渲染一个表格,表格的内容会随着用户的操作而重新请求数据,并且在用户离开这个表格所在的页面(路由)后缓存数据,再次进入该页面的时使用已缓存数据。
目前在使用的表格组件是纯函数组件,只负责渲染,数据请求则写在其父组件,数组则存在
Redux
中。经过考虑,需要把数据请求的逻辑移入表格组件中,使得表格组件承担更多的职责,在React 16.8
之前,我们不得不把表格组件写成class
组件。
而现在,可以使用hooks
,以最少的更改,来实现这一需求。
需要哪些hooks
?
我们的需求是把请求数据的逻辑移入到表格组件中,表格的数据仍然保存在Redux
中。众所周知,数据获取是一个有副作用的操作,而useEffect
这个hooks
就是用来处理有副作用的操作。
在使用hooks
之前,我们一般在componentDidMount
和componentDidUpdate
或者是很少使用的componentWillUnMount
来进行DOM操作,数据请求等副作用操作。而useEffect
则可以简单的看做是这三个生命周期函数的合集,其在组件的这三个生命周期时,都会被调用到。
所以,使用useEffect
解决了所有问题。
“真”解决了所有问题?
先把代码写起来:1
2
3
4
5
6
7
8
9
10import React, { useEffect } from 'react';
const MyTable = ({ fetchData, param }) => {
useEffect(() => {
fetchData(param);
}, [param])
return (
<Table />
);
}
export default connect(stateToProps,actionToProps)(MyTable);
仅仅三行代码,就搞定了。更改params
,一切正常;从其它页面进入,看起来也很正常。可为什么是看起来正常呢?因为你打开控制台,查看network xhr
,再进入页面,请求发送了!并没有使用缓存的数据!
所以,问题并没有解决。
如何更好的利用缓存数据
问题:既然
useEffect
能够在上述三个生命周期中都执行,那么有没有办法区分出首次渲染和更新呢?
答案是肯定的!在上面的代码中,我们使用了useEffect(func, [param])
的形式,其实useEffect
的第二个参数如果指定,那么useEffect
就不是每次都执行了,而是只有param
改变了才会执行。并且特别的,如果第二个参数传入[]
空数组,那么useEffect
只会执行一次,也就是说,useEffect
只会在componentDidMount
执行!代码写起来:1
2
3
4
5
6// 其它代码不变,再加一个hook
useEffect(() => {
if (data.status !== 'fulfilled') {
fetchData(param);
}
}, [])
上面的hook
会在初次渲染完成后执行,如果缓存数据的状态不是fulfilled
,才请求数据。
问题仍然没有解决
很显而易见的是,即便是加了一个
hook
,而第二个useEffect
仍然会执行,所以仍然会在初次加载完成后请求数据。
这时候,就需要另一个hook
出场了,那就是useState
,我们需要在组件中维持一个是否是首次渲染的状态,只有当非首次渲染的时候,才会去执行第一个hook
,因此可以避免不必要的的数据请求。代码码起来:1
2
3
4
5
6
7
8
9
10
11
12const [isInitial, changeInitialToFalse] = useState(true);
useEffect(() => {
if (!isInitial) {
fetchData(param);
}
}, [param]);
useEffect(() => {
changeInitialToFalse(false);
if (data.status !== 'fulfilled') {
fetchData(param);
}
}, []);
经过测试,这一次是真一切正常了,缓存也已经用上。
尾巴
这是一次再普通不过的组件改造,这也是我在实际项目中第一次使用hooks
。并且经历了从最开始遇到需求到决定使用hooks
来最小化修改,到遇到问题差点改成更熟悉的class
组件,到最终解决问题。最大的收货是,我对useEffect
的了解又深刻了一些。把数据请求逻辑移到组件内部,除了降低组件间的耦合,更大程度上可以配合前一篇提到的ScrollLoad
来做真正的滚动加载。毕竟数据才是组件的灵魂,数据都不懒加载,组件懒加载的意义就减了一半,手动狗头。
完。