fix(B04): prevent image URI flash across initial render and detail remounts
This commit is contained in:
1 parent
0b613e522d
commit
342820690f
7 files changed
+217
-24
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { memo, useEffect, useState, type ReactElement } from 'react'
|
||||
import { memo, useEffect, useMemo, useState, type ReactElement } from 'react'
|
||||
import './image-renderer.css'
|
||||
import {
|
||||
isSafeImageUrl,
|
||||
@@ -20,7 +20,13 @@ export interface MarkdownImageProps {
|
||||
}
|
||||
|
||||
/** REA ev_717fa5e… src-DbE1_mgM.js [721564,722288); URL resolver is an explicit host adapter. */
|
||||
export const MarkdownImage = memo(function MarkdownImage({
|
||||
export const MarkdownImage = memo(function MarkdownImage(props: MarkdownImageProps): ReactElement {
|
||||
// A changed source owns a fresh lifecycle and DOM image, including A → B → A.
|
||||
// This also prevents the browser retaining the previous decoded image while loading.
|
||||
return <MarkdownImageSource key={props.src} {...props} />
|
||||
})
|
||||
|
||||
function MarkdownImageSource({
|
||||
src,
|
||||
alt = '',
|
||||
urlList,
|
||||
@@ -29,35 +35,40 @@ export const MarkdownImage = memo(function MarkdownImage({
|
||||
preferHostResolution = false,
|
||||
onPreviewImage
|
||||
}: MarkdownImageProps): ReactElement {
|
||||
const [resolvedUrl, setResolvedUrl] = useState<string>()
|
||||
const [failed, setFailed] = useState(false)
|
||||
const request = useMemo(() => ({ src, imageUrlResolver, preferHostResolution, onResolve }),
|
||||
[src, imageUrlResolver, preferHostResolution, onResolve])
|
||||
const [resolution, setResolution] = useState<{
|
||||
request: typeof request
|
||||
url?: string
|
||||
failed?: boolean
|
||||
}>(() => ({ request }))
|
||||
const directUrl = isSafeImageUrl(src) && !(preferHostResolution && requiresHostImageResolution(src))
|
||||
? src : undefined
|
||||
// Reset this component before committing children, not in a passive effect:
|
||||
// an immediate image error must belong to the new request too.
|
||||
if (resolution.request !== request) setResolution({ request })
|
||||
// Never display a previous resolver's result, even before effect cleanup runs.
|
||||
const current = resolution.request === request ? resolution : undefined
|
||||
const resolvedUrl = directUrl ?? current?.url
|
||||
const failed = current?.failed === true
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
setFailed(false)
|
||||
if (directUrl) return
|
||||
|
||||
if (isSafeImageUrl(src) && !(preferHostResolution && requiresHostImageResolution(src))) {
|
||||
setResolvedUrl(src)
|
||||
return
|
||||
}
|
||||
|
||||
setResolvedUrl(undefined)
|
||||
setResolution({ request })
|
||||
resolveImageUrl(src, imageUrlResolver, preferHostResolution).then((resolved) => {
|
||||
if (cancelled) return
|
||||
if (!resolved) {
|
||||
setFailed(true)
|
||||
return
|
||||
}
|
||||
setResolvedUrl(resolved)
|
||||
if (resolved !== src) onResolve?.(src, resolved)
|
||||
setResolution({ request, url: resolved, failed: !resolved })
|
||||
if (resolved && resolved !== src) onResolve?.(src, resolved)
|
||||
}).catch(() => {
|
||||
if (!cancelled) setFailed(true)
|
||||
if (!cancelled) setResolution({ request, failed: true })
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [imageUrlResolver, onResolve, preferHostResolution, src])
|
||||
}, [directUrl, imageUrlResolver, onResolve, preferHostResolution, request, src])
|
||||
|
||||
if (!resolvedUrl || failed) {
|
||||
return (
|
||||
@@ -66,7 +77,7 @@ export const MarkdownImage = memo(function MarkdownImage({
|
||||
aria-busy={!failed}
|
||||
role="img"
|
||||
>
|
||||
{alt || src}
|
||||
{alt || (failed ? '图片加载失败' : '图片加载中…')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -83,9 +94,10 @@ export const MarkdownImage = memo(function MarkdownImage({
|
||||
className="cr-image-block markdown-content__image"
|
||||
src={resolvedUrl}
|
||||
alt={alt}
|
||||
onError={() => setFailed(true)}
|
||||
onError={() => setResolution((previous) => previous.request === request
|
||||
? { request, failed: true } : previous)}
|
||||
onClick={openPreview}
|
||||
draggable={false}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user