2021.04.27

김승우·2021년 4월 27일
0

TIL

목록 보기
53/68

😊 2021.04.27 ~ 2021.04.28

- 사이드 프로젝트

🎉 TIL

1. Vue 루트 컴포넌트, Router, Store 인스턴스가 생성되는 시점을 이용한 스토어 초기 세팅

  1. 기존에 SearchPage의 컴포넌트 가드에서 실행하던 Search 스토어 모듈 초기 세팅을, Store/index.js 파일에서 실행하도록 변경했다.
  2. store, router가 루트 컴포넌트 App.vue의 라이프 사이클 훅이 실행되기 전에 먼저 실행되므로, 초기 설정을 하기에 적합하다고 생각했다.
    => 변경한 방식의 단점 : "store/index.js" 내의 소스가 길어졌고, 추가적으로 다른 설정이 필요할 경우 코드가 더 추가되어야 하는데, 더 복잡해질 우려가 있다.
    ( Search 모듈을 선언하는 파일 내에서 초기 설정을 하고 싶었으나, store의 commit, dispatch 함수를 사용할 방법을 찾지 못했다. )
  • 적용한 소스
//store/index.js
// SearchPage 진입 시 최초 데이터 설정
const searchText = getUrlQuery("q");
if (isValidString(searchText)) {
    store.commit("search/SET_SEARCH_TEXT", searchText);
    store.commit("search/SET_IS_SEARCH_FORM", true);
}
  • 화면이 새로고침 될 때마다 스토어가 재 생성되므로 초기 설정이 실행된다.
  • url에서 가져온 데이터를 Search 모듈의 초기 값으로 설정해주는 로직.

2. 무한 스크롤을 이용한 추가 데이터 요청

2.1 최초 데이터 요청과 무한 스크롤을 통한 추가 데이터 요청 구분하기

굉장히 오랫동안 고민한 기능이다. 우선 API 요청 함수를 두개를 생성해서 사용할지 고민했지만 하나를 사용하기로 결정했다.
따라서, 하나의 API 함수에서 추가 데이터 요청인지 아니면 최초 데이터 요청인지를 아는 방법이 필요했다.
API 함수의 인자로 전달되는 page 파라미터 값을 사용하기로 결정했다.
=> 검색어가 변경되거나, 최초에 화면에 진입해서 데이터를 요청할 때는 항상 page 값이 1이 되고, 무한 스크롤을 통한 추가 데이터 요청 시에는 page가 2이상이 되므로 이 점을 이용하기로 했다.
state에 isLoading, isFiniteLoading이라는 두개의 데이터를 선언하고, SearchPage.vue 컴포넌트에서 각 로딩이 참이 될 경우 렌더링하는 로딩 영역 두개를 마크업했다.

  • 마크업 구조
<!-- 1. 로딩 스피너 -->
<Loading v-if="isLoading"></Loading>
<!-- 2. 컨텐츠 -->
<div v-else>
  <ResultList></ResultList>
  <!-- 3. 인피니트 로딩 스피너 -->
  <InfiniteLoading v-if="isInfiniteLoading"></InfiniteLoading>
</div>
  • 적용한 소스
async SEARCH_MOVIE({ dispatch, commit, state }, { query, page }) {
  if (!page) throw new Error(`Invalid Page : ${page}`);

  // 1. 추가 데이터 요청인지 체크
  const isInfinite = page > 1;

  try {
    // 2. 
    dispatch("SET_LOADING", {
      isInfinite,
      isLoading: true,
    });

    const data = await movieApi.searchMovie(query, page);

    const { total_pages, results } = data;

    // 조회 가능 총 페이지수 commit
    commit("SET_TOTAL_PAGES", total_pages);

    // 현재 조회한 페이지 commit
    commit("SET_CURRENT_PAGE", page);

    // 3.
    const newResult = isInfinite
    ? [...state.searchResult, ...results]
    : results;

    commit("SET_SEARCH_RESULT", newResult);

    return data;
  } catch (error) {
    throw error.data;
  } finally {
    // 4.
    dispatch("SET_LOADING", {
      isInfinite,
      isLoading: false,
    });
  }
},
  
