Vue 인피니티 스크롤 컴포넌트 만들어보기

김승우·2021년 9월 13일
1

Vue.js 강의 내용 정리

목록 보기
15/15

😊 만든 계기

: 프로젝트에 사용하려고 했던 vue 인피니티 스크롤 컴포넌트가 vue3에서 지원을 하지 않아서 형태를 참고해서 직접 컴포넌트를 구현해 보았다.


😱 만들고나니 느낀점

: 정말 간단하게 만들어서 그런지 컴포넌트가 유연하지 않다..
부모 자식 컴포넌트간 통신하는 새로운 방법에 대해서 알게되었다
기존에는 부모 -> 자식은 props로 자식 -> 부모는 이벤트를 통해서 통신하는 방법만 알고 있었는데, 자식이 부모에 이벤트를 전달할 때 data를 변경할 수 있는 메소드를 가진 객체(참고한 라이브러리에서는 $state)를 전달하고, 이 $state의 메소드를 실행해서 자식의 data를 변경하는 방법을 알 수 있었다.


🐱‍🚀 개선 사항

  1. 현재는 v-if를 통해 라이프 사이클 훅이 다시 실행되어야만 observer를 갱신할 수 있다.
  2. slot을 통해 spinner 변경하기
  3. 로직 분리하기

👀 참고

  1. vue-infinite-loading 라이브러리
  2. force IntersectionObserver to update

🎉 소스

  • InfiniteScroll.vue
<!-- InfiniteScroll.vue -->
<template>
	<div class="infinite-scroll">
		<Spinner class="infinite-scroll__spinner" v-if="isLoading" />
	</div>
</template>

<script>
import Spinner from '@/components/common/Spinner.vue';

class ObserverState {
	constructor() {
		this.status = '';
	}

	loading() {
		this.status = 'loading';
	}

	complete() {
		this.status = 'complete';
	}

	error() {
		this.status = 'error';
	}

	loaded() {
		this.status = 'loaded';
	}
}

export default {
	name: 'infinite-scroll',

	components: {
		Spinner,
	},

	data() {
		return {
			// 옵저버 상태
			state: new ObserverState(),

			// 옵저버 인스턴스
			observer: null,

			// 옵저버 옵션
			options: {
				threshold: 0.5,
			},
		};
	},

	computed: {
		isLoading() {
			return this.state.status === 'loading';
		},

		isComplete() {
			return this.state.status === 'complete';
		},

		isError() {
			return this.state.status === 'error';
		},

		isLoaded() {
			return this.state.status === 'loaded';
		},
	},

	watch: {
		state: {
			handler(newState) {
				if (newState.status === 'loaded') {
					this.refresh();
				}
			},

			deep: true,
		},
	},

	created() {
		// 옵저버 인스턴스 생성
		this.createInstance();
	},

	mounted() {
		if (this.observer instanceof IntersectionObserver) {
			// observe 시작
			this.observer.observe(this.$el);
		}
	},

	methods: {
		// 옵저버 인스턴스 생성
		createInstance() {
			const observer = new IntersectionObserver(
				this.observeHandler,
				this.options,
			);

			this.observer = observer;
		},

		// intersect 콜백
		observeHandler([entry]) {
			const { intersectionRatio } = entry;

			if (intersectionRatio >= this.options.threshold) {
				const { status } = this.state;

				// 로딩 중이거나 실행이 완료되었을 경우 함수 종료
				if (status === 'loading' || status === 'complete') return;

				this.state.loading();
				this.$emit('infinite', this.state);
			}
		},

		/**
		 * https://stackoverflow.com/questions/51402840/force-intersectionobserver-update
		 */
		refresh() {
			if (this.observer instanceof IntersectionObserver) {
				this.observer.unobserve(this.$el);
				this.observer.observe(this.$el);
			}
		},
	},
};
</script>

<style lang="scss" scoped></style>
  • SearchPage.vue
<InfiniteScroll @infinite="infiniteHandler" />

  methods: {
    async infiniteHandler(state) {
      try {
        const results = await this.fetchMoreData();

        if (results.length) {
          state.loaded();
        } else {
          state.complete();
        }
      } catch (error) {
        console.log(error.message);
      }
    }
  }
profile
사람들에게 좋은 경험을 선사하고 싶은 주니어 프론트엔드 개발자

0개의 댓글