0%

[React Native] 무한 스크롤(infinite scroll) 만들기

이번에는 무한 스크롤(infinite scroll)를 구현합니다. “[React Native] 인스타그램 UI 만들기” 시리즈의 개발환경을 그대로 사용합니다. 그리고 아래 블로그 내용을 참고 하여 구현하였습니다.

참고: https://blog.nativebase.io/building-infinite-scroll-in-react-native-e717602553f8

 

라이브러리 설치하기

무한 스크롤을 구현하기 위해서 impagination 라이브러리를 사용합니다. impagination는 페이징 가능한 레코드의 lazy 데이터 처리 레이어입니다. impagination는 종속성이 없기 때문에, JS 환경이면 어디서나 사용가능합니다.

1
$ yarn add impagination util

 

Impagination 데이터세트 만들기

./src/components/HomeTab.js을 수정합니다.

 

먼저 impaginationimport 합니다.

1
import Dataset from 'impagination';

 

그리고 HomeTab 컴포넌트 내부에 setupImpagination 함수를 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class HomeTab extends Component
setupImpagination = () => {
let dataset = new Dataset({
pageSize: DEFAULT_LIMIT, // 한번에 가져올 레코드 갯수
observe: (datasetState) =>
// 새로운 `state`가 생성될때 마다 호출됩니다.
this.setState({ datasetState });
},
fetch(pageOffset, pageSize, stats) {
// 서버에서 데이터를 가져옵니다.
return _fetchFeeds();

});

dataset.setReadOffset(0); // Dataset 호출
this.setState({ dataset });


render() { //... }

  • Datasetfetch() 함수에 전달되는 값은 세 가지입니다.
  • pageOffset는 가져올 현재 페이지, pageSize는 한 페이지의 레코드 수, 그리고 stats입니다.
  • API에서 지원한다면, stats에는 totalPages를 저장할 수 있습니다.

 

스팀잇 피드 가져오는 API에는 pageOffset이 없으므로 아래와 같은 방법으로 조회해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class HomeTab extends Component
setupImpagination = () => {
_fetchFeeds = () =>
const { startAuthor, startPermlink } = this.state.next; // 가져올 레코드 시작 지점
return this.fetchFeeds({
tag: 'kr',
limit: DEFAULT_LIMIT + 1,
startAuthor,
startPermlink
}).then(feeds =>
let next = {
startAuthor: '',
startPermlink: '',

if(feeds.length > DEFAULT_LIMIT) {
const { author, permlink } = feeds.pop();
next = {
startAuthor: author,
startPermlink: permlink


this.setState({ next });
return feeds;
});


let dataset = new Dataset({ //... });


render() { //... }

  • 마지막 레코드에서 다음 레코드의 시작 부분을 알아야 하기 때문에, 레코드를 하나 더 가져옵니다.
  • 그리고 가져온 레코드가 DEFAULT_LIMIT 보다 크면 마지막 레코드를 pop하여 다음 레코드의 시작 지점을 알아냅니다.

 

이제 컴포넌트 constructorstate를 선언합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class HomeTab extends Component
constructor(props) {
super(props);
this.state = {
dataset: null,
datasetState: null,
next: {
startAuthor: null,
startPermlink: null,

};

setupImpagination() { //... }
render() { //... }

 

마지막으로 컴포넌트 마운트되기 시작할때, setupImpagination() 함수를 호출합니다.

1
2
3
componentWillMount() {
this.setupImpagination();

 

화면에 datasetState 출력하기

이제 ImpaginationdatasetState을 루프돌면서 CardComponent 컴포넌트를 반환하도록 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HomeTab extends Component
constructor(props) { //... }
setupImpagination() { //... }
componentWillMount() { //... }
render () {
return (
<Container style={style.container}>
<Header>{/* ... */}</Header>
<Content>
{/* ... */}

this.state.datasetState.map(record => {
const { content } = record;
return <CardComponent data={ content } key={ content.post_id }/>
})

</Content>
</Container>
)


  • 이렇게 하고 앱을 호출하면 Cannot read property 'title' of null 에러가 발생합니다. 이것은 Impagination 는 생성되자 마자 필요한 array 데이터를 생성합니다.
  • array에 포함되어 있는 record 에는 다음과 같은 속성이 있습니다. isRequested, isSettled, isPending, isResolved, isRejected

 

레코드(record)가 완전한 상태가 아니면, 스피너가 보이도록 수정합니다.

1
2
3
4
5
6
7
this.state.feeds.map(record =>
if (!record.isSettled) {
return <Spinner key= Math.random() }/>;

const { content } = record;
return <CardComponent data={ content } key={ content.post_id }/>
})

 

무한 스크롤 만들기

이제 마지막 단계입니다. 스크롤하여 다음 레코드를 자동으로 불러올 차례입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class HomeTab extends Component
constructor(props) { //... }
setupImpagination() { //... }
componentWillMount() { //... }
setCurrentReadOffset = (event) => {
let itemHeight = 402;
let currentOffset = Math.floor(event.nativeEvent.contentOffset.y);
let currentItemIndex = Math.ceil(currentOffset / itemHeight);
this.state.dataset.setReadOffset(currentItemIndex);

render () {
return (
<Container>
<Header>{/* ... */}</Header>
<Content
scrollEventThrottle={300}
onScroll={this.setCurrentReadOffset}>
{/* ... */}

this.state.datasetState.map(record => {
const { content } = record;
return <CardComponent data={ content } key={ content.post_id }/>
})

</Content>
</Container>
)


  • 스크롤 이벤트가 발생하면 setCurrentReadOffset() 함수가 호출됩니다.
  • 이벤트 호출 시간은 scrollEventThrottle 속성을 사용하여 300(ms) 로 설정하였습니다.

 

다음은 완성한 앱입니다.

 

작업한 소스코드는 모두 깃허브에 업로드 되어있습니다. 그리고 샘플앱은 expo 클라이언트를 사용하면 확인해 볼 수 있습니다.

여기까지 읽어주셔서 감사합니다.


Sponsored ( Powered by dclick )
DCLICK: 광고 기능을 소개 합니다

지난주에 dclick 에서 Advertise 기능이 오픈 되었습니다. Advertise 메뉴 …


Originally posted on http://steemit.com