perf: reduce re-render when ctx menu open
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
85d8e226fb
commit
96ff9f660c
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<PropsWithChildren<React.HTMLProps<HTMLElement>>> & {
|
|||
const dark = useIsDark()
|
||||
|
||||
const uiFont = useUISettingKey("uiFontFamily")
|
||||
const reduceMotion = useReduceMotion()
|
||||
|
||||
return (
|
||||
<root.div {...rest}>
|
||||
|
|
@ -112,6 +114,7 @@ export const ShadowDOM: FC<PropsWithChildren<React.HTMLProps<HTMLElement>>> & {
|
|||
[uiFont],
|
||||
)}
|
||||
id="shadow-html"
|
||||
data-motion-reduce={reduceMotion}
|
||||
data-theme={dark ? "dark" : "light"}
|
||||
className="font-theme"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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 = <A extends keyof JSX.IntrinsicElements = "div">(
|
||||
const HTMLImpl = <A extends keyof JSX.IntrinsicElements = "div">(
|
||||
props: {
|
||||
children: string | null | undefined
|
||||
as: A
|
||||
|
|
@ -83,3 +83,5 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
|
|||
</MarkdownRenderContainerRefContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const HTML = memo(HTMLImpl)
|
||||
|
|
|
|||
|
|
@ -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<React.HTMLAttributes<HTMLParagraphElement>, 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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = <T>(selector: (ctx: EntryContentContext) => T) =>
|
||||
useContextSelector(EntryContentContext, selector)
|
||||
|
|
@ -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 : <ContainerToc key={entryId} />),
|
||||
[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}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
readerFontFamily
|
||||
? {
|
||||
fontFamily: readerFontFamily,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
style={stableRenderStyle}
|
||||
className="duration-200 ease-in-out animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:fade-in-0 f-motion-reduce:slide-in-from-bottom-0"
|
||||
key={entry.entries.id}
|
||||
>
|
||||
|
|
@ -222,16 +229,10 @@ export const EntryContentRender: Component<{ entryId: string }> = ({ entryId, cl
|
|||
{!isInReadabilityMode ? (
|
||||
<ShadowDOM>
|
||||
<HTML
|
||||
accessory={isPeekModal ? undefined : <ContainerToc key={entryId} />}
|
||||
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 (
|
||||
<RootPortal to={wrappedElement!}>
|
||||
|
|
@ -433,4 +434,4 @@ const ContainerToc: FC = () => {
|
|||
</div>
|
||||
</RootPortal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const LayoutActionButton = () => {
|
||||
const feedColumnShow = useFeedColumnShow()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue