无限大日志渲染

120 阅读1分钟

react

import { memo, useEffect, useReducer, useRef, useMemo } from 'react';
import cloneDeep from 'lodash/cloneDeep';
import { HugeLogWrapper, HeaderEmptyWrapper, FooterEmptyWrapper } from './styles';

const [currentLog, deepLengthMap, maxLength, splitLength, headerMap, footerMap]: any = [[], new Map(), 10, 2, new Map(), new Map()];
let [data, offset, deep, timeOut, isInit, isUp, isDown, maxDeep]: any = [{ length: maxLength }, 0, 0, void 0, true, false, true, 0];

const initialState = {
  contentCount: 0,
};
const reducer = (state: any, payload: any) => ({ ...state, ...payload });

const RenderHugeLog = (props: any) => {
  const {
    ApiMethod,
    logPath,
    isRefresh,
  } = props;

  const [state, dispatch] = useReducer(reducer, initialState);
  const { contentCount } = state;

  const domRef: any = useRef(null);

  const getContentList = async () => {
    do {
      const res: any = await ApiMethod({
        limit: 10,
        logPath,
        offset,
      });
      data = res?.data?.data;
      const dataLength = data?.length;
      deepLengthMap.set(deep, { dataLength, offset });

      offset += dataLength;

      currentLog.push({ deep, data });
      currentLog?.sort?.((a: any, b: any) => a.deep - b.deep);
      if (currentLog.length > maxLength) currentLog.shift();

      if (dataLength < 10 || (currentLog.length === maxLength)) {
        dispatch({
          contentCount: contentCount + 1,
        });
        isInit = false;
      }

      if (dataLength < 10) maxDeep = deep;

      deep += 1;
    } while (data?.length > 9 && currentLog.length < maxLength);
  };

  const intersectionObserver = new IntersectionObserver((entries: any) => {
    if (isInit) return;
    entries?.forEach?.((entry: any) => {
      if (entry?.isIntersecting) {
        const entryClassName = entry.target.className;
        if (entryClassName === 'header_dom' && isUp) {
          const firstDeep = currentLog?.[0]?.deep;
          if (firstDeep >= splitLength) {
            currentLog.splice(maxLength - splitLength);
            [deep, offset] = [firstDeep - splitLength, deepLengthMap.get(firstDeep - splitLength)?.offset ?? 0];
          } else {
            [deep, offset] = [0, 0];
          }
          [isInit, isDown] = [true, true];
          getContentList();
          if (deep === 0) isUp = false;
        } else if (entryClassName === 'footer_dom' && isDown) {
          const lastDeep = currentLog?.at?.(-1)?.deep;
          const lastItem = deepLengthMap.get(lastDeep);
          [deep, offset] = [lastDeep + 1, lastItem.offset + lastItem.dataLength];
          if (maxDeep !== 0 && maxDeep < deep) return;
          const replaceList = currentLog.splice(0, splitLength);

          const headerDom: any = document.querySelector('div.header_dom ');
          headerMap.set(Math.floor(replaceList[0]?.deep / splitLength), headerDom?.clientHeight ?? 0);

          [isInit, isUp] = [true, true];
          getContentList();
        }
      }
    });
  });

  useEffect(() => {
    getContentList();
    return () => {
      if (timeOut) {
        clearTimeout(timeOut);
        timeOut = void 0;
      }
      deepLengthMap?.clear?.();
      headerMap?.clear?.();
      footerMap?.clear?.();
      currentLog.length = 0;
      [data, offset, deep, isInit, isUp, isDown] = [{ length: maxLength }, 0, 0, true, false, true];
    };
  }, [logPath, isRefresh]);

  const renderRowDom = (list: any, startRow: number) => (
    <>
      {
        list.map((content: string, index: number) => (
          <section className="content_row" key={`${content.slice(0, maxLength)}${index}`}>
            <span className="row_number">{startRow + index + 1}</span>
            <span className="content_wrapper" dangerouslySetInnerHTML={{ __html: content }} />
          </section>
        ))
      }
    </>
  );

  const startRow = deepLengthMap.get(currentLog?.[0]?.deep)?.offset ?? 0;
  const renderListDom = useMemo(() => {
    if (currentLog?.length === maxLength) {
      const cloneLogs = cloneDeep(currentLog);
      const endLogs = cloneLogs.splice(maxLength - splitLength, splitLength);
      const headerLogs = cloneLogs.splice(0, splitLength);

      ['header_dom', 'footer_dom'].forEach((name: string) => {
        const dom = document.querySelector(`div.${name}`);
        if (dom) intersectionObserver.unobserve?.(dom);
      });

      timeOut = setTimeout(() => {
        ['header_dom', 'footer_dom'].forEach((name: string) => {
          const dom = document.querySelector(`div.${name}`);
          if (dom) intersectionObserver.observe(dom);
        });
      });

      const headerList = headerLogs?.map?.((item: any) => item.data)?.flat?.() ?? [];
      const cloneList = cloneLogs?.map?.((item: any) => item.data)?.flat?.() ?? [];
      const endList = endLogs?.map?.((item: any) => item.data)?.flat?.() ?? [];

      return (
        <>
          {
            [
              { list: headerList, className: 'header_dom', startRow },
              { list: cloneList, className: 'body_dom', startRow: startRow + headerList.length },
              { list: endList, className: 'footer_dom', startRow: startRow + headerList.length + cloneList.length },
            ].map(({ list, className, startRow: sr }: any) => (
              <div className={className} key={className + startRow}>
                {
                  renderRowDom(list, sr)
                }
              </div>
            ))
          }
        </>
      );
    }
    return renderRowDom(currentLog?.map?.((item: any) => item.data)?.flat?.(), startRow);
  }, [contentCount]);

  let HeaderEmptyHeight = 0;
  for (let i = 0; i < Math.floor(currentLog?.[0]?.deep ?? 0) / splitLength; i += 1) {
    HeaderEmptyHeight += headerMap.get(i)
  }

  return (
    <HugeLogWrapper
      ref={domRef}
      lineWidth={`${startRow + (currentLog?.map?.((item: any) => item.data)?.flat?.()?.length ?? 0)}`.length * 14 + 5}
    >
      <HeaderEmptyWrapper height={HeaderEmptyHeight} />
      {
        renderListDom
      }
      <FooterEmptyWrapper />
    </HugeLogWrapper>
  );
};

export default memo(RenderHugeLog);