티스토리 뷰
0. 들어가면서
지금까지 뷰에 대한 기초와 axios에 대해 실습을 해보고 lifecycle hook에 대해서도 적용을 해보았다. 이제 우리가 버튼을 누르면 5개씩 사진과 설명을 가져오는 형태에서, 페이스북처럼 무한 스크롤을 적용시켜보자. 이전 코드에서 추가를 시킬 예정이기 때문에 이전 코드를 아래의 url을 타고 보고 오자.
이번에 우리가 사용할 것은 scrollmonitor이다. 쉽게 말하면 viewport, 즉 쉽게 말하자면 정해진 화면에서 벗어나게 되면 특정 이벤트를 실행하거나 특정 함수를 실행시키는 방법이다. 여기서는 화면 스크롤을 내리다가 끝부분이 오면 특정 함수를 실행시켜 다음 데이터를 더 불러오는 방식을 사용한다고 생각하면 된다.
먼저 html으로 scrollmonitor 구현한 뒤에 Vue안에 넣어서 앞에서 배운 lifecycle hook을 사용해서 어디에 넣어야 하는지를 알아보자.
1. 검색하기
우리는 항상 구글을 통해 검색한다. 구글에서 scrollmonitor CDN을 검색해 보자.
cdnjs.com/libraries/scrollmonitor
나는 위의 사이트가 검색돼서 들어갔다. 그리고 아래와 같이 CDN을 얻었다.
https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.4/scrollMonitor.js
이제 적용을 해보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<button @click='getPhotos' class="button-bottom">GET PHOTOS</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
}
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
})
</script>
</body>
</html>
기존에 사용했던 코드에 CDN만 추가한 것이다. 이제 scrollmonitor의 사용법을 보자. 아래의 url은 scrollmonitor의 사용법이 적혀있는 깃헙이다.
github.com/stutrek/scrollmonitor?utm_source=cdnjs&utm_medium=cdnjs_link&utm_campaign=cdnjs_library
위의 url을 들어가 보니 아래의 정보를 찾을 수 있었다.
대략 보니 itemToWatch라는 id값을 가진부분에 scrollMonitor.create를 사용하여 elementWatcher 변수를 만들고, elementWatcher가 보이면(Viewport안에 들어오면) 함수를 실행하는 느낌인 것 같다. 적용해보자.
2. 적용하기
우선 코드를 적어보자.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<button @click='getPhotos' class="button-bottom">GET PHOTOS</button>
</div>
<div id="bottomSensor"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
}
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
})
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
//watchar 가 화면에 들어오면, 콜백함수를 실행하겠다
watcher.enterViewport(() => {
//console.log('____BOTTOM_____') // 바닦 도착 할때마다 BOTTOM 뜬다
// 주의할 점은 페이지 뜨는 순간 bottom이 보인다.
app.getPhotos()
})
</script>
</body>
</html>
우선 바디 부분에 <div id="bottomSensor"></div> 를 추가해 줬다. 그 이유는 이 부분이 화면에 보이면 아래 코드에서 콜백 함수를 실행할 수 있게 하기 위해서 이다.
아래 추가한 코드를 보면 bottomSensor id인 div를 가져와서 bottomSensor변수에 넣어준다. 그리고 scrollMonitor.create를 이용해서 다시 watcher에 넣어준다. 그리고 watchar가 화면에 들어오면 콜백 함수를 실행시킨다고 생각하면 된다.
실행해보면 위의 그림과 같이 스크롤을 무한으로 내리기가 가능하다. 그리고 console로 찍어보면 오른쪽과 같이 스크롤이 늘어날 때마다 찍히는 것을 알 수 있다.
3. Vue에 넣기
현재 우리는 watcher가 html에 있다는 것을 알 수 있다. 왜냐하면 <div id='app'> 안에 안 만들고 밖의 div를 다시 만들어서 구현을 했기 때문이다. 그렇다고 <div id='app'> 안으로 넣어서 두 개의 div를 하나의 div app으로 합치면 될까? 물론 안된다. 왜냐하면 HTML이 Vue 인스턴스와 연결된 순간부터, Life cycle hook의 영향을 받기 때문이다. 따라서 <div id="bottomSensor"></div> 도 이제 <div id="app"> 안으로 넣어야겠다고 넣어주면 구현한 코드가 꼬이게 된다.
결론부터 말하면 우리가 넣어야 할 부분은 created가 아니라 mounted 일 때다. 왜냐하면 created를 할 시점은 HTML 파일이 무엇인지도 모르고 해석하지도 않았을 때이기 때문이다. 즉, created일 때 위의 구현한 내용을 넣으면 HTML과 아무 관련이 없는 함수를 실행한 것이 된다. DOM이 완성되고 화면에 표시되는 시점은 바로 Mounted 부분이다. 혹시나 DOM에 대해 모르겠다면 아래의 url을 읽고 오자.
정리
created와 mounted를 언제 사용하면 될지 간단히 정리해보자.
- created : 초기화 이후 AJAX 요청을 보내기 좋은 시점(Data, Methods에 접근 가능하다.)
- mounted : DOM과 Vue 인스턴스가 연동이 완료되고 난 이후에 실행할 일. 연동 이후에 바로 실행되기 때문에 이 시점보다 빠르게 연동되게 하는 건 불가능하다.
mounted에 넣은 코드를 확인해 보자.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<button @click='getPhotos' class="button-bottom">GET PHOTOS</button>
</div>
<div id="bottomSensor"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
},
addScrollWatcher: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
watcher.enterViewport(() => {
console.log('____BOTTOM_____')
this.getPhotos()
})
}
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
mounted: function() {
this.addScrollWatcher()
}
})
</script>
</body>
</html>
4. 최적화하기
지금까지 스크롤을 내리면 자동으로 추가되는 것을 했다. 이제 스크롤을 내리다가 특정 버튼을 누르면 최상위로 스크롤이 올라가는 버튼을 만들어 보자. 아래의 코드를 보면 scrollToTop 함수를 추가했고, 버튼을 바꾼 것을 확인할 수 있다.
4.1 스크롤 최상단 올리는 버튼 구현
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<!-- <button @click='getPhotos' class="button-bottom">GET PHOTOS</button> -->
<button @click='scrollToTop' class="button-bottom">^</button>
</div>
<div id="bottomSensor"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
},
addScrollWatcher: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
watcher.enterViewport(() => {
console.log('____BOTTOM_____')
this.getPhotos()
})
},
scrollToTop: function() {
window.scroll(0,0) //window도 삭제해도됨
}
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
mounted: function() {
this.addScrollWatcher()
}
})
</script>
</body>
</html>
window.scroll(0,0) 코드에서 window를 삭제해도 똑같게 올라가는 것을 알 수 있다.
구현한 그림을 보면 오른쪽 아래 부분의 버튼을 누르면 최상단으로 올라가는 것을 알 수 있다.
4.2 비동기(Asynchronous)
한 고객이 무한 스크롤을 매크로로 계속한다면 어떻게 될까? 서버가 느려질 가능성이 있다. 따라서 우리는 그것을 방지해 보자. 이때 필요한 개념이 비동기이다. 간단히 개념을 알아보자.
- 동기(Synchronous) : 어떤 객체 또는 함수 내부에서 다른 함수를 호출했을 때 이 함수의 결과를 호출한 쪽에서 처리하면 동기
- 대부분의 함수는 동기이다.
- 비동기(Asynchronous) : 어떤 객체 또는 함수 내부에서 다른 함수를 호출했을 때 이 함수의 결과를 호출한 쪽에서 처리하지 않으면 비동기( ex_ setTimeout )
- 쉽게 말하면 코드는 순차적으로 이뤄줘야 하는데 순차적으로 이루어지지 않는 경우가 비동기이다.
- 블로킹(Blocking) : 자신의 수행 결과가 끝날 때까지 제어권을 가짐
- 예를 들면, 사용자가 입력할 때까지 어떠한 동작도 하지 않고 기다린다.
- 논블로킹(non-blocking) : 블로킹이 아닌 경우를 논블로킹이라 한다.
4.3 적용
우리가 작성할 코드에서 비동기 요청으로 주의할 부분은 AJAX부분과 setTimeout 부분을 조심하면 된다. 왜냐하면 AJAX는 get 방식으로 보낸 후에 응답이 들어오지 않으면 무한 대기상태에 빠질 수 있기 때문이다. 그리고 setTimeout 부분은 지정한 시간을 대기하고 실행하기 때문에 비동기 부분으로 생각할 수 있다.
setTimeout을 추가하여 코드를 작성해 보면 아래와 같다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<!-- <button @click='getPhotos' class="button-bottom">GET PHOTOS</button> -->
<button @click='scrollToTop' class="button-bottom">^</button>
</div>
<div id="bottomSensor"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
},
addScrollWatcher: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
watcher.enterViewport(() => {
// console.log('____BOTTOM_____')
setTimeout(() => (
this.getPhotos()
), 500)
// this.getPhotos()
})
},
scrollToTop: function() {
window.scroll(0,0) //window도 삭제해도됨
}
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
mounted: function() {
this.addScrollWatcher()
}
})
</script>
</body>
</html>
4.4 무한 스크롤 오류 잡아보기
위에서 작성한 코드 중에 이 부분을 보자.
addScrollWatcher: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
watcher.enterViewport(() => {
setTimeout(() => (
this.getPhotos()
), 500)
})
},
enterViewport라는 건, `화면 안에 들어오면! `이라는 말이다. 그래서 재실행하려면 화면 밖으로 나갔다가 다시 들어와야 한다. 이 부분의 가장 큰 문제를 화면 비율을 작게 만들어 스크롤이 안되게 만들어 버리면 무한 스크롤이 작동하지 않는다.
그렇다면 Viewport라는 부분은 어디인지가 궁금해 할 수 있다. 아래의 url을 들어가 보면 보이는 부분, 즉 vuewport는 true이고 보이지 않는 부분은 false라는 것을 알 수 있다. 들어가서 확인을 해보자.
stutrek.github.io/scrollmonitor/demos/stress.html
여기서 우리가 쓸 부분은 isFullyinviewport이다. isFullyinviewport는 쉽게 말하면, 우리가 지정한 buttonsensor사 화면에 보인다면 센서가 안 보일 때까지 밀어낸다. 즉, data가 바뀐 후에 리랜더 됐을 때 div의 id가 buttonsensor가 viewport 안에 있으면 함수를 계속 실행해서 센서를 아래로 밀어낸다고 보면 된다. 따라서 isFullyinviewport를 구현한다면 우리가 처음에 적은 created 부분을 지워도 자동으로 첫 부분에서도 data를 생성시키는 것을 알 수 있다. 아래는 구현한 코드이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.button-bottom {
position: fixed;
right: 10vw;
bottom: 20vh;
}
</style>
<title>Scroller</title>
</head>
<body>
<div id="app">
<div v-for="photo in photos">
<h5>{{ photo.title }}</h5>
<img :src="photo.thumbnailUrl" :alt="photo.title">
</div>
<!-- <button @click='getPhotos' class="button-bottom">GET PHOTOS</button> -->
<button @click='scrollToTop' class="button-bottom">^</button>
</div>
<div id="bottomSensor"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- scrollMonitor -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/scrollmonitor/1.2.0/scrollMonitor.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
photos: [],
page: 1,
},
methods: {
getPhotos: function () {
const options = {
params:{
_page: this.page++, // 기존값 가져와서 쓰고나서 1을 올린다//++page는 올리고 쓴다
_limit: 5,
}
}
this.page++ // 기존값 가져와서 쓰고나서 1을 올린다
axios.get('https://jsonplaceholder.typicode.com/photos', options)
.then((res) => {
this.photos = [...this.photos, ...res.data]
})
.catch(err => console.error(err))
},
addScrollWatcher: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
watcher.enterViewport(() => {
// console.log('____BOTTOM_____')
setTimeout(() => (
this.getPhotos()
), 500)
// this.getPhotos()
})
},
scrollToTop: function() {
window.scroll(0,0) //window도 삭제해도됨
},
loadUntilViewportIsFull: function () {
const bottomSensor = document.querySelector('#bottomSensor')
const watcher = scrollMonitor.create(bottomSensor)
if (watchar.isFullyInviewport) {
this.getPhotos() //있으면 가져와. 없으면 가져오지마
}
},
},
created: function (){
// 초기화 이후 AJAX 요청을 보내기 좋은 Hook이 created이다.
this.getPhotos()
},
mounted: function() {
this.addScrollWatcher()
},
updated: function() {
this.loadUntilViewportIsFull()
},
})
</script>
</body>
</html>
5. 마무리
하나씩 공부하다 보면 사실 배우는 게 없는 것 같아도 엄청나게 많은 것들을 배우고 있다는 것을 알 수 있을 것이다. 이제 computed부분을 배우러 가보자. 사실 vue cli를 빨리 알려주고 싶지만, 생각보다 기초가 많은 것 같다. 지금 하고 있는 것들을 하지 않으면 cli를 배우다가 분명 막혀서 찾아보면 시간이 배로 들것이다. 꼭 공부하고 넘어가도록 하자.
'Web > Vue.js' 카테고리의 다른 글
Vue CLI 기초 정리 (0) | 2020.12.08 |
---|---|
[Vue.js] 5. computed 기초 정리 (0) | 2020.12.07 |
[Vue.js] 3. lifecycle hook 적용하기 (0) | 2020.11.20 |
[Vue.js] 2. 데이터 받기(get) (0) | 2020.11.17 |
[Vue.js] 1. 시작하기 (0) | 2020.11.04 |
- Total
- Today
- Yesterday
- TensorFlow
- NextJS
- nextjs autoFocus
- Deque
- 자연어처리
- Python
- read_csv
- react
- typescript
- react autoFocus
- vuejs
- UserCreationForm
- BFS
- useState
- nodejs
- mongoDB
- 클라우데라
- error:0308010C:digital envelope routines::unsupported
- login
- Queue
- 자료구조
- Vue
- DFS
- logout
- django
- Express
- useHistory 안됨
- JavaScript
- pandas
- next.config.js
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |