Skip to content

Commit 95c3cf9

Browse files
committed
feat: add image list example
1 parent 8b9dc4b commit 95c3cf9

File tree

10 files changed

+340
-131
lines changed

10 files changed

+340
-131
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"classnames": "^2.3.1",
1111
"framer-motion": "^4.1.17",
1212
"gh-pages": "^3.2.3",
13+
"lodash-es": "^4.17.21",
1314
"node-sass": "^5.0.0",
1415
"react": "^17.0.2",
1516
"react-dom": "^17.0.2",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Stack, Skeleton } from '@chakra-ui/react';
2+
3+
interface Props {
4+
count?: number;
5+
height?: number;
6+
}
7+
8+
const StackSkeleton = ({ count = 5, height = 150 }: Props) => {
9+
return (
10+
<Stack>
11+
{Array.from({ length: count }).map((_, index) => (
12+
<Skeleton height={height} key={index} />
13+
))}
14+
</Stack>
15+
);
16+
};
17+
18+
export default StackSkeleton;

src/helpers/scroll.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* 무한스크롤 처리를 위한 스크롤 위치 체크
3+
*/
4+
export const checkInfiniteScrollPosition = ({ bottom = 800 }: { bottom?: number }) => {
5+
const { scrollHeight, clientHeight } = document.documentElement;
6+
const scrollTop = window.scrollY;
7+
const targetTop = scrollHeight - bottom;
8+
const currentTop = scrollTop + clientHeight;
9+
return targetTop <= currentTop;
10+
};

src/pages/ImageList/index.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,60 @@
1-
import React from 'react';
1+
import { useEffect, useState, useCallback } from 'react';
2+
import { Container, Heading, Button, Text } from '@chakra-ui/react';
3+
import StackSkleton from '../../components/StackSkeleton';
4+
import './index.scss';
5+
6+
export interface ImageListItem {
7+
id: number;
8+
title: string;
9+
thumbnailUrl: string;
10+
}
211

312
const ImageList = () => {
4-
return <div>ImageList</div>;
13+
const [list, setList] = useState<ImageListItem[]>([]);
14+
15+
const addList = useCallback(() => {
16+
fetch('https://jsonplaceholder.typicode.com/photos').then((res) => {
17+
const data = res.json();
18+
19+
data.then((newList) => {
20+
setList([...list, ...newList]);
21+
});
22+
});
23+
}, [list, setList]);
24+
25+
useEffect(() => {
26+
addList();
27+
}, []);
28+
29+
return (
30+
<>
31+
<Container padding={0} marginBottom={5} textAlign="center">
32+
<Heading size="md" mb={5} textAlign="center">
33+
react-virtualized (X)
34+
</Heading>
35+
<Button mb={5} colorScheme="blue" onClick={addList}>
36+
목록 추가하기
37+
</Button>
38+
<Text fontSize="20px" color="tomato">
39+
현재목록: {list.length}
40+
</Text>
41+
</Container>
42+
43+
<section>
44+
{list.length ? (
45+
list.map(({ title, thumbnailUrl }, index) => (
46+
<div className="text-list-item">
47+
<div>index: {index}</div>
48+
<div>title: {title}</div>
49+
<div>body: {thumbnailUrl}</div>
50+
</div>
51+
))
52+
) : (
53+
<StackSkleton count={5} />
54+
)}
55+
</section>
56+
</>
57+
);
558
};
659

760
export default ImageList;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.image-list-item {
2+
padding: 10px;
3+
margin-bottom: 10px;
4+
color: #fff;
5+
background: #011627;
6+
7+
display: flex;
8+
justify-content: flex-start;
9+
10+
.thumb-wrap {
11+
margin-right: 20px;
12+
13+
img {
14+
max-width: 150px;
15+
}
16+
}
17+
}
Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,128 @@
1-
import React from 'react';
1+
import { useEffect, useState, useCallback, useRef } from 'react';
2+
import { WindowScroller, CellMeasurer, CellMeasurerCache, AutoSizer, List, ListRowProps } from 'react-virtualized';
3+
import { checkInfiniteScrollPosition } from '../../helpers/scroll';
4+
import { throttle } from 'lodash-es';
5+
6+
import { ImageListItem } from '../ImageList';
7+
import { Container, Heading, Button, Text } from '@chakra-ui/react';
8+
import StackSkleton from '../../components/StackSkeleton';
9+
10+
import './index.scss';
11+
12+
const cellCache = new CellMeasurerCache({
13+
defaultWidth: 100,
14+
fixedWidth: true,
15+
});
16+
17+
let totalList: ImageListItem[] = [];
218

