10-7. prefetching 이 적용된 무한스크롤링 구현하기

자, 이제 prefetching 이 적용된 무한스크롤링을 구현 할 차례입니다. prefetching, 그렇게 복잡할것은 없습니다. 이 단어가 의미하는 그대로 '미리 불러오기' 작업이 중간에 진행되는 것 뿐입니다.

이 흐름을 정리하자면, 다음과 같습니다.

  • 초기 렌더링 20개 불러오기 후 렌더링
  • 렌더링이 끝나면 바로 그 다음 20개 불러오기, 하지만 아직 렌더링 하지 않음
  • 대기.. 유저가 아래로 스크롤중
  • 스크롤이 바닥과 가까워지면, 이미 불러왔었던것들을 렌더링해줌
  • 바로, 다음 리스트를 미리 불러와서 담아둠
  • 다시 스크롤이 바닥과 가까워지면 바로 렌더링
  • 무한 반복...

흐름이 어느정도 이해가 가나요? 이해가 안간다면, 직접 구현해 보는것이 더 빠를지도 모릅니다.

우선 리덕스 모듈부터 작성을 해볼까요?

src/redux/modules/posts.js

import { createAction, handleActions } from 'redux-actions';

import { Map, List, fromJS } from 'immutable';
import * as PostsAPI from 'lib/api/posts';
import { pender } from 'redux-pender';

const LOAD_POST = 'posts/LOAD_POST'; // 포스트 리스트 초기 로딩
const PREFETCH_POST = 'posts/PREFETCH_POST'; // 포스트 미리 로딩
const SHOW_PREFETCHED_POST = 'posts/SHOW_PREFETCHED_POST'; // 미리 로딩된 포스트 화면에 보여주기

export const loadPost = createAction(LOAD_POST, PostsAPI.list);
export const prefetchPost = createAction(PREFETCH_POST, PostsAPI.next); // URL
export const showPrefetchedPost = createAction(SHOW_PREFETCHED_POST);

const initialState = Map({
    next: '',
    data: List(),
    nextData: List()
});

export default handleActions({
    ...pender({
        type: LOAD_POST,
        onSuccess: (state, action) => {
            const { next, data } = action.payload.data;
            return state.set('next', next)
                        .set('data', fromJS(data));
        }
    }),
    ...pender({
        type: PREFETCH_POST,
        onSuccess: (state, action) => {
            // nextData 에 결과를 담아둡니다.
            const { next, data } = action.payload.data;
            return state.set('next', next)
                        .set('nextData', fromJS(data));
        }
    }),
    [SHOW_PREFETCHED_POST]: (state, action) => {
        // data 의 뒷부분에 nextData 를 붙여주고,
        // 기존의 nextData 는 비워줍니다.

        const nextData = state.get('nextData');
        return state.update('data', data => data.concat(nextData))
                    .set('nextData', List());
    }
}, initialState);

그 다음엔, PostListContainer 에서 prefetching 로직을 작성하겠습니다.

src/components/PostListContainer.js

import React, { Component } from 'react';
import PostList from 'components/Shared/PostList';
import { connect } from 'react-redux';
import {bindActionCreators} from 'redux';
import * as postsActions from 'redux/modules/posts';

class PostListContainer extends Component {
    prev = null

    // 포스트 목록 초기로딩
    load = async () => {
        const { PostsActions } = this.props;

        try {
            await PostsActions.loadPost();
            const { next } = this.props;

            if(next) {
                // 다음 불러올 포스트들이 있다면 미리 로딩을 해둔다
                await PostsActions.prefetchPost(next);
            }
        } catch (e) {
            console.log(e);
        }
    }

    // 다음 목록 불러오기
    loadNext = async () => {
        const { PostsActions, next } = this.props;

        PostsActions.showPrefetchedPost(); // 미리 불러왔던걸 보여준 다음에

        if(next === this.prev || !next) return; // 이전에 했던 요청과 동일하면 요청하지 않는다.
        this.prev = next;

        // 다음 데이터 요청
        try {
            await PostsActions.prefetchPost(next);
        } catch (e) {
            console.log(e);
        }
        this.handleScroll(); // 한번 더 호출함으로써, 인터넷 느린 상황에 밀리는 현상 방지
    }



    // 스크롤 리스너
    handleScroll = () => {
        const { nextData } = this.props;
        if(nextData.size === 0) return; // 미리 불러온 데이터 없으면 작업 중지

        const { innerHeight } = window;
        const { scrollHeight } = document.body;
        // IE 에서는 body.scrollTop 대신에 document.documentElement.scrollTop 사용해야함
        const scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;

        if(scrollHeight - innerHeight - scrollTop < 100) {
            this.loadNext();
        }
    }

    componentDidMount() {
        // 컴포넌트가 마운트 됐을 때 호출 합니다.
        this.load();
        window.addEventListener('scroll', this.handleScroll);
    }

    componentWillUnmount() {
        // 컴포넌트가 언마운트 될 때에는 스크롤 이벤트리스너를 제거합니다
        window.removeEventListener('scroll', this.handleScroll);
    }

    render() {
        const { data } = this.props;
        return (
            <PostList posts={data}/>
        );
    }
}

export default connect(
    (state) => ({
        next: state.posts.get('next'),
        data: state.posts.get('data'),
        nextData: state.posts.get('nextData')
    }),
    (dispatch) => ({
        PostsActions: bindActionCreators(postsActions, dispatch)
    })
)(PostListContainer);

여기까지 작업을 하고 나면, prefetching 이 적용된 무한스크롤링이 성공적으로 구현됩니다!

results matching ""

    No results matching ""