From f189c37a1a29d1bba26e046a6c5804e181aec89b Mon Sep 17 00:00:00 2001 From: Haixing <65376724+HaixingOoO@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:42:58 +0800 Subject: [PATCH] fix(image): fix image for not trigger native event the bug (#2616) --- src/avatar/__tests__/avatar.test.tsx | 12 ++++---- src/image/Image.tsx | 42 +++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/avatar/__tests__/avatar.test.tsx b/src/avatar/__tests__/avatar.test.tsx index 89349eb7e0..a4c784f036 100644 --- a/src/avatar/__tests__/avatar.test.tsx +++ b/src/avatar/__tests__/avatar.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { vi, render, fireEvent } from '@test/utils'; +import { vi, render, simulateImageEvent, mockDelay } from '@test/utils'; import Avatar from '../Avatar'; describe('Avatar 组件测试', () => { @@ -29,9 +29,11 @@ describe('Avatar 组件测试', () => { test('Avatar onError 回调', async () => { const mockOnErrorFn = vi.fn(); - const wrapper = render(); - const image = wrapper.getByAltText('test-avatar'); - fireEvent(image, new Event('error')); - expect(mockOnErrorFn).toHaveBeenCalledTimes(1); + const { container } = render(); + const imageDom = container.querySelector('img'); + simulateImageEvent(imageDom, 'error'); + await mockDelay(300); + expect(mockOnErrorFn).toHaveBeenCalled(); + expect(mockOnErrorFn.mock.calls[0][0].e.type).toBe('error'); }); }); diff --git a/src/image/Image.tsx b/src/image/Image.tsx index 754d68b1e9..f561ac7db8 100644 --- a/src/image/Image.tsx +++ b/src/image/Image.tsx @@ -13,6 +13,15 @@ import { StyledProps } from '../common'; import useDefaultProps from '../hooks/useDefaultProps'; import useImagePreviewUrl from '../hooks/useImagePreviewUrl'; +export function isImageValid(src: string) { + return new Promise((resolve) => { + const img = document.createElement('img'); + img.onerror = () => resolve(false); + img.onload = () => resolve(true); + img.src = src; + }); +} + export type ImageProps = TdImageProps & StyledProps & { onClick?: (e: MouseEvent) => void; @@ -93,7 +102,11 @@ const InternalImage: React.ForwardRefRenderFunction }, [lazy, imageRef]); const [hasError, setHasError] = useState(false); + + // 判断是否执行过onError事件,要不在CSR模式下会执行两次onError + const isFirstError = useRef(false); const handleError = (e: SyntheticEvent) => { + isFirstError.current = true; setHasError(true); if (fallback) { setImageSrc(fallback); @@ -102,13 +115,39 @@ const InternalImage: React.ForwardRefRenderFunction onError?.({ e }); }; + const imgRef = useRef(); useEffect(() => { if (hasError && previewUrl) { setHasError(false); } - // eslint-disable-next-line + // 在SSR在判断执行onError + previewUrl && + isImageValid(previewUrl as string).then((isValid) => { + // SSR模式下会执行,CSR模式下不会执行 + // 这里添加setTimeout是因为CSR image渲染时,onError有时快有时慢,会导致执行顺序不同导致的bug + setTimeout(() => { + if (!isValid && !isFirstError.current) { + // SSR模式下获取不到imaage的合成事件,暂时传递image实例 + handleError(imgRef.current); + } + }, 0); + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [previewUrl]); + useEffect(() => { + // SSR下执行 + if (imgRef.current) { + const { complete, naturalWidth, naturalHeight } = imgRef.current; + if (complete && naturalWidth !== 0 && naturalHeight !== 0) { + // SSR模式下获取不到imaage的合成事件,暂时传递image实例 + handleLoad(imgRef.current); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const hasMouseEvent = overlayTrigger === 'hover'; const [shouldShowOverlay, setShouldShowOverlay] = useState(!hasMouseEvent); const handleToggleOverlay = (overlay: boolean) => { @@ -148,6 +187,7 @@ const InternalImage: React.ForwardRefRenderFunction const url = typeof imageSrc === 'string' ? imageSrc : previewUrl; return (