<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>달과 함께하는 IT</title>
    <link>https://zerosial.tistory.com/</link>
    <description>주니어 프론트엔드 개발자의 좌충우돌 개발 블로그</description>
    <language>ko</language>
    <pubDate>Mon, 13 Jul 2026 20:08:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>제로시엘</managingEditor>
    <image>
      <title>달과 함께하는 IT</title>
      <url>https://tistory1.daumcdn.net/tistory/2540478/attach/f1f6c7b13c6c43df8a7255b2bfdcfeb9</url>
      <link>https://zerosial.tistory.com</link>
    </image>
    <item>
      <title>[React + Native] 한글 입력 및 키보드 (천지인 키보드) 문제</title>
      <link>https://zerosial.tistory.com/entry/React-Native-%ED%95%9C%EA%B8%80-%EC%9E%85%EB%A0%A5-%EB%B0%8F-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%B2%9C%EC%A7%80%EC%9D%B8-%ED%82%A4%EB%B3%B4%EB%93%9C-%EB%AC%B8%EC%A0%9C</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Native (웹앱) 환경에서 빌드중인 웹의 입력기에서 일부 조건 혹은 일부 키보드 형태에 따라 몇가지 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 처음의 문제는 천지인 키보드에서 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_OsK0Ursabp.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VboTZ/btsILv4MGdA/vmMqG8Yu30SWewwkJqm6o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VboTZ/btsILv4MGdA/vmMqG8Yu30SWewwkJqm6o0/img.png&quot; data-alt=&quot;문제가 되는건 ㆍ을 입력하는 것&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VboTZ/btsILv4MGdA/vmMqG8Yu30SWewwkJqm6o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVboTZ%2FbtsILv4MGdA%2FvmMqG8Yu30SWewwkJqm6o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;453&quot; data-filename=&quot;chrome_OsK0Ursabp.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문제가 되는건 ㆍ을 입력하는 것&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;문제 1. 자수 제한을 단순하게 걸었더니 천지인 키보드 사용시 문제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;가장 심플한 예시로 예를 들어 천지인 키보드로 송을 입력하게 된다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;ㅅ &lt;span style=&quot;text-align: center;&quot;&gt;ㆍ =&amp;gt;&amp;nbsp; 소 로 일시적으로 글자수가 한자 더 늘어나게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;이 때 예를 들어 8자 (maxLength나 slice를 통한 강제 조작)을 하게 되는 경우 ㅅ에서 소로 넘어가지 않는 문제가 발생했다.&lt;br /&gt;&lt;br /&gt;기존 코드는 다음과 같이 입력을 처리하는 방식이였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721884022902&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const changeValue = (e) =&amp;gt; {
    const sliceValue = e.target.value.slice(0, maxLength);
    setValue(sliceValue);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 문제는 react의 Composition을 조절하는 기능을 통해 수정할 수 있었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721884169323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleCompositionStart = (e) =&amp;gt; {
    if (e.target.value.length &amp;gt;= maxLength) {
      return;
    }

    setIsComposing(true);
  };

  const handleCompositionEnd = (e) =&amp;gt; {
    setIsComposing(false);
    const inputValue = e.target.value;
    setValue(inputValue.slice(0, maxLength));
  };

  const handleChange = (e) =&amp;gt; {
    let inputValue = e.target.value;
    if (!isComposing) {
      inputValue = e.target.value.slice(0, maxLength);
    }
    setValue(inputValue);
  };

// input의 경우
	&amp;lt;textarea
          type=&quot;text&quot;
          value={value}
          onChange={handleChange}
          onCompositionStart={handleCompositionStart}
          onCompositionEnd={handleCompositionEnd}
	/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글자가 조합되는 composing 시작 시점과 종료 시점을 컨트롤 하여 조합시에 슬라이스를 치는것이 아닌 문자 조합이 끝나는 시점에서 자수를 체크 및 slice를 치는 방식으로 변경하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 2. 일부 자동완성 에서 자수제한이 먹지 않고 비정상적으로 보임&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;PotPlayer64_22ToxD0A0l.gif&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eLaxf8/btsIMviF6qv/0J51NNKuJJyrfFDGe3zCk0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eLaxf8/btsIMviF6qv/0J51NNKuJJyrfFDGe3zCk0/img.gif&quot; data-alt=&quot;원래는 8자 제한이 되어야 한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eLaxf8/btsIMviF6qv/0J51NNKuJJyrfFDGe3zCk0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/eLaxf8/btsIMviF6qv/0J51NNKuJJyrfFDGe3zCk0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;286&quot; height=&quot;589&quot; data-filename=&quot;PotPlayer64_22ToxD0A0l.gif&quot; data-origin-width=&quot;286&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원래는 8자 제한이 되어야 한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 문제를 해결하고나니 다음과 같이 조합중에는 따로 slice를 치지 않을뿐더러 일부 &quot;삼성 키보드&quot; 등에서 자동완성 기능이 무제한적으로 늘어나거나 2글짜식 입력되는 골때리는 현상이 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721884434257&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 포커스를 강제로 취소시키고 다시 설정
    if (inputValue.length &amp;gt;= maxLength) {
      textareaRef.current.setSelectionRange(maxLength, maxLength);
    }


&amp;lt;textarea
          ref={textareaRef}
          value={value}
          onInput={changeValue}
          onCompositionStart={handleCompositionStart}
          onCompositionEnd={handleCompositionEnd}
        /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 통해 강제적으로 포커스를 취소시키는 동작을 통해 자동완성이 자수제한을 넘기게 된다면 강제로 끊고 기존 로직에서 처리하도록 변경하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제 3. 더 골때리는 상황이 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강제로 포커스를 조절하다 보니 더 골때리는 상황이 발생했다. 가장 큰 문제는 리액트의 onCompositionEnd의 시점을 강제로 포커스를 끊거나 다시 활성화하면 제대로 가져오지 않는 현상이 발생하였다. 자동적으로 글자 조합이 끝난다면 정상적으로 handleCompositionEnd가 실행되지만 강제로 포커스 혹은 문자열을 조작하다 보면 해당 이벤트가 누락된 것&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 수정 코드&lt;/p&gt;
&lt;pre id=&quot;code_1721884993096&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 글자 조합 완료
  const handleCompositionEnd = (e) =&amp;gt; {
    // 조합이 완성되는 것은 일정 시간 후 해제되는 시점을 감지하여 maxLength를 넘는다면 조정
    setValue(e.target.value.slice(0, maxLength));
  };

  const handleChange = (e) =&amp;gt; {
    let inputValue = e.target.value;

    // 천지인 포함 maxLength + 1 초과 혹은 천지인 미포함 maxLength가 초과한다면 인풋 및 글자 자동완성을 해제하고 30자로 조정후 리턴
    if (
      (inputValue.length &amp;gt; maxLength + 1 &amp;amp;&amp;amp; inputValue.endsWith(&quot;ㆍ&quot;)) ||
      (inputValue.length &amp;gt; maxLength &amp;amp;&amp;amp; !inputValue.endsWith(&quot;ㆍ&quot;))
    ) {
      inputValue = inputValue.slice(0, maxLength);
    }

    setValue(inputValue);
  };

