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