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 (