From ea2d8e2ecda3cb0f8a3966bfc58e56340e70be67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=A8=80=E8=82=86?= <18x@loacg.com>
Date: Thu, 7 Jan 2021 14:39:57 +0800
Subject: [PATCH] refactor(affix): use Composition api (#3447)
* refactor(affix): use Composition api
* fix: affix code style
---
components/affix/index.tsx | 253 ++++++++++++++++++-------------------
components/affix/utils.ts | 3 +-
2 files changed, 127 insertions(+), 129 deletions(-)
diff --git a/components/affix/index.tsx b/components/affix/index.tsx
index be0e5448a..46869e7ea 100644
--- a/components/affix/index.tsx
+++ b/components/affix/index.tsx
@@ -1,9 +1,19 @@
-import { CSSProperties, defineComponent, inject } from 'vue';
+import {
+ CSSProperties,
+ defineComponent,
+ inject,
+ ref,
+ reactive,
+ watch,
+ onMounted,
+ getCurrentInstance,
+ onUnmounted,
+} from 'vue';
import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames';
import omit from 'omit.js';
import ResizeObserver from '../vc-resize-observer';
-import BaseMixin from '../_util/BaseMixin';
+// import BaseMixin from '../_util/BaseMixin';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import { defaultConfigProvider } from '../config-provider';
import warning from '../_util/warning';
@@ -50,81 +60,29 @@ const AffixProps = {
};
const Affix = defineComponent({
name: 'AAffix',
- mixins: [BaseMixin],
props: AffixProps,
emits: ['change', 'testUpdatePosition'],
- setup() {
- return {
- configProvider: inject('configProvider', defaultConfigProvider),
- };
- },
- data() {
- return {
+ setup(props, { slots, emit, expose }) {
+ const configProvider = inject('configProvider', defaultConfigProvider);
+ const placeholderNode = ref();
+ const fixedNode = ref();
+ const state = reactive({
affixStyle: undefined,
placeholderStyle: undefined,
status: AffixStatus.None,
lastAffix: false,
prevTarget: null,
timeout: null,
- };
- },
- watch: {
- target(val) {
- let newTarget = null;
- if (val) {
- newTarget = val() || null;
- }
- if (this.prevTarget !== newTarget) {
- removeObserveTarget(this);
- if (newTarget) {
- addObserveTarget(newTarget, this);
- // Mock Event object.
- this.updatePosition();
- }
- this.prevTarget = newTarget;
- }
- },
- offsetTop() {
- this.updatePosition();
- },
- offsetBottom() {
- this.updatePosition();
- },
- },
- beforeMount() {
- this.updatePosition = throttleByAnimationFrame(this.updatePosition);
- this.lazyUpdatePosition = throttleByAnimationFrame(this.lazyUpdatePosition);
- },
- mounted() {
- const { target } = this;
- if (target) {
- // [Legacy] Wait for parent component ref has its value.
- // We should use target as directly element instead of function which makes element check hard.
- this.timeout = setTimeout(() => {
- addObserveTarget(target(), this);
- // Mock Event object.
- this.updatePosition();
- });
- }
- },
- updated() {
- this.measure();
- },
- beforeUnmount() {
- clearTimeout(this.timeout);
- removeObserveTarget(this);
- (this.updatePosition as any).cancel();
- // https://github.com/ant-design/ant-design/issues/22683
- (this.lazyUpdatePosition as any).cancel();
- },
- methods: {
- getOffsetTop() {
- const { offset, offsetBottom } = this;
- let { offsetTop } = this;
- if (typeof offsetTop === 'undefined') {
+ });
+ const currentInstance = getCurrentInstance();
+
+ const getOffsetTop = () => {
+ const { offset, offsetBottom } = props;
+ let { offsetTop } = props;
+ if (offsetTop === undefined) {
offsetTop = offset;
warning(
- typeof offset === 'undefined',
+ offset === undefined,
'Affix',
'`offset` is deprecated. Please use `offsetTop` instead.',
);
@@ -134,26 +92,19 @@ const Affix = defineComponent({
offsetTop = 0;
}
return offsetTop;
- },
-
- getOffsetBottom() {
- return this.offsetBottom;
- },
- // =================== Measure ===================
- measure() {
- const { status, lastAffix } = this;
- const { target } = this;
- if (
- status !== AffixStatus.Prepare ||
- !this.$refs.fixedNode ||
- !this.$refs.placeholderNode ||
- !target
- ) {
+ };
+ const getOffsetBottom = () => {
+ return props.offsetBottom;
+ };
+ const measure = () => {
+ const { status, lastAffix } = state;
+ const { target } = props;
+ if (status !== AffixStatus.Prepare || !fixedNode.value || !placeholderNode.value || !target) {
return;
}
- const offsetTop = this.getOffsetTop();
- const offsetBottom = this.getOffsetBottom();
+ const offsetTop = getOffsetTop();
+ const offsetBottom = getOffsetBottom();
const targetNode = target();
if (!targetNode) {
@@ -164,7 +115,7 @@ const Affix = defineComponent({
status: AffixStatus.None,
} as AffixState;
const targetRect = getTargetRect(targetNode);
- const placeholderReact = getTargetRect(this.$refs.placeholderNode as HTMLElement);
+ const placeholderReact = getTargetRect(placeholderNode.value as HTMLElement);
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
if (fixedTop !== undefined) {
@@ -193,41 +144,39 @@ const Affix = defineComponent({
newState.lastAffix = !!newState.affixStyle;
if (lastAffix !== newState.lastAffix) {
- this.$emit('change', newState.lastAffix);
+ emit('change', newState.lastAffix);
}
-
- this.setState(newState);
- },
-
- prepareMeasure() {
- this.setState({
+ // update state
+ Object.assign(state, newState);
+ };
+ const prepareMeasure = () => {
+ Object.assign(state, {
status: AffixStatus.Prepare,
affixStyle: undefined,
placeholderStyle: undefined,
});
- this.$forceUpdate();
-
// Test if `updatePosition` called
if (process.env.NODE_ENV === 'test') {
- this.$emit('testUpdatePosition');
+ emit('testUpdatePosition');
}
- },
- updatePosition() {
- this.prepareMeasure();
- },
- lazyUpdatePosition() {
- const { target } = this;
- const { affixStyle } = this;
+ };
+
+ const updatePosition = throttleByAnimationFrame(() => {
+ prepareMeasure();
+ });
+ const lazyUpdatePosition = throttleByAnimationFrame(() => {
+ const { target } = props;
+ const { affixStyle } = state;
// Check position change before measure to make Safari smooth
if (target && affixStyle) {
- const offsetTop = this.getOffsetTop();
- const offsetBottom = this.getOffsetBottom();
+ const offsetTop = getOffsetTop();
+ const offsetBottom = getOffsetBottom();
const targetNode = target();
- if (targetNode && this.$refs.placeholderNode) {
+ if (targetNode && placeholderNode.value) {
const targetRect = getTargetRect(targetNode);
- const placeholderReact = getTargetRect(this.$refs.placeholderNode as HTMLElement);
+ const placeholderReact = getTargetRect(placeholderNode.value as HTMLElement);
const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);
const fixedBottom = getFixedBottom(placeholderReact, targetRect, offsetBottom);
@@ -240,30 +189,78 @@ const Affix = defineComponent({
}
}
// Directly call prepare measure since it's already throttled.
- this.prepareMeasure();
- },
- },
-
- render() {
- const { prefixCls, affixStyle, placeholderStyle, $slots, $props } = this;
- const getPrefixCls = this.configProvider.getPrefixCls;
- const className = classNames({
- [getPrefixCls('affix', prefixCls)]: affixStyle,
+ prepareMeasure();
});
- const props = omit($props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']);
- return (
- {
- this.updatePosition();
- }}
- >
-
-
- {$slots.default?.()}
-
-
-
+
+ expose({
+ updatePosition,
+ lazyUpdatePosition,
+ });
+ watch(
+ () => props.target,
+ val => {
+ let newTarget = null;
+ if (val) {
+ newTarget = val() || null;
+ }
+ if (state.prevTarget !== newTarget) {
+ removeObserveTarget(currentInstance);
+ if (newTarget) {
+ addObserveTarget(newTarget, currentInstance);
+ // Mock Event object.
+ updatePosition();
+ }
+ state.prevTarget = newTarget;
+ }
+ },
);
+ watch(() => [props.offsetTop, props.offsetBottom], updatePosition);
+ watch(
+ () => state.status,
+ () => {
+ measure();
+ },
+ );
+
+ onMounted(() => {
+ const { target } = props;
+ if (target) {
+ // [Legacy] Wait for parent component ref has its value.
+ // We should use target as directly element instead of function which makes element check hard.
+ state.timeout = setTimeout(() => {
+ addObserveTarget(target(), currentInstance);
+ // Mock Event object.
+ updatePosition();
+ });
+ }
+ });
+
+ onUnmounted(() => {
+ clearTimeout(state.timeout);
+ removeObserveTarget(currentInstance);
+ (updatePosition as any).cancel();
+ // https://github.com/ant-design/ant-design/issues/22683
+ (lazyUpdatePosition as any).cancel();
+ });
+
+ return () => {
+ const { prefixCls } = props;
+ const { affixStyle, placeholderStyle } = state;
+ const { getPrefixCls } = configProvider;
+ const className = classNames({
+ [getPrefixCls('affix', prefixCls)]: affixStyle,
+ });
+ const restProps = omit(props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target']);
+ return (
+
+
+
+ {slots.default?.()}
+
+
+
+ );
+ };
},
});
diff --git a/components/affix/utils.ts b/components/affix/utils.ts
index d1b7bd373..715453ff3 100644
--- a/components/affix/utils.ts
+++ b/components/affix/utils.ts
@@ -81,7 +81,8 @@ export function addObserveTarget(
entity!.eventHandlers[eventName] = addEventListener(target, eventName, () => {
entity!.affixList.forEach(
targetAffix => {
- (targetAffix as any).lazyUpdatePosition();
+ const { lazyUpdatePosition } = (targetAffix as any).exposed;
+ lazyUpdatePosition();
},
(eventName === 'touchstart' || eventName === 'touchmove') && supportsPassive
? ({ passive: true } as EventListenerOptions)