319
const ImageListVirtualized = () => {
4-
return <div>ImageListVirtualized</div>;
20+
const [list, setList] = useState<ImageListItem[]>([]);
21+
const listRef = useRef<List>(null);
22+
23+
const rowRenderer = ({ index, key, parent, style }: ListRowProps) => {
24+
return (
25+
<CellMeasurer cache={cellCache} parent={parent} key={key} columnIndex={0} rowIndex={index}>
26+
{({ measure }) => (
27+
<div style={style} key={index}>
28+
<div className="image-list-item">
29+
<section className="thumb-wrap">
30+
<img src={list[index].thumbnailUrl} alt="" onLoad={measure} />
31+
</section>
32+
<section>
33+
<p>index: {index}</p>
34+
<p>title: {list[index].title}</p>
35+
</section>
36+
</div>
37+
</div>
38+
)}
39+
</CellMeasurer>
40+
);
41+
};
42+
43+
const fetchData = useCallback(async () => {
44+
// const res = await fetch('https://jsonplaceholder.typicode.com/photos');
45+
// console.log('res :>> ', res);
46+
fetch('https://jsonplaceholder.typicode.com/photos').then((res) => {
47+
const data = res.json();
48+
49+
data.then((newList) => {
50+
totalList = newList;
51+
addList();
52+
});
53+
});
54+
}, []);
55+
56+
const addList = useCallback(() => {
57+
if (!totalList.length) {
58+
return;
59+
}
60+
61+
const data = totalList.splice(0, 100);
62+
setList([...list, ...data]);
63+
}, [list]);
64+
65+
const onScroll = useCallback(() => {
66+
const isNeedFetching = checkInfiniteScrollPosition({ bottom: 600 });
67+
if (isNeedFetching) {
68+
addList();
69+
}
70+
}, [addList]);
71+
72+
useEffect(() => {
73+
fetchData();
74+
}, [fetchData]);
75+
76+
useEffect(() => {
77+
const onScrollTrottle = throttle(onScroll, 100);
78+
window.addEventListener('scroll', onScrollTrottle);
79+
return () => window.removeEventListener('scroll', onScrollTrottle);
80+
}, [onScroll]);
81+
82+
return (
83+
<>
84+
<Container padding={0} marginBottom={5} textAlign="center">
85+
<Heading size="md" mb={5} textAlign="center">
86+
react-virtualized (O)
87+
</Heading>
88+
<Button mb={5} colorScheme="blue" onClick={addList}>
89+
목록 추가하기
90+
</Button>
91+
<Text fontSize="20px" color="tomato">
92+
현재목록: {list.length}
93+
</Text>
94+
</Container>
95+
96+
<section>
97+
{list.length ? (
98+
<WindowScroller>
99+
{({ height, scrollTop, isScrolling, onChildScroll }) => (
100+
<AutoSizer disableHeight>
101+
{({ width }) => (
102+
<List
103+
ref={listRef}
104+
autoHeight
105+
height={height}
106+
width={width}
107+
isScrolling={isScrolling}
108+
overscanRowCount={0}
109+
onScroll={onChildScroll}
110+
scrollTop={scrollTop}
111+
rowCount={list.length}
112+
rowHeight={cellCache.rowHeight}
113+
rowRenderer={rowRenderer}
114+
deferredMeasurementCache={cellCache}
115+
/>
116+
)}
117+
</AutoSizer>
118+
)}
119+
</WindowScroller>
120+
) : (
121+
<StackSkleton count={5} />
122+
)}
123+
</section>
124+
</>
125+
);
5126
};
6127

7128
export default ImageListVirtualized;

src/pages/TextList/index.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.text-list-item {
2+
padding: 10px;
3+
margin-bottom: 10px;
4+
color: #fff;
5+
background: #011627;
6+
}

src/pages/TextList/index.tsx

