From e9aef2b001b79a1f34af20493377e2a6e1cf1ede Mon Sep 17 00:00:00 2001 From: kangbyeonghyeon Date: Thu, 5 Sep 2024 00:40:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20"[=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=ED=85=8C=ED=81=AC=20=EC=BA=A0=ED=8D=BC=EC=8A=A4=202=EA=B8=B0]?= =?UTF-8?q?=202=EC=B0=A8=20=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=20=EC=A7=84=ED=96=89=EA=B8=B0"=20=ED=8F=AC=EC=8A=A4?= =?UTF-8?q?=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" | 366 ++++++++ _site/archive.html | 5 + _site/feed.xml | 796 ++++++++---------- _site/index.html | 15 +- _site/kakao2.html | 5 + _site/sitemap.xml | 4 + ...\354\247\204\355\226\211\352\270\260.html" | 448 ++++++++++ 7 files changed, 1192 insertions(+), 447 deletions(-) create mode 100644 "_posts/2024-07-22-[\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] 2\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" create mode 100644 "_site/\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-2\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.html" diff --git "a/_posts/2024-07-22-[\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] 2\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-22-[\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] 2\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" new file mode 100644 index 0000000..dd2bc69 --- /dev/null +++ "b/_posts/2024-07-22-[\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] 2\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" @@ -0,0 +1,366 @@ +--- +layout: post +title: "[카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기" +category: kakao2 +--- + +
+
+
+ +이전 코드리뷰에 이어서 **불필요한 코드 정리**, **성능 최적화**, 그리고 **타입 충돌 해결** 등의 내용에 집중해 리팩토링을 진행해보았다. + +이번 포스팅에서는, 자주 사용했던 **rest operator**와 **barrel file pattern**, 그리고 **절대 경로** 문제를 해결하는 과정을 다루고, 이를 바탕으로 리팩토링한 주요 항목들을 정리해보았다. + +
+
+
+ +# 1. .env 파일이 깃허브에 올라갔을 때 해결 방법 + +프로젝트의 **.env** 파일이 깃허브에 실수로 올라간 경우, 해당 파일을 원격 저장소에서 삭제하면서도 로컬에서는 유지하는 방법을 사용해야한다. + +이는 다음 명령어를 통해 해결할 수 있었다. + +
+ +```bash +git rm --cached .env +``` + +
+ +이후 다시 커밋하고 푸시하면 **.env** 파일이 원격 저장소에서 삭제되지만, 로컬 개발 환경에서는 계속 사용할 수 있었다. + +
+
+
+ +# 2. 불필요한 rest operator 제거 + +**rest operator**를 습관적으로 사용하던 문제가 있었다. + +특히, 컴포넌트를 구현할 때 필수적으로 사용해야 한다고 오해한 부분이 있었지만, **제한된 props**만을 받는 컴포넌트에서는 불필요한 경우가 많다. + +예를 들어, 아래의 `Button` 컴포넌트 같은 경우, rest operator를 사용하여 Button의 다양한 속성들을 props로 받을 수 있게 설계할 수 있다. + +
+ +```tsx +type ButtonTheme = "kakao" | "primary" | "darkGray"; +type ButtonSize = "small" | "large" | "responsive"; + +export interface ButtonProps extends ButtonHTMLAttributes { + theme?: ButtonTheme; + size?: ButtonSize; + children: ReactNode; +} + +export default function Button({ + theme = "kakao", + size = "large", + children, + ...rest +}: ButtonProps) { + return ( + + {children} + + ); +} +``` + +
+ +하지만 특정 props만을 받는 **GoodsItem** 컴포넌트를 예시로 들면, rest operator를 사용하여 추가적인 props를 받을 필요가 없다. + +
+ +```tsx +interface GoodsItemProps { + imageSrc: string; + subtitle: string; + title: string; + amount: number; + rankingIndex?: number; +} + +export default function GoodsItem({ + imageSrc, + subtitle, + title, + amount, + rankingIndex, + ...rest +}: GoodsItemProps) { + const imageSize = rankingIndex ? IMAGE_SIZE_RANKING : IMAGE_SIZE_GOODS_ITEM; + + const renderRanking = () => + rankingIndex && ; + + return ( + + ... + + ); +} +``` + +
+ +때문에 아래와 같이 제한된 props를 받는 컴포넌트에서 불필요하게 rest operator를 사용하는 것은 피하고, 필요한 props만을 명시적으로 전달하는 방식으로 개선할 수 있었다. + +
+ +```tsx +interface GoodsItemProps { + imageSrc: string; + subtitle: string; + title: string; + amount: number; + rankingIndex?: number; +} + +export default function GoodsItem({ + imageSrc, + subtitle, + title, + amount, + rankingIndex, +}: GoodsItemProps) { + const imageSize = rankingIndex ? IMAGE_SIZE_RANKING : IMAGE_SIZE_GOODS_ITEM; + + const renderRanking = () => + rankingIndex && ; + + return ...; +} +``` + +
+
+
+ +# 3. 불필요한 Barrel File Pattern 지양 + +```tsx +export { default as Button } from "./Button"; +export { default as Footer } from "../features/Layout/Footer"; +export { default as InputField } from "./Form/InputField"; +export { default as Image } from "./Image"; +export { default as GoodsItem } from "./GoodsItem"; +export { default as Ranking } from "./GoodsItem/Ranking"; +export { default as Header } from "../features/Layout/Header"; +export { default as Container } from "./Layout/Container"; +export { default as Grid } from "./Layout/Grid"; +export { default as CenteredContainer } from "./Layout/CenteredContainer"; +export { default as StatusHandler } from "./StatusHandler"; +``` + +
+ +초기에 **barrel file pattern**을 사용하여 `index.ts` 파일에서 여러 컴포넌트를 한 번에 export하는 방식을 사용했었다. + +믈론, import 구문을 간결하게 만들 수 있는 장점은 있지만, 프로젝트 규모가 커지게 되면 모든 모듈들을 한번에 로드하는 데 시간이 오래 걸릴 수 있고, 성능에 부정적인 영향을 미칠 수 있다. + +
+ +- **모든 모듈 로드 문제**: 배럴 파일은 폴더 내의 모든 모듈을 한 번에 로드하기 때문에, 필요하지 않은 모듈까지 로드되어 성능 저하가 발생할 수 있다. +- **불필요한 사용**: 소규모 컴포넌트나 단일 용도의 컴포넌트에까지 배럴 파일을 사용하면 오히려 가독성을 떨어뜨리고, 성능에 부정적인 영향을 미칠 수 있다. + +
+ +때문에, **`components/common` 경로**와 같이 여러 공통 컴포넌트들을 관리하는 디렉토리에서는 배럴 파일의 사용을 유지하였고, 소수의 컴포넌트만 있는 디렉토리에서는 **불필요한 배럴 파일을 제거**하여 불필요한 모듈 로드와 성능 저하를 예방하도록 개선하게 되었다. + +
+ +배럴 파일 사용 시의 성능 문제에 대한 자세한 정보는 해당 [문서](https://github.com/yeonjuan/dev-blog/blob/master/JavaScript/speeding-up-the-javascript-ecosystem-the-barrel-file-debacle.md)를 참고하였다. + +
+
+
+ +# 4. useInfiniteScroll 훅 개선 + +```tsx +import { useEffect } from "react"; +import { useInView } from "react-intersection-observer"; + +interface useInfiniteScrollProps { + fetchNextPage: () => void; + hasNextPage?: boolean; + isFetchingNextPage?: boolean; +} + +export default function useInfiniteScroll({ + fetchNextPage, + hasNextPage, + isFetchingNextPage, +}: useInfiniteScrollProps) { + const { ref, inView } = useInView(); + + useEffect(() => { + if (inView && hasNextPage && !isFetchingNextPage) fetchNextPage(); + }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]); + + return ref; +} +``` + +
+ +기존 **useInfiniteScroll** 훅은 react-query와 결합되어 있다. + +하지만 해당 훅의 핵심 역할은 **스크롤이 끝에 도달하면 fetchNextPage를 호출**하는 것이므로, 데이터 의존성을 낮추고 훅의 역할을 더 명확히 하기 위해서는 **조건문**을 받아 처리하는 방식으로 리팩토링하는 것이 낫은 방식이라고 판단하였다. + +
+ +```tsx +import { useEffect } from "react"; +import { useInView } from "react-intersection-observer"; + +interface useInfiniteScrollProps { + condition: boolean; + fetchNextPage: () => void; +} + +export default function useInfiniteScroll({ + condition, + fetchNextPage, +}: useInfiniteScrollProps) { + const { ref, inView } = useInView(); + + useEffect(() => { + if (inView && condition) fetchNextPage(); + }, [inView, condition, fetchNextPage]); + + return ref; +} +``` + +
+ +이렇게 되면 해당 훅은 조건문을 받아서 fetchNextPage를 호출하는 역할만 수행하게 되므로 react-query와의 결합은 줄이고 유연성은 높일 수 있다. + +
+
+
+ +# 5. @types 절대경로 충돌 해결 + +기존에 `@types`로 aliasing하니 module을 찾을 수 없다는 에러가 발생하였다. + +원인은 `node_modules`에 있는 `@types`와 충돌되었기 때문이었다. + +해결 방법은 두 가지가 있다. + +
+ +첫번째는 `"@types/custom/*": ["src/types/custom/*"]`처럼 `@types` 하위에 별도 중첩 네임스페이스를 사용해서 피하는 방식과 다른 이름을 쓰는 방식이있다. + +하지만, npm 패키지에서 설치한 타입인지 내부 프로젝트에서 직접 관리하는 코드인지 쉽게 알기 힘들기에, 두번째 방법으로 그냥 다른 이름으로 aliasing하는 것이 더 좋은 방법이 될 수 있다고 판단하였다. + +따라서 프로젝트 내부에서 사용되는 타입이라는 의미로 `@internalTypes`로 aliasing 하기로 결정하였다. + +
+ +이후 `tsconfig`, `craco.config`, `eslintrc` 파일의 절대경로 alias를 다시 작성하여 해결하였다. + +
+ +```tsx +// tsconfig.path.json + +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@components/*": ["./src/components/*"], + "@assets/*": ["./src/assets/*"], + "@hooks/*": ["./src/hooks/*"], + "@pages/*": ["./src/pages/*"], + "@routes/*": ["./src/routes/*"], + "@utils/*": ["./src/utils/*"], + "@context/*": ["./src/context/*"], + "@internalTypes/*": ["./src/types/*"], + "@apis/*": ["./src/apis/*"] + } + } +} +``` + +
+ +```tsx +// craco.config.js + +const path = require("path"); + +module.exports = { + webpack: { + alias: { + "@": path.resolve(__dirname, "src/"), + "@components": path.resolve(__dirname, "src/components"), + "@assets": path.resolve(__dirname, "src/assets"), + "@hooks": path.resolve(__dirname, "src/hooks"), + "@pages": path.resolve(__dirname, "src/pages"), + "@routes": path.resolve(__dirname, "src/routes"), + "@utils": path.resolve(__dirname, "src/utils"), + "@context": path.resolve(__dirname, "src/context"), + "@internalTypes": path.resolve(__dirname, "src/types"), + "@apis": path.resolve(__dirname, "src/apis"), + }, + }, +}; +``` + +
+ +```tsx +// .eslintrc + +{ + "settings": { + "import/resolver": { + "alias": { + "map": [ + ["@", "./src"], + ["@components", "./src/components"], + ["@assets", "./src/assets"], + ["@hooks", "./src/hooks"], + ["@pages", "./src/pages"], + ["@routes", "./src/routes"], + ["@utils", "./src/utils"], + ["@context", "./src/context"], + ["@internalTypes", "./src/types"], + ["@apis", "./src/apis"] + ], + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + } +} +``` + +
+
+
+ +# 마치면서 + +이번 리팩토링 과정에서, **rest operator**를 습관적으로 남발했던 점을 되돌아볼 수 있었다. + +특히, **제한된 props만을 받는 컴포넌트**에서는 rest operator가 오히려 **불필요한 코드**가 될 수 있다는 것을 깨달았다. + +
+ +다음으로, **barrel file pattern**을 사용해 import 구문을 간결하게 만드는 데 집중했으나, 프로젝트가 커질수록 **불필요한 모듈 로드**로 인해 **성능 저하**가 발생할 수 있다는 문제를 알게 되었다. + +모든 컴포넌트를 배럴 파일로 묶는 것이 항상 최선의 방법은 아니었고, **컴포넌트 수가 많은 디렉토리에서는 배럴 파일을 유지하되**, 그렇지 않은 경우에는 배럴 파일을 제거하여 **불필요한 의존성 로드**를 줄이는 것이 성능 최적화에 도움이 된다는 것을 알 수 있었다. + +
+ +마지막으로, **절대 경로 설정**이 편리하지만, `@types`와 같은 네임스페이스와 **충돌이 발생할 수 있다는 문제**도 알게 되었다. + +프로젝트 내부에서 사용하는 타입과 외부 라이브러리의 타입이 충돌할 수 있기 때문에, 이를 해결하기 위한 **aliasing 전략**이 중요하다는 것을 알게 되었다. diff --git a/_site/archive.html b/_site/archive.html index 2bd9912..3780cb9 100644 --- a/_site/archive.html +++ b/_site/archive.html @@ -83,6 +83,11 @@

Hi, I’m Kang Byeong-hyeon.

[우선] 우선을 중단하며 +
  • + 2024-07-22 + [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기
  • 2024-07-21 Jekyll2024-09-04T23:55:55+09:00http://localhost:4000/feed.xmlKang Byeong-hyeon 👨🏻‍💻Kang Byeonghyeon's dev blog +Jekyll2024-09-05T00:39:46+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 /> <br /> <br /></p> @@ -87,7 +87,358 @@ <p><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%2F177cb61f-fded-413f-8102-8744f3785e96%2FUntitled.png?id=085baf51-28de-4570-9ca5-9dac1eaff929&amp;table=block" alt="Untitled" /></p>Kang ByeonghyeonHusky와 lint-staged로 Git Hooks 자동화하기2024-07-21T00:00:00+09:002024-07-21T00:00:00+09:00http://localhost:4000/Husky%EC%99%80%20lint-staged%EB%A1%9C%20Git%20Hooks%20%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0<p><br /> +<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%2F177cb61f-fded-413f-8102-8744f3785e96%2FUntitled.png?id=085baf51-28de-4570-9ca5-9dac1eaff929&amp;table=block" alt="Untitled" /></p>Kang Byeonghyeon[카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기2024-07-22T00:00:00+09:002024-07-22T00:00:00+09:00http://localhost:4000/%5B%EC%B9%B4%EC%B9%B4%EC%98%A4%20%ED%85%8C%ED%81%AC%20%EC%BA%A0%ED%8D%BC%EC%8A%A4%202%EA%B8%B0%5D%202%EC%B0%A8%20%EC%BD%94%EB%93%9C%EB%A6%AC%EB%B7%B0%20%EB%B0%98%EC%98%81%20%EB%B0%8F%20%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%20%EC%A7%84%ED%96%89%EA%B8%B0<p><br /> +<br /> +<br /></p> + +<p>이전 코드리뷰에 이어서 <strong>불필요한 코드 정리</strong>, <strong>성능 최적화</strong>, 그리고 <strong>타입 충돌 해결</strong> 등의 내용에 집중해 리팩토링을 진행해보았다.</p> + +<p>이번 포스팅에서는, 자주 사용했던 <strong>rest operator</strong>와 <strong>barrel file pattern</strong>, 그리고 <strong>절대 경로</strong> 문제를 해결하는 과정을 다루고, 이를 바탕으로 리팩토링한 주요 항목들을 정리해보았다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="1-env-파일이-깃허브에-올라갔을-때-해결-방법">1. .env 파일이 깃허브에 올라갔을 때 해결 방법</h1> + +<p>프로젝트의 <strong>.env</strong> 파일이 깃허브에 실수로 올라간 경우, 해당 파일을 원격 저장소에서 삭제하면서도 로컬에서는 유지하는 방법을 사용해야한다.</p> + +<p>이는 다음 명령어를 통해 해결할 수 있었다.</p> + +<p><br /></p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git <span class="nb">rm</span> <span class="nt">--cached</span> .env +</code></pre></div></div> + +<p><br /></p> + +<p>이후 다시 커밋하고 푸시하면 <strong>.env</strong> 파일이 원격 저장소에서 삭제되지만, 로컬 개발 환경에서는 계속 사용할 수 있었다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="2-불필요한-rest-operator-제거">2. 불필요한 rest operator 제거</h1> + +<p><strong>rest operator</strong>를 습관적으로 사용하던 문제가 있었다.</p> + +<p>특히, 컴포넌트를 구현할 때 필수적으로 사용해야 한다고 오해한 부분이 있었지만, <strong>제한된 props</strong>만을 받는 컴포넌트에서는 불필요한 경우가 많다.</p> + +<p>예를 들어, 아래의 <code class="language-plaintext highlighter-rouge">Button</code> 컴포넌트 같은 경우, rest operator를 사용하여 Button의 다양한 속성들을 props로 받을 수 있게 설계할 수 있다.</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">ButtonTheme</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">kakao</span><span class="dl">"</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">darkGray</span><span class="dl">"</span><span class="p">;</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">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="k">export</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">kakao</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">large</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">rest</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">rest</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><br /></p> + +<p>하지만 특정 props만을 받는 <strong>GoodsItem</strong> 컴포넌트를 예시로 들면, rest operator를 사용하여 추가적인 props를 받을 필요가 없다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">GoodsItemProps</span> <span class="p">{</span> + <span class="nl">imageSrc</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">subtitle</span><span class="p">:</span> <span class="kr">string</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">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">rankingIndex</span><span class="p">?:</span> <span class="kr">number</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">GoodsItem</span><span class="p">({</span> + <span class="nx">imageSrc</span><span class="p">,</span> + <span class="nx">subtitle</span><span class="p">,</span> + <span class="nx">title</span><span class="p">,</span> + <span class="nx">amount</span><span class="p">,</span> + <span class="nx">rankingIndex</span><span class="p">,</span> + <span class="p">...</span><span class="nx">rest</span> +<span class="p">}:</span> <span class="nx">GoodsItemProps</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">imageSize</span> <span class="o">=</span> <span class="nx">rankingIndex</span> <span class="p">?</span> <span class="nx">IMAGE_SIZE_RANKING</span> <span class="p">:</span> <span class="nx">IMAGE_SIZE_GOODS_ITEM</span><span class="p">;</span> + + <span class="kd">const</span> <span class="nx">renderRanking</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> + <span class="nx">rankingIndex</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nc">Ranking</span> <span class="na">rankingIndex</span><span class="p">=</span><span class="si">{</span><span class="nx">rankingIndex</span><span class="si">}</span> <span class="p">/&gt;;</span> + + <span class="k">return</span> <span class="p">(</span> + <span class="p">&lt;</span><span class="nc">StyledGoodsItem</span> <span class="na">rankingIndex</span><span class="p">=</span><span class="si">{</span><span class="nx">rankingIndex</span><span class="si">}</span> <span class="si">{</span><span class="p">...</span><span class="nx">rest</span><span class="si">}</span><span class="p">&gt;</span> + ... + <span class="p">&lt;/</span><span class="nc">StyledGoodsItem</span><span class="p">&gt;</span> + <span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p>때문에 아래와 같이 제한된 props를 받는 컴포넌트에서 불필요하게 rest operator를 사용하는 것은 피하고, 필요한 props만을 명시적으로 전달하는 방식으로 개선할 수 있었다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">GoodsItemProps</span> <span class="p">{</span> + <span class="nl">imageSrc</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> + <span class="nl">subtitle</span><span class="p">:</span> <span class="kr">string</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">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> + <span class="nl">rankingIndex</span><span class="p">?:</span> <span class="kr">number</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">GoodsItem</span><span class="p">({</span> + <span class="nx">imageSrc</span><span class="p">,</span> + <span class="nx">subtitle</span><span class="p">,</span> + <span class="nx">title</span><span class="p">,</span> + <span class="nx">amount</span><span class="p">,</span> + <span class="nx">rankingIndex</span><span class="p">,</span> +<span class="p">}:</span> <span class="nx">GoodsItemProps</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">imageSize</span> <span class="o">=</span> <span class="nx">rankingIndex</span> <span class="p">?</span> <span class="nx">IMAGE_SIZE_RANKING</span> <span class="p">:</span> <span class="nx">IMAGE_SIZE_GOODS_ITEM</span><span class="p">;</span> + + <span class="kd">const</span> <span class="nx">renderRanking</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> + <span class="nx">rankingIndex</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nc">Ranking</span> <span class="na">rankingIndex</span><span class="p">=</span><span class="si">{</span><span class="nx">rankingIndex</span><span class="si">}</span> <span class="p">/&gt;;</span> + + <span class="k">return</span> <span class="p">&lt;</span><span class="nc">StyledGoodsItem</span> <span class="na">rankingIndex</span><span class="p">=</span><span class="si">{</span><span class="nx">rankingIndex</span><span class="si">}</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nc">StyledGoodsItem</span><span class="p">&gt;;</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /> +<br /> +<br /></p> + +<h1 id="3-불필요한-barrel-file-pattern-지양">3. 불필요한 Barrel File Pattern 지양</h1> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Button</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Button</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Footer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../features/Layout/Footer</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">InputField</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Form/InputField</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Image</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Image</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">GoodsItem</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./GoodsItem</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Ranking</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./GoodsItem/Ranking</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Header</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../features/Layout/Header</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Container</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Layout/Container</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">Grid</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Layout/Grid</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">CenteredContainer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./Layout/CenteredContainer</span><span class="dl">"</span><span class="p">;</span> +<span class="k">export</span> <span class="p">{</span> <span class="k">default</span> <span class="k">as</span> <span class="nx">StatusHandler</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./StatusHandler</span><span class="dl">"</span><span class="p">;</span> +</code></pre></div></div> + +<p><br /></p> + +<p>초기에 <strong>barrel file pattern</strong>을 사용하여 <code class="language-plaintext highlighter-rouge">index.ts</code> 파일에서 여러 컴포넌트를 한 번에 export하는 방식을 사용했었다.</p> + +<p>믈론, import 구문을 간결하게 만들 수 있는 장점은 있지만, 프로젝트 규모가 커지게 되면 모든 모듈들을 한번에 로드하는 데 시간이 오래 걸릴 수 있고, 성능에 부정적인 영향을 미칠 수 있다.</p> + +<p><br /></p> + +<ul> + <li><strong>모든 모듈 로드 문제</strong>: 배럴 파일은 폴더 내의 모든 모듈을 한 번에 로드하기 때문에, 필요하지 않은 모듈까지 로드되어 성능 저하가 발생할 수 있다.</li> + <li><strong>불필요한 사용</strong>: 소규모 컴포넌트나 단일 용도의 컴포넌트에까지 배럴 파일을 사용하면 오히려 가독성을 떨어뜨리고, 성능에 부정적인 영향을 미칠 수 있다.</li> +</ul> + +<p><br /></p> + +<p>때문에, <strong><code class="language-plaintext highlighter-rouge">components/common</code> 경로</strong>와 같이 여러 공통 컴포넌트들을 관리하는 디렉토리에서는 배럴 파일의 사용을 유지하였고, 소수의 컴포넌트만 있는 디렉토리에서는 <strong>불필요한 배럴 파일을 제거</strong>하여 불필요한 모듈 로드와 성능 저하를 예방하도록 개선하게 되었다.</p> + +<p><br /></p> + +<p>배럴 파일 사용 시의 성능 문제에 대한 자세한 정보는 해당 <a href="https://github.com/yeonjuan/dev-blog/blob/master/JavaScript/speeding-up-the-javascript-ecosystem-the-barrel-file-debacle.md">문서</a>를 참고하였다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="4-useinfinitescroll-훅-개선">4. useInfiniteScroll 훅 개선</h1> + +<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">useEffect</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">useInView</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-intersection-observer</span><span class="dl">"</span><span class="p">;</span> + +<span class="kr">interface</span> <span class="nx">useInfiniteScrollProps</span> <span class="p">{</span> + <span class="nl">fetchNextPage</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span> + <span class="nl">hasNextPage</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span> + <span class="nl">isFetchingNextPage</span><span class="p">?:</span> <span class="nx">boolean</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">useInfiniteScroll</span><span class="p">({</span> + <span class="nx">fetchNextPage</span><span class="p">,</span> + <span class="nx">hasNextPage</span><span class="p">,</span> + <span class="nx">isFetchingNextPage</span><span class="p">,</span> +<span class="p">}:</span> <span class="nx">useInfiniteScrollProps</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">ref</span><span class="p">,</span> <span class="nx">inView</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useInView</span><span class="p">();</span> + + <span class="nx">useEffect</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">inView</span> <span class="o">&amp;&amp;</span> <span class="nx">hasNextPage</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">isFetchingNextPage</span><span class="p">)</span> <span class="nx">fetchNextPage</span><span class="p">();</span> + <span class="p">},</span> <span class="p">[</span><span class="nx">inView</span><span class="p">,</span> <span class="nx">hasNextPage</span><span class="p">,</span> <span class="nx">isFetchingNextPage</span><span class="p">,</span> <span class="nx">fetchNextPage</span><span class="p">]);</span> + + <span class="k">return</span> <span class="nx">ref</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p>기존 <strong>useInfiniteScroll</strong> 훅은 react-query와 결합되어 있다.</p> + +<p>하지만 해당 훅의 핵심 역할은 <strong>스크롤이 끝에 도달하면 fetchNextPage를 호출</strong>하는 것이므로, 데이터 의존성을 낮추고 훅의 역할을 더 명확히 하기 위해서는 <strong>조건문</strong>을 받아 처리하는 방식으로 리팩토링하는 것이 낫은 방식이라고 판단하였다.</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">useEffect</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">useInView</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-intersection-observer</span><span class="dl">"</span><span class="p">;</span> + +<span class="kr">interface</span> <span class="nx">useInfiniteScrollProps</span> <span class="p">{</span> + <span class="nl">condition</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">;</span> + <span class="nl">fetchNextPage</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">void</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">useInfiniteScroll</span><span class="p">({</span> + <span class="nx">condition</span><span class="p">,</span> + <span class="nx">fetchNextPage</span><span class="p">,</span> +<span class="p">}:</span> <span class="nx">useInfiniteScrollProps</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="p">{</span> <span class="nx">ref</span><span class="p">,</span> <span class="nx">inView</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useInView</span><span class="p">();</span> + + <span class="nx">useEffect</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">inView</span> <span class="o">&amp;&amp;</span> <span class="nx">condition</span><span class="p">)</span> <span class="nx">fetchNextPage</span><span class="p">();</span> + <span class="p">},</span> <span class="p">[</span><span class="nx">inView</span><span class="p">,</span> <span class="nx">condition</span><span class="p">,</span> <span class="nx">fetchNextPage</span><span class="p">]);</span> + + <span class="k">return</span> <span class="nx">ref</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p>이렇게 되면 해당 훅은 조건문을 받아서 fetchNextPage를 호출하는 역할만 수행하게 되므로 react-query와의 결합은 줄이고 유연성은 높일 수 있다.</p> + +<p><br /> +<br /> +<br /></p> + +<h1 id="5-types-절대경로-충돌-해결">5. @types 절대경로 충돌 해결</h1> + +<p>기존에 <code class="language-plaintext highlighter-rouge">@types</code>로 aliasing하니 module을 찾을 수 없다는 에러가 발생하였다.</p> + +<p>원인은 <code class="language-plaintext highlighter-rouge">node_modules</code>에 있는 <code class="language-plaintext highlighter-rouge">@types</code>와 충돌되었기 때문이었다.</p> + +<p>해결 방법은 두 가지가 있다.</p> + +<p><br /></p> + +<p>첫번째는 <code class="language-plaintext highlighter-rouge">"@types/custom/*": ["src/types/custom/*"]</code>처럼 <code class="language-plaintext highlighter-rouge">@types</code> 하위에 별도 중첩 네임스페이스를 사용해서 피하는 방식과 다른 이름을 쓰는 방식이있다.</p> + +<p>하지만, npm 패키지에서 설치한 타입인지 내부 프로젝트에서 직접 관리하는 코드인지 쉽게 알기 힘들기에, 두번째 방법으로 그냥 다른 이름으로 aliasing하는 것이 더 좋은 방법이 될 수 있다고 판단하였다.</p> + +<p>따라서 프로젝트 내부에서 사용되는 타입이라는 의미로 <code class="language-plaintext highlighter-rouge">@internalTypes</code>로 aliasing 하기로 결정하였다.</p> + +<p><br /></p> + +<p>이후 <code class="language-plaintext highlighter-rouge">tsconfig</code>, <code class="language-plaintext highlighter-rouge">craco.config</code>, <code class="language-plaintext highlighter-rouge">eslintrc</code> 파일의 절대경로 alias를 다시 작성하여 해결하였다.</p> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// tsconfig.path.json</span> + +<span class="p">{</span> + <span class="dl">"</span><span class="s2">compilerOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">baseUrl</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">.</span><span class="dl">"</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">paths</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">@/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@components/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/components/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@assets/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/assets/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@hooks/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/hooks/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@pages/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/pages/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@routes/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/routes/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@utils/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/utils/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@context/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/context/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@internalTypes/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/types/*</span><span class="dl">"</span><span class="p">],</span> + <span class="dl">"</span><span class="s2">@apis/*</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">./src/apis/*</span><span class="dl">"</span><span class="p">]</span> + <span class="p">}</span> + <span class="p">}</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// craco.config.js</span> + +<span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">path</span><span class="dl">"</span><span class="p">);</span> + +<span class="kr">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span> + <span class="na">webpack</span><span class="p">:</span> <span class="p">{</span> + <span class="na">alias</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">@</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@components</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/components</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@assets</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/assets</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@hooks</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/hooks</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@pages</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/pages</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@routes</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/routes</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@utils</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/utils</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@context</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/context</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@internalTypes</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/types</span><span class="dl">"</span><span class="p">),</span> + <span class="dl">"</span><span class="s2">@apis</span><span class="dl">"</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">"</span><span class="s2">src/apis</span><span class="dl">"</span><span class="p">),</span> + <span class="p">},</span> + <span class="p">},</span> +<span class="p">};</span> +</code></pre></div></div> + +<p><br /></p> + +<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .eslintrc</span> + +<span class="p">{</span> + <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">alias</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> + <span class="dl">"</span><span class="s2">map</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@components</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/components</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@assets</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/assets</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@hooks</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/hooks</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@pages</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/pages</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@routes</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/routes</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@utils</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/utils</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@context</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/context</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@internalTypes</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/types</span><span class="dl">"</span><span class="p">],</span> + <span class="p">[</span><span class="dl">"</span><span class="s2">@apis</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/apis</span><span class="dl">"</span><span class="p">]</span> + <span class="p">],</span> + <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</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> +</code></pre></div></div> + +<p><br /> +<br /> +<br /></p> + +<h1 id="마치면서">마치면서</h1> + +<p>이번 리팩토링 과정에서, <strong>rest operator</strong>를 습관적으로 남발했던 점을 되돌아볼 수 있었다.</p> + +<p>특히, <strong>제한된 props만을 받는 컴포넌트</strong>에서는 rest operator가 오히려 <strong>불필요한 코드</strong>가 될 수 있다는 것을 깨달았다.</p> + +<p><br /></p> + +<p>다음으로, <strong>barrel file pattern</strong>을 사용해 import 구문을 간결하게 만드는 데 집중했으나, 프로젝트가 커질수록 <strong>불필요한 모듈 로드</strong>로 인해 <strong>성능 저하</strong>가 발생할 수 있다는 문제를 알게 되었다.</p> + +<p>모든 컴포넌트를 배럴 파일로 묶는 것이 항상 최선의 방법은 아니었고, <strong>컴포넌트 수가 많은 디렉토리에서는 배럴 파일을 유지하되</strong>, 그렇지 않은 경우에는 배럴 파일을 제거하여 <strong>불필요한 의존성 로드</strong>를 줄이는 것이 성능 최적화에 도움이 된다는 것을 알 수 있었다.</p> + +<p><br /></p> + +<p>마지막으로, <strong>절대 경로 설정</strong>이 편리하지만, <code class="language-plaintext highlighter-rouge">@types</code>와 같은 네임스페이스와 <strong>충돌이 발생할 수 있다는 문제</strong>도 알게 되었다.</p> + +<p>프로젝트 내부에서 사용하는 타입과 외부 라이브러리의 타입이 충돌할 수 있기 때문에, 이를 해결하기 위한 <strong>aliasing 전략</strong>이 중요하다는 것을 알게 되었다.</p>Kang ByeonghyeonHusky와 lint-staged로 Git Hooks 자동화하기2024-07-21T00:00:00+09:002024-07-21T00:00:00+09:00http://localhost:4000/Husky%EC%99%80%20lint-staged%EB%A1%9C%20Git%20Hooks%20%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0<p><br /> <br /> <br /></p> @@ -2961,443 +3312,4 @@ root.render<span class="o">(</span> <p>Storybook을 통해 UI 컴포넌트를 독립적으로 개발하고 테스트하는 방법을 배우고, Chromatic을 사용하여 스토리북을 배포하고 CI/CD 파이프라인에 통합하는 방법을 알게되었다.</p> -<p>이러한 과정을 통해 UI 컴포넌트를 더 효율적으로 개발하고, 팀 내에서 공유하고 피드백을 받을 수 있어, 특히 디자이너와의 협업에서의 큰 도움이 될 것 같다는 생각이 들었다.</p>Kang ByeonghyeonStorybook 소개ESLint &amp; Prettier 설치 및 룰 설정을 통한 프로젝트 초기 세팅하기2024-07-02T00:00:00+09:002024-07-02T00:00:00+09:00http://localhost:4000/ESLint%20&%20Prettier%20%EC%84%A4%EC%B9%98%20%EB%B0%8F%20%EB%A3%B0%20%EC%84%A4%EC%A0%95%EC%9D%84%20%ED%86%B5%ED%95%9C%20%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%20%EC%B4%88%EA%B8%B0%20%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0<p><br /> -<br /> -<br /></p> -<h1 id="prettier">Prettier</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%2F3ee51dc3-7337-4ff7-be1f-78f1cda77386%2Fimage.jpg?id=4c55162c-a04f-4c31-b83f-9559a5a579f2&amp;table=block" alt="prettier" /></p> - -<p>Prettier는 코드 정리 규칙을 세부적으로 설정해놓으면, 정해진 <strong>규칙</strong>에 맞게 자동으로 정렬해서 가독성을 높이고 <strong>코드 스타일</strong>을 통일할 수 있는 플러그인이다.</p> - -<h2 id="prettier-설치하기">Prettier 설치하기</h2> - -<p><code class="language-plaintext highlighter-rouge">npm install -D prettier</code> 를 통해 prettier를 개발자 모드로 설치한다.</p> - -<h2 id="prettier-옵션들-톺아보기">Prettier 옵션들 톺아보기</h2> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">arrowParens</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">always</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// 화살표 함수의 매개변수가 하나일 때 괄호를 사용할지 여부</span> - <span class="dl">"</span><span class="s2">bracketSpacing</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// 객체 리터럴에서 중괄호 내부에 공백 삽입 여부</span> - <span class="dl">"</span><span class="s2">endOfLine</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">auto</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// EoF 방식, OS별로 처리 방식이 다름</span> - <span class="dl">"</span><span class="s2">htmlWhitespaceSensitivity</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">css</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// HTML 공백 감도 설정</span> - <span class="dl">"</span><span class="s2">jsxBracketSameLine</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// JSX의 마지막 `&gt;`를 다음 줄로 내릴지 여부</span> - <span class="dl">"</span><span class="s2">jsxSingleQuote</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// JSX에 single quote 사용 여부</span> - <span class="dl">"</span><span class="s2">printWidth</span><span class="dl">"</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="c1">// 한 줄에 출력되는 코드의 최대 길이</span> - <span class="dl">"</span><span class="s2">proseWrap</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">preserve</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// markdown 텍스트의 줄바꿈 방식 (v1.8.2)</span> - <span class="dl">"</span><span class="s2">quoteProps</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">as-needed</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// 객체 속성에 쿼테이션 적용 방식</span> - <span class="dl">"</span><span class="s2">semi</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// 세미콜론 사용 여부</span> - <span class="dl">"</span><span class="s2">singleQuote</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// single 쿼테이션 사용 여부</span> - <span class="dl">"</span><span class="s2">tabWidth</span><span class="dl">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="c1">// 탭 간격</span> - <span class="dl">"</span><span class="s2">trailingComma</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">all</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// 여러 줄을 사용할 때, 후행 콤마 사용 방식</span> - <span class="dl">"</span><span class="s2">useTabs</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// 탭 사용 여부</span> - <span class="dl">"</span><span class="s2">vueIndentScriptAndStyle</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// Vue 파일의 script와 style 태그의 들여쓰기 여부 (v1.19.0)</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span> <span class="c1">// 사용할 parser를 지정, 자동으로 지정됨</span> - <span class="dl">"</span><span class="s2">filepath</span><span class="dl">"</span><span class="p">:</span> <span class="dl">""</span><span class="p">,</span> <span class="c1">// parser를 유추할 수 있는 파일을 지정</span> - <span class="dl">"</span><span class="s2">rangeStart</span><span class="dl">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// 포맷팅을 부분 적용할 파일의 시작 라인 지정</span> - <span class="dl">"</span><span class="s2">rangeEnd</span><span class="dl">"</span><span class="p">:</span> <span class="kc">Infinity</span><span class="p">,</span> <span class="c1">// 포맷팅 부분 적용할 파일의 끝 라인 지정,</span> - <span class="dl">"</span><span class="s2">requirePragma</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// 파일 상단에 미리 정의된 주석을 작성하고 Pragma로 포맷팅 사용 여부 지정</span> - <span class="dl">"</span><span class="s2">insertPragma</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// 미리 정의된 @format marker의 사용 여부 (v1.8.0)</span> - <span class="dl">"</span><span class="s2">overrides</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> - <span class="p">{</span> - <span class="dl">"</span><span class="s2">files</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">*.json</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">options</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">printWidth</span><span class="dl">"</span><span class="p">:</span> <span class="mi">200</span> - <span class="p">}</span> - <span class="p">}</span> - <span class="p">]</span> <span class="c1">// 특정 파일별로 옵션을 다르게 지정함, ESLint 방식 사용</span> -<span class="p">}</span> -</code></pre></div></div> - -<p>더 많은 옵션들은 아래의 Prettier 사이트의 playground 탭을 통해 직접 적용해보고 어떤 식으로 포맷팅되는지 확인할 수 있다.</p> - -<p>프로젝트 초기 팀원들과 코드 포맷팅 룰을 정할 때 시각적으로 확인할 수 있다는 점에서 유용하게 사용될 것 같다.</p> - -<p><a href="https://prettier.io/playground/">Prettier Playground</a></p> - -<h2 id="prettierrc-파일을-생성하고-옵션-적용하기">.prettierrc 파일을 생성하고 옵션 적용하기</h2> - -<p>코드 포맷팅 룰을 정했다면, root 폴더에 <code class="language-plaintext highlighter-rouge">.prettierrc</code> 파일을 생성하고 적용할 옵션들을 설정한다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">singleQuote</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">typescript</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">semi</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">useTabs</span><span class="dl">"</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">tabWidth</span><span class="dl">"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">printWidth</span><span class="dl">"</span><span class="p">:</span> <span class="mi">120</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">arrowParens</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">always</span><span class="dl">"</span> -<span class="p">}</span> -</code></pre></div></div> - -<ul> - <li><code class="language-plaintext highlighter-rouge">singleQuote</code>: quote를 single로 사용할지, double로 사용할지 결정</li> - <li><code class="language-plaintext highlighter-rouge">semi</code>: 명령문 마지막에 세미콜론 사용 여부</li> - <li><code class="language-plaintext highlighter-rouge">quoteProps</code>: 객체 안에 key의 따옴표를 필요한 경우에만 사용</li> - <li><code class="language-plaintext highlighter-rouge">useTabs</code>: 공백 대신 탭으로 줄을 들여 쓸지 여부</li> - <li><code class="language-plaintext highlighter-rouge">printWidth</code>: 라인의 최대 길이 설정 (120)</li> - <li><code class="language-plaintext highlighter-rouge">tabWidth</code>: 들여쓰기의 칸 수 (2칸)</li> - <li><code class="language-plaintext highlighter-rouge">arrowParens</code>: 화살표 함수에서 매개변수를 항상 괄호로 감싸도록 설정</li> -</ul> - -<p>어떤 룰을 적용해야할지 아직 미숙했기에, 가장 많이 사용하는 옵션들을 구글링해서 적용해보았다.</p> - -<h2 id="명령-단축어-지정하기">명령 단축어 지정하기</h2> - -<p>작성한 .prettierrc의 옵션들을 자동으로 적용할 수 있도록 명령어를 단축어로 지정할 수 있다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="p">...</span> - <span class="dl">"</span><span class="s2">format</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">prettier --write --cache src/**/*</span><span class="dl">"</span> -<span class="p">}</span> -</code></pre></div></div> - -<p><code class="language-plaintext highlighter-rouge">format</code>이라는 단축어를 통해 <code class="language-plaintext highlighter-rouge">prettier --write --cache src/**/*</code> 명령어를 수행하도록 작성했다.</p> - -<p>즉, 커맨드창에 <code class="language-plaintext highlighter-rouge">npm run format</code>만 입력하면 src 폴더에 있는 모든 파일들을 포맷팅할 수 있다.</p> - -<ul> - <li><code class="language-plaintext highlighter-rouge">--write</code>: Prettier가 파일을 읽고 포맷한 후, 포맷된 결과를 해당 파일에 덮어쓴다.</li> - <li><code class="language-plaintext highlighter-rouge">--cache</code>: Prettier가 이전에 포맷한 파일의 정보를 캐시에 저장하여, 이후 실행 시 변경되지 않은 파일은 다시 포맷하지 않도록 한다.</li> -</ul> - -<p>cache 파일은 <code class="language-plaintext highlighter-rouge">node_modules</code> 내부에 저장되므로 따로 <code class="language-plaintext highlighter-rouge">.gitignore</code>로 지정하지 않아도 된다.</p> - -<p><br /> -<br /> -<br /></p> -<h1 id="eslint">ESLint</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%2F271249c3-7046-4e4d-b024-442f1fadecfc%2Feslint.png?id=7040c36e-f5d6-4b67-aa22-337eaedad422&amp;table=block" alt="eslint" /></p> - -<ul> - <li><strong>ES</strong>: ECMA Script로서 JavaScript를 표준화하기 위해 만들어진 규격</li> - <li><strong>Lint</strong>: 소스 코드를 분석하여 오류, 버그, 코딩 스타일 등을 에러로 표시</li> -</ul> - -<p>즉, ESLint는 소스코드를 분석하여 <strong>문법적 오류, 버그, 코드 스타일을 검사하고 에러를 표시</strong>하며, <strong>코드 포맷팅</strong>도 가능하게 해주는 플러그인이다.</p> - -<h2 id="eslint-설치하기">ESLint 설치하기</h2> - -<p><code class="language-plaintext highlighter-rouge">npm init @eslint/config</code> 명령어를 입력하면 아래 절차대로 ESLint를 설치할 수 있다.</p> - -<p>그러나 <strong>CRA</strong>로 프로젝트를 구성했다면 기본적으로 ESLint가 설치되어 있으므로 아래의 절차대로 설치할 필요는 없다.</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%2F8069ee46-aa95-4985-a2d7-fd46c0fe4092%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.10.11.png?id=c53e80e0-664f-435a-a3a2-5ca08304a8d7&amp;table=block" alt="스크린샷 2024-06-25 오후 9.10.11.png" /></p> - -<p><strong>1. How would you like to use ESLint?</strong></p> - -<p><strong>❯ To check syntax, find problems, and enforce code style</strong></p> - -<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%2F92621560-e752-4cde-bc03-bab275a2fd36%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.11.00.png?id=71c5917b-fe65-4d3d-81f7-b3e071da1934&amp;table=block" alt="스크린샷 2024-06-25 오후 9.11.00.png" /></p> - -<p><strong>2. What type of modules does your project use?</strong></p> - -<p><strong>❯ JavaScript modules (import/export)</strong></p> - -<p>import/export 를 사용하면 JavaScript modules, require/exports 를 사용하면 CommonJS 를 선택한다.</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%2F611550ed-81a1-49ad-a013-5fa75a676fd5%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.11.14.png?id=4d91578d-8362-4fb1-9344-9f78ec5e2abd&amp;table=block" alt="스크린샷 2024-06-25 오후 9.11.14.png" /></p> - -<p><strong>3. Which framework does your project use?</strong></p> - -<p><strong>❯ React</strong></p> - -<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%2F1f971842-5fbd-4ce4-a917-eefccd5cffe4%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.14.05.png?id=654194ae-b021-4bdf-8c9a-cfed6ba7b2be&amp;table=block" alt="스크린샷 2024-06-25 오후 9.14.05.png" /></p> - -<p><strong>4. The React plugin doesn’t officially support ESLint v9 yet. What would you like to do?</strong></p> - -<p><strong>❯ Install ESLint v8.x</strong></p> - -<p>리액트 플러그인은 아직 공식적으로 ESLint v9을 지원하지 않는다하여 8.x 버전을 설치하였다.</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%2F11a79582-a824-40a8-bea5-eff6f4c1615e%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.14.58.png?id=0ba5de08-4b7d-4107-a29c-015cb7c4821b&amp;table=block" alt="스크린샷 2024-06-25 오후 9.14.58.png" /></p> - -<p><strong>5. Does your project use TypeScript?</strong></p> - -<p><strong>❯ Yes</strong></p> - -<p>자바스크립트를 사용하면 No, 타입스크립트를 사용하면 Yes 를 선택한다.</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%2Ff815790b-48aa-4813-925f-2ef2e508695f%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.15.15.png?id=d2cd4ce8-7825-44fc-ac4d-dfb5ad5316f7&amp;table=block" alt="스크린샷 2024-06-25 오후 9.15.15.png" /></p> - -<p><strong>6. Where does your code run?</strong></p> - -<p><strong>❯ Browser</strong></p> - -<p>중복 선택 가능하며, 코드가 웹 브라우저 환경에서 실행되면 Browser, Node.js 환경에서 실행되면 Node 를 선택한다.</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%2F98e2eca0-2680-4785-b771-a1658f99d0a8%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.16.26.png?id=7f606696-6438-4f38-80a3-5383ca5d2ff8&amp;table=block" alt="스크린샷 2024-06-25 오후 9.16.26.png" /></p> - -<p><strong>7. The config that you’ve selected requires the following dependencies</strong></p> - -<p><strong>❯ Yes</strong></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%2Fdfb71288-3765-4012-9de0-827ecff60ccc%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-25_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_9.16.38.png?id=b2e31a7f-80f8-4fde-be3f-8dd788ef8faa&amp;table=block" alt="스크린샷 2024-06-25 오후 9.16.38.png" /></p> - -<p><strong>8. Which package manager do you want to use?</strong></p> - -<p><strong>❯ npm</strong></p> - -<p>본인이 선호하는 패키지 매니저를 선택한다.</p> - -<p>설치가 완료되면 <code class="language-plaintext highlighter-rouge">eslint.config.mjs</code> 파일이 root 폴더에 생성된다. 익숙한 JSON 형식으로 <code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일을 생성하여 다음과 같이 설정한다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// package.json</span> - -<span class="dl">"</span><span class="s2">devDependencies</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">@eslint/js</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^9.5.0</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">eslint</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^8.57.0</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">eslint-plugin-react</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^7.34.3</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">globals</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^15.6.0</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">typescript-eslint</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">^7.14.1</span><span class="dl">"</span> -<span class="p">}</span> -</code></pre></div></div> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// .eslintrc</span> - -<span class="p">{</span> - <span class="dl">"</span><span class="s2">env</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">browser</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">es2021</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">extends</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">react-app</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">eslint:recommended</span><span class="dl">"</span><span class="p">],</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@typescript-eslint/parser</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parserOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">ecmaFeatures</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">ecmaVersion</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">latest</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">sourceType</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">module</span><span class="dl">"</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">rules</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">react/jsx-filename-extension</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> <span class="p">}]</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">node</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</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> -</code></pre></div></div> - -<h2 id="eslint-config-airbnb">eslint-config-airbnb</h2> - -<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%2F82b9653f-278b-4421-a020-099b0adf547b%2Fimage_(1).png?id=f0686f93-d9b6-4e67-9ed6-9c7d23c50109&amp;table=block" alt="https://prod-files-secure.s3.us-west-2.amazonaws.com/13897cab-0dd6-431f-b847-04477372a586/82b9653f-278b-4421-a020-099b0adf547b/image_(1).png" /></p> - -<p>현재 가장 많이 사용되는 스타일 가이드는 Airbnb에서 정의한 자바스크립트 스타일 가이드다.</p> - -<p><code class="language-plaintext highlighter-rouge">npm i -D eslint-config-airbnb</code> 명령어를 통해 설치하고, <code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일에 <code class="language-plaintext highlighter-rouge">extends</code>에 <strong>“airbnb”</strong>를 추가해준다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">env</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">browser</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">es2021</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">extends</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">react-app</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">eslint:recommended</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">airbnb</span><span class="dl">"</span><span class="p">],</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@typescript-eslint/parser</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parserOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">ecmaFeatures</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">ecmaVersion</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">latest</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">sourceType</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">module</span><span class="dl">"</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">rules</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">react/jsx-filename-extension</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> <span class="p">}]</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">node</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</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> -</code></pre></div></div> - -<h2 id="eslint-config-prettier">eslint-config-prettier</h2> - -<p>ESLint와 Prettier의 기능이 겹치는 부분이 있어 <strong>충돌</strong>이 발생할 수 있다.</p> - -<p><code class="language-plaintext highlighter-rouge">eslint-config-prettier</code>를 설치하여 겹치는 기능을 비활성화해준다.</p> - -<p><code class="language-plaintext highlighter-rouge">npm i -D eslint-config-prettier</code> 명령어로 설치하고 <code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일에 <code class="language-plaintext highlighter-rouge">extends</code>에 <strong>“prettier”</strong>를 추가한다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">env</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">browser</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">es2021</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">extends</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">react-app</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">eslint:recommended</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">airbnb</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">prettier</span><span class="dl">"</span><span class="p">],</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@typescript-eslint/parser</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parserOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">ecmaFeatures</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">ecmaVersion</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">latest</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">sourceType</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">module</span><span class="dl">"</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">rules</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">react/jsx-filename-extension</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> <span class="p">}]</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">node</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</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> -</code></pre></div></div> - -<h2 id="eslint-룰-적용해보기">ESLint 룰 적용해보기</h2> - -<p><code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일에 <code class="language-plaintext highlighter-rouge">rules</code> 부분에 원하는 에러 룰을 추가한다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">env</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">browser</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">es2021</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span> - <span class="dl">"</span><span class="s2">extends</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">react-app</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">eslint:recommended</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">airbnb</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">prettier</span><span class="dl">"</span><span class="p">],</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@typescript-eslint/parser</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parserOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">ecmaFeatures</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span> - <span class="dl">"</span><span class="s2">ecmaVersion</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">latest</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">sourceType</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">module</span><span class="dl">"</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">rules</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">react/jsx-filename-extension</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> <span class="p">}],</span> - <span class="dl">"</span><span class="s2">no-var</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="o">**</span><span class="dl">"</span><span class="s2">no-multiple-empty-lines</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">no-console</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">allow</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">warn</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">info</span><span class="dl">"</span><span class="p">]</span> <span class="p">}],</span> - <span class="dl">"</span><span class="s2">eqeqeq</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">dot-notation</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">no-unused-vars</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="o">**</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">node</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</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> -</code></pre></div></div> - -<ul> - <li><code class="language-plaintext highlighter-rouge">"no-var": "error"</code>: <code class="language-plaintext highlighter-rouge">var</code> 키워드로 변수를 선언하는 것을 금지하고 <code class="language-plaintext highlighter-rouge">let</code> 또는 <code class="language-plaintext highlighter-rouge">const</code>를 사용</li> - <li><code class="language-plaintext highlighter-rouge">"no-multiple-empty-lines": "error"</code>: 연속으로 여러 개의 빈 줄을 사용하는 것을 금지</li> - <li><code class="language-plaintext highlighter-rouge">"no-console": ["error", { "allow": ["warn", "error", "info"] }]</code>: <code class="language-plaintext highlighter-rouge">console</code> 객체의 사용을 제한</li> - <li><code class="language-plaintext highlighter-rouge">"eqeqeq": "error"</code>: <code class="language-plaintext highlighter-rouge">==</code>와 <code class="language-plaintext highlighter-rouge">!=</code> 연산자를 사용하는 것을 금지하고 <code class="language-plaintext highlighter-rouge">===</code>와 <code class="language-plaintext highlighter-rouge">!==</code>를 사용</li> - <li><code class="language-plaintext highlighter-rouge">"dot-notation": "error"</code>: 객체 속성 접근 시 점 표기법(<code class="language-plaintext highlighter-rouge">.</code>)을 사용</li> - <li><code class="language-plaintext highlighter-rouge">"no-unused-vars": "error"</code>: 선언한 변수를 사용하지 않는 것을 금지</li> -</ul> - -<p>더 많은 룰은 <a href="https://eslint.org/docs/latest/rules/">ESLint 사이트</a>에서 확인할 수 있다.</p> - -<h2 id="명령-단축어-지정하기-1">명령 단축어 지정하기</h2> - -<p>설정한 ESLint를 자동으로 적용할 수 있도록 명령어를 단축어로 지정한다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">lint</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">eslint --cache src/**/*</span><span class="dl">"</span> -<span class="p">}</span> -</code></pre></div></div> - -<p><code class="language-plaintext highlighter-rouge">lint</code>라는 단축어를 통해 <code class="language-plaintext highlighter-rouge">eslint --cache src/**/*</code> 명령어를 수행하도록 작성했다.</p> - -<p>커맨드창에 <code class="language-plaintext highlighter-rouge">npm run lint</code>만 입력하면 src 폴더에 있는 모든 파일들을 린팅할 수 있다.</p> - -<p>lint도 prettier와 마찬가지로 <code class="language-plaintext highlighter-rouge">--cache</code> 옵션을 주어 캐싱되도록 하여 변경된 부분만 적용해 속도를 높일 수 있다.</p> - -<p>ESLint는 root 폴더에 <code class="language-plaintext highlighter-rouge">.eslintcache</code> 파일을 생성하므로 <code class="language-plaintext highlighter-rouge">.gitignore</code> 해당 경로를 추가하여 GitHub에 push되지 않도록 한다.</p> - -<h2 id="eslint-절대-경로-이슈-발생">ESLint 절대 경로 이슈 발생</h2> - -<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%2F2bcd1db9-3b4d-4dbf-acf3-a5ebf3f6ba0d%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-26_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_5.44.42.png?id=df3e0305-9786-4294-a467-47459a1716bc&amp;table=block" alt="절대경로 이슈" /></p> - -<p>ESLint가 <code class="language-plaintext highlighter-rouge">@components/Button.tsx</code> 모듈의 경로를 찾지 못할 때는 <strong>alias resolver</strong>가 제대로 설정되지 않아 생긴 문제였다.</p> - -<p>이를 해결하려면 <code class="language-plaintext highlighter-rouge">eslint-plugin-import</code>와 <code class="language-plaintext highlighter-rouge">eslint-import-resolver-alias</code> 플러그인을 설치한다.</p> - -<p><code class="language-plaintext highlighter-rouge">npm install eslint-plugin-import@latest --save-devnpm install eslint-import-resolver-alias --save-dev</code></p> - -<p>설치 후 <code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일에서 <strong>import/resolver/alias</strong> 부분에 각 절대 경로에 해당하는 alias를 지정해준다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span> - <span class="dl">"</span><span class="s2">env</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">browser</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">es2021</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span> - <span class="dl">"</span><span class="s2">extends</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">react-app</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">eslint:recommended</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">plugin:import/typescript</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">airbnb</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">prettier</span><span class="dl">"</span><span class="p">],</span> - <span class="dl">"</span><span class="s2">parser</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">@typescript-eslint/parser</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">parserOptions</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">ecmaFeatures</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">},</span> - <span class="dl">"</span><span class="s2">ecmaVersion</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">latest</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">sourceType</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">module</span><span class="dl">"</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">rules</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">no-var</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">no-multiple-empty-lines</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">no-console</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">allow</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">warn</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">info</span><span class="dl">"</span><span class="p">]</span> <span class="p">}],</span> - <span class="dl">"</span><span class="s2">eqeqeq</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">dot-notation</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">no-unused-vars</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">react/jsx-filename-extension</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> <span class="p">}],</span> - <span class="dl">"</span><span class="s2">import/extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">ignorePackages</span><span class="dl">"</span><span class="p">]</span> - <span class="p">},</span> - <span class="dl">"</span><span class="s2">settings</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="o">**</span><span class="dl">"</span><span class="s2">import/resolver</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">alias</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">map</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span> - <span class="p">[</span><span class="dl">"</span><span class="s2">@components</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">./src/components</span><span class="dl">"</span><span class="p">]</span> - <span class="p">],</span> - <span class="dl">"</span><span class="s2">extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">.js</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.jsx</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.ts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">.tsx</span><span class="dl">"</span><span class="p">]</span> - <span class="p">}</span> - <span class="p">}</span><span class="o">**</span> - <span class="p">}</span> -<span class="p">}</span> -</code></pre></div></div> - -<p>절대 경로 문제로 발생한 ESLint 에러가 해결된다.</p> - -<h2 id="위처럼-했지만-다시-또-이슈-발생">위처럼 했지만 다시 또 이슈 발생…</h2> - -<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%2Fd22afd11-4a57-4e86-b9f4-745492d03ab8%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-26_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_6.57.16.png?id=032d9ffd-9663-4e37-9c95-b02bb6b44379&amp;table=block" alt="이슈발생" /></p> - -<p>이유를 잘 모르겠지만, 구글링한 결과 <code class="language-plaintext highlighter-rouge">rules</code>에 <code class="language-plaintext highlighter-rouge">"import/no-unresolved": "off"</code>를 추가하면 해결된다고 하여 작성했다.</p> - -<p>파일 확장자를 무시하도록 ESLint 설정을 업데이트해보았다.</p> - -<p><code class="language-plaintext highlighter-rouge">.eslintrc</code> 파일에서 <code class="language-plaintext highlighter-rouge">import/extensions</code> 규칙을 수정하여 <code class="language-plaintext highlighter-rouge">.tsx</code> 확장자의 기재를 무시할 수 있도록 수정하여 해결하였다.</p> - -<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">import/extensions</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">ignorePackages</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> - <span class="dl">"</span><span class="s2">js</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">never</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">jsx</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">never</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">ts</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">never</span><span class="dl">"</span><span class="p">,</span> - <span class="dl">"</span><span class="s2">tsx</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">never</span><span class="dl">"</span> -<span class="p">}]</span> -</code></pre></div></div> - -<p><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%2Ffdab6972-ecb1-4a71-b82f-6bf7c2954623%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.23.14.png?id=366f1244-be63-41c1-b8b7-28be1a45b31d&amp;table=block" alt="스크린샷 2024-06-27 오후 3.23.14.png" /></p> - -<p><br /> -<br /> -<br /></p> -<h1 id="마치면서">마치면서</h1> - -<p>이번 글에서는 Prettier와 ESLint를 활용해 CRA + TypeScript 프로젝트에서 코드 스타일을 통일하고, 가독성을 높이는 방법을 다뤘다.</p> - -<p>절대 경로 설정부터 Prettier와 ESLint의 설치 및 설정, 그리고 각종 이슈 해결 방법까지 자세히 살펴보았다.</p> - -<p>초기 프로젝트 세팅을 통해 코드 품질을 높이고, 팀 내의 코드 스타일을 통일할 수 있기를 기대할 수 있을 것 같다.</p>Kang ByeonghyeonPrettier \ No newline at end of file +<p>이러한 과정을 통해 UI 컴포넌트를 더 효율적으로 개발하고, 팀 내에서 공유하고 피드백을 받을 수 있어, 특히 디자이너와의 협업에서의 큰 도움이 될 것 같다는 생각이 들었다.</p>Kang ByeonghyeonStorybook 소개 \ No newline at end of file diff --git a/_site/index.html b/_site/index.html index dcf2128..0fc7ce5 100644 --- a/_site/index.html +++ b/_site/index.html @@ -144,6 +144,11 @@

  • step2
    • + 2024-07-22 + [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기 +
    • 2024-07-15 [카카오 테크 캠퍼스 2기] 1차 코드리뷰 반영 및 리팩토링 진행기 [우선] 우선을 중단하며 +
    • + 2024-07-22 + [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기
    • 2024-07-21 [카카오 테크 캠퍼스 2기] 1차 코드리뷰 반영 및 리팩토링 진행기 -
    • - 2024-07-14 - React Query와 Concurrent UI Pattern으로 서버 상태 효율적으로 관리하기
    • See more posts...Hi, I’m Kang Byeong-hyeon.
      ..

      KaKao Tech Campus 2th Step2

      • + 2024-07-22 + [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기 +
      • 2024-07-15 [카카오 테크 캠퍼스 2기] 1차 코드리뷰 반영 및 리팩토링 진행기2024-07-21T00:00:00+09:00 +http://localhost:4000/%EC%B9%B4%EC%B9%B4%EC%98%A4-%ED%85%8C%ED%81%AC-%EC%BA%A0%ED%8D%BC%EC%8A%A4-2%EA%B8%B0-2%EC%B0%A8-%EC%BD%94%EB%93%9C%EB%A6%AC%EB%B7%B0-%EB%B0%98%EC%98%81-%EB%B0%8F-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%EC%A7%84%ED%96%89%EA%B8%B0.html +2024-07-22T00:00:00+09:00 + + http://localhost:4000/%EC%9A%B0%EC%84%A0-%EC%9A%B0%EC%84%A0%EC%9D%84-%EC%A4%91%EB%8B%A8%ED%95%98%EB%A9%B0.html 2024-07-28T00:00:00+09:00 diff --git "a/_site/\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-2\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.html" "b/_site/\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-2\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.html" new file mode 100644 index 0000000..6023fdf --- /dev/null +++ "b/_site/\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-2\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.html" @@ -0,0 +1,448 @@ + + + + + + + [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +
        +

        Hi, I’m Kang Byeong-hyeon.

        +
        +
        +
        .. + +

        [카카오 테크 캠퍼스 2기] 2차 코드리뷰 반영 및 리팩토링 진행기

        + +


        +
        +

        + +

        이전 코드리뷰에 이어서 불필요한 코드 정리, 성능 최적화, 그리고 타입 충돌 해결 등의 내용에 집중해 리팩토링을 진행해보았다.

        + +

        이번 포스팅에서는, 자주 사용했던 rest operatorbarrel file pattern, 그리고 절대 경로 문제를 해결하는 과정을 다루고, 이를 바탕으로 리팩토링한 주요 항목들을 정리해보았다.

        + +


        +
        +

        + +

        1. .env 파일이 깃허브에 올라갔을 때 해결 방법

        + +

        프로젝트의 .env 파일이 깃허브에 실수로 올라간 경우, 해당 파일을 원격 저장소에서 삭제하면서도 로컬에서는 유지하는 방법을 사용해야한다.

        + +

        이는 다음 명령어를 통해 해결할 수 있었다.

        + +


        + +
        git rm --cached .env
        +
        + +


        + +

        이후 다시 커밋하고 푸시하면 .env 파일이 원격 저장소에서 삭제되지만, 로컬 개발 환경에서는 계속 사용할 수 있었다.

        + +


        +
        +

        + +

        2. 불필요한 rest operator 제거

        + +

        rest operator를 습관적으로 사용하던 문제가 있었다.

        + +

        특히, 컴포넌트를 구현할 때 필수적으로 사용해야 한다고 오해한 부분이 있었지만, 제한된 props만을 받는 컴포넌트에서는 불필요한 경우가 많다.

        + +

        예를 들어, 아래의 Button 컴포넌트 같은 경우, rest operator를 사용하여 Button의 다양한 속성들을 props로 받을 수 있게 설계할 수 있다.

        + +


        + +
        type ButtonTheme = "kakao" | "primary" | "darkGray";
        +type ButtonSize = "small" | "large" | "responsive";
        +
        +export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
        +  theme?: ButtonTheme;
        +  size?: ButtonSize;
        +  children: ReactNode;
        +}
        +
        +export default function Button({
        +  theme = "kakao",
        +  size = "large",
        +  children,
        +  ...rest
        +}: ButtonProps) {
        +  return (
        +    <StyledButton theme={theme} size={size} {...rest}>
        +      {children}
        +    </StyledButton>
        +  );
        +}
        +
        + +


        + +

        하지만 특정 props만을 받는 GoodsItem 컴포넌트를 예시로 들면, rest operator를 사용하여 추가적인 props를 받을 필요가 없다.

        + +


        + +
        interface GoodsItemProps {
        +  imageSrc: string;
        +  subtitle: string;
        +  title: string;
        +  amount: number;
        +  rankingIndex?: number;
        +}
        +
        +export default function GoodsItem({
        +  imageSrc,
        +  subtitle,
        +  title,
        +  amount,
        +  rankingIndex,
        +  ...rest
        +}: GoodsItemProps) {
        +  const imageSize = rankingIndex ? IMAGE_SIZE_RANKING : IMAGE_SIZE_GOODS_ITEM;
        +
        +  const renderRanking = () =>
        +    rankingIndex && <Ranking rankingIndex={rankingIndex} />;
        +
        +  return (
        +    <StyledGoodsItem rankingIndex={rankingIndex} {...rest}>
        +      ...
        +    </StyledGoodsItem>
        +  );
        +}
        +
        + +


        + +

        때문에 아래와 같이 제한된 props를 받는 컴포넌트에서 불필요하게 rest operator를 사용하는 것은 피하고, 필요한 props만을 명시적으로 전달하는 방식으로 개선할 수 있었다.

        + +


        + +
        interface GoodsItemProps {
        +  imageSrc: string;
        +  subtitle: string;
        +  title: string;
        +  amount: number;
        +  rankingIndex?: number;
        +}
        +
        +export default function GoodsItem({
        +  imageSrc,
        +  subtitle,
        +  title,
        +  amount,
        +  rankingIndex,
        +}: GoodsItemProps) {
        +  const imageSize = rankingIndex ? IMAGE_SIZE_RANKING : IMAGE_SIZE_GOODS_ITEM;
        +
        +  const renderRanking = () =>
        +    rankingIndex && <Ranking rankingIndex={rankingIndex} />;
        +
        +  return <StyledGoodsItem rankingIndex={rankingIndex}>...</StyledGoodsItem>;
        +}
        +
        + +


        +
        +

        + +

        3. 불필요한 Barrel File Pattern 지양

        + +
        export { default as Button } from "./Button";
        +export { default as Footer } from "../features/Layout/Footer";
        +export { default as InputField } from "./Form/InputField";
        +export { default as Image } from "./Image";
        +export { default as GoodsItem } from "./GoodsItem";
        +export { default as Ranking } from "./GoodsItem/Ranking";
        +export { default as Header } from "../features/Layout/Header";
        +export { default as Container } from "./Layout/Container";
        +export { default as Grid } from "./Layout/Grid";
        +export { default as CenteredContainer } from "./Layout/CenteredContainer";
        +export { default as StatusHandler } from "./StatusHandler";
        +
        + +


        + +

        초기에 barrel file pattern을 사용하여 index.ts 파일에서 여러 컴포넌트를 한 번에 export하는 방식을 사용했었다.

        + +

        믈론, import 구문을 간결하게 만들 수 있는 장점은 있지만, 프로젝트 규모가 커지게 되면 모든 모듈들을 한번에 로드하는 데 시간이 오래 걸릴 수 있고, 성능에 부정적인 영향을 미칠 수 있다.

        + +


        + +
          +
        • 모든 모듈 로드 문제: 배럴 파일은 폴더 내의 모든 모듈을 한 번에 로드하기 때문에, 필요하지 않은 모듈까지 로드되어 성능 저하가 발생할 수 있다.
        • +
        • 불필요한 사용: 소규모 컴포넌트나 단일 용도의 컴포넌트에까지 배럴 파일을 사용하면 오히려 가독성을 떨어뜨리고, 성능에 부정적인 영향을 미칠 수 있다.
        • +
        + +


        + +

        때문에, components/common 경로와 같이 여러 공통 컴포넌트들을 관리하는 디렉토리에서는 배럴 파일의 사용을 유지하였고, 소수의 컴포넌트만 있는 디렉토리에서는 불필요한 배럴 파일을 제거하여 불필요한 모듈 로드와 성능 저하를 예방하도록 개선하게 되었다.

        + +


        + +

        배럴 파일 사용 시의 성능 문제에 대한 자세한 정보는 해당 문서를 참고하였다.

        + +


        +
        +

        + +

        4. useInfiniteScroll 훅 개선

        + +
        import { useEffect } from "react";
        +import { useInView } from "react-intersection-observer";
        +
        +interface useInfiniteScrollProps {
        +  fetchNextPage: () => void;
        +  hasNextPage?: boolean;
        +  isFetchingNextPage?: boolean;
        +}
        +
        +export default function useInfiniteScroll({
        +  fetchNextPage,
        +  hasNextPage,
        +  isFetchingNextPage,
        +}: useInfiniteScrollProps) {
        +  const { ref, inView } = useInView();
        +
        +  useEffect(() => {
        +    if (inView && hasNextPage && !isFetchingNextPage) fetchNextPage();
        +  }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
        +
        +  return ref;
        +}
        +
        + +


        + +

        기존 useInfiniteScroll 훅은 react-query와 결합되어 있다.

        + +

        하지만 해당 훅의 핵심 역할은 스크롤이 끝에 도달하면 fetchNextPage를 호출하는 것이므로, 데이터 의존성을 낮추고 훅의 역할을 더 명확히 하기 위해서는 조건문을 받아 처리하는 방식으로 리팩토링하는 것이 낫은 방식이라고 판단하였다.

        + +


        + +
        import { useEffect } from "react";
        +import { useInView } from "react-intersection-observer";
        +
        +interface useInfiniteScrollProps {
        +  condition: boolean;
        +  fetchNextPage: () => void;
        +}
        +
        +export default function useInfiniteScroll({
        +  condition,
        +  fetchNextPage,
        +}: useInfiniteScrollProps) {
        +  const { ref, inView } = useInView();
        +
        +  useEffect(() => {
        +    if (inView && condition) fetchNextPage();
        +  }, [inView, condition, fetchNextPage]);
        +
        +  return ref;
        +}
        +
        + +


        + +

        이렇게 되면 해당 훅은 조건문을 받아서 fetchNextPage를 호출하는 역할만 수행하게 되므로 react-query와의 결합은 줄이고 유연성은 높일 수 있다.

        + +


        +
        +

        + +

        5. @types 절대경로 충돌 해결

        + +

        기존에 @types로 aliasing하니 module을 찾을 수 없다는 에러가 발생하였다.

        + +

        원인은 node_modules에 있는 @types와 충돌되었기 때문이었다.

        + +

        해결 방법은 두 가지가 있다.

        + +


        + +

        첫번째는 "@types/custom/*": ["src/types/custom/*"]처럼 @types 하위에 별도 중첩 네임스페이스를 사용해서 피하는 방식과 다른 이름을 쓰는 방식이있다.

        + +

        하지만, npm 패키지에서 설치한 타입인지 내부 프로젝트에서 직접 관리하는 코드인지 쉽게 알기 힘들기에, 두번째 방법으로 그냥 다른 이름으로 aliasing하는 것이 더 좋은 방법이 될 수 있다고 판단하였다.

        + +

        따라서 프로젝트 내부에서 사용되는 타입이라는 의미로 @internalTypes로 aliasing 하기로 결정하였다.

        + +


        + +

        이후 tsconfig, craco.config, eslintrc 파일의 절대경로 alias를 다시 작성하여 해결하였다.

        + +


        + +
        // tsconfig.path.json
        +
        +{
        +  "compilerOptions": {
        +    "baseUrl": ".",
        +    "paths": {
        +      "@/*": ["./src/*"],
        +      "@components/*": ["./src/components/*"],
        +      "@assets/*": ["./src/assets/*"],
        +      "@hooks/*": ["./src/hooks/*"],
        +      "@pages/*": ["./src/pages/*"],
        +      "@routes/*": ["./src/routes/*"],
        +      "@utils/*": ["./src/utils/*"],
        +      "@context/*": ["./src/context/*"],
        +      "@internalTypes/*": ["./src/types/*"],
        +      "@apis/*": ["./src/apis/*"]
        +    }
        +  }
        +}
        +
        + +


        + +
        // craco.config.js
        +
        +const path = require("path");
        +
        +module.exports = {
        +  webpack: {
        +    alias: {
        +      "@": path.resolve(__dirname, "src/"),
        +      "@components": path.resolve(__dirname, "src/components"),
        +      "@assets": path.resolve(__dirname, "src/assets"),
        +      "@hooks": path.resolve(__dirname, "src/hooks"),
        +      "@pages": path.resolve(__dirname, "src/pages"),
        +      "@routes": path.resolve(__dirname, "src/routes"),
        +      "@utils": path.resolve(__dirname, "src/utils"),
        +      "@context": path.resolve(__dirname, "src/context"),
        +      "@internalTypes": path.resolve(__dirname, "src/types"),
        +      "@apis": path.resolve(__dirname, "src/apis"),
        +    },
        +  },
        +};
        +
        + +


        + +
        // .eslintrc
        +
        +{
        +  "settings": {
        +    "import/resolver": {
        +      "alias": {
        +        "map": [
        +          ["@", "./src"],
        +          ["@components", "./src/components"],
        +          ["@assets", "./src/assets"],
        +          ["@hooks", "./src/hooks"],
        +          ["@pages", "./src/pages"],
        +          ["@routes", "./src/routes"],
        +          ["@utils", "./src/utils"],
        +          ["@context", "./src/context"],
        +          ["@internalTypes", "./src/types"],
        +          ["@apis", "./src/apis"]
        +        ],
        +        "extensions": [".js", ".jsx", ".ts", ".tsx"]
        +      }
        +    }
        +  }
        +}
        +
        + +


        +
        +

        + +

        마치면서

        + +

        이번 리팩토링 과정에서, rest operator를 습관적으로 남발했던 점을 되돌아볼 수 있었다.

        + +

        특히, 제한된 props만을 받는 컴포넌트에서는 rest operator가 오히려 불필요한 코드가 될 수 있다는 것을 깨달았다.

        + +


        + +

        다음으로, barrel file pattern을 사용해 import 구문을 간결하게 만드는 데 집중했으나, 프로젝트가 커질수록 불필요한 모듈 로드로 인해 성능 저하가 발생할 수 있다는 문제를 알게 되었다.

        + +

        모든 컴포넌트를 배럴 파일로 묶는 것이 항상 최선의 방법은 아니었고, 컴포넌트 수가 많은 디렉토리에서는 배럴 파일을 유지하되, 그렇지 않은 경우에는 배럴 파일을 제거하여 불필요한 의존성 로드를 줄이는 것이 성능 최적화에 도움이 된다는 것을 알 수 있었다.

        + +


        + +

        마지막으로, 절대 경로 설정이 편리하지만, @types와 같은 네임스페이스와 충돌이 발생할 수 있다는 문제도 알게 되었다.

        + +

        프로젝트 내부에서 사용하는 타입과 외부 라이브러리의 타입이 충돌할 수 있기 때문에, 이를 해결하기 위한 aliasing 전략이 중요하다는 것을 알게 되었다.

        + +
        + +
        + + \ No newline at end of file