[프로젝트-DediCats] Devlog-12

김대연·2020년 2월 19일
0

Project DediCats

목록 보기
13/16

오늘은 앱의 포스트 기능에서 자주 볼 수 있는 Infinite Scroll 과 Pull down Refresh 기능을 구현했다. 백엔드 팀원분들에게 이 기능을 위해 pagination 기능을 추가하고 싶다고 전달하였고, 다행히 어렵지 않은 부분이라 계속 진행할 수 있었다.

먼저 검색을 해보니 ScrollView 보다는 FlatList 를 구현하기가 용이하다고 한다. 그리고 해당 블로그에서 자세하게 예시를 보여주어 어렵지 않게 구현할 수 있었다.

import React from 'react';
import { inject, observer } from 'mobx-react';
import { withNavigation } from 'react-navigation';
import {
  StyleSheet,
  View,
  FlatList,
  SafeAreaView,
  ActivityIndicator,
  KeyboardAvoidingView,
} from 'react-native';
import * as Font from 'expo-font';
import CatPost from './CatPost';
import CatPostInput from './CatPostInput';


class CatPostList extends React.Component {
  componentDidMount() {
    this.props.getPostList(this.props.navigation);
  }

  _renderItem = ({ item }) => (
    <CatPost
      catId={this.props.catId}
      item={item}
      setCatPost={this.props.setCatPost}
      convertDateTime={this.props.convertDateTime}
    />
  );

  renderFooter = () => (this.props.isLoadingPost ? (
    <View style={styles.loader}>
      <ActivityIndicator size="large" />
    </View>
  ) : (
    <View />
  ));

  render() {
    const {
      postList,
      _handleLoadMorePosts,
      isRefreshingPost,
      _handleRefresh,
    } = this.props;

    return (
      <View style={styles.container}>
        <View style={styles.radiusView}>
          <SafeAreaView style={styles.safeArea}>
            <KeyboardAvoidingView style={styles.keyboard}>
              {this.state.visibility ? <CatPostInput /> : null}
            </KeyboardAvoidingView>
            <FlatList
              data={postList} // render 될 배열
              renderItem={this._renderItem} // render 함수
              keyExtractor={(item, idx) => `post_${item.id}_${idx}`}
              showsVerticalScrollIndicator={false}
              onEndReached={_handleLoadMorePosts} // FlatList 끝에 닿으면 실행되는 함수
              onEndReachedThreshold={0}
              ListFooterComponent={this.renderFooter} // Footer 컴포넌트
              refreshing={isRefreshingPost} // 새로고침 boolean
              onRefresh={_handleRefresh} // 새로고침 함수
              initialNumToRender={3}
            />
          </SafeAreaView>
        </View>
      </View>
    );
  }
}

export default inject(({
  cat, post, helper, comment,
}) => ({
  catId: cat.selectedCatBio[0].id,
  setCatPost: comment.setCatPost,
  postList: post.postList,
  getPostList: post.getPostList,
  _handleLoadMorePosts: post._handleLoadMorePosts,
  _handleRefresh: post._handleRefresh,
  isLoadingPost: post.isLoadingPost,
  isRefreshingPost: post.isRefreshingPost,
  convertDateTime: helper.convertDateTime,
  maxPostPage: post.maxPostPage,
}))(observer(withNavigation(CatPostList)));

그리고 CatPostList에 사용된 된 state 와 function 들이 있는 PostStore다. 이 중 Infinite Scroll 과 Pull down Refresh 에 관련된 부분만 가져왔다.

import { observable, action, decorate } from 'mobx';
import axios from 'axios';
import { SERVER_URL } from 'react-native-dotenv';
import { Alert } from 'react-native';

const defaultCredential = { withCredentials: true };

class PostStore {
  constructor(root) {
    this.root = root;
  }

  postList = []; // 포스트들을 포함하는 배열

  postPage = 0; // 페이지네이션 카운트

  maxPostPage = 0; // 페이지네이션 최대 카운트

  isRefreshingPost = false; // 새로고침을 확인하는 boolean

  isLoadingPost = false; // 로딩을 확인하는 boolean

  // 탭 렌더 시 포스트를 받아오는 함수
  // axios로 catPost들을 get해서 this.info.postList 업데이트
  getPostList = async navigation => {
    try {
      const catId = this.root.cat.selectedCatBio[0].id;
      const url = `${SERVER_URL}/post/${catId}/${this.postPage}`;
      // 비동기로 요청/응답을 처리한다.
      const res = await axios.get(url);
      // 응답이 존재한다면,
      if (res) {
        // 페이지네이션 최대 카운트를 할당한다.
        this.maxPostPage = res.data.maxcount;
        // 그리고 현재 새로고침을 하는 상황이라면,
        if (this.isRefreshingPost) {
          // postList를 갱신 후 boolean을 false로 변경.
          this.postList = res.data.post;
          this.isRefreshingPost = false;
          return res;
        }
        // 로딩하는 상황이라면 concat을 통해 추가된 배열을 병합.
        this.postList = this.postList.concat(res.data.post);
        this.isLoadingPost = false;
      }
    } catch (err) {
      this.root.auth.expiredTokenHandler(err, navigation);
      console.error(error);
    }
  };

  //만약 현재 페이지가 최대값보다 작을 때는 카운트를 +1하며 getPostList 를 실행한다.
  _handleLoadMorePosts = navigation => {
    if (this.maxPostPage > this.postPage) {
      this.isLoadingPost = true;
      this.postPage += 1;
      this.getPostList(navigation);
    }
  };

  // 만약 현재 페이지 카운트를 0으로 리셋 후 getPostList 를 실행한다.
  _handleRefresh = navigation => {
    this.isRefreshingPost = true;
    this.postPage = 0;
    this.getPostList(navigation);
  };

decorate(PostStore, {
  postList: observable,
  postPage: observable,
  isRefreshingPost: observable,
  isLoadingPost: observable,
  getPostList: action,
  _handleLoadMorePosts: action,
  _handleRefresh: action,
});
export default PostStore;

지도 API를 다룰 때도 느꼈지만 이렇게 바로 눈으로 구현된 것을 확인할 수 있는 프론트 작업도 상당히 매력적인 것 같다. 특히 평상시에 자주 볼 수 있었던 기능을 실제로 구현해낼 때는 더욱 그렇다. 일단은 이번 프로젝트부터 무사히 마무리하는게 최대 목표지만, 나중에 네이티브 언어를 배워서 좀 더 최적화된 앱을 개발해보고 싶어졌다.

0개의 댓글