Browse Source

perf: close #909 reduce message items render time

Yidadaa 1 year ago
parent
commit
a69cec89fb
2 changed files with 61 additions and 57 deletions
  1. 4 7
      app/components/chat.tsx
  2. 57 50
      app/components/markdown.tsx

+ 4 - 7
app/components/chat.tsx

@@ -1,5 +1,5 @@
 import { useDebouncedCallback } from "use-debounce";
-import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
+import { useState, useRef, useEffect, useLayoutEffect } from "react";
 
 import SendWhiteIcon from "../icons/send-white.svg";
 import BrainIcon from "../icons/brain.svg";
@@ -64,12 +64,9 @@ import {
   useMaskStore,
 } from "../store/mask";
 
-const Markdown = dynamic(
-  async () => memo((await import("./markdown")).Markdown),
-  {
-    loading: () => <LoadingIcon />,
-  },
-);
+const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
+  loading: () => <LoadingIcon />,
+});
 
 function exportMessages(messages: Message[], topic: string) {
   const mdText =

+ 57 - 50
app/components/markdown.tsx

@@ -9,6 +9,7 @@ import { useRef, useState, RefObject, useEffect } from "react";
 import { copyToClipboard } from "../utils";
 
 import LoadingIcon from "../icons/three-dots.svg";
+import React from "react";
 
 export function PreCode(props: { children: any }) {
   const ref = useRef<HTMLPreElement>(null);
@@ -29,6 +30,32 @@ export function PreCode(props: { children: any }) {
   );
 }
 
+function _MarkDownContent(props: { content: string }) {
+  return (
+    <ReactMarkdown
+      remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
+      rehypePlugins={[
+        RehypeKatex,
+        [
+          RehypeHighlight,
+          {
+            detect: false,
+            ignoreMissing: true,
+          },
+        ],
+      ]}
+      components={{
+        pre: PreCode,
+      }}
+      linkTarget={"_blank"}
+    >
+      {props.content}
+    </ReactMarkdown>
+  );
+}
+
+export const MarkdownContent = React.memo(_MarkDownContent);
+
 export function Markdown(
   props: {
     content: string;
@@ -38,69 +65,49 @@ export function Markdown(
   } & React.DOMAttributes<HTMLDivElement>,
 ) {
   const mdRef = useRef<HTMLDivElement>(null);
+  const renderedHeight = useRef(0);
+  const inView = useRef(false);
 
   const parent = props.parentRef.current;
   const md = mdRef.current;
-  const rendered = useRef(true); // disable lazy loading for bad ux
-  const [counter, setCounter] = useState(0);
-
-  useEffect(() => {
-    // to triggr rerender
-    setCounter(counter + 1);
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [props.loading]);
-
-  const inView =
-    rendered.current ||
-    (() => {
-      if (parent && md) {
-        const parentBounds = parent.getBoundingClientRect();
-        const mdBounds = md.getBoundingClientRect();
-        const isInRange = (x: number) =>
-          x <= parentBounds.bottom && x >= parentBounds.top;
-        const inView = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
-
-        if (inView) {
-          rendered.current = true;
-        }
 
-        return inView;
-      }
-    })();
+  if (parent && md) {
+    const parentBounds = parent.getBoundingClientRect();
+    const twoScreenHeight = Math.max(500, parentBounds.height * 2);
+    const mdBounds = md.getBoundingClientRect();
+    const isInRange = (x: number) =>
+      x <= parentBounds.bottom + twoScreenHeight &&
+      x >= parentBounds.top - twoScreenHeight;
+    inView.current = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
+  }
 
-  const shouldLoading = props.loading || !inView;
+  if (inView.current && md) {
+    renderedHeight.current = Math.max(
+      renderedHeight.current,
+      md.getBoundingClientRect().height,
+    );
+  }
 
   return (
     <div
       className="markdown-body"
-      style={{ fontSize: `${props.fontSize ?? 14}px` }}
+      style={{
+        fontSize: `${props.fontSize ?? 14}px`,
+        height:
+          !inView.current && renderedHeight.current > 0
+            ? renderedHeight.current
+            : "auto",
+      }}
       ref={mdRef}
       onContextMenu={props.onContextMenu}
       onDoubleClickCapture={props.onDoubleClickCapture}
     >
-      {shouldLoading ? (
-        <LoadingIcon />
-      ) : (
-        <ReactMarkdown
-          remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
-          rehypePlugins={[
-            RehypeKatex,
-            [
-              RehypeHighlight,
-              {
-                detect: false,
-                ignoreMissing: true,
-              },
-            ],
-          ]}
-          components={{
-            pre: PreCode,
-          }}
-          linkTarget={"_blank"}
-        >
-          {props.content}
-        </ReactMarkdown>
-      )}
+      {inView.current &&
+        (props.loading ? (
+          <LoadingIcon />
+        ) : (
+          <MarkdownContent content={props.content} />
+        ))}
     </div>
   );
 }