Skip to content

Commit

Permalink
feat: "Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기" 포스팅
Browse files Browse the repository at this point in the history
  • Loading branch information
kang-kibong committed Sep 12, 2024
1 parent d69dccd commit 6ac9257
Show file tree
Hide file tree
Showing 9 changed files with 966 additions and 672 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default function PrivateRoute() {

<br />

![스크린샷 2024-07-09 오후 3.43.31.png](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F07b05441-80a1-40ee-905f-1f48f33198d9%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-07-09_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.43.31.png?id=16af2b38-fe81-4e65-9abe-fce08c84d9b3&table=block)|![스크린샷 2024-07-09 오후 3.43.46.png](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fe0be7e07-9819-452e-a057-9c79c6ea5d76%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-07-09_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.43.46.png?id=8a7d5a06-46c4-4891-a66c-30b409738f3e&table=block)
![스크린샷 2024-07-09 오후 3.43.31.png](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F07b05441-80a1-40ee-905f-1f48f33198d9%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-07-09_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.43.31.png?id=ac17bd95-d4e2-40ee-a957-fa62b09b7847&table=block)|![스크린샷 2024-07-09 오후 3.43.46.png](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fe0be7e07-9819-452e-a057-9c79c6ea5d76%2F%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2024-07-09_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.43.46.png?id=046f9ea9-8f70-4702-bce3-f084bae7d555&table=block)

이전에는 maxWidth에 대한 값들을 `assets/variants`라는 파일에서 관리하고 있었다.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
---
layout: post
title: "Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기"
category: frontend
---

<br />
<br />
<br />

![image.png](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F81a04c70-d4c3-4ff4-95a2-594f9296fa7a%2Fimage.png?id=89b54ddf-81d3-48ed-822a-db6cc91edb05&table=block)

<br />

기능을 구현하다 보면 반복되는 요소를 컴포넌트로 만들 때, 해당 컴포넌트 내부의 일부 요소들이 변동될 가능성이 생긴다.

특히 특정 데이터가 내부의 각 요소(텍스트, 버튼, 이미지 등)를 동적으로 변화해야 하는 경우, 어떻게 하면 유연성을 높이고 변경 사항에 쉽게 대응할 수 있을지 고민할 수 있다.

이번에는 Compound 패턴을 통해 컴포넌트의 유연성을 높이고 복잡한 요구 사항에 쉽게 대응할 수 있는 방법을 설명하고자 한다.

쉬운 이해를 위해 **PostCard** 컴포넌트를 구현하는 예시로, context와 하위 컴포넌트를 통해 어떻게 하면 데이터를 효율적으로 관리하고, 컴포넌트를 유연하게 구성하는지 살펴보도록 하자.

<br />
<br />
<br />

# props로 분기 처리

초기에는 컴포넌트에 여러 `props`를 전달하고, 그에 따라 내부에서 조건부로 분기처리하여 렌더링할 요소를 결정하려 했다.

다음은 `PostCard` 컴포넌트에서 각 게시물마다 제목, 내용, 사용자 정보가 다를 때, 이 데이터를 `props`로 받아 내부에서 조건에 따라 UI를 변화시키도록 한 예시이다.

<br />

```tsx
type PostType = {
id: number;
title: string;
content: string;
user?: {
id: number;
name: string;
};
};

interface Props {
post: PostType;
}

const PostCard = ({ post }: Props) => {
return (
<div>
<h2>{post.title}</h2>
<p>{post.content}</p>
{post.user && <span>{post.user.name}</span>}
<div>
<Button theme="primary">Read More</Button>
<Button theme="default">Comments</Button>
</div>
</div>
);
};

export default PostCard;
```

<br />

이 방식은 작은 프로젝트에서는 효율적일 수 있지만, 프로젝트가 커질수록 요구 사항이 바뀌거나 새로운 UI 요소가 추가될 때마다 컴포넌트 내부에 조건문이 많아지고 계속해서 수정해야 하는 불편함이 생기곤 한다.

이로 인해 추후에는 코드의 가독성과 유지보수성이 떨어뜨릴 수 있는 문제가 발생할 수 있다.

<br />
<br />
<br />

# Compound 패턴의 필요성

Compound 패턴이란 하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담하여 구성하는 패턴을 의미한다.

즉, 부모 컴포넌트는 UI의 기본 구조와 데이터를 제공하는 컨테이너로서의 역할을 하고, 자식 컴포넌트는 특정 부분의 동작만 담당하도록 역할을 구성하는 것을 말한다.

이러한 방식으로 작성하게 되면, 부모와 자식 간의 관계가 더욱 명확해지고, 자식 컴포넌트의 역할을 유연하게 커스터마이징할 수 있는 장점이 있다.

<br />
<br />
<br />

# Compound 패턴을 적용한 PostCard 컴포넌트

**PostCard** 컴포넌트를 Compound 패턴으로 리팩터링하여, 내부의 변동 요소를 쉽게 관리할 수 있도록 해보자.

이를 위해, **Context API**를 사용하면, 데이터를 부모 컴포넌트로 부터 전역적으로 관리하고, 자식 컴포넌트가 부모로부터 필요한 데이터를 쉽게 전달받을 수 있도록 구성할 수 있다.

<br />

## 1. PostCardContext 구현

```tsx
// PostCardContext.tsx

import { createContext, ReactNode, useContext } from "react";
import { PostType } from "./PostCard";

type PostCardContextProps = {
post: PostType;
};

const PostCardContext = createContext<PostCardContextProps | undefined>(
undefined
);

export const usePostCardContext = () => {
const context = useContext(PostCardContext);
if (!context)
throw new Error(
"usePostCardContext must be used within a PostCardContext.Provider"
);
return context;
};

interface ProviderProps {
post: PostType;
children: ReactNode;
}

export const PostCardContextProvider = ({ post, children }: ProviderProps) => {
return (
<PostCardContext.Provider value={{ post }}>
{children}
</PostCardContext.Provider>
);
};
```

<br />

부모 컴포넌트에서 게시물 데이터를 받아 자식 컴포넌트에 전달할 수 있도록 `PostCardContext` 를 생성한다.

이후, 자식 컴포넌트가 해당 게시물 데이터를 받아올 수 있도록 **usePostCardContext** 커스텀 훅을 구현한다.

마지막으로 `Provider`를 통해 하위 컴포넌트에 게시물 데이터를 공유할 수 있도록 **PostCardContextProvider** 컴포넌트를 구현한다.

<br />

## 2. 하위 컴포넌트 구현

이제 Compound 패턴의 핵심인 독립적인 하위 컴포넌트를 구현해야한다.

`Title`, `User`, `Actions` 하위 컴포넌트를 각각 구성하고, `usePostCardContext`를 통해 `PostCardContext`로부터 필요한 게시물 데이터를 받아 렌더링을 수행하도록 한다.

<br />

```tsx
// components/index.tsx

import { usePostCardContext } from "../PostCardContext";
import { Button } from "@sparklon/react";

export const Title = () => {
const { post } = usePostCardContext();
return <h2>{post.title}</h2>;
};

export const User = () => {
const { post } = usePostCardContext();
return <h2>{post.user.name}</h2>;
};

export const Actions = () => {
return (
<div>
<Button theme="primary">Read More</Button>
<Button theme="default">Comments</Button>
</div>
);
};
```

<br />

위와 같이 작성하게 되면 부모 컴포넌트에서 전달된 데이터를 필요에 따라 쉽게 사용할 수 있으며, 추후에 변경 사항이 있을때 각 하위 컴포넌트만 수정하면 되기 때문에 유지보수성이 높아진다.

<br />

## 3. PostCard 컴포넌트

마지막으로 게시물 데이터를 `PostCardContextProvider`로 전달하고, 하위 컴포넌트가 해당 데이터를 활용할 수 있도록 돕는 `PostCard` 컴포넌트를 구현해보자.

<br />

```tsx
import { ReactNode } from "react";
import { PostCardContextProvider } from "./PostCardContext";
import { Title, User, Actions } from "./components";

export type PostType = {
id: number;
title: string;
content: string;
user?: {
id: number;
name: string;
};
};

interface Props {
post: PostType;
children: ReactNode;
}

const PostCard = ({ post, children }: Props) => {
return (
<PostCardContextProvider post={post}>
<div>{children}</div>
</PostCardContextProvider>
);
};

export default PostCard;

PostCard.Title = Title;
PostCard.User = User;
PostCard.Actions = Actions;
```

<br />

게시물 데이터를 `PostCardContextProvider`를 통해 하위 컴포넌트로 전달하고, children을 통해 하위 컴포넌트들을 렌더링할 수 있도록 래퍼 컴포넌트 형태로 구성한다.

이후, Compound 패턴에 맞게 각 하위 컴포넌트들을 `PostCard` 컴포넌트에 설정한다.

<br />
<br />
<br />

# 사용 예시

이제 `PostCard` 컴포넌트를 활용하여 각 게시물의 정보를 유연하게 렌더링할 수 있다.

부모 컴포넌트에서 특정 데이터를 `props`로 전달하면, context를 통해 해당 데이터가 하위 컴포넌트로 자동으로 전달된다.

하위 컴포넌트는 context로부터 필요한 데이터를 가져와 독립적으로 동작하게 되고, `props`로 별도의 데이터를 전달할 필요가 없게 된다.

이로써, 각각의 컴포넌트의 배치와 역할을 유연하게 조정할 수 있으며, 추후에 발생하는 변경 사항에 쉽게 대처할 수 있는 구조를 갖출 수 있게 되었다.

<br />

```tsx
import PostCard from "./PostCard";

const post = {
id: 1,
title: "How to Use Compound Pattern",
content: "This is an example of how to implement compound components.",
user: {
id: 1,
name: "John Doe",
},
};

const App = () => {
return (
<PostCard post={post}>
<PostCard.User />
<PostCard.Title />
<PostCard.Buttons />
</PostCard>
);
};

export default App;
```

<br />
<br />
<br />

# 마치면서

이번 글에서는 `PostCard` 컴포넌트를 예시로 **Compound 패턴**에 대해 자세히 알아보았다.

Compound 패턴을 사용하면 컴포넌트 내부 요소들이 **동적**으로 변화할 때 부모와 자식 컴포넌트 간의 **데이터 흐름**을 명확하게 관리할 수 있고, 각 하위 컴포넌트를 **독립적으로** **관리**할 수 있다는 장점을 알게 되었다.

이를 통해, 추후 발생될 수 있는 변동 사항에 대해 유연하게 대처할 수 있다는 것 또한 확인할 수 있었다.

따라서, 독자들도 유사한 상황에 처해 있거나, 컴포넌트의 유연성과 재사용성을 높일 필요성이 있다면, **Compound 패턴** 도입에 대해 고려해보면 좋을 것 같다.
5 changes: 5 additions & 0 deletions _site/archive.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ <h1 class="title">Hi, I’m <strong>Kang Byeong-hyeon</strong>.</h1>
</header>
</a>
<div class="w"><a href="/">..</a><h1>All Posts</h1><ul><li>
<span>2024-08-12</span>
<a href="/compound-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD-%EC%9C%A0%EC%97%B0%ED%95%98%EA%B2%8C-%EB%8C%80%EC%B2%98%ED%95%98%EA%B8%B0.html" class="post-item"
>Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기</a
>
</li><li>
<span>2024-07-28</span>
<a href="/%EC%9A%B0%EC%84%A0-%EC%9A%B0%EC%84%A0%EC%9D%84-%EC%A4%91%EB%8B%A8%ED%95%98%EB%A9%B0.html" class="post-item"
>[우선] 우선을 중단하며</a
Expand Down
Loading

0 comments on commit 6ac9257

Please sign in to comment.