/lecture

해피데빙·2022년 10월 6일
0

vue

목록 보기
9/9
  1. 인피니티 스크롤
  • window에서 스크롤 하는 경우 (search.vue)
  • div에서 스크롤 하는 경우 (article.vue, exam.vue)
  1. axios
  • api 응답오기 전에 다른 api 요청을 날리는 경우 cancel 처리
  1. visual viewport
  • 모바일에서 가상키보드 활성화 이벤트가 따로 없어서 visual viewport로 감지
  1. dialog 특징
  • menu: 특정 컴포넌트를 기준으로 좌표설정해서 띄움 (position, x, y prop 확인)
  • bottomsheet: 스크롤 가능한 컴포넌트

인피니티 스크롤

  1. vue에서 컴포넌트 생성(created) 시 window.addEventListener('scroll', this.loadMore)
  2. this.loadMore
  • getScrollTop로 위에서부터 scroll한 px
  • getScrollThreshold로 document의 offsetHeight - innerheight - 600
  • scrollTop이 getScrollThreshold보다 크고 this.isFetched가 true면 다시 fetchLectures를 한다
  1. fetchLectures
  • axios.CancelToken : Axios의 취소 토큰 - 요청을 취소할 수 있다
    cf. CancelToken.source() 팩토리를 사용하여 취소 토큰을 만들 수 있다

cancelToken 사용법 예시

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 에러 핸들링
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// 요청 취소하기 (메시지 파라미터는 옵션입니다)
source.cancel('Operation canceled by the user.');

1) views/pages/lecture/search.js
: window.addEventListener('scroll', this.loadMore)

created(){ 
const previouspageCache = window.sessionStorage.getItem('previouspage')
//현재 페이지
const pageCache = getPageCache()
//JSON.parse(window.sessionStorage.getItem(key))

if(/^\/lecture\/view/.test(previouspageCache) && pageCache !== null){
//현재 페이지가 lecture/view + pageCache에 뭐가 들어가있으면 

	this.lectures = pageCache.lectures;
    this.campusId = pageCache.campusId; 
    window.addEventListener('scroll', this.loadMore) 
    return; 
}
window.addEventListener('scroll', this.loadMore) 
}

destroyed(){
window.removeEventListener('scroll', this.loadMore)
}

methods:{ 
   async fetchLectures({refresh} = {}) {
      if (this.cancelSource) { //cancelSource에 취소토큰을 할당했으면
        this.cancelSource.cancel(); //요청취소하기 
        this.cancelSource = undefined; //원상복구
      }
      if (refresh) { //refresh하라고 했으면 빈 배열로
        this.lectures = [];
      }
      const {campusId, limit} = this;
      const {query} = this.$route;
      const keyword = query.keyword.trim();
      if (keyword.replace(/\s/g, '').length === 0) {
        return;
      }
      this.isFetched = false;
      const data = {
        campusId,
        field: query.condition,
        keyword,
        limit,
        offset: this.lectures.length
      };
      this.cancelSource = this.$axios.CancelToken.source();
      //axios 요청을 취소할 수 있는 취소토큰을 CancelToken.source()를 이용해서 만든다 
      const cancelToken = this.cancelSource.token;
      //
      try {
        const {data: {result, status}} = await this.$http.post('/find/lecture/list/keyword', data, {cancelToken});
        //요청 취소. cancelToken이 undefined여도 괜찮은걸까?
        this.cancelSource = undefined;
        //원상복구
        if (status === 'success') {
        //성공했으면
          const {lectures} = result;
          //결과 중 lectures받아서
          if (lectures.length === 0) {
            window.removeEventListener('scroll', this.loadMore);
            //길이가 0이면 삭제
          } else {
          //길이가 0이 아니면 
            const addHightlightTag = (text) => {
              if (!text) {
                return text;
              }
              return text.replace(keyword, `<span class="highlight">${keyword}</span>`);
              //
            };
            this.lectures = this.lectures.concat(lectures.map(lecture => {
              const name = query.condition === 'name' ? addHightlightTag(lecture.name.replace(/</g, '&lt;')) : lecture.name.replace(/</g, '&lt;');
              const professor = query.condition === 'professor' ? addHightlightTag(lecture.professor?.replace(/</g, '&lt;')) : lecture.professor?.replace(/</g, '&lt;');
              return {
                ...lecture,
                name,
                professor
              };
            }));
            window.addEventListener('scroll', this.loadMore);
            setPageCache({
              campusId,
              lectures: this.lectures
            });
          }
        }
        this.isFetched = true;
        //post를 해서 데이터를 다 받아오면 true로
      } catch (err) {
        if (!this.$axios.isCancel(err)) {
          throw err;
        }
      }
    },
loadMore: throttle(function(){
	const scrollTop = getScrollTop(); 
    const scrollThreshold = getScrollThreshold(600) 
    if(scrollTop > scrollThreshold && this.isFetched){
    	this.fetchLectures();
    }, 100) 
   }
})
}