&amp;lt;textarea
          value={value}
          onChange={changeValue}
          onCompositionEnd={handleCompositionEnd}
        /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로는 onChange 시점에서 maxLength와 maxLength + 1 시점에 유동적으로 슬라이스 하며 (&lt;b&gt;슬라이스 및 input값을 변경하면 컴포넌트가 재로드(setValue) 하면서 강제적으로 자동완성 또한 해제된다&lt;/b&gt;) 또한 내가 글자를 쓰다가 넘어갈 떄 이외에 자동완성 등을 통해 늘어나도 한번 더 slice를 통한 문자열을 재배치 함으로써 해결했다.&lt;/p&gt;</description>
      <category>IT 학습/Problem Solving</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/110</guid>
      <comments>https://zerosial.tistory.com/entry/React-Native-%ED%95%9C%EA%B8%80-%EC%9E%85%EB%A0%A5-%EB%B0%8F-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%B2%9C%EC%A7%80%EC%9D%B8-%ED%82%A4%EB%B3%B4%EB%93%9C-%EB%AC%B8%EC%A0%9C#entry110comment</comments>
      <pubDate>Thu, 25 Jul 2024 14:27:12 +0900</pubDate>
    </item>
    <item>
      <title>Docker를 통한 문서 배포 자동화</title>
      <link>https://zerosial.tistory.com/entry/Docker%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%AC%B8%EC%84%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_pAKVMbTrbG.png&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btsRyQ/btsFAue5qMs/y949koKoEqXrA68qinKsx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btsRyQ/btsFAue5qMs/y949koKoEqXrA68qinKsx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btsRyQ/btsFAue5qMs/y949koKoEqXrA68qinKsx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtsRyQ%2FbtsFAue5qMs%2Fy949koKoEqXrA68qinKsx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;193&quot; height=&quot;175&quot; data-filename=&quot;chrome_pAKVMbTrbG.png&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드는 &lt;b&gt;Docker를 사용하여 Node.js 애플리케이션과 Nginx 서버를 구성&lt;/b&gt;하고, 효율적으로 배포하기 위한 전반적인 프로세스를 다룹니다. 특히 &lt;b&gt;SVN에서 코드를 체크아웃&lt;/b&gt;하고, &lt;b&gt;문서를 생성&lt;/b&gt;하며, &lt;b&gt;live-server&lt;/b&gt;를 이용한 로컬 개발 환경 설정에 초점을 맞춥니다. 또한, &lt;b&gt;Docker Compose를 활용하여 여러 컨테이너를 관리&lt;/b&gt;하는 방법에 대해서도 설명합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;샘플 파일&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/dwAuos/btsFyyo2vYO/CKGj4L8dvpT7DxzgONuJK1/Sample.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;Sample.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;br /&gt;배포 Github&lt;br /&gt;&lt;a href=&quot;https://github.com/zerosial/Docker_svn_Nginx&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/zerosial/Docker_svn_Nginx&lt;/a&gt;&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1709625257976&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - zerosial/Docker_svn_Nginx&quot; data-og-description=&quot;Contribute to zerosial/Docker_svn_Nginx development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/zerosial/Docker_svn_Nginx&quot; data-og-url=&quot;https://github.com/zerosial/Docker_svn_Nginx&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KyLMD/hyVuhyxfkn/WiLdBbJDkOxzgUKlTTvRs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/zerosial/Docker_svn_Nginx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/zerosial/Docker_svn_Nginx&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KyLMD/hyVuhyxfkn/WiLdBbJDkOxzgUKlTTvRs0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - zerosial/Docker_svn_Nginx&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to zerosial/Docker_svn_Nginx development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배포 파일 및 환경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_fgiy0ZuGHY.png&quot; data-origin-width=&quot;134&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXUINm/btsFAusC0CI/zI9jt589z7594GSevWcqqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXUINm/btsFAusC0CI/zI9jt589z7594GSevWcqqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXUINm/btsFAusC0CI/zI9jt589z7594GSevWcqqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXUINm%2FbtsFAusC0CI%2FzI9jt589z7594GSevWcqqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;255&quot; data-filename=&quot;chrome_fgiy0ZuGHY.png&quot; data-origin-width=&quot;134&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 폴더를 터미널 혹은 VSC를 통하여 열고 Docker가 설치된 상태로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker-compose build&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 배포&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker-compose up -d&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SVN URL 및 정보 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update_docs.sh 및 Dockerfile.node의 svn 체크아웃 및 업데이트 시 URL 아이디, 비밀번호, SSL 우회 명령어가 삽입되어 있습니다 해당 URL과 세부정보는 상황에 따라 수정하면 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포트 변경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 도커 내부에 8080에서 live-server가 호스팅되며 해당 컨테이너를 &lt;b&gt;nginx/default.conf&lt;/b&gt; 를 통해 배포하게 됩니다. 포트 설정이 변경될 경우 해당 파일의 listen 부분에서 포트를 변경하고 &lt;b&gt;docker-compose.yml&lt;/b&gt; 에서 추가적으로 8720:8720으로 작업된 부분을 수정하시면 됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성 주기 및 시간 관리&lt;/b&gt; &lt;br /&gt;&lt;b&gt;RUN (crontab -l ; echo &quot;* * * * * /app/update_docs.sh &amp;gt;&amp;gt; /var/log/cron.log 2&amp;gt;&amp;amp;1&quot;) | crontab -&lt;/b&gt; 위의 명령어를 통해 주기 등록됩니다. 크론 스케줄 표현식으로, 5개의 별표(*)는 분, 시, 일, 월, 요일을 나타냅니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* * * * *&lt;/b&gt;: 모두 별표로 설정되어 있으므로 매분마다 해당 명령을 실행하라는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0 0 * * *&lt;/b&gt;: 이 표현은 매일 자정(0시 0분)에 명령을 실행하라는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;30 6 * * *&lt;/b&gt;: 이 표현은 매일 (6시 30분)에 명령을 실행하라는 의미입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Node.js 컨테이너 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js 기반의 Docker 이미지를 사용하여 애플리케이션 빌드 환경을 구성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WORKDIR을 /app&lt;/b&gt;으로 설정하여 애플리케이션의 작업 디렉토리를 지정합니다.&lt;/li&gt;
&lt;li&gt;필요한 시스템 패키지(&lt;b&gt;subversion&lt;/b&gt;, &lt;b&gt;cron&lt;/b&gt;)를 설치합니다.&lt;/li&gt;
&lt;li&gt;SVN에서 프로젝트 코드를 체크아웃하고, 필요한 npm 패키지(&lt;b&gt;live-server&lt;/b&gt;)를 글로벌로 설치합니다.&lt;/li&gt;
&lt;li&gt;**npm install**을 실행하여 프로젝트 의존성을 설치합니다.&lt;/li&gt;
&lt;li&gt;문서 생성 스크립트(&lt;b&gt;npm run docs&lt;/b&gt;)를 실행하여 프로젝트 문서를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Nginx 컨테이너 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Nginx를 사용하여 Node.js 애플리케이션을 프록시하는 웹 서버 환경을 구성합니다.&lt;/li&gt;
&lt;li&gt;Nginx의 설정 파일을 커스터마이즈하여 Node.js 애플리케이션으로 요청을 전달합니다.&lt;/li&gt;
&lt;li&gt;외부 요청을 처리할 수 있도록 포트 매핑을 구성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker Compose를 이용한 서비스 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt; 파일을 작성하여 Node.js와 Nginx 컨테이너를 정의하고 네트워크를 통해 연결합니다.&lt;/li&gt;
&lt;li&gt;볼륨 마운트를 사용하여 개발 중인 코드를 컨테이너와 동기화할 수 있지만, 볼륨 마운트가 프로덕션 빌드에 영향을 미치지 않도록 주의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 해결 및 최적화&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 누락 문제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Docker Compose의 볼륨 마운트 설정으로 인해 컨테이너 내 파일이 호스트와 동기화되면서 기대한 파일이 누락될 수 있습니다. 해결책으로 볼륨 마운트 설정을 재검토하거나 개발과 배포 환경을 분리합니다.&lt;/li&gt;
&lt;/ul&gt;
이번 환경의 경우 checkout 한 svn 파일이 도커 내 /app에 배치되지 않아 새부 설정을 확인하다 보니 Docker-compose에서 해당 부분이 발견되어 수정하였습니다.
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;// As-is  
	node-container:
    build: # Node.js 애플리케이션 빌드 설정
      context: .
      dockerfile: Dockerfile.node
    **volumes:
      - .:/app**
    networks:
      - mm-app-network # 사용할 네트워크 정의

