记一次React Hooks的使用

有这么一个需求:需要渲染一个表格,表格的内容会随着用户的操作而重新请求数据,并且在用户离开这个表格所在的页面(路由)后缓存数据,再次进入该页面的时使用已缓存数据。

目前在使用的表格组件是纯函数组件,只负责渲染,数据请求则写在其父组件,数组则存在Redux中。经过考虑,需要把数据请求的逻辑移入表格组件中,使得表格组件承担更多的职责,在React 16.8之前,我们不得不把表格组件写成class组件。

而现在,可以使用hooks,以最少的更改,来实现这一需求。

需要哪些hooks?

我们的需求是把请求数据的逻辑移入到表格组件中,表格的数据仍然保存在Redux中。众所周知,数据获取是一个有副作用的操作,而useEffect这个hooks就是用来处理有副作用的操作。

在使用hooks之前,我们一般在componentDidMountcomponentDidUpdate或者是很少使用的componentWillUnMount来进行DOM操作,数据请求等副作用操作。而useEffect则可以简单的看做是这三个生命周期函数的合集,其在组件的这三个生命周期时,都会被调用到。

所以,使用useEffect解决了所有问题。

“真”解决了所有问题?

先把代码写起来:

1
2
3
4
5
6
7
8
9
10
import 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
12
const [isInitial, changeInitialToFalse] = useState(true);
useEffect(() => {
if (!isInitial) {
fetchData(param);
}
}, [param]);
useEffect(() => {
changeInitialToFalse(false);
if (data.status !== 'fulfilled') {
fetchData(param);
}
}, []);

经过测试,这一次是真一切正常了,缓存也已经用上。

尾巴

这是一次再普通不过的组件改造,这也是我在实际项目中第一次使用hooks。并且经历了从最开始遇到需求到决定使用hooks来最小化修改,到遇到问题差点改成更熟悉的class组件,到最终解决问题。最大的收货是,我对useEffect的了解又深刻了一些。把数据请求逻辑移到组件内部,除了降低组件间的耦合,更大程度上可以配合前一篇提到的ScrollLoad来做真正的滚动加载。毕竟数据才是组件的灵魂,数据都不懒加载,组件懒加载的意义就减了一半,手动狗头。

完。

分享到 评论