Skip to content

Commit ea2d8e2

Browse files
authored
refactor(affix): use Composition api (#3447)
* refactor(affix): use Composition api * fix: affix code style
1 parent 1b68a46 commit ea2d8e2

File tree

2 files changed

+126
-128
lines changed

2 files changed

+126
-128
lines changed

components/affix/index.tsx

+124-127
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import { CSSProperties, defineComponent, inject } from 'vue';
1+
import {
2+
CSSProperties,
3+
defineComponent,
4+
inject,
5+
ref,
6+
reactive,
7+
watch,
8+
onMounted,
9+
getCurrentInstance,
10+
onUnmounted,
11+
} from 'vue';
212
import PropTypes from '../_util/vue-types';
313
import classNames from '../_util/classNames';
414
import omit from 'omit.js';
515
import ResizeObserver from '../vc-resize-observer';
6-
import BaseMixin from '../_util/BaseMixin';
16+
// import BaseMixin from '../_util/BaseMixin';
717
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
818
import { defaultConfigProvider } from '../config-provider';
919
import warning from '../_util/warning';
@@ -50,81 +60,29 @@ const AffixProps = {
5060
};
5161
const Affix = defineComponent({
5262
name: 'AAffix',
53-
mixins: [BaseMixin],
5463
props: AffixProps,
5564
emits: ['change', 'testUpdatePosition'],
56-
setup() {
57-
return {
58-
configProvider: inject('configProvider', defaultConfigProvider),
59-
};
60-
},
61-
data() {
62-
return {
65+
setup(props, { slots, emit, expose }) {
66+
const configProvider = inject('configProvider', defaultConfigProvider);
67+
const placeholderNode = ref();
68+
const fixedNode = ref();
69+
const state = reactive({
6370
affixStyle: undefined,
6471
placeholderStyle: undefined,
6572
status: AffixStatus.None,
6673
lastAffix: false,
6774
prevTarget: null,
6875
timeout: null,
69-
};
70-
},
71-
watch: {
72-
target(val) {
73-
let newTarget = null;
74-
if (val) {
75-
newTarget = val() || null;
76-
}
77-
if (this.prevTarget !== newTarget) {
78-
removeObserveTarget(this);
79-
if (newTarget) {
80-
addObserveTarget(newTarget, this);
81-
// Mock Event object.
82-
this.updatePosition();
83-
}
84-
this.prevTarget = newTarget;
85-
}
86-
},
87-
offsetTop() {
88-
this.updatePosition();
89-
},
90-
offsetBottom() {
91-
this.updatePosition();
92-
},
93-
},
94-
beforeMount() {
95-
this.updatePosition = throttleByAnimationFrame(this.updatePosition);
96-
this.lazyUpdatePosition = throttleByAnimationFrame(this.lazyUpdatePosition);
97-
},
98-
mounted() {
99-
const { target } = this;
100-
if (target) {
101-
// [Legacy] Wait for parent component ref has its value.
102-
// We should use target as directly element instead of function which makes element check hard.
103-
this.timeout = setTimeout(() => {
104-
addObserveTarget(target(), this);
105-
// Mock Event object.
106-
this.updatePosition();
107-
});
108-
}
109-
},
110-
updated() {
111-
this.measure();
112-
},
113-
beforeUnmount() {
114-
clearTimeout(this.timeout);
115-
removeObserveTarget(this);
116-
(this.updatePosition as any).cancel();
117-
// https://github.com/ant-design/ant-design/issues/22683
118-
(this.lazyUpdatePosition as any).cancel();
119-
},
120-
methods: {
121-
getOffsetTop() {
122-
const { offset, offsetBottom } = this;
123-
let { offsetTop } = this;
124-
if (typeof offsetTop === 'undefined') {
76+
});
77+
const currentInstance = getCurrentInstance();
78+
79+
const getOffsetTop = () => {
80+
const { offset, offsetBottom } = props;
81+
let { offsetTop } = props;
82+
if (offsetTop === undefined) {
12583
offsetTop = offset;
12684
warning(
127-
typeof offset === 'undefined',
85+
offset === undefined,
12886
'Affix',
12987
'`offset` is deprecated. Please use `offsetTop` instead.',
13088
);
@@ -134,26 +92,19 @@ const Affix = defineComponent({
13492
offsetTop = 0;
13593
}
13694
return offsetTop;
137-
},
138-
139-
getOffsetBottom() {
140-
return this.offsetBottom;
141-
},
142-
// =================== Measure ===================
143-
measure() {
144-
const { status, lastAffix } = this;
145-
const { target } = this;
146-
if (
147-
status !== AffixStatus.Prepare ||
148-
!this.$refs.fixedNode ||
149-
!this.$refs.placeholderNode ||
150-
!target
151-
) {
95+
};
96+
const getOffsetBottom = () => {
97+
return props.offsetBottom;
98+
};
99+
const measure = () => {
100+
const { status, lastAffix } = state;
101+
const { target } = props;
102+
if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
152103
return;
153104
}
154105

155-
const offsetTop = this.getOffsetTop();
156-
const offsetBottom = this.getOffsetBottom();
106+
const offsetTop = getOffsetTop();
107+
const offsetBottom = getOffsetBottom();
157108

158109
const targetNode = target();
159110
if (!targetNode) {
@@ -164,7 +115,7 @@ const Affix = defineComponent({
164115
status: AffixStatus.None,
165116
} as AffixState;
166117
const targetRect = getTargetRect(targetNode);
167-
const placeholderReact = getTargetRect(this.$refs.placeholderNode as HTMLElement);
118+
const placeholderReact = getTargetRect(placeholderNode.value as HTMLElement);
168119
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
169120
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
170121
if (fixedTop !== undefined) {
@@ -193,41 +144,39 @@ const Affix = defineComponent({
193144

194145
newState.lastAffix = !!newState.affixStyle;
195146
if (lastAffix !== newState.lastAffix) {
196-
this.$emit('change', newState.lastAffix);
147+
emit('change', newState.lastAffix);
197148
}
198-
199-
this.setState(newState);
200-
},
201-
202-
prepareMeasure() {
203-
this.setState({
149+
// update state
150+
Object.assign(state, newState);
151+
};
152+
const prepareMeasure = () => {
153+
Object.assign(state, {
204154
status: AffixStatus.Prepare,
205155
affixStyle: undefined,
206156
placeholderStyle: undefined,
207157
});
208-
this.$forceUpdate();
209-
210158
// Test if `updatePosition` called
211159
if (process.env.NODE_ENV === 'test') {
212-
this.$emit('testUpdatePosition');
160+
emit('testUpdatePosition');
213161
}
214-
},
215-
updatePosition() {
216-
this.prepareMeasure();
217-
},
218-
lazyUpdatePosition() {
219-
const { target } = this;
220-
const { affixStyle } = this;
162+
};
163+
164+
const updatePosition = throttleByAnimationFrame(() => {
165+
prepareMeasure();
166+
});
167+
const lazyUpdatePosition = throttleByAnimationFrame(() => {
168+
const { target } = props;
169+
const { affixStyle } = state;
221170

222171
// Check position change before measure to make Safari smooth
223172
if (target && affixStyle) {
224-
const offsetTop = this.getOffsetTop();
225-
const offsetBottom = this.getOffsetBottom();
173+
const offsetTop = getOffsetTop();
174+
const offsetBottom = getOffsetBottom();
226175

227176
const targetNode = target();
228-
if (targetNode && this.$refs.placeholderNode) {
177+
if (targetNode && placeholderNode.value) {
229178
const targetRect = getTargetRect(targetNode);
230-
const placeholderReact = getTargetRect(this.$refs.placeholderNode as HTMLElement);
179+
const placeholderReact = getTargetRect(placeholderNode.value as HTMLElement);
231180
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
232181
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
233182

@@ -240,30 +189,78 @@ const Affix = defineComponent({
240189
}
241190
}
242191
// Directly call prepare measure since it's already throttled.
243-
this.prepareMeasure();
244-
},
245-
},
192+
prepareMeasure();
193+
});
246194

247-
render() {
248-
const { prefixCls, affixStyle, placeholderStyle, $slots, $props } = this;
249-
const getPrefixCls = this.configProvider.getPrefixCls;
250-
const className = classNames({
251-
[getPrefixCls('affix', prefixCls)]: affixStyle,
195+
expose({
196+
updatePosition,
197+
lazyUpdatePosition,
252198
});
253-
const props = omit($props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']);
254-
return (
255-
<ResizeObserver
256-
onResize={() => {
257-
this.updatePosition();
258-
}}
259-
>
260-
<div {...props} style={placeholderStyle} ref="placeholderNode">
261-
<div class={className} ref="fixedNode" style={affixStyle}>
262-
{$slots.default?.()}
263-
</div>
264-
</div>
265-
</ResizeObserver>
199+
watch(
200+
() => props.target,
201+
val => {
202+
let newTarget = null;
203+
if (val) {
204+
newTarget = val() || null;
205+
}
206+
if (state.prevTarget !== newTarget) {
207+
removeObserveTarget(currentInstance);
208+
if (newTarget) {
209+
addObserveTarget(newTarget, currentInstance);
210+
// Mock Event object.
211+
updatePosition();
212+
}
213+
state.prevTarget = newTarget;
214+
}
215+
},
266216
);
217+
watch(() => [props.offsetTop, props.offsetBottom], updatePosition);
218+
watch(
219+
() => state.status,
220+
() => {
221+
measure();
222+
},
223+
);
224+
225+
onMounted(() => {
226+
const { target } = props;
227+
if (target) {
228+
// [Legacy] Wait for parent component ref has its value.
229+
// We should use target as directly element instead of function which makes element check hard.
230+
state.timeout = setTimeout(() => {
231+
addObserveTarget(target(), currentInstance);
232+
// Mock Event object.
233+
updatePosition();
234+
});
235+
}
236+
});
237+
238+
onUnmounted(() => {
239+
clearTimeout(state.timeout);
240+
removeObserveTarget(currentInstance);
241+
(updatePosition as any).cancel();
242+
// https://github.com/ant-design/ant-design/issues/22683
243+
(lazyUpdatePosition as any).cancel();
244+
});
245+
246+
return () => {
247+
const { prefixCls } = props;
248+
const { affixStyle, placeholderStyle } = state;
249+
const { getPrefixCls } = configProvider;
250+
const className = classNames({
251+
[getPrefixCls('affix', prefixCls)]: affixStyle,
252+
});
253+
const restProps = omit(props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']);
254+
return (
255+
<ResizeObserver onResize={updatePosition}>
256+
<div {...restProps} style={placeholderStyle} ref={placeholderNode}>
257+
<div class={className} ref={fixedNode} style={affixStyle}>
258+
{slots.default?.()}
259+
</div>
260+
</div>
261+
</ResizeObserver>
262+
);
263+
};
267264
},
268265
});
269266

components/affix/utils.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ export function addObserveTarget(
8181
entity!.eventHandlers[eventName] = addEventListener(target, eventName, () => {
8282
entity!.affixList.forEach(
8383
targetAffix => {
84-
(targetAffix as any).lazyUpdatePosition();
84+
const { lazyUpdatePosition } = (targetAffix as any).exposed;
85+
lazyUpdatePosition();
8586
},
8687
(eventName === 'touchstart' || eventName === 'touchmove') && supportsPassive
8788
? ({ passive: true } as EventListenerOptions)

0 commit comments

Comments
 (0)