<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kkzimo.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sun, 13 Apr 2025 12:34:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kkzimo.log</title>
            <url>https://velog.velcdn.com/images/_iieun/profile/5a4b110f-1e40-44db-8194-1e830d3814a1/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kkzimo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_iieun" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React Deep Dive - 3주차 회고]]></title>
            <link>https://velog.io/@_iieun/React-Deep-Dive-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@_iieun/React-Deep-Dive-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 13 Apr 2025 12:34:24 GMT</pubDate>
            <description><![CDATA[<h2 id="3주차-목표">3주차 목표</h2>
<ol>
<li>React의 기본 Hook과 HOC를 직접 구현하며 내부 동작 원리 이해하기</li>
<li>직접 구현한 Hook을 활용해서 기존 코드 리팩토링해보기</li>
</ol>
<h2 id="과제-회고">과제 회고</h2>
<p>이번 주는 <code>useCallback</code>, <code>useMemo</code>, <code>useRef</code>, <code>useDeepMemo</code> 같은 React 기본 Hook을 직접 구현해보고, <code>memo</code>, <code>deepMemo</code> 같은 HOC도 직접 만들어보며 동작 원리를 이해했다. </p>
<h3 id="memo---hoc-직접-구현하기">memo - HOC 직접 구현하기</h3>
<h4 id="어려웠던-점">어려웠던 점</h4>
<p><code>memo</code>는 컴포넌트를 인자로 받아 props가 바뀌지 않으면 리렌더링을 생략하는 방식으로 동작한다. 
그래서 처음에는 함수 컴포너트의 결과를 클로저 내부에 저장해두고, props가 이전과 동일하다면 저장된 결과를 반환하도록 구현했다. 
하지만 여러 곳에서 <code>memo</code>를 사용했을 때, 클로저에 저장된 값들이 덮어써지면서 props가 변경되지 않았음에도 불구하고 컴포넌트가 다시 렌더링되는 문제가 발생했다. </p>
<h4 id="해결-과정">해결 과정</h4>
<p>클로저 대신 <code>userRef</code>를 사용해, <code>memo</code>가 적용된 각 컴포넌트마다 별도로 결과를 저장하도록 변경했다. 
이렇게 저장 범위를 분리하니 문제 없이 동작했다. </p>
<h4 id="알게-된-것">알게 된 것</h4>
<p>클로저의 저장 범위(scope)를 명확히 설정하지 않으면 예상치 못한 리렌더링 문제가 발생할 수 있다는 걸 경험했다. </p>
<h3 id="context-api에서의-렌더링-최적화">Context API에서의 렌더링 최적화</h3>
<p>직접 만든 Hook을 적용하며 기존 코드를 리팩토링했고, Context를 사용할 때 불필요한 렌더링을 최소화하는 구조를 고민했다. </p>
<h4 id="어려웠던-부분">어려웠던 부분</h4>
<p>우선 AppContext에 같이 있던 theme(다크모드 theme 설정), notification(로그인 알람 토스트), auth(로그인)를 각각의 context로 분리하였다.
그리고 각각의 context 상태가 변경 될 때, 불필요한 리렌더링이 발생하지 않도록 구조를 나누는 데 집중하였다.
예를 들어, 사용자가 로그인하면 알림 토스트가 나타나도록 구현되어 있었기 때문에 AuthProvider에서 NotificationContext를 참조해야 했다.
하지만 Context API는 context value가 변경되면 해당 값을 구독하는 모든 컴포넌트가 리렌더링된다는 특성이 있기 때문에,
헤더에 있는 로그인 버튼으로 로그인했을 때 알림 토스트가 뜨고, 그 알림을 닫았을 때 auth를 구독 중인 Header 컴포넌트가 불필요하게 리렌더링되지 않도록 구조를 어떻게 분리할지 고민하였다.</p>
<h4 id="해결-과정-1">해결 과정</h4>
<p><code>AuthProvider</code> 안에서 직접 <code>NotificationContext</code>를 구독하는 대신, <code>addNotification</code>만 prop으로 전달하고 <code>AuthProvider</code> 자체를 <code>memo()</code>로 감쌌다. </p>
<pre><code class="language-typescript">...

&lt;NotificationProvider&gt;
    &lt;AuthWithNotification&gt;{children}&lt;/AuthWithNotification&gt;
&lt;/NotificationProvider&gt;

...

function AuthWithNotification({ children }: AuthWithNotificationProps) {
  const { addNotification } = useNotification(&quot;AuthWithNotification&quot;);
  return (
    &lt;AuthProvider addNotification={addNotification}&gt;{children}&lt;/AuthProvider&gt;
  );
}
</code></pre>
<p>이렇게 하면 <code>notification</code>과 관련된 상태가 변경돼도 <code>AuthProvider</code>는 리렌더링되지 않는다. 또한, <code>notification</code>과 <code>AuthProvider</code>의 의존관계를 prop으로 명시적으로 표현할 수 있다.</p>
<h4 id="알게-된-것-1">알게 된 것</h4>
<p>멘토의 코드를 참고하면서 <code>AuthProvider</code> 안에서 <code>useNotification</code>을 직접 사용하되, 
<code>useCallback</code>과 <code>useMemo</code>를 활용해 <code>authContextValue</code>의 참조를 고정시키는 방식이 더 구조적으로 깔끔할 수 있겠다는 생각이 들었다. </p>
<pre><code class="language-typescript">export const UserProvider: React.FC&lt;{ children: React.ReactNode }&gt; = ({
  children,
}) =&gt; {
...

const login = useCallback(
    (email: string) =&gt; {
      setUser({ id: 1, name: &quot;홍길동&quot;, email });
      addNotification(&quot;성공적으로 로그인되었습니다&quot;, &quot;success&quot;);
    },
    [addNotification],
  );

  const logout = useCallback(() =&gt; {
    setUser(null);
    addNotification(&quot;로그아웃되었습니다&quot;, &quot;info&quot;);
  }, [addNotification]);

  const userContextValue = useMemo(
    () =&gt; ({ user, login, logout }),
    [user, login, logout],
  );

  ...
}</code></pre>
<p>추가적으로 멘토의 코드 리뷰를 통해 추천받은 방식 중 하나가, <code>notification</code>과 <code>AuthProvider</code>의 의존관계를 더 명시적으로 쓰도록 강제하고 싶다면 의존관계를 children 콜백 패턴으로 표현하는 것이다. </p>
<pre><code class="language-typescript">export const AppProvider = ({ children }: AppProviderProps) =&gt; {
  return (
    &lt;ThemeProvider&gt;
      &lt;NotificationProvider&gt;
        {(notificationAPI) =&gt; (
          &lt;AuthProvider notificationAPI={notificationAPI}&gt;
            {children}
          &lt;/AuthProvider&gt;
        )}
      &lt;/NotificationProvider&gt;
    &lt;/ThemeProvider&gt;
  );
};</code></pre>
<p>이 방식은 <code>AuthProvider</code>가 <code>notification</code> API를 명시적으로 전달받게 되므로, 구조적으로 <code>NotificationProvider</code>가 상위에 존재해야 한다는 제약을 코드 레벨에서 자연스럽게 강제할 수 있다.
의존관계를 좀 더 겉으로 드러내고 명확히 표현할 수 있다는 점에서 인상 깊었다.
기존의 <code>useNotificationContext</code> 방식도 간결하고 편리하지만, 의존성이 암묵적으로 감춰지는 단점이 있다는 걸 생각해보지 못했다. 
설계적인 안정성과 구조적인 명확함이 중요한 상황이라면, 이런 형태의 의존성 전달도 충분히 고려해볼 만하다는 생각이 들었다.</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>이번 과제를 진행하면서 학습메이트에게 <a href="https://github.com/radix-ui/primitives/blob/c0a7128cbbae62c8e09be7141f0a06e61998c2d6/packages/react/context/src/create-context.tsx#L3">Redix UI의 undefined safe한 context 생성 함수</a>를 추천받았다. 
이 코드 패턴을 참고하면 <code>createContext</code>를 좀 더 간결하게 사용할 수 있고, Provider와 useContext hook을 한 번에 구성할 수 있어서 적용해보면 좋겠다는 생각이 들었다. 
역시 같이 공부하면 참고할 수 있는 자료나 시야가 훨씬 넓어지는 느낌이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Nx generator로 자주 하는 작업 자동화하기]]></title>
            <link>https://velog.io/@_iieun/Nx-generator%EB%A1%9C-%EC%9E%90%EC%A3%BC-%ED%95%98%EB%8A%94-%EC%9E%91%EC%97%85-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_iieun/Nx-generator%EB%A1%9C-%EC%9E%90%EC%A3%BC-%ED%95%98%EB%8A%94-%EC%9E%91%EC%97%85-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Jan 2023 08:19:16 GMT</pubDate>
            <description><![CDATA[<p>Nx는 Monorepo를 지원하는 빌드 시스템입니다.
Nx generator를 사용하면 파일 생성, 수정, 이동, 삭제가 필요한 코드를 쉽게 작성할 수 있습니다. 또한 자주 하는 작업을 간단한 command로 자동화할 수 있습니다.</p>
<h2 id="generator-생성하기">Generator 생성하기</h2>
<pre><code>nx g generator svg-icon-codegen --project=plugin</code></pre><ul>
<li><code>libs/plugin/src/generators</code> 경로에 <code>svg-icon-codegen</code> 이름을 가진 generator를 생성합니다.</li>
<li><code>--dry-run</code> 옵션을 사용하면 CLI에서 미리 실행 결과를 볼 수 있습니다. (실제로 변경이 일어나지 않음) <img src="https://velog.velcdn.com/images/_iieun/post/6f944e7d-8d2b-4a2b-887b-a004649f1b4a/image.png" alt=""></li>
</ul>
<h2 id="generator-실행하기">Generator 실행하기</h2>
<pre><code>nx generate svg-icon-codegen</code></pre><ul>
<li>svg-icon-codegen이라는 generator를 실행합니다.</li>
<li><code>svg-icon-codegen/generator.ts</code> 코드가 실행됩니다. </li>
</ul>
<h2 id="generator-코드-작성하기">Generator 코드 작성하기</h2>
<pre><code class="language-typescript">// The initial generator function

import { Tree } from &#39;@nrwl/devkit&#39;;

export default async function (tree: Tree, options: any) {
    ...
}</code></pre>
<ul>
<li><code>schema.json</code>에 properties를 정의하면 <code>options</code>의 property로 받아서 사용할 수 있습니다. </li>
<li><code>@nrwl/devkit</code> 패키지는 파일 수정, 파일 읽기 및 업데이트, AST(Abstract Syntax Tree) 작업을 위한 많은 유틸리티 기능을 제공합니다.</li>
</ul>
<p><strong><em>ex. project path 가져오기</em></strong>
&#39;shared-ui-system&#39;의 root path를 가져옵니다. </p>
<pre><code class="language-typescript">const projectRoot = readProjectConfiguration(tree, &#39;shared-ui-system&#39;).root;</code></pre>
<p><strong><em>ex. path 합치기</em></strong>
&#39;shared-ui-system&#39; project 하위 icons path에 CalendarPlusIcon path를 붙여서 새로운 path를 만듭니다.  </p>
<pre><code class="language-typescript">// libs/shared/ui-system/src/lib/icons/CalendarPlusIcon

const path = joinPathFragments(projectRoot, `src/lib/icons/${options.name}Icon`);</code></pre>
<p><strong><em>ex. 파일 생성하기</em></strong></p>
<p><code>generateFiles(tree: Tree, srcFolder: string, target: string, substitution)</code>
srcFolder에 있는 파일을 target 경로에 생성합니다. </p>
<pre><code class="language-typescript">generateFiles(tree, joinPathFragments(__dirname, &#39;./files/src&#39;), `${iconRoot}/__generated__/`, { ...options, template: &#39;&#39; });</code></pre>
<p>substitution에 정의한 key는 value로 replace 되어집니다. 
<code>{ template: &#39;&#39; }</code> 로 설정하면 srcFolder에 <code>index.ts__template__</code> 파일은 index.ts 파일로 생성됩니다. </p>
<p><strong><em>ex. 파일 수정하기</em></strong></p>
<pre><code class="language-typescript">const contents = tree.read(filePath).toString();
const newContents = `export * as ${options.name}Icon from &#39;./${options.name}Icon&#39;;`;

tree.write(filePath, contents + newContents);</code></pre>
<h2 id="팁">팁</h2>
<p><code>shema.json</code>에서 properties 설정할 때 <code>x-prompt</code>를 설정하면 한결 친절?하게 generator를 실행시킬 수 있습니다 :)</p>
<p><strong><em>ex. react native svg icon component 생성하기 
<code>&lt;HomeSmileIcon.Fill /&gt;</code>, <code>&lt;HomeSmileIcon.Line /&gt;</code></em></strong></p>
<p><img src="https://velog.velcdn.com/images/_iieun/post/c9466dcf-f2c0-4f3b-9453-2cbdcff96b33/image.gif" alt=""></p>
<h2 id="참고">참고</h2>
<p><a href="https://nx.dev/packages/nx-plugin/generators/generator">Nx Docs generator</a>
<a href="https://nx.dev/recipes/generators/local-generators#local-generators">Nx Docs local-generators</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 Lodash 번들 사이즈 최적화하기]]></title>
            <link>https://velog.io/@_iieun/Next.js%EC%97%90%EC%84%9C-Lodash-%EB%B2%88%EB%93%A4-%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0-bnkvjrje</link>
            <guid>https://velog.io/@_iieun/Next.js%EC%97%90%EC%84%9C-Lodash-%EB%B2%88%EB%93%A4-%EC%82%AC%EC%9D%B4%EC%A6%88-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0-bnkvjrje</guid>
            <pubDate>Fri, 30 Dec 2022 05:51:37 GMT</pubDate>
            <description><![CDATA[<p>Bundle Analyzer를 돌려보고 Lodash가 이상한 모양새로 꽤 큰 사이즈로 자리 잡고 있다는 것을 알게 되었습니다.</p>
<ul>
<li>Gzipped size: 33.81KB
<img src="https://velog.velcdn.com/images/_iieun/post/d09c169d-126a-45b8-9ee2-6ae6c7a4e432/image.png" alt=""></li>
</ul>
<h2 id="lodash-모듈-용량-최적화-방법">Lodash 모듈 용량 최적화 방법</h2>
<p>lodash가 번들을 크게 만드는 이유는 lodash가 CommonJS로 작성되어 있기 때문입니다. (<a href="https://web.dev/i18n/ko/commonjs-larger-bundles/">CommonJS가 번들을 더 크게 만드는 이유</a> 참고)</p>
<p>lodash tree shaking 관련 검색을 해보니 2가지 정도 방법이 있었습니다.</p>
<ol>
<li>member style imports를 default style imports로 변경하기</li>
<li>lodash-es 사용하기</li>
</ol>
<p>저는 이 중에서 member style imports를 default style imports로 변경하는 방법으로 작업을 진행하였습니다.</p>
<p>member style imports를 변경해야 하는 이유는 모든 lodash 함수가 포함된 lodash.js 파일을 통째로 가져오기 때문입니다.
실제로 측정한 lodash 번들을 살펴보면 왼쪽에 lodash.js가 통째로 들어있는 것을 확인할 수 있습니다.</p>
<p><strong>member sytle imports</strong></p>
<pre><code class="language-typescript">import { merge } from &#39;lodash&#39;</code></pre>
<p><strong>default style imports</strong></p>
<pre><code class="language-typescript">import merge from &#39;lodash/merge&#39;</code></pre>
<p>프로젝트에 있는 모든 lodash 코드를 직접 찾아서 바꿔줄 수도 있지만 Babel이나 SWC, Nex.js config 설정으로 간단하게 작업할 수 있습니다.</p>
<h3 id="babel-설정하기">Babel 설정하기</h3>
<p> <a href="https://www.npmjs.com/package/babel-plugin-transform-imports">babel-plugin-transform-imports plugin</a>을 사용하면 기존 코드를 모두 수정하지 않고 default style imports로 변경할 수 있습니다.</p>
<h3 id="swc-설정하기">SWC 설정하기</h3>
<p> Babel이 아닌 SWC를 사용하고 있다면 webpack에 <a href="https://www.npmjs.com/package/swc-plugin-transform-import">swc-plugin-transform-import</a> plugin을 적용합니다. </p>
<p>Next.js 11 이상 버전에서는 default 설정으로 Babel 대신 SWC를 사용하고 있습니다. (SWC is 20x faster than Babel on a single thread and 70x faster on four cores.)</p>
<p>Next.js에서 Babel 설정(.babelrc 파일 생성)을 한다면 자동으로 JavaScript/TypeScript를 컴파일하기 위해 SWC를 사용하지 않고 Next.js 11에서 사용했던 것과 같은 방식으로 Babel을 사용하게 됩니다.(<a href="https://nextjs.org/docs/messages/swc-disabled">SWC disabled</a>)</p>
<h3 id="nextjs-modularizeimports-기능-사용하기">Next.js modularizeImports 기능 사용하기</h3>
<p>Next.js에서 제공하는 실험적인 기능으로 <a href="https://www.npmjs.com/package/swc-plugin-transform-import">Modularize Imports</a> 설정을 지원합니다.</p>
<pre><code class="language-typescript">module.exports = {
  experimental: {
    modularizeImports: {
            lodash: {
        transform: &#39;lodash/{{member}}&#39;,
      },
    },
  },
}</code></pre>
<ul>
<li>lodash member style imports를 default style imports로 변환해 줍니다.</li>
</ul>
<pre><code class="language-typescript">module.exports = {
  experimental: {
    modularizeImports: {
      &#39;my-library/?(((\\w*)?/?)*)&#39;: {
        transform: &#39;my-library/{{ matches.[1] }}/{{member}}&#39;,
      },
    },
  },
}</code></pre>
<ul>
<li>lodash가 아닌 라이브러리에도 커스텀하게 설정이 가능합니다.</li>
</ul>
<h2 id="lodash-최적화-결과">Lodash 최적화 결과</h2>
<ul>
<li>Gzipped size: 11.94KB</li>
</ul>
<p><img src="https://velog.velcdn.com/images/_iieun/post/0ed9990d-9683-4e17-9efa-06c82ccc4934/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>