2) views/service.js : setPageCache, getPageCache

export function setPageCache(data) {
  Object.keys(window.sessionStorage)
    .filter(key => key.startsWith('pagecache_'))
    .forEach(key => window.sessionStorage.removeItem(key));
  const {pathname, search} = window.location;
  const key = `pagecache_${pathname}${search}`;
  window.sessionStorage.setItem(key, JSON.stringify(data));
}
//remove를 해서 다시 set

function getPageCache(){
	const {pathname, search} = window.location
    const key = `pagecache_${pathname}${search}`
    try{
    	return JSON.parse(window.sessionStorage.getItem(key)) 
    }catch{ 
    	return null
    }
}

export function getScrollThreshold(offset = 0) {
  const {offsetHeight} = document.scrollingElement || document.documentElement;
  //returns the height of an element, including vertical padding and borders, as an integer.

  return offsetHeight - window.innerHeight - offset;
  //innerHeight property of the Window interface : returns the interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
}

function getScrollTop(){
	const {scrollTop} = document.scrollingElement || document.documentElement;  //문서 불러온다
    return scrollTop
    //scrollTop은 위에서부터 몇 px인지
}

3)throttle.js
//함수를 실행하기 전에 기다릴 시간을 정해준다

export default function throttle(func, wait, options){
 var timeout, context, args, result; 
 var previous = 0; 
 if(!options) options = {}; 
 
 var later = function(){
  previous = options.leading === false ? 0 : now(); 
  timeout = null; 
  result = func.apply(context, args) 
  if(!timeout) context = args = null; 
 }
 
 var throttled = function(){
 	var _now = now(); 
    if(!previous && options.leading === false) previous = _now;
    var remaining = wait - (_now - previous);
    context = this; 
    args = arguments;
    if(remaining <= 0 || remaining > wait){
    	if(timeout){
        	clearTimeout(timeout);
            timeout = null;
        }
        previous = _now; 
        result = func.apply(context, args) 
        if(!timeout)context = args = null;
    }
  
   }
  
}

dialog

1.article.vue

  • 상위 컴포넌트에서 하위 컴포넌트 사용 :
  • v-bind="menu.rate"
  • v-slot="{item:{count, icon, title}}"
    //js에서 넘기고 있는 값을 하위 컴포넌트인 menu.vue로 넘긴다
    //props.item의 작업을 미리해서 바로 count, icon, title 사용 가능
  1. article.js
  • data(){return menu{rate: undefined}}
  • methods:{handleClickSortButton(){
    ...
    this.menu.sort = {
    items : this.sortOptions,
    x:targetDom.right,
    y:targetDom.bottom + 8,
    position: 'topright'
    }
    }
  1. menu.js
    props:{items, x, y, position}
    //props로서 각각의 조건 정의 ex. required : true/false
  2. menu.vue

    //이 자리에 상위 컴포넌트에서 v-slot으로 넘긴 값들이 들어온다
  • touchmove : The touchmove event is fired when one or more touch points are moved along the touch surface.

  • wheel : The wheel event fires when the user rotates a wheel button on a pointing device (typically a mouse).

vue 이벤트 발생
: 컴포넌트 통신 방법 중 하위 컴포넌트에서 상위 컴포넌트로 통신하는 방식

이벤트 발생 코드 형식
하위 컴포넌트의 메서드나 라이프 사이클 훅과 같은 곳에 아래와 같은 코드 추가

//하위 컴포넌트의 내용
this.$emit('이벤트 명'); 

해당 이벤트를 수신하기 위해 상위 컴포넌트의 템플릿에 아래와 같이 구현
:v-on:이벤트 명="상위 컴포넌트의 실행할 메서드 명 또는 연산"

<!-- 상위 컴포넌트의 템플릿 -->
<div id="app">
  <child-component v-on:이벤트명A="함수명A"></child-component>
</div>

ex.

// 하위 컴포넌트 : childComponent
var childComponent = {
  methods: {
     함수명A: function() {
      this.$emit('이벤트명A ex.click');
    }
  }
}

// 상위 컴포넌트 : root 컴포넌트
new Vue({
  el: '#app',
  components: {
    'child-component': childComponent
  },
  methods: {
    함수명B: function() {
      alert('event received');
    }
  }
})

<div id="app">
  <child-component v-on:이벤트명A="함수명B"></child-component>
</div>

childComponent -> sendEvent() 실행 시 update이벤트 발생 -> 상위 컴포넌트인 루트 컴포넌트의 v-on 디렉티브로 이벤트를 받아 show

talk.vue

profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글