diff --git a/src/Image.tsx b/src/Image.tsx index e557653..a7ab2e9 100644 --- a/src/Image.tsx +++ b/src/Image.tsx @@ -1,7 +1,7 @@ import useMergedState from '@rc-component/util/lib/hooks/useMergedState'; import classnames from 'classnames'; import * as React from 'react'; -import { useContext, useMemo, useState } from 'react'; +import { useContext, useMemo, useState, forwardRef, useImperativeHandle, useRef } from 'react'; import type { InternalPreviewConfig, InternalPreviewSemanticName, @@ -71,11 +71,16 @@ export interface ImageProps onError?: (e: React.SyntheticEvent) => void; } -interface CompoundedComponent

extends React.FC

{ +// 定义 ImageRef 接口,只包含 nativeElement 属性 +export interface ImageRef { + nativeElement: HTMLImageElement | null; +} + +interface CompoundedComponent

extends React.ForwardRefExoticComponent

> { PreviewGroup: typeof PreviewGroup; } -const ImageInternal: CompoundedComponent = props => { +const ImageInternal = forwardRef((props, ref) => { const { // Misc prefixCls = 'rc-image', @@ -107,6 +112,13 @@ const ImageInternal: CompoundedComponent = props => { ...otherProps } = props; + // 创建内部引用来跟踪 image 元素 + const imageElementRef = useRef(null); + + // 使用 useImperativeHandle 暴露自定义 ref 对象 + useImperativeHandle(ref, () => ({ + nativeElement: imageElementRef.current, + })); const groupContext = useContext(PreviewGroupContext); // ========================== Preview =========================== @@ -193,6 +205,16 @@ const ImageInternal: CompoundedComponent = props => { onClick?.(e); }; + // ========================== Image Ref ========================== + const handleRef = (img: HTMLImageElement | null) => { + if (!img) { + return; + } + // 保存到内部引用 + imageElementRef.current = img; + getImgRef(img); + }; + // =========================== Render =========================== return ( <> @@ -223,7 +245,7 @@ const ImageInternal: CompoundedComponent = props => { ...styles.image, ...style, }} - ref={getImgRef} + ref={handleRef} {...srcAndOnload} width={width} height={height} @@ -269,12 +291,11 @@ const ImageInternal: CompoundedComponent = props => { )} ); -}; - +}) as CompoundedComponent; ImageInternal.PreviewGroup = PreviewGroup; if (process.env.NODE_ENV !== 'production') { ImageInternal.displayName = 'Image'; } -export default ImageInternal; +export default ImageInternal; \ No newline at end of file diff --git a/tests/ref.test.tsx b/tests/ref.test.tsx new file mode 100644 index 0000000..a21adc8 --- /dev/null +++ b/tests/ref.test.tsx @@ -0,0 +1,90 @@ +import { act, fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import Image, { ImageRef } from '../src'; + +describe('Image ref forwarding', () => { + // 测试对象类型的 ref + it('should provide access to internal img element via nativeElement', () => { + const ref = React.createRef(); + const { container } = render( + test image, + ); + + // 确保 ref.current.nativeElement 指向正确的 img 元素 + expect(ref.current).not.toBeNull(); + expect(ref.current?.nativeElement).toBe(container.querySelector('.rc-image-img')); + expect(ref.current?.nativeElement?.tagName).toBe('IMG'); + expect(ref.current?.nativeElement?.alt).toBe('test image'); + }); + + // 测试回调类型的 ref + it('should work with callback ref', () => { + let imgRef: ImageRef | null = null; + const callbackRef = (el: ImageRef | null) => { + imgRef = el; + }; + + const { container } = render( + , + ); + + // 确保回调 ref 被调用,且 nativeElement 指向正确的 img 元素 + expect(imgRef).not.toBeNull(); + expect(imgRef?.nativeElement).toBe(container.querySelector('.rc-image-img')); + }); + + // 测试通过 nativeElement 访问 img 元素的属性和方法 + it('should allow access to img element properties and methods via nativeElement', () => { + const ref = React.createRef(); + render( + , + ); + + // 确保可以通过 ref.nativeElement 访问 img 元素的属性 + expect(ref.current?.nativeElement?.width).toBe(200); + expect(ref.current?.nativeElement?.height).toBe(100); + + // 可以测试调用 img 元素的方法 + // 注意:某些方法可能在 jsdom 环境中不可用,根据实际情况调整 + }); + + // 测试 ref.nativeElement 在组件重新渲染时保持稳定 + it('should maintain stable nativeElement reference across re-renders', () => { + const ref = React.createRef(); + const { rerender } = render( + , + ); + + const initialImgElement = ref.current?.nativeElement; + expect(initialImgElement).not.toBeNull(); + + // 重新渲染组件,但保持 ref 不变 + rerender( + updated alt, + ); + + // 确保 ref.nativeElement 引用的还是同一个 img 元素 + expect(ref.current?.nativeElement).toBe(initialImgElement); + expect(ref.current?.nativeElement?.alt).toBe('updated alt'); + }); + +});