10 글 목록을 띄워 줄 메인 페이지 구현하기 - 카테고리 목록 컴포넌트 구현하기
source: categories/study/gatsby/gatsby_9-01.md
카테고리 목록 Props로 받아 띄워주기
해당 부분에서 드디어 Props를 활용하는 코드가 등장합니다.
CategoryList 컴포넌트와 Styled Component에서 어떻게 타입을 지정하고, Props를 받아 사용하는지 알아봅시다.
먼저, 어떤 Props를 받는지 알아야합니다.
CategoryList 컴포넌트는 다음과 같이 부모 컴포넌트로부터 데이터를 받도록 구현할 것입니다.
{
"selectedCategory": "Web",
"categoryList": {
"All": 5,
"Web": 3,
"Mobile": 2
}
}
그러기 위해 CategoryList.tsx
파일을 생성한 다음, 아래와 같이 컴포넌트 Props 타입을 지정해주면 됩니다.
src/components/Main/CategoryList.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
export type CategoryListProps = {
selectedCategory: string
categoryList: {
// 프로퍼티 이름은 문자열, 프로퍼티 값은 숫자임을 나타내는 타입 표기 방법
[key: string]: number
}
}
const CategoryList: FunctionComponent<CategoryListProps> = function ({
selectedCategory,
categoryList,
}) {
return <div />
}
export default CategoryList
이제 다시 CategoryList 컴포넌트로 돌아와봅시다.
가장 먼저 저희는 카테고리를 나열하기 위한 Wrapper 컴포넌트를 정의해봅시다.
그리고, Wrapper 컴포넌트 내에 Props로 받아온 categoryList 데이터를 띄워봅시다.
카테고리 아이템 형식은 #카테고리명(게시글수)
입니다.
src/components/Main/CategoryList.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
export type CategoryListProps = {
selectedCategory: string
categoryList: {
[key: string]: number
}
}
const CategoryListWrapper = styled.div`
display: flex;
flex-wrap: wrap;
width: 768px;
margin: 100px auto 0;
`
const CategoryList: FunctionComponent<CategoryListProps> = function ({
selectedCategory,
categoryList,
}) {
return (
<CategoryListWrapper>
{Object.entries(categoryList).map(([name, count]) => (
<div key={name}>
#{name}({count})
</div>
))}
</CategoryListWrapper>
)
}
export default CategoryList
여기서 사용된 자바스크립트 표준 API인 Object.prototype.entries
메서드는 객체의 열거 가능한 속성들을 [key, value]
쌍의 값들을 가진 배열을 반환하는 기능을 가지고 있습니다.
결국 배열을 반환하기 때문에 map 메서드를 호출할 수 있는데, 배열을 매개변수로 받기 때문에 위와 같이 작성할 수 있습니다.
카테고리 아이템 컴포넌트 구현하기
저희는 나열되어있는 카테고리 아이템을 클리갛게되면 /?category=Web
과 같은 형식의 URL로 이동되도록 구현할 것입니다.
그리고 Query Parameter로 받은 category 이름과 동일한 아이템의 폰트를 더 굵게 표시해줄 것입니다.
이를 위해 앞서 사용법을 익혔던 Gatsby의 Link 컴포넌트를 기반으로 새로운 Styled Component를 만들어주겠습니다.
src/components/Main/CategoryList.tsx
import React, { FunctionComponent } from 'react'
import styled from '@emotion/styled'
import { Link } from 'gatsby'
// ...
const CategoryItem = styled(Link)`
margin-right: 20px;
padding: 5px 0;
font-size: 18px;
cursor: pointer;
&:last-of-type {
margin-right: 0;
}
`
const CategoryList: FunctionComponent<CategoryListProps> = function ({
// ...
이제 컴포넌트 내부에서 CategoryItem 컴포넌트를 불러와 적용해봅시다.
기본적으로 넘겨주는 to
라는 이름의 링크 데이터와 Query Parameter 값과 일치하는지를 나타내는 active라는 이름의 데이터를 props로 넘겨주겠습니다.
src/components/Main/CategoryList.tsx
// ...
const CategoryList: FunctionComponent<CategoryListProps> = function ({
selectedCategory,
categoryList,
}) {
return (
<CategoryListWrapper>
{Object.entries(categoryList).map(([name, count]) => (
<CategoryItem
to={`/?category=${name}`}
active={name === selectedCategory}
key={name}
>
#{name}({count})
</CategoryItem>
))}
</CategoryListWrapper>
)
}
// ...
여기까지만 작성한 상태로 페이지에 접속하면 콘솔창에 오류가 뜰 것입니다.
이는 active라는 이름의 props가 boolean 형태라서 그런데요, 단순히 이 값을 문자열 형태로 바꿔줘도 해결이 가능하지만 저희는 다른 방법으로 이 문제를 해결해봅시다.
문제는 결국 active 속성이라는 것입니다.
그럼, 이 속성을 Styled Component에서 Props로 받아서 처리만 하게끔 구현할 수는 없을까요?
이를 위해 Styled 함수의 매개변수로 Link 함수만 넘기던 것을 아래와 같이 변형할 수 있습니다.
src/components/Main/CategoryList.tsx
// ...
const CategoryItem = styled(({ active, ...props }) => (
<Link {...props} />
))`
margin-right: 20px;
padding: 5px 0;
font-size: 18px;
font-weight: ${({ active }) => (active ? '800' : '400')};
cursor: pointer;
&:last-of-type {
margin-right: 0;
}
`
// ...
active라는 키 값을 가진 프로퍼티를 제외한 나머지 프로퍼티들만 props로 Link 컴포넌트에 넘겨줌으로써 해결이 가능합니다.
하지만 파라미터 부분에서 에러가 발생하는데, 이를 위해 Props 타입을 지정해봅시다.
총 두 곳에 타입을 지정해야 하는데, 한 곳은 styled 함수의 인자 값으로 넘기는 다른 함수의 파라미터 타입, 다른 한 곳은 CategoryItem Styled Component의 Props 타입입니다.
그러나 전자에 해당하는 데이터만 알아내어 타입을 설정하면 후자의 경우에도 동일하게 적용이 가능합니다.
그럼 어떤 데이터를 Props로 받을까요?
단순히 Styled 함수 내에서 Props를 콘솔창에 출력해보면 다음과 같은 데이터를 받아오는 것을 알 수 있습니다.
- active : 현재 선택된 카테고리인지 확인하기 위한 Props
- children : 컴포넌트 내부의 요소들
- className : Styled Component를 통해 정의한 스타일을 적용하기 위한 클래스명
- to : Link 컴포넌트에 전달하기 위한 경로
이렇게 어떤 데이터를 받는지 확인했으니, 그에 대한 타입을 선언하고 적용해봅시다.
src/components/Main/CategoryList.tsx
import React, { FunctionComponent, ReactNode } from 'react'
import styled from '@emotion/styled'
import { Link } from 'gatsby'
type CategoryItemProps = {
active: boolean;
}
type GatsbyLinkProps = {
children: ReactNode;
className?: string;
to: string;
} & CategoryItemProps
// ...
여기서는 두 가지의 타입을 선언했는데요, 첫 번째는 CategoryItem
컴포넌트에서 사용할 Props
, 두 번째는 Link
컴포넌트에 전달해주기 위한 Props입니다.
원래는 GatsbyLinkProps
에 active 속성도 존재하지만 중복되는 문제도 있고, CategoryItem 컴포넌트에서는 그 외의 Props는 사용하지 않기 때문에 분리하였습니다.
따라서 &
연산자를 통해 Intersection
타입을 정의하여 두 타입을 하나로 합쳐주었습니다.
그럼 타입을 정의해주었으니, Styled Component 부분에 적용해봅시다.
src/components/Main/CategoryList.tsx
// ...
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const CategoryItem = styled(({ active, ...props }: GatsbyLinkProps) => (
<Link {...props} />
))<CategoryItemProps>`
margin-right: 20px;
padding: 5px 0;
font-size: 18px;
font-weight: ${({ active }) => (active ? '800' : '400')};
cursor: pointer;
&:last-of-type {
margin-right: 0;
}
`
// ...
여기까지 모두 끝났다면 메인 페이지에 접속해보세요.