refactor: improve MarkdownSvgRenderer re-render

This commit is contained in:
one 2025-08-16 18:42:02 +08:00
parent 3d44f2da44
commit 5d4a1f49d6

View File

@ -1,5 +1,5 @@
import { makeSvgSizeAdaptive } from '@renderer/utils/image'
import React, { FC, useEffect, useRef, useState } from 'react'
import React, { FC, useEffect, useRef } from 'react'
interface SvgProps extends React.SVGProps<SVGSVGElement> {
'data-needs-measurement'?: 'true'
@ -11,35 +11,40 @@ interface SvgProps extends React.SVGProps<SVGSVGElement> {
* This component handles two types of SVGs passed from `react-markdown`:
*
* 1. **Pre-processed SVGs**: Simple SVGs that were already handled by the
* `rehypeScalableSvg` plugin. These are rendered directly with zero
* performance overhead.
* `rehypeScalableSvg` plugin. These are rendered directly.
*
* 2. **SVGs needing measurement**: Complex SVGs (e.g., with unit-based
* dimensions like "100pt") are flagged with `data-needs-measurement`.
* This component will perform a one-time, off-screen measurement for
* these SVGs upon mounting to ensure they are rendered correctly and
* scalably.
* 2. **SVGs needing measurement**: Complex SVGs are flagged with
* `data-needs-measurement`. This component performs a one-time DOM
* mutation upon mounting to make them scalable. To prevent React from
* reverting these changes during subsequent renders, it stops passing
* the original `width` and `height` props after the mutation is complete.
*/
const MarkdownSvgRenderer: FC<SvgProps> = (props) => {
const { 'data-needs-measurement': needsMeasurement, ...restProps } = props
const svgRef = useRef<SVGSVGElement>(null)
const [isMeasured, setIsMeasured] = useState(false)
const isMeasuredRef = useRef(false)
useEffect(() => {
if (needsMeasurement && svgRef.current && !isMeasured) {
// The element is a real DOM node, we can now measure it.
if (needsMeasurement && svgRef.current && !isMeasuredRef.current) {
// Directly mutate the DOM element to make it adaptive.
makeSvgSizeAdaptive(svgRef.current)
// Set flag to prevent re-measuring on subsequent renders
setIsMeasured(true)
// Set flag to prevent re-measuring. This does not trigger a re-render.
isMeasuredRef.current = true
}
}, [needsMeasurement, isMeasured])
}, [needsMeasurement])
// For SVGs that need measurement, we render them once with their original
// props to allow the ref to capture the DOM element for measurement.
// The `useEffect` will then trigger, process the element, and cause a
// re-render with the correct scalable attributes.
// For simple SVGs, they are rendered correctly from the start.
return <svg ref={svgRef} {...restProps} />
// Create a mutable copy of props to potentially modify.
const finalProps = { ...restProps }
// If the SVG has been measured and mutated, we prevent React from
// re-applying the original width and height attributes on subsequent renders.
// This preserves the changes made by `makeSvgSizeAdaptive`.
if (isMeasuredRef.current) {
delete finalProps.width
delete finalProps.height
}
return <svg ref={svgRef} {...finalProps} />
}
export default MarkdownSvgRenderer