前言
倒计时是开发过程中很常见的组件,顺其自然的我们也会按照以往的开发形式实现,然而在React Hooks
中去实现这个组件的时候,很有可能你就遇到“坑”了,在不了解React Hooks
的情形下,又不知从何排查问题以及了解问题的根本,因此这篇文章出现了~!
废话不多说,先码正确示例,以解兄弟姐们们的燃眉之急,之后再来根据错误示例分析问题。
正确示例1
import { useState, useEffect } from 'react';
export const CountDown = (props: any) => {
const { countDown } = props;
let cd: number = countDown;
const timer: any = null;
const [time, setTime] = useState<string>('');
const dealData = () => {
if (cd <= 0) {
setTime('');
return timer && clearTimeout(timer);
}
const d = parseInt(cd / (24 * 60 * 60) + '');
const h = parseInt(((cd / (60 * 60)) % 24) + '');
const m = parseInt(((cd / 60) % 60) + '');
const s = parseInt((cd % 60) + '');
setTime(`倒计时: ${d}天${h}时${m}分${s}秒`);
cd--;
timer = setTimeout(() => {
dealData();
}, 1000);
};
useEffect((): any => {
dealData();
return () => {
timer && clearTimeout(timer);
}
}, []);
return <div className={styles.CountDown}>{time}</div>;
};
正确示例2
import { useState, useEffect, useRef } from 'react';
export const CountDown = (props: any) => {
const { countDown } = props;
const cd = useRef<number>(countDown);
const timer = useRef<any>(null);
const [time, setTime] = useState<string>('');
const dealData = () => {
if (cd.current <= 0) {
setTime('');
return timer.current && clearTimeout(timer.current);
}
const d = parseInt(cd.current / (24 * 60 * 60) + '');
const h = parseInt(((cd.current / (60 * 60)) % 24) + '');
const m = parseInt(((cd.current / 60) % 60) + '');
const s = parseInt((cd.current % 60) + '');
setTime(`倒计时: ${d}天${h}时${m}分${s}秒`);
cd.current--;
timer.current = setTimeout(() => {
dealData();
}, 1000);
};
useEffect((): any => {
dealData();
return () => {
timer.current && clearTimeout(timer.current);
}
}, []);
return <div className={styles.CountDown}>{time}</div>;
};
错误示例
import { useState, useEffect } from 'react';
export const CountDown = (props: any) => {
const { countDown } = props;
const [cd, setCd] = useState<number>(countDown);
const timer: any = null;
const [time, setTime] = useState<string>('');
const dealData = () => {
if (cd <= 0) {
setTime('');
return timer && clearTimeout(timer);
}
const d = parseInt(cd / (24 * 60 * 60) + '');
const h = parseInt(((cd / (60 * 60)) % 24) + '');
const m = parseInt(((cd / 60) % 60) + '');
const s = parseInt((cd % 60) + '');
setTime(`倒计时: ${d}天${h}时${m}分${s}秒`);
setCd(cd--);
timer = setTimeout(() => {
dealData();
}, 1000);
};
useEffect((): any => {
dealData();
return () => {
timer && clearTimeout(timer);
}
}, []);
return <div className={styles.CountDown}>{time}</div>;
};
错误示例会出现的问题
cd
在useEffect
中始终是初始值;- 在
DOM
元素上,会出现先是初始值,然后从初始值减1
,之后就不会在变化
原因
- 因为
useEffect
的第二个参数是[]
,所以re-render
(更新渲染)的时候不会重新执行effect
函数,所以cd
在useEffect
中始终是初始值; - 因为
useEffect
第一次执行的时候即初始化的时候利用setCd(cd--)
重新对cd
赋值,所以会触发视图重新渲染一次; - 如果
useEffect
没有第二个参数[]
,既没有依赖,那么就相当于didMount
和DidUpdate
两个生命周期的合集,所以更新的时候会重新执行useEffect
【注意】:useEffect
的第一个参数是一个匿名函数,匿名函数执行完会立即被销毁,所以在组件重新更新的时候,会重新调用匿名函数,并获取更改后的cd
值
解决方法
如上示例1
,2
,因为方式1
打破了React
纯函数的规则,所以更加建议方式2
【注意】
- 在倒计时为
0
的时候一定要销毁倒计时 - 在组件销毁的时候一定要手动销毁倒计时,否则即使跳转到其他页面,倒计时依旧在进行