solid.js 虚拟列表

import {
  batch,
  createEffect,
  createSignal,
  For,
  JSX,
  MergeProps,
  mergeProps,
  on,
  onMount,
} from "solid-js";

type DefaultProps<T, K extends keyof T> = MergeProps<[Required<Pick<T, K>>, T]>;

function useDefaultProps<T, K extends keyof T>(
  props: T,
  defaults: Required<Pick<T, K>>
): DefaultProps<T, K> {
  // eslint-disable-next-line solid/reactivity
  return mergeProps(defaults, props);
}

type OnParameters<T1, T2> = Parameters<typeof on<T1, T2>>;

let useEffectWatch = <T1, T2>(
  a: OnParameters<T1, T2>[0],
  b: OnParameters<T1, T2>[1],
  c?: OnParameters<T1, T2>[2]
) => {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  createEffect(on(a, b, c));
};

let useEffectWatchDefer = <T1, T2>(
  a: OnParameters<T1, T2>[0],
  b: OnParameters<T1, T2>[1]
) => {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  createEffect(on(a, b, { defer: true }));
};

export function VirtualList<T>(inProps: {
  data: T[];
  rowHeight: number;
  renderRow: (row: T) => JSX.Element;
  overscanCount?: number;
  style?: JSX.CSSProperties;
  class?: string;
}) {

  let props = useDefaultProps(inProps, {
    overscanCount: 5,
  });

  let rootElement: HTMLDivElement;

  const [height, setHeight] = createSignal<number>(0);
  const [rowCount, setRowCount] = createSignal<number>(0);

  const [offset, setOffset] = createSignal<number>(0);
  const [start, setStart] = createSignal<number>(0);
  const [end, setEnd] = createSignal<number>(0);
  const [selection, setSelection] = createSignal<T[]>([]);

  //确保容器的高度始终设置为根元素的偏移高度。
  const resize = () => {
    if (height() !== rootElement.offsetHeight) {
      setHeight(rootElement.offsetHeight);
      setRowCount(getRowCount());
    }
  };

  // 获取渲染的第一个元素索引
  const getStart = () => {
    let ret = Number((offset() / props.rowHeight).toFixed(0));
    ret = Math.max(0, ret - (ret % props.overscanCount));

    //console.log("getStart:", ret);

    return ret;
  };

  // 获取展示的行数
  const getRowCount = () => {
    let ret = Number((height() / props.rowHeight).toFixed(0)) + props.overscanCount;

    //console.log("getRowCount:", ret);

    return ret;
  };

  // 获取渲染的最后一个元素索引
  const getEnd = () => {
    let ret = start() + rowCount() + 1;

    //console.log("getEnd:", ret);

    return ret;
  };

  const tick = () => {
    batch(() => {
      setOffset(rootElement.scrollTop);
      setStart(getStart());
      setEnd(getEnd());
      // 筛选渲染范围的数据
      setSelection(props.data?.slice(start(), end()));
      // console.log("tick:", {
      //   offset: offset(),
      //   start: start(),
      //   end: end(),
      //   selection: selection(),
      //   data: props.data,
      // });
    });
  };

  const handleScroll = () => {
    //console.log(offset(), rootElement.scrollTop);

    if (offset() != rootElement.scrollTop) {
      tick();
    }
  };

  onMount(() => {
    resize();
    tick();
    rootElement?.addEventListener?.("resize", resize);
  });

  // 首次不触发, 交给 onMount
  useEffectWatchDefer(
    () => props.data, // 只监听 props.data
    () => {
      tick();
    }
  );
  return (
    <div
      ref={(r) => (rootElement = r)}
      style={{ overflow: "auto", ...props.style }}
      class={props.class}
      onScroll={handleScroll}
    >
      <div
        style={{
          position: "relative",
          overflow: "hidden",
          width: "100%",
          "min-height": "100%",
          height: `${(props.data?.length ?? 0) * props.rowHeight}px`,
        }}
      >
        <div
          style={{
            position: "absolute",
            top: `${start() * props.rowHeight}px`,
            left: 0,
            height: "100%",
            width: "100%",
            overflow: "visible",
          }}
        >
          <For each={selection()}>
            {(row) => {
              return props.renderRow(row);
            }}
          </For>
        </div>
      </div>
    </div>
  );
}

使用

<VirtualList
  style={{height:'100%'}}
  data={dataList()!}
  rowHeight={30}
  renderRow={(row) => {
     return (
      <div style={{ height: "30px" }} >...</div>
     )
  }}
/>

你可能感兴趣的:(JavaScript,javascript,前端,solid,solid.js,solidjs,VirtualList)