Ant-design 源码分析之数据展示(十)Image
2021SC@SDUSC
一、组件结构
1、ant代码结构
2、组件结构
ant中Image的index.tsx中引入了PreviewGroup。
二、antd组件调用关系
1、index.tsx
导入相应模块以及相应的ICON图标
import * as React from 'react';
import { useContext } from 'react';
import EyeOutlined from '@ant-design/icons/EyeOutlined';
import RcImage, { ImageProps } from 'rc-image';
import defaultLocale from '../locale/en_US';
import PreviewGroup, { icons } from './PreviewGroup';
import { ConfigContext } from '../config-provider';
import { getTransitionName } from '../_util/motion';
声明CompositionImage接口
export interface CompositionImage<P> extends React.FC<P> {
PreviewGroup: typeof PreviewGroup;
}
alt:图像描述,类型为string
fallback:加载失败容错地址,类型为string
height:图像高度,类型为string | number
placeholder:加载占位, 为 true 时使用默认占位,类型为ReactNode
preview:预览参数,为 false 时禁用,类型为boolean | previewType
src:图片地址,类型为string
width:图像宽度,类型为string | number
onError:加载错误回调,类型为(event: Event) => void
const Image: CompositionImage<ImageProps> = ({
prefixCls: customizePrefixCls,
preview,
...otherProps
}) => {
const { getPrefixCls } = useContext(ConfigContext);
const prefixCls = getPrefixCls('image', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const { locale: contextLocale = defaultLocale } = useContext(ConfigContext);
const imageLocale = contextLocale.Image || defaultLocale.Image;
const mergedPreview = React.useMemo(() => {
if (preview === false) {
return preview;
}
const _preview = typeof preview === 'object' ? preview : {};
return {
mask: (
<div className={`${prefixCls}-mask-info`}>
<EyeOutlined />
{imageLocale?.preview}
</div>
),
icons,
..._preview,
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
};
}, [preview, imageLocale]);
return <RcImage prefixCls={prefixCls} preview={mergedPreview} {...otherProps} />;
};
export { ImageProps };
Image.PreviewGroup = PreviewGroup;
export default Image;
2、PreviewGroup.tsx
导入相应模块以及相应的ICON图标
import * as React from 'react';
import RcImage from 'rc-image';
import RotateLeftOutlined from '@ant-design/icons/RotateLeftOutlined';
import RotateRightOutlined from '@ant-design/icons/RotateRightOutlined';
import ZoomInOutlined from '@ant-design/icons/ZoomInOutlined';
import ZoomOutOutlined from '@ant-design/icons/ZoomOutOutlined';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import { GroupConsumerProps } from 'rc-image/lib/PreviewGroup';
import { ConfigContext } from '../config-provider';
import { getTransitionName } from '../_util/motion';
声明接口
export const icons = {
rotateLeft: <RotateLeftOutlined />,
rotateRight: <RotateRightOutlined />,
zoomIn: <ZoomInOutlined />,
zoomOut: <ZoomOutOutlined />,
close: <CloseOutlined />,
left: <LeftOutlined />,
right: <RightOutlined />,
};
const InternalPreviewGroup: React.FC<GroupConsumerProps> = ({
previewPrefixCls: customizePrefixCls,
preview,
...props
}) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('image-preview', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
//预览参数为false时禁用
const mergedPreview = React.useMemo(() => {
if (preview === false) {
return preview;
}
const _preview = typeof preview === 'object' ? preview : {};
return {
..._preview,
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
};
}, [preview]);
return (
<RcImage.PreviewGroup
preview={mergedPreview}
previewPrefixCls={prefixCls}
icons={icons}
{...props}
/>
);
};
export default InternalPreviewGroup;
3、Rc-Image:Image.tsx
导入相应模块以及相应的ICON图标
import * as React from 'react';
import { useState } from 'react';
import cn from 'classnames';
import { getOffset } from 'rc-util/lib/Dom/css';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import { GetContainer } from 'rc-util/lib/PortalWrapper';
import Preview, { PreviewProps } from './Preview';
import PreviewGroup, { context } from './PreviewGroup';
import { IDialogPropTypes } from 'rc-dialog/lib/IDialogPropTypes';
声明ImagePreviewType接口
export interface ImagePreviewType
extends Omit<
IDialogPropTypes,
'mask' | 'visible' | 'closable' | 'prefixCls' | 'onClose' | 'afterClose' | 'wrapClassName'
> {
src?: string;
visible?: boolean;
onVisibleChange?: (value: boolean, prevValue: boolean) => void;
getContainer?: GetContainer | false;
mask?: React.ReactNode;
maskClassName?: string;
icons?: PreviewProps['icons'];
}
visible?: boolean;
onVisibleChange?: (visible, prevVisible) => void;
getContainer?:string | HTMLElement | (() => HTMLElement)
src:图片地址,类型为string
mask:,类型为ReactNode
maskClassName:string
current:number
let uuid = 0;
export interface ImageProps
extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'placeholder' | 'onClick'> {
// Original
src?: string;
wrapperClassName?: string;
wrapperStyle?: React.CSSProperties;
prefixCls?: string;
previewPrefixCls?: string;
placeholder?: React.ReactNode;
fallback?: string;
preview?: boolean | ImagePreviewType;
/**
* @deprecated since version 3.2.1
*/
onPreviewClose?: (value: boolean, prevValue: boolean) => void;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
}
interface CompoundedComponent<P> extends React.FC<P> {
PreviewGroup: typeof PreviewGroup;
}
图片状态
type ImageStatus = 'normal' | 'error' | 'loading';
const ImageInternal: CompoundedComponent<ImageProps> = ({
src: imgSrc,
alt,
onPreviewClose: onInitialPreviewClose,
prefixCls = 'rc-image',
previewPrefixCls = `${prefixCls}-preview`,
placeholder,
fallback,
width,
height,
style,
preview = true,
className,
onClick,
onError: onImageError,
wrapperClassName,
wrapperStyle,
// Img
crossOrigin,
decoding,
loading,
referrerPolicy,
sizes,
srcSet,
useMap,
...otherProps
}) => {
//加载占位为true是默认占位
const isCustomPlaceholder = placeholder && placeholder !== true;
const {
src: previewSrc,
visible: previewVisible = undefined,
onVisibleChange: onPreviewVisibleChange = onInitialPreviewClose,
getContainer: getPreviewContainer = undefined,
mask: previewMask,
maskClassName,
icons,
...dialogProps
}: ImagePreviewType = typeof preview === 'object' ? preview : {};
const src = previewSrc ?? imgSrc;
const isControlled = previewVisible !== undefined;
const [isShowPreview, setShowPreview] = useMergedState(!!previewVisible, {
value: previewVisible,
onChange: onPreviewVisibleChange,
});
const [status, setStatus] = useState<ImageStatus>(isCustomPlaceholder ? 'loading' : 'normal');
//图片状态
const [mousePosition, setMousePosition] = useState<null | { x: number; y: number }>(null);
const isError = status === 'error';
const {
isPreviewGroup,
setCurrent,
setShowPreview: setGroupShowPreview,
setMousePosition: setGroupMousePosition,
registerImage,
} = React.useContext(context);
const [currentId] = React.useState<number>(() => {
uuid += 1;
return uuid;
});
const canPreview = preview && !isError;
const isLoaded = React.useRef(false);
const onLoad = () => {
setStatus('normal');
};
const onError = (e: React.SyntheticEvent<HTMLImageElement, Event>) => {
if (onImageError) {
onImageError(e);
}
setStatus('error');
};
const onPreview: React.MouseEventHandler<HTMLDivElement> = e => {
if (!isControlled) {
const { left, top } = getOffset(e.target);
if (isPreviewGroup) {
setCurrent(currentId);
setGroupMousePosition({
x: left,
y: top,
});
} else {
setMousePosition({
x: left,
y: top,
});
}
}
if (isPreviewGroup) {
setGroupShowPreview(true);
} else {
setShowPreview(true);
}
if (onClick) onClick(e);
};
const onPreviewClose = (e: React.SyntheticEvent<Element>) => {
e.stopPropagation();
setShowPreview(false);
if (!isControlled) {
setMousePosition(null);
}
};
const getImgRef = (img?: HTMLImageElement) => {
isLoaded.current = false;
if (status !== 'loading') return;
if (img?.complete && (img.naturalWidth || img.naturalHeight)) {
isLoaded.current = true;
onLoad();
}
};
// Keep order start
// Resolve https://github.com/ant-design/ant-design/issues/28881
// Only need unRegister when component unMount
React.useEffect(() => {
const unRegister = registerImage(currentId, src);
return unRegister;
}, []);
React.useEffect(() => {
registerImage(currentId, src, canPreview);
}, [src, canPreview]);
// Keep order end
React.useEffect(() => {
if (isError) {
setStatus('normal');
}
if (isCustomPlaceholder && !isLoaded.current) {
setStatus('loading');
}
}, [imgSrc]);
const wrapperClass = cn(prefixCls, wrapperClassName, {
[`${prefixCls}-error`]: isError,
});
//fallback
const mergedSrc = isError && fallback ? fallback : src;
const imgCommonProps = {
crossOrigin,
decoding,
loading,
referrerPolicy,
sizes,
srcSet,
useMap,
alt,
className: cn(
`${prefixCls}-img`,
{
[`${prefixCls}-img-placeholder`]: placeholder === true,
},
className,
),
style: {
height,
...style,
},
};
return (
<>
<div
{...otherProps}
className={wrapperClass}
onClick={canPreview ? onPreview : onClick}
style={{
width,
height,
...wrapperStyle,
}}
>
<img
{...imgCommonProps}
ref={getImgRef}
{...(isError && fallback
? {
src: fallback,
}
: { onLoad, onError, src: imgSrc })}
/>
{status === 'loading' && (
<div aria-hidden="true" className={`${prefixCls}-placeholder`}>
{placeholder}
</div>
)}
{/* Preview Click Mask */}
{previewMask && canPreview && (
<div className={cn(`${prefixCls}-mask`, maskClassName)}>{previewMask}</div>
)}
</div>
{!isPreviewGroup && canPreview && (
<Preview
aria-hidden={!isShowPreview}
visible={isShowPreview}
prefixCls={previewPrefixCls}
onClose={onPreviewClose}
mousePosition={mousePosition}
src={mergedSrc}
alt={alt}
getContainer={getPreviewContainer}
icons={icons}
{...dialogProps}
/>
)}
</>
);
};
ImageInternal.PreviewGroup = PreviewGroup;
ImageInternal.displayName = 'Image';
export default ImageInternal;