手写react-lazyload
2024-08-22 10:39 阅读(251)

很多人一定用过react-loadable,这个库已经很久没有更新了,而且对vite的react项目很不友好,会报错,所以我一般会用react-lazyload来对react项目中的资源做懒加载。

react-loadable: 其实就是对import()加载组件的封装,它是在加载组件的时候,才去动态加载所需的资源。

react-lazyload:  是对IntersectionObserver的封装。

其实懒加载的实现形式只有2种:一种是利用import()动态加载,另一种是利用window的 IntersectionObserver 观察器。import会单独打包,但是观察器不行,import()利用异步加载的方式,在组件渲染的时候,才去异步请求对应的资源。而观察器是资源即将显示的时候,才去加载。

基本使用如下:

说白了就是用监听器先去监听这个元素,当监听到元素以后我们就在他的构造函数里面做想做的事情,就OK了。


1.react-lazyload使用

import img1 from './assets/img1.png';
import img2 from './assets/img2.png';
import LazyLoad from 'react-lazyload';

export default function App() {
  return (
    <div>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <p>xxxxxx</p>
      <LazyLoad placeholder={<div>loading...</div>}>
        <img src={img1} />
      </LazyLoad>
      <LazyLoad placeholder={<div>loading...</div>}>
        <img src={img2} />
      </LazyLoad>
    </div>
  );
};

就是用LazyLoad组件包裹需要懒加载的组件,在没有加上之前用 Loading来占位,资源拿到以后就显示资源。


2.手写react-lazyload

搭建基础组件:

import {
  FC,
  ReactNode,
  useState
} from 'react';

interface MyLazyloadProps {
  placeholder?: ReactNode,
  children: ReactNode,
}

const MyLazyload: FC<MyLazyloadProps> = (props) => {

  const {
    children,
    placeholder,
  } = props;

  const [visible, setVisible] = useState(false);

  return <div >
    {visible ? children : placeholder}
  </div>;
};

export default MyLazyload;


引入interscetionObserver


import {
  FC,
  ReactNode,
  useState,
  useEffect,
  useRef
} from 'react';

interface MyLazyloadProps {
  placeholder?: ReactNode,
  children: ReactNode,
}

const MyLazyload: FC<MyLazyloadProps> = (props) => {

  const {
    children,
    placeholder,
  } = props;

  const [visible, setVisible] = useState(false);
  const observerRef = useRef<IntersectionObserver>();
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const node = containerRef.current as HTMLDivElement;
    observerRef.current = new IntersectionObserver((entry) => {
      if (entry[0].isIntersecting) {
        setVisible(true);

        if (node) {
          observerRef.current?.unobserve(node);
        }
      }
    });


    observerRef.current.observe(node);

    () => {
      if (node) {
        observerRef.current?.unobserve(node);
      }
    };
  });

  return <div ref={containerRef}>
    {visible ? children : placeholder}
  </div>;
};

export default MyLazyload;


就是在useEffect里面用IntersectionOberver监控它自己的div元素,当监控元素出现在页面上的时候,将visible设置成true,显示需要懒加载的元素。


测试看看:

下拉显示图片

你要想做出一个超级棒的封装,就需要给我们的懒加载组件添加上一些属性:


import {
    CSSProperties,
    FC,
    ReactNode,
    useRef,
    useState
} from 'react';

interface MyLazyloadProps{
    className?: string,
    style?: CSSProperties,
    placeholder?: ReactNode,
    offset?: string | number,
    width?: number | string,
    height?: string | number,
    onContentVisible?: () => void,
    children: ReactNode,
}

const MyLazyload: FC<MyLazyloadProps> = (props) => {

    const {
        className = '',
        style,
        offset = 0,
        width,
        onContentVisible,
        placeholder,
        height,
        children
    } = props;

    const containerRef = useRef<HTMLDivElement>(null);
    const [visible, setVisible] = useState(false);

    const styles = { height, width, ...style };

    return <div ref={containerRef} className={className} style={styles}>
        {visible? children : placeholder}
    </div>
}

export default MyLazyload;

然后把IntersectionObserver的监听代码加上就好了。