<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jh_lee95.log</title>
        <link>https://velog.io/</link>
        <description> </description>
        <lastBuildDate>Wed, 13 Sep 2023 06:43:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jh_lee95.log</title>
            <url>https://velog.velcdn.com/images/jh_lee95/profile/b674a360-72ec-47b8-b6db-bd2a79c820c6/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jh_lee95.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jh_lee95" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[concurrentMode와 useTransition 활용]]></title>
            <link>https://velog.io/@jh_lee95/concurrentMode%EC%99%80-useTransition-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@jh_lee95/concurrentMode%EC%99%80-useTransition-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Wed, 13 Sep 2023 06:43:45 GMT</pubDate>
            <description><![CDATA[<h1 id="concurrent-mode">Concurrent Mode</h1>
<p>JavaScript는 싱글 스레드 언어입니다. 이는 하나의 작업을 수행할 때 다른 작업을 동시에 수행할 수 없음을 의미합니다. 하지만 React에서 concurrent mode를 사용하면 여러 작업을 동시에 처리할 수 있습니다.</p>
<blockquote>
<p>📌 concurrent mode를 사용하면 여러 작업을 동시에 처리 가능</p>
</blockquote>
<h2 id="react가-여러-작업을-동시에-처리하는-방식">React가 여러 작업을 동시에 처리하는 방식</h2>
<p>React는 여러 작업을 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행합니다. 서로 다른 작업들이 실제로 동시에 수행되는 것은 아니지만, 작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼 보이게 되는 것입니다. 이를 동시성이라고 합니다.</p>
<p>이렇게 React는 동시성 개념을 도입해 싱글 스레드 환경에서 여러 작업을 동시에 할 수 있게 되었습니다.</p>
<h2 id="concurrent-mode의-동작-원리">Concurrent mode의 동작 원리</h2>
<p>특정 state가 변경되었을 때 현 UI를 유지하고 해당 변경에 따른 UI 업데이트를 동시에 준비합니다. 준비 중인 UI의 렌더링 단계가 특정 조건에 부합하게 되면 실제 DOM에 반영하는 것이죠.</p>
<p>그래서 fallback으로 지정했던 컴포넌트를 띄우는 것이 아니라 현 UI를 유지하면서 변경을 준비합니다.</p>
<h2 id="렌더링-단계">렌더링 단계</h2>
<p><img src="https://velog.velcdn.com/images/jh_lee95/post/6f91a4ef-893f-4513-898e-a96f944d3462/image.png" alt="">
state 변경의 관점에서 보는 렌더링 관계는 위와 같이 3단계가 있습니다.</p>
<p><strong>1. Transition 단계</strong></p>
<p>Transition는 state 변경 직후에 일어날 수 있는 UI 렌더링 단계입니다.</p>
<p>Pending : useTransition 훅을 사용하면 state 변경 직후에 UI를 업데이트하지 않고 현 UI를 잠시 유지할 수 있는데 이를 Pending 상태라고 합니다.
Receded : useTransition 훅을 사용하지 않은 기본 상태. state 변경 직후 UI(Loader를 생각)가 변경됩니다.</p>
<p>그럼 Pending으로 걸어놨는데, 로딩 시간이 길어지면 UI에 안 좋은 거 아닌가?</p>
<p>→ Pending 상태에서도 Receded 상태로 넘어갈 수 있습니다! Pending 상태의 시간이 useTransition 옵션으로 지정된 timeoutMs를 넘으면 강제로 Receded 상태로 넘어갑니다.</p>
<pre><code class="language-typescript">const [isPending, startTransition] = useTransition({
    timeoutMs: 2000
  });</code></pre>
<p><strong>2. Loading 단계</strong></p>
<p>현재 컴포넌트의 자식 요소에서 발생되는 비동기 처리하는 과정을 처리 중인 단계입니다. ex) Loader, SkeletionUI</p>
<p><strong>3. Done 단계</strong></p>
<p>비동기 처리가 완료됨에 따라 완성된 UI를 보여줍니다.</p>
<h2 id="usetransition-활용법">useTransition 활용법</h2>
<p>위의 원리를 참고하여 Suspense를 사용한 리액트 코드내의 로딩 중 의미없는 깜빡임 이슈를 useTransition을 활용하여 해결할 수 있습니다.</p>
<ul>
<li>isPending : 작업이 지연되고 있음을 알리는 boolean</li>
<li>startTransition : 낮은 우선순위로 실행할 함수를 인자로 받는다.</li>
</ul>
<pre><code class="language-typescript">function App() {
  const [isPending, startTransition] = useTransition()
  const [page, setPage] = useState(1)

  const onNextPage = () =&gt; {
    startTransition(() =&gt; {
      setPage(prev =&gt; prev + 1)
    })
  }

  return (
    &lt;Suspense fallback={&lt;Loader /&gt;}&gt;
      &lt;Foo page={page} onNextPage={onNextPage} /&gt;
    &lt;/Suspense&gt;
  )
}

function Foo({ page, onNextPage }) {
  return /* 성공했을 경우만 고려하여 작성 가능 */
}</code></pre>
<p>useTranstion으로부터 나온 startTransition이라는 함수에 상태 업데이트 로직을 부여하면 해당 상태 업데이트로 인해 새롭게 발생하는 비동기 처리가 끝날 때까지 화면 렌더링 변화를 지연시킵니다. 정확히는 원래의 UI를 보여주다가 업데이트된 UI를 보여주는 형태입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[server component 그리고 serialization]]></title>
            <link>https://velog.io/@jh_lee95/server-component-%EA%B7%B8%EB%A6%AC%EA%B3%A0-serialization</link>
            <guid>https://velog.io/@jh_lee95/server-component-%EA%B7%B8%EB%A6%AC%EA%B3%A0-serialization</guid>
            <pubDate>Mon, 28 Aug 2023 11:48:00 GMT</pubDate>
            <description><![CDATA[<h3 id="최근-서버컴포넌트를-활용한-작업을-하다가-이해할-수-없는-서버컴포넌트-에러를-마주하였고-이를-이해하기-위해-서버컴포넌트-메커니즘에-대해-서치한-내용을-정리해보려고-한다">최근 서버컴포넌트를 활용한 작업을 하다가 이해할 수 없는 서버컴포넌트 에러를 마주하였고 이를 이해하기 위해 서버컴포넌트 메커니즘에 대해 서치한 내용을 정리해보려고 한다.</h3>
<h2 id="서버-컴포넌트">서버 컴포넌트?</h2>
<p>React의 서버 컴포넌트 (RSC)를 통해 서버와 클라이언트 사이에서 React 애플리케이션을 상호 보완적으로 렌더링 할 수 있다. RSC의 도입으로 애플리케이션의 일부는 서버에서, 다른 일부는 클라이언트에서 렌더링하는 유연성을 갖게 되었다.</p>
<h2 id="rsc의-동작방식">RSC의 동작방식</h2>
<p>서버 컴포넌트의 작동 방식을 깊이 파악하기 위해서는, RSC가 실제로 어떤 방식으로 렌더링되는지 알아야한다. 아래의 예시처럼 서버 컴포넌트와 클라이언트 컴포넌트가 혼합된 화면 구조를 생각해보자.
<img src="https://velog.velcdn.com/images/jh_lee95/post/9cc01580-280b-4f6d-83af-e25915ef873c/image.png" alt=""></p>
<p>사용자가 특정 페이지를 열기 위해 서버에 요청을 보내면, 서버는 그 시점부터 루트 컴포넌트부터 시작하여 컴포넌트 트리를 <strong>직렬화된 JSON 형식</strong>으로 재구성하게 된다.</p>
<h3 id="직렬화">직렬화?</h3>
<blockquote>
<p>직렬화는 데이터나 객체의 상태를 다른 환경 (예: 파일, 메모리, 네트워크)에 저장하거나 전송하기 위해 특정 형식으로 변환하는 절차를 의미한다.</p>
</blockquote>
<p>주의할점은 모든 객체를 직렬화할 수는 없다는 것이다. 대표적으로 function은 직렬화가 불가능한 객체다. function이 실행코드와 실행 컨텍스트를 모두 포함하는 개념이기 때문인데, 함수는 자신이 선언된 스코프에 대한 참조를 유지하고, 그 시점의 외부 변수에 대한 참조를 기억하고 있다. js의 클로저가 바로 이런 현상을 가리키는 용어이기도 하다.</p>
<pre><code class="language-typescript">const tmp = 1;

const tmpLog = () =&gt; {
    console.log(tmp)
}

tmpLog() //1</code></pre>
<p>이처럼 함수의 실행 컨텍스트, 스코프, 클로저까지 직렬화할 수는 없기 때문에 function은 직렬화가 불가능한 객체로 분류되는 것이다.</p>
<p>직렬화는 서버 컴포넌트를 모두 실행하여 JSON 객체 형태의 구조로 변환하는 과정이다. 예를 들면 아래와 같다.</p>
<pre><code class="language-typescript">React.createElement(&quot;div&quot;, {style: {backgroundColor: &#39;blue&#39;}}, &quot;Server Component&quot;)

{
&quot;$$typeof&quot;: &quot;react.element&quot;,
&quot;type&quot;: &quot;div&quot;,
&quot;props&quot;: { &quot;style&quot;: {backgroundColor: &quot;blue&quot;}, &quot;children&quot;: &quot;Server Component&quot; },
...
} </code></pre>
<p>그러나 이런 절차를 모든 컴포넌트에 적용하는 것은 아니다. RCC의 경우, 직접 해석하는 것을 생략한다. 그렇다고 RCC를 공백으로 남겨두면 실제 컴포넌트 구조와의 일관성이 떨어진다. 이를 해결하기 위해 RCC가 렌더링되는 위치를 명시하는 특별한 placeholder를 배치한다.</p>
<p>예를 들어</p>
<pre><code class="language-typescript">{
  &quot;$$typeof&quot;: &quot;react.element&quot;,
  &quot;type&quot;: {
    &quot;$$typeof&quot;: &quot;react.module.ref&quot;,
    &quot;name&quot;: &quot;main&quot;, 
    &quot;filepath&quot;: &quot;./path/ClientComp.js&quot;
  },
  &quot;props&quot;: { &quot;content&quot;: &quot;child content&quot; },
}</code></pre>
<p>앞서 언급한 바와 같이, RCC는 함수의 형태를 가지므로 직렬화가 불가능하다. 그래서 React는 &quot;module reference&quot;라는 새로운 유형을 도입하여 직렬화 문제를 해결한다. 이 유형을 사용하여 함수를 직접 참조하는 대신 해당 컴포넌트의 위치나 경로를 지정함으로써 직렬화 문제를 회피하고 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[nextjs] Server Action]]></title>
            <link>https://velog.io/@jh_lee95/nextjs-Server-Action</link>
            <guid>https://velog.io/@jh_lee95/nextjs-Server-Action</guid>
            <pubDate>Thu, 17 Aug 2023 14:55:23 GMT</pubDate>
            <description><![CDATA[<h2 id="how-use-it">How use it</h2>
<p>클라이언트에서 서버의 함수를 호출할 수 있는 기능입니다. 해당 함수는 아래 3가지 방법으로 호출할 수 있습니다.</p>
<ul>
<li>form의 action</li>
<li>formAction</li>
<li>react의 startTansition</li>
</ul>
<h3 id="1-form의-action-사용하기">1. form의 action 사용하기</h3>
<pre><code class="language-typescript">import db from &#39;./db&#39;
import { redirect } from &#39;next/navigation&#39;

async function create(formData: FormData){
  &#39;use serevr&#39;
  const post = await db.post.insert({
    title: formData.get(&#39;title&#39;),
    content: formData.get(&#39;content&#39;),
  })

  redirect(`/blog/${post.slug}`)
}

export default function Page(){
  return (
    &lt;form action={create}&gt;
      &lt;input type=&quot;text&quot; name=&quot;title&quot; /&gt;
      &lt;textarea name=&quot;content&quot; /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  )
}</code></pre>
<h2 id="2-formaction-사용하기">2. formAction 사용하기</h2>
<pre><code class="language-typescript">export default function Page() {
  async function handleSubmit() {
    &#39;use server&#39;;
    // ...
  }

  async function submitImage() {
    &#39;use server&#39;;
    // ...
  }

  return (
    &lt;form action={handleSubmit}&gt;
      &lt;input type=&quot;text&quot; name=&quot;name&quot; /&gt;
      &lt;input type=&quot;image&quot; formAction={submitImage} /&gt;
      &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<h3 id="3-react의-starttransition-사용하기">3. React의 startTransition 사용하기</h3>
<p>client component에서 server action을 호출하는 방법</p>
<pre><code class="language-typescript">// app/actions.js
&#39;use server&#39;; // client-component 파일에서 server action 직접 정의할 수 없기 때문에, 따로 파일을 만들어야합니다.

export async function addItem(id) {
  await addItemToDb(id);
  revalidatePath(`/product/${id}`);
}

// app/components/example-client-component.js
&#39;use client&#39;;

import { useTransition } from &#39;react&#39;;
import { addItem } from &#39;../actions&#39;;

function ExampleClientComponent({ id }) {
  let [isPending, startTransition] = useTransition();

  return (
    &lt;button onClick={() =&gt; startTransition(() =&gt; addItem(id))}&gt;
      Add To Cart
    &lt;/button&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 사이트 성능 메트릭]]></title>
            <link>https://velog.io/@jh_lee95/%EC%9B%B9-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5-%EB%A9%94%ED%8A%B8%EB%A6%AD</link>
            <guid>https://velog.io/@jh_lee95/%EC%9B%B9-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EC%84%B1%EB%8A%A5-%EB%A9%94%ED%8A%B8%EB%A6%AD</guid>
            <pubDate>Wed, 26 Jul 2023 18:18:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>최근 초기 렌더링 개선 작업을 위한 Next.js SSG(Static Site Generation) 도입 과정에서 웹 사이트 성능 메트릭에 대한 개념이 아직 부족하다고 느껴 주요 개념들을 공부하고 이곳에 정리하고자 한다.</p>
</blockquote>
<h1 id="주요-web-지표metrics-항목">주요 Web 지표(metrics) 항목<img src="https://velog.velcdn.com/images/jh_lee95/post/6828d1e0-b8d4-4160-91a2-1d81d504e3f0/image.jpeg" alt=""></h1>
<h3 id="1-ttfb-time-to-first-byte">1. TTFB (Time To First Byte)</h3>
<blockquote>
<p>페이지를 요청했을 때 서버에서 데이터의 첫 번째 바이트가 도착하는 시점을 나타낸다. TTFB는 주로 서버 성능과 직결된다.</p>
</blockquote>
<h4 id="최적화-방법">최적화 방법</h4>
<ul>
<li>서버 애플리케이션 로직 최적화</li>
<li>DB 쿼리 최적화 또는 더 빠른 DB로 마이그레이션</li>
<li>더 많은 RAM 또는 CPU를 갖도록 서버 하드웨어 업그레이드</li>
</ul>
<h3 id="2-fcp-first-contentful-paint">2. FCP (First Contentful Paint)</h3>
<blockquote>
<p>페이지가 로드되기 시작하고 컨텐츠의 일부가 화면에 렌더링 될 때 까지의 시간을 의미한다.</p>
</blockquote>
<h4 id="최적화-방법-1">최적화 방법</h4>
<ul>
<li>렌더링을 block 하는 리소스 제거</li>
<li>CSS minify</li>
<li>사용하지 않는 CSS 제거</li>
<li>요청할 origin에 preconnect: <code>&lt;link rel=&quot;preconnect&quot;&gt;</code></li>
<li>서버 응답 시간 (TTFB) 줄이기</li>
<li>여러 페이지 리다이렉트 줄이기</li>
<li>키 요청 preload: <code>&lt;link rel=&quot;preload&quot;&gt;</code></li>
<li>지나치게 큰 네트워크 payload를 피하기</li>
<li>효율적인 캐시 정책으로 정적 자산 제공</li>
<li>과도한 DOM size를 피하기</li>
<li>중요한 요청의 depth를 최소화</li>
<li>웹 폰트 로드 중에 텍스트가 계속 보이도록 함</li>
<li>요청 수를 줄이고 전송 크기를 작게 하기</li>
</ul>
<h3 id="3-fmp-first-meaningful-paint">3. FMP (First Meaningful Paint)</h3>
<blockquote>
<p>사용자가 웹 사이트가 로드되었다고 인식하는 시간. 브라우저가 페이지의 주요 컨텐츠들을 화면에 렌더링하기 시작하는 순간이다. 다만, FMP는 페이지 로드의 작은 차이에 지나치게 민감하기 때문에 일관성이 떨어져서 LCP를 측정하는 것을 권장하고 있다.</p>
</blockquote>
<h3 id="4-tti-time-to-interactive">4. TTI (Time To Interactive)</h3>
<blockquote>
<p>웹 페이지가 완전히 상호작용이 가능(interactive)하게 되는 시점을 나타낸다. 컨텐츠를 볼 수 있지만 스크롤 할 수 없거나 항목을 클릭해도 효과가 없으면 interactive 하지 않은 것이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 렌더링 과정 & 최적화]]></title>
            <link>https://velog.io/@jh_lee95/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@jh_lee95/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Sun, 23 Jul 2023 15:11:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jh_lee95/post/2072a091-6cd4-4430-9eba-d5f51f8b625e/image.webp" alt="">
최근 모달 공통 모듈 작업을 하며 브라우저 렌더링 성능에 관한 이슈를 마주쳐서 다시 한번 브라우저 렌더링 과정을 공부하고 상기하는 목적으로 글을 정리해보려고 한다.</p>
<p><a href="https://velog.io/@jh_lee95/%ED%8C%80%EC%9B%90%EB%93%A4%EC%9D%B4-%EC%82%AC%EC%9A%A9%ED%95%A0-%EA%B3%B5%ED%86%B5-%EB%AA%A8%EB%8B%AC-%EB%AA%A8%EB%93%88%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90">팀원들이 사용할 공통 모달 모듈을 만들어보자.</a></p>
<h1 id="💻-브라우저">💻 브라우저</h1>
<p>브라우저 렌더링 과정을 알아보기전에 브라우저가 무엇인지 먼저 알아보자. 우선 브라우저는 우리가 흔히 인터넷에 접속할 때 사용하는 Chrome, Safari, Firefox, Internet Explorer 등을 말한다.</p>
<p>MDN에서는 브라우저에 대해 웹에서 페이지를 찾아서 보여주고, 사용자가 하이퍼링크를 통해 다른 페이지로 이동할 수 있도록 하는 프로그램이라고 설명하고 있다. 여기서 중요하다고 생각하는 부분은 찾아서 보여준다는 것이다.</p>
<p>브라우저는 유저가 선택한 자원을 서버로 부터 받아와서 유저에게 보여준다. 이 자원은 페이지 외에도 이미지, 비디오 등의 컨텐츠들도 포함된다. 받아온 자원들을 렌더링 과정을 통해 유저에게 보여주게 된다.</p>
<h2 id="🪜-브라우저-렌더링-과정">🪜 브라우저 렌더링 과정</h2>
<p>렌더링의 기본적인 동작 과정은 다음과 같다.</p>
<ol>
<li><p>HTML 파일과 CSS 파일을 파싱해서 각각 Tree를 만든다. (Javascript)</p>
</li>
<li><p>두 Tree를 결합하여 Rendering Tree를 만든다. (Style)</p>
</li>
<li><p>Rendering Tree에서 각 노드의 위치와 크기를 계산한다. (Layout)</p>
</li>
<li><p>계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만든다. (Paint)</p>
</li>
<li><p>레이어를 합성하여 실제 화면에 나타낸다. (Composite)</p>
</li>
</ol>
<p>각 단계를 좀 더 자세하게 알아보자.</p>
<h3 id="javascript">Javascript</h3>
<p>브라우저가 페이지를 렌더링하려면 가장 먼저 받아온 HTML 파일을 해석해야한다. Parsing 단계는 HTML 파일을 해석하여 DOM(Document Object Model) Tree를 구성하는 단계이다.</p>
<p>파싱 중 HTML에 CSS가 포함되어 있다면 CSSOM(CSS Object Model) Tree 구성 작업도 함께 진행한다.
<img src="https://velog.velcdn.com/images/jh_lee95/post/fcbf5901-f36a-44a7-a020-96fc0d32951e/image.png" alt=""></p>
<h3 id="style">Style</h3>
<p>Style 단계에서는 Parsing 단계에서 생성된 DOM Tree와 CSSOM Tree를 매칭시켜서 Render Tree를 구성한다. Render Tree는 실제로 화면에 그려질 Tree이다.</p>
<p>예를 들면 Render Tree를 구성할때 visibility: hidden은 요소가 공간을 차지하고, 보이지만 않기 때문에 Render Tree에 포함이 되지만, display: none 의 경우 Render Tree에서 제외된다.
<img src="https://velog.velcdn.com/images/jh_lee95/post/f4c456b4-fed9-4841-b9eb-06208f6fc71d/image.png" alt=""></p>
<h3 id="layout">Layout</h3>
<p>Layout 단계에서는 Render Tree를 화면에 어떻게 배치해야 할 것인지 노드의 정확한 위치와 크기를 계산한다.</p>
<p>루트부터 노드를 순회하면서 노드의 정확한 크기와 위치를 계산하고 Render Tree에 반영한다. 만약 크기 값을 %로 지정하였다면, Layout 단계에서 % 값을 계산해서 픽셀 단위로 변환한다.</p>
<h3 id="paint">Paint</h3>
<p>Paint 단계에서는 Layout 단계에서 계산된 값을 이용해 Render Tree의 각 노드를 화면상의 실제 픽셀로 변환한다. 이때 픽셀로 변환된 결과는 하나의 레이어가 아니라 여러 개의 레이어로 관리된다.</p>
<p>당연한 말이지만 스타일이 복잡할수록 Paint 시간도 늘어난다. 예를 들어, 단색 배경의 경우 시간과 작업이 적게 필요하지만, 그림자 효과는 시간과 작업이 더 많이 필요하다.</p>
<h3 id="composite">Composite</h3>
<p>Composite 단계에서는 Paint 단계에서 생성된 레이어를 합성하여 실제 화면에 나타낸다. 우리는 화면에서 웹 페이지를 볼 수 있다.</p>
<h1 id="🛠-브라우저-렌더링-최적화---reflow-repaint-줄이기">🛠 브라우저 렌더링 최적화 - Reflow, Repaint 줄이기</h1>
<h2 id="reflow-layout">Reflow (Layout)</h2>
<p>위에서 언급된 렌더링 과정을 거친 뒤에 최종적으로 페이지가 그려진다고 해서 렌더링 과정이 다 끝난것이 아니다. 어떠한 액션이나 이벤트에 따라 html 요소의 크기나 위치등 레이아웃 수치를 수정하면 그에 영향을 받는 자식 노드나 부모 노드들을 포함하여 Layout 과정을 다시 수행하게 된다. 이렇게 되면 Render Tree와 각 요소들의 크기와 위치를 다시 계산하게 된다. 이러한 과정을 Reflow라고 한다.</p>
<p>Reflow가 일어나는 대표적인 경우는 아래와 같다.</p>
<ol>
<li><p>페이지 초기 렌더링 시(최초 Layout 과정)</p>
</li>
<li><p>윈도우 리사이징 시 (Viewport 크기 변경시)</p>
</li>
<li><p>노드 추가 또는 제거</p>
</li>
<li><p>요소의 위치, 크기 변경 (left, top, margin, padding, border, width, height, 등)</p>
</li>
<li><p>폰트 변경 과(텍스트 내용) 이미지 크기 변경(크기가 다른 이미지로 변경 시)</p>
</li>
</ol>
<h2 id="repaint-paint">Repaint (Paint)</h2>
<p>Reflow만 수행되면 실제 화면에 반영되지 않는다. 위에서 언급된 렌더링 과정과 같이 Render Tree를 다시 화면에 그려주는 과정이 필요하다. 결국은 Paint 단계가 다시 수행되는 것이며 이를 Repaint 라고 한다.</p>
<p>하지만 무조건 Reflow가 일어나야 Repaint가 일어나는것은 아니다. background-color, visibility와 같이 레이아웃에는 영향을 주지 않는 스타일 속성이 변경되었을 때는 Reflow를 수행할 필요가 없기 때문에 Repaint만 수행하게 된다.</p>
<h2 id="🌟-reflow-repaint-줄이기">🌟 Reflow, Repaint 줄이기</h2>
<h4 id="1-사용하지-않는-노드에는-visibilty-invisible-보다-display-none을-사용하기">1. 사용하지 않는 노드에는 visibilty: invisible 보다 display: none을 사용하기</h4>
<p>visibility invisible은 레이아웃 공간을 차지하기 때문에 reflow의 대상이 된다. 하지만  display none은 Layout 공간을 차지하지 않아 Render Tree에서 아예 제외된다.</p>
<h4 id="2-reflow-repaint-가-발생하는-속성-사용-피하기">2. Reflow, Repaint 가 발생하는 속성 사용 피하기</h4>
<p>아래는 각각 Reflow, Repaint가 일어나는 CSS 속성들 이다. Reflow가 일어나면 Repaint는 필연적으로 일어나야 하기 때문에 가능하다면 Reflow가 발생하는 속성보다 Repaint 만 발생하는 속성을 사용하는것이 좋다.</p>
<h4 id="reflow가-일어나는-대표적인-속성">Reflow가 일어나는 대표적인 속성</h4>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>position</td>
<td>width</td>
<td>height</td>
<td>left</td>
<td>top</td>
</tr>
<tr>
<td>right</td>
<td>bottom</td>
<td>margin</td>
<td>padding</td>
<td>border</td>
</tr>
<tr>
<td>border-width</td>
<td>clear</td>
<td>display</td>
<td>float</td>
<td>font-family</td>
</tr>
<tr>
<td>font-size</td>
<td>font-weight</td>
<td>line-height</td>
<td>min-height</td>
<td>overflow</td>
</tr>
<tr>
<td>text-align</td>
<td>vertical-align</td>
<td>white-space</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h4 id="repaint가-일어나는-대표적인-속성">Repaint가 일어나는 대표적인 속성</h4>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>background</td>
<td>background-image</td>
<td>background-position</td>
<td>background-repeat</td>
<td>background-size</td>
</tr>
<tr>
<td>border-radius</td>
<td>border-style</td>
<td>box-shadow</td>
<td>color</td>
<td>line-style</td>
</tr>
<tr>
<td>outline</td>
<td>outline-color</td>
<td>outline-style</td>
<td>outline-width</td>
<td>text-decoration</td>
</tr>
<tr>
<td>visibility</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
<p><strong>또한 Reflow Repaint가 일어나지 않는 transform, opacitiy와 같은 속성도 있다. 따라서 left, right, width, height 보다 transform을, visibility/display 보다 opacitiy를 사용하는 것이 성능 개선에 도움이 된다.</strong></p>
<h4 id="3-영향을-주는-노드-줄이기">3. 영향을 주는 노드 줄이기</h4>
<p>Javascript + Css를 조합하여 애니메이션이 많거나 레이아웃 변화가 많은 요소의 경우 position을 absolute 또는 fixed를 사용하여 영향을 받는 주변 노드들을 줄일 수 있다. fixed와 같이 영향을 받는 노드가 전혀 없는 경우 reflow과정이 전혀 필요가 없어지기 때문에 Repaint 연산비용만 들게 된다.</p>
<p>또다른 방법은 애니메이션 시작시 요소를 absolute, fixed로 변경 후 애니메이션이 종료되었을 때 원상복구 하는 방법도 Reflow, Repaint 연산을 줄이는데에 도움이 된다.</p>
<h4 id="4-프레임-줄이기">4. 프레임 줄이기</h4>
<p>단순히 생각하면 0.1초에 1px씩 이동하는 요소보다 3px씩 이동하는 요소가 Reflow, Repaint 연산비용이 3배가 줄어든다고 볼 수 있다. 따라서 부드러운 효과를 조금 줄여 성능을 개선할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀원들이 사용할 공통 모달 모듈을 만들어보자]]></title>
            <link>https://velog.io/@jh_lee95/%ED%8C%80%EC%9B%90%EB%93%A4%EC%9D%B4-%EC%82%AC%EC%9A%A9%ED%95%A0-%EA%B3%B5%ED%86%B5-%EB%AA%A8%EB%8B%AC-%EB%AA%A8%EB%93%88%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@jh_lee95/%ED%8C%80%EC%9B%90%EB%93%A4%EC%9D%B4-%EC%82%AC%EC%9A%A9%ED%95%A0-%EA%B3%B5%ED%86%B5-%EB%AA%A8%EB%8B%AC-%EB%AA%A8%EB%93%88%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Fri, 21 Jul 2023 05:34:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jh_lee95/post/534e473d-1591-4a11-b528-b199d3dc7ee5/image.webp" alt="">
현재 재직하고 있는 회사에서 여러개의 프로젝트를 한 개의 레포지토리로 관리하는 모노레포 구조를 적용하고 있다. 모달 공통 모듈을 만들어서 모든 프로젝트에서 간편한게 사용할 수 있도록하는 모달 작업을 맡아서 개발하게 되었다.</p>
<h2 id="전형적인-모달-관리-패턴-🤔">전형적인 모달 관리 패턴 🤔</h2>
<pre><code class="language-typescript">import { useState } from &quot;react&quot;;
import Modal from &quot;./components/Modal&quot;;

function App() {
  const [isShowing, setIsShowing] = useState(false);
  const openModal = () =&gt; {
    setIsShowing(true);
  };
  return (
    &lt;div&gt;
      &lt;button onClick={openModal}&gt;Open modal&lt;/button&gt;
      {isShowing &amp;&amp; &lt;Modal /&gt;}
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>위와 같은 모달 패턴은 modal의 open 여부를 판단하는 boolean state를 따로 선언해야 하고 만약 모달의 개수가 여러개일 경우 각각의 boolean state를 선언해야하는 번거로움이 있다. 또한 JSX return 부분에서 조건부 렌더링을 시키는 코드가 추가되어야 하기 때문에 코드가 복잡해지는 단점이 있다.
그래서 최대한 이런 패턴을 지양하고 싶었다.</p>
<h2 id="promise-모달-패턴-🔥">Promise 모달 패턴 🔥</h2>
<p>어떻게하면 모달 관리를 직관적인 코드로 작성하고 팀원들이 쉽게 사용할 수 있도록 할까 고민하던 찰나에 <a href="https://www.npmjs.com/package/react-modal-promise">react-promise-modal</a> 이라는 라이브러리를 발견했다. 해당 라이브러리는 Promise 객체를 이용하여 모달 상태를 비동기 패턴으로 관리할 수 있는 모달 hooks를 제공해준다.
하지만 이 라이브러리는 마지막 npm 업데이트가 2021년일 정도로 지속적인 업데이트가 되고있지 않은 상황인데다가 가장 중요한건 <strong>react-bootstrap 이라는 서드파티 라이브러리와의 결합을 해야지만 사용할 수 있었다.</strong> 
react-bootstrap의 용량이 1.4MB 인것도 문제지만 이미 tailwindCSS와 framer-motion의 조합으로 UI 개발을 진행하고 있는 상황에서 모달 하나만을 위해 react-bootstrap을 적용한다는건 굉장히 비합리적이였다. 따라서 react-promise-modal을 적용하지는 않되 해당 라이브러리를 참고하여 모달을 위한 custom hook을 따로 만들기로 했다.</p>
<h2 id="modal-custom-hook">Modal Custom Hook</h2>
<pre><code class="language-typescript">export type PromiseResolvePayload&lt;Action extends string&gt; =
  Action extends &quot;CONFIRM&quot;
    ? { action: Action; value: any }
    : { action: Action };

export type CloseModalProps = {
  closeModal: (
    param?: PromiseResolvePayload&lt;&quot;CANCEL&quot;&gt; | PromiseResolvePayload&lt;&quot;CONFIRM&quot;&gt;
  ) =&gt; void;
};

type ModalOptions&lt;Props extends CloseModalProps&gt; = {
  modalComponent: React.FunctionComponent&lt;Props&gt;;
  modalProps?: Omit&lt;Props, &quot;closeModal&quot;&gt;;
  position: &quot;top&quot; | &quot;center&quot; | &quot;bottom&quot;;
  animation?: &quot;bottom-up&quot;;
  overlayColor?: &quot;black&quot; | &quot;white&quot;;
  isFullScreen?: boolean;
};</code></pre>
<pre><code class="language-typescript">export default function useModal() {
  const { modals } = useSnapshot(globalModalState);

  const showModal = useCallback(
    &lt;Props extends CloseModalProps&gt;(options: ModalOptions&lt;Props&gt;) =&gt; {
      return new Promise&lt;PromiseResolvePayload&lt;&quot;CONFIRM&quot; | &quot;CANCEL&quot;&gt;&gt;(
        (resolve) =&gt; {
          globalState.modals = [
            ...modals,
            {
              ...options,
              position: modalPosition[options.position],
              animation: options.animation
                ? modalAnimation[options.animation][options.position]
                : {},
              resolve,
              id: modalId++,
            },
          ];
        }
      );
    },
    [modals]
  );

  const closeModal = useCallback(
    (data: PromiseResolvePayload&lt;&quot;CANCEL&quot;&gt;) =&gt; {
      const newModals = [...modals];
      const lastModal = newModals.pop();
      lastModal?.resolve(data);
      globalState.modals = newModals;
    },
    [modals]
  );

  return { showModal, closeModal };
}</code></pre>
<p>위와 같이 useModal 커스텀훅 안에 showModal, closeModal 메서드 2개를 선언하였다.
showModal의 파라미터로는 required 필드(모달 컴포넌트, position)가 있고, 그 외 optional한 4가지 파라미터를 추가로 넘기게된다.
showModal을 호출할 시 Promise 객체를 return 시키며 이때 Promise callback 함수에서 globalState의 modals 배열에 push 한다.</p>
<h3 id="clientconetexttsx-코드-일부">ClientConetext.tsx 코드 일부</h3>
<pre><code class="language-typescript"> return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Toaster position=&quot;bottom-center&quot; duration={3000} /&gt;
      {children}
      &lt;ReactQueryDevtools /&gt;
      {modals.map((modal) =&gt; {
        const Modal = modal.modalComponent;

        return (
          &lt;ModalContainer
            key={modal.id}
            closeModal={closeModal}
            animation={modal.animation}
            position={modal.position}
            overlayColor={modal.overlayColor}
            isFullScreen={modal.isFullScreen}
          &gt;
            &lt;Modal closeModal={closeModal} {...modal.modalProps} /&gt;
          &lt;/ModalContainer&gt;
        );
      })}
    &lt;/QueryClientProvider&gt;
  );</code></pre>
<p><strong>파일 계층에서 최상위인 ClientContext에서 modals의 배열을 렌더링 시킨다.</strong> 
ModalContainer 컴포넌트는 Overlay와 모달의 position 및 animation(framer-motion)을 조정하는 역할을 한다.</p>
<p>closeModal 메서드는 </p>
<pre><code class="language-typescript">closeModal({ action: &#39;confirm&#39; | &#39;cancel&#39;})</code></pre>
<p>위 형태로 호출되며 closeModal 파라미터 값은 showModal 메서드에서 return 된 Promise 객체의 resolve 파라미터로 전달되게 된다.</p>
<h2 id="modal-custom-hook-사용예시">Modal Custom Hook 사용예시</h2>
<h3 id="모달-컴포넌트-파일-생성">모달 컴포넌트 파일 생성</h3>
<pre><code class="language-typescript">import { CloseModalProps } from &quot;./hooks/useModal&quot;;
type TestModalProps = {
  title: string;
  subTitle: string;
} &amp; CloseModalProps;

const TestModal = ({ title, subTitle, closeModal }: TestModalProps) =&gt; {
  const cancel = (): void =&gt; {
    closeModal({ action: &quot;CANCEL&quot; });
  };
  const confirm = (): void =&gt; {
    closeModal({ action: &quot;CONFIRM&quot;, value: { name: &quot;leejoonhee&quot;, age: 28 } });
  };

  return (
    &lt;div
      className=&quot;relative overflow-y-scroll flex flex-col px-6 py-10 bg-white w-full&quot;
    &gt;
      &lt;h1 className=&quot;typo-head1&quot;&gt;{title}&lt;/h1&gt;
      &lt;h2 className=&quot;typo-subHead1&quot;&gt;{subTitle}&lt;/h5&gt;
      &lt;div className=&quot;flex gap-x-2 mt-4&quot;&gt;
        &lt;CommonButton
          theme=&quot;light&quot;
          onClick={() =&gt; {
            cancel();
          }}
        &gt;
          CLOSE
        &lt;/CommonButton&gt;
        &lt;CommonButton
          theme=&quot;primary&quot;
          onClick={() =&gt; {
            confirm();
          }}
        &gt;
          CONFIRM
        &lt;/CommonButton&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default TestModal;
</code></pre>
<h3 id="showmodal-호출">showModal 호출</h3>
<pre><code class="language-typescript">const { showModal } = useModal();

  const onHandleModalButton = async () =&gt; {
    const testModalRes = await showModal({
      modalComponent: TestModal,
      modalProps: { title: &quot;Test&quot;, subTitle: &quot;Test&quot; },
      position: &quot;bottom&quot;,
      animation: &quot;bottom-up&quot;,
    });

    if (testModalRes.action === &quot;CONFIRM&quot;) {
      console.log(&quot;confirm&quot;, testModalRes.value);
      await axios.get(&#39;/test/confirm&#39;, { params: testModalRes.value })
    } else {
      console.log(&quot;cancel&quot;);
    }
  };</code></pre>
<p>예시와 같이 async/await 문법을 사용하여 비동기적으로 showModal 메서드를 호출하게 된다. 모달 response로부터 &quot;CONFIRM&quot; | &quot;CANCEL&quot; 액션을 return 받은 후 액션값에 따라 로직을 분기처리 할 수 있다. 또한 더이상 모달 상태에 대한 코드를 작성하지 않아도 되기 때문에 매우 직관적인 코드 작성이 가능하다!</p>
<p>이와같이 Modal 공통 모듈을 만들었으며 오프라인 코드리뷰 시간에 팀원들에게도 매우 긍정적인 피드백을 받았다. 현재 진행형으로 팀원들과 같이 모듈을 사용하면서 좀 더 보완할 수 있는 방법을 지속적으로 찾고있다.</p>
<h1 id="🌟-작업을-하며-마주쳤던-이슈">🌟 작업을 하며 마주쳤던 이슈</h1>
<p>작업을 대강 마무리하고 실기기로 웹에서 테스트해봤을 때 PC와 Android 환경에서는 문제가 없이 잘 동작했다. <strong>하지만 아이폰에서는 모달이 나타날 때 애니메이션이 매우 버벅이는 이슈가 있었다.</strong>
처음에는 Safari 이슈인가? 해서 chrome, whale 앱 등에서도 테스트해봤지만 버벅이는건 똑같았다.. 애니메이션을 표현하는 framer-motion 라이브러리를 다운그레이드 해보기도 하고 dev환경이라 그런가 build해서 실환경에서 테스트해보기도 하는 등 몇시간을 구른 결과 결국 근본적인 이유인 성능 최적화를 중심으로 구글링을 시작했다.</p>
<h3 id="원인">원인</h3>
<p>성능최적화 키워드로 찾아봤을 때 원인을 금방 찾을 수 있었다.
기존에 사용하던 framer-motion을 이용한 모달 애니메이션 값을</p>
<pre><code class="language-css">bottom: {
      initial: { bottom: -getWindowHeight() },
      animate: { bottom: &quot;0px&quot; },
    }</code></pre>
<p>이런 형식으로 주고 있었다. (getWindowHeight 함수는 window.innerHeight 값을 return 하는 함수)</p>
<p>이게 문제가 되는 이유는 다름아닌 css position 속성인 bottom 값이다. 그 이유는 top, right, bottom, left 속성 사용 시 reflow를 발생시키기 때문이다. 애니메이션을 위해 -window.innerHeight 부터 0px까지 bottom 값이 동적으로 변하였고 reflow가 계속적으로 발생하였으니 성능이 악화되는건 너무나도 당연한 결과였다.</p>
<h3 id="해결">해결</h3>
<p>그럼 렌더링 최적화와 동시에 어떻게 모달 애니메이션을 표현하지? 를 고민하며 찾아보던 중 매우 간단하게 해결법을 찾을 수 있었다. 그건 바로 css position 속성 말고 transform 속성으로 대체하는 것. 
<strong>transform은 Repaint, Reflow가 일어나지 않기 때문에 높은 성능의 애니메이션을 구현할 수 있다.</strong> </p>
<p>결국 기존 framer-motion 애니메이션 코드를 </p>
<pre><code class="language-css">bottom: {
      style: { willChange: &quot;transform&quot; },
      initial: { transform: `translateY(${-getWindowHeight()})` },
      animate: { transform: &quot;translateY(0%)&quot; },
    }</code></pre>
<p>이렇게 수정하니 IOS 환경 뿐만아니라 레거시 한 모바일(아이폰8)에서도 애니메이션이 매우 매끄럽게 잘 동작했다!
또한 요소의 변화를 미리 브라우저에게 알려주어 브라우저가 미리 최적화를 하게 할 수 있는 속성인 <strong>will-change</strong> 속성을 추가하여 렌더링 성능 향상을 최대한 끌어올렸다.</p>
<p><strong>이 이슈를 접하면서 내가 간과하고 있었던 브라우저 렌더링 과정의 중요성을 다시 한번 느끼게 해주었고 조만간 브라우저 렌더링 과정에 대한 글을 정리하여 업로드 할 예정이다.</strong>  </p>
<p>참고자료
<a href="https://www.npmjs.com/package/react-modal-promise">react-promise-modal</a>
<a href="https://boxfoxs.tistory.com/409">브라우저 렌더링 성능 최적화</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD | GithubAction]]></title>
            <link>https://velog.io/@jh_lee95/CICD-GithubAction</link>
            <guid>https://velog.io/@jh_lee95/CICD-GithubAction</guid>
            <pubDate>Wed, 01 Mar 2023 14:12:51 GMT</pubDate>
            <description><![CDATA[<h1 id="0-글의-목적">0. 글의 목적</h1>
<p>이미 실무 환경에서 GithubAction을 이용하여 CI/CD 환경을 직접 구축하기도 해보고 사용하고 있지만 CI/CD라는 용어 자체의 개념에 대해선 두루뭉실하게 알고있는 느낌이라 CI/CD 개념을 정리하기 위해 글을 작성합니다. </p>
<h1 id="1-cicd란">1. CI/CD란?</h1>
<p><img src="https://velog.velcdn.com/images/jh_lee95/post/8a196114-dffa-472a-8b1d-615383e6b9c7/image.png" alt=""></p>
<blockquote>
<p>개발 &gt; 빌드 &gt; 테스트 &gt; 릴리즈 &gt; 배포</p>
</blockquote>
<p>CI/CD는 애플리케이션 개발 단계부터 배포 때까지의 모든 단계를 자동화를 통해서 좀 더 효율적이고 빠르게 사용자에게 빈번히 배포할 수 있는 것을 말한다.</p>
<p><strong>CI</strong> - <strong>지속적인 통합</strong>으로, 새로운 코드가 작성되었을 때 주기적으로 빌드 및 테스트가 진행되면서 공유되는 repository에 merge 되는 것
<strong>CD</strong> - <strong>지속적인 배포(제공)</strong>으로, CI단계를 거친 코드를 실제 운영 서버에 자동 배포 하는 것</p>
<h1 id="2-ci-단계에서">2. CI 단계에서,</h1>
<p>개발자는 github와 같은 형상관리 시스템에 계속 커밋하고 통합합니다. 통합한 코드를 빌드하고 테스트하여 버그가 발생하면 버그를 해결합니다. 이 과정에서 빌드와 테스트를 자동으로 진행 해주면 아래와 같은 장점이 있습니다.</p>
<blockquote>
<ul>
<li>여러 개발자가 관련된 코드를 작업하여 발생하는 충돌 문제를 해결할 수 있다.</li>
</ul>
</blockquote>
<ul>
<li>코드 검증에 들어가는 시간이 줄어들며, 개발 편의성이 좋아진다.</li>
<li>커밋 할 때 마다 빌드와 자동 테스트가 이루어져 잘못된 merge를 예방할 수 있다.</li>
</ul>
<p>repository의 특정 브랜치에 push가 되었거나, PR을 요청하였을 때를 확인하여 코드 테스트 및 통합을 할 수 있도록 합니다.</p>
<h1 id="3-cd-단계에서">3. CD 단계에서,</h1>
<p>공유 repository에 통합된 코드가 CI 단계를 마쳤다면 해당 코드는 배포할 준비가 되었습니다. 이 코드를 자동으로 배포하여 사용자는 항상 최신 버전의 서비스를 사용할 수 있게 됩니다. 배포를 자동화 함으로써 일일이 deploy하지 않아도 되며, 배포보다 개발에 집중할 수 있게 됩니다.</p>
<p>하지만, Delivery와 Deployment는 약간 다릅니다.</p>
<blockquote>
<ul>
<li>지속적 제공 (Continuous Delivery)<ul>
<li>CI를 마치고 릴리즈가 가능한 상태라면 배포까지 자동으로 해주는 것</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li>지속적 배포 (Continuous Deployment)<ul>
<li>CI를 마치고 릴리즈가 가능한 상태라면 사람의 검증을 통해 수동으로 배포하는 것
<img src="https://velog.velcdn.com/images/jh_lee95/post/e71a65ad-ad99-430f-b430-f0c653edb254/image.png" alt=""></li>
</ul>
</li>
</ul>
<h1 id="3-cicd-툴">3. CI/CD 툴</h1>
<p>CI/CD 툴은 주로, Jenkins, Github Action, CircleCI,TeamCity 등등 찾아보니 많은 툴이 있다고 합니다. 그 중에서 저에게 가장 익숙한 CI/CD 툴인 GithubAction에 대한 개념을 간단하게 정리해보았습니다.</p>
<h3 id="github-action-이란">Github Action 이란?<img src="https://velog.velcdn.com/images/jh_lee95/post/81fb7e5f-26c0-4721-bc6c-48f504bc5488/image.png" alt=""></h3>
<p><strong>Github Action</strong>은 Github에서 제공하는 CI/CD 툴입니다. 주로 개발 코드를 Github에서 관리하기 때문에 Github에 등록된 Repository 라면 CI/CD 관리가 정말 쉽습니다.</p>
<p>GitHub Actions를 사용하면 자동으로 코드 저장소에서 어떤 이벤트(event)가 발생했을 때 특정 작업이 일어나게 하거나 주기적으로 어떤 작업들을 반복해서 실행시킬 수도 있습니다. 예를 들어, 누군가가 코드 저장소에 Pull Request를 생성하게 되면 GitHub Actions를 통해 해당 코드 변경분에 문제가 없는지 각종 검사를 진행할 수 있고요. 어떤 새로운 코드가 메인(main) 브랜치에 유입(push)되면 GitHub Actions를 통해 소프트웨어를 빌드(build)하고 상용 서버에 배포(deploy)할 수도 있습니다. 뿐만 아니라 매일 밤 특정 시각에 그날 하루에 대한 통계 데이터를 수집시킬 수도 있습니다.</p>
<h3 id="github-actions-사용법을-익히기-전-꼭-알아야할-용어들">GitHub Actions 사용법을 익히기 전 꼭 알아야할 용어들</h3>
<blockquote>
<ul>
<li><strong>Workflows</strong> </li>
</ul>
</blockquote>
<ul>
<li>하나 이상의 작업(Job)을 실행하는 자동화된 프로세스입니다. 워크플로우는 YAML 파일에 의해 정의되고 특정 이벤트(Event)에 의해 트리거 되거나 직접 수동으로 트리거 될 수 있습니다.<ul>
<li><strong>Events</strong> </li>
<li>위 워크플로우를 실행하게 하는 트리거를 뜻합니다. 새로운 코드를 push하거나, 새로운 PR을 생성하는 행위를 예로 들 수 있습니다.</li>
<li><strong>Jobs</strong> </li>
</ul>
</li>
<li>workflow내 정의된 여러 개의 순서 있는 작업(step)들의 묶음을 뜻합니다. 실행하고 싶은 workflow에서 실행할 작업들을 순서에 맞게 정의해놓은 부분이며, 각 작업(step)들은 서로 의존적입니다. 따라서 첫 번째 step에서 내가 작성한 애플리케이션을 빌드한 뒤 다음 스텝에서 첫 번째 스텝에서 빌드된 애플리케이션을 활용해 테스트를 돌릴 수 있습니다.<ul>
<li><strong>Actions</strong></li>
<li>복잡하며 자주 반복되는 작업들을 사용하기 편하게 만들어놓은 어플리케이션입니다. 라이브러리와 비슷한 개념이죠.</li>
<li><strong>Runners</strong></li>
<li>Workflow가 실행되는 서버. Ubuntu, Windows, MacOS를 지원합니다. 각 Runner는 한 번에 하나의 Job만 실행 가능합니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript | Generic]]></title>
            <link>https://velog.io/@jh_lee95/TypeScript-Generic</link>
            <guid>https://velog.io/@jh_lee95/TypeScript-Generic</guid>
            <pubDate>Tue, 28 Feb 2023 14:02:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jh_lee95/post/d35cb35d-1e87-463b-ba29-37c645048e48/image.png" alt=""></p>
<h1 id="0-타입스크립트-제네릭">0. 타입스크립트 제네릭</h1>
<h4 id="타입스크립트-제네릭generic">타입스크립트 제네릭(Generic)</h4>
<p>제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다.
정적 type 언어는 클래스나 함수를 정의할 때 type을 선언해야 한다.
Generic은 코드를 작성할 때가 아니라 코드를 수행될 때(런타임) 타입을 명시한다.
코드를 작성할 때 식별자를 써서 아직 정해지지 않은 타입을 표시한다.
일반적으로 식별자는 T, U, V 등을 사용한다.
필드 이름의 첫 글자를 사용하기도 한다.</p>
<ol>
<li>제네릭이란? 왜 제네릭을 써야하나?
Generic이란 데이터의 타입을 일반화한다(generalize)한다는 것을 뜻한다.
Generic은 자료형을 정하지 않고 여러 타입을 사용할 수 있게 해준다.
즉, 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다. 한번의 선언으로 다양한 타입에 &#39;재사용&#39;이 가능하다는 장점이 있다.</li>
</ol>
<p>제네릭을 쓰지 않을 경우, 불필요한 타입 변환을 하기 때문에 프로그램의 성능에 악영향을 미치기도 하는데, 제네릭을 사용하게되면 따로 타입 변환을 할 필요가 없어서 프로그램의 성능이 향상되는 장점이 있다. (링크)</p>
<ol start="2">
<li>generic이 없다면?
위처럼 generic을 쓰지 않는다면, 1) 타입을 미리 지정하거나 2) any를 이용하여 구현할 수 있다.
1) 타입을 미리 지정하자면, 확실한 타입체크가 이뤄질 수 있겠지만 항상 number라는 타입을 받아야하므로 범용성이 떨어진다.
2) 그렇다고 any를 사용한다면 자료의 타입을 제한할 수 없을 뿐더러, 이 function을 통해 어떤 타입의 데이터가 리턴되는지 알 수 없다.</li>
</ol>
<p>이런 경우에 사용할 수 있는 것이 제네릭이다.</p>
]]></description>
        </item>
    </channel>
</rss>