最近写了一个(古老的)轮播组件,用的React的hooks写法,大概涉及到了我们最常用的useState
, useEffect
这两个hooks,还有可能(有些人)比较陌生的useRef
, useCallback
。
本篇文章的重点不是去教大家怎么实现一个轮播组件,而是想通过实战,结合业务需求让大家理解useRef
, useCallback
这两个hook
,知道它们大概的用途,能够遇到具体需求的时候想起使用它们。
轮播
轮播组件嘛,大家都懂得,需求大概分成两块,第一需要自动轮播;第二要有点击功能,点击哪个,播放哪个,并且要重置轮播时间。
首先说第一点需求,就是设置一个定时器,不断的累加、计数,如果超出了数组长度,则把计数器重置,从头开始继续轮播;第二点呢,就是需要增加一个点击事件,点击的时候要去重新设置定时器,然后从当前点击的地方,继续轮播。
所以设置定时器
这个方法有两个地方都需要用到,肯定是要提取成一个function
的。
在实战代码里面提取出来的这个function
,就叫start
。
useRef
useRef
用到的最多的场景应该就是加载第三方资源了,比如引入echarts
或者高德地图,初始化的时候需要传入一个dom
节点,这种时候通常就用useRef
来保存节点。但是在今天这个实战里,用到useRef
不是来保存节点的,而是用来保存setInterval
定时器返回的ID
的。
从我个人的使用感受来看,我觉得函数式组件里的useRef
其实有点类似于类组件里的通过this.xxx
的方式来跨生命周期保存变量、数据,然后这个变量的改变也不会触发重新渲染。
基本的使用方法如下(下面这个例子只是单纯的举例,没有实际意义😄)
import React, { useRef, useEffect } from 'react';
function A(){
const timer = useRef();
useEffect(() =>{
timer.current = setInterval(()=>{
// XXXX
})
return () => clearInterval(timer.current)
},[])
return (
<div />
)
}
useCallback
我们在一个函数式组件A
里面定义一个方法b
,当A
重新渲染(执行)的时候,里面的方法b
其实也会被重新生成,也就是其引用会指向一个新的地址。
import React, { useEffect } from 'react';
function A(){
function b(){
return '这是b方法';
}
useEffect(()=>{
fetch('XXXXXX' + b());
}, [b]) // 传入这种依赖项,毫无意义,不起作用!
return (
<div>{b()}</div>
)
}
如果我们此时把函数b
当作一个依赖项,传递给useEffect
的第二个参数,其实是起不到我们想要的作用的,因为每次组件A
重新渲染,这个useEffect
的里面的第一个方法都会被重新执行一次。
这种时候useCallback
就派上用场了,我们把b
用useCallback
包裹一下,传入b
所依赖的参数,如下
const bUseCallback = useCallback(b, []);
useEffect(()=>{
fetch('XXXXXX' + bUseCallback());
}, [bUseCallback]) // 传入这种依赖项,才是正确的做法!
代码
对上面两个hooks
做出了一点解释之后,我就直接上代码了
import React, { useState, useEffect, useRef, useCallback } from 'react';
function Carousel(props) {
const { data = [], time } = props;
const [active, setActive] = useState(0);
const timer = useRef(); // 保存setInterval的ID
// 开始轮播
const start = useCallback(() => {
if (timer.current) {
clearInterval(timer.current);
}
const len = data.length;
timer.current = setInterval(() => {
setActive((v) => {
if (v >= len - 1) {
return 0;
}
return v + 1;
});
}, time);
}, [data, time]);
useEffect(() => {
start();
return () => clearInterval(timer.current);
}, [start]);
function handleClick(i) {
setActive(i);
start();
}
return (
<div>
{
data.map((item, index) => (
<div
key={item}
style={{ color: index === active ? 'red' : 'green' }}
onClick={() => handleClick(index)}
>
{item}
</div>
))
}
</div>
);
}
Carousel.defaultProps = {
time: 5000, // 轮播时间 ms
data: ['第一个', '第二个', '第三个'],
};
export default Carousel;
总结
上面代码逻辑不复杂,主要有如下几个步骤
- 通过
useState
设置一个状态,用来保存当前的激活项。有一点需要注意的是,调用setXXX
更新状态的时候,最好用函数式更新,以便每次都能拿到最新状态。 - 提取出了
start
方法,用来在需要的时候启用 - 用
useRef
来保存定时器返回的ID
,以便调用clearInterval
清空 - 由于
start
方法是useEffect
的依赖项,所以需要用useEffect
包裹一下
以上,如有疑问或错误,欢迎在评论区交流👏👏👏