From 6ac925760bc5ad38f39f2def4ddca34b0382891c Mon Sep 17 00:00:00 2001 From: kangbyeonghyeon Date: Thu, 12 Sep 2024 21:03:31 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20"Compound=20=ED=8C=A8=ED=84=B4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=82=AC=ED=95=AD=20=EC=9C=A0=EC=97=B0=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=8C=80=EC=B2=98=ED=95=98=EA=B8=B0"=20=ED=8F=AC?= =?UTF-8?q?=EC=8A=A4=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...1 \354\247\204\355\226\211\352\270\260.md" | 2 +- ...00\354\262\230\355\225\230\352\270\260.md" | 287 ++++++ _site/archive.html | 5 + ...\354\262\230\355\225\230\352\270\260.html" | 372 +++++++ _site/feed.xml | 944 ++++++------------ _site/frontend.html | 5 + _site/index.html | 15 +- _site/sitemap.xml | 4 + ...\354\247\204\355\226\211\352\270\260.html" | 4 +- 9 files changed, 966 insertions(+), 672 deletions(-) create mode 100644 "_posts/2024-08-12-Compound \355\214\250\355\204\264\354\234\274\353\241\234 \354\273\264\355\217\254\353\204\214\355\212\270 \353\263\200\352\262\275\354\202\254\355\225\255 \354\234\240\354\227\260\355\225\230\352\262\214 \353\214\200\354\262\230\355\225\230\352\270\260.md" create mode 100644 "_site/compound-\355\214\250\355\204\264\354\234\274\353\241\234-\354\273\264\355\217\254\353\204\214\355\212\270-\353\263\200\352\262\275\354\202\254\355\225\255-\354\234\240\354\227\260\355\225\230\352\262\214-\353\214\200\354\262\230\355\225\230\352\270\260.html" diff --git "a/_posts/2024-07-15-[\354\271\264\354\271\264\354\230\244 \355\205\214\355\201\254 \354\272\240\355\215\274\354\212\244 2\352\270\260] 1\354\260\250 \354\275\224\353\223\234\353\246\254\353\267\260 \353\260\230\354\230\201 \353\260\217 \353\246\254\355\214\251\355\206\240\353\247\201 \354\247\204\355\226\211\352\270\260.md" "b/_posts/2024-07-15-[\354\271\264\354\271\264\354\230\244 \355\205\214\355\201\254 \354\272\240\355\215\274\354\212\244 2\352\270\260] 1\354\260\250 \354\275\224\353\223\234\353\246\254\353\267\260 \353\260\230\354\230\201 \353\260\217 \353\246\254\355\214\251\355\206\240\353\247\201 \354\247\204\355\226\211\352\270\260.md" index 426a692..e669f53 100644 --- "a/_posts/2024-07-15-[\354\271\264\354\271\264\354\230\244 \355\205\214\355\201\254 \354\272\240\355\215\274\354\212\244 2\352\270\260] 1\354\260\250 \354\275\224\353\223\234\353\246\254\353\267\260 \353\260\230\354\230\201 \353\260\217 \353\246\254\355\214\251\355\206\240\353\247\201 \354\247\204\355\226\211\352\270\260.md" +++ "b/_posts/2024-07-15-[\354\271\264\354\271\264\354\230\244 \355\205\214\355\201\254 \354\272\240\355\215\274\354\212\244 2\352\270\260] 1\354\260\250 \354\275\224\353\223\234\353\246\254\353\267\260 \353\260\230\354\230\201 \353\260\217 \353\246\254\355\214\251\355\206\240\353\247\201 \354\247\204\355\226\211\352\270\260.md" @@ -158,7 +158,7 @@ export default function PrivateRoute() {
-![스크린샷 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`라는 파일에서 관리하고 있었다. diff --git "a/_posts/2024-08-12-Compound \355\214\250\355\204\264\354\234\274\353\241\234 \354\273\264\355\217\254\353\204\214\355\212\270 \353\263\200\352\262\275\354\202\254\355\225\255 \354\234\240\354\227\260\355\225\230\352\262\214 \353\214\200\354\262\230\355\225\230\352\270\260.md" "b/_posts/2024-08-12-Compound \355\214\250\355\204\264\354\234\274\353\241\234 \354\273\264\355\217\254\353\204\214\355\212\270 \353\263\200\352\262\275\354\202\254\355\225\255 \354\234\240\354\227\260\355\225\230\352\262\214 \353\214\200\354\262\230\355\225\230\352\270\260.md" new file mode 100644 index 0000000..e5e0a7d --- /dev/null +++ "b/_posts/2024-08-12-Compound \355\214\250\355\204\264\354\234\274\353\241\234 \354\273\264\355\217\254\353\204\214\355\212\270 \353\263\200\352\262\275\354\202\254\355\225\255 \354\234\240\354\227\260\355\225\230\352\262\214 \353\214\200\354\262\230\355\225\230\352\270\260.md" @@ -0,0 +1,287 @@ +--- +layout: post +title: "Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기" +category: frontend +--- + +
+
+
+ +![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) + +
+ +기능을 구현하다 보면 반복되는 요소를 컴포넌트로 만들 때, 해당 컴포넌트 내부의 일부 요소들이 변동될 가능성이 생긴다. + +특히 특정 데이터가 내부의 각 요소(텍스트, 버튼, 이미지 등)를 동적으로 변화해야 하는 경우, 어떻게 하면 유연성을 높이고 변경 사항에 쉽게 대응할 수 있을지 고민할 수 있다. + +이번에는 Compound 패턴을 통해 컴포넌트의 유연성을 높이고 복잡한 요구 사항에 쉽게 대응할 수 있는 방법을 설명하고자 한다. + +쉬운 이해를 위해 **PostCard** 컴포넌트를 구현하는 예시로, context와 하위 컴포넌트를 통해 어떻게 하면 데이터를 효율적으로 관리하고, 컴포넌트를 유연하게 구성하는지 살펴보도록 하자. + +
+
+
+ +# props로 분기 처리 + +초기에는 컴포넌트에 여러 `props`를 전달하고, 그에 따라 내부에서 조건부로 분기처리하여 렌더링할 요소를 결정하려 했다. + +다음은 `PostCard` 컴포넌트에서 각 게시물마다 제목, 내용, 사용자 정보가 다를 때, 이 데이터를 `props`로 받아 내부에서 조건에 따라 UI를 변화시키도록 한 예시이다. + +
+ +```tsx +type PostType = { + id: number; + title: string; + content: string; + user?: { + id: number; + name: string; + }; +}; + +interface Props { + post: PostType; +} + +const PostCard = ({ post }: Props) => { + return ( +
+

{post.title}

+

{post.content}

+ {post.user && {post.user.name}} +
+ + +
+
+ ); +}; + +export default PostCard; +``` + +
+ +이 방식은 작은 프로젝트에서는 효율적일 수 있지만, 프로젝트가 커질수록 요구 사항이 바뀌거나 새로운 UI 요소가 추가될 때마다 컴포넌트 내부에 조건문이 많아지고 계속해서 수정해야 하는 불편함이 생기곤 한다. + +이로 인해 추후에는 코드의 가독성과 유지보수성이 떨어뜨릴 수 있는 문제가 발생할 수 있다. + +
+
+
+ +# Compound 패턴의 필요성 + +Compound 패턴이란 하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담하여 구성하는 패턴을 의미한다. + +즉, 부모 컴포넌트는 UI의 기본 구조와 데이터를 제공하는 컨테이너로서의 역할을 하고, 자식 컴포넌트는 특정 부분의 동작만 담당하도록 역할을 구성하는 것을 말한다. + +이러한 방식으로 작성하게 되면, 부모와 자식 간의 관계가 더욱 명확해지고, 자식 컴포넌트의 역할을 유연하게 커스터마이징할 수 있는 장점이 있다. + +
+
+
+ +# Compound 패턴을 적용한 PostCard 컴포넌트 + +**PostCard** 컴포넌트를 Compound 패턴으로 리팩터링하여, 내부의 변동 요소를 쉽게 관리할 수 있도록 해보자. + +이를 위해, **Context API**를 사용하면, 데이터를 부모 컴포넌트로 부터 전역적으로 관리하고, 자식 컴포넌트가 부모로부터 필요한 데이터를 쉽게 전달받을 수 있도록 구성할 수 있다. + +
+ +## 1. PostCardContext 구현 + +```tsx +// PostCardContext.tsx + +import { createContext, ReactNode, useContext } from "react"; +import { PostType } from "./PostCard"; + +type PostCardContextProps = { + post: PostType; +}; + +const PostCardContext = createContext( + 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 ( + + {children} + + ); +}; +``` + +
+ +부모 컴포넌트에서 게시물 데이터를 받아 자식 컴포넌트에 전달할 수 있도록 `PostCardContext` 를 생성한다. + +이후, 자식 컴포넌트가 해당 게시물 데이터를 받아올 수 있도록 **usePostCardContext** 커스텀 훅을 구현한다. + +마지막으로 `Provider`를 통해 하위 컴포넌트에 게시물 데이터를 공유할 수 있도록 **PostCardContextProvider** 컴포넌트를 구현한다. + +
+ +## 2. 하위 컴포넌트 구현 + +이제 Compound 패턴의 핵심인 독립적인 하위 컴포넌트를 구현해야한다. + +`Title`, `User`, `Actions` 하위 컴포넌트를 각각 구성하고, `usePostCardContext`를 통해 `PostCardContext`로부터 필요한 게시물 데이터를 받아 렌더링을 수행하도록 한다. + +
+ +```tsx +// components/index.tsx + +import { usePostCardContext } from "../PostCardContext"; +import { Button } from "@sparklon/react"; + +export const Title = () => { + const { post } = usePostCardContext(); + return

{post.title}

; +}; + +export const User = () => { + const { post } = usePostCardContext(); + return

{post.user.name}

; +}; + +export const Actions = () => { + return ( +
+ + +
+ ); +}; +``` + +
+ +위와 같이 작성하게 되면 부모 컴포넌트에서 전달된 데이터를 필요에 따라 쉽게 사용할 수 있으며, 추후에 변경 사항이 있을때 각 하위 컴포넌트만 수정하면 되기 때문에 유지보수성이 높아진다. + +
+ +## 3. PostCard 컴포넌트 + +마지막으로 게시물 데이터를 `PostCardContextProvider`로 전달하고, 하위 컴포넌트가 해당 데이터를 활용할 수 있도록 돕는 `PostCard` 컴포넌트를 구현해보자. + +
+ +```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 ( + +
{children}
+
+ ); +}; + +export default PostCard; + +PostCard.Title = Title; +PostCard.User = User; +PostCard.Actions = Actions; +``` + +
+ +게시물 데이터를 `PostCardContextProvider`를 통해 하위 컴포넌트로 전달하고, children을 통해 하위 컴포넌트들을 렌더링할 수 있도록 래퍼 컴포넌트 형태로 구성한다. + +이후, Compound 패턴에 맞게 각 하위 컴포넌트들을 `PostCard` 컴포넌트에 설정한다. + +
+
+
+ +# 사용 예시 + +이제 `PostCard` 컴포넌트를 활용하여 각 게시물의 정보를 유연하게 렌더링할 수 있다. + +부모 컴포넌트에서 특정 데이터를 `props`로 전달하면, context를 통해 해당 데이터가 하위 컴포넌트로 자동으로 전달된다. + +하위 컴포넌트는 context로부터 필요한 데이터를 가져와 독립적으로 동작하게 되고, `props`로 별도의 데이터를 전달할 필요가 없게 된다. + +이로써, 각각의 컴포넌트의 배치와 역할을 유연하게 조정할 수 있으며, 추후에 발생하는 변경 사항에 쉽게 대처할 수 있는 구조를 갖출 수 있게 되었다. + +
+ +```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 ( + + + + + + ); +}; + +export default App; +``` + +
+
+
+ +# 마치면서 + +이번 글에서는 `PostCard` 컴포넌트를 예시로 **Compound 패턴**에 대해 자세히 알아보았다. + +Compound 패턴을 사용하면 컴포넌트 내부 요소들이 **동적**으로 변화할 때 부모와 자식 컴포넌트 간의 **데이터 흐름**을 명확하게 관리할 수 있고, 각 하위 컴포넌트를 **독립적으로** **관리**할 수 있다는 장점을 알게 되었다. + +이를 통해, 추후 발생될 수 있는 변동 사항에 대해 유연하게 대처할 수 있다는 것 또한 확인할 수 있었다. + +따라서, 독자들도 유사한 상황에 처해 있거나, 컴포넌트의 유연성과 재사용성을 높일 필요성이 있다면, **Compound 패턴** 도입에 대해 고려해보면 좋을 것 같다. diff --git a/_site/archive.html b/_site/archive.html index 3780cb9..ea1153b 100644 --- a/_site/archive.html +++ b/_site/archive.html @@ -79,6 +79,11 @@

Hi, I’m Kang Byeong-hyeon.

..

All Posts

  • + 2024-08-12 + Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기 +
  • 2024-07-28 [우선] 우선을 중단하며 + + + + + + Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +

    Hi, I’m Kang Byeong-hyeon.

    +
    +
    +
    .. + +

    Compound 패턴으로 컴포넌트 변경사항 유연하게 대처하기

    + +


    +
    +

    + +

    image.png

    + +


    + +

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

    + +

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

    + +

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

    + +

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

    + +


    +
    +

    + +

    props로 분기 처리

    + +

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

    + +

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

    + +


    + +
    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;
    +
    + +


    + +

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

    + +

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

    + +


    +
    +

    + +

    Compound 패턴의 필요성

    + +

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

    + +

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

    + +

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

    + +


    +
    +

    + +

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

    + +

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

    + +

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

    + +


    + +

    1. PostCardContext 구현

    + +
    // 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=>
    +      {children}
    +    </PostCardContext.Provider>
    +  );
    +};
    +
    + +


    + +

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

    + +

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

    + +

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

    + +


    + +

    2. 하위 컴포넌트 구현

    + +

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

    + +

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

    + +


    + +
    // 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>
    +  );
    +};
    +
    + +


    + +

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

    + +


    + +

    3. PostCard 컴포넌트

    + +

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

    + +


    + +
    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;
    +
    + +


    + +

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

    + +

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

    + +


    +
    +

    + +

    사용 예시

    + +

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

    + +

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

    + +

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

    + +

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

    + +


    + +
    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;
    +
    + +


    +
    +

    + +

    마치면서

    + +

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

    + +

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

    + +

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

    + +

    따라서, 독자들도 유사한 상황에 처해 있거나, 컴포넌트의 유연성과 재사용성을 높일 필요성이 있다면, Compound 패턴 도입에 대해 고려해보면 좋을 것 같다.

    + +
    + +
    + + \ No newline at end of file diff --git a/_site/feed.xml b/_site/feed.xml index 79ea246..37d8ed8 100644 --- a/_site/feed.xml +++ b/_site/feed.xml @@ -1,5 +1,280 @@ -Jekyll2024-09-05T00:59:58+09:00http://localhost:4000/feed.xmlKang Byeong-hyeon 👨🏻‍💻Kang Byeonghyeon's dev blog -Kang Byeonghyeon[우선] 우선을 중단하며2024-07-28T00:00:00+09:002024-07-28T00:00:00+09:00http://localhost:4000/%5B%EC%9A%B0%EC%84%A0%5D%20%EC%9A%B0%EC%84%A0%EC%9D%84%20%EC%A4%91%EB%8B%A8%ED%95%98%EB%A9%B0<p><br /> +Jekyll2024-09-12T21:02:46+09:00http://localhost:4000/feed.xmlKang Byeong-hyeon 👨🏻‍💻Kang Byeonghyeon's dev blog +Kang ByeonghyeonCompound 패턴으로 컴포넌트 변경사항 유연하게 대처하기2024-08-12T00:00:00+09:002024-08-12T00:00:00+09:00http://localhost:4000/Compound%20%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C%20%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%20%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD%20%EC%9C%A0%EC%97%B0%ED%95%98%EA%B2%8C%20%EB%8C%80%EC%B2%98%ED%95%98%EA%B8%B0<p><br /> +<br /> +<br /></p> + +<p><img src="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&amp;table=block" alt="image.png" /></p> + +<p><br /></p> + +<p>기능을 구현하다 보면 반복되는 요소를 컴포넌트로 만들 때, 해당 컴포넌트 내부의 일부 요소들이 변동될 가능성이 생긴다.</p> + +<p>특히 특정 데이터가 내부의 각 요소(텍스트, 버튼, 이미지 등)를 동적으로 변화해야 하는 경우, 어떻게 하면 유연성을 높이고 변경 사항에 쉽게 대응할 수 있을지 고민할 수 있다.</p> + +<p>이번에는 Compound 패턴을 통해 컴포넌트의 유연성을 높이고 복잡한 요구 사항에 쉽게 대응할 수 있는 방법을 설명하고자 한다.</p> + +<p>쉬운 이해를 위해 <strong>PostCard</strong> 컴포넌트를 구현하는 예시로, context와 하위 컴포넌트를 통해 어떻게 하면 데이터를 효율적으로 관리하고, 컴포넌트를 유연하게 구성하는지 살펴보도록 하자.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="props로-분기-처리">props로 분기 처리</h1> + +<p>초기에는 컴포넌트에 여러 <code class="language-plaintext highlighter-rouge">props</code>를 전달하고, 그에 따라 내부에서 조건부로 분기처리하여 렌더링할 요소를 결정하려 했다.</p> + +<p>다음은 <code class="language-plaintext highlighter-rouge">PostCard</code> 컴포넌트에서 각 게시물마다 제목, 내용, 사용자 정보가 다를 때, 이 데이터를 <code class="language-plaintext highlighter-rouge">props</code>로 받아 내부에서 조건에 따라 UI를 변화시키도록 한 예시이다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">PostType</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">content</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">user</span><span class="p">?:</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="p">};</span> +<span class="p">};</span> + +<span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span> + <span class="nl">post</span><span class="p">:</span> <span class="nx">PostType</span><span class="p">;</span> +<span class="p">}</span> + +<span class="kd">const</span> <span class="nx">PostCard</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">post</span> <span class="p">}:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">content</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span> + <span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">user</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span><span class="si">}</span> + <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">theme</span><span class="p">=</span><span class="s">"primary"</span><span class="p">&gt;</span>Read More<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">theme</span><span class="p">=</span><span class="s">"default"</span><span class="p">&gt;</span>Comments<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span> + <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">};</span> + +<span class="k">export</span> <span class="k">default</span> <span class="nx">PostCard</span><span class="p">;</span> +</code></pre></div></div> + +<p><br /></p> + +<p>이 방식은 작은 프로젝트에서는 효율적일 수 있지만, 프로젝트가 커질수록 요구 사항이 바뀌거나 새로운 UI 요소가 추가될 때마다 컴포넌트 내부에 조건문이 많아지고 계속해서 수정해야 하는 불편함이 생기곤 한다.</p> + +<p>이로 인해 추후에는 코드의 가독성과 유지보수성이 떨어뜨릴 수 있는 문제가 발생할 수 있다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="compound-패턴의-필요성">Compound 패턴의 필요성</h1> + +<p>Compound 패턴이란 하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담하여 구성하는 패턴을 의미한다.</p> + +<p>즉, 부모 컴포넌트는 UI의 기본 구조와 데이터를 제공하는 컨테이너로서의 역할을 하고, 자식 컴포넌트는 특정 부분의 동작만 담당하도록 역할을 구성하는 것을 말한다.</p> + +<p>이러한 방식으로 작성하게 되면, 부모와 자식 간의 관계가 더욱 명확해지고, 자식 컴포넌트의 역할을 유연하게 커스터마이징할 수 있는 장점이 있다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="compound-패턴을-적용한-postcard-컴포넌트">Compound 패턴을 적용한 PostCard 컴포넌트</h1> + +<p><strong>PostCard</strong> 컴포넌트를 Compound 패턴으로 리팩터링하여, 내부의 변동 요소를 쉽게 관리할 수 있도록 해보자.</p> + +<p>이를 위해, <strong>Context API</strong>를 사용하면, 데이터를 부모 컴포넌트로 부터 전역적으로 관리하고, 자식 컴포넌트가 부모로부터 필요한 데이터를 쉽게 전달받을 수 있도록 구성할 수 있다.</p> + +<p><br /></p> + +<h2 id="1-postcardcontext-구현">1. PostCardContext 구현</h2> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// PostCardContext.tsx</span> + +<span class="k">import</span> <span class="p">{</span> <span class="nx">createContext</span><span class="p">,</span> <span class="nx">ReactNode</span><span class="p">,</span> <span class="nx">useContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">PostType</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./PostCard</span><span class="dl">"</span><span class="p">;</span> + +<span class="kd">type</span> <span class="nx">PostCardContextProps</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">post</span><span class="p">:</span> <span class="nx">PostType</span><span class="p">;</span> +<span class="p">};</span> + +<span class="kd">const</span> <span class="nx">PostCardContext</span> <span class="o">=</span> <span class="nx">createContext</span><span class="o">&lt;</span><span class="nx">PostCardContextProps</span> <span class="o">|</span> <span class="kc">undefined</span><span class="o">&gt;</span><span class="p">(</span> + <span class="kc">undefined</span> +<span class="p">);</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">usePostCardContext</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">PostCardContext</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">context</span><span class="p">)</span> + <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span> + <span class="dl">"</span><span class="s2">usePostCardContext must be used within a PostCardContext.Provider</span><span class="dl">"</span> + <span class="p">);</span> + <span class="k">return</span> <span class="nx">context</span><span class="p">;</span> +<span class="p">};</span> + +<span class="kr">interface</span> <span class="nx">ProviderProps</span> <span class="p">{</span> + <span class="nl">post</span><span class="p">:</span> <span class="nx">PostType</span><span class="p">;</span> + <span class="nl">children</span><span class="p">:</span> <span class="nx">ReactNode</span><span class="p">;</span> +<span class="p">}</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">PostCardContextProvider</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">post</span><span class="p">,</span> <span class="nx">children</span> <span class="p">}:</span> <span class="nx">ProviderProps</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nc">PostCardContext</span><span class="p">.</span><span class="nc">Provider</span> <span class="na">value</span><span class="p">=&gt;</span> + <span class="si">{</span><span class="nx">children</span><span class="si">}</span> + <span class="p">&lt;/</span><span class="nc">PostCardContext</span><span class="p">.</span><span class="nc">Provider</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">};</span> +</code></pre></div></div> + +<p><br /></p> + +<p>부모 컴포넌트에서 게시물 데이터를 받아 자식 컴포넌트에 전달할 수 있도록 <code class="language-plaintext highlighter-rouge">PostCardContext</code> 를 생성한다.</p> + +<p>이후, 자식 컴포넌트가 해당 게시물 데이터를 받아올 수 있도록 <strong>usePostCardContext</strong> 커스텀 훅을 구현한다.</p> + +<p>마지막으로 <code class="language-plaintext highlighter-rouge">Provider</code>를 통해 하위 컴포넌트에 게시물 데이터를 공유할 수 있도록 <strong>PostCardContextProvider</strong> 컴포넌트를 구현한다.</p> + +<p><br /></p> + +<h2 id="2-하위-컴포넌트-구현">2. 하위 컴포넌트 구현</h2> + +<p>이제 Compound 패턴의 핵심인 독립적인 하위 컴포넌트를 구현해야한다.</p> + +<p><code class="language-plaintext highlighter-rouge">Title</code>, <code class="language-plaintext highlighter-rouge">User</code>, <code class="language-plaintext highlighter-rouge">Actions</code> 하위 컴포넌트를 각각 구성하고, <code class="language-plaintext highlighter-rouge">usePostCardContext</code>를 통해 <code class="language-plaintext highlighter-rouge">PostCardContext</code>로부터 필요한 게시물 데이터를 받아 렌더링을 수행하도록 한다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// components/index.tsx</span> + +<span class="k">import</span> <span class="p">{</span> <span class="nx">usePostCardContext</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../PostCardContext</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">Button</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@sparklon/react</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">Title</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">post</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">usePostCardContext</span><span class="p">();</span> + <span class="k">return</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;;</span> +<span class="p">};</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">User</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">post</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">usePostCardContext</span><span class="p">();</span> + <span class="k">return</span> <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h2</span><span class="p">&gt;;</span> +<span class="p">};</span> + +<span class="k">export</span> <span class="kd">const</span> <span class="nx">Actions</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">theme</span><span class="p">=</span><span class="s">"primary"</span><span class="p">&gt;</span>Read More<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nc">Button</span> <span class="na">theme</span><span class="p">=</span><span class="s">"default"</span><span class="p">&gt;</span>Comments<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span> + <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">};</span> +</code></pre></div></div> + +<p><br /></p> + +<p>위와 같이 작성하게 되면 부모 컴포넌트에서 전달된 데이터를 필요에 따라 쉽게 사용할 수 있으며, 추후에 변경 사항이 있을때 각 하위 컴포넌트만 수정하면 되기 때문에 유지보수성이 높아진다.</p> + +<p><br /></p> + +<h2 id="3-postcard-컴포넌트">3. PostCard 컴포넌트</h2> + +<p>마지막으로 게시물 데이터를 <code class="language-plaintext highlighter-rouge">PostCardContextProvider</code>로 전달하고, 하위 컴포넌트가 해당 데이터를 활용할 수 있도록 돕는 <code class="language-plaintext highlighter-rouge">PostCard</code> 컴포넌트를 구현해보자.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">ReactNode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">PostCardContextProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./PostCardContext</span><span class="dl">"</span><span class="p">;</span> +<span class="k">import</span> <span class="p">{</span> <span class="nx">Title</span><span class="p">,</span> <span class="nx">User</span><span class="p">,</span> <span class="nx">Actions</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./components</span><span class="dl">"</span><span class="p">;</span> + +<span class="k">export</span> <span class="kd">type</span> <span class="nx">PostType</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">title</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">content</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">user</span><span class="p">?:</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="p">};</span> +<span class="p">};</span> + +<span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span> + <span class="nl">post</span><span class="p">:</span> <span class="nx">PostType</span><span class="p">;</span> + <span class="nl">children</span><span class="p">:</span> <span class="nx">ReactNode</span><span class="p">;</span> +<span class="p">}</span> + +<span class="kd">const</span> <span class="nx">PostCard</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">post</span><span class="p">,</span> <span class="nx">children</span> <span class="p">}:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nc">PostCardContextProvider</span> <span class="na">post</span><span class="p">=</span><span class="si">{</span><span class="nx">post</span><span class="si">}</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">children</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span> + <span class="p">&lt;/</span><span class="nc">PostCardContextProvider</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">};</span> + +<span class="k">export</span> <span class="k">default</span> <span class="nx">PostCard</span><span class="p">;</span> + +<span class="nx">PostCard</span><span class="p">.</span><span class="nx">Title</span> <span class="o">=</span> <span class="nx">Title</span><span class="p">;</span> +<span class="nx">PostCard</span><span class="p">.</span><span class="nx">User</span> <span class="o">=</span> <span class="nx">User</span><span class="p">;</span> +<span class="nx">PostCard</span><span class="p">.</span><span class="nx">Actions</span> <span class="o">=</span> <span class="nx">Actions</span><span class="p">;</span> +</code></pre></div></div> + +<p><br /></p> + +<p>게시물 데이터를 <code class="language-plaintext highlighter-rouge">PostCardContextProvider</code>를 통해 하위 컴포넌트로 전달하고, children을 통해 하위 컴포넌트들을 렌더링할 수 있도록 래퍼 컴포넌트 형태로 구성한다.</p> + +<p>이후, Compound 패턴에 맞게 각 하위 컴포넌트들을 <code class="language-plaintext highlighter-rouge">PostCard</code> 컴포넌트에 설정한다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="사용-예시">사용 예시</h1> + +<p>이제 <code class="language-plaintext highlighter-rouge">PostCard</code> 컴포넌트를 활용하여 각 게시물의 정보를 유연하게 렌더링할 수 있다.</p> + +<p>부모 컴포넌트에서 특정 데이터를 <code class="language-plaintext highlighter-rouge">props</code>로 전달하면, context를 통해 해당 데이터가 하위 컴포넌트로 자동으로 전달된다.</p> + +<p>하위 컴포넌트는 context로부터 필요한 데이터를 가져와 독립적으로 동작하게 되고, <code class="language-plaintext highlighter-rouge">props</code>로 별도의 데이터를 전달할 필요가 없게 된다.</p> + +<p>이로써, 각각의 컴포넌트의 배치와 역할을 유연하게 조정할 수 있으며, 추후에 발생하는 변경 사항에 쉽게 대처할 수 있는 구조를 갖출 수 있게 되었다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">PostCard</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./PostCard</span><span class="dl">"</span><span class="p">;</span> + +<span class="kd">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> + <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">How to Use Compound Pattern</span><span class="dl">"</span><span class="p">,</span> + <span class="na">content</span><span class="p">:</span> <span class="dl">"</span><span class="s2">This is an example of how to implement compound components.</span><span class="dl">"</span><span class="p">,</span> + <span class="na">user</span><span class="p">:</span> <span class="p">{</span> + <span class="na">id</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> + <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">John Doe</span><span class="dl">"</span><span class="p">,</span> + <span class="p">},</span> +<span class="p">};</span> + +<span class="kd">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nc">PostCard</span> <span class="na">post</span><span class="p">=</span><span class="si">{</span><span class="nx">post</span><span class="si">}</span><span class="p">&gt;</span> + <span class="p">&lt;</span><span class="nc">PostCard</span><span class="p">.</span><span class="nc">User</span> <span class="p">/&gt;</span> + <span class="p">&lt;</span><span class="nc">PostCard</span><span class="p">.</span><span class="nc">Title</span> <span class="p">/&gt;</span> + <span class="p">&lt;</span><span class="nc">PostCard</span><span class="p">.</span><span class="nc">Buttons</span> <span class="p">/&gt;</span> + <span class="p">&lt;/</span><span class="nc">PostCard</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">};</span> + +<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span> +</code></pre></div></div> + +<p><br /> +<br /> +<br /></p> + +<h1 id="마치면서">마치면서</h1> + +<p>이번 글에서는 <code class="language-plaintext highlighter-rouge">PostCard</code> 컴포넌트를 예시로 <strong>Compound 패턴</strong>에 대해 자세히 알아보았다.</p> + +<p>Compound 패턴을 사용하면 컴포넌트 내부 요소들이 <strong>동적</strong>으로 변화할 때 부모와 자식 컴포넌트 간의 <strong>데이터 흐름</strong>을 명확하게 관리할 수 있고, 각 하위 컴포넌트를 <strong>독립적으로</strong> <strong>관리</strong>할 수 있다는 장점을 알게 되었다.</p> + +<p>이를 통해, 추후 발생될 수 있는 변동 사항에 대해 유연하게 대처할 수 있다는 것 또한 확인할 수 있었다.</p> + +<p>따라서, 독자들도 유사한 상황에 처해 있거나, 컴포넌트의 유연성과 재사용성을 높일 필요성이 있다면, <strong>Compound 패턴</strong> 도입에 대해 고려해보면 좋을 것 같다.</p>Kang Byeonghyeon[우선] 우선을 중단하며2024-07-28T00:00:00+09:002024-07-28T00:00:00+09:00http://localhost:4000/%5B%EC%9A%B0%EC%84%A0%5D%20%EC%9A%B0%EC%84%A0%EC%9D%84%20%EC%A4%91%EB%8B%A8%ED%95%98%EB%A9%B0<p><br /> <br /> <br /></p> @@ -859,8 +1134,8 @@ <table> <tbody> <tr> - <td><img src="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&amp;table=block" alt="스크린샷 2024-07-09 오후 3.43.31.png" /></td> - <td><img src="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&amp;table=block" alt="스크린샷 2024-07-09 오후 3.43.46.png" /></td> + <td><img src="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&amp;table=block" alt="스크린샷 2024-07-09 오후 3.43.31.png" /></td> + <td><img src="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&amp;table=block" alt="스크린샷 2024-07-09 오후 3.43.46.png" /></td> </tr> </tbody> </table> @@ -2653,663 +2928,4 @@ root.render<span class="o">(</span> <p>하지만 <code class="language-plaintext highlighter-rouge">createBrowserRouter</code>를 사용하기 위해서는 새로운 접근 방식에 대한 학습이 필요하기 때문에 러닝 커브가 존재하고, 프로젝트 내에서 <code class="language-plaintext highlighter-rouge">react-router-dom</code> v6.4 이상을 사용하지 않는다면 무의미할 수 있다는 점에서 버전 의존성이 단점으로 작용할 수 있을 것 같다.</p> -<p>독자들은 각 프로젝트의 요구 사항에 맞는 라우팅 방식을 선택해서 보다 효율적으로 라우팅을 관리해보길 바란다.</p>Kang ByeonghyeonStorybook으로 UI 테스팅과 배포(CI) 한번에 해결하기2024-07-03T00:00:00+09:002024-07-03T00:00:00+09:00http://localhost:4000/Storybook%EC%9C%BC%EB%A1%9C%20UI%20%ED%85%8C%EC%8A%A4%ED%8C%85%EA%B3%BC%20%EB%B0%B0%ED%8F%AC(CI)%20%ED%95%9C%EB%B2%88%EC%97%90%20%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0<p><br /> -<br /> -<br /></p> -<h1 id="storybook-소개">Storybook 소개</h1> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fc0c9b078-f570-4e92-9f4a-0ec76b4d6de7%2F%25E1%2584%2583%25E1%2585%25A1%25E1%2584%258B%25E1%2585%25AE%25E1%2586%25AB%25E1%2584%2585%25E1%2585%25A9%25E1%2584%2583%25E1%2585%25B3.png?id=9a9c29c2-ad1d-40cf-aeb1-86e254e949da&amp;table=block" alt="storybook" width="300" /></p> - -<p><br /></p> - -<p>Storybook은 <strong>UI 테스팅 툴</strong>로, 내부 개발자를 위한 문서화 도구와 외부 공개용 디자인 시스템의 기본 플랫폼으로 활용된다.</p> - -<p>Storybook의 기본 구성 단위는 스토리(Story)로, 하나의 UI 컴포넌트는 여러 개의 스토리를 가질 수 있다.</p> - -<p>각 스토리는 컴포넌트의 사용 예시를 보여주기 때문에 Storybook을 사용하면 UI 컴포넌트를 독립적으로 시각적으로 테스트하며 개발할 수 있다.</p> - -<p>이를 통해 UI 라이브러리를 사용하는 개발자들은 코드를 보지 않고도 컴포넌트를 체험하고 활용할 수 있어 매우 유용하다.</p> - -<p><br /> -<br /> -<br /></p> - -<h1 id="storybook-설치-및-설정하기">Storybook 설치 및 설정하기</h1> - -<h2 id="storybook-설치하기">Storybook 설치하기</h2> - -<p>Storybook 설치를 결정했다면 <code class="language-plaintext highlighter-rouge">npx storybook@latest init</code> 명령어를 입력하면 설치할 수 있다.</p> - -<p>프로젝트 환경에 맞게 알아서 Storybook을 설치해준다.</p> - -<p>설치가 완료되면 root 폴더에 <code class="language-plaintext highlighter-rouge">.storybook</code> 폴더가 생성되고, 그 안에 <code class="language-plaintext highlighter-rouge">main.ts</code>와 <code class="language-plaintext highlighter-rouge">preview.ts</code> 파일이 생성된다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F573ecb16-a880-4d85-bb93-9929df989f94%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-06-27_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.24.43.png?id=777fb53a-fe4f-46c8-84c3-f69ebbb71fa4&amp;table=block" alt="스크린샷 2024-06-27 오후 3.24.43.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F34a5db66-0d86-4343-8e0b-2bf64e98beaa%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-06-27_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.24.28.png?id=4732428d-19a6-424e-b366-7bdebf68ea1c&amp;table=block" alt="스크린샷 2024-06-27 오후 3.24.28.png" /></p> - -<p>또한 <code class="language-plaintext highlighter-rouge">src</code> 폴더 내부에 <code class="language-plaintext highlighter-rouge">stories</code>라는 폴더가 생성되며, 그 안에는 예시 파일들이 생성된다.</p> - -<p>Storybook에 대한 전반적인 설정은 <code class="language-plaintext highlighter-rouge">.storybook</code> 폴더 내부에 있는 파일들에서 설정하며, 컴포넌트에 대한 story 파일 생성은 <code class="language-plaintext highlighter-rouge">{ComponentName}.stories.ts</code> 형태로 작성하면 된다.</p> - -<p>필자는 <code class="language-plaintext highlighter-rouge">src</code> 폴더에 <code class="language-plaintext highlighter-rouge">stories</code> 폴더를 따로 분리하지 않고 <code class="language-plaintext highlighter-rouge">component</code> 폴더에 각 컴포넌트에 해당하는 폴더를 생성하여 해당 폴더 내부에서 stories 파일을 생성하는 방식으로 작성해보았다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fedd8cfa5-ecf9-468c-9375-e58070dd5024%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.32.59.png?id=7960ec81-2993-4d8d-bf1b-2f21bcee7fa6&amp;table=block" alt="스크린샷 2024-07-01 오후 2.32.59.png" /></p> - -<h2 id="storybookmaints--previewts">.storybook/main.ts &amp; preview.ts</h2> - -<p><code class="language-plaintext highlighter-rouge">.storybook</code> 내부에 있는 <code class="language-plaintext highlighter-rouge">main.ts</code>는 Storybook에 대한 전반적인 설정을 할 수 있는 파일이다.</p> - -<p>해당 파일의 코드는 다음과 같은 의미를 가진다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .storybook/main.ts</span> - -<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">StorybookConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react-webpack5</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">config</span><span class="p">:</span> <span class="nx">StorybookConfig</span> <span class="o">=</span> <span class="p">{</span> - <span class="c1">// Storybook에 사용한 mdx, .stories 파일의 위치를 의미합니다.</span> - <span class="na">stories</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">../src/**/*.mdx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">../src/**/*.stories.@(js|jsx|mjs|ts|tsx)</span><span class="dl">"</span><span class="p">],</span> - <span class="c1">// 적용할 addon을 의미합니다.</span> - <span class="na">addons</span><span class="p">:</span> <span class="p">[</span> - <span class="dl">"</span><span class="s2">@storybook/preset-create-react-app</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-onboarding</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-links</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-essentials</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@chromatic-com/storybook</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-interactions</span><span class="dl">"</span><span class="p">,</span> - <span class="p">],</span> - <span class="c1">// 현재 Storybook의 프레임워크를 뜻합니다.</span> - <span class="na">framework</span><span class="p">:</span> <span class="p">{</span> - <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@storybook/react-webpack5</span><span class="dl">"</span><span class="p">,</span> - <span class="na">options</span><span class="p">:</span> <span class="p">{},</span> - <span class="p">},</span> - <span class="c1">// static 폴더의 경로를 의미합니다.</span> - <span class="na">staticDirs</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">../public</span><span class="dl">"</span><span class="p">],</span> -<span class="p">};</span> -<span class="k">export</span> <span class="k">default</span> <span class="nx">config</span><span class="p">;</span> -</code></pre></div></div> - -<p>추가적으로 있는 <code class="language-plaintext highlighter-rouge">preview.ts</code>는 Storybook을 실행했을 때 브라우저에서 보이는 미리보기 화면을 설정할 수 있는 파일이다.</p> - -<p>해당 파일의 코드는 다음과 같은 의미를 가진다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .storybook/preview.ts</span> - -<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Preview</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">preview</span><span class="p">:</span> <span class="nx">Preview</span> <span class="o">=</span> <span class="p">{</span> - <span class="c1">// parameters는 스토리에 대한 메타데이터 정보들, 주로 Storybook feature와 addon에 대한 설정</span> - <span class="na">parameters</span><span class="p">:</span> <span class="p">{</span> - <span class="na">actions</span><span class="p">:</span> <span class="p">{</span> <span class="na">argTypesRegex</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^on[A-Z].*</span><span class="dl">"</span> <span class="p">},</span> - <span class="na">controls</span><span class="p">:</span> <span class="p">{</span> - <span class="na">matchers</span><span class="p">:</span> <span class="p">{</span> - <span class="na">color</span><span class="p">:</span> <span class="sr">/</span><span class="se">(</span><span class="sr">background|color</span><span class="se">)</span><span class="sr">$/i</span><span class="p">,</span> - <span class="na">date</span><span class="p">:</span> <span class="sr">/Date$/</span><span class="p">,</span> - <span class="p">},</span> - <span class="p">},</span> - <span class="p">},</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="k">default</span> <span class="nx">preview</span><span class="p">;</span> -</code></pre></div></div> - -<p><br /> -<br /> -<br /></p> - -<h1 id="storybook-사용해보기">Storybook 사용해보기</h1> - -<h2 id="react-컴포넌트-작성">React 컴포넌트 작성</h2> - -<p>예를 들어 Button 컴포넌트를 만든다고 가정해자.</p> - -<p><code class="language-plaintext highlighter-rouge">src/component/common</code> 경로에 <code class="language-plaintext highlighter-rouge">Button.tsx</code> 파일을 생성하고, <code class="language-plaintext highlighter-rouge">ButtonProps</code>를 타입으로 갖는 Button 컴포넌트를 다음과 같이 작성해보았다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">ButtonHTMLAttributes</span><span class="p">,</span> <span class="nx">ReactNode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="nx">StyledButton</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Button.styled</span><span class="dl">"</span><span class="p">;</span> - -<span class="k">export</span> <span class="kd">type</span> <span class="nx">ButtonTheme</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">primary</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">kakao</span><span class="dl">"</span><span class="p">;</span> -<span class="k">export</span> <span class="kd">type</span> <span class="nx">ButtonSize</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">small</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">medium</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">large</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">responsive</span><span class="dl">"</span><span class="p">;</span> - -<span class="kr">interface</span> <span class="nx">ButtonProps</span> <span class="kd">extends</span> <span class="nx">ButtonHTMLAttributes</span><span class="o">&lt;</span><span class="nx">HTMLButtonElement</span><span class="o">&gt;</span> <span class="p">{</span> - <span class="nx">theme</span><span class="p">?:</span> <span class="nx">ButtonTheme</span><span class="p">;</span> - <span class="nl">size</span><span class="p">?:</span> <span class="nx">ButtonSize</span><span class="p">;</span> - <span class="nl">children</span><span class="p">:</span> <span class="nx">ReactNode</span><span class="p">;</span> -<span class="p">}</span> - -<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="p">({</span> - <span class="nx">theme</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">primary</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">size</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">medium</span><span class="dl">"</span><span class="p">,</span> - <span class="nx">children</span><span class="p">,</span> - <span class="p">...</span><span class="nx">props</span> -<span class="p">}:</span> <span class="nx">ButtonProps</span><span class="p">)</span> <span class="p">{</span> - <span class="k">return</span> <span class="p">(</span> - <span class="p">&lt;</span><span class="nc">StyledButton</span> <span class="na">theme</span><span class="p">=</span><span class="si">{</span><span class="nx">theme</span><span class="si">}</span> <span class="na">size</span><span class="p">=</span><span class="si">{</span><span class="nx">size</span><span class="si">}</span> <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span><span class="p">&gt;</span> - <span class="si">{</span><span class="nx">children</span><span class="si">}</span> - <span class="p">&lt;/</span><span class="nc">StyledButton</span><span class="p">&gt;</span> - <span class="p">);</span> -<span class="p">}</span> -</code></pre></div></div> - -<p>현재 <code class="language-plaintext highlighter-rouge">theme</code>과 <code class="language-plaintext highlighter-rouge">size</code>를 props로 받고 있고, 특정 string 값을 입력하면 그에 따른 Button의 디자인이 변경되는 역할을 하고 있는 컴포넌트이다.</p> - -<h2 id="기본-story-추가하기">기본 Story 추가하기</h2> - -<p>먼저 <code class="language-plaintext highlighter-rouge">Meta</code>와 <code class="language-plaintext highlighter-rouge">StoryObj</code>에 대해 살펴보자.</p> - -<p><code class="language-plaintext highlighter-rouge">Meta</code>는 스토리북 메타데이터를 정의하는데 사용되고, <code class="language-plaintext highlighter-rouge">StoryObj</code>는 개별 스토리의 정의를 위해 사용된다.</p> - -<h3 id="meta-object">Meta Object</h3> - -<p><code class="language-plaintext highlighter-rouge">meta</code> 객체는 Storybook이 스토리 목록에서 컴포넌트를 어떻게 표시할지를 결정하는 설정이다.</p> - -<p><code class="language-plaintext highlighter-rouge">meta</code> 객체를 생성하고, 생성할 Story의 컴포넌트 타입을 제네릭으로 넘겨준다.</p> - -<p>이후 해당 컴포넌트의 세부적인 설정은 다음과 같이 작성할 수 있다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Button.stories.ts</span> - -<span class="k">import</span> <span class="nx">Button</span><span class="p">,</span> <span class="p">{</span> <span class="nx">ButtonProps</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@/components/common/Button</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Meta</span><span class="p">,</span> <span class="nx">StoryObj</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">meta</span><span class="p">:</span> <span class="nx">Meta</span><span class="o">&lt;</span><span class="nx">ButtonProps</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">common/Button</span><span class="dl">"</span><span class="p">,</span> - <span class="na">component</span><span class="p">:</span> <span class="nx">Button</span><span class="p">,</span> - <span class="na">tags</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">autodocs</span><span class="dl">"</span><span class="p">],</span> - <span class="na">argTypes</span><span class="p">:</span> <span class="p">{</span> - <span class="na">theme</span><span class="p">:</span> <span class="p">{</span> - <span class="na">control</span><span class="p">:</span> <span class="p">{</span> - <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">select</span><span class="dl">"</span><span class="p">,</span> - <span class="na">options</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">kakao</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">outline</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">lightGray</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">darkGray</span><span class="dl">"</span><span class="p">],</span> - <span class="p">},</span> - <span class="p">},</span> - <span class="na">size</span><span class="p">:</span> <span class="p">{</span> - <span class="na">control</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">select</span><span class="dl">"</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">small</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">large</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">responsive</span><span class="dl">"</span><span class="p">]</span> <span class="p">},</span> - <span class="p">},</span> - <span class="p">},</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="k">default</span> <span class="nx">meta</span><span class="p">;</span> -</code></pre></div></div> - -<ul> - <li><code class="language-plaintext highlighter-rouge">title</code>: 스토리북 UI에서 이 스토리가 어디에 위치할지를 지정한다. 여기서는 ‘common/Button’으로, ‘common’ <strong>카테고리</strong> 아래 ‘Button’ 스토리가 생성되게 된다.</li> - <li><code class="language-plaintext highlighter-rouge">component</code>: 해당 메타데이터가 어떤 컴포넌트를 다루는지 지정한다. 여기서는 <code class="language-plaintext highlighter-rouge">Button</code> 컴포넌트를 의미한다.</li> - <li><code class="language-plaintext highlighter-rouge">tags</code>: 자동으로 Docs를 생성하기 위해 사용되는 태그이다.</li> - <li><code class="language-plaintext highlighter-rouge">argTypes</code>: 스토리북에서 해당 컴포넌트의 props를 조절할 수 있는 UI 컨트롤을 정의할 수 있다. 현재 Button 컴포넌트에서는 <code class="language-plaintext highlighter-rouge">theme</code>과 <code class="language-plaintext highlighter-rouge">size</code> 속성에 대해 <code class="language-plaintext highlighter-rouge">select</code> 컨트롤 타입을 설정하여 조절할 수 있도록 하였다.</li> -</ul> - -<p>최종적으로 옵션을 설정하였다면, <code class="language-plaintext highlighter-rouge">meta</code> 객체를 export defalt 해준다.</p> - -<h3 id="storyobj">StoryObj</h3> - -<p>메타데이터를 설정하였다면 개별 스토리의 정의를 위해 <code class="language-plaintext highlighter-rouge">StoryObj</code>를 통해 Story의 컴포넌트 타입을 정의한다.</p> - -<p>이후 각 스토리를 작성합니다.</p> - -<p>예를 들어 <code class="language-plaintext highlighter-rouge">kakao</code> theme을 갖는 <code class="language-plaintext highlighter-rouge">Kakao</code> 스토리와 <code class="language-plaintext highlighter-rouge">primary</code> theme을 갖는 <code class="language-plaintext highlighter-rouge">Primary</code> 스토리를 정의해본다고 가정한다면 다음과 같이 작성할 수 있다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Button.stories.ts</span> - -<span class="p">...</span> - -<span class="kd">type</span> <span class="nx">Story</span> <span class="o">=</span> <span class="nx">StoryObj</span><span class="o">&lt;</span><span class="nx">ButtonProps</span><span class="o">&gt;</span><span class="p">;</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Kakao</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">args</span><span class="p">:</span> <span class="p">{</span> - <span class="na">children</span><span class="p">:</span> <span class="dl">'</span><span class="s1">버튼 텍스트</span><span class="dl">'</span><span class="p">,</span> - <span class="na">theme</span><span class="p">:</span> <span class="dl">'</span><span class="s1">kakao</span><span class="dl">'</span><span class="p">,</span> - <span class="na">size</span><span class="p">:</span> <span class="dl">'</span><span class="s1">large</span><span class="dl">'</span><span class="p">,</span> - <span class="p">},</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Primary</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">args</span><span class="p">:</span> <span class="p">{</span> - <span class="na">children</span><span class="p">:</span> <span class="dl">'</span><span class="s1">버튼 텍스트</span><span class="dl">'</span><span class="p">,</span> - <span class="na">theme</span><span class="p">:</span> <span class="dl">'</span><span class="s1">primary</span><span class="dl">'</span><span class="p">,</span> - <span class="na">size</span><span class="p">:</span> <span class="dl">'</span><span class="s1">large</span><span class="dl">'</span><span class="p">,</span> - <span class="p">},</span> -<span class="p">};</span> -</code></pre></div></div> - -<ul> - <li><code class="language-plaintext highlighter-rouge">StoryObj</code>를 통해 컴포넌트 타입을 정의한다.</li> - <li>각 스토리를 <code class="language-plaintext highlighter-rouge">export</code>하고 <code class="language-plaintext highlighter-rouge">Story</code> 타입으로 설정하고 객체를 생성한다. 해당 스토리 객체 내부에서 <code class="language-plaintext highlighter-rouge">args</code> 속성을 통해 해당 Button 컴포넌트의 props에 따른 기본 값을 설정할 수 있다.</li> -</ul> - -<h3 id="최종-코드">최종 코드</h3> - -<p><code class="language-plaintext highlighter-rouge">meta</code>와 <code class="language-plaintext highlighter-rouge">StoryObj</code>를 통한 스토리북 생성을 위한 최종 코드는 다음과 같다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Button.stories.ts</span> - -<span class="k">import</span> <span class="nx">Button</span><span class="p">,</span> <span class="p">{</span> <span class="nx">ButtonProps</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@/components/common/Button</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">Meta</span><span class="p">,</span> <span class="nx">StoryObj</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">meta</span><span class="p">:</span> <span class="nx">Meta</span><span class="o">&lt;</span><span class="nx">ButtonProps</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">title</span><span class="p">:</span> <span class="dl">"</span><span class="s2">common/Button</span><span class="dl">"</span><span class="p">,</span> - <span class="na">component</span><span class="p">:</span> <span class="nx">Button</span><span class="p">,</span> - <span class="na">tags</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">autodocs</span><span class="dl">"</span><span class="p">],</span> - <span class="na">argTypes</span><span class="p">:</span> <span class="p">{</span> - <span class="na">theme</span><span class="p">:</span> <span class="p">{</span> - <span class="na">control</span><span class="p">:</span> <span class="p">{</span> - <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">select</span><span class="dl">"</span><span class="p">,</span> - <span class="na">options</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">kakao</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">outline</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">black</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">lightGray</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">darkGray</span><span class="dl">"</span><span class="p">],</span> - <span class="p">},</span> - <span class="p">},</span> - <span class="na">size</span><span class="p">:</span> <span class="p">{</span> - <span class="na">control</span><span class="p">:</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">select</span><span class="dl">"</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">small</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">large</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">responsive</span><span class="dl">"</span><span class="p">]</span> <span class="p">},</span> - <span class="p">},</span> - <span class="p">},</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="k">default</span> <span class="nx">meta</span><span class="p">;</span> - -<span class="kd">type</span> <span class="nx">Story</span> <span class="o">=</span> <span class="nx">StoryObj</span><span class="o">&lt;</span><span class="nx">ButtonProps</span><span class="o">&gt;</span><span class="p">;</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Kakao</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">args</span><span class="p">:</span> <span class="p">{</span> - <span class="na">children</span><span class="p">:</span> <span class="dl">"</span><span class="s2">버튼 텍스트</span><span class="dl">"</span><span class="p">,</span> - <span class="na">theme</span><span class="p">:</span> <span class="dl">"</span><span class="s2">kakao</span><span class="dl">"</span><span class="p">,</span> - <span class="na">size</span><span class="p">:</span> <span class="dl">"</span><span class="s2">large</span><span class="dl">"</span><span class="p">,</span> - <span class="p">},</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Primary</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">args</span><span class="p">:</span> <span class="p">{</span> - <span class="na">children</span><span class="p">:</span> <span class="dl">"</span><span class="s2">버튼 텍스트</span><span class="dl">"</span><span class="p">,</span> - <span class="na">theme</span><span class="p">:</span> <span class="dl">"</span><span class="s2">primary</span><span class="dl">"</span><span class="p">,</span> - <span class="na">size</span><span class="p">:</span> <span class="dl">"</span><span class="s2">large</span><span class="dl">"</span><span class="p">,</span> - <span class="p">},</span> -<span class="p">};</span> -</code></pre></div></div> - -<h2 id="hooks를-곁들인-story-추가하기">hooks를 곁들인 Story 추가하기</h2> - -<p>hooks와 같이 컴포넌트가 동작하는 Story를 추가하려면 렌더링할 컴포넌트와 hooks와 같이 함수로 정의하고 render시키면 된다.</p> - -<p>사용자가 선택한 카테고리를 상태를 갖는 Wish라는 컴포넌트를 예시를 들어 설명해보면 다음과 같이 작성해볼 수 있다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> -<span class="k">import</span> <span class="p">{</span> <span class="nx">Meta</span><span class="p">,</span> <span class="nx">StoryObj</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@storybook/react</span><span class="dl">'</span><span class="p">;</span> -<span class="k">import</span> <span class="nx">useFilter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@hooks/useFilter</span><span class="dl">'</span><span class="p">;</span> -<span class="k">import</span> <span class="nx">Wish</span><span class="p">,</span> <span class="p">{</span> <span class="nx">WishProps</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">;</span> - -<span class="c1">// meta 생성</span> - -<span class="p">...</span> - -<span class="o">**</span><span class="kd">function</span> <span class="nx">WishWithFilterHooks</span><span class="p">(</span><span class="nx">args</span><span class="p">:</span> <span class="nx">WishProps</span><span class="p">)</span> <span class="p">{</span> - <span class="kd">const</span> <span class="p">{</span> <span class="nx">selectedWish</span><span class="p">,</span> <span class="nx">selectWish</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useFilter</span><span class="p">(</span><span class="dl">''</span><span class="p">,</span> <span class="nx">FILTER_WISHS</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">wish</span><span class="p">);</span> - - <span class="k">return</span> <span class="p">&lt;</span><span class="nc">Wish</span> <span class="si">{</span><span class="p">...</span><span class="nx">args</span><span class="si">}</span> <span class="na">selectedWish</span><span class="p">=</span><span class="si">{</span><span class="nx">selectedWish</span><span class="si">}</span> <span class="na">selectWish</span><span class="p">=</span><span class="si">{</span><span class="nx">selectWish</span><span class="si">}</span> <span class="p">/&gt;;</span> -<span class="p">}</span><span class="o">**</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Default</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{</span> - <span class="o">**</span><span class="na">render</span><span class="p">:</span> <span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nc">WishWithFilterHooks</span> <span class="si">{</span><span class="p">...</span><span class="nx">args</span><span class="si">}</span> <span class="p">/&gt;,</span><span class="o">**</span> - <span class="na">args</span><span class="p">:</span> <span class="p">{</span> - <span class="na">selectedWish</span><span class="p">:</span> <span class="nx">FILTER_WISHS</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">wish</span><span class="p">,</span> - <span class="na">selectWish</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{},</span> - <span class="p">},</span> -<span class="p">};</span> -</code></pre></div></div> - -<p>WishWithFilterHooks라는 함수를 정의하고 useFilter라는 커스텀훅을 불러온다.</p> - -<p>이후 Wish 컴포넌트에 해당 hooks와 관련한 상태를 props로 전달해주어 state에 따라 동작할 수 있도록 하는 컴포넌트 함수를 만든다.</p> - -<p>주의할 점은 jsx문법을 사용하기 때문에 파일 확장자또한 tsx 혹은 jsx로 작성해야한다.</p> - -<p>이후 Story에 render 속성을 통해 해당 컴포넌트를 렌더링 시키면 hooks와 함께하는 Story를 추가할 수 있다.</p> - -<h3 id="결과">결과</h3> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fa9565c80-8a2c-4780-b09c-b061ff8bb608%2FJul-03-2024_22-06-15.gif?id=c3b64706-76f1-4b49-8165-85dd3807324c&amp;table=block" alt="Jul-03-2024 22-06-15.gif" /></p> - -<h2 id="preview-확인하기">Preview 확인하기</h2> - -<p><code class="language-plaintext highlighter-rouge">npm run storybook</code> 명령어를 입력하면 브라우저에 스토리북이 실행되며, 다음과 같이 common카테고리에 Button컴포넌트에 해당하는 Kakao, Primary스토리가 생성된 것을 확인할 수 있다.</p> - -<p>추가로, 메타 데이터에 <code class="language-plaintext highlighter-rouge">tags: ['autodocs']</code>를 설정했기 때문에 자동으로 Docs가 생성된 것도 확인할 수 있다.</p> - -<p>또한 각 생성한 Story도 잘 나오는 것을 확인할 수 있다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fd6cd56c9-1553-4970-9835-c82c073a42f1%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.26.19.png?id=503f60e8-01ac-49e0-b8f8-c6d717abcdd3&amp;table=block" alt="결과" /></p> - -<h2 id="useparams-모킹-및-react-router-컴포넌트용-storybook-설정하기"><code class="language-plaintext highlighter-rouge">useParams</code> 모킹 및 React Router 컴포넌트용 Storybook 설정하기</h2> - -<p>React 애플리케이션을 개발할 때 URL 매개변수(<code class="language-plaintext highlighter-rouge">useParams</code>)에 의존하는 컴포넌트를 테스트할 필요가 있다.</p> - -<p>이러한 경우 Storybook에서 <code class="language-plaintext highlighter-rouge">useParams</code>를 모킹(mocking)하는 것이 유용하다.</p> - -<h3 id="컴포넌트-설정">컴포넌트 설정</h3> - -<p>먼저 <code class="language-plaintext highlighter-rouge">useParams</code>를 사용하는 간단한 컴포넌트를 만들어보자.</p> - -<p>이 컴포넌트는 URL에서 <code class="language-plaintext highlighter-rouge">themeKey</code>를 가져와 표시한다고 가정한다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SectionHeader.tsx</span> -<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="p">{</span> <span class="nx">useParams</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">SectionHeader</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FC</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> - <span class="kd">const</span> <span class="p">{</span> <span class="nx">themeKey</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useParams</span><span class="o">&lt;</span><span class="p">{</span> <span class="na">themeKey</span><span class="p">:</span> <span class="kr">string</span> <span class="p">}</span><span class="o">&gt;</span><span class="p">();</span> - - <span class="k">return</span> <span class="p">&lt;</span><span class="nt">h1</span><span class="p">&gt;</span>Theme: <span class="si">{</span><span class="nx">themeKey</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;;</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="k">default</span> <span class="nx">SectionHeader</span><span class="p">;</span> -</code></pre></div></div> - -<h3 id="storybook-설정">Storybook 설정</h3> - -<p>이제 Storybook에서 <code class="language-plaintext highlighter-rouge">useParams</code>를 모킹하기 위해 필요한 설정을 해본다.</p> - -<p>먼저, <code class="language-plaintext highlighter-rouge">useParams</code>를 모킹할 데코레이터를 만든다.</p> - -<p>이 데코레이터는 Storybook 스토리를 <code class="language-plaintext highlighter-rouge">MemoryRouter</code>로 감싸고 초기 URL을 설정한다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SectionHeader.stories.tsx</span> - -<span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">ReactNode</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="p">{</span> <span class="nx">MemoryRouter</span><span class="p">,</span> <span class="nx">Route</span><span class="p">,</span> <span class="nx">Routes</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-router-dom</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="p">{</span> <span class="nx">Meta</span><span class="p">,</span> <span class="nx">StoryObj</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="nx">SectionHeader</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./SectionHeader</span><span class="dl">"</span><span class="p">;</span> - -<span class="kr">interface</span> <span class="nx">MockUseParamsDecoratorProps</span> <span class="p">{</span> - <span class="nl">themeKey</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> - <span class="nl">children</span><span class="p">:</span> <span class="nx">ReactNode</span><span class="p">;</span> -<span class="p">}</span> - -<span class="kd">function</span> <span class="nx">MockUseParamsDecorator</span><span class="p">({</span> - <span class="nx">themeKey</span><span class="p">,</span> - <span class="nx">children</span><span class="p">,</span> -<span class="p">}:</span> <span class="nx">MockUseParamsDecoratorProps</span><span class="p">)</span> <span class="p">{</span> - <span class="k">return</span> <span class="p">(</span> - <span class="p">&lt;</span><span class="nc">MemoryRouter</span> <span class="na">initialEntries</span><span class="p">=</span><span class="si">{</span><span class="p">[</span><span class="s2">`/theme/</span><span class="p">${</span><span class="nx">themeKey</span><span class="p">}</span><span class="s2">`</span><span class="p">]</span><span class="si">}</span><span class="p">&gt;</span> - <span class="p">&lt;</span><span class="nc">Routes</span><span class="p">&gt;</span> - <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/theme/:themeKey"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="nx">children</span><span class="si">}</span> <span class="p">/&gt;</span> - <span class="p">&lt;/</span><span class="nc">Routes</span><span class="p">&gt;</span> - <span class="p">&lt;/</span><span class="nc">MemoryRouter</span><span class="p">&gt;</span> - <span class="p">);</span> -<span class="p">}</span> -</code></pre></div></div> - -<h3 id="storybook-메타-설정">Storybook 메타 설정</h3> - -<p>이제 <code class="language-plaintext highlighter-rouge">MockUseParamsDecorator</code>를 사용하여 Storybook 메타 설정한다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span> - -<span class="kd">const</span> <span class="nx">meta</span><span class="p">:</span> <span class="nx">Meta</span><span class="o">&lt;</span><span class="k">typeof</span> <span class="nx">SectionHeader</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">common/SectionHeader/Default</span><span class="dl">'</span><span class="p">,</span> - <span class="na">component</span><span class="p">:</span> <span class="nx">SectionHeader</span><span class="p">,</span> - <span class="na">tags</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">autodocs</span><span class="dl">'</span><span class="p">],</span> - <span class="o">**</span><span class="na">decorators</span><span class="p">:</span> <span class="p">[</span> - <span class="p">(</span><span class="nx">Story</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> - <span class="p">&lt;</span><span class="nc">MockUseParamsDecorator</span> <span class="na">themeKey</span><span class="p">=</span><span class="s">"light-gifts"</span><span class="p">&gt;</span> - <span class="p">&lt;</span><span class="nc">Story</span> <span class="p">/&gt;</span> - <span class="p">&lt;/</span><span class="nc">MockUseParamsDecorator</span><span class="p">&gt;</span> - <span class="p">),</span> - <span class="p">],</span><span class="o">**</span> -<span class="p">};</span> - -<span class="k">export</span> <span class="k">default</span> <span class="nx">meta</span><span class="p">;</span> - -<span class="kd">type</span> <span class="nx">Story</span> <span class="o">=</span> <span class="nx">StoryObj</span><span class="o">&lt;</span><span class="k">typeof</span> <span class="nx">SectionHeader</span><span class="o">&gt;</span><span class="p">;</span> - -<span class="k">export</span> <span class="kd">const</span> <span class="nx">Default</span><span class="p">:</span> <span class="nx">Story</span> <span class="o">=</span> <span class="p">{};</span> -</code></pre></div></div> - -<p>이 설정을 통해 <code class="language-plaintext highlighter-rouge">SectionHeader</code> 컴포넌트는 <code class="language-plaintext highlighter-rouge">themeKey</code>가 “light-gifts”인 URL로 렌더링하게 된다.</p> - -<p><br /> -<br /> -<br /></p> - -<h1 id="storybook-절대경로-설정">Storybook 절대경로 설정</h1> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F598b5c49-7b46-4b16-b2a6-8df22c980fe3%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-06-27_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.53.37.png?id=482720fe-061b-400d-9bc9-031c989c82b2&amp;table=block" alt="스크린샷 2024-06-27 오후 9.53.37.png" /></p> - -<p>Button 컴포넌트의 스토리를 작성하고 스토리북을 실행해본 결과 Button 컴포넌트를 찾을 수 없다고 나왔다</p> - -<p>컴포넌트의 경로를 상대 경로로 변경한 후 스토리북을 실행해본 결과가 잘 나오는 것을 확인할 수 있었고, 이는 절대 경로 설정으로 인해 생긴 이슈로 판단하게 되었다.</p> - -<p>Storybook에서의 절대 경로 이슈를 구글링해본 결과 <code class="language-plaintext highlighter-rouge">tsconfig-paths-webpack-plugin</code>을 설치하고 스토리북을 설정하면 해결할 수 있다는 것을 알게되었다.</p> - -<p>이후 아래의 공식 문서를 통해 해당 플러그인에 대해 검색하여 자세히 확인해보았다.</p> - -<p><a href="https://storybook.js.org/docs/builders/webpack#typescript-modules-are-not-resolved-within-storybook">Storybook</a></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F245ce781-340a-4e4b-b4e7-a8a0509413e1%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-06-27_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_10.21.44.png?id=57019eca-6808-4557-91ce-df5be0711f1f&amp;table=block" alt="스크린샷 2024-06-27 오후 10.21.44.png" /></p> - -<p>위 공식 문서를 토대로 <code class="language-plaintext highlighter-rouge">tsconfig-paths-webpack-plugin</code>을 설치하기 위해 <code class="language-plaintext highlighter-rouge">npm i -D tsconfig-paths-webpack-plugin</code> 명령어를 입력한 후, <code class="language-plaintext highlighter-rouge">.storybook/main.ts</code> 파일에 webpackFinal을 다음과 같이 추가했다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">StorybookConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@storybook/react-webpack5</span><span class="dl">"</span><span class="p">;</span> -<span class="k">import</span> <span class="nx">TsconfigPathsPlugin</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">tsconfig-paths-webpack-plugin</span><span class="dl">"</span><span class="p">;</span> - -<span class="kd">const</span> <span class="nx">config</span><span class="p">:</span> <span class="nx">StorybookConfig</span> <span class="o">=</span> <span class="p">{</span> - <span class="na">stories</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">../src/**/*.mdx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">../src/**/*.stories.@(js|jsx|mjs|ts|tsx)</span><span class="dl">"</span><span class="p">],</span> - <span class="na">addons</span><span class="p">:</span> <span class="p">[</span> - <span class="dl">"</span><span class="s2">@storybook/preset-create-react-app</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-onboarding</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-links</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-essentials</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@chromatic-com/storybook</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">@storybook/addon-interactions</span><span class="dl">"</span><span class="p">,</span> - <span class="p">],</span> - <span class="na">framework</span><span class="p">:</span> <span class="p">{</span> - <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@storybook/react-webpack5</span><span class="dl">"</span><span class="p">,</span> - <span class="na">options</span><span class="p">:</span> <span class="p">{},</span> - <span class="p">},</span> - <span class="na">staticDirs</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">../public</span><span class="dl">"</span><span class="p">],</span> - <span class="na">webpackFinal</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">config</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> - <span class="k">if</span> <span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">resolve</span><span class="p">)</span> <span class="p">{</span> - <span class="nx">config</span><span class="p">.</span><span class="nx">resolve</span><span class="p">.</span><span class="nx">plugins</span> <span class="o">=</span> <span class="p">[</span> - <span class="p">...(</span><span class="nx">config</span><span class="p">.</span><span class="nx">resolve</span><span class="p">.</span><span class="nx">plugins</span> <span class="o">||</span> <span class="p">[]),</span> - <span class="k">new</span> <span class="nx">TsconfigPathsPlugin</span><span class="p">({</span> - <span class="na">extensions</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">resolve</span><span class="p">.</span><span class="nx">extensions</span><span class="p">,</span> - <span class="p">}),</span> - <span class="p">];</span> - <span class="p">}</span> - <span class="k">return</span> <span class="nx">config</span><span class="p">;</span> - <span class="p">},</span> -<span class="p">};</span> -<span class="k">export</span> <span class="k">default</span> <span class="nx">config</span><span class="p">;</span> -</code></pre></div></div> - -<p>위와 같이 입력하고 다시 확인해본 결과 절대 경로 이슈가 해결된 것을 확인할 수 있었다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F0ad7b380-4e04-48f0-921e-9c825d9b23ff%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-06-27_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.53.20.png?id=88cd9403-69fa-4bdb-a0bc-6bf2511fe024&amp;table=block" alt="결과" /></p> - -<p><br /> -<br /> -<br /></p> - -<h1 id="storybook-온라인-배포하기">Storybook 온라인 배포하기</h1> - -<h2 id="정적-앱으로-내보내기">정적 앱으로 내보내기</h2> - -<p>스토리북을 정적 앱으로 배포하기 위해 <code class="language-plaintext highlighter-rouge">npm run build-storybook</code> 명령어를 실행하면 <code class="language-plaintext highlighter-rouge">storybook-static</code> 디렉토리에 정적인 스토리북이 생성되며, 이를 정적 사이트 호스팅 서비스에 배포할 수 있다.</p> - -<h2 id="크로마틱chromatic으로-배포하기">크로마틱(Chromatic)으로 배포하기</h2> - -<p>스토리북 관리자가 만든 무료 배포 서비스인 <a href="https://www.chromatic.com/?utm_source=storybook_website&amp;utm_medium=link&amp;utm_campaign=storybook">Chromatic</a>을 사용하여 더욱 쉽게 배포할 수 있다.</p> - -<p>Chromatic을 사용하여 클라우드에서 스토리북을 안전하게 배포하고 호스팅하기 위해 설치를 진행해보자.</p> - -<h3 id="chromatic-설치">Chromatic 설치</h3> - -<p><code class="language-plaintext highlighter-rouge">npm install -D chromatic</code> 명령어를 통해 chromatic을 설치한다.</p> - -<p>이후 <a href="https://www.chromatic.com/start?utm_source=storybook_website&amp;utm_medium=link&amp;utm_campaign=storybook">Chromatic</a> 사이트에 들어가서 GitHub 계정으로 로그인하고 연결할 repository를 설정한다.</p> - -<p>Storybook을 클릭하고 프로젝트를 위해 생성된 고유한 <code class="language-plaintext highlighter-rouge">project-token</code>을 복사한다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Ff5d391b2-480a-41dd-bfe2-5ab4fe25791e%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.28.55.png?id=addeabe2-1c5b-480c-b6ac-3e044c316f4f&amp;table=block" alt="스크린샷 2024-07-01 오후 3.28.55.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F502f3e98-769f-4ae3-8bdc-e49deb31aec2%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.29.09.png?id=a37b5bcd-c37c-4be7-87a9-c76886f4b793&amp;table=block" alt="스크린샷 2024-07-01 오후 3.29.09.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fa4b9d559-41c9-4575-a6b2-add7a0d0d3ff%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.29.34.png?id=8b394510-3780-40cc-bca3-e252828fe3ff&amp;table=block" alt="스크린샷 2024-07-01 오후 3.29.34.png" /></p> - -<p><img src="https://app.super.so/_next/image?url=https%3A%2F%2Fassets.super.so%2F103836b2-5162-4915-b9b3-3e82eeac52e9%2Fimages%2F010a9304-0a2f-4b0a-a515-41eddee7dc46%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.30.08.png&amp;w=1080&amp;q=90" alt="스크린샷 2024-07-01 오후 3.30.08.png" /></p> - -<h3 id="명령어를-통한-배포">명령어를 통한 배포</h3> - -<p>명령어를 통해 스토리북을 빌드(build)하고 배포하기 위해 <code class="language-plaintext highlighter-rouge">npx chromatic --project-token={project-token}</code> 명령어를 실행한다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fb2b13388-079f-4831-b87f-acee7b0d49c6%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.44.48.png?id=9805a7bc-1289-491f-abad-76c8c27a3be0&amp;table=block" alt="명령어 배포" /></p> - -<p>완료되면 배포된 스토리북의 <code class="language-plaintext highlighter-rouge">https://random-uuid.chromatic.com</code> 링크를 받을 수 있다. 해당 링크를 팀과 공유하여 피드백을 받으면 된다.</p> - -<h3 id="크로마틱을-통한-지속적-배포ci하기">크로마틱을 통한 지속적 배포(CI)하기</h3> - -<p>이제 프로젝트가 GitHub 저장소에 호스팅 되었으므로 자동으로 스토리북을 배포하기 위해 지속적 통합(continuous integration, CI) 서비스를 이용할 수 있다.</p> - -<p><a href="https://github.com/features/actions">GitHub Actions</a>는 GitHub에 내장된 무료 CI 서비스로, 쉽게 자동으로 배포할 수 있도록 해준다.</p> - -<h3 id="github-actions-추가하기">GitHub Actions 추가하기</h3> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F32539ab7-2d8e-4b87-ac5e-7a3e3f798e6e%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.14.29.png?id=89418012-6cec-4af8-81f0-b61bd80eaabc&amp;table=block" alt="스크린샷 2024-07-01 오후 4.14.29.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fc55e96d2-a3a4-4232-9300-0e65352ab118%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_5.22.52.png?id=fa02786c-839a-4431-b755-a7e8a4906705&amp;table=block" alt="스크린샷 2024-07-01 오후 5.22.52.png" /></p> - -<p>CI로 지속적 배포를 할 것이기 때문에 package.json의 scripts 부분에서 chromatic 명령어를 제거하고, 프로젝트의 기본 폴더에 <code class="language-plaintext highlighter-rouge">.github</code> 디렉토리를 만들고 그 안에 <code class="language-plaintext highlighter-rouge">workflows</code> 디렉토리를 만들어준다.</p> - -<p>이후 내부에 <code class="language-plaintext highlighter-rouge">chromatic.yml</code>이라는 파일을 아래와 같이 생성한다.</p> - -<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow name</span> -<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Chromatic</span><span class="nv"> </span><span class="s">Deployment"</span> - -<span class="c1"># Event for the workflow</span> -<span class="na">on</span><span class="pi">:</span> <span class="s">push</span> - -<span class="c1"># List of jobs</span> -<span class="na">jobs</span><span class="pi">:</span> - <span class="na">test</span><span class="pi">:</span> - <span class="c1"># Operating System</span> - <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> - <span class="c1"># Job steps</span> - <span class="na">steps</span><span class="pi">:</span> - <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v1</span> - <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">yarn</span> - <span class="c1">#👇 Adds Chromatic as a step in the workflow</span> - <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">chromaui/action@v1</span> - <span class="c1"># Options required for Chromatic's GitHub Action</span> - <span class="na">with</span><span class="pi">:</span> - <span class="c1">#👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/ to obtain it</span> - <span class="na">projectToken</span><span class="pi">:</span> <span class="s">$</span> - <span class="na">token</span><span class="pi">:</span> <span class="s">$ // 삭제는 선택!</span> -</code></pre></div></div> - -<p><code class="language-plaintext highlighter-rouge">CHROMATIC_PROJECT_TOKEN</code>을 코드에 기재하면 보안에 취약하기 때문에 따로 GitHub Action의 <code class="language-plaintext highlighter-rouge">secret</code>을 통해 관리해야 한다.</p> - -<p>여기서 <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code>은 사용해도 되고 안 해도 된다.</p> - -<p>.yml 작성이 완료되면 이어서 commit과 push를 하기 전에 <code class="language-plaintext highlighter-rouge">CHROMATIC_PROJECT_TOKEN</code>과<code class="language-plaintext highlighter-rouge">GITHUB_TOKEN(선택)</code>을 secret으로 설정해보자.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F486a0513-267c-4a45-9696-ee74b82265cd%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.35.19.png?id=f550be64-98d6-4ed3-b083-435d968fe924&amp;table=block" alt="스크린샷 2024-07-01 오후 4.35.19.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F366d051d-e08c-4787-bb21-6d02574ac7e8%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.36.28.png?id=09309de8-c581-40f2-9c42-959612ab711c&amp;table=block" alt="스크린샷 2024-07-01 오후 4.36.28.png" /></p> - -<p>위와 같이 먼저 해당 repository에 Settings &gt; Secrets and Variables &gt; Actions &gt; Respository secrets에서 New repository secret버튼을 클릭하고 Storybook을 배포하면서 받은 토큰을 <code class="language-plaintext highlighter-rouge">CHROMATIC_PROJECT_TOKEN</code>에 값으로 추가한다.</p> - -<p>만약, <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code>도 추가할 경우, 개인 설정을 통해 Personal access token을 발급받아야 한다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F6555ea19-6e88-4bb6-b3a9-1cf756110498%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.39.00.png?id=facce4cc-b943-46dd-b348-00dc2b302988&amp;table=block" alt="스크린샷 2024-07-01 오후 4.39.00.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fbd08c28c-e3ab-447f-b6a9-86c4efcd2d37%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.40.00.png?id=eea519b8-bf96-4ad8-b119-e08fa0f4d08a&amp;table=block" alt="스크린샷 2024-07-01 오후 4.40.00.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F68ab7a49-3490-4fa0-84ab-fd036935e1c7%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.41.00.png?id=cc86a84c-1cc6-43bf-95fd-85037b7b9b3f&amp;table=block" alt="스크린샷 2024-07-01 오후 4.41.00.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F9cc93d11-6954-46e1-8708-30cfcfe19af6%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.41.49.png?id=8966db00-270b-4a0b-b0bd-2689ce06ec0d&amp;table=block" alt="스크린샷 2024-07-01 오후 4.41.49.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F1fefa50b-bde5-4fe4-9548-3d8a5f74762f%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.42.25.png?id=917ee94a-4662-49eb-ad1f-20c613a3a28a&amp;table=block" alt="스크린샷 2024-07-01 오후 4.42.25.png" /></p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F81949adf-e08b-44ae-96b1-9f0e23f7360f%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.43.20.png?id=35660ee8-a008-4bfb-ab63-112009dbbb52&amp;table=block" alt="스크린샷 2024-07-01 오후 4.43.20.png" /></p> - -<p>쓰는 목적을 Note에 작성하고 scope은 이단 repo로 설정하였다.</p> - -<p>이후 <strong>Generate Token</strong> 버튼을 클릭하여 token을 생성한다.</p> - -<p>생성한 token은 다른 곳에 잘 적어놓는다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2Fcd54a582-3bca-4e55-a65b-6dec186c4353%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.45.31.png?id=a951ce51-88c1-4c62-8419-d78b6b7bdb7e&amp;table=block" alt="스크린샷 2024-07-01 오후 4.45.31.png" /></p> - -<p>이후 secret에 <strong>GITHUB_TOKEN</strong>이라는 이름으로 secret을 생성하려고 했는데 <strong>“GITHUB_”</strong>라는 이름으로 시작할 수 없다고 하여, <code class="language-plaintext highlighter-rouge">TOKEN</code>이라는 이름으로 다시 secret을 생성하였다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F1f80a2c5-b840-4f3c-b5b2-236d7710d76a%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.46.27.png?id=968c3eae-f10c-467c-b342-6a2a5601f39a&amp;table=block" alt="스크린샷 2024-07-01 오후 4.46.27.png" /></p> - -<p>성공적으로 secret을 추가하고, yml 파일에서 <code class="language-plaintext highlighter-rouge">GITHUB_TOKEN</code> 대신 <code class="language-plaintext highlighter-rouge">TOKEN</code>을 사용하도록 수정한다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span> <span class="nx">Workflow</span> <span class="nx">name</span> -<span class="nx">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Chromatic Deployment</span><span class="dl">'</span> - -<span class="err">#</span> <span class="nx">Event</span> <span class="k">for</span> <span class="nx">the</span> <span class="nx">workflow</span> -<span class="nx">on</span><span class="p">:</span> <span class="nx">push</span> - -<span class="err">#</span> <span class="nx">List</span> <span class="k">of</span> <span class="nx">jobs</span> -<span class="nx">jobs</span><span class="p">:</span> - <span class="nx">test</span><span class="p">:</span> - <span class="err">#</span> <span class="nx">Operating</span> <span class="nx">System</span> - <span class="nx">runs</span><span class="o">-</span><span class="nx">on</span><span class="p">:</span> <span class="nx">ubuntu</span><span class="o">-</span><span class="nx">latest</span> - <span class="err">#</span> <span class="nx">Job</span> <span class="nx">steps</span> - <span class="nx">steps</span><span class="p">:</span> - <span class="o">-</span> <span class="nx">uses</span><span class="p">:</span> <span class="nx">actions</span><span class="o">/</span><span class="nx">checkout</span><span class="p">@</span><span class="nd">v1</span> - <span class="o">-</span> <span class="nx">run</span><span class="p">:</span> <span class="nx">yarn</span> - <span class="err">#👇</span> <span class="nx">Adds</span> <span class="nx">Chromatic</span> <span class="k">as</span> <span class="nx">a</span> <span class="nx">step</span> <span class="k">in</span> <span class="nx">the</span> <span class="nx">workflow</span> - <span class="o">-</span> <span class="nx">uses</span><span class="p">:</span> <span class="nx">chromaui</span><span class="o">/</span><span class="nx">action</span><span class="p">@</span><span class="nd">v1</span> - <span class="err">#</span> <span class="nx">Options</span> <span class="nx">required</span> <span class="k">for</span> <span class="nx">Chromatic</span><span class="dl">'</span><span class="s1">s GitHub Action - with: - #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/ to obtain it - projectToken: $ - # token: $ -</span></code></pre></div></div> - -<p>수정이 완료되면 commit하고 push한다.</p> - -<p>이후 repository의 actions 탭에서 CI 작동 여부를 확인해본다.</p> - -<p><img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F71f24472-4284-4c8b-8c8b-10ace0635c27%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-01_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_5.26.33.png?id=d49643c5-4599-40b4-bedd-7016f9047b04&amp;table=block" alt="스크린샷 2024-07-01 오후 5.26.33.png" /></p> - -<p>잘 작동하는 것을 확인할 수 있다.</p> - -<h3 id="-스토리북-배포-링크-pr-코멘트-자동으로-남기는-액션-추가">+ 스토리북 배포 링크, PR 코멘트 자동으로 남기는 액션 추가</h3> - -<p>![다운로드 (1).png](&lt;<img src="https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F13897cab-0dd6-431f-b847-04477372a586%2F31ac7e4b-41dc-41a9-a33d-c0da53661488%2F%25E1%2584%2583%25E1%2585%25A1%25E1%2584%258B%25E1%2585%25AE%25E1%2586%25AB%25E1%2584%2585%25E1%2585%25A9%25E1%2584%2583%25E1%2585%25B3_(1).png?id=2e4c949f-1647-41e4-a71e-56c4488ff388&amp;table=block" alt="&lt;https://prod-files-secure.s3.us-west-2.amazonaws.com/13897cab-0dd6-431f-b847-04477372a586/31ac7e4b-41dc-41a9-a33d-c0da53661488/%E1%84%83%E1%85%A1%E1%84%8B%E1%85%AE%E1%86%AB%E1%84%85%E1%85%A9%E1%84%83%E1%85%B3_(1).png&gt;" />&gt;)</p> - -<p>다음과 같이 배포 링크를 PR에 코멘트로 자동으로 남기도록 설정할 수 있다.</p> - -<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span> <span class="nx">Workflow</span> <span class="nx">name</span> -<span class="nx">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Chromatic Deployment</span><span class="dl">'</span> - -<span class="err">#</span> <span class="nx">Event</span> <span class="k">for</span> <span class="nx">the</span> <span class="nx">workflow</span> -<span class="nx">on</span><span class="p">:</span> <span class="nx">push</span> - -<span class="err">#</span> <span class="nx">List</span> <span class="k">of</span> <span class="nx">jobs</span> -<span class="nx">jobs</span><span class="p">:</span> - <span class="nx">test</span><span class="p">:</span> - <span class="err">#</span> <span class="nx">Operating</span> <span class="nx">System</span> - <span class="nx">runs</span><span class="o">-</span><span class="nx">on</span><span class="p">:</span> <span class="nx">ubuntu</span><span class="o">-</span><span class="nx">latest</span> - <span class="err">#</span> <span class="nx">Job</span> <span class="nx">steps</span> - <span class="nx">steps</span><span class="p">:</span> - <span class="p">...</span> - <span class="o">-</span> <span class="nx">name</span><span class="p">:</span> <span class="nx">Create</span> <span class="nx">comment</span> <span class="nx">PR</span> - <span class="nx">uses</span><span class="p">:</span> <span class="nx">thollander</span><span class="o">/</span><span class="nx">actions</span><span class="o">-</span><span class="nx">comment</span><span class="o">-</span><span class="nx">pull</span><span class="o">-</span><span class="nx">request</span><span class="p">@</span><span class="nd">v1</span> - <span class="nx">env</span><span class="p">:</span> - <span class="nx">TOKEN</span><span class="p">:</span> <span class="nx">$</span> - <span class="kd">with</span><span class="p">:</span> - <span class="nx">message</span><span class="p">:</span> <span class="dl">"</span><span class="s2">🚀storybook: $</span><span class="dl">"</span> -</code></pre></div></div> - -<p><br /> -<br /> -<br /></p> - -<h1 id="마치면서">마치면서</h1> - -<p>이번 글에서는 Storybook을 설치하고 설정하는 방법을 다뤘보았다.</p> - -<p>Storybook을 통해 UI 컴포넌트를 독립적으로 개발하고 테스트하는 방법을 배우고, Chromatic을 사용하여 스토리북을 배포하고 CI/CD 파이프라인에 통합하는 방법을 알게되었다.</p> - -<p>이러한 과정을 통해 UI 컴포넌트를 더 효율적으로 개발하고, 팀 내에서 공유하고 피드백을 받을 수 있어, 특히 디자이너와의 협업에서의 큰 도움이 될 것 같다는 생각이 들었다.</p>Kang ByeonghyeonStorybook 소개 \ No newline at end of file +<p>독자들은 각 프로젝트의 요구 사항에 맞는 라우팅 방식을 선택해서 보다 효율적으로 라우팅을 관리해보길 바란다.</p>Kang Byeonghyeon \ No newline at end of file diff --git a/_site/frontend.html b/_site/frontend.html index df48dc8..d498946 100644 --- a/_site/frontend.html +++ b/_site/frontend.html @@ -79,6 +79,11 @@

    Hi, I’m Kang Byeong-hyeon.

    ..

    frontend