返回

React Hook & TypeScript打造虚拟滚动组件,轻松实现高效渲染巨量数据!

前端

手把手教你用React Hook和TypeScript打造虚拟滚动组件

1. 明确目标

虚拟滚动组件的目标是:无论用户如何滚动,浏览器始终只渲染当前视口内的元素,从而提高巨量数据渲染的性能。

2. 理解原理

虚拟滚动组件是通过控制视口的滚动位置来实现的。滚动时,组件只更新当前视口范围内的元素,而其他元素保持隐藏状态。滚动完成后,组件根据新的视口位置更新渲染的元素。

3. 开始动手

1)创建React项目

npx create-react-app virtual-scroll-list --template @typescript

2)安装依赖

npm install react-virtualized

3)创建组件

// VirtualScroll.tsx
import React, { useRef, useState } from "react";
import { useVirtual } from "react-virtualized";

interface Props {
  data: any[];
  renderItem: (item: any) => JSX.Element;
}

const VirtualScroll = ({ data, renderItem }: Props) => {
  const scrollRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);

  const { size, start, end } = useVirtual({
    size: data.length,
    overscanCount: 20,
    onScrollToIndex: setScrollTop,
    scrollToIndex: scrollTop,
  });

  return (
    <div ref={scrollRef}>
      {data.slice(start, end).map(renderItem)}
    </div>
  );
};

export default VirtualScroll;

4)使用组件

// App.tsx
import React from "react";
import VirtualScroll from "./VirtualScroll";

const data = new Array(1000).fill(0).map((_, i) => i);

const App = () => {
  const renderItem = ({ index }) => (
    <div key={index} style={{ height: "50px" }}>
      Item {index}
    </div>
  );

  return (
    <div style={{ height: "500px", overflow: "auto" }}>
      <VirtualScroll data={data} renderItem={renderItem} />
    </div>
  );
};

export default App;

4. 总结提升

1)优化性能

// VirtualScroll.tsx
const VirtualScroll = ({ data, renderItem }: Props) => {
  const scrollRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);

  const { size, start, end } = useVirtual({
    size: data.length,
    overscanCount: 20,
    onScrollToIndex: setScrollTop,
    scrollToIndex: scrollTop,
    getScrollTop: () => scrollRef.current?.scrollTop ?? 0,
    setScrollTop: (scrollToIndex) => {
      if (scrollRef.current) {
        scrollRef.current.scrollTo({
          top: scrollToIndex,
          behavior: "smooth",
        });
      }
    },
  });

  return (
    <div ref={scrollRef}>
      <div style={{ height: size * 50 }}>
        {data.slice(start, end).map(renderItem)}
      </div>
    </div>
  );
};

2)支持自定义样式

// VirtualScroll.tsx
const VirtualScroll = ({ data, renderItem, itemHeight }: Props) => {
  const scrollRef = useRef<HTMLDivElement>(null);
  const [scrollTop, setScrollTop] = useState(0);

  const { size, start, end } = useVirtual({
    size: data.length,
    overscanCount: 20,
    onScrollToIndex: setScrollTop,
    scrollToIndex: scrollTop,
    getScrollTop: () => scrollRef.current?.scrollTop ?? 0,
    setScrollTop: (scrollToIndex) => {
      if (scrollRef.current) {
        scrollRef.current.scrollTo({
          top: scrollToIndex,
          behavior: "smooth",
        });
      }
    },
  });

  return (
    <div ref={scrollRef}>
      <div style={{ height: size * itemHeight }}>
        {data.slice(start, end).map(renderItem)}
      </div>
    </div>
  );
};

通过以上步骤,我们成功实现了一个虚拟滚动组件,可以轻松应对巨量数据的渲染需求。