From 96ff9f660c0a469daafbb5f5cad47370bcd3a8f0 Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 12 Sep 2024 21:50:16 +0800 Subject: [PATCH] perf: reduce re-render when ctx menu open Signed-off-by: Innei --- eslint.config.mjs | 2 + .../src/components/common/ShadowDOM.tsx | 3 ++ .../src/components/ui/markdown/Markdown.tsx | 6 ++- .../ui/markdown/renderers/MarkdownP.tsx | 4 +- .../src/modules/entry-content/header.tsx | 6 ++- .../entry-content/{hooks.tsx => hooks.ts} | 5 ++- .../src/modules/entry-content/index.tsx | 37 ++++++++++--------- .../src/modules/entry-content/provider.tsx | 2 +- .../src/modules/feed-column/header.tsx | 6 +-- src/renderer/src/modules/feed-column/list.tsx | 7 +++- 10 files changed, 47 insertions(+), 31 deletions(-) rename src/renderer/src/modules/entry-content/{hooks.tsx => hooks.ts} (59%) diff --git a/eslint.config.mjs b/eslint.config.mjs index 249d7d4ab..be55c956d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -18,6 +18,8 @@ export default defineConfig( "unicorn/prefer-math-trunc": "off", "@eslint-react/no-clone-element": 0, "@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": 0, + // NOTE: Disable this temporarily + "react-compiler/react-compiler": 0, "no-restricted-syntax": 0, "no-restricted-globals": [ "error", diff --git a/src/renderer/src/components/common/ShadowDOM.tsx b/src/renderer/src/components/common/ShadowDOM.tsx index eae8e7412..a6ec8812e 100644 --- a/src/renderer/src/components/common/ShadowDOM.tsx +++ b/src/renderer/src/components/common/ShadowDOM.tsx @@ -1,4 +1,5 @@ import { useUISettingKey } from "@renderer/atoms/settings/ui" +import { useReduceMotion } from "@renderer/hooks/biz/useReduceMotion" import { useIsDark } from "@renderer/hooks/common" import { nanoid } from "nanoid" import type { FC, PropsWithChildren, ReactNode } from "react" @@ -100,6 +101,7 @@ export const ShadowDOM: FC>> & { const dark = useIsDark() const uiFont = useUISettingKey("uiFontFamily") + const reduceMotion = useReduceMotion() return ( @@ -112,6 +114,7 @@ export const ShadowDOM: FC>> & { [uiFont], )} id="shadow-html" + data-motion-reduce={reduceMotion} data-theme={dark ? "dark" : "light"} className="font-theme" > diff --git a/src/renderer/src/components/ui/markdown/Markdown.tsx b/src/renderer/src/components/ui/markdown/Markdown.tsx index b4f385561..1a15eb38a 100644 --- a/src/renderer/src/components/ui/markdown/Markdown.tsx +++ b/src/renderer/src/components/ui/markdown/Markdown.tsx @@ -2,7 +2,7 @@ import { parseHtml } from "@renderer/lib/parse-html" import type { RemarkOptions } from "@renderer/lib/parse-markdown" import { parseMarkdown } from "@renderer/lib/parse-markdown" import { cn } from "@renderer/lib/utils" -import { createElement, Fragment, useEffect, useMemo, useState } from "react" +import { createElement, Fragment, memo, useEffect, useMemo, useState } from "react" import { MarkdownRenderContainerRefContext } from "./context" @@ -34,7 +34,7 @@ export const Markdown: Component< ) } -export const HTML = ( +const HTMLImpl = ( props: { children: string | null | undefined as: A @@ -83,3 +83,5 @@ export const HTML = ( ) } + +export const HTML = memo(HTMLImpl) diff --git a/src/renderer/src/components/ui/markdown/renderers/MarkdownP.tsx b/src/renderer/src/components/ui/markdown/renderers/MarkdownP.tsx index 013aa8ec5..f6170b59f 100644 --- a/src/renderer/src/components/ui/markdown/renderers/MarkdownP.tsx +++ b/src/renderer/src/components/ui/markdown/renderers/MarkdownP.tsx @@ -1,5 +1,5 @@ import { FeedViewType } from "@renderer/lib/enum" -import { useEntryContentContext } from "@renderer/modules/entry-content/hooks" +import { useEntryContentContextSelector } from "@renderer/modules/entry-content/hooks" import * as React from "react" import { ensureAndRenderTimeStamp } from "../utils" @@ -7,7 +7,7 @@ import { ensureAndRenderTimeStamp } from "../utils" export const MarkdownP: Component< React.DetailedHTMLProps, HTMLParagraphElement> > = ({ children, ...props }) => { - const { view } = useEntryContentContext() + const view = useEntryContentContextSelector((s) => s.view) const parseTimeline = view === FeedViewType.Audios if (parseTimeline && typeof children === "string") { const renderer = ensureAndRenderTimeStamp(children) diff --git a/src/renderer/src/modules/entry-content/header.tsx b/src/renderer/src/modules/entry-content/header.tsx index 3930c3800..dd97852c1 100644 --- a/src/renderer/src/modules/entry-content/header.tsx +++ b/src/renderer/src/modules/entry-content/header.tsx @@ -20,12 +20,12 @@ import { useEntry } from "@renderer/store/entry/hooks" import { useFeedById } from "@renderer/store/feed" import { noop } from "foxact/noop" import { AnimatePresence, m } from "framer-motion" -import { useMemo, useState } from "react" +import { memo, useMemo, useState } from "react" import { useEntryContentScrollToTop, useEntryTitleMeta } from "./atoms" import { EntryReadHistory } from "./components/EntryReadHistory" -export function EntryHeader({ +function EntryHeaderImpl({ view, entryId, className, @@ -211,3 +211,5 @@ const ElectronAdditionActions = window.electron ) } : noop + +export const EntryHeader = memo(EntryHeaderImpl) diff --git a/src/renderer/src/modules/entry-content/hooks.tsx b/src/renderer/src/modules/entry-content/hooks.ts similarity index 59% rename from src/renderer/src/modules/entry-content/hooks.tsx rename to src/renderer/src/modules/entry-content/hooks.ts index 4cfcb8bb4..ee5df81c0 100644 --- a/src/renderer/src/modules/entry-content/hooks.tsx +++ b/src/renderer/src/modules/entry-content/hooks.ts @@ -1,5 +1,5 @@ import { isDev } from "@renderer/constants" -import { useContext } from "react" +import { useContext, useContextSelector } from "use-context-selector" import { EntryContentContext } from "./provider" @@ -10,3 +10,6 @@ export const useEntryContentContext = () => { } return ctx } + +export const useEntryContentContextSelector = (selector: (ctx: EntryContentContext) => T) => + useContextSelector(EntryContentContext, selector) diff --git a/src/renderer/src/modules/entry-content/index.tsx b/src/renderer/src/modules/entry-content/index.tsx index e1b15a570..e7d5c1a8e 100644 --- a/src/renderer/src/modules/entry-content/index.tsx +++ b/src/renderer/src/modules/entry-content/index.tsx @@ -36,7 +36,7 @@ import { getPreferredTitle, useFeedById, useFeedHeaderTitle } from "@renderer/st import type { FallbackRender } from "@sentry/react" import { ErrorBoundary } from "@sentry/react" import type { FC } from "react" -import { useEffect, useLayoutEffect, useRef } from "react" +import { memo, useEffect, useLayoutEffect, useMemo, useRef } from "react" import { useHotkeys } from "react-hotkeys-hook" import { LoadingWithIcon } from "../../components/ui/loading" @@ -137,6 +137,19 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl const isPeekModal = useInPeekModal() + const contentAccessories = useMemo( + () => (isPeekModal ? undefined : ), + [entryId, isPeekModal], + ) + const stableRenderStyle = useMemo( + () => + readerFontFamily + ? { + fontFamily: readerFontFamily, + } + : undefined, + [readerFontFamily], + ) if (!entry) return null const content = entry?.entries.content ?? data?.entries.content @@ -162,13 +175,7 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl ref={scrollerRef} >
@@ -222,16 +229,10 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl {!isInReadabilityMode ? ( } + accessory={contentAccessories} as="article" className="prose !max-w-full dark:prose-invert prose-h1:text-[1.6em]" - style={ - readerFontFamily - ? { - fontFamily: readerFontFamily, - } - : undefined - } + style={stableRenderStyle} renderInlineStyle={readerRenderInlineStyle} > {content} @@ -414,7 +415,7 @@ const RenderError: FallbackRender = ({ error }) => { ) } -const ContainerToc: FC = () => { +const ContainerToc: FC = memo(() => { const wrappedElement = useWrappedElement() return ( @@ -433,4 +434,4 @@ const ContainerToc: FC = () => {
) -} +}) diff --git a/src/renderer/src/modules/entry-content/provider.tsx b/src/renderer/src/modules/entry-content/provider.tsx index 3ceb42bcb..0410b6f27 100644 --- a/src/renderer/src/modules/entry-content/provider.tsx +++ b/src/renderer/src/modules/entry-content/provider.tsx @@ -1,5 +1,5 @@ import { FeedViewType } from "@renderer/lib/enum" -import { createContext } from "react" +import { createContext } from "use-context-selector" export interface EntryContentContext { entryId: string diff --git a/src/renderer/src/modules/feed-column/header.tsx b/src/renderer/src/modules/feed-column/header.tsx index 3720c0672..719ae2689 100644 --- a/src/renderer/src/modules/feed-column/header.tsx +++ b/src/renderer/src/modules/feed-column/header.tsx @@ -10,7 +10,7 @@ import { stopPropagation } from "@renderer/lib/dom" import { cn } from "@renderer/lib/utils" import { m } from "framer-motion" import type { FC, PropsWithChildren } from "react" -import { useCallback, useRef, useState } from "react" +import { memo, useCallback, useRef, useState } from "react" import { Link } from "react-router-dom" import { toast } from "sonner" @@ -29,7 +29,7 @@ const useBackHome = (active: number) => { ) } -export const FeedColumnHeader = () => { +export const FeedColumnHeader = memo(() => { const [active] = useSidebarActiveView() const navigateBackHome = useBackHome(active) @@ -70,7 +70,7 @@ export const FeedColumnHeader = () => { ) -} +}) const LayoutActionButton = () => { const feedColumnShow = useFeedColumnShow() diff --git a/src/renderer/src/modules/feed-column/list.tsx b/src/renderer/src/modules/feed-column/list.tsx index ff699cbbc..ec8915014 100644 --- a/src/renderer/src/modules/feed-column/list.tsx +++ b/src/renderer/src/modules/feed-column/list.tsx @@ -1,3 +1,4 @@ + import * as HoverCard from "@radix-ui/react-hover-card" import { useUISettingKey } from "@renderer/atoms/settings/ui" import { ScrollArea } from "@renderer/components/ui/scroll-area" @@ -14,7 +15,7 @@ import { useFeedStore } from "@renderer/store/feed" import { getSubscriptionByFeedId, useSubscriptionByView } from "@renderer/store/subscription" import { useFeedUnreadStore } from "@renderer/store/unread" import { AnimatePresence, m } from "framer-motion" -import { Fragment, useMemo, useState } from "react" +import { Fragment, memo, useMemo, useState } from "react" import { Link } from "react-router-dom" import { @@ -57,7 +58,7 @@ const useUpdateUnreadCount = () => { }) } -export function FeedList({ className, view }: { className?: string; view: number }) { +function FeedListImpl({ className, view }: { className?: string; view: number }) { const [expansion, setExpansion] = useState(false) const data = useGroupedData(view) @@ -348,3 +349,5 @@ const SortableList = (props: FeedListProps) => { } } } + +export const FeedList = memo(FeedListImpl)