15 메인 페이지에 실제 데이터 띄워보기 - 메인 페이지에서 Props로 받아 포스트 데이터 출력하기
source: categories/study/gatsby/gatsby_9-06.md
페이지 컴포넌트에서 쿼리 작성하기
시작하기에 앞서, 최소한 2개의 포스트 아이템을 띄워주기 위해 저번에 작성한 마크다운 파일을 그대로 복사 붙여넣기 해줍시다.
그리고 적절한 사이즈의 사진을 contents 디렉토리에 test.png
이름으로 저장해주세요.
페이지 컴포넌트에서 쿼리를 작성하는 방법은 GraphQL Query 알아보기 챕터에서 이미 다룬 내용이기 때문에 방법이 잘 기억나지 않는 경우에는 한 번 더 읽어보고 오시는 것을 추천드립니다.
이제 필요한 데이터를 받아오는 쿼리를 작성해봅시다.
저희가 필요한 마크다운 파일 데이터는 제목, 날짜, 요약, 카테고리, 썸네일 이미지입니다.
그럼 필요한 데이터를 받아오기 위한 쿼리를 다음과 같이 작성해줍시다.
src/pages/index.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
import GlobalStyle from 'components/Common/GlobalStyle'
import Footer from 'components/Common/Footer'
import CategoryList from 'components/Main/CategoryList'
import Introduction from 'components/Main/Introduction'
import PostList from 'components/Main/PostList'
import { graphql } from 'gatsby'
const CATEGORY_LIST = {
All: 5,
Web: 3,
Mobile: 2,
}
const Container = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`
const IndexPage: FunctionComponent = function () {
return (
<Container>
<GlobalStyle />
<Introduction />
<CategoryList selectedCategory="Web" categoryList={CATEGORY_LIST} />
<PostList />
<Footer />
</Container>
)
}
export default IndexPage
export const getPostList = graphql`
query getPostList {
allMarkdownRemark(
sort: { order: DESC, fields: [frontmatter___date, frontmatter___title] }
) {
edges {
node {
id
frontmatter {
title
summary
date(formatString: "YYYY.MM.DD.")
categories
thumbnail {
publicURL
}
}
}
}
}
}
`
기본적으로 데이터는 날짜, 제목을 기준으로 내림차순 정렬이 되어있으며, 날짜는 2021.02.10.의 형태로 불러오도록 지정했습니다.
쿼리를 통해 받은 데이터는 data라는 이름의 Props에 담겨있습니다.
따라서 각 파일의 데이터가 들어있는 배열인 edges
를 받아오기 위해 다음과 같이 작성할 수 있습니다.
src/pages/index.tsx
// ...
const IndexPage: FunctionComponent = function ({
data: {
allMarkdownRemark: { edges },
},
}) {
// ...
}
이렇게 Props를 받았으니 그에 대한 타입을 선언해야 합니다.
데이터 형식은 쿼리문을 그대로 따라가기 때문에 다음과 같이 작성해줄 수 있습니다.
src/pages/index.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
import GlobalStyle from 'components/Common/GlobalStyle'
import Footer from 'components/Common/Footer'
import CategoryList from 'components/Main/CategoryList'
import Introduction from 'components/Main/Introduction'
import PostList from 'components/Main/PostList'
import { graphql } from 'gatsby'
type IndexPageProps = {
data: {
allMarkdownRemark: {
edges: [
{
node: {
id: string
frontmatter: {
title: string
summary: string
date: string
categories: string[]
thumbnail: {
publicURL: string
}
}
}
},
]
}
}
}
// ...
const IndexPage: FunctionComponent<IndexPageProps> = function ({
// ...
하지만 node 프로퍼티부터는 PostList 컴포넌트에서도 데이터 형식을 그대로 사용합니다.
따라서 해당 부분은 PostType이라는 이름으로 PostList 컴포넌트로 옮겨주겠습니다.
또, 이제 더미데이터는 사용하지 않기 때문에 지워주겠습니다.
src/components/Main/PostList.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
import PostItem from 'components/Main/PostItem'
export type PostType = {
node: {
id: string
frontmatter: {
title: string
summary: string
date: string
categories: string[]
thumbnail: {
publicURL: string
}
}
}
}
const PostListWrapper = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
width: 768px;
margin: 0 auto;
padding: 50px 0 100px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
width: 100%;
padding: 50px 20px;
}
`
const PostList: FunctionComponent = function () {
return <PostListWrapper></PostListWrapper>;
}
export default PostList
이에 따라 메인 페이지 컴포넌트에서는 아래와 같이 코드가 변경됩니다.
src/pages/index.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
import GlobalStyle from 'components/Common/GlobalStyle'
import Footer from 'components/Common/Footer'
import CategoryList from 'components/Main/CategoryList'
import Introduction from 'components/Main/Introduction'
import PostList, { PostType } from 'components/Main/PostList'
import { graphql } from 'gatsby'
type IndexPageProps = {
data: {
allMarkdownRemark: {
edges: PostType[]
}
}
}
// ...
이렇게 받은 데이터는 posts라는 이름으로 PostList 컴포넌트에 Props로 넘겨주겠습니다.
src/pages/index.tsx
// ...
const IndexPage: FunctionComponent<IndexPageProps> = function ({
data: {
allMarkdownRemark: { edges },
},
}) {
return (
<Container>
<GlobalStyle />
<Introduction />
<CategoryList selectedCategory="Web" categoryList={CATEGORY_LIST} />
<PostList posts={edges} />
<Footer />
</Container>
)
}
// ...
PostList 컴포넌트에서 PostItem 컴포넌트로 Props 넘겨주기
Props를 받기에 앞서, 타입부터 정의해주겠습니다.
메인 페이지 컴포넌트에서는 edges
프로퍼티에 대해 타입을 PostType[]
으로 지정했기 때문에 PostList 컴포넌트에서도 그대로 사용할 수 있습니다.
src/components/Main/PostList.tsx
// ...
export type PostType = {
node: {
id: string
frontmatter: {
title: string
summary: string
date: string
categories: string[]
thumbnail: {
publicURL: string
}
}
}
}
type PostListProps = {
posts: PostType[]
}
// ...
const PostList: FunctionComponent<PostListProps> = function ({
posts,
}) {
// ...
}
그럼 이제 map 메서드를 통해 posts 배열에 있는 값을 PostItem 컴포넌트에 넘겨주겠습니다.
다음과 같이 코드를 수정해주세요.
src/components/Main/PostList.tsx
// ...
const PostList: FunctionComponent<PostListProps> = function ({ posts }) {
return (
<PostListWrapper>
{posts.map(
({
node: { id, frontmatter },
}: PostType) => (
<PostItem
{...frontmatter}
link="https://www.google.co.kr/"
key={id}
/>
),
)}
</PostListWrapper>
)
}
export default PostList
src/components/Main/PostItem.tsx
// ...
type PostItemProps = {
title: string
date: string
categories: string[]
summary: string
thumbnail: {
publicURL: string
}
link: string
}
// ...
const PostItem: FunctionComponent<PostItemProps> = function ({
title,
date,
categories,
summary,
thumbnail: { publicURL },
link,
}) {
return (
<PostItemWrapper to={link}>
<ThumbnailImage src={publicURL} alt="Post Item Image" />
<PostItemContent>
<Title>{title}</Title>
<Date>{date}</Date>
<Category>
{categories.map(category => (
<CategoryItem key={category}>{category}</CategoryItem>
))}
</Category>
<Summary>{summary}</Summary>
</PostItemContent>
</PostItemWrapper>
)
}
export default PostItem
여기까지 한 후, 2개의 포스트 아이템이 화면에 잘 출력되는지 확인해주세요.
중복되는 타입 분리하기
그런데 여기서 PostItem 컴포넌트의 Props 타입과 PostList 컴포넌트의 PostListItemType 타입 중 중복되는 부분이 존재하는데 보이시나요?
바로 PostListItemType 타입 중, frontmatter 프로퍼티에 해당하는 값이 중복됩니다.
중복을 피하기 위해 src/types/PostItem.types.ts
파일을 생성하고 아래와 같이 내용을 입력해주세요.
src/types/PostItem.types.ts
export type PostFrontmatterType = {
title: string
date: string
categories: string[]
summary: string
thumbnail: {
publicURL: string
}
}
export type PostListItemType = {
node: {
id: string
frontmatter: PostFrontmatterType
}
}
이렇게 생성한 타입을 PostList 컴포넌트에서 불러와 아래와 같이 적용해봅시다.
src/components/Main/PostList.tsx
// ...
import { PostListItemType } from 'types/PostItem.types'
// 기존에 정의했던 PostListItemType 삭제
type PostListProps = {
posts: PostListItemType[]
}
// ...
그리고 PostItem 컴포넌트도 아래와 같이 변경해줍니다.
src/components/Main/PostItem.tsx
// ...
import { PostFrontmatterType } from 'types/PostItem.types'
type PostItemProps = PostFrontmatterType & { link: string }
// ...
마지막으로 IndexPage 컴포넌트에도 변경 사항을 반영해줍시다.
src/pages/index.tsx
// ...
import { PostListItemType } from 'types/PostItem.types'
type IndexPageProps = {
data: {
allMarkdownRemark: {
edges: PostListItemType[]
}
}
}
// ...