Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(image): fix image for not trigger native event the bug #2616

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/avatar/__tests__/avatar.test.tsx
Original file line number Diff line number Diff line change
@@ -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 组件测试', () => {
Expand Down Expand Up @@ -29,9 +29,11 @@ describe('Avatar 组件测试', () => {

test('Avatar onError 回调', async () => {
const mockOnErrorFn = vi.fn();
const wrapper = render(<Avatar image="http://error/" alt="test-avatar" onError={mockOnErrorFn}></Avatar>);
const image = wrapper.getByAltText('test-avatar');
fireEvent(image, new Event('error'));
expect(mockOnErrorFn).toHaveBeenCalledTimes(1);
const { container } = render(<Avatar image="http://error/" alt="test-avatar" onError={mockOnErrorFn}></Avatar>);
const imageDom = container.querySelector('img');
simulateImageEvent(imageDom, 'error');
await mockDelay(300);
expect(mockOnErrorFn).toHaveBeenCalled();
expect(mockOnErrorFn.mock.calls[0][0].e.type).toBe('error');
});
});
42 changes: 41 additions & 1 deletion src/image/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>) => void;
Expand Down Expand Up @@ -93,7 +102,11 @@ const InternalImage: React.ForwardRefRenderFunction<HTMLDivElement, ImageProps>
}, [lazy, imageRef]);

const [hasError, setHasError] = useState(false);

// 判断是否执行过onError事件,要不在CSR模式下会执行两次onError
const isFirstError = useRef(false);
const handleError = (e: SyntheticEvent<HTMLImageElement>) => {
isFirstError.current = true;
setHasError(true);
if (fallback) {
setImageSrc(fallback);
Expand All @@ -102,13 +115,39 @@ const InternalImage: React.ForwardRefRenderFunction<HTMLDivElement, ImageProps>
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) => {
Expand Down Expand Up @@ -148,6 +187,7 @@ const InternalImage: React.ForwardRefRenderFunction<HTMLDivElement, ImageProps>
const url = typeof imageSrc === 'string' ? imageSrc : previewUrl;
return (
<img
ref={imgRef}
src={url}
onError={handleError}
onLoad={handleLoad}
Expand Down
Loading