/**
* Search 모듈 로딩 상태 업데이트
* @param {Boolean} isInfinite : 추가 로딩 여부
* @param {Boolean} isLoading : 로딩 상태 값 true/false
*/
SET_LOADING({ commit }, { isInfinite, isLoading }) {
   if (isInfinite) {
      commit("SET_IS_INFINITE_LOADING", isLoading);
    } else {
      commit("SET_IS_LOADING", isLoading);
    }
},
  • 소스 설명
    1. 인자로 전달받은 page가 1보다 큰지 체크해서 추가 데이터 요청인지, 초기 데이터 요청인지 구분했다.
    2. 두 개의 로딩 state를 선택해서 변경하기위한 action. SET_LOADINGisInfinite, isLoading 두개의 인자를 전달받는다. isInfinite를 통해서 어떤 state를 업데이트하는 커밋을 실행할지 분기처리하고, isLoading을 커밋의 인자로 전달한다.
    3. 추가 데이터일 경우 기존의 데이터에 Push, 아닐 경우 기존의 데이터를 새로운 배열로 업데이트한다.
    4. finally {} 내에서 로딩을 종료한다.

2.1 스크롤 한 높이 구하기

window.pageYOffset ||
    (document.documentElement || document.body.parentNode || document.body)
        .scrollTop;

2.2 스크롤 방향 계산

2.3 스크롤 이벤트 함수

  • 적용한 소스
async onScroll(event) {
  // 페이지가 아직 완전히 로드되지 않았을 경우 실행 방지
  if (!this.isPageLoaded) return;

  // 현재 진행중인 스크롤 이벤트가 있을 경우 실행 방지
  if (this.isScrolling) return;

  // 높이 계산
  const $content = this.$refs.movieList;

  if (!$content) return;

  // 스크롤 한 거리
  const documentScrollHeight =
        window.pageYOffset ||
        (
          document.documentElement ||
          document.body.parentNode ||
          document.body
        ).scrollTop;

  const contentRect = $content.getBoundingClientRect();

  // 인피니티 스크롤 API 요청 위치: 스크롤한 거리가 컨텐츠의 중간 지점을 지날 경우
  const targetPoint =
        documentScrollHeight + contentRect.top + contentRect.height / 2;

  // 중간 지점 보다 작을 경우 리턴
  if (documentScrollHeight < targetPoint) return;

  this.isScrolling = true;
  this.page++;
  // API 요청
  await this.fetchData();

  // API 요청 딜레이 적용
  // isScrolling을 통해 이벤트 핸들러 실행을 제어하고 있으므로, API 요청을한 후 requestDelay만큼의 시간이
  // 지나기 전 까지 API 요청을 막을 수 있음
  setTimeout(() => {
    this.isScrolling = false;
  }, this.requestDelay || 500);
},

참고

  1. 무한 스크롤 오늘의 집

  2. 무한 스크롤 구현 예제

  3. 현재 진행 중인 API 요청 수 카운트

  4. RemoveEventListener 참고

  5. throttled된 이벤트 remove 참고

  6. throttling & debouncing 참고


🎉 TODO

1. intersectionObserver API 적용하기

2. 초기 API 요청 후 요청 할 수있는 데이터가 있고, 화면에 여백이 있으면 추가 데이터 요청해서 화면 채우기


2. TIL

1. url 쿼리스트링 변경하기

const queryString = new URLSearchParams(window.location.search);

const currentId = queryString.get("categoryId");

if( currentId == newId ) return;

queryString.set("categoryId", newId);
window.history.replaceState(null, null, "?" + queryString.toString());

2. Vue Mixin, extends, component 생성되고, created 훅 실행되는 순서 공부하기

  • ✨ 믹스인의 created 훅에서 컴포넌트 안에있는 메소드를 사용할 수 있다.
profile
사람들에게 좋은 경험을 선사하고 싶은 주니어 프론트엔드 개발자

0개의 댓글