使用 React 和 Intersection Observer API 轻松实现懒加载
我已经更新了 useIntersectionObserver hook 的实现,请参考这篇文章。
在本文中,我将编写一个 React Hook 和一个 React 组件,帮助你在 ReactJS 中实现懒加载。
什么是交叉路口观察者 API?
基本上,交叉观察器会监视元素,并检查它们是否与文档的视口(或者大多数情况下,与浏览器视口)相交。
更多信息请参阅MDN 文档。
创建 React Hook
首先,让我们从一个像这样的空钩子开始:
export function useIntersectionObserver(){
}
然后我们可以添加一个状态来告诉我们组件是否相交,并返回该状态:
export function useIntersectionObserver(){
const [isIntersecting, setIsIntersecting] = useState(false);
return isIntersecting;
}
现在,我们需要一个可以容纳观察者的引用:
export function useIntersectionObserver(){
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
return isIntersecting;
}
由于我们需要为观察者指定目标元素,让我们在钩子函数中添加参数和状态:
export function useIntersectionObserver(ref: MutableRefObject<Element | null>){
const [element, setElement] = useState<Element | null>(null);
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
useEffect(() => {
setElement(ref.current);
}, [ref]);
return isIntersecting;
}
现在,我们可以创建一个观察者来观察元素:
export function useIntersectionObserver(ref: MutableRefObject<Element | null>){
const [element, setElement] = useState<Element | null>(null);
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
useEffect(() => {
setElement(ref.current);
}, [ref]);
useEffect(() => {
if (!element) return;
const ob = observer.current = new IntersectionObserver(([entry]) => {
const isElementIntersecting = entry.isIntersecting;
setIsIntersecting(isElementIntersecting);
})
ob.observe(element);
}, [element])
return isIntersecting;
}
组件卸载或目标元素更改后,不要忘记断开观察者连接。
export function useIntersectionObserver(ref: MutableRefObject<Element | null>){
const [element, setElement] = useState<Element | null>(null);
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
const cleanOb = () => {
if (observer.current) {
observer.current.disconnect()
}
}
useEffect(() => {
setElement(ref.current);
}, [ref]);
useEffect(() => {
if (!element) return;
cleanOb();
const ob = observer.current = new IntersectionObserver(([entry]) => {
const isElementIntersecting = entry.isIntersecting;
setIsIntersecting(isElementIntersecting);
})
ob.observe(element);
return () => {
cleanOb()
}
}, [element])
return isIntersecting;
}
现在,我们希望能够配置观察者,所以让我们将选项作为参数添加到钩子函数中:
export function useIntersectionObserver(ref: MutableRefObject<Element | null>, options: IntersectionObserverInit = {}){
const [element, setElement] = useState<Element | null>(null);
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
const cleanOb = () => {
if (observer.current) {
observer.current.disconnect()
}
}
useEffect(() => {
setElement(ref.current);
}, [ref]);
useEffect(() => {
if (!element) return;
cleanOb();
const ob = observer.current = new IntersectionObserver(([entry]) => {
const isElementIntersecting = entry.isIntersecting;
setIsIntersecting(isElementIntersecting);
}, { ...options })
ob.observe(element);
return () => {
cleanOb()
}
}, [element, options ])
return isIntersecting;
}
有关选项的更多信息,请参阅MDN 文档。
最后,由于我们通常不想删除已渲染的内容,让我们添加一个参数,允许我们选择是否要在与目标元素相交后断开观察者连接。
export function useIntersectionObserver(ref: MutableRefObject<Element | null>, options: IntersectionObserverInit = {}, forward: boolean = true) {
const [element, setElement] = useState<Element | null>(null);
const [isIntersecting, setIsIntersecting] = useState(false);
const observer = useRef<null | IntersectionObserver>(null);
const cleanOb = () => {
if (observer.current) {
observer.current.disconnect()
}
}
useEffect(() => {
setElement(ref.current);
}, [ref]);
useEffect(() => {
if (!element) return;
cleanOb()
const ob = observer.current = new IntersectionObserver(([entry]) => {
const isElementIntersecting = entry.isIntersecting;
if (!forward) {
setIsIntersecting(isElementIntersecting)
} else if (forward && !isIntersecting && isElementIntersecting) {
setIsIntersecting(isElementIntersecting);
cleanOb()
};
}, { ...options })
ob.observe(element);
return () => {
cleanOb()
}
}, [element, options ])
return isIntersecting;
}
创建延迟加载组件
一旦我们有了所需的钩子,用它创建一个懒加载组件就非常简单了:
interface LazyLoadProps {
tag?: ComponentType | keyof JSX.IntrinsicElements
children: ReactNode
style?: CSSProperties
className?: string
root?: Element | Document | null
threshold?: number | number[]
rootMargin?: string
forward?: boolean
}
export function LazyLoad(props: LazyLoadProps) {
const { tag = 'div', children, style, className } = props;
const Tag: any = tag;
const ref = useRef<Element>(null)
const isIntersecting = useIntersectionObserver(ref, {
root: props.root ?? null,
threshold: props.threshold ?? 0,
rootMargin: props.rootMargin
}, props.forward);
return (
<Tag
ref={ref}
style={style}
className={className}
children={isIntersecting ? children : null}
/>
)
}
好了,我们开始吧。
感谢您阅读本文。如果您发现任何问题,请告知我。
hook 和 Lazyload 组件都包含在我的 npm 包ax-react-lib中。
文章来源:https://dev.to/anxiny/easy-lazy-loading-with-react-intersection-observer-api-1dll