项目场景:
小程序项目中需要记录用户在当前页面停留的时长,下意识想到在useState
中记录一个时间戳,在useEffect
中的return
中获取一个时间戳,减去state
中记录的时间戳,用得到的值取请求接口。
问题描述:
考虑到用户网络情况,如果用户在页面还没加载完时等不及就进行了返回操作,此时记录页面停留时长显然不太合理。所以应该在useEffect
中请求成功返回后,再进行记录时间戳作为用户开始浏览的时间。
const [timestamp, setTimestamp] = useState(0);
useEffect(() => {
... // 执行请求
setTimestamp(new Date().getTime());
return () => {
if(timestamp) { // 如果时间戳不为0 意味着是加载完成后才离开的页面
saveTime(new Date().getTime() - timestamp);// 记录停留时长
}
}
},[]);// 只在初次渲染和卸载时触发
可是每当返回的时候,根本不会发送请求记录停留时长,不管当前页面是否加载完成。
原因分析:
查看react文档后意识到,当给useEffect
传递数组作为第二个参数时,需要确保数组中包含了所有外部作用域中会随时间变化并且在effect
(也就是传入useEffect
的第一个参数)中使用的变量,否则代码会引用到先前渲染中的旧变量。
上面代码中就是没将timestamp
写到useEffect
的第二个参数中,所以取到的timestamp
值一直为0,自然不会发送请求记录停留时长。
所以如果想在useEffect
取到最新的timestamp
值的话需要将timestamp
写到useEffect
的第二个参数中
const [timestamp, setTimestamp] = useState(0);
useEffect(() => {
... // 执行请求
setTimestamp(new Date().getTime());// 改变timestamp 导致无限循环
return () => {
if(timestamp) {
saveTime(new Date().getTime() - timestamp);
}
}
}, [timestamp]);// timestamp改变时触发
但是这样当timestamp
改变时又会去重新设置timestamp
的值,就会无限循环,导致报错。
解决方案:
useEffect
如果要在effect
中取到最新的prop
、state
,必须得将相应的prop
、state
传入第二个参数数组中。但是如果是取useRef
创建的对象中的值,则不用将ref
传入第二个参数数组,也能获取到ref
中的最新值。所以可以在ref
中记录时间戳。
const timestamp = useRef(0);
useEffect(() => {
... // 执行请求
timestamp.current = new Date().getTime();
return () => {
if(timestamp.current) {
saveTime(new Date().getTime() - timestamp.current);// 请求接口保存时长
}
}
}, []);
因为useRef
创建的是一个普通 Javascript
对象,而且useRef
会在每次渲染时返回同一个ref
对象。所以总能获取到ref
对象中current
属性的最新值。
当然,如果在effect
外部没有使用到timestamp
,则不使用useRef
,直接将timestamp
定义在effect
内部也是可以的,就像
useEffect(() => {
... // 执行请求
const timestamp = new Date().getTime();
return () => {
if(timestamp) {
saveTime(new Date().getTime() - timestamp);// 请求接口保存时长
}
}
}, []);