Lines changed: 30 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,30 @@
1-
import { useEffect, useState, useRef, useCallback } from 'react';
2-
import { WindowScroller, CellMeasurer, CellMeasurerCache, AutoSizer, List, ListRowProps } from 'react-virtualized';
1+
import { useEffect, useState, useCallback } from 'react';
32
import { Container, Heading, Button, Text } from '@chakra-ui/react';
3+
import StackSkleton from '../../components/StackSkeleton';
4+
import './index.scss';
45

5-
interface Post {
6+
export interface TextListItem {
67
id: number;
7-
userId: number;
8-
title: string;
8+
name: string;
9+
email: string;
910
body: string;
1011
}
1112

12-
const cache = new CellMeasurerCache({
13-
defaultWidth: 100,
14-
fixedWidth: true,
15-
});
16-
1713
const TextList = () => {
18-
const [posts, setPosts] = useState<Post[]>([]);
19-
const listRef = useRef<List>(null);
20-
21-
const rowRenderer = ({ index, key, parent, style }: ListRowProps) => {
22-
return (
23-
<CellMeasurer cache={cache} parent={parent} key={key} columnIndex={0} rowIndex={index}>
24-
<div style={style}>
25-
<div
26-
style={{
27-
padding: 10,
28-
marginBottom: 10,
29-
color: 'white',
30-
backgroundColor: '#282c34',
31-
}}
32-
>
33-
<div>index: {index}</div>
34-
<div>title: {posts[index].title}</div>
35-
<div>body: {posts[index].body}</div>
36-
</div>
37-
</div>
38-
</CellMeasurer>
39-
);
40-
};
14+
const [list, setList] = useState<TextListItem[]>([]);
4115

42-
const addPosts = useCallback(() => {
43-
fetch('https://jsonplaceholder.typicode.com/%3Cspan%20class="x x-first x-last">posts').then((response) => {
44-
const data = response.json();
16+
const addList = useCallback(() => {
17+
fetch('https://jsonplaceholder.typicode.com/%3Cspan%20class="x x-first x-last">comments').then((res) => {
18+
const data = res.json();
4519

46-
data.then((newPosts) => {
47-
setPosts([...posts, ...newPosts]);
20+
data.then((newList) => {
21+
setList([...list, ...newList]);
4822
});
4923
});
50-
}, [posts, setPosts]);
24+
}, [list, setList]);
5125

5226
useEffect(() => {
53-
addPosts();
27+
addList();
5428
}, []);
5529

5630
return (
@@ -59,35 +33,28 @@ const TextList = () => {
5933
<Heading size="md" mb={5} textAlign="center">
6034
react-virtualized (X)
6135
</Heading>
62-
<Button mb={5} colorScheme="blue" onClick={addPosts}>
36+
<Button mb={5} colorScheme="blue" onClick={addList}>
6337
목록 추가하기
6438
</Button>
6539
<Text fontSize="20px" color="tomato">
66-
현재목록: {posts.length}
40+
현재목록: {list.length}
6741
</Text>
6842
</Container>
69-
<WindowScroller>
70-
{({ height, scrollTop, isScrolling, onChildScroll }) => (
71-
<AutoSizer disableHeight>
72-
{({ width }) => (
73-
<List
74-
ref={listRef}
75-
autoHeight
76-
height={height}
77-
width={width}
78-
isScrolling={isScrolling}
79-
overscanRowCount={0}
80-
onScroll={onChildScroll}
81-
scrollTop={scrollTop}
82-
rowCount={posts.length}
83-
rowHeight={cache.rowHeight}
84-
rowRenderer={rowRenderer}
85-
deferredMeasurementCache={cache}
86-
/>
87-
)}
88-
</AutoSizer>
43+
44+
<section>
45+
{list.length ? (
46+
list.map(({ name, email, body }, index) => (
47+
<div className="text-list-item" key={index}>
48+
<p>index: {index}</p>
49+
<p>email: {email}</p>
50+
<p>name: {name}</p>
51+
<p>body: {body}</p>
52+
</div>
53+
))
54+
) : (
55+
<StackSkleton count={5} />
8956
)}
90-
</WindowScroller>
57+
</section>
9158
</>
9259
);
9360
};

0 commit comments

Comments
 (0)