减少一个组件的加载时间
懒惰加载是一个概念,我们可以用它来减少网络应用中特定页面的初始加载时间。
在正常情况下,当用户最初加载页面时,页面的所有内容都会被加载,然而,有时用户并不关心页面底部的内容,也懒得滚动。因此,加载所有内容将是徒劳的。
通过使用懒惰加载,我们根据用户的需求来渲染内容,所以当用户向下滚动时,我们会逐渐加载内容,而不是在最初渲染页面的时候。
在这篇文章中,我将解释我们如何在Next.js中懒惰地加载组件。但请记住,这并不是针对在服务器端渲染的组件。
想一想这样的场景:你有多个组件被加载到一个组件中,比如一个有很多部分的登陆页面,也有很多的API调用。
import Child1 from "../Child1";
import Child2 from "../Child2";
import Child3 from "../Child3";
import Child4 from "../Child4";
import Child5 from "../Child5";
const ParentComponent = () => {
return <div>
<Child1/>
<Child2/>
<Child3/>
<Child4/>
<Child5/>
</div>
}
假设当用户访问上述页面时,他们在初始视口中只看到Child1 和Child2 的内容。
要看到Child3 组件和下面的内容,用户需要向下滚动。然而,有些用户甚至可能不需要看到页面底部的内容。
但是根据我们的代码片段,一旦用户访问页面,所有的5个子组件都会被渲染,如果这些组件中有任何数据获取逻辑,也会有一些API调用。
正如我前面提到的,大多数情况下这是不必要的,因为有些用户可能甚至不想看到其余的内容。
这是一个可以使用懒惰加载来减少组件的加载时间的例子。
让我们开始动手吧
- 第一步,我们需要一种方法来检测用户在滚动时某个特定组件的可见性。为此,我将使用
[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)。
由于这将被多次使用,我将创建一个自定义钩子。
import { useState, useEffect } from "react";
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
这个自定义的钩子得到一个引用并观察它。我们使用isIntersecting state并使用entry更新状态。isIntersecting 。从自定义钩子中,我们返回isIntersecting 状态。如果isIntersecting 值为真,这意味着用户可以看到ref元素。
2.2.在下一步,我们必须为每个子组件创建引用,并使用我们的自定义钩子(useOnScreen )观察其可见性。
import Child1 from "../Child1";
import Child2 from "../Child2";
import Child3 from "../Child3";
import Child4 from "../Child4";
import Child5 from "../Child5";
import useOnScreen from "../useOnScreen";
const ParentComponent = () => {
const child3Ref = useRef();
const child3RefValue = useOnScreen(child3Ref);
return <div>
<Child1/>
<Child2/>
<div ref={child3Ref}>
{child3RefValue &&<Child3/>}
</div>
<Child4/>
<Child5/>
</div>
}
现在,只有当*Child3RefValue* 为真时,Child3 组件才会呈现。但还有一个问题。想一想,当用户向下滚动,然后再向后滚动时,会出现什么情况。在这样的情况下,*child3RefValue* 会像false → true → false → true那样更新。
因此,当值两次更新为真时,整个Child3 组件将渲染两次。我们不需要这种情况发生,因此我们需要防止后续渲染。
3.3.为了防止该组件渲染超过一次,我们必须在Parent 组件中保留一个状态值。
import Child1 from "../Child1";
import Child2 from "../Child2";
import Child3 from "../Child3";
import Child4 from "../Child4";
import Child5 from "../Child5";
import useOnScreen from "../useOnScreen";
const ParentComponent = () => {
const child3Ref = useRef();
const child3RefValue = useOnScreen(child3Ref);
const [isChild3Ref, setIsChild3Ref] = useState(false);
useEffect(() => {
if (!isChild3Ref)
setIsChild3Ref(child3RefValue);
}, [child3RefValue])
return <div>
<Child1/>
<Child2/>
<div ref={child3Ref}>
{child3RefValue && <Child3/>}
</div>
<Child4/>
<Child5/>
</div>
}
现在,当child3RefValue 发生变化时,useEffect 将会运行,如果只有isChild3Ref 是false ,状态将被更新。根据isChild3Ref 的值,Child3 组件将被渲染。因此,即使用户向上和向下滚动了几次,该组件也只渲染一次。
你可以使用Next.js动态导入再进一步。所以只有当isChild3Ref 状态值为真时,Child3 组件才会被导入。
import Child1 from "../Child1";
import Child2 from "../Child2";
import Child4 from "../Child4";
import Child5 from "../Child5";
import useOnScreen from "../useOnScreen";
const Child3 = dynamic(() => import("../Child3"));
const ParentComponent = () => {
const child3Ref = useRef();
const child3RefValue = useOnScreen(child3Ref);
const [isChild3Ref, setIsChild3Ref] = useState(false);
useEffect(() => {
if (!isChild3Ref)
setIsChild3Ref(child3RefValue);
}, [child3RefValue])
return <div>
<Child1/>
<Child2/>
<div ref={child3Ref}>
{isChild3Ref && <Child3/>}
</div>
<Child4/>
<Child5/>
</div>
}
总结
懒加载是防止页面上不必要的内容渲染的一个好方法。通过使用它,你可以最大限度地减少页面的初始加载时间,因为子组件将只在用户需要时才会被渲染。当你遇到类似的情况时,不要忘记尝试一下。
如果你对Next.js更感兴趣,你可以参考我关于Next.js预渲染的文章。
编码愉快!