diff --git a/src/composition/use-global-event.ts b/src/composition/use-global-event.ts index 8dfb0fff9..c387ba1f2 100644 --- a/src/composition/use-global-event.ts +++ b/src/composition/use-global-event.ts @@ -9,7 +9,7 @@ import { } from 'vue'; export function useGlobalEvent( - target: EventTarget, + target: Ref, event: string, handler: EventListener, passive = false, @@ -18,23 +18,23 @@ export function useGlobalEvent( let binded: boolean; function add() { - if (binded || (flag && !flag.value)) { + if (binded || (flag && !flag.value) || !target.value) { return; } - on(target, event, handler, passive); + on(target.value, event, handler, passive); binded = true; } function remove() { - if (binded) { - off(target, event, handler); + if (binded && target.value) { + off(target.value, event, handler); binded = false; } } if (flag) { - watch(() => { + watch(flag, () => { flag.value ? add() : remove(); }); } diff --git a/src/list/index.js b/src/list/index.js index d5fc44ede..d4b2c7707 100644 --- a/src/list/index.js +++ b/src/list/index.js @@ -1,10 +1,20 @@ +import { + ref, + watch, + nextTick, + onUpdated, + onMounted, + getCurrentInstance, +} from 'vue'; + // Utils import { createNamespace } from '../utils'; import { isHidden } from '../utils/dom/style'; import { getScroller } from '../utils/dom/scroll'; -// Mixins -import { BindEventMixin } from '../mixins/bind-event'; +// Composition +import { useRect } from '../composition/use-rect'; +import { useGlobalEvent } from '../composition/use-global-event'; // Components import Loading from '../loading'; @@ -12,16 +22,6 @@ import Loading from '../loading'; const [createComponent, bem, t] = createNamespace('list'); export default createComponent({ - mixins: [ - BindEventMixin(function (bind) { - if (!this.scroller) { - this.scroller = getScroller(this.$el); - } - - bind(this.scroller, 'scroll', this.check); - }), - ], - props: { error: Boolean, loading: Boolean, @@ -45,37 +45,21 @@ export default createComponent({ emits: ['load', 'update:error', 'update:loading'], - data() { - return { - // use sync innerLoading state to avoid repeated loading in some edge cases - innerLoading: this.loading, - }; - }, + setup(props, { emit, slots }) { + // use sync innerLoading state to avoid repeated loading in some edge cases + const loading = ref(false); + const rootRef = ref(); + const scrollerRef = ref(); + const placeholderRef = ref(); - updated() { - this.innerLoading = this.loading; - }, - - mounted() { - if (this.immediateCheck) { - this.check(); - } - }, - - watch: { - loading: 'check', - finished: 'check', - }, - - methods: { - // @exposed-api - check() { - this.$nextTick(() => { - if (this.innerLoading || this.finished || this.error) { + const check = () => { + nextTick(() => { + if (loading.value || props.finished || props.error) { return; } - const { $el: el, scroller, offset, direction } = this; + const scroller = scrollerRef.value; + const { offset, direction } = props; let scrollerRect; if (scroller.getBoundingClientRect) { @@ -90,12 +74,12 @@ export default createComponent({ const scrollerHeight = scrollerRect.bottom - scrollerRect.top; /* istanbul ignore next */ - if (!scrollerHeight || isHidden(el)) { + if (!scrollerHeight || isHidden(rootRef.value)) { return false; } let isReachEdge = false; - const placeholderRect = this.$refs.placeholder.getBoundingClientRect(); + const placeholderRect = useRect(placeholderRef); if (direction === 'up') { isReachEdge = scrollerRect.top - placeholderRect.top <= offset; @@ -104,71 +88,89 @@ export default createComponent({ } if (isReachEdge) { - this.innerLoading = true; - this.$emit('update:loading', true); - this.$emit('load'); + loading.value = true; + emit('update:loading', true); + emit('load'); } }); - }, - - clickErrorText() { - this.$emit('update:error', false); - this.check(); - }, - - genLoading() { - if (this.innerLoading && !this.finished) { - return ( -
- {this.$slots.loading ? ( - this.$slots.loading() - ) : ( - {this.loadingText || t('loading')} - )} -
- ); - } - }, - - genFinishedText() { - if (this.finished) { - const text = this.$slots.finished - ? this.$slots.finished() - : this.finishedText; + }; + const renderFinishedText = () => { + if (props.finished) { + const text = slots.finished ? slots.finished() : props.finishedText; if (text) { return
{text}
; } } - }, + }; - genErrorText() { - if (this.error) { - const text = this.$slots.error ? this.$slots.error() : this.errorText; + const clickErrorText = () => { + emit('update:error', false); + check(); + }; + const renderErrorText = () => { + if (props.error) { + const text = slots.error ? slots.error() : props.errorText; if (text) { return ( -
+
{text}
); } } - }, - }, + }; - render() { - const Placeholder =
; - const Content = this.$slots.default?.(); + const renderLoading = () => { + if (loading.value && !props.finished) { + return ( +
+ {slots.loading ? ( + slots.loading() + ) : ( + {props.loadingText || t('loading')} + )} +
+ ); + } + }; - return ( -
- {this.direction === 'down' ? Content : Placeholder} - {this.genLoading()} - {this.genFinishedText()} - {this.genErrorText()} - {this.direction === 'up' ? Content : Placeholder} -
- ); + watch([() => props.loading, () => props.finished], check); + + onUpdated(() => { + loading.value = props.loading; + }); + + onMounted(() => { + scrollerRef.value = getScroller(rootRef.value); + + if (props.immediateCheck) { + check(); + } + }); + + useGlobalEvent(scrollerRef, 'scroll', check); + + // @exposed-api + const vm = getCurrentInstance().proxy; + vm.check = check; + + return () => { + const Content = slots.default?.(); + const Placeholder = ( +
+ ); + + return ( +
+ {props.direction === 'down' ? Content : Placeholder} + {renderLoading()} + {renderFinishedText()} + {renderErrorText()} + {props.direction === 'up' ? Content : Placeholder} +
+ ); + }; }, });