// To-be
	node-container:
    build: # Node.js 애플리케이션 빌드 설정
      context: .
      dockerfile: Dockerfile.node
    networks:
      - mm-app-network # 사용할 네트워크 정의

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨테이너 내부의 폴더 및 파일 확인 (log)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간 중간 RUN npm run docs &amp;amp;&amp;amp; ls -la /app/docs 와 같이 ls -la 명령어를 통해 빌드된 도커의 파일들을 확인하며 /var/log/cron.log 파일을 생성하는 로직을 통해 백그라운드에서 실행되는 cron이 정상 작동하는지 확인했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경 변수 및 PATH 설정&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌로 설치된 npm등의 패키지가 update_docs.sh에서 cron으로 재실행 될때 작동하지 않는 문제는 &lt;b&gt;PATH&lt;/b&gt; 환경 변수 설정을 통해 해결할 수 있습니다. Dockerfile에서 환경 변수를 적절히 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SVN의 SSL 만료 오류&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 오류의 경우 update 및 checkout 시 마지막 명령어에 --non-interactive --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other 를 추가함으로 해결하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 별 설명&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;build-package.json 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build-package.json&lt;/b&gt; 파일은 특정 npm 패키지(&lt;b&gt;jsdoc&lt;/b&gt;, &lt;b&gt;minami&lt;/b&gt;, &lt;b&gt;taffydb&lt;/b&gt;)의 설치와 문서 생성 스크립트를 정의합니다. 이 파일은 Docker 빌드 과정에서 사용되어, 프로젝트 문서화에 필요한 최소한의 의존성만을 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 package.json이 설치&amp;amp;빌드&amp;amp;테스트 등 문서화 작업에 관계 없는 패키지가 많아 builder-package.json 을 따로 만들고 기존 파일을 대체하는 방식으로 작성되었습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;dependencies&quot;: {
    &quot;jsdoc&quot;: &quot;^4.0.2&quot;, // JSDoc 패키지, JavaScript 코드 문서화 도구
    &quot;minami&quot;: &quot;^1.2.3&quot;, // JSDoc 문서의 템플릿으로 사용될 Minami 스타일
    &quot;taffydb&quot;: &quot;^2.7.3&quot; // JSDoc에서 사용되는 인메모리 데이터베이스
  },
  &quot;scripts&quot;: {
    &quot;docs&quot;: &quot;jsdoc -c jsdoc.conf.json&quot; // jsdoc 명령어를 이용해 문서를 생성하는 스크립트
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;docker-compose.yml 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt; 파일은 Docker Compose를 사용하여 **node-container**와 &lt;b&gt;nginx-container&lt;/b&gt; 서비스를 정의하고, 네트워크를 통해 이들을 연결합니다. 이 구성을 통해 Node.js 애플리케이션을 Nginx를 통해 서빙하는 아키텍처를 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;

services:
  node-container:
    build: # Node.js 애플리케이션 빌드 설정
      context: .
      dockerfile: Dockerfile.node
    networks:
      - mm-app-network # 사용할 네트워크 정의

  nginx-container:
    build: # Nginx 서버 빌드 설정
      context: .
      dockerfile: Dockerfile.nginx
    ports:
      - &quot;8720:8720&quot; # 호스트와 컨테이너 간 포트 매핑
    depends_on:
      - node-container # node-container에 의존성 명시
    networks:
      - mm-app-network # 사용할 네트워크 정의

networks:
  mm-app-network: # 네트워크 정의
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Dockerfile.node 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Dockerfile.node**는 Node.js 기반의 애플리케이션을 빌드하고, 문서를 생성하기 위한 설정을 포함합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Node.js 기반 빌드 스테이지
FROM node:16.20.1

# 작업 디렉토리 설정
WORKDIR /app

# 필수 패키지 설치
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y subversion cron

# 애플리케이션 파일 및 설정 복사
COPY . /app

# SVN에서 프로젝트 코드 체크아웃
RUN svn checkout SVN_URL --username=&quot;svn 아이디&quot; --password='svn 암호' --non-interactive --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other .

# 기존 package.json에서 필요없는 의존성을 빼고 필수 패키지만 설정
COPY build-package.json /app/package.json

# live-server 글로벌 설치
RUN npm install -g live-server

# 의존성 설치 및 문서 생성
RUN npm install
RUN npm run docs &amp;amp;&amp;amp; ls -la /app/docs

# Cron 작업 설정 및 주기적인 문서갱신 관리파일
RUN chmod +x /app/update_docs.sh
RUN (crontab -l ; echo &quot;* * * * * /app/update_docs.sh &amp;gt;&amp;gt; /var/log/cron.log 2&amp;gt;&amp;amp;1&quot;) | crontab -

# Live-server 실행 및 cron 시작
CMD cron -f &amp;amp; live-server /app/docs --port=8080 --host=0.0.0.0 --entry-file=index.html&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Dockerfile.nginx 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dockerfile.nginx&lt;/b&gt; 파일은 Nginx 웹 서버를 설정하고, 커스텀 Nginx 구성 파일을 컨테이너에 복사하는 과정을 정의합니다. 이 파일을 통해 Node.js 애플리케이션에 대한 프록시 서버로 Nginx를 구성하고, 외부 요청을 애플리케이션으로 전달할 수 있도록 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# Nginx 환경 설정
FROM nginx:alpine

# 'nginx:alpine' 이미지 사용: 이는 Nginx 웹 서버가 설치된 경량화된 Alpine Linux 기반 이미지입니다.
# Alpine Linux는 작은 이미지 크기와 보안, 간단한 구성으로 인해 Docker 컨테이너에 많이 사용됩니다.

# Nginx 구성 파일 복사
COPY nginx/default.conf /etc/nginx/conf.d/default.conf

# 'nginx/default.conf' 파일을 컨테이너의 '/etc/nginx/conf.d/default.conf' 위치로 복사합니다.
# 이 과정은 컨테이너 내부의 Nginx 설정을 사용자 정의 설정으로 대체합니다.
# 커스텀 구성 파일은 Nginx 서버의 동작 방식을 정의하며, 특히 Node.js 애플리케이션으로의 프록시 설정을 포함할 수 있습니다.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nginx/default.conf&lt;/b&gt; 파일 내부에 정의된 설정은 Nginx가 클라이언트로부터 받은 요청을 &lt;b&gt;node-container&lt;/b&gt; 서비스의 8080 포트로 전달하도록 지정합니다. 이를 통해 Nginx는 정적 파일을 처리하고, 동적 요청은 Node.js 애플리케이션으로 전달하는 역할을 수행하게 됩니다. 또한, 요청의 헤더를 조작하여 애플리케이션으로 전달하기 전에 필요한 정보를 추가할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;update_docs.sh 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;update_docs.sh&lt;/b&gt; 스크립트는 SVN에서 최신 코드를 가져오고, 문서를 재생성하는 작업을 자동화합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash
cd /app || exit

# SVN 저장소에서 최신 코드 업데이트
svn update --username=&quot;svn 아이디&quot; --password='svn 암호' --non-interactive --trust-server-cert-failures=unknown-ca,cn-mismatch,expired,not-yet-valid,other

# npm 전역설치 절대경로 추가
export PATH=$PATH:/usr/local/bin

# 업데이트된 코드를 바탕으로 문서 재생성
npm run docs

# 문서 생성 완료 메시지 출력
echo &quot;Documentation has been updated.&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;nginx/default.conf 설명 및 주석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**nginx/default.conf**는 Nginx 서버의 구성을 정의하며, Node.js 애플리케이션으로의 요청 프록싱을 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;nginxCopy code
server {
    listen 8720; # Nginx가 8720 포트에서 리스닝

    location / {
        proxy_pass &amp;lt;http://node-container:8080&amp;gt;; # Node.js 애플리케이션으로 요청을 전달
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/109</guid>
      <comments>https://zerosial.tistory.com/entry/Docker%EB%A5%BC-%ED%86%B5%ED%95%9C-%EB%AC%B8%EC%84%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94#entry109comment</comments>
      <pubDate>Tue, 5 Mar 2024 16:54:28 +0900</pubDate>
    </item>
    <item>
      <title>리액트 쿼리와 주스텐드 그리고 우아콘</title>
      <link>https://zerosial.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%99%80-%EC%A3%BC%EC%8A%A4%ED%85%90%EB%93%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%9A%B0%EC%95%84%EC%BD%98</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;작년 우아콘과 더불어 회사 내 사내 발표시 사용했던 ppt가 있어서 공유합니다.&lt;br /&gt;&lt;br /&gt;진작 공유하고 싶었지만.. 사용한 코드나 예시 스크린샷이 아직 공개된 앱이 아니라서 참고 참았다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 발표에 사용한 PPT를 이제야 공유합니다&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #3c3838; text-align: start;&quot;&gt;&lt;a href=&quot;https://gamma.app/public/React-query-Zustand-0yejbuupd15mi5t&quot;&gt;https://gamma.app/public/React-query-Zustand-0yejbuupd15mi5t&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_NhjDZgrPkW.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAhiEM/btsEBXpFDv8/g6r4JM2UPWZbGSX6IuwKpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAhiEM/btsEBXpFDv8/g6r4JM2UPWZbGSX6IuwKpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAhiEM/btsEBXpFDv8/g6r4JM2UPWZbGSX6IuwKpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAhiEM%2FbtsEBXpFDv8%2Fg6r4JM2UPWZbGSX6IuwKpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;434&quot; data-filename=&quot;chrome_NhjDZgrPkW.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 열화된 (직접찍느라) 사진들이 있지만.. 첫 발표라 설랬던 내용을 공유하고 싶습니다.&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/108</guid>
      <comments>https://zerosial.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC%EC%99%80-%EC%A3%BC%EC%8A%A4%ED%85%90%EB%93%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%9A%B0%EC%95%84%EC%BD%98#entry108comment</comments>
      <pubDate>Thu, 8 Feb 2024 13:02:31 +0900</pubDate>
    </item>
    <item>
      <title>2023년 근황 및 2024년 목표  </title>
      <link>https://zerosial.tistory.com/entry/2023%EB%85%84-%EA%B7%BC%ED%99%A9-%EB%B0%8F-2024%EB%85%84-%EB%AA%A9%ED%91%9C-%F0%9F%A4%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2022년 이맘때쯤 개발자가 되기로 다짐하고 벌써 2년이 지났다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ebbf63ca-7cbd-48e1-826d-0e65833a7a3d.webp&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbMWBs/btsEeKrABqj/KXRI6zzANmKpoKkulaD2uk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbMWBs/btsEeKrABqj/KXRI6zzANmKpoKkulaD2uk/img.webp&quot; data-alt=&quot;2년 전 글을 읽다가 일본 풍경을 올려둿길래 올해도 올려본다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbMWBs/btsEeKrABqj/KXRI6zzANmKpoKkulaD2uk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbMWBs%2FbtsEeKrABqj%2FKXRI6zzANmKpoKkulaD2uk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1600&quot; height=&quot;1066&quot; data-filename=&quot;ebbf63ca-7cbd-48e1-826d-0e65833a7a3d.webp&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1066&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2년 전 글을 읽다가 일본 풍경을 올려둿길래 올해도 올려본다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;말도 많고 탈도 많고 고민과 역경도 많았지만 나름 보람찬 2년을 보냈다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;결론부터 말하면 나는 1년차 프론트엔드 개발자가 되었고 &lt;b&gt;1000만명&lt;/b&gt; 가까이 사용하는 앱의 일부 기능 및 꽤 많은 컴포넌트와 페이지를 개발하였다   (정확히 말하면 너무 오래된 앱이라 10몇년만에 새로운 앱을 만들게 됬고 그 프로젝트의 초기부터 완성까지 경험할 수 있었다.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2023년 상반기&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;부트캠프 종료 + 취업을 위한 우당탕탕 생존기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_ztFMRiLVZA.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQEWOE/btsEhryjzJi/8jPaMWSlw7h0x5WBm1OVSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQEWOE/btsEhryjzJi/8jPaMWSlw7h0x5WBm1OVSk/img.png&quot; data-alt=&quot;이게 벌써 5기가 끝나고 6기가 나온다니..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQEWOE/btsEhryjzJi/8jPaMWSlw7h0x5WBm1OVSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQEWOE%2FbtsEhryjzJi%2F8jPaMWSlw7h0x5WBm1OVSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;345&quot; data-filename=&quot;chrome_ztFMRiLVZA.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이게 벌써 5기가 끝나고 6기가 나온다니..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 데브코스 3기를 행하며 2023년 상반기는 말 그대로 &lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;프로젝트의 연속&lt;/b&gt;&lt;/span&gt;이였다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 이 시기에 2년을 통틀어 가장 많은 코드를 처보고 가장 많은 공부의 시간을 할해했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 상세 후기는 각 각 글을 올려 생략하겠지만 얼마 전 다시 들어가 보니 이걸 왜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 짯지 하는 코드가 너무 많더라..&amp;nbsp;&lt;b&gt;리액트보다 Next.js가 무조건 좋다라고 &lt;b&gt;Next.js&lt;/b&gt; 로 2개&lt;/b&gt;의 프로젝트를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행했던것도 지금 보면 너무.. 용감했달까 그때 당시의 고민과 지금의 고민거리가 달라진 것 같다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 여전히 흥미가 가고 기술 스택이 좋지만 최근 대규모 프로젝트를 진행하면서 SSR이나 SEO&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 무조건 최신기술을 쓴다고 장점만 생기는 게 아니라는 사실과 더불어 &lt;br /&gt;&lt;br /&gt;안정성에 더욱 노력하게 되어서 그런 것 같기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;취업의 고배와 쓴맛 그리고 고생 끝 취업성공&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_E69LCwFceW.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVz5mF/btsEfsdjAab/hEZqiUT99WSrCvhuEyaZx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVz5mF/btsEfsdjAab/hEZqiUT99WSrCvhuEyaZx0/img.png&quot; data-alt=&quot;321개만 있는게 아니다.. 흥미가 있는건 읽어서 사실상 500개 이상의 이력서 다이브&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVz5mF/btsEfsdjAab/hEZqiUT99WSrCvhuEyaZx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVz5mF%2FbtsEfsdjAab%2FhEZqiUT99WSrCvhuEyaZx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;454&quot; data-filename=&quot;chrome_E69LCwFceW.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;321개만 있는게 아니다.. 흥미가 있는건 읽어서 사실상 500개 이상의 이력서 다이브&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 한달정도 충전시간을 가진 후 바로 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;취업&lt;/b&gt;&lt;/span&gt;에 돌입했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나이, 비전공, 신입 등 많은 마이너스 포인트가 있었고 서탈도 엄청나게 당했지만 점점 면접을 봐가며 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;합격률이 높아&lt;/b&gt;&lt;/span&gt;졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간 이후부터는 마치 &lt;b&gt;게임의 가챠&lt;/b&gt;를 하는 기분이였다  &lt;br /&gt;&lt;s&gt;(요번주에 20개를 썻는대 면접이 2건이나 되내 무려 10%확율 가차)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 약 5월 말부터 시작한 취준은 8월 초에 끝났으며 최종 합격 3곳 중&lt;br /&gt;&lt;br /&gt;현재 다니는 파인원 커뮤니케이션에 최종 입사를 결정하게 되었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_YO6UKZPCap.png&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kWTfJ/btsEeKE9lgJ/sChO2nL4yx1BBiGy3uwoTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kWTfJ/btsEeKE9lgJ/sChO2nL4yx1BBiGy3uwoTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kWTfJ/btsEeKE9lgJ/sChO2nL4yx1BBiGy3uwoTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkWTfJ%2FbtsEeKE9lgJ%2FsChO2nL4yx1BBiGy3uwoTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;105&quot; data-filename=&quot;chrome_YO6UKZPCap.png&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;면접 &amp;amp; 이력서 단계에서 중요시 했던 것&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이력서는 최대한 깔끔하게 양식은 가장 범용적인 사람인, 좀 예쁘게 나오는 랠릿 이력서를 주로 활용했으며 포트폴리오 등 전체 이력서는 노션에다 관리 및 공개&lt;/li&gt;
&lt;li&gt;각 프로젝트는 내가 했던 역활을 간단하게 정리하면서 사진 및 시각적으로 무슨 프로젝트인지 알 수 있게 Gif등을 배치&lt;/li&gt;
&lt;li&gt;면접 공부는 계속 반복했으며 키워드에서 이야기를 풀어나가는 연습을 함. 발음 특히 천천히 말하는 노력을 많이 함&lt;/li&gt;
&lt;li&gt;나이, 비전공, 신입을 해결할 수 있는 각각의 이야기를 준비함 (나이가 xxx한 대신 경험이 많다, 비전공이지만 흥미나 공부는 진행중이였다 등등..)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;회사 선택 시 중요시 했던 점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 적기 전에 붙었던 3개의 회사 정보다&lt;br /&gt;중소 SI (매출 100억 개발자 130명정도)&lt;br /&gt;React 70% Javascript 및 Jquery(레거시 유지보수) 20% 기타 10% &lt;br /&gt;3천 초반&lt;br /&gt;&lt;br /&gt;소형 스타트업 (매출 7억 개발자 4명 비개발 합쳐서 23명)&lt;br /&gt;Next.js&amp;nbsp;+&amp;nbsp;Recoil &lt;br /&gt;3천 초반 &lt;br /&gt;&lt;br /&gt;중형 퍼블리셔 (매출 50억 개발자 23명 비개발 합쳐서 55명) &lt;br /&gt;풀스택 (이라 쓰고 외주받은걸 사람단위로 치는 분위기&lt;br /&gt;3천 중반&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연봉은 앞자리수가 3이라면 개의치 않기로 마음먹었기에 가장 중요한 것은 개발 분위기나 기술 그리고 문화였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;월급 등이 문제없이 지급되는 흑자기업 (매출이 꾸준함)&lt;/li&gt;
&lt;li&gt;어느정도 개발자 틀이 잡혀있고 인원수가 많아 내가 원하던 프론트나 웹에 집중가능성&lt;/li&gt;
&lt;li&gt;기술 스택이 너무 오래되지 않고 최신 스택을 활용 가능 한가&lt;/li&gt;
&lt;li&gt;가능하면 복지는 많이, 워라밸도 많을수록 다다익선&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 기술 스택만 보고&amp;nbsp;&lt;b&gt;2번&lt;/b&gt;의 스타트업을 가려고 했다. 하지만 개발자가 많고 흑자인 기업을 고려하게 되면서&amp;nbsp;&lt;b&gt;1번&lt;/b&gt;도 고민하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 1번의 경우 미리 입사한 친구에게 듣기로 리액트등 상세한 기술스택과 복지, 개발문화를 알 수 있었고 SI라 걱정이 됬지만 입사를 결정하게 됬다 (매우 만족중)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2023년 하반기&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;신입으로 시작하는 프론트엔드 생활&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫 입사 후 신입으로 보딩기간이 있었고 기간은 역량에 따라 다르다고 들었다.&lt;br /&gt;&lt;br /&gt;아예 비동기부터 모르고 입사한 분도 있어 길게는 3달 이상 사전교육을 하는경우도 있다고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다행이 나는 나름 공부기간이 길었고 프론트에 올인한 덕분에 대부분 개인 프로젝트 등으로 소화해놔서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따로 추가 교육 없이 1~2주 정도 SVN등 조금 생소한 툴과 커밋룰등을 익히고 바로 프로젝트에 투입됬다&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_20240201_105706_U+.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBBLV2/btsECjMGmDA/W0VyuLcSO5XmjE0Pr4Akf1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBBLV2/btsECjMGmDA/W0VyuLcSO5XmjE0Pr4Akf1/img.jpg&quot; data-alt=&quot;이제는 앱이 공개되어 보여줄 수 있는 첫 완성 프로젝트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBBLV2/btsECjMGmDA/W0VyuLcSO5XmjE0Pr4Akf1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBBLV2%2FbtsECjMGmDA%2FW0VyuLcSO5XmjE0Pr4Akf1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2400&quot; data-filename=&quot;Screenshot_20240201_105706_U+.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이제는 앱이 공개되어 보여줄 수 있는 첫 완성 프로젝트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만들면서 고민하고 생각한 것들을 정리하면 아마 포스트 5개이상 나올 듯 한데&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시간이 날 때마다 정리해서 써야겠다  &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래도 실 다운로드가 천만 이상 이나 되는 앱을 오자마자 만들게 되서 너무 좋은 경험이였구&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중간부터 꽤 많은 부분을 맡겨주셔서 너무 만족스러웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20240208_125002720.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doK3wc/btsEB0GAtco/BVgwKpP72Zku2SP7cJBLq1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doK3wc/btsEB0GAtco/BVgwKpP72Zku2SP7cJBLq1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doK3wc/btsEB0GAtco/BVgwKpP72Zku2SP7cJBLq1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoK3wc%2FbtsEB0GAtco%2FBVgwKpP72Zku2SP7cJBLq1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;1404&quot; data-filename=&quot;KakaoTalk_20240208_125002720.jpg&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 이것저것 고민이 많지만 올 한해에는 더욱 아름다운 코드를 짤 수 있게 노력하고 싶다  &lt;/p&gt;</description>
      <category>기타/근황</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/107</guid>
      <comments>https://zerosial.tistory.com/entry/2023%EB%85%84-%EA%B7%BC%ED%99%A9-%EB%B0%8F-2024%EB%85%84-%EB%AA%A9%ED%91%9C-%F0%9F%A4%94#entry107comment</comments>
      <pubDate>Thu, 8 Feb 2024 12:51:57 +0900</pubDate>
    </item>
    <item>
      <title>React W Hooks 톺아보기</title>
      <link>https://zerosial.tistory.com/entry/React-W-Hooks-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h1&gt;시작하기에 앞서&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트를 통해 커스텀 훅의 사용 예시와 더불어 실제 프로젝트의 적용 코드 및 사용 주의사항 등을 공유할 예정입니다.&lt;/p&gt;
&lt;h1&gt;먼저 Hooks 란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리액트(React)의 훅(Hooks)&lt;/b&gt;은 함수형 컴포넌트에서 &lt;b&gt;상태 관리&lt;/b&gt;, &lt;b&gt;라이프 사이클&lt;/b&gt; &lt;b&gt;이벤트 처리&lt;/b&gt;, 그리고 React의 다른 &lt;b&gt;기능들을 사용&lt;/b&gt;할 수 있게 해주는 기능들입니다. 훅을 사용하면 클래스 기반 컴포넌트에서만 가능했던 여러 기능들을 함수형 컴포넌트 에서도 쉽게 사용할 수 있습니다. 우리가 자주 쓰는 &lt;code&gt;useEffect&lt;/code&gt; , &lt;code&gt;useState&lt;/code&gt;를 포함해 정의된 Hooks를 통해 React의 생명주기를 다룰 수 있습니다.&lt;/p&gt;
&lt;h1&gt;Hooks의 종류&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/hooks&quot;&gt;Built-in React Hooks &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Built-in React Hooks &amp;ndash; React&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react.dev](&lt;a href=&quot;https://react.dev/reference/react/hooks&quot;&gt;https://react.dev/reference/react/hooks&lt;/a&gt;)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상태 훅(State Hooks)&lt;/b&gt;: 컴포넌트가 사용자 입력과 같은 정보를 &quot;기억&quot;하게 해줍니다. 예를 들어, &lt;b&gt;&lt;code&gt;useState&lt;/code&gt;&lt;/b&gt;와 &lt;b&gt;&lt;code&gt;useReducer&lt;/code&gt;&lt;/b&gt;가 이에 해당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 훅(Context Hooks)&lt;/b&gt;: 컴포넌트가 멀리 떨어진 부모로부터 정보를 props 없이 받을 수 있게 해줍니다. &lt;b&gt;&lt;code&gt;useContext&lt;/code&gt;&lt;/b&gt;가 이에 해당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;참조 훅(Ref Hooks)&lt;/b&gt;: 컴포넌트가 렌더링에 사용되지 않는 정보(예: DOM 노드, 타임아웃 ID)를 유지할 수 있도록 합니다. &lt;b&gt;&lt;code&gt;useRef&lt;/code&gt;&lt;/b&gt;와 &lt;b&gt;&lt;code&gt;useImperativeHandle&lt;/code&gt;&lt;/b&gt;이 여기에 속합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과 훅(Effect Hooks)&lt;/b&gt;: 컴포넌트가 외부 시스템과 연결되고 동기화할 수 있도록 합니다. &lt;b&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useLayoutEffect&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useInsertionEffect&lt;/code&gt;&lt;/b&gt;가 이에 해당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 훅(Performance Hooks)&lt;/b&gt;: 재렌더링 성능을 최적화하기 위해 사용됩니다. &lt;b&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/b&gt;와 &lt;b&gt;&lt;code&gt;useCallback&lt;/code&gt;&lt;/b&gt;이 여기에 해당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 훅(Resource Hooks)&lt;/b&gt;: 컴포넌트가 상태의 일부로 갖지 않고도 리소스에 접근할 수 있게 해줍니다. &lt;b&gt;&lt;code&gt;use&lt;/code&gt;&lt;/b&gt;가 이에 해당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기타 훅(Other Hooks)&lt;/b&gt;: 주로 라이브러리 작성자에게 유용하며, 일반적인 애플리케이션 코드에서는 자주 사용되지 않습니다. &lt;b&gt;&lt;code&gt;useDebugValue&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useId&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useSyncExternalStore&lt;/code&gt;&lt;/b&gt;가 여기에 속합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 정의 훅(Your own Hooks)&lt;/b&gt;: 사용자가 자신의 JavaScript 함수로 커스텀 훅을 정의할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8가지의 Hooks로 나눌 수 있으며 이 중 우리가 알아볼 것은 사용자 정의 훅 즉 Custom Hook이다.&lt;/p&gt;
&lt;h1&gt;Custom Hook이란?&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Custom Hooks: Sharing logic between components&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅: 컴포넌트간의 로직 공유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅의 경우 여러 곳에서 반복적으로 사용되는 로직을 분리하여 재사용성을 높이고 가독성을 향상시킬 수 있습니다.&lt;/p&gt;
&lt;h1&gt;예시 1) 간단한 적용 예&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 통해 간단한 사례로 커스텀 훅을 사용하는 예시에 대해 설명하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;function InputComponent({ inputRef }) {
  return &amp;lt;input ref={inputRef} /&amp;gt;;
}

function ButtonComponent({ onClick }) {
  return &amp;lt;button onClick={onClick}&amp;gt;Focus on Input&amp;lt;/button&amp;gt;;
}

function ParentComponent() {
  const inputRef = useRef();

  const focusInput = () =&amp;gt; {
    inputRef.current.focus();
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;InputComponent inputRef={inputRef} /&amp;gt;
      &amp;lt;ButtonComponent onClick={focusInput} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 인풋과 버튼 그리고 그 두 가지를 합친 컴포넌트가 있다고 가정하겠습니다. 단순하게 버튼을 클릭 시 input에 포커스를 주는 기능을 하고 있습니다. 이와 같은 기능을 커스텀 훅을 통해 분리한다면 다음과 같아집니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;// 분리된 커스텀 훅
function useInputFocus() {
  const inputRef = useRef(null);

  const focusInput = () =&amp;gt; {
    inputRef.current &amp;amp;&amp;amp; inputRef.current.focus();
  };

  return [inputRef, focusInput];
}

//실제 사용 예시
function InputComponent({ inputRef }) {
  return &amp;lt;input ref={inputRef} /&amp;gt;;
}

function ButtonComponent({ onClick }) {
  return &amp;lt;button onClick={onClick}&amp;gt;Focus on Input&amp;lt;/button&amp;gt;;
}

function ParentComponent() {
  const [inputRef, focusInput] = useInputFocus();

  return (
    &amp;lt;&amp;gt;
      &amp;lt;InputComponent inputRef={inputRef} /&amp;gt;
      &amp;lt;ButtonComponent onClick={focusInput} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 useRef, useEffect 등 여러 훅을 사용하는 로직을 분리하는데 사용합니다. 앞으로 useInputFocus를 통해 간편하게 인풋과 포커스를 한줄에 처리할 수 있게 되었습니다.&lt;/p&gt;
&lt;h1&gt;예시 2) 실제 적용한 useStickyTab 함수&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 훅으로 분리되지 않은 함수의 예시를 보여드리겠습니다. 다음과 같이 스크롤 시 특정 높이에 붙을 경우 자동으로 탭의 형식을 변경해주는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(프로젝트의 특성상 CSS의 제작을 퍼블리셔에게 위임했는데 overflow등의 속성값의 남발로 인해 CSS의 Sticky의 옵션이 적용되지 않아 직접 작성해야 되는 상황 이였습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chrome_jbf2lhakcB.gif&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4NN5e/btsCoxUAGa6/YyDQiWcvNfYDTcBkGvfPnK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4NN5e/btsCoxUAGa6/YyDQiWcvNfYDTcBkGvfPnK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4NN5e/btsCoxUAGa6/YyDQiWcvNfYDTcBkGvfPnK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/4NN5e/btsCoxUAGa6/YyDQiWcvNfYDTcBkGvfPnK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;325&quot; height=&quot;485&quot; data-filename=&quot;chrome_jbf2lhakcB.gif&quot; data-origin-width=&quot;325&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 작성되었던 코드
const LifestyleTabSwiper = () =&amp;gt; {

// 하단부터 탭이 LIFESTYLE_SCROLL_HOLD 와 겹치는 부분을 IntersectionObserver로 감지하여 클래스를 변경함
  const LIFESTYLE_SCROLL_HOLD = 50;
  const [isTabSticky, setIsTabSticky] = useState(false);
  const tabRef = useRef(null);

  useEffect(() =&amp;gt; {
    if (!tabRef?.current) return;
    const tabElement = tabRef.current;

    const observerCallback = (entries) =&amp;gt; {
      entries.forEach((entry) =&amp;gt; {
        const shouldStick = !entry.isIntersecting;

        if (shouldStick !== isTabSticky) {
          tabElement.classList.toggle(&quot;is--scroll&quot;, shouldStick);
          setIsTabSticky(shouldStick);
        }
      });
    };

    const observerOptions = {
      root: null,
      rootMargin: `-${LIFESTYLE_SCROLL_HOLD}px 0px 0px 0px`,
      threshold: 1,
    };

    const observer = new IntersectionObserver(
      observerCallback,
      observerOptions
    );
    observer.observe(tabElement);

    return () =&amp;gt; observer.unobserve(tabElement);
  }, [isTabSticky, tabRef]);

  return (
    &amp;lt;article className=&quot;life--styling--tabs&quot;&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;div className=&quot;tab--wrapper type--2&quot;&amp;gt;
          &amp;lt;div className=&quot;tab--header--wrapper&quot; ref={tabRef}&amp;gt;
            &amp;lt;LifestyleTabList
              data={data}
              currentTab={currentTab}
              goToTab={goToTab}
            /&amp;gt;
          &amp;lt;/div&amp;gt;
          ... 이하 줄임&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성하고 정상 기능하지만 혹여나 다른 탭에도 같은 기능이 필요하면 긴 코드를 다시 써야하고 너무 길어져 흐름을 읽기 어려워졌습니다. 이제 이 sticky 기능을 훅으로 분리해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 분리된 커스텀 훅
export function useStickyTab(LIFESTYLE_SCROLL_HOLD) {
  const [isTabSticky, setIsTabSticky] = useState(false);
  const tabRef = useRef(null);

  useEffect(() =&amp;gt; {
    if (!tabRef?.current) return;
    const tabElement = tabRef.current;

    const observerCallback = (entries) =&amp;gt; {
      entries.forEach((entry) =&amp;gt; {
        const shouldStick = !entry.isIntersecting;

        if (shouldStick !== isTabSticky) {
          tabElement.classList.toggle(&quot;is--scroll&quot;, shouldStick);
          setIsTabSticky(shouldStick);
        }
      });
    };

    const observerOptions = {
      root: null,
      rootMargin: `-${LIFESTYLE_SCROLL_HOLD}px 0px 0px 0px`,
      threshold: 1,
    };

    const observer = new IntersectionObserver(
      observerCallback,
      observerOptions
    );
    observer.observe(tabElement);

    return () =&amp;gt; observer.unobserve(tabElement);
  }, [isTabSticky, tabRef]);

  return tabRef;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;// 기존 코드에서 사용하기
const LifestyleTabSwiper = () =&amp;gt; {
  const tabRef = useStickyTab(50);

  return (
    &amp;lt;article className=&quot;life--styling--tabs&quot;&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;div className=&quot;tab--wrapper type--2&quot;&amp;gt;
          &amp;lt;div className=&quot;tab--header--wrapper&quot; ref={tabRef}&amp;gt;
            &amp;lt;LifestyleTabList
              data={data}
              currentTab={currentTab}
              goToTab={goToTab}
            /&amp;gt;
          &amp;lt;/div&amp;gt;
          ...이하 줄임&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어느 함수에서도 고정을 원하는 tabRef가 생기면&lt;br /&gt;&lt;code&gt;const tabRef = useStickyTab(높이);&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 통해 편리하게 사용할 수 있게 되었습니다.&lt;/p&gt;
&lt;h1&gt;사용 시 주의해야 할 점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 훅의 사용시 주의해야 될 점 및 커스텀 훅을 사용시 주의사항을 정리해 보았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;함수 컴포넌트의 최상위에서만 훅 사용&lt;/b&gt;: 훅은 함수 컴포넌트의 최상위 레벨에서만 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내부에서 훅을 호출하면 안 됩니다. 이 규칙은 훅의 호출 순서가 보장되도록 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리액트 함수 컴포넌트 내에서만 훅 사용&lt;/b&gt;: 훅은 리액트 함수 컴포넌트나 커스텀 훅 내에서만 호출해야 합니다. 일반 JavaScript 함수에서는 훅을 사용할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의존성 배열 관리에 주의&lt;/b&gt;: &lt;b&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useMemo&lt;/code&gt;&lt;/b&gt;, &lt;b&gt;&lt;code&gt;useCallback&lt;/code&gt;&lt;/b&gt; 등의 훅에서는 의존성 배열(dependency array)을 정확하게 관리해야 합니다. 의존성 배열에 포함된 값이 변경될 때만 훅이 실행됩니다. 배열을 잘못 관리하면 메모리 누수나 불필요한 렌더링이 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;훅의 규칙 준수&lt;/b&gt;: 리액트 팀은 훅을 올바르게 사용하기 위해 두 가지 주요 규칙을 제시했습니다: &quot;훅은 항상 최상위에서만 호출해야 한다&quot;와 &quot;훅은 리액트 함수 컴포넌트나 커스텀 훅 내에서만 호출해야 한다&quot;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 업데이트에 주의&lt;/b&gt;: 상태 업데이트 함수는 비동기적으로 작동합니다. 따라서, 최신 상태 값을 필요로 하는 경우에는 주의가 필요합니다. 이럴 때는 함수형 업데이트를 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 누수 방지&lt;/b&gt;: 사이드 이펙트를 관리하는 &lt;b&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/b&gt; 훅에서는 컴포넌트가 언마운트될 때 정리(clean-up) 작업을 해야 합니다. 예를 들어, 구독(subscriptions), 타이머, 진행 중인 요청 등을 정리해야 메모리 누수를 방지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 정의 훅의 네이밍&lt;/b&gt;: 커스텀 훅을 만들 때는 &lt;b&gt;&lt;code&gt;use&lt;/code&gt;&lt;/b&gt;로 시작하는 이름을 사용하는 것이 좋습니다. 이는 훅의 사용법을 명확하게 하고, 리액트의 린트 규칙과도 일치합니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/106</guid>
      <comments>https://zerosial.tistory.com/entry/React-W-Hooks-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0#entry106comment</comments>
      <pubDate>Sun, 24 Dec 2023 14:29:20 +0900</pubDate>
    </item>
    <item>
      <title>정규 표현식 에서의 특수 문자 이스케이프 (feat: 및줄)</title>
      <link>https://zerosial.tistory.com/entry/%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%97%90%EC%84%9C%EC%9D%98-%ED%8A%B9%EC%88%98-%EB%AC%B8%EC%9E%90-%EC%9D%B4%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84-feat-%EB%B0%8F%EC%A4%84</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식은 문자열 검색 및 치환 등의 작업에 사용되는 강력한 도구입니다. 그러나 정규 표현식 내에서는 특정 특수 문자들이 특별한 의미를 가지고 있습니다. 예를 들어:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; : 어떤 문자와도 일치합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; : 앞의 문자나 그룹이 0번 이상 반복됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; : 앞의 문자나 그룹이 1번 이상 반복됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로, 이러한 특수 문자들을 문자 그대로 검색하려면 이스케이프 처리가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pysx1/btszkaCfsvP/yGJUFLbaw3xBGq2LGsupj0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pysx1/btszkaCfsvP/yGJUFLbaw3xBGq2LGsupj0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pysx1/btszkaCfsvP/yGJUFLbaw3xBGq2LGsupj0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpysx1%2FbtszkaCfsvP%2FyGJUFLbaw3xBGq2LGsupj0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;1.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;escapeRegExp 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이스케이프 처리는 특수 문자 앞에 역슬래시(&lt;code&gt;\&lt;/code&gt;)를 붙여주는 것을 말합니다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;/**
 * 정규 표현식에서 사용되는 특수 문자를 이스케이프합니다.
 * @param {string} string - 이스케이프 할 문자열.
 * @returns {string} 이스케이프된 문자열을 반환합니다.
 */
const escapeRegExp = (string) =&amp;gt; string.replace(/[.*+?^${}()|[\]\\]/g, &quot;\\$&amp;amp;&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;escapeRegExp&lt;/code&gt;&lt;/b&gt; 함수는 문자열 내의 특수 문자들을 자동으로 이스케이프 처리하여 반환해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네, 해당 &lt;b&gt;&lt;code&gt;escapeRegExp&lt;/code&gt;&lt;/b&gt; 함수는 정규 표현식에서 사용되는 특수 문자들을 이스케이프하는 역할을 합니다. 함수의 로직과 함께 해당 정규 표현식의 역할을 자세히 설명드리겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수의 로직:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&lt;b&gt;escapeRegExp&lt;/b&gt;&lt;/code&gt; 함수는 입력된 문자열 &lt;b&gt;&lt;code&gt;string&lt;/code&gt;&lt;/b&gt;에 포함된 정규 표현식에서 사용되는 특수 문자들을 이스케이프 (즉, &lt;code&gt;\&lt;/code&gt; 문자를 앞에 붙여서 문자 그대로 해석되게 하는 것) 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이스케이프 과정:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;replace&lt;/code&gt; 메서드는 위의 정규 표현식을 사용하여 &lt;code&gt;string&lt;/code&gt;에서 정규 표현식의 특수 문자들을 찾아 이스케이프 처리합니다. 그리고 이스케이프 처리된 문자열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;taggerscript&quot;&gt;&lt;code&gt;const testString = &quot;a.b*c+?^$|[]{}()&quot;;
console.log(escapeRegExp(testString)); // &quot;a\.b\*c\+\?\^\$\|\[\]\{\}\(\)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로, &lt;code&gt;escapeRegExp&lt;/code&gt; 함수는 정규 표현식에서 사용되는 특수 문자들을 문자 그대로 해석될 수 있도록 이스케이프 처리해 줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문자열에서의 특정 텍스트 언더라인 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이스케이프 처리된 문자열을 사용하여, 주어진 문자열에서 특정 텍스트를 언더라인 처리하는 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const underlineText = ({ inputString, targetText }) =&amp;gt; {
  if (!inputString || !targetText) return &quot;&quot;;
  const regex = new RegExp(escapeRegExp(targetText), &quot;g&quot;);
  return inputString.replace(regex, `&amp;lt;u&amp;gt;${targetText}&amp;lt;/u&amp;gt;`);
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제와 결과값&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 설명한 함수를 사용하여 실제 예제를 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. escapeRegExp를 사용할 때:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식의 특수 문자들을 그대로 문자로 인식하게 됩니다. 즉, &lt;code&gt;.&lt;/code&gt;이나 &lt;code&gt;*&lt;/code&gt;와 같은 문자를 그대로 &lt;code&gt;.&lt;/code&gt; 혹은 &lt;code&gt;*&lt;/code&gt;로 인식하게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. escapeRegExp를 사용하지 않을 때:&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식의 특수 문자들이 특별한 의미로 동작합니다. 예를 들어, &lt;code&gt;.&lt;/code&gt;는 모든 문자와 일치하게 되고, &lt;code&gt;*&lt;/code&gt;는 앞의 문자가 0번 이상 반복되는지를 확인하게 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예제:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inputString이 &lt;code&gt;&quot;a.b*c&quot;&lt;/code&gt;이고, targetText가 &lt;code&gt;&quot;.&quot;&lt;/code&gt;일 경우:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;escapeRegExp를 사용할 때&lt;/b&gt;: &lt;code&gt;&quot;.&quot;&lt;/code&gt;는 그대로 점 문자로 인식되어 &lt;code&gt;a.b*c&lt;/code&gt;에서 &lt;code&gt;.&lt;/code&gt; 문자만을 대상으로 변경합니다.&lt;br /&gt;결과: &lt;code&gt;a&lt;u&gt;&amp;lt;u&amp;gt;.&amp;lt;/u&amp;gt;&lt;/u&gt;b*c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;escapeRegExp를 사용하지 않을 때&lt;/b&gt;: &lt;code&gt;&quot;.&quot;&lt;/code&gt;는 정규 표현식에서 모든 문자와 일치하는 특수 문자로 인식되어 &lt;code&gt;a.b*c&lt;/code&gt;의 모든 문자를 대상으로 변경합니다.&lt;br /&gt;결과: &lt;u&gt;&lt;code&gt;&amp;lt;u&amp;gt;a&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;.&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;b&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;*&amp;lt;/u&amp;gt;&amp;lt;u&amp;gt;c&amp;lt;/u&amp;gt;&lt;/code&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;에러 발생 상황:&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;escapeRegExp&lt;/code&gt;를 사용하지 않고, targetText에 정규 표현식의 특수 문자가 포함되어 있을 때 예상치 못한 동작이나 에러가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;br /&gt;inputString이 &lt;code&gt;&quot;a[b*c&quot;&lt;/code&gt;이고, targetText가 &lt;code&gt;&quot;[&quot;&lt;/code&gt;일 경우:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;escapeRegExp를 사용할 때&lt;/b&gt;: &lt;code&gt;&quot;[b*c&quot;&lt;/code&gt;에서 &lt;code&gt;[&lt;/code&gt; 문자만을 대상으로 변경합니다.&lt;br /&gt;결과: &lt;code&gt;a&lt;u&gt;&amp;lt;u&amp;gt;[&amp;lt;/u&amp;gt;&lt;/u&gt;b*c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;escapeRegExp를 사용하지 않을 때&lt;/b&gt;: &lt;code&gt;&quot;[&lt;/code&gt;는 문자 집합의 시작을 나타내는 특수 문자로 인식되어 에러가 발생합니다. (&lt;b&gt;&lt;code&gt;Uncaught SyntaxError: Invalid regular expression: /[/: Unterminated character class&lt;/code&gt;&lt;/b&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, &lt;code&gt;escapeRegExp&lt;/code&gt;는 정규 표현식의 특수 문자를 그대로 문자로 인식하도록 도와주며, 예상치 못한 동작이나 에러를 방지해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YGkV9/btszutNi1tj/d5eFpCwdVa2QDQr7Nnloh1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YGkV9/btszutNi1tj/d5eFpCwdVa2QDQr7Nnloh1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YGkV9/btszutNi1tj/d5eFpCwdVa2QDQr7Nnloh1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYGkV9%2FbtszutNi1tj%2Fd5eFpCwdVa2QDQr7Nnloh1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;2.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규 표현식을 사용할 때 특수 문자를 문자 그대로 검색하고 싶다면, 반드시 이스케이프 처리가 필요합니다. 이를 위해 &lt;code&gt;escapeRegExp&lt;/code&gt; 함수와 같은 도우미 함수를 사용하면 편리하게 작업을 수행할 수 있습니다.&lt;/p&gt;</description>
      <category>IT 학습/Javascript</category>
      <category>Escape함수</category>
      <category>검색</category>
      <category>정규표현식</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/105</guid>
      <comments>https://zerosial.tistory.com/entry/%EC%A0%95%EA%B7%9C-%ED%91%9C%ED%98%84%EC%8B%9D-%EC%97%90%EC%84%9C%EC%9D%98-%ED%8A%B9%EC%88%98-%EB%AC%B8%EC%9E%90-%EC%9D%B4%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84-feat-%EB%B0%8F%EC%A4%84#entry105comment</comments>
      <pubDate>Mon, 30 Oct 2023 11:38:57 +0900</pubDate>
    </item>
    <item>
      <title>React에서 스크롤 기반 헤더 최적화 하기</title>
      <link>https://zerosial.tistory.com/entry/React%EC%97%90%EC%84%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B8%B0%EB%B0%98-%ED%97%A4%EB%8D%94-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%98%EA%B8%B0</link>
      <description>&lt;h1&gt;React에서 스크롤 기반 헤더 최적화하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 기반의 동작은 웹 애플리케이션에서 흔히 볼 수 있는 패턴입니다. 이런 패턴은 사용자 경험(UX)을 크게 향상시켜주지만, 잘못 구현될 경우 성능에 문제를 가져올 수 있습니다. 이 글에서는 React Hook을 사용하여 스크롤에 따라 헤더 내용을 바꾸는 작업의 최적화 과정을 함께 살펴보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;초기 구현: 이전 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 개선 전에 사용하던 코드는 아래와 같습니다:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useEffect } from &quot;react&quot;;
import { Store } from &quot;Helper&quot;;
import { CommonHeaderTypePageActions } from &quot;Action&quot;;

const useProminentHeader = (ref, title) =&amp;gt; {
  useEffect(() =&amp;gt; {
    if (!ref.current) return;

    const handleStickyTitle = () =&amp;gt; {
      const headerEl = document.querySelector(&quot;.header--wrapper&quot;);
      const headerHeight = headerEl.getBoundingClientRect().height;
      const rect = ref.current.getBoundingClientRect();
      if (headerHeight &amp;gt; rect.bottom) {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: title,
          })
        );
      } else {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: &quot;&quot;,
          })
        );
      }
    };

    window.addEventListener(&quot;scroll&quot;, handleStickyTitle);

    return () =&amp;gt; {
      window.removeEventListener(&quot;scroll&quot;, handleStickyTitle); 
    };
  }, [ref, title]);
};

export { useProminentHeader };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 초기 구현은 스크롤마다 DOM의 위치를 계산하여 헤더 제목을 업데이트하는 방식입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 방안 고려&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Throttling vs Intersection Observer&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 이벤트는 사용자의 손가락 움직임에 따라 굉장히 빈번하게 발생합니다. 이런 상황에서 스크롤 이벤트의 빈번한 처리는 성능 저하의 주범이 될 수 있습니다. 이를 해결하는 방법으로 Throttling을 고려해 볼 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Throttling:&lt;/b&gt; 주어진 시간동안 이벤트 처리를 한 번만 하도록 제한하는 기술입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 우리는 스크롤 이벤트 대신 &lt;code&gt;Intersection Observer&lt;/code&gt;를 선택했습니다. 가장 큰 이유로는 Throttleing의 경우 정확한 시점이 아닌 지연된 값을 판단하게 되고 &lt;code&gt;Intersection Observer&lt;/code&gt; 를 사용함으로 인해 이벤트 처리 횟수가 크게 줄어들었고, Throttling의 필요성이 사라졌습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Intersection Observer의 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Intersection Observer&lt;/code&gt;는 웹 페이지의 특정 요소와 viewport 사이의 교차 상태를 비동기적으로 관찰할 수 있게 해주는 API입니다. 이를 사용하면, 효율적으로 요소의 가시성을 체크할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Memoization의 고려&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Memoization은 연산 결과를 저장하여 동일한 연산의 반복 실행을 줄이는 최적화 기법입니다. 이번 예제에서는 성능 향상에 크게 도움이 되지 않았지만성능 최적화에서 종종 유용하게 쓰입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 최적화: 최종 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;code&gt;Intersection Observer&lt;/code&gt;를 적용하여 개선된 코드입니다:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useEffect, useCallback } from &quot;react&quot;;
import { Store } from &quot;Helper&quot;;
import { CommonHeaderTypePageActions } from &quot;Action&quot;;

const useProminentHeader = (ref, title) =&amp;gt; {
  const observerCallback = useCallback(
    (entries) =&amp;gt; {
      entries.forEach((entry) =&amp;gt; {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: entry.isIntersecting ? &quot;&quot; : title,
          })
        );
      });
    },
    [title]
  );

  useEffect(() =&amp;gt; {
    const currentRef = ref.current;
    if (!currentRef) {
      return;
    }

    const observer = new IntersectionObserver(observerCallback, {
      threshold: 1,
      rootMargin: &quot;-24px 0px 0px 0px&quot;,
    });

    observer.observe(currentRef);

    return () =&amp;gt; {
      observer.unobserve(currentRef);
    };
  }, [ref, observerCallback]);

  return null;
};

export { useProminentHeader };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 코드는 &lt;code&gt;Intersection Observer&lt;/code&gt;를 통해 헤더의 가시성을 효율적으로 판단하고, 필요할 때만 헤더의 제목을 업데이트합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 평가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 코드를 실제 환경에서 비교하기 위해 테스트를 진행했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시스템 환경:&lt;/b&gt; 2 CPU cores, 4GB RAM, Ubuntu 20.04&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브라우저:&lt;/b&gt; Chrome 92&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 페이지:&lt;/b&gt; 2,000개의 아이템 목록&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 결과&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기존 코드:&lt;/b&gt; 평균 반응 시간 120ms&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최적화 코드:&lt;/b&gt; 평균 반응 시간 45ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Intersection Observer&lt;/code&gt;를 도입한 코드는 기존 코드에 비해 약 2.6배 더 빠른 반응 속도를 보였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 기반의 헤더 동작 최적화를 위해 &lt;code&gt;Intersection Observer&lt;/code&gt;를 도입하였고, 그 결과 큰 성능 향상을 얻을 수 있었습니다. 이러한 최적화는 사용자의 경험을 향상시키는 중요한 요소입니다. 따라서, 개발 초기부터 성능 최적화를 고려하는 것이 매우 중요하다는 것을 재확인 하였습니다.&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <category>Intersection Observer</category>
      <category>React</category>
      <category>test</category>
      <category>Throttling</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/104</guid>
      <comments>https://zerosial.tistory.com/entry/React%EC%97%90%EC%84%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B8%B0%EB%B0%98-%ED%97%A4%EB%8D%94-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%98%EA%B8%B0#entry104comment</comments>
      <pubDate>Tue, 24 Oct 2023 14:03:26 +0900</pubDate>
    </item>
    <item>
      <title>Redux란? (리덕스)</title>
      <link>https://zerosial.tistory.com/entry/Redux%EB%9E%80-%EB%A6%AC%EB%8D%95%EC%8A%A4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. SPA에서 Redux의 개념&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 페이지 응용 프로그램(Single Page Application, SPA)&lt;/b&gt;에서 Redux는 상태 관리 라이브러리로써 사용됩니다. SPA는 전통적인 웹사이트와 달리 페이지 이동 없이 동적으로 내용을 업데이트하며, 이로 인해 상태 관리가 복잡해질 수 있습니다. Redux는 중앙 집중화된 상태 저장소를 통해 SPA의 상태를 예측 가능하게 관리하고 변경을 추적하는데 도움을 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. React에서 Redux를 사용하는 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 Redux를 사용하기 위해서는 몇 가지 단계를 거쳐야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Redux 설치하기&lt;/b&gt;:&lt;/li&gt;
&lt;li&gt;npm install redux react-redu&lt;/li&gt;
&lt;li&gt;&lt;b&gt;액션(Action) 정의하기&lt;/b&gt;: 액션은 상태 변경을 설명하는 객체로, 액션 타입과 필요한 데이터를 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리듀서(Reducer) 정의하기&lt;/b&gt;: 리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스토어(Store) 생성하기&lt;/b&gt;: 리듀서를 결합하고 초기 상태를 가지는 스토어를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리액트 앱과 연결하기&lt;/b&gt;: &lt;b&gt;react-redux&lt;/b&gt; 라이브러리를 사용하여 리액트 앱과 Redux 스토어를 연결합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 예시 코드
// actions.js
export const ADD_TODO = 'ADD_TODO';
export const addTodo = (text) =&amp;gt; ({
  type: ADD_TODO,
  text
});

// reducers.js
import { ADD_TODO } from './actions';

const initialState = [];

const todoReducer = (state = initialState, action) =&amp;gt; {
  switch (action.type) {
    case ADD_TODO:
      return [...state, { text: action.text, completed: false }];
    default:
      return state;
  }
};

// store.js
import { createStore } from 'redux';
import todoReducer from './reducers';

const store = createStore(todoReducer);

export default store;

// App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import TodoList from './TodoList';

function App() {
  return (
    &amp;lt;Provider store={store}&amp;gt;
      &amp;lt;TodoList /&amp;gt;
    &amp;lt;/Provider&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. redux-logger에 대하여&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;redux-logger&lt;/b&gt;는 Redux의 동작을 개발자에게 보여주는 미들웨어입니다. 액션과 이에 따른 상태 변화를 콘솔에 자세하게 기록해줍니다. 개발 중에 상태 변화를 추적하거나 버그를 찾는데 도움을 주며, 개발자 경험을 향상시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;redux-logger 설치 및 사용&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;npm install redux-logger
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// store.js
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import todoReducer from './reducers';

const store = createStore(todoReducer, applyMiddleware(logger));

export default store;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/103</guid>
      <comments>https://zerosial.tistory.com/entry/Redux%EB%9E%80-%EB%A6%AC%EB%8D%95%EC%8A%A4#entry103comment</comments>
      <pubDate>Wed, 30 Aug 2023 15:16:43 +0900</pubDate>
    </item>
    <item>
      <title>JEST란?</title>
      <link>https://zerosial.tistory.com/entry/JEST%EB%9E%80</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JEST&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest는 JavaScript 애플리케이션을 위한 테스트 프레임워크입니다. 주로 React 프로젝트에서 컴포넌트 및 함수의 유닛 테스트와 통합 테스트를 작성하는 데 사용됩니다. Jest는 간편한 구문과 기능을 제공하여 테스트 작성을 쉽고 효율적으로 할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jest의 특징:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간결한 문법과 쉬운 설정으로 테스트 작성을 용이하게 함.&lt;/li&gt;
&lt;li&gt;테스트 실행을 위한 별도의 설정 없이 기본적인 설정 제공.&lt;/li&gt;
&lt;li&gt;테스트 코드에서 Mocking 및 Spy 기능을 내장하여 외부 의존성 테스트 가능.&lt;/li&gt;
&lt;li&gt;병렬 실행과 자체적인 결과 리포팅 기능 제공.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jest 사용법 예시:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;설치&lt;/b&gt;: 프로젝트 디렉토리에서 Jest를 설치합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-bash&quot;&gt; bashCopy code
 npm install --save-dev jest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 작성&lt;/b&gt;: &lt;b&gt;&lt;code&gt;sum.js&lt;/code&gt;&lt;/b&gt;라는 간단한 함수를 테스트하는 예시입니다.
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt; javascriptCopy code
 // sum.test.js
 const sum = require('./sum');

 test('adds 1 + 2 to equal 3', () =&amp;gt; {
   expect(sum(1, 2)).toBe(3);
 });&lt;/code&gt;&lt;/pre&gt;
이 예시에서는 &lt;b&gt;&lt;code&gt;test&lt;/code&gt;&lt;/b&gt; 함수로 테스트 케이스를 작성하고, &lt;b&gt;&lt;code&gt;expect&lt;/code&gt;&lt;/b&gt; 함수로 예상 결과를 검증합니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-jsx&quot;&gt; javascriptCopy code
 // sum.js
 function sum(a, b) {
   return a + b;
 }
 module.exports = sum;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 실행&lt;/b&gt;: 터미널에서 다음 명령으로 테스트를 실행할 수 있습니다.Jest는 프로젝트 내의 &lt;b&gt;&lt;code&gt;*.test.js&lt;/code&gt;&lt;/b&gt; 또는 &lt;b&gt;&lt;code&gt;*.spec.js&lt;/code&gt;&lt;/b&gt; 파일을 찾아서 실행하고 결과를 보여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-bash&quot;&gt; bashCopy code
 npx jest&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jest는 이 외에도 다양한 기능과 메서드를 제공하여 더 복잡한 테스트 시나리오를 다룰 수 있습니다. Snapshot 테스팅, 비동기 테스트, Mocking, 테스트 그룹화 등 다양한 기능을 활용하여 애플리케이션의 품질을 보장하는 데 사용할 수 있습니다. Jest의 공식 문서와 예제를 참고하여 더 자세한 정보를 얻을 수 있습니다.&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/102</guid>
      <comments>https://zerosial.tistory.com/entry/JEST%EB%9E%80#entry102comment</comments>
      <pubDate>Tue, 29 Aug 2023 17:31:01 +0900</pubDate>
    </item>
    <item>
      <title>React에서 자주 사용되는 통신(비동기) 라이브러리</title>
      <link>https://zerosial.tistory.com/entry/React%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-%ED%86%B5%EC%8B%A0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</link>
      <description>&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;React Query&lt;/b&gt;: React Query는 API 호출, 데이터 캐싱, 상태 관리, 리렌더링 최적화 등을 제공하는 라이브러리입니다. 쿼리와 뮤테이션을 사용하여 비동기 작업을 관리하며, 데이터를 효율적으로 가져오고 관리하는데 도움을 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Axios&lt;/b&gt;: Axios는 HTTP 클라이언트 라이브러리로, 서버와의 HTTP 요청을 보내고 응답을 받는데 사용됩니다. Promise 기반으로 작성되어 있어 간편하게 비동기 요청을 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redux-Saga&lt;/b&gt;: Redux-Saga는 Redux와 함께 사용되는 미들웨어 라이브러리로, 사이드 이펙트 관리를 위해 사용됩니다. 주로 복잡한 비동기 흐름을 관리하고 제어하는 데 사용되며, 제너레이터 함수를 이용하여 비동기 로직을 작성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Redux-Thunk&lt;/b&gt;: Redux-Thunk는 Redux의 미들웨어로, 간단한 비동기 작업을 처리할 때 사용됩니다. 액션 생성자가 함수를 반환하여 비동기 작업을 처리할 수 있게 해주며, 주로 간단한 비동기 로직을 처리하는데 활용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SWR (stale-while-revalidate)&lt;/b&gt;: SWR은 데이터 패칭 및 캐싱 라이브러리로, React 컴포넌트에서 데이터 패칭을 편리하게 다룰 수 있도록 도와줍니다. React Query와 유사한 목적을 가지고 있으며, 특히 RESTful API에 적합합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 Redux-Observable, MobX, Recoil 등 다양한 상태 관리 및 비동기 작업 관련 라이브러리들이 있습니다. 프로젝트의 요구 사항과 팀의 선호도에 따라 적절한 라이브러리를 선택하여 사용할 수 있습니다.&lt;/p&gt;</description>
      <category>IT 학습/프레임워크</category>
      <author>제로시엘</author>
      <guid isPermaLink="true">https://zerosial.tistory.com/101</guid>
      <comments>https://zerosial.tistory.com/entry/React%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%82%AC%EC%9A%A9%EB%90%98%EB%8A%94-%ED%86%B5%EC%8B%A0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC#entry101comment</comments>
      <pubDate>Fri, 25 Aug 2023 15:33:13 +0900</pubDate>
    </item>
  </channel>
</rss>