Skip to content

Commit f617d6b

Browse files
committed
refactor(avatar): refactor using composition api
1 parent 44cd93e commit f617d6b

File tree

3 files changed

+164
-165
lines changed

3 files changed

+164
-165
lines changed

components/avatar/Avatar.tsx

+157-144
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,183 @@
11
import { tuple, VueNode } from '../_util/type';
2-
import { CSSProperties, defineComponent, inject, nextTick, PropType } from 'vue';
2+
import {
3+
CSSProperties,
4+
defineComponent,
5+
inject,
6+
nextTick,
7+
onMounted,
8+
onUpdated,
9+
PropType,
10+
ref,
11+
watch,
12+
} from 'vue';
313
import { defaultConfigProvider } from '../config-provider';
4-
import { getComponent } from '../_util/props-util';
14+
import { getPropsSlot } from '../_util/props-util';
515
import PropTypes from '../_util/vue-types';
616

7-
export default defineComponent({
17+
export interface AvatarProps {
18+
prefixCls?: string;
19+
shape?: 'circle' | 'square';
20+
size?: 'large' | 'small' | 'default' | number;
21+
src?: string;
22+
srcset?: string;
23+
icon?: typeof PropTypes.VNodeChild;
24+
alt?: string;
25+
loadError?: () => boolean;
26+
}
27+
28+
const Avatar = defineComponent<AvatarProps>({
829
name: 'AAvatar',
9-
props: {
10-
prefixCls: PropTypes.string,
11-
shape: PropTypes.oneOf(tuple('circle', 'square')),
12-
size: {
13-
type: [Number, String] as PropType<'large' | 'small' | 'default' | number>,
14-
default: 'default',
15-
},
16-
src: PropTypes.string,
17-
/** Srcset of image avatar */
18-
srcset: PropTypes.string,
19-
/** @deprecated please use `srcset` instead `srcSet` */
20-
srcSet: PropTypes.string,
21-
icon: PropTypes.VNodeChild,
22-
alt: PropTypes.string,
23-
loadError: {
24-
type: Function as PropType<() => boolean>,
25-
},
26-
},
27-
setup() {
28-
return {
29-
configProvider: inject('configProvider', defaultConfigProvider),
30-
};
31-
},
32-
data() {
33-
return {
34-
isImgExist: true,
35-
isMounted: false,
36-
scale: 1,
37-
lastChildrenWidth: undefined,
38-
lastNodeWidth: undefined,
39-
};
40-
},
41-
watch: {
42-
src() {
43-
nextTick(() => {
44-
this.isImgExist = true;
45-
this.scale = 1;
46-
// force uodate for position
47-
this.$forceUpdate();
48-
});
49-
},
50-
},
51-
mounted() {
52-
nextTick(() => {
53-
this.setScale();
54-
this.isMounted = true;
55-
});
56-
},
57-
updated() {
58-
nextTick(() => {
59-
this.setScale();
60-
});
61-
},
62-
methods: {
63-
setScale() {
64-
if (!this.$refs.avatarChildren || !this.$refs.avatarNode) {
30+
setup(props, { slots }) {
31+
const isImgExist = ref(true);
32+
const isMounted = ref(false);
33+
const scale = ref(1);
34+
const lastChildrenWidth = ref<number>(undefined);
35+
const lastNodeWidth = ref<number>(undefined);
36+
37+
const avatarChildrenRef = ref<HTMLElement>(null);
38+
const avatarNodeRef = ref<HTMLElement>(null);
39+
40+
const configProvider = inject('configProvider', defaultConfigProvider);
41+
42+
const setScale = () => {
43+
if (!avatarChildrenRef.value || !avatarNodeRef.value) {
6544
return;
6645
}
67-
const childrenWidth = (this.$refs.avatarChildren as HTMLElement).offsetWidth; // offsetWidth avoid affecting be transform scale
68-
const nodeWidth = (this.$refs.avatarNode as HTMLElement).offsetWidth;
46+
const childrenWidth = avatarChildrenRef.value.offsetWidth; // offsetWidth avoid affecting be transform scale
47+
const nodeWidth = avatarNodeRef.value.offsetWidth;
6948
// denominator is 0 is no meaning
7049
if (
7150
childrenWidth === 0 ||
7251
nodeWidth === 0 ||
73-
(this.lastChildrenWidth === childrenWidth && this.lastNodeWidth === nodeWidth)
52+
(lastChildrenWidth.value === childrenWidth && lastNodeWidth.value === nodeWidth)
7453
) {
7554
return;
7655
}
77-
this.lastChildrenWidth = childrenWidth;
78-
this.lastNodeWidth = nodeWidth;
56+
lastChildrenWidth.value = childrenWidth;
57+
lastNodeWidth.value = nodeWidth;
7958
// add 4px gap for each side to get better performance
80-
this.scale = nodeWidth - 8 < childrenWidth ? (nodeWidth - 8) / childrenWidth : 1;
81-
},
82-
handleImgLoadError() {
83-
const { loadError } = this.$props;
84-
const errorFlag = loadError ? loadError() : undefined;
59+
scale.value = nodeWidth - 8 < childrenWidth ? (nodeWidth - 8) / childrenWidth : 1;
60+
};
61+
62+
const handleImgLoadError = () => {
63+
const { loadError } = props;
64+
const errorFlag = loadError?.();
8565
if (errorFlag !== false) {
86-
this.isImgExist = false;
66+
isImgExist.value = false;
8767
}
88-
},
89-
},
90-
render() {
91-
const { prefixCls: customizePrefixCls, shape, size, src, alt, srcset, srcSet } = this.$props;
92-
const icon = getComponent(this, 'icon');
93-
const getPrefixCls = this.configProvider.getPrefixCls;
94-
const prefixCls = getPrefixCls('avatar', customizePrefixCls);
68+
};
9569

96-
const { isImgExist, scale, isMounted } = this.$data;
70+
watch(
71+
() => props.src,
72+
() => {
73+
nextTick(() => {
74+
isImgExist.value = true;
75+
scale.value = 1;
76+
// this.$forceUpdate();
77+
});
78+
},
79+
);
9780

98-
const sizeCls = {
99-
[`${prefixCls}-lg`]: size === 'large',
100-
[`${prefixCls}-sm`]: size === 'small',
101-
};
81+
onMounted(() => {
82+
nextTick(() => {
83+
setScale();
84+
isMounted.value = true;
85+
});
86+
});
10287

103-
const classString = {
104-
[prefixCls]: true,
105-
...sizeCls,
106-
[`${prefixCls}-${shape}`]: shape,
107-
[`${prefixCls}-image`]: src && isImgExist,
108-
[`${prefixCls}-icon`]: icon,
109-
};
88+
onUpdated(() => {
89+
nextTick(() => {
90+
setScale();
91+
});
92+
});
11093

111-
const sizeStyle: CSSProperties =
112-
typeof size === 'number'
113-
? {
114-
width: `${size}px`,
115-
height: `${size}px`,
116-
lineHeight: `${size}px`,
117-
fontSize: icon ? `${size / 2}px` : '18px',
118-
}
119-
: {};
120-
121-
let children: VueNode = this.$slots.default?.();
122-
if (src && isImgExist) {
123-
children = (
124-
<img src={src} srcset={srcset || srcSet} onError={this.handleImgLoadError} alt={alt} />
125-
);
126-
} else if (icon) {
127-
children = icon;
128-
} else {
129-
const childrenNode = this.$refs.avatarChildren;
130-
if (childrenNode || scale !== 1) {
131-
const transformString = `scale(${scale}) translateX(-50%)`;
132-
const childrenStyle: CSSProperties = {
133-
msTransform: transformString,
134-
WebkitTransform: transformString,
135-
transform: transformString,
136-
};
137-
const sizeChildrenStyle =
138-
typeof size === 'number'
139-
? {
140-
lineHeight: `${size}px`,
141-
}
142-
: {};
143-
children = (
144-
<span
145-
class={`${prefixCls}-string`}
146-
ref="avatarChildren"
147-
style={{ ...sizeChildrenStyle, ...childrenStyle }}
148-
>
149-
{children}
150-
</span>
151-
);
94+
return () => {
95+
const { prefixCls: customizePrefixCls, shape, size, src, alt, srcset } = props;
96+
const icon = getPropsSlot(slots, props, 'icon');
97+
const getPrefixCls = configProvider.getPrefixCls;
98+
const prefixCls = getPrefixCls('avatar', customizePrefixCls);
99+
100+
const classString = {
101+
[prefixCls]: true,
102+
[`${prefixCls}-lg`]: size === 'large',
103+
[`${prefixCls}-sm`]: size === 'small',
104+
[`${prefixCls}-${shape}`]: shape,
105+
[`${prefixCls}-image`]: src && isImgExist.value,
106+
[`${prefixCls}-icon`]: icon,
107+
};
108+
109+
const sizeStyle: CSSProperties =
110+
typeof size === 'number'
111+
? {
112+
width: `${size}px`,
113+
height: `${size}px`,
114+
lineHeight: `${size}px`,
115+
fontSize: icon ? `${size / 2}px` : '18px',
116+
}
117+
: {};
118+
119+
let children: VueNode = slots.default?.();
120+
if (src && isImgExist.value) {
121+
children = <img src={src} srcset={srcset} onError={handleImgLoadError} alt={alt} />;
122+
} else if (icon) {
123+
children = icon;
152124
} else {
153-
const childrenStyle: CSSProperties = {};
154-
if (!isMounted) {
155-
childrenStyle.opacity = 0;
125+
const childrenNode = avatarChildrenRef.value;
126+
127+
if (childrenNode || scale.value !== 1) {
128+
const transformString = `scale(${scale.value}) translateX(-50%)`;
129+
const childrenStyle: CSSProperties = {
130+
msTransform: transformString,
131+
WebkitTransform: transformString,
132+
transform: transformString,
133+
};
134+
const sizeChildrenStyle =
135+
typeof size === 'number'
136+
? {
137+
lineHeight: `${size}px`,
138+
}
139+
: {};
140+
children = (
141+
<span
142+
class={`${prefixCls}-string`}
143+
ref={avatarChildrenRef}
144+
style={{ ...sizeChildrenStyle, ...childrenStyle }}
145+
>
146+
{children}
147+
</span>
148+
);
149+
} else {
150+
children = (
151+
<span class={`${prefixCls}-string`} ref={avatarChildrenRef} style={{ opacity: 0 }}>
152+
{children}
153+
</span>
154+
);
156155
}
157-
children = (
158-
<span class={`${prefixCls}-string`} ref="avatarChildren" style={{ opacity: 0 }}>
159-
{children}
160-
</span>
161-
);
162156
}
163-
}
164-
return (
165-
<span ref="avatarNode" class={classString} style={sizeStyle}>
166-
{children}
167-
</span>
168-
);
157+
return (
158+
<span ref={avatarNodeRef} class={classString} style={sizeStyle}>
159+
{children}
160+
</span>
161+
);
162+
};
169163
},
170164
});
165+
166+
Avatar.props = {
167+
prefixCls: PropTypes.string,
168+
shape: PropTypes.oneOf(tuple('circle', 'square')),
169+
size: {
170+
type: [Number, String] as PropType<'large' | 'small' | 'default' | number>,
171+
default: 'default',
172+
},
173+
src: PropTypes.string,
174+
/** Srcset of image avatar */
175+
srcset: PropTypes.string,
176+
icon: PropTypes.VNodeChild,
177+
alt: PropTypes.string,
178+
loadError: {
179+
type: Function as PropType<() => boolean>,
180+
},
181+
};
182+
183+
export default Avatar;

0 commit comments

Comments
 (0)