周五周会也算是混过去了。昨天还是没又去做工作,研究了两天Next.js的组件懒加载,希望能做到评论组件滚动到可见时再加载,中间也是踩了不少坑,写了整整两天。
首先既然要懒加载,我的需求那肯定要客户端渲染去请求接口来获取评论数据,再自己去渲染。可是我这是纯前端项目,后端寄生于Notion,不好创建接口(后面才知道是可以的)。最开始看见是next.js.config可以设置转发,那干脆直接转发到Notion Api得了。可是,Notion似乎对转发的请求也做了跨域的验证,但凡是转发过去的请求返回都是400,而正常请求是没问题的。最可恶的是他也没给我准确原因,message里就提示“Invalid Url”,导致我还以为转发的地址写错了。
既然转发这条路走不通,那只能想其他办法了。后来在官方文档上看见是可以创建路由的,只要在app目录下新建一个route.ts|js文件就可以被正确识别成接口了,要注意的一点是route.ts|js是不可以和page.js之类的文件在同一级的。很好立即,因为有page文件就代表着这里是页面,而有route文件就代表着这里是接口。
获取数据没问题后,根据这篇文章设置滚动懒加载,可是写完后发现不对劲,在不停地请求获取评论的接口。这整的我一头雾水,他的请求频率还很规律,大概一秒一次。昨晚看到了一点钟,才想起来原作者在Footer做了一个小时钟,难道是这个小时钟引起的?我半信半疑地把时钟的hook代码注释掉后,请求频率还真下去了。其实这里也埋下了伏笔,如果我在这里多去想想搜搜,后面也会轻松很多,不会话这么多的时间。就是因为useEffect()触发了组件的重新渲染,才会一直在请求接口。既然不好改,那就把评论数据放在useState里呗,就让它去请求一次数据。因为有两组数据,一组是评论信息,还有一组是用户信息,我最开始用两个useEffect(),监听文章id和评论数据。当文章id改变时去获取更新评论数据,评论数据改变时去获取用户信息。想法很美好,现实很残酷。接口虽然两个都有去请求,但是渲染时根本没有用户信息。咦,明明用户信息都返回了呀。还记得之前说useEffect()会出发组件重新渲染吗,是因为第一个获取评论数据触发了页面的重新渲染,所以此时没有用户数据。但是为什么第二次没有去重新渲染我没搞懂。把两个useEffect()整合成一个后,问题完美解决。
与此同时还有一个问题。按照上面的设置懒加载教学写好代码后,有个奇怪的现象:第一次进页面没加载评论,正常;评论组件可见时加载,正常;当组件又不可见时,它直接消失了;再恢复可见时,它又重新加载了。明明原文中提到过true-false-true的问题,代码我看了感觉也没啥问题。在评论中也有人说如果滚动到组件不可见时会重新渲染,可是这人贴的代码和文章并没有什么区别,看来作者时看到评论后又修改过原文了。
到这里你还记得useEffect()可以触发组件重新渲染吗?如果我还记得那么当时就不会花费这么多时间了。
const useOnScreen = (ref) => {
const [isIntersecting, setIntersecting] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => setIntersecting(entry.isIntersecting)
)
if (ref.current) {
observer.observe(ref.current)
}
}, [])
return isIntersecting
}
export default useOnScreen
在她最开始定义的hook里用到了useEffect()来返回判断组件是否可见,然后又用useEffect()返回的isIntersecting来判断保证组件只渲染一次。重点就在第二个的useEffect()里,不管isChild3Ref值是否改变,都触发了组件的重新渲染。最后我把useOnScreen修改如下,对isInteresting进行判断,当isInteresting为true时便不再更新值。这么改后,就可以把第二个组件渲染的useEffect()给删掉了。最终完美解决。
// Annotate the return type of the function
const useOnScreen = (ref: RefObject<HTMLDivElement>): boolean => {
// Annotate the state variable type
const [isIntersecting, setIntersecting] = useState<boolean>(false)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
//对isInterescting判断,避免重复改变
if (!isIntersecting && entry.isIntersecting) {
setIntersecting(entry.isIntersecting)
}
})
if (ref.current) {
observer.observe(ref.current)
}
}, [])
return isIntersecting
}
export default useOnScreen
最后其实还有个小问题,第一次进入文章的时候会有“正在加载评论…”的loading,然后他又马上会消失,此时正在发送接口请求,还没有拿到数据。等数据都有了之后,loading又会出现,然后评论跟着出现。这个原因我还没去细究,等有时间再去解决吧,这两天被useEffect()已经搞得人晕掉了。