<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>function_dh</title>
        <link>https://velog.io/</link>
        <description>🍄 성장형 괴물..</description>
        <lastBuildDate>Sun, 02 Feb 2025 06:07:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>function_dh</title>
            <url>https://velog.velcdn.com/images/function_dh/profile/c88d53b6-a963-4993-bd19-5b4a783b522b/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. function_dh. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/function_dh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[nginx 세팅 및 PM2 무중단 서비스]]></title>
            <link>https://velog.io/@function_dh/nginx-%EC%84%B8%ED%8C%85-%EB%B0%8F-PM2-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@function_dh/nginx-%EC%84%B8%ED%8C%85-%EB%B0%8F-PM2-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Sun, 02 Feb 2025 06:07:26 GMT</pubDate>
            <description><![CDATA[<p>현재 회사에서 신규로 서비스를 개발하게 되면서 배포와 서비스 운영 관련하여 고민이 참 많았습니다. next를 주로 사용하기도 했고 아무래도 vercel에서 제작했다보니 서비스 관련하여 여러 설정이나 CI/CD 부분에서 상당히 세팅이 간단하다는 점이 있었는데요.</p>
<p>그럼에도 불구하고 직접 nginx와 pm2 세팅을 통해서 서비스를 운영하고 있기 때문에 그 내용을 짧게나마 정리해서 풀어보려 합니다.</p>
<h3 id="nginx와-pm2를-선택한-이유는">nginx와 pm2를 선택한 이유는?</h3>
<p>Nginx 대신 (Vercel, AWS Amplify 등)을 활용하면 장점들이 상당히 많습니다. 예를 들어 해당 플랫폼에 CI/CD가 내장 되어 있기 때문에 Jenkins 설정이나 따로 서버 운영이 필요없고 레포지토리를 해당 서비스에 연동해서 빌드 및 배포를 자동화 할 수 있기 때문입니다.
뿐만 아니라 확장성 이라던지 인프라 부분에서도 개발자가 많이 신경 쓰지 않아도 설정을 통해서 쉽게 적용할 수 있다고 생각합니다.</p>
<p>여러 장점들도 많지만 반대로 단점들도 많다고 생각하는데요. 일단 플랫폼에 의존성이 높아지게 된다고 생각합니다. 의존성이 높아지게 되면 개발자 스스로 어떤 방식으로 빌드된 파일이 배포 후 적용 되는지 알기 힘들고 제한적인 서버가 아니다 보니 비용적인 측면에서도 예측이 어렵다는 점도 큰 단점이라 생각합니다.</p>
<p>현재 회사는 자체 서버도 보유하고 있었고 비용적인 측면도 최대한 줄이면서 신규 서비스를 운영해야 하는 상황이였기 때문에 처음 서비스 시작전 nginx와 pm2, jenkins 세팅을 통해서 서비스 운영을 하기로 결정했습니다.</p>
<p>위의 내용에 조금 더해서 간단하게 표로 요약하면 아래와 같습니다.</p>
<h3 id="vercel과-nginx-비교-요약"><strong>Vercel과 Nginx 비교 요약</strong></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Nginx</th>
<th>Vercel</th>
</tr>
</thead>
<tbody><tr>
<td><strong>설치/운영</strong></td>
<td>직접 설치 및 관리 필요</td>
<td>자동화된 설치 및 운영</td>
</tr>
<tr>
<td><strong>배포 편의성</strong></td>
<td>Jenkins 등 추가 툴 필요</td>
<td>Git Push로 간편 배포</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td>수동 설정 필요</td>
<td>자동 확장 지원</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>고정 서버 비용 발생</td>
<td>사용량 기반 과금</td>
</tr>
<tr>
<td><strong>커스터마이징</strong></td>
<td>완전한 제어 가능</td>
<td>제한적 설정 가능</td>
</tr>
<tr>
<td><strong>CDN</strong></td>
<td>별도 설정 필요</td>
<td>기본 제공</td>
</tr>
<tr>
<td>요약하자면 트래픽 변화가 크고 관리 리소스를 줄이고 싶다면 <strong>Vercel</strong> 같은 서버리스 플랫폼이 유리하다 생각하지만 커스터마이징이 중요한 경우(특히 캐시 정책, 로깅, 보안 규칙 등) <strong>Nginx</strong> 기반 배포가 적합하다 판단했습니다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h3 id="서비스-목표">서비스 목표</h3>
<p>안전하게 서비스를 운영 및 배포하기 위해서 5가지 목표를 잡았습니다.</p>
<ul>
<li>자동 재시작</li>
<li>서비스 모니터링</li>
<li>로드 밸런싱</li>
<li>무중단 배포</li>
<li>로그 관리</li>
</ul>
<p>위와 같이 5가지 인데 해당 부분을 직접 세팅하고 운영하기에는 상당한 지식과 시간이 많이 소요 되기 때문에 pm2를 활용하여 보다 쉽고 관리하게 편하게 적용하도록 했습니다.</p>
<p>pm2의 경우 서버가 예기치 않게 종료 되거나 오류가 발생했을 때 애플리케이션을 자동으로 <strong>재시작하고, 모니터링, 로깅, 로드 밸런싱</strong> 등의 기능을 제공하여 안정성을 높이기 때문입니다.</p>
<ol>
<li><p><strong>자동 재시작</strong>: 애플리케이션이 비정상적으로 종료될 경우 PM2는 자동으로 재시작합니다.</p>
<ul>
<li>비정상적인 경우는 특정 메모리에 도달하거나 의도치 않게 프로세스가 종료 되었을 때 pm2에서 서비스를 자동으로 재시작 해주는 기능입니다.</li>
</ul>
</li>
<li><p><strong>서비스 모니터링</strong>: pm2의 모니터링 기능을 통해서 애플리케이션의 상태, CPU 및 메모리 사용량을 실시간으로 모니터링할 수 있습니다.</p>
</li>
<li><p><strong>로드 밸런싱</strong>: cluster 모드를 통해 여러 인스턴스를 실행하고 트래픽을 분산시킬 수 있습니다.</p>
</li>
<li><p><strong>무중단 배포</strong>: pm2 reload를 통해서 실행중인 프로세스를 하나 남기고 하나씩 껐다 재시작 하기 때문에 무중단으로 가능하게 됩니다.</p>
</li>
<li><p><strong>로그 관리</strong>: 애플리케이션의 로그를 효율적으로 관리하고 분석할 수 있습니다.</p>
<ul>
<li><p><code>pm2 install pm2-logrotate</code> 설치</p>
</li>
<li><p>pm2-logrotate 설정</p>
<pre><code class="language-jsx">  pm2 set pm2-logrotate:max_size 20M      # 로그 파일이 20MB를 넘으면 로테이션
  pm2 set pm2-logrotate:retain 30         # 최대 보관하는 로그의 파일 수
  pm2 set pm2-logrotate:compress true    # 압축된 형태로 로그 저장
  pm2 set pm2-logrotate:rotateInterval &#39;0 0 * * *&#39; # 매일 자정마다 로그 로테이션
  pm2 set pm2-logrotate:workerInterval 86400 # 로그 크기를 확인하는 간격 (현재는 하루)</code></pre>
</li>
<li><p>pm2 conf pm2-logrotate - 로그 로테이션 설정 정보 확인 (<a href="https://github.com/keymetrics/pm2-logrotate">공식 문서</a>)</p>
</li>
<li><p>압축된 형태(.gz)로 보기 생성되기 때문에 zless를 통해서 압축 해제 없이 확인 가능</p>
</li>
</ul>
</li>
</ol>
<h3 id="pm2-설치-및-명령어-정리">pm2 설치 및 명령어 정리</h3>
<ul>
<li><p><code>npm install pm2 -g</code></p>
</li>
<li><p>명령어 모음</p>
<pre><code class="language-jsx"> // 상태 확인
  pm2 status
  // 로그
  pm2 logs service-app
  // 재시작
  pm2 restart service-app
  // 중지
  pm2 stop service-app
  // 삭제
  pm2 delete service-app
  // 모두 삭제
  pm2 delete all
  // 현재 설정으로 pm2 시작
  pm2 start ecosystem.config.cjs --env production
  // pm2 모니터
  pm2 monit
  // pm2 log 정리
  pm2 flush</code></pre>
</li>
<li><p><code>ecosystem.config.js</code>로 pm2 cluster로 동작하게 수정</p>
<pre><code class="language-jsx">  module.exports = {
    apps: [
      {
        name: &#39;service-app&#39;,
        script: &#39;pnpm&#39;,
        args: &#39;start&#39;,
        cwd: &#39;./apps/service-app&#39;, // Next.js 프로젝트 루트 경로
        instances: &#39;max&#39;,
        exec_mode: &#39;cluster&#39;,
        watch: false,
        env: {
          NODE_ENV: &#39;development&#39;,
          PORT: 3101,
        },
        env_stage: {
          NODE_ENV: &#39;stage&#39;,
          PORT: 3102,
        },
        env_production: {
          NODE_ENV: &#39;production&#39;,
          PORT: 3000, // 또는 사용하려는 포트 번호
        },
        increment_var: &#39;PORT&#39;,
      },
    ],
  }</code></pre>
</li>
<li><p>cpu나 메모리 같은 경우는 아래 명령어를 통해 쉽게 확인이 가능합니다.</p>
<ul>
<li>cpu 확인 - <code>less /proc/cpuinfo</code></li>
<li>cpu 코어 확인 - <code>nproc</code></li>
<li>메모리 확인 - <code>free -h</code></li>
</ul>
</li>
<li><p>현재 서비스 중인 dev 서버의 경우 cpu 코어의 갯수가 2개 이기 때문에 2개 생성되게 됩니다.
<img src="https://velog.velcdn.com/images/function_dh/post/12ace3ec-344d-4063-9ad1-7d440ffd33e3/image.jpg" alt=""></p>
</li>
</ul>
<ul>
<li><code>pm2 start ecosystem.config.cjs --env production</code> - 환경 구별 가능</li>
</ul>
<h3 id="설정-파일-정리-nginx">설정 파일 정리 (nginx)</h3>
<p>다음은 nginx 세팅입니다. nginx 세팅 같은 경우는 인증서와 서버 관련 세팅이 있기 때문에 최대한 next 세팅 부분만 살펴보도록 하겠습니다.</p>
<p>Next.js 애플리케이션을 빌드 예시</p>
<pre><code class="language-bash"># 모노레포 루트 디렉토리에서
node 설치
pnpm install
pnpm build
pnpm start</code></pre>
<p>Nginx 설정 파일을 다음과 같이 구성합니다.</p>
<pre><code>server {
  # Next.js 애플리케이션 라우트
  location / {
      proxy_pass http://localhost:3000;
      proxy_http_version 1.1;
      more_set_headers &#39;Server: ----&#39;;
      real_ip_header X-Forwarded-For;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection &#39;upgrade&#39;;
      proxy_set_header Host $host;
      proxy_cache_bypass $http_upgrade;
  }
}
</code></pre><p>이 설정 파일을 저장한 후 Nginx를 재시작</p>
<pre><code class="language-bash">sudo service nginx restart</code></pre>
<h3 id="설정-파일-설명">설정 파일 설명</h3>
<p><strong>Next.js 애플리케이션 프록시</strong></p>
<blockquote>
<ul>
<li><code>proxy_pass http://localhost:3000;</code>
요청을 localhost의 3000 포트로 전달합니다. 즉, Nginx가 Next.js 애플리케이션이 실행 중인 서버에 요청을 전달합니다.</li>
</ul>
</blockquote>
<ul>
<li><code>proxy_http_version 1.1;</code>
HTTP/1.1 버전을 사용하여 프록시 요청을 수행하도록 설정합니다. 이는 웹소켓과 같은 기능을 지원하기 위해 필요합니다.</li>
<li><code>more_set_headers &#39;Server: ----&#39;;</code>
서버 응답의 Server 헤더를 수정합니다. 일반적으로 보안상의 이유로 서버 정보를 숨기기 위해 사용합니다.</li>
<li><code>real_ip_header X-Forwarded-For;</code>
클라이언트의 실제 IP 주소를 추적하기 위해 X-Forwarded-For 헤더를 사용합니다. Nginx가 뒤에 있는 서버에 이 정보를 전달하도록 설정합니다.</li>
<li><code>proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;</code>
클라이언트의 IP 주소를 포함하여 X-Forwarded-For 헤더를 설정합니다. 이를 통해 원래 요청자의 IP 주소를 추적할 수 있습니다.</li>
<li><code>proxy_set_header Upgrade $http_upgrade;</code>
웹소켓 연결을 위한 업그레이드 요청을 처리하기 위해 Upgrade 헤더를 설정합니다.</li>
<li><code>proxy_set_header Connection &#39;upgrade&#39;;</code>
연결을 웹소켓으로 업그레이드하기 위해 필요한 헤더를 설정합니다.</li>
<li><code>proxy_set_header Host $host;</code>
클라이언트가 요청한 원래의 호스트 이름을 Host 헤더에 설정합니다. 이는 서버가 요청을 올바르게 처리하는 데 도움이 됩니다.</li>
<li><code>proxy_cache_bypass $http_upgrade;</code>
웹소켓 업그레이드 요청에 대해 캐시를 우회하도록 설정합니다. 웹소켓 연결은 캐시되지 않아야 하기 때문에 이 설정이 필요합니다.</li>
</ul>
<p><strong>요약</strong>
nginx를 사용하여 Next.js 애플리케이션에 대한 요청을 처리하고, 클라이언트의 IP 주소를 유지하며, 웹소켓 연결을 지원하는 프록시 서버로 작동하도록 구성합니다. 이를 통해 성능을 높이고 보안을 강화할 수 있습니다.
웹소켓 부분의 설정이 필요한 이유는 웹소켓이 클라이언트와 서버 간의 지속적인 연결을 유지하여 양방향 통신을 가능하게 하는 프로토콜 입니다. 실시간 데이터 전송과 효율적인 연결 유지를 위해 필요하며, Nginx가 이러한 프로토콜을 제대로 지원하도록 해줍니다.</p>
<h3 id="참고-사이트">참고 사이트</h3>
<ul>
<li><a href="https://engineering.linecorp.com/ko/blog/pm2-nodejs">https://engineering.linecorp.com/ko/blog/pm2-nodejs</a></li>
<li><a href="https://mio-java.tistory.com/101">https://mio-java.tistory.com/101</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[모노 레포지토리 세팅, Next14 app router 개발 회고]]></title>
            <link>https://velog.io/@function_dh/%EB%AA%A8%EB%85%B8-%EB%A0%88%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EC%84%B8%ED%8C%85-Next14-app-router-%EA%B0%9C%EB%B0%9C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@function_dh/%EB%AA%A8%EB%85%B8-%EB%A0%88%ED%8F%AC%EC%A7%80%ED%86%A0%EB%A6%AC-%EC%84%B8%ED%8C%85-Next14-app-router-%EA%B0%9C%EB%B0%9C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 27 Jan 2025 05:53:11 GMT</pubDate>
            <description><![CDATA[<h3 id="설명에-앞서">설명에 앞서</h3>
<p>새로운 회사로 이직하면서 기존과 다른 개발 경험을 많이 접하게 되었습니다.
프론트엔드 개발자가 없었기 때문에 초기 세팅부터 팀 문화까지 전부 결정해야 했기 때문입니다.</p>
<p>이전 회사의 경우에는 인프라부터 신규 프로젝트를 구성할 때에도 크게 관여할 수 있는 부분이 적었습니다. 아무래도 경력이 많은 개발자가 많기도 하였고 리드직이 아니였기 때문에 제안까지는 가능하지만 신규 세팅이나 CI/CD 부분에서 기존세팅을 활용하는 경우가 많았기 때문입니다. </p>
<p>하지만 지금의 경우에는 혼자 결정하고 진행하여야 했기 때문에 프로젝트 세팅부터 고민할 거리가 많았습니다. 앞으로 어떤 프로젝트들이 추가될지 고려하여야 했고 현재 개발하는 서비스에 맞게 프로젝트를 세팅하고 현재 개발팀의 환경까지 생각하여야 했기 때문입니다. 추가적으로 기획팀이나 마케팅팀의 고려사항도 반영하는 건 당연했습니다.</p>
<p>이런 여런 상황들을 반영하여 문서로 정리하고 신규 서비스를 개발하면서 정리한 내용들을 간략하게 소개해보려고 합니다. </p>
<p>많은 내용이 있을지 모르겠지만 작게 나마 도움이 되었으면 좋겠습니다.</p>
<h3 id="프로젝트-기술-스펙">프로젝트 기술 스펙</h3>
<p>가장 먼저 개발해야 했던 프로젝트는 &#39;크로스샵&#39; 이라는 외국인을 위한 이커머스 쇼핑몰 이였습니다. 크로스 플랫폼을 회사에서 원했었기 때문에 기존 크로스 앱에서 크로스샵으로 연결되는 웹뷰 페이지, 웹에서 접근 가능한 모바일 페이지를 개발하는 것이 목표였고 다른 이커머스와 다른점은 다국어를 적용해야 한다는 점 이였습니다.</p>
<p>신규 세팅이였기 때문에 SEO와 추후에 프론트 엔드 개발자의 채용을 위해서 개발언어는 Next14의 app router를 사용하기로 생각했습니다. SEO 측면에서도 쉽고 빠르게 개발할 수 있었고 다양한 렌더링 방식을 fetch를 통해서 간단하게 설정할 수 있었기 때문입니다. 또한 새롭게 도입된 개념인 서버 컴포넌트(RSC)를 활용하여 빠르게 렌더링할 수 있는게 장점이라 생각했습니다. 서버 컴포넌트가 렌더링이 빠른 이유는 서버 단에서 말 그대로 컴포넌트를 렌더링 하기 때문인데요. 간략하게 살펴보면 RSC Payload를 서버에서 생성한 후 클라이언트 컴포넌트의 빈자리는 Placeholder로 남겨둔 상태에서 실제 HTML을 생성해서 서버에 요청이 들어오는 즉시 보여주게 됩니다.(pre-rendering) 이후에 스트리밍 기능을 활용해서 먼저 생성된 html을 먼저 보여주고 생성된 순서대로 순차적으로 페이지를 보여준 후 하이드레이션 과정이 일어나기 때문에 기존 렌더링보다 빠르게 처리되는 것 입니다.</p>
<p>또한 빠르게 변하는 프론트 엔드 생태계에서 신규 기술을 안정적으로 도입하는 것이 큰 장점으로 다가올 것이라 생각했습니다.</p>
<p>next를 제외하고 다른 패키지의 경우는 간력하게 아래에 선정 이유를 작성해두었습니다.</p>
<p>물론 추후 next가 아닌 다른 세팅이 필요할 경우에도 추가 세팅만 마무리되면 바로 적용할 수 있도록 레포지토리 세팅을 구성 해두었습니다.</p>
<h4 id="기술-스펙">기술 스펙</h4>
<ul>
<li><code>개발언어</code> React 18, Typescript</li>
<li><code>프레임워크</code> Next.js v14 (app router)</li>
<li><code>서버상태관리</code> TanStack Query v5</li>
<li><code>상태관리</code> Zustand</li>
<li><code>유틸리티 css</code> <a href="https://nextjs.org/docs/pages/building-your-application/styling/css-modules">css module</a> , TailwindCSS</li>
<li><code>CI/CD</code> Gitlab, Jenkins, nginx, pm2</li>
<li><code>다국어 지원 관련</code> <a href="https://next-intl-docs.vercel.app/docs/getting-started/app-router/without-i18n-routing">next-intl</a>, 회사 다국어 api</li>
<li><code>서비스 모니터링 및 로깅</code>: Sentry, Grafana</li>
</ul>
<h4 id="선정-이유">선정 이유</h4>
<ul>
<li><code>개발언어</code> React 18, Typescript<ul>
<li>추후 개발자 영입 및 유지보수, 수많은 커뮤니티, 호환 가능한 라이브러리 등을 고려하여 선정</li>
</ul>
</li>
<li><code>프레임워크</code> Next.js v14<ul>
<li>Next14 에서만 사용 가능한 app router를 통하여 RSC(리액트 서버컴포넌트)로 개발 진행</li>
<li><a href="https://www.freecodecamp.org/korean/news/how-to-use-react-server-components/">서버 컴포넌트</a>를 활용하여 직렬화된 데이터를 json 형식으로 내려 받기 때문에 제로 번들 사이즈 가능</li>
<li>SEO 관련하여 쉽게 개발이 가능</li>
</ul>
</li>
<li><code>서버상태관리</code> TanStack Query v5<ul>
<li>서버 데이터 관리에서 충분한 라이브러리라고 판단</li>
<li>RSC와 같이 사용하기에 좋은 시너지 <a href="https://velog.io/@cnsrn1874/you-might-not-need-react-query">(링크)</a></li>
</ul>
</li>
<li><code>상태관리</code> Zustand<ul>
<li>보일러 플레이트가 적기 때문에 쉽게 사용 가능</li>
<li>redux devtools로 디버깅이 가능</li>
<li>flux패턴으로 되어 있기 때문에 data 추적에 용이</li>
</ul>
</li>
<li><code>유틸리티 css</code> <a href="https://nextjs.org/docs/pages/building-your-application/styling/css-modules">css module</a> , TailwindCSS<ul>
<li>RSC는 서버에서 렌더링되기 때문에 css-in-js를 사용할 수가 없음<ul>
<li>클라이언트에서 동적으로 스타일을 생성하기 때문</li>
<li>런타임 오버 헤드로 성능상의 이슈가 존재</li>
</ul>
</li>
<li>따라서 제로 런타임 스타일을 적용을 위해서 선정</li>
<li>TailwindCSS의 경우 커스텀을 제외한 스타일의 경우 템플릿에 맞추어 개발하기 때문에 개발 속도 향상을 위하여 같이 사용</li>
</ul>
</li>
<li><code>CI/CD</code> Gitlab, Jenkins, nginx, pm2</li>
<li><code>다국어 지원 관련</code> - next-intl, 회사 다국어 api</li>
</ul>
<h4 id="모노-레포지토리-부가-설명">모노 레포지토리 부가 설명</h4>
<p><img src="https://velog.velcdn.com/images/function_dh/post/3b43cc32-cf96-46c1-af2e-dcbddc34a7a1/image.png" alt=""></p>
<p>모노 레포지토리의 경우 현재는 turborepo가 적용되어 있지만 초기 구성 당시에는 pnpm-workspace 세팅을 통해서만 모노 레포지토리로 구성을 해두었습니다.</p>
<p>프로젝트가 1개인데 구성을 할 필요가 있었나? 싶을 수도 있지만 다른 프로젝트를 추가로 진행할 일이 무조건 생길거라는 확신이 있었고 실제로 현재에는 플랫폼이 확장됨에 따라 현재의 구성을 잘 활용하고 있습니다.</p>
<p>1개의 서비스만 운영을 할 때에는 개발 당시에 공통으로 사용할 수 있는 컴포넌트를 고려하여 상위의 packages 폴더안에 기능별로 폴더를 나누어 미리 root단에서 패키지로 구별을 해두었습니다.</p>
<p>컴포넌트의 경우 공통으로 구별할 button이나 loading, layout관련 등 미리 분리가 가능했고 eslint, tailwind 같이 공통 세팅을 가져가는 부분도 쉽게 분리가 가능했습니다.!</p>
<h4 id="app-router에서-리액트-쿼리를-사용한-이유는">app router에서 리액트 쿼리를 사용한 이유는?</h4>
<p><img src="https://velog.velcdn.com/images/function_dh/post/00e8a425-3e97-4184-ab30-60bf2aa87a55/image.png" alt=""></p>
<p>rsc에서 리액트 쿼리를 충분히 활용할 수 있다고 생각했습니다. 물론 fetch를 활용하면 되지 않나? 싶을 수 있지만 서버 컴포넌트에서도  prefetchQuery 를 통하여 쿼리를 사용할 수도 있고 미리 불러온 쿼리를 재사용하여 api 호출을 최대한 줄이고 유저들로 하여금 렌더링 속도를 최적화 시킬 수 있다고 판단 했습니다.</p>
<p>물론 무조건적으로 쿼리를 활용하는 것이 아닌 클라이언트 컴포넌트에서 캐시를 좀더 손쉽게 사용하거나 mutation의 활용이 필요한 부분에서만 사용할 수 있도록 반영 해두었습니다.</p>
<h3 id="프로젝트-폴더-구조">프로젝트 폴더 구조</h3>
<p>프로젝트 폴더 구조의 경우 next의 app router 폴더구조를 기본으로 가져가고  root단의 구조의 경우 모노 레포지토리의 구조를 가져가 추후 다른 개발자가 확인 하였을 때에도 큰 문제없이 구조만으로 이해가 되도록 구조를 만드려고 노력했습니다.</p>
<p>현재는 vite 프로젝트의 경우도 next와 동일한 폴더 구조를 가져갈 수 있도록 라우터 세팅을 하여 개발을 진행하였습니다.</p>
<pre><code class="language-jsx">apps                       // 각각의 모노레포를 담는 폴더       
└── monorepo               // 하나의 레포지토리
    ├── app                // app 라우터
    │   ├── api            // Route Handlers 처리 
    │   └── shop           // 하나의 페이지 - &#39;/shop&#39; url로 설정
    ├── components         // 공통으로 사용될 컴포넌트 폴더 
    │   ├── common         // 사이트 공통 컴포넌트
    │   ├── feature        // 특정 페이지에서 사용하는 공통 컴포넌트
    │   ├── service        // 외부 라이브러리에 의존하는 공통 컴포넌트
    │   ├── ui             // 레이아웃 영역의 공통 컴포넌트
    │   └── (item)         // () 라우팅 그룹 - 같은 목적의 파일 그룹 처리
    │       └── _layout    // private 폴더로 라우팅 처리되지 않음
    ├── constants          // 공통으로 사용하는 상수 정보 관리
    ├── hooks              // 공통으로 사용되는 리액트 훅 정리
    │   └── queries        // 공통으로 리액트 쿼리에서 사용할 쿼리 분리 (key, query)
    ├── public             // 리소스 파일 폴더 (image, locales)  
    ├── styles             // 공통으로 사용되는 스타일
    ├── type               // 공통으로 사용되는 타입
    ├── server             // server에서 사용하는 함수 정의 (다국어, user-agent 등)
    ├── store              // 해당 레포지토리 store
    └── utils              // 공통으로 사용되는 uitl 
packages                   // 각각의 모노레포에서 사용할 패키지
├── components             // 각각의 모노레포에서 사용할 컴포넌트
│   └── src
│       ├── etc
│       ├── flex 
│       ├── grid 
│       └── loading
├── config                 // 각각의 모노레포에서 사용할 config
│   ├── tailwind-config     
│   └── ts-config 
├── constants              // 각각의 모노레포에서 사용할 constants
│   ├── colors             // tailwind 커스텀 색상
│   └── iso                // 다국어 설정
├── ui                     // 각각의 모노레포에서 사용할 ui관련 컴포넌트
│   ├── colors             // scss 커스텀 색상
│   ├── font               // web 공통 font
│   └── layout             // mobile, pc, reset 스타일
└── utils                  // 각각의 모노레포에서 사용할 utils</code></pre>
<h4 id="컴포넌트-상세-폴더구조">컴포넌트 상세 폴더구조</h4>
<ul>
<li><p>One Folder - One Component - 규칙</p>
</li>
<li><p>추가적으로 해당 컴포넌트에 기능이 추가되거나 삭제 되었을 때 폴더 구조에 영향을 주지 않기 때문에 해당 방식으로 폴더구조를 정의 했습니다</p>
<pre><code class="language-jsx">  src
  └── flex
      ├── index.js
      ├── flex.jsx
      ├── flex.test.js
      └── flex.module.scss</code></pre>
</li>
<li><p>index 파일 작성시</p>
<pre><code class="language-jsx">  export { default } from &#39;./flex&#39;;</code></pre>
</li>
<li><p>flex 컴포넌트를 import할때는 아래처럼 작성</p>
<pre><code class="language-jsx">  import flex from &#39;../flex/index.js&#39;;

  // 하지만 자바스크립트에서는 /index.js가 기본값이기 때문에 아래처럼 작성 가능
  import flex from &#39;../flex&#39;;</code></pre>
</li>
</ul>
<p>글을 작성하다 보니 설명할 내용들이 너무 길어져 각 부분별로 상세하게 작성을 못한거 같아 아쉬움이 많이 남지만 최대한 기술적인 내용보다 실제로 프로젝트에 적용을 한 이유나 개발을 하면서 구조를 정할 때의 목적을 작성하려고 노력했습니다.</p>
<p>긴글 읽어 주셔서 감사합니다. :) </p>
<h3 id="참고-사이트">참고 사이트</h3>
<p><a href="https://velog.io/@baby_dev/Next.js-13-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0-%EC%9D%B4%EC%81%9C-%EC%9E%A1%EA%B8%B0%EC%88%A0">https://velog.io/@baby_dev/Next.js-13-폴더-구조-이쁜-잡기술</a>
<a href="https://aierse.tistory.com/4">https://aierse.tistory.com/4</a> - 리액트 폴더 구조 설계 5단계
<a href="https://nextjs.org/docs/getting-started/project-structure">https://nextjs.org/docs/getting-started/project-structure</a> - next 라우팅 규칙</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[솔드아웃 상품 상세 페이지 렌더링 속도개선]]></title>
            <link>https://velog.io/@function_dh/%EC%86%94%EB%93%9C%EC%95%84%EC%9B%83-%EC%83%81%ED%92%88-%EC%83%81%EC%84%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8D%EB%8F%84%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@function_dh/%EC%86%94%EB%93%9C%EC%95%84%EC%9B%83-%EC%83%81%ED%92%88-%EC%83%81%EC%84%B8-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8D%EB%8F%84%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Thu, 28 Mar 2024 03:00:04 GMT</pubDate>
            <description><![CDATA[<h3 id="개선-배경">개선 배경</h3>
<p>기존 솔드아웃 상품 상세 페이지의 경우 진입시 상당한 긴 렌더링 시간을 가지고 있었습니다..</p>
<p>그 이유는 상품 상세 data 관련 api 통신이 완료되기 까지 빈화면인 상태로 무한히 대기 할 수 밖에 없는 구조로 되어 있었기 때문인데요</p>
<p>이러한 부분이 반복되다 보면 커머스적으로 심각한 문제이기도 하고 사용자 입장에서 불편한 경험이 쌓이게 되어 유저의 이탈이 많아 질 것으로 생각이 되었습니다.</p>
<p>따라서 렌더링이 느려지는 요소를 분석하여 상품 상세 페이지의 렌더링 속도를 개선하기로 했습니다! 🤓</p>
<h3 id="문제점들">문제점들</h3>
<ol>
<li>상품 상세 api가 너무 많은 data를 가지고 있어 서버 응답이 오래 걸리는 이슈</li>
<li>해당 api의 data가 없을 경우 상품 상세 페이지 전체를 노출하지 않아 로딩창만 보이는 이슈</li>
<li>제품 상세 페이지 진입시 보이지 않는 영역의 컴포넌트까지 한번에 렌더링 되는 이슈</li>
</ol>
<p>프론트 이슈가 아닌 부분도 있지만 충분히 백엔드 작업자 분과 협의하여 해결할 수 있을 거라 판단하여 위 문제점들을 토대로 개선을 진행하기로 하였습니다.</p>
<h3 id="개선-내용정리-요약"><strong>개선 내용정리 (요약)</strong></h3>
<ul>
<li>제품 상세 api - <code>productDetail</code>가 너무 많은 data를 가지고 있어 서버 응답이 오래 걸리는 이슈<ul>
<li>해당 페이지에서 모든 data가 하나의 api에서만 처리하고 있었기 때문에 api 분리 요청을 드려서 기본적으로 <code>첫 페이지 진입시 보여지는 컨텐츠 영역</code>에 한정하여 api 분리 요청</li>
<li>상세페이지 기본 정보 api 추가 - <code>basic api</code> 로 초기 렌더링을 변경하여 서버 응답 줄이도록 처리</li>
</ul>
</li>
<li>상품상세 페이지 진입시 상품상세 api data 값이 없으면 페이지를 노출하지 않음<ul>
<li>상품상세 페이지 진입 후 바로 보여지는 컴포넌트의 경우 기본 data init값을 넣어주어 layout을 유지하도록 변경</li>
<li>api 분리로 인하여 초기 로딩 속도가 개선되어 의미가 없어진 Loading 컴포넌트는 제거</li>
</ul>
</li>
<li>productDetail(제품 상세 api data)로 모든 상품상세 값을 제어하고 있어서 값이 바뀌면 리렌더링 되기 때문에 최적화 필요<ul>
<li>productDetail에 있던 data를 컴포넌트 별로 분리</li>
<li><code>viewLogicMethods</code> 를 분리하여 각 컴포넌트 별 data 세팅을 분리하여 처리</li>
</ul>
</li>
<li>제품 상세에 진입 시 첫 로드시 많은 컴포넌트가 한번에 렌더링 되기 때문에 속도가 느림<ul>
<li><code>observer</code> 기능을 활용하여 해당 컴포넌트에 도달 했을 때 data 적용하도록 수정</li>
<li>상품 특성, 다른 고객이 함께 본 상품, 연관 추천 컨텐츠 등 페이지 하단에 위치한 컴포넌트 위주로 처리</li>
</ul>
</li>
</ul>
<h3 id="개선-상세-내용"><strong>개선 상세 내용</strong></h3>
<ol>
<li>기존 페이지에서 상품 상세 api 관련 data를 모두 처리하여 api 통신이 끝나기 전까지 화면 노출이 되지 않아 상세 페이지 로딩 속도가 느리게 느껴지는 점이 있었습니다.<ul>
<li>basic api를 사용하여 첫 화면 진입시 보이는 data와 meta tag 세팅을 진행하여 SEO 부분도 함께 처리하였습니다.
  <img src="https://velog.velcdn.com/images/function_dh/post/29c606a9-7b4d-450d-a30e-fcdb9eec8735/image.png" alt=""></li>
</ul>
</li>
</ol>
<ol start="2">
<li>위와 같이 변경하면서 <code>productDetail</code> data에 의존하던 컴포넌트의 경우 모두 에러가 발생하여 기본값으로 init data를 초기에 세팅한 후에 <code>mounted</code>에서 <code>promise all</code>을 활용하여 api가 호출 완료 되기 전까지 화면이 페인팅 될 수 있도록 수정하였습니다.</li>
<li>mounted에서 api 호출이 완료되면 <code>viewLogicMethods.setItemDetail()</code> 메소드가 동작하여 각 컴포넌트에서 활용하는 data로 분리되어 값을 할당하게 됩니다.
 <img src="https://velog.velcdn.com/images/function_dh/post/d7341c78-99cf-4870-a757-0e5c816048e7/image.png" alt=""></li>
</ol>
<pre><code>데이터 할당 관련 예시</code></pre><ol start="4">
<li><p>가장 로딩이 느린 <code>차트 컴포넌트</code>의 경우도 페이지 첫 로딩시 해당 컴포넌트에서 차트 관련 data를 설정 하는 것이 아니라 상품상세 컴포넌트 <code>mounted</code>부분으로 변경하여 <code>promise all</code> 부분에 포함되어 초기 로딩속도가 빨라졌습니다.</p>
</li>
<li><p>상품 상세 하단 영역의 경우 불필요한 서버 렌더링을 줄이기 위해 보이지 않는 부분의 컴포넌트는 <code>client-only</code> 처리 하였습니다.</p>
</li>
<li><p><code>제품 특성 안내 팝업, 연관 추천 콘텐츠</code>의 경우 어드민에서 에디터를 통해서 값이 들어가기 때문에 이미지 같은 경우 원본 이미지가 전송되어 상당히 큰 이미지를 가지고 있었습니다. (2000x1500)</p>
<p> 따라서 해당 부분에 <code>observer</code> 기능을 적용하여 첫 페이지 로딩시가 아닌 해당 컴포넌트에 스크롤이 도착했을 경우 값을 할당 되게 변경하였습니다.
 <img src="https://velog.velcdn.com/images/function_dh/post/bc1f6441-25b0-436b-be3c-492692408f79/image.png" alt=""></p>
</li>
</ol>
<h3 id="그래서-속도가-얼마나-빨라-졌는데">그래서 속도가 얼마나 빨라 졌는데?</h3>
<p>속도 테스트는 stage 서버 기준으로 작성 되었습니다.</p>
<p><strong>LightHouse</strong>
접근성 부분도 같이 변경하여 <code>24점 상승 → 86점</code>
seo <code>8점 상승 → 100점</code>
퍼포먼스의 경우 <code>10점이 상승 → 63점</code></p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/1171049d-5869-4f67-8b81-0bd8cfddb911/image.png" alt=""> ⬆️개선전 stage</p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/d002c79b-e270-4b68-affc-abb306b68fc7/image.png" alt=""> ⬆️개선후 stage
<br /></p>
</blockquote>
<p><strong>Performance</strong>
<strong>FCP (First Contentful Paint)</strong> 1.8초 → <strong>0.7초</strong>
<strong>LCP (Largest Contentful Paint)</strong> 2.9초 → <strong>2.3초</strong></p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/105009f1-f71e-4be8-923c-13c86c099e61/image.png" alt=""> ⬆️개선전 performance</p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/ef9c0f37-1362-424b-b595-8fc77cf01d0b/image.png" alt=""> ⬆️개선후 performance</p>
</blockquote>
<h3 id="실제로-느껴보자">실제로 느껴보자!</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/15c3f787-f434-4034-8bfb-b24a44683552/image.gif" alt=""> <strong>FCP (First Contentful Paint)</strong> - 1.87초 
<strong>LCP (Largest Contentful Paint)</strong> - 1.98초</p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/function_dh/post/7c523d7a-7d82-4522-8dbc-0daeffc755c4/image.gif" alt=""> <strong>FCP (First Contentful Paint)</strong> - 0.46초
<strong>LCP (Largest Contentful Paint)</strong> - 1.56초</p>
</blockquote>
<p>위의 이미지에서 처음 레이아웃이 그려지는 시간, data가 컴포넌트에 세팅되어 렌더링이 완료되는 시점에 중점을 두어 보면 좋을 것 같습니다 :)</p>
<h3 id="마무리"><strong>마무리</strong></h3>
<p>생각보다 좋은 결과가 나오게 되었는데요! 이번 작업을 통하여 <code>FCP의 경우 1.4초</code>, <code>LCP의 경우 0.4초</code> 정도 렌더링이 빨라지게 되었습니다!</p>
<p>작업을 진행하면서 너무나 많은 부분들이 잘못 되어 있고 개선할 방향이 앞으로도 많이 남았기 때문에 (이미지 렌더링 처리, 역할에 따른 컴포넌트 분리 등) 추가적인 작업을 꼭! 진행해야겠다는 반성도 하게 되었습니다.</p>
<p>하지만 한편으로는 하나씩 풀어가다보면 얽혀있는 실도 풀 수 있듯이 꾸준하게 개발적으로 옳은 방향으로 나아가려고 하는 것 같아 만족스러운 부분도 있다 생각합니다.</p>
<p>작업을 진행하면서 test시에 버그 제보를 주신 팀원 분들과 상품상세 개선 관련하여 많은 초석을 다진 같은 파트인 용문님께 감사 인사 드립니다 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Context API 의존성 주입에 관하여..]]></title>
            <link>https://velog.io/@function_dh/React-Context-API-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@function_dh/React-Context-API-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Sat, 16 Mar 2024 14:39:13 GMT</pubDate>
            <description><![CDATA[<h3 id="context-api가-뭔데">Context API가 뭔데?</h3>
<p>리액트 공식 문서에 따르면 아래와 같이 설명하고 있습니다.</p>
<pre><code class="language-jsx">context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도
컴포넌트 트리 전체에 데이터를 제공할 수 있습니다.

일반적인 React 애플리케이션에서 데이터는 위에서 아래로(즉, 부모로부터 자식에게)
props를 통해 전달되지만,애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는
props의 경우(예를 들면 선호 로케일, UI 테마) 이 과정이 번거로울 수 있습니다.

context를 이용하면, 트리 단계마다 명시적으로 props를 넘겨주지 않아도
많은 컴포넌트가 이러한 값을 공유하도록 할 수 있습니다.</code></pre>
<p>따라서 props drilling을 방지 할 수 있습니다.
vue랑 비교해본다면 event bus와 비슷한 목적을 가지고 있습니다.</p>
<h3 id="사용법">사용법</h3>
<p>기본적으로 <code>React.createContext</code> 메소드를 통하여 context 객체를 만들어 사용 합니다.</p>
<pre><code class="language-jsx">const NewContext = React.createContext(defaultValue);

// 타입 지정도 가능합니다.
const NewContext2 = React.createContext&lt;{value:string}&gt;({value:&#39;text&#39;});</code></pre>
<p>defaultValue의 경우 컴포넌트 트리 안에서 provider를 찾지 못할 때 사용하는 기본값 입니다.
context 객체를 만든 후에  <code>React.Provider</code> 통하여 전달 받은 값을 하위에 위치하는 컴포넌트들에게 전달 해줄 수 있습니다.
provider 안에 provider를 사용 할 수도 있고 context 객체는 가장 가까운 provider를 통하여 값을 전달 받게 됩니다.</p>
<pre><code class="language-jsx">// ui 관련 mode를 전달하는 예시
const DisplayContext = React.createContext&lt;{mode:&#39;light&#39;|&#39;dark&#39;}&gt;({mode:&#39;light&#39;});

return (
  &lt;DisplayContext.Provider value={mode:&#39;light&#39;}&gt;
    &lt;App /&gt;
  &lt;/DisplayContextProvider&gt;
)</code></pre>
<p>주의 해야 할 점은 context를 바라보고 있는 하위 컴포넌트들은 Provider의 값이 변경되면 전부 렌더링 됩니다.
따라서 context 안에 연관성이 없는 값들을 함께 넣을 경우 성능 이슈가 발생할 수 있습니다.
이제 선언을 했으니 사용을 해야겠죠?</p>
<pre><code class="language-jsx">const App = () =&gt; {
    // 선언한 context를 불러와서 안에 있는 mode값을 사용
    const { mode } = React.useContext(DisplayContext);

    return (
    &lt;div style={{background : mode === &#39;light&#39; ? &#39;white&#39; : &#39;black&#39;}}&gt;
      Hello
    &lt;/div&gt;
    )
}</code></pre>
<p>함수형 컴포넌트에서는 <code>useContext</code>를 이용해 context에 넣어준 값을 꺼내올 수 있습니다.</p>
<pre><code class="language-jsx">export const useDisplayContext = () =&gt; React.useContext(DisplayContext);</code></pre>
<p>또한 useContext를 custom hook 형태로 한번 더 감싸서
컴포넌트에선 해당 훅만 호출하여 사용할 수 있습니다.</p>
<p>너무 간편하죠? </p>
<p>부모에게서 props를 내려받지 않고 건너뛰어 값을 공유한다는 것이
마치 전역으로 상태를 관리하는 것만 같습니다.
<strong>하지만 Context는 상태 관리 툴이 아닙니다!!</strong></p>
<h3 id="엥-상태관리-툴이-아니야">엥 상태관리 툴이 아니야?</h3>
<p>아래와 같이 중첩으로 사용할 경우 값 변경이 발생 했을 때 추적이 어렵고
관리적인 측면에서도 파악하기가 쉽지 않다는 점이 있습니다.</p>
<pre><code class="language-jsx">&lt;Context1.Provider value={...}&gt;
  &lt;Context2.Provider value={...}&gt;
    &lt;Context3.Provider value={...}&gt;
      &lt;Context4.Provider value={...}&gt;
          ...
      &lt;/Context4.Provider&gt;
    &lt;/Context3.Provider&gt;
  &lt;/Context2.Provider&gt;
&lt;/Context1.Provider&gt;</code></pre>
<p>상태관리 툴 같은 경우 다음과 같은 조건이 필요합니다. <code>값 저장, 값 읽기, 값 변경</code></p>
<p>Context는 자체적으로 어딘가에 값을 저장 하지도 않고 useState의 setState처럼 값을 변경하는 기능을 제공하지도 않습니다.</p>
<p>단지 Provider에 넣어준 value를 하위 Children 아무곳에서나 useContext를 사용해 꺼내쓰기만 하기 때문입니다.</p>
<p>따라서 특정 라이브러리나 기능을 <strong>인터페이스로 추상화</strong>한 후 인터페이스를 확장한 객체의 인스턴스를 Context에 주입합니다. </p>
<p>이를 사용할 때는 Context를 사용하며 그 기능을 <strong>직접적으로 사용하는 것이 아닌</strong> <strong>추상화한 인터페이스를 가르키게 하여 쉽게 교체 가능 하게함</strong>으로써 <strong>의존성 주입의 개념</strong>을 가졌다고 할 수 있습니다.</p>
<h3 id="의존성을-주입해보자">의존성을 주입해보자</h3>
<p>예시로 보통 서버와 통신을 할 때 Axios 라이브러리를 사용하여 통신을 합니다.</p>
<pre><code class="language-jsx">const data = await axios.get(&quot;URL&quot;);</code></pre>
<p>직접적으로 axios 호출하기 때문에 라이브러리에 문제가 생기거나 추후 라이브러리가 교체될 경우 관련된 코드를 모두 찾아서 수정해야 하는 문제가 발생하게 됩니다.</p>
<p>따라서 해당 문제를 context api에 의존성을 주입하여 해결해보겠습니다.</p>
<pre><code class="language-jsx">export interface FetchInterface {
  get&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt;;
  post&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt;;
}</code></pre>
<p>rest api를 통하여 서버와 통신할 때 사용하는 get, post의 기능을 추상화하여 interface를 만들어 줍니다.</p>
<p>interface를 만든 후에 implements 통하여 class가 interface에 맞는지 체크를 합니다.</p>
<pre><code class="language-jsx">// axios 사용시
export class Axios implements FetchInterface {
  async get&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt; {
    const { data } = await axios.get&lt;T&gt;(url, { params });
    return data;
  }
  async post&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt; {
    const { data } = await axios.post(url, { params });
    return data;
  }
}</code></pre>
<p>axios 라이브러리가 사용 불가능할 때 대체 할 기본 값인 Fetch도 준비해줍니다.</p>
<pre><code class="language-jsx">// fetch 사용시
export class Fetch implements FetchInterface {
  async get&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt; {
    const data = await fetch(url, {
      method: &quot;GET&quot;,
      body: JSON.stringify(params)
    }).then((response) =&gt; response.json());
    return data;
  }
  async post&lt;T&gt;(url: string, params?: any | any[]): Promise&lt;T&gt; {
    const data = await fetch(url, {
      method: &quot;POST&quot;,
      body: JSON.stringify(params)
    }).then((response) =&gt; response.json());
    return data;
  }
}</code></pre>
<p>자! 실제로 사용해봅시다.</p>
<pre><code class="language-jsx">// context 객체 생성
const FetchContext = React.createContext&lt;FetchInterface&gt;(new Fetch());</code></pre>
<p>기본 default 값으로 사용할 Fetch 클래스의 인스턴스를 전달 해줍니다.</p>
<pre><code class="language-jsx">export const FetchContextProvider = ({
  children
}: {
  children: JSX.Element;
}) =&gt; {
  return (
        // 추후 fetching 라이브러리 변경 시 인터페이스 정의대로 클래스를 생성 후,
        // value에 인스턴스만 갈아 끼워주면 된다.
    &lt;FetchContext.Provider value={new Axios()}&gt;
      {children}
    &lt;/FetchContext.Provider&gt;
  );
};</code></pre>
<p>provider쪽 value에는 클래스 인스턴스를 value 값으로 넣어줍니다. 따라서 위에서 선언한 Axios 클래스의 인스턴스를 전달 해줍니다.</p>
<pre><code class="language-jsx">export default function App() {
  const [state, setState] = useState&lt;any&gt;();
  const { get } = useFetchContext();

  const getServerData = useCallback(async () =&gt; {
    const data = await get&lt;any&gt;(&quot;https://jsonplaceholder.typicode.com/posts&quot;);
    return data;
  }, [get]);

  useEffect(() =&gt; {
    getServerData().then((response) =&gt; setState(response));
  }, [getServerData]);

  return (
    &lt;FetchContextProvider&gt;
      &lt;div&gt;{state &amp;&amp; JSON.stringify(state)}&lt;/div&gt;
    &lt;/FetchContextProvider&gt;
  );
}
</code></pre>
<p>사용하는 곳에서는 특정 기술을 직접 호출하지 않고 context로 정의한 함수를 이용하기 때문에 <strong>로직과 특정 기술의 분리</strong>가 가능해집니다.</p>
<p>이렇게 특정 기능이나 기술을 정해진 형식대로 정의해 놓으면 사용하는 컴포넌트는 불필요한 변경에 자유로워질 뿐만 아니라 테스트할 때는 상위에서 mocking Provider만 제공 하기만 하면 된다는 장점이 있습니다.</p>
<h3 id="context를-사용시-리렌더링이-발생하는-이유는">context를 사용시 리렌더링이 발생하는 이유는?</h3>
<p>렌더링이 되는 이유는 context는 “보이지 않는 props” 또는 “내부 props”와 같기 때문입니다.</p>
<pre><code class="language-jsx">const App = React.memo(() =&gt; {
    const { mode } = React.useContext(DisplayContext);

    return (
    &lt;div style={{background : mode === &#39;light&#39; ? &#39;white&#39; : &#39;black&#39;}}&gt;
      Hello
    &lt;/div&gt;
    )
});

// 위에 코드와 동일하다
const App = React.memo(({mode}) =&gt; {
    return (
    &lt;div style={{background : mode === &#39;light&#39; ? &#39;white&#39; : &#39;black&#39;}}&gt;
      Hello
    &lt;/div&gt;
    )
});</code></pre>
<p><code>App</code> 은 props가 없는 순수한 컴포넌트이기 때문에 부모 컴포넌트의 상태값이 변경되어도 리렌더링이 발생하지 않아야 하지만 context를 통한 내부 종속성이 존재하게 됩니다.
<code>mode</code>는 context를 통해 리액트 상태값으로 저장되고 전달됩니다.</p>
<p>해당 <code>mode</code> 변수가 변경되면 리렌더링이 발생하고 <code>App</code> 은 이전 스냅샷에 의존하지 않고 새 스냅샷을 생성합니다. 
리액트는 이 컴포넌트가 <code>UserContext</code> 를 사용하고 있는걸 알기 때문에 <code>mode</code>를 props인 것처럼 취급합니다.</p>
<h3 id="참고-문헌">참고 문헌</h3>
<p><a href="https://lasbe.tistory.com/166">https://lasbe.tistory.com/166</a>
<a href="https://velog.io/@cnsrn1874/react-query-and-react-context">https://velog.io/@cnsrn1874/react-query-and-react-context</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트의 타입추론🤖 (type inference)]]></title>
            <link>https://velog.io/@function_dh/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%B6%94%EB%A1%A0-%EB%98%91%EB%98%91%ED%95%9C-%EC%B2%AD%EB%85%84</link>
            <guid>https://velog.io/@function_dh/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%B6%94%EB%A1%A0-%EB%98%91%EB%98%91%ED%95%9C-%EC%B2%AD%EB%85%84</guid>
            <pubDate>Tue, 12 Mar 2024 12:34:01 GMT</pubDate>
            <description><![CDATA[<h2 id="타입추론">타입추론</h2>
<p> 기본적으로 타입스크립트 Language server에 의해 타입이 추론되게 됩니다</p>
<ul>
<li>함수 파라미터의 기본값, 반환값 지정</li>
<li>변수를 선언해서 값을 할당 할 때</li>
</ul>
<p><img src="https://velog.velcdn.com/images/function_dh/post/50bd790e-01d0-4092-b766-8126f0a8abe9/image.png" alt=""></p>
<p> 이미지와 같이 test 변수에서 어떤 타입을 return 해주는지 자동으로 보여줍니다.</p>
<h3 id="그러면-여기서-number를-전달하면-어떻게-될까">그러면 여기서 Number를 전달하면 어떻게 될까?</h3>
<ul>
<li><p><code>test(123)</code> 실행시 타입 에러가 발생한다.</p>
</li>
<li><p>주어진 코드에서 <strong><code>test</code></strong> 함수는 타입이 <code>String</code>인 <strong><code>text</code></strong> 파라미터를 가지고 있습니다.</p>
</li>
<li><p>함수를 호출할 때 아무런 값이 전달되지 않으면 기본값 <strong><code>&#39;text&#39;</code></strong>가 사용됩니다. 그리고 아무런 동작없이 파라미터를 그대로 return 하기 때문에 return값을 <code>String</code> 타입으로 추론하게 됩니다.</p>
</li>
<li><p>따라서 이 경우에는 함수가 String을 return하기를 기대하고 있기 때문에 Number 타입을 전달할 수 없습니다.</p>
<pre><code class="language-jsx">  // return 값이 string으로 고정되기 때문에 Number값을 전달해도 에러가 발생하지 않는다
  const test = (text = &#39;text&#39;) =&gt; {
    return String(text)
  }</code></pre>
</li>
</ul>
<h3 id="제네릭-에서의-사용">제네릭 에서의 사용</h3>
<p>제네릭에 전달한 타입 값으로 타입을 추론하여 사용하는 것이 기본 개념입니다.</p>
<pre><code class="language-jsx">interface Dropdown&lt;T&gt; {
    value:T;
    title:string;
}

interface DetailedDropdown&lt;K&gt; extends  Dropdown&lt;K&gt; {
    description:string;
    tag:K;
}

// 예시
const test:DetailedDropdown&lt;string&gt; = {
    description:string;
    tag:string;
    value:string;
    title:string;
}</code></pre>
<p>extends에 의해 확장되어 Dropdown의 전달된 K도 DetailedDropdown에 전달한 타입으로 추론됩니다.</p>
<h3 id="best-common-type"><strong>Best Common Type</strong></h3>
<p>타입을 추론할 때 number 일수도 있고? string 일수도 있고?
이럴 경우에는 가장 근접한 타입을 추론해서 그 값들을 표현해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/function_dh/post/b72a6bb8-654b-4272-b7ba-02fef04e94d3/image.png" alt=""></p>
<p>위에 배열 안에 값으로 number타입과 null 타입이 있는 걸 알 수 있고 가장 근접한 타입을 추론해서 <code>(number | null)[]</code> 타입이라고 명시해주고 있습니다.</p>
<h3 id="문맥상의-타이핑contextual-typing"><strong>문맥상의 타이핑(Contextual Typing)</strong></h3>
<p>타입스크립트에서 타입을 추론하는 또 하나의 방식은 바로 코드의 위치(문맥)로 타입을 결정하는 것입니다.</p>
<pre><code class="language-jsx">window.onmousedown = function(mouseEvent) {
  console.log(mouseEvent.button);   //&lt;- OK
  console.log(mouseEvent.studyveryhard); //&lt;- Error!
};</code></pre>
<ol>
<li>위 코드를 타입스크립트 서버 관점에서 보면 <code>window.onmousedown</code>에 할당되는 함수의 타입을 추론하기 위해 <code>window.onmousedown</code> 타입을 검사합니다. </li>
<li>타입 검사가 끝나고 나면 함수의 파라미터 타입이 <code>MouseEvent</code>와 연관이 있다고 추론하기 때문에 mouseEvent 인자에 button 속성은 있지만 studyveryhard 속성은 없다고 결론을 내립니다.</li>
<li><code>MouseEvent</code> 타입으로 추론을 하는 이유는 onmousedown에 설정된 타입을 보면 알 수 있습니다.</li>
</ol>
<p>문맥상 판단하기 때문에 아래 onscroll 메소드에서는 에러가 발생합니다.</p>
<pre><code class="language-jsx">// uiEvent로 타입을 판단하기 때문에 에러가 발생한다.
window.onscroll = function (uiEvent) {
  console.log(uiEvent.button); //&lt;- Error!
}</code></pre>
<h3 id="타입스크립트language-server">타입스크립트(Language Server)</h3>
<p>타입스크립트(Language Server) 서버는 통합 개발 환경(IDE) 같은 클라이언트 도구들에게 타입스크립트 코드의 분석, 오류 검출 및 리팩토링과 같은 기능을 제공합니다.</p>
<p>Language Server는 여러 언어와 툴이 상호 작용할 수 있도록 설계된 표준화된 인터페이스인 Language Server Protocol(LSP)를 구현합니다.</p>
<p>LSP는 클라이언트와 서버 간의 통신을 위한 명세로, 언어 서버는 LSP를 따라 구현함으로써 여러 클라이언트에서 동일한 언어 서버를 사용할 수 있게 됩니다.</p>
<p>Language Server의 장점은 클라이언트와 서버 간의 표준화된 프로토콜을 사용하여 다양한 개발 환경에서 동일한 언어 서버를 사용할 수 있게 해줍니다. 따라서 여러 편집 툴에서 동일한 언어 서버를 사용하여 일관된 개발 경험을 제공할 수 있습니다.</p>
<h3 id="타입스크립트language-server-동작-순서">타입스크립트(Language Server) 동작 순서</h3>
<ol>
<li>편집 툴에서 TS 파일을 열면 해당 파일에 대한 프로젝트를 초기화 하도록 tsserver에 요청을 보냅니다. 
이 요청에는 파일 경로와 TypeScript 버전 및 구성 옵션이 포함됩니다.</li>
<li>tsserver는 tsconfig.json 파일에서 프로젝트 구성을 읽고 해당 파일에 대한 TypeScript 프로젝트를 설정합니다.</li>
<li>편집기에서 입력하면 코드 완성, 코드 탐색 및 심볼 검색과 같은 기능을 위해 tsserver에 요청을 보냅니다.</li>
<li>tsserver는 TypeScript 코드를 분석하고 요청된 정보가 포함된 응답을 편집기로 다시 보냅니다.</li>
<li>파일을 저장하면 tsserver가 코드를 다시 확인하고 오류나 경고를 편집기에 전달합니다.</li>
</ol>
<h3 id="여담">여담..</h3>
<ul>
<li>이직 준비가 어느정도 마무리 되고 나면 &#39;우아한 타입스크립트 with 리액트&#39; 책을 정독하면 좋을 것 같다는 생각이 많이 든다.</li>
<li>실제로 업무에 있어서 타입을 좀 더 맛있게(?) 쓰고 싶은데 정해져 있는 일정에 맞춰서 작업을 할 때마다 뭔가 스스로 만족 스럽지 못한 적이 많아서 추후에라도 개선을 진행해서 타입 관련해서 보완하면 좋을 것 같다.</li>
</ul>
<h3 id="참고문헌">참고문헌</h3>
<ul>
<li><a href="https://velog.io/@fromzoo/%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0-Type-Inference">https://velog.io/@fromzoo/타입-추론-Type-Inference</a></li>
<li><a href="https://velog.io/@dessin/tsserver">https://velog.io/@dessin/tsserver</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트의 변수 - 제네릭 타입]]></title>
            <link>https://velog.io/@function_dh/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B3%80%EC%88%98-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@function_dh/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B3%80%EC%88%98-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85</guid>
            <pubDate>Tue, 05 Mar 2024 13:27:25 GMT</pubDate>
            <description><![CDATA[<h2 id="제네릭-타입">제네릭 타입</h2>
<p>기존에 고정된 타입을 넘어
유니온 타입을 넘어…
함수 오버로딩을 넘어…
컴포넌트를 재사용 가능하게 구축하고 싶다면?</p>
<p>여러가지 타입 사용이 가능한 제네릭 타입을 사용해보자!</p>
<pre><code class="language-jsx">// 제네릭 타입으로 선언한 함수
function getText&lt;T&gt;(text: T): T {
  return text;
}</code></pre>
<ul>
<li>일반적인 타입과 다르게 함수의 선언 시점이 아니라 <strong>사용 시점</strong>에 타입을 선언해줍니다.</li>
<li><strong><code>T</code></strong>는 제네릭 타입 파라미터로, 함수가 호출될 때 실제 타입으로 대체됩니다.</li>
<li>쉽게 제네릭 타입은 <strong>함수의 파라미터</strong>를 떠올리면 됩니다.</li>
<li>함수의 반환값은 입력된 인자의 타입과 동일합니다.</li>
</ul>
<pre><code class="language-jsx">// 함수를 호출할 때 string 타입으로 선언
getText&lt;string&gt;(&#39;test&#39;);

// 아래와 같은 결과를 가지게 된다!
function getText&lt;string&gt;(text: string): string {
  return text;
}</code></pre>
<h3 id="그래서-뭐가-좋아">그래서 뭐가 좋아?</h3>
<ol>
<li>런타임 시전이 아닌 컴파일 시점에서 사전에 에러 방지가 가능합니다.</li>
<li>타입이 정해져 있지 않기 때문에 재사용성이 높겠죠?</li>
<li>개발을 할 때 IDE에서 타입을 미리 알고 있기 때문에 개발하기 편합니다.</li>
</ol>
<h3 id="사용시-주의-사항">사용시 주의 사항</h3>
<p>타입을 배열로 받을 경우 제네릭 타입은 <code>T[], Array&lt;T&gt;</code> 로 해주어야 합니다.</p>
<p>나머지 타입이나 객체의 경우 따로 처리하지 않아도 됩니다.</p>
<p>배열 타입만 따로 선언해야 하는 이유는 엄격한 타입 체크를 위해서 입니다. </p>
<p>만약 <strong><code>T</code></strong>만으로 배열인지 아닌지를 구분한다면, 배열이 아닌 값도 전달할 수 있고, 그로 인해 예기치 못한 동작이 발생할 수 있기 때문입니다.</p>
<pre><code class="language-jsx"> // 배열은 length가 있기 때문에 에러가 발생하지 않음
function getArray&lt;T&gt;(arg: T[]): T[] {
   console.log(arg.length);
   return arg;
}</code></pre>
<h3 id="그러면-any랑-어떻게-달라">그러면 any랑 어떻게 달라?</h3>
<p>any의 경우 함수에 어떤 값이 전달되었고 어떤 값이 반환 되었는지 알 수없지만</p>
<p>제네릭 타입의 경우 함수를 호출할 때 타입을 전달하기 때문에 명확히 알 수 있고 타입 추론으로 타입 체크도 가능합니다.</p>
<pre><code class="language-jsx">// 타입추론 짧은 예시 
// 컴파일러에서 전달되는 인수의 값이 Number 타입인 걸 알 수 있다.
getText(123);</code></pre>
<h3 id="interface에서의-활용">interface에서의 활용</h3>
<p>option의 타입이 일정하지 않다는 가정하에 아래와 같이 제네릭 타입을 설정하여 활용할 수 있습니다.</p>
<pre><code class="language-jsx">// option에는 제네릭 타입으로 선언되어 어떤 타입이든 들어올 수 있다
interface IExample&lt;T&gt; { 
   name: string;
   price: number;
   option: T;
}

const EX1: IExample&lt;string&gt; = {
   name: &#39;s20&#39;,
   price: 900,
   option: &#39;good&#39;,
}

const EX2: IExample&lt;{ color: string; coupon: boolean }&gt; = {
   name: &#39;s21&#39;,
   price: 1000,
   option: { color: &#39;read&#39;, coupon: false },
};</code></pre>
<h3 id="제네릭-제약-조건-with-extends">제네릭 제약 조건 with extends</h3>
<p>위에서 설명 했듯이 어떠한 타입을 선언하든 사용이 가능하지만 반대로 타입을 제한할 수 있는 기능도 존재합니다.</p>
<p>일반적으로 interface에서 extends를 사용하면 타입의 확장이 이루어 지지만 제네릭 타입의 경우 extends를 사용한 <code>타입의 종류가 제한</code>되게 됩니다.</p>
<pre><code class="language-jsx">type numOrStr = number | string;

// 제네릭에 적용될 타입에 number | string 만 허용
function identity&lt;T extends numOrStr&gt;(p1: T): T {
   return p1;
}

identity(1);
identity(&#39;a&#39;);

identity(true); // 에러!!!!!
identity([]); // 에러!!!!!</code></pre>
<h3 id="속성-제약-활용">속성 제약 활용</h3>
<p>특정한 프로퍼티를 사용해야 하는 경우를 가정하면 타입의 제약 활용이 중요하다고 생각합니다.</p>
<pre><code class="language-jsx">// 아래와 같이 선언하면 에러가 발생합니다.
function getArray&lt;T&gt;(arg: T): T {
   console.log(arg.length);
   return arg;
}</code></pre>
<p>위에 함수를 예시로 제네릭 타입에 어떤 타입도 들어갈 수 있는데 사람의 입장에서 생각하면 뭐든지 가능하니까 당연한거 아닐까? 생각 할 수 있지만 </p>
<p>타입을 결정하는 컴파일러의 경우 타입을 전혀 알 수 없기 때문에 length라는 프로퍼티를 사용할 수가 없습니다.</p>
<pre><code class="language-jsx">function getArray&lt;T&gt;(arg: T): T {
    if(typeof arg === &quot;string&quot; || Array.isArray(arg)) {
        console.log(arg.length);
    }
    return arg;
}</code></pre>
<p>타입 가드를 통해서 방지도 가능하지만 length가 아닌 직접 생성한 프로퍼티의 경우에는 사용이 불가능 합니다.</p>
<pre><code class="language-jsx">interface isOption{
   option: number;
}

function getArray&lt;T extends isOption&gt;(arg: T): T {
   console.log(arg.option);
   return arg;
}

getArray({ option: 10, title: &#39;text&#39; });
getArray(3); // 에러 발생!</code></pre>
<p>제네릭 타입은 반드시 <code>{ option: number }</code> 프로퍼티가 포함되어야 하는 제약 조건을 쉽게 선언할 수가 있습니다.</p>
<h3 id="매개변수-제약조건-활용">매개변수 제약조건 활용</h3>
<p>제네릭도 여러개 사용이 가능합니다.
일반적인 방법 말고 응용이 더 중요하겠죠?</p>
<pre><code class="language-jsx">// 전달 받은 객체의 value를 리턴해주는 함수
function getProperty&lt;T, K extends keyof T&gt;(obj: T, key: K) {
   return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, &#39;a&#39;); // 성공
getProperty(x, &#39;m&#39;); // 오류: 인수의 타입 &#39;m&#39; 은 &#39;a&#39; | &#39;b&#39; | &#39;c&#39; | &#39;d&#39;에 해당되지 않음.

// keyof 를 통해 전달 유니온 타입으로 변환된다.
function getProperty&lt;T, K extends &#39;a&#39; | &#39;b&#39; | &#39;c&#39; | &#39;d&#39;&gt;(obj: T, key: K) {
   return obj[key];
}
</code></pre>
<h3 id="함수-제약조건">함수 제약조건</h3>
<p>전달 받는 파라미터가 <code>콜백함수</code>일 경우 제네릭 타입 설정이 가능합니다.</p>
<p>여기서 유추 할 수 있는게 함수 자체도 타입이 될 수 있다는 점 입니다!</p>
<pre><code class="language-jsx">function translate&lt;T extends (a: string) =&gt; number, K extends string&gt;(x: T, y: K): number {
  return x(y)
}

// 문자숫자를 넣으면 정수로 변환해주는 함수
const num = translate((a) =&gt; +a, &#39;10&#39;)

console.log(&#39;num: &#39;, num) // num : 10</code></pre>
<h3 id="제네릭-함수-타입">제네릭 함수 타입</h3>
<p>위에서는 전달하는 파라미터를 제어해서 제약을 주는 부분을 확인했고 파라미터 뿐만 아니라 함수 타입 구조를 설정하는 것도 가능합니다.</p>
<pre><code class="language-jsx">// 제네릭 함수 타입 구조
interface GenericIdentityFn {
  &lt;T&gt;(arg: T): T 
}

const identity: GenericIdentityFn = (arg) =&gt; {
  return arg
}

identity&lt;number&gt;(100)</code></pre>
<p>함수를 할당 할때 제네릭을 결정하는 방법도 가능합니다.</p>
<pre><code class="language-jsx">interface GenericIdentityFn&lt;T&gt; {
  (arg: T): T

const identity: GenericIdentityFn&lt;number&gt; = (arg) =&gt; {
  return arg
}

identity(100)</code></pre>
<h3 id="참고문헌">참고문헌</h3>
<ul>
<li><a href="https://typescript-kr.github.io/pages/generics.html">https://typescript-kr.github.io/pages/generics.html</a></li>
<li><a href="https://joshua1988.github.io/ts/guide/generics.html#%EC%A0%9C%EB%84%A4%EB%A6%AD-generics-%EC%9D%98-%EC%82%AC%EC%A0%84%EC%A0%81-%EC%A0%95%EC%9D%98">https://joshua1988.github.io/ts/guide/generics.html#제네릭-generics-의-사전적-정의</a> - 타입스크립트 핸드북</li>
<li><a href="https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Generic-%ED%83%80%EC%9E%85-%EC%A0%95%EB%B3%B5%ED%95%98%EA%B8%B0#%EA%B3%A0%EA%B8%89_%ED%83%80%EC%9E%85_-_%EC%A0%9C%EB%84%A4%EB%A6%ADgenerics">https://inpa.tistory.com/entry/TS-📘-타입스크립트-Generic-타입-정복하기#고급<em>타입</em>-_제네릭generics</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[처음 만난 react-query (리액트 쿼리) 개념 정리]]></title>
            <link>https://velog.io/@function_dh/react-query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@function_dh/react-query-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 19 Feb 2024 11:49:14 GMT</pubDate>
            <description><![CDATA[<h1 id="리액트-쿼리">리액트 쿼리</h1>
<h3 id="1-리액트-쿼리란">1. 리액트 쿼리란?</h3>
<ul>
<li>React 환경에서 비동기 Query 과정을 도와주는 라이브러리<ul>
<li>Query가 뭔가요???
→ 데이터 조회나 조작을 수행하기 위한 질문</li>
</ul>
</li>
</ul>
<h3 id="2-왜-사용하는데">2. 왜 사용하는데?</h3>
<ul>
<li>주로 서버에서 비동기적으로 데이터를 가져오거나 업데이트 하는데 사용</li>
<li>리액트에서 서버 상태를 불러오고, <strong>캐싱</strong>해서 지속적으로 동기화</li>
<li>리액트 컴포넌트 내부에서 간단하게 사용 가능</li>
<li>리액트 쿼리에서 제공하는 캐싱, Window Focus Refetching 등 다양한 기능을 활용하여 API 요청과 관련된 번잡한 작업 없이 “핵심 로직”에 집중 가능</li>
</ul>
<h3 id="캐싱">캐싱</h3>
<ul>
<li>캐싱을 통한 복사본이 존재<ul>
<li>따라서 데이터 재접근 속도가 높음</li>
<li>데이터가 동일할 경우 데이터 호출 방지</li>
<li>최신의 데이터를 fresh한 데이터, 기존의 데이터를 stale한 데이터라고 말하는 것을 기억하자</li>
</ul>
</li>
</ul>
<h3 id="🤔-최신-데이터인지는-어떻게-알어">🤔 최신 데이터인지는 어떻게 알어?</h3>
<ul>
<li>실제로 서버 데이터를 불러와 캐싱하고 그 이후에 서버 데이터가 변경 되었다면??? 
⇒ 매우 곤란</li>
<li>따라서 데이터 갱신이 필요하다!</li>
</ul>
<h3 id="그러면-언제-데이터를-갱신해">그러면 언제 데이터를 갱신해???</h3>
<ol>
<li>화면을 보고 있을 때</li>
<li>페이지의 전환이 일어났을 때</li>
<li>이벤트가 발생해 데이터를 요청할 때</li>
</ol>
<p>따라서 리액트 쿼리 옵션에서는 당연히 지원을 하고 있다</p>
<pre><code class="language-jsx">refetchOnWindowFocus, // 브라우저에 포커스가 들어온 경우
refetchOnMount, // 새로운 컴포넌트 마운트가 발생한 경우
refetchOnReconnect, // 네트워크 재연결이 발생한 경우
staleTime, //default: 0
gcTime, //default: 5분 (60 * 5 * 1000)</code></pre>
<h3 id="staletime-gctime-이건-뭔데요"><code>staleTime,</code> <code>gcTime</code> 이건 뭔데요?</h3>
<p><strong>staleTime</strong></p>
<ul>
<li>staleTime은 데이터가 fresh → stale 상태로 변경되는 데 걸리는 시간</li>
<li>fresh 상태일 때는 트리거가 발생해도 Refetch가 일어나지 않는다!</li>
<li>기본값이 0이라 따로 설정하지 않으면 트리거 발생시 무조건 Refetch 발생</li>
<li>데이터 자체가 fresh한 상태인지 아닌지 여부 체크용도 (stale 상태면 refetch가 이루어진다)</li>
</ul>
<p><strong>gcTime</strong></p>
<ul>
<li>캐싱이 된 데이터가 유지되는 시간이다 
→ 시간이 지나면? 가비지 콜렉터로 수집되어 메모리에서 삭.제.</li>
<li>컴포넌트가 unmount 되면 사용된 데이터는 inactive 상태로 바뀌고, 이때 데이터는 gcTime만큼 유지한다</li>
<li>gcTime 유지 시간동안 해당 데이터를 사용하는 컴포넌트가 다시 mount되면, 새로운 데이터를 fetch해오는 동안 캐싱된 데이터를 보여준다.</li>
<li>즉, 캐싱된 데이터를 계속 보여주는게 아니라 fetch하는 동안 <strong>임시로</strong> 보여준다는 것</li>
</ul>
<p>추가로 사용자가 특정 이벤트가 발생했을 때 Refetching을 하도록 설정 가능</p>
<h3 id="client-데이터와-server-데이터-간의-분리한다"><strong>Client 데이터와 Server 데이터 간의 분리한다!</strong></h3>
<ul>
<li>Client Data: 모달 관련 데이터, 페이지 관련 데이터 등등..
→ 자체적으로 사용하는 상태값이라 생각하면 편하겠죠?</li>
<li>Server Data: 사용자 정보, 비즈니스 로직 관련 정보 등등…
→ <strong>비동기 API 호출을 통해 불러오는 데이터</strong></li>
</ul>
<h3 id="그럼-왜-기존-상태관리를-사용-안하는데">그럼 왜 기존 상태관리를 사용 안하는데?</h3>
<ul>
<li><p>대부분 전역상태 라이브러리(redux, recoli)의 경우 Client 데이터를 관리하는데 로직이 집중되어있기 때문!
→ Server 데이터까지 효율적으로 관리하기에는 한계</p>
</li>
<li><p>이러한 문제에 대한 해결책을 보도록 하자</p>
</li>
<li><p>queryKey를 통하여 내부적으로 데이터 재요청, 캐싱, 쿼리를 공유하기 위해 사용한다.</p>
<pre><code class="language-jsx">  // 컴포넌트 내에서 isPending과 error 상태를 통해
  // 현재 쿼리의 상태를 쉽게 처리할 수 있다!
  function Example() {
    const { isPending, error, data } = useQuery({
      queryKey: [&#39;repoData&#39;],
      queryFn: () =&gt;
        fetch(&#39;https://api.github.com/repos/TanStack/query&#39;).then((res) =&gt;
          res.json(),
        ),
    })

    if (isPending) return &#39;Loading...&#39;

    if (error) return &#39;An error has occurred: &#39; + error.message

    return (
      &lt;div&gt;
        &lt;h1&gt;{data.name}&lt;/h1&gt;
        &lt;p&gt;{data.description}&lt;/p&gt;
        &lt;strong&gt;👀 {data.subscribers_count}&lt;/strong&gt;{&#39; &#39;}
        &lt;strong&gt;✨ {data.stargazers_count}&lt;/strong&gt;{&#39; &#39;}
        &lt;strong&gt;🍴 {data.forks_count}&lt;/strong&gt;
      &lt;/div&gt;
    )
  }</code></pre>
</li>
<li><p>결론적으로 <strong>Client 데이터는 상태 관리 라이브러리가 관리</strong></p>
</li>
<li><p><strong>Server 데이터는 리액트 쿼리가 관리하는 구조</strong></p>
</li>
<li><p>짜잔~ <strong>Client 데이터와 Server 데이터를 온전하게 분리!!</strong></p>
</li>
</ul>
<h3 id="참고-문헌">참고 문헌</h3>
<ul>
<li><a href="https://velog.io/@kandy1002/React-Query-%ED%91%B9-%EC%B0%8D%EC%96%B4%EB%A8%B9%EA%B8%B0">https://velog.io/@kandy1002/React-Query-푹-찍어먹기</a></li>
<li><a href="https://velog.io/@devjooj/React-React-Query-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0-Queries-%EA%B0%9C%EB%85%90%ED%8E%B8">https://velog.io/@devjooj/React-React-Query-사용-이유-Queries-개념편</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue composition API를 사용해보자]]></title>
            <link>https://velog.io/@function_dh/Vue-composition-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@function_dh/Vue-composition-API%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 24 Jan 2022 03:32:01 GMT</pubDate>
            <description><![CDATA[<h3 id="소개">소개</h3>
<p>Vue3에서 제시하는 새로운 방식의 컴포넌트 구조를 소개하려고 합니다.
Vue2에서도 적용이 가능하고 타입스크립트와 함께 사용하기도 용이하기 때문에 실무에 적용하는 것도
생각보다는 어렵지 않았습니다! 🥳</p>
<p>그럼 어떤 부분이 달라졌는지 간략하게 설명 후 기존 코드를 리팩토링 하면서 설명하겠습니다.
일단 공식 문서에서 composition api를 사용하는 이유는 기존의 컴포넌트 방식에서는
규모가 커졌을 경우 논리적인 관점에서 봤을 때 코드를 연속 적으로 점프해서 확인해야 하기 때문에
가독성이 떨어진다고 명시하고 있습니다.</p>
<p><img src="https://images.velog.io/images/function_dh/post/38982539-7cbe-4980-9422-8ecb2c851474/img.png" alt=""> 쉽게 생각해서 같은 색상으로 표시된 부분이 같은 기능을 담당하고 있다고 생각하면 됩니다. 
보통 작업을 하면서 data, computed, methods 등 영역들이 점차 늘어나면서 같은 컴포넌트 내에서도 기능에 따라 확인 할 부분이 나눠지게 됩니다.
하지만 composition api에서는 함수별로 나눠서 라이프 싸이클을 쪼개서 사용할 수도 있고 기능별로 나누어 표현할 수 있기 때문에 어떠한 기능을 확인 할 때 한 곳만 확인할 수 있게 됩니다!
따라서 이미지와 같이 가독성을 높이기 위해 사용이 가능하고 코드의 공유 및 재사용에 용이하기 때문에 해당 방식을 추천하고 있습니다.</p>
<h3 id="설치">설치</h3>
<pre><code>npm install @vue/composition-api</code></pre><p>npm 통하여 해당 패키지를 설치하면 vue3에서 제공하는 composition api를 vue2에서 쉽게 사용할 수 있습니다.
설치후 main.js에 해당 패키지를 등록해야 vue2에서 정상 적용 됩니다!</p>
<pre><code>import VueCompositionApi from &#39;@vue/composition-api&#39;
Vue.use(VueCompositionApi)</code></pre><h3 id="사용-방식">사용 방식</h3>
<p><img src="https://images.velog.io/images/function_dh/post/d1b84a5e-1991-4dc5-820e-ca6ac22e4a8a/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.04.49.png" alt="">버튼 클릭시 현재 count가 증가하거나 감소하는 간단한 코드가 있습니다. 이미지에서는 1개의 기능밖에 없지만 실제 개발을 진행 할 때는 많이 기능이 있다고 가정했을 때 count data가 어떤 동작을 하는지 파악하려면 기능이 쪼개져 작성되어 있기 때문에 해당 컴포넌트를 계속 찾아봐야 됩니다... 
<img src="https://images.velog.io/images/function_dh/post/2633070d-7e92-48f1-b834-b5729f736fe0/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-01-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.21.42.png" alt="">따라서 위에 코드를 변경하면 위와 같이 함수 형식으로 한눈에 확인 할 수 있게 되는데 countCode 변수를 보면
안에 <strong>count(data), currentCount(computed), upCount,downCount(methods)</strong> 로 모두 담겨있는 걸 확인 할 수 있습니다! 만약 count관련 수정이 있으면 해당 함수만 확인하면 되겠죠? 😎</p>
<p>이런식으로 react hook처럼 사용이 가능하기 때문에 좀 더 재사용 측면에서 뛰어나고 코드의 가독성도 대폭 향상되었습니다. 지금 바로 프로젝트에 도입 해보는건 어떨까요?</p>
<h3 id="참고-사이트">참고 사이트</h3>
<p><a href="https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api">https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api</a>
<a href="https://velog.io/@ausg/Vue2-%EC%93%B0%EC%84%B8%EC%9A%94-Composition-API-%ED%95%9C%EB%B2%88-%EB%93%9C%EC%85%94%EB%B3%B4%EC%84%B8%EC%9A%94">https://velog.io/@ausg/Vue2-쓰세요-Composition-API-한번-드셔보세요</a>
<a href="https://geundung.dev/102">https://geundung.dev/102</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue] scss 추가 및 전역 스타일 설정]]></title>
            <link>https://velog.io/@function_dh/Vue-scss-%EC%B6%94%EA%B0%80-%EB%B0%8F-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@function_dh/Vue-scss-%EC%B6%94%EA%B0%80-%EB%B0%8F-%EC%A0%84%EC%97%AD-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 23 Jun 2021 03:54:33 GMT</pubDate>
            <description><![CDATA[<h3 id="설명">설명</h3>
<p>Vue cli3 이상부터는 프로젝트를 생성할 때 scss 관련 기능을 쉽게 추가할 수 있습니다. 하지만 프로젝트 진행 중간에 scss를 적용할 일이 생겨 해당 내용을 정리하였습니다 🙂</p>
<h3 id="scss-패키지-설치">scss 패키지 설치</h3>
<p>제일 먼저 해당 package에 node-sass와 sass-loader를 설치해줍니다.</p>
<pre><code class="language-jsx">npm install --save-dev node-sass sass-loader</code></pre>
<p>주의 할 점은 vue 2.6 버전에서는 sass-loader 10버전 대를 지원하기 때문에 그보다 버전이 높다면 10으로 버전을 내리고 재 설치 해주시면 됩니다.</p>
<h3 id="사용법">사용법</h3>
<p>설치를 완료했다면 끝입니다! 이제 바로 컴포넌트 내에서 scss를 사용할 수 있습니다. 간단한 설치 만으로 사용이 가능한 이유는 vue-loader에서 기본으로 설정되어 있는 webpack 설정 때문에 패키지 설치 후 컴포넌트 내에서 lang 속성을 지정해주면 자동으로 Loader를 사용하여 바로 사용할 수 있습니다. </p>
<pre><code class="language-jsx">// 컴포넌트 내에 lang 속성으로 scss를 명시 해줍니다.
// 스타일 내부 scss 파일 import하는 방법
// 경로에서 @의 경우 /src와 같은 의미입니다
&lt;style lang=&quot;scss&quot;&gt;
  @import &quot;@/asstes/scss/파일명&quot;;
&lt;/style&gt;</code></pre>
<h3 id="전역-스타일-및-변수-설정">전역 스타일 및 변수 설정</h3>
<p>변수를 담아둔 scss파일을 매번 컴포넌트에서 불러와서 사용하는 것은 매우 불편한 방법이고 효율적이지 않습니다. 따라서 자주 사용하는 변수나 reset스타일, mixin같은 경우 전역 스타일를 설정하여 사용이 가능합니다.</p>
<p>설정 방법은 프로젝트 최상단에 vue.config.js 파일을 생성하여 webpack 설정을 추가할 수 있습니다. sass-loader 버전에 따라서 설정법이 조금씩 다른데 전체적인 구조는 같지만 8버전에서는 prependData로 선언해야 하고 그 이상의 버전에서는 아래 처럼 additionalData를 선언하면 전역으로 scss이 적용되게 됩니다.</p>
<pre><code class="language-jsx">// webpack 설정을 추가
module.exports = {
  css : {
    loaderOptions : {
      sass : {
        additionalData: `
          @import &quot;@/assets/scss/abstracts/abstracts.scss&quot;;
        `
      }
    }
  }
}</code></pre>
<pre><code class="language-jsx">// 예시
// abstracts.scc
$TEXT_DEAFULT : #333;

// 다른 컴포넌트
&lt;style lang=&quot;scss&quot;&gt;
  p {
    color : $TEXT_DEAFULT
  }
&lt;/style&gt;</code></pre>
<h3 id="vue-cli-2버전의-경우">vue cli 2버전의 경우</h3>
<p>만약 cli2 버전의 경우 webpack.confing.js 에서 설정을 변경해줘야 합니다.</p>
<pre><code class="language-jsx">{
  test: \/.scss&amp;/,
  use: [
    &quot;vue-styles-loader&quot;,
    &quot;css-loader&quot;,
    {
      loader: &quot;scss-loader&quot;,
      options: {
        data: `
          @import &quot;@/assets/scss/abstracts/abstracts.scss&quot;;
        `
      }
    }
  ]</code></pre>
<h3 id="참고-사이트">참고 사이트</h3>
<p><a href="https://m.blog.naver.com/mgveg/221900939600">[Vue.js] Vue CLI 4에서 SCSS 적용 및 사용</a></p>
<p><a href="https://yilpe93.github.io/vue/vue-set-scss/">Vue. vue-cli에서 SCSS 적용과 Global SCSS 적용</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Vue] 스타일 가이드 - 필수]]></title>
            <link>https://velog.io/@function_dh/Vue-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%80%EC%9D%B4%EB%93%9C-%ED%95%84%EC%88%98</link>
            <guid>https://velog.io/@function_dh/Vue-%EC%8A%A4%ED%83%80%EC%9D%BC-%EA%B0%80%EC%9D%B4%EB%93%9C-%ED%95%84%EC%88%98</guid>
            <pubDate>Mon, 07 Jun 2021 08:47:31 GMT</pubDate>
            <description><![CDATA[<p>Vue 공식 문서 기반으로 작성한 <strong>필수 스타일 가이드</strong>에 대한 내용 정리입니다 🙂</p>
<p>자세한 내용은 공식문서를 참고 부탁드립니다.</p>
<p><a href="https://vuejs.org/v2/style-guide/index.html">Style Guide - Vue.js</a></p>
<h2 id="내용">내용</h2>
<p><strong>1. 컴포넌트의 이름은 합성어를 사용한다.</strong></p>
<ul>
<li><p>html 태그의 경우 한 단어로 태그가 구성되어 있기 때문에 충돌 방지를 위하여 컴포넌트 이름의 경우 합성어 사용을 권장하고 있습니다.</p>
<pre><code class="language-jsx">  // 나쁜 예
  &lt;main&gt;HTML 태그&lt;/main&gt;

  // HTML의 태그와 이름이 같기 때문에 충돌 문제가 생길 수 있음
  export default {
    name: &#39;main&#39;,
  }

  // 좋은 예
  export default {
    name: &#39;MainHome&#39;,
</code></pre>
</li>
</ul>
<p><strong>2. 컴포넌트의 data는 함수로 반환해야 한다.</strong></p>
<ul>
<li><p>data의 값이 객체(object)일 경우 컴포넌트를 사용할 때 같은 인스턴스를 공유하게 됩니다.
예시로 아래 TodoList 컴포넌트를 보면 todos 배열에 담긴 인스턴스 값이 모든 TodoList 컴포넌트에서 공유하게 되어 컴포넌트를 재사용하기 어렵습니다.</p>
<pre><code class="language-jsx">  // 나쁜 예 
  // 모든 인스턴스가 동일한 data를 참조
  name : &#39;TodoList&#39;
  data: {
    todos: [&#39;할일1&#39;, &#39;할일2&#39;]
  }</code></pre>
</li>
<li><p>따라서 아래 코드와 같이 각 컴포넌트에서 자체적으로 data를 관리하기 위해 함수 안에서 객체를 반환하여 고유한 data를 가지게 합니다.</p>
<pre><code class="language-jsx">  // 좋은 예
  name : &#39;TodoList&#39;
  data: function () {
    return {
        todos: [&#39;할일1&#39;, &#39;할일2&#39;]
    }
  }</code></pre>
</li>
</ul>
<p><strong>3. Props는 최대한 상세히 작성합니다.</strong></p>
<ul>
<li><p>props의 타입을 미리 파악하여 컴포넌트의 사용 방법을 보다 쉽게 사용할 수 있습니다.</p>
</li>
<li><p>또한 전달 값이 설정한 타입과 다르면 경고 메시지를 출력하기 때문에 오류를 미리 방지할 수 있습니다.</p>
<pre><code class="language-jsx">  // 나쁜 예
  props : [&#39;num&#39;, &#39;txt&#39;]

  // 좋은 예
  props : {
      num : Number,
      txt : String
  }

  // 좀 더 상세한 설정도 가능합니다.
  props : {
      num : {
          type : Number,
          default : 10,
      },
      txt : {
          type : String,
          default : &#39;텍스트 props&#39;,
      },
  }</code></pre>
</li>
</ul>
<p><strong>4. v-for를 사용 시 key를 필수로 지정해야 합니다.</strong></p>
<ul>
<li><p>서브트리 내부 컴포넌트 상태 유지를 위하여 무조건 key와 함께 사용 해야 합니다. Vue에서 구조를 예측 할 수 있게 해야 하기 때문입니다.</p>
<pre><code class="language-jsx">  // 나쁜 예
  &lt;ul&gt;
    &lt;li v-for=&quot;todo in todos&quot;&gt;
      {{ todo.text }}
    &lt;/li&gt;
  &lt;/ul&gt;

  // 좋은 예
  &lt;ul&gt;
    &lt;li
      v-for=&quot;todo in todos&quot;
      :key=&quot;todo.id&quot;
    &gt;
      {{ todo.text }}
    &lt;/li&gt;
  &lt;/ul&gt;

  // data 참고
  data: function () {
    return {
      todos: [
        {
          id: 1,
          text: &#39;텍스트1&#39;
        },
        {
          id: 2,
          text: &#39;텍스트2&#39;
        }
      ]
    }
  }</code></pre>
</li>
</ul>
<p><strong>5. v-if와 v-for를 동시에 사용하면 안됩니다.</strong></p>
<ul>
<li><p>v-for로 리스트를 반복할 때 조건을 추가하여 나타내야 하는 상황이 존재합니다.
(예시 - isActive값이 true인 경우만 노출)</p>
<pre><code class="language-jsx">  // 나쁜 예
  &lt;ul&gt;
    &lt;li
      v-for=&quot;user in users&quot;
      v-if=&quot;user.isActive&quot;
      :key=&quot;user.id&quot;
    &gt;
      {{ user.name }}
    &lt;/li&gt;
  &lt;/ul&gt;</code></pre>
</li>
<li><p>Vue에서는 v-for가 v-if보다 우선 순위가 높기 때문에 위와 같은 상황이면 반복문이 끝나고 다시 반복문을 반복하면서 v-if 조건을 체크하게 되어 성능이 떨어지게 됩니다.</p>
</li>
<li><p>따라서 예시 코드와 같이 computed를 활용하여 배열에 대한 조건을 계산하게 하면 값이 바뀔 때 즉시 렌더링 되는 부분과 더불어 효율이 좋아집니다.</p>
<pre><code class="language-jsx">  // 좋은 예
  &lt;ul&gt;
    &lt;li
      v-for=&quot;user in activeUsers&quot;
      :key=&quot;user.id&quot;
    &gt;
      {{ user.name }}
    &lt;/li&gt;
  &lt;/ul&gt;

  // filter를 사용하여 해당 조건의 새 배열을 반환 합니다.
  computed: {
    activeUsers() {
      return this.users.filter(user =&gt; {
        user.isActive
      })
    }
  }</code></pre>
</li>
</ul>
<p><strong>6. 컴포넌트 스타일 스코프</strong></p>
<ul>
<li><p>최상위 컴포넌트인 App의 경우 스타일에 대한 전역 설정을 하지만 다른 컴포넌트의 경우 scoped의 범위가 지정되어야 합니다.</p>
</li>
<li><p>라이브러리, 규모가 큰 프로젝트의 경우 사용하는 클래스 이름의 충돌을 방지할 수도 있습니다.</p>
</li>
<li><p>scoped 지정, BEM사용, style module 사용 등 선택하여 사용하는 것을 권유하고 있습니다.</p>
<pre><code class="language-jsx">  // sopced 사용
  &lt;style scoped&gt;
  .button {
    border: none;
    border-radius: 2px;
  }

  .button-close {
    background-color: red;
  }
  &lt;/style&gt;

  // module 사용
  &lt;template&gt;
    &lt;button :class=&quot;[$style.button, $style.buttonClose]&quot;&gt;X&lt;/button&gt;
  &lt;/template&gt;

  &lt;style module&gt;
  .button {
    border: none;
    border-radius: 2px;
  }

  .buttonClose {
    background-color: red;
  }
  &lt;/style&gt;

  // BEM 사용
  &lt;style&gt;
  .c-Button {
    border: none;
    border-radius: 2px;
  }

  .c-Button--close {
    background-color: red;
  }
  &lt;/style&gt;</code></pre>
</li>
</ul>
<p><strong>7. private 속성 이름 규칙</strong></p>
<ul>
<li><p>mixin이나 플러그인 등 개인 속성에 대한 규칙의 접두사로  $_ 를 함께 사용하는 것을 권장합니다. 또한 충돌이 생길 만한 이름은 사용하지 않습니다.</p>
</li>
<li><p>Vue에서는 _(언더바) 접두사를 사용하여 개인 속성을 정의하기 때문에 인스턴스 속성의 이름을 정의 할 때 접두사로 사용하지 않는 것이 좋습니다. 현재 사용하고 있지 않더라도 이후 버전에서 추가 될 수 있기 때문에 권장하지 않습니다.</p>
</li>
<li><p>또한 $의 경우도 Vue에서 사용자에게 노출되는 특수 인스턴스의 속성이기 때문에 개인적인 속성의 접두사로 사용하는 것은 좋지 않습니다.</p>
<pre><code class="language-jsx">  // 나쁜 예
  var myGreatMixin = {
    methods: {
      _update: function () {  // _ 언더바 사용
      },
          $update: function () {  // $ 사용
      },
          $_update:: function () {  // 단순한 이름을 사용하여 충돌 가능성 (update)
      },
    }
  }

  // 좋은 예
  var myGreatMixin = {
    methods: {
      $_myGreatMixin_update: function () { // 충돌이 나지 않는 이름 및 $_ 접두사 사용
      }
    }
  }

  // 조금 더 좋은 예
  var myGreatMixin = {
    methods: {
      publicMethod() {
        myPrivateFunction()
      }
    }
  }

  function myPrivateFunction() {
    // ...
  }

  export default myGreatMixin</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[IE NodeList forEach 메소드 사용시 오류 해결]]></title>
            <link>https://velog.io/@function_dh/IE-NodeList-forEach-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@function_dh/IE-NodeList-forEach-%EB%A9%94%EC%86%8C%EB%93%9C-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 27 May 2021 01:04:02 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>ie에서 document.querySelectorAll()을 담은 객체에 forEach 메소드를 사용하면 에러가 노출되어 해결 방법을 정리 하였습니다. </p>
<p>기존 ForEach 사용법은 Element 객체의 프로토타입 메서드인 querySelectorAll이 NodeList를 반환하고, NodeList 객체의 프로토타입 메소드인 forEach를 이용한 것입니다. 하지만 ie에서 실행하면 해당 메소드를 실행 할 수 없다고 나오는데 이유는 ie는 NodeList 객체에 forEach 메서드를 지원하지 않습니다.😡</p>
<h2 id="해결">해결</h2>
<p>ES2015에서 추가된 Array.from 메서드를 사용하면 NodeList나 HTMLCollection을 순수 배열로 반환 받을 수 있습니다.</p>
<pre><code class="language-jsx">// Array.from 메소드 사용
let storeItem = document.querySelectorAll(&#39;.store-item&#39;)

Array.from(storeItem).forEach(function(el) {
  el.classList.remove(&#39;active&#39;)
})

// 정말 배열인지 확인
Array.isArray(storeItem)  // false
Array.isArray(Array.from(storeItem))  // true</code></pre>
<p>위의 코드처럼 메소드를 사용하면 NodeList가 아닌 배열로 변환되어 출력되게 됩니다. </p>
<p>아래 이미지에서 1번째 출력값은 Array.from() 메소드를 사용하여 NodeList를 배열로 바꾼 값이고 2번째 이미지는 기존 querySelectorAll() 메소드를 사용하여 출력 된 NodeList 입니다.
<img src="https://images.velog.io/images/function_dh/post/a259a6ba-63a2-4cc0-afeb-c0ee8d7a5eb0/_2021-01-11__3.26.05.png" alt=""></p>
<h2 id="참고-사이트">참고 사이트</h2>
<p><a href="https://blog.eunsatio.io/develop/Javascript%EB%A1%9C-HTML-%EC%9A%94%EC%86%8C-%EC%88%9C%ED%9A%8C%ED%95%98%EA%B8%B0">Javascript로 HTML 요소 순회하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[jquery UI selectmenu 접근성 고려]]></title>
            <link>https://velog.io/@function_dh/jquery-UI-selectmenu-%EC%A0%91%EA%B7%BC%EC%84%B1-%EA%B3%A0%EB%A0%A4</link>
            <guid>https://velog.io/@function_dh/jquery-UI-selectmenu-%EC%A0%91%EA%B7%BC%EC%84%B1-%EA%B3%A0%EB%A0%A4</guid>
            <pubDate>Tue, 26 Jan 2021 00:36:41 GMT</pubDate>
            <description><![CDATA[<h2 id="설명">설명</h2>
<p>jquery ui 위젯을 사용해서 select box에 대한 커스텀을 쉽게 제작할 수 있습니다. 심지어 aria와 관련된 웹 접근성도 자동으로 추가되고 사용자의 액션에 따라 해당 값이 상황에 맞게 변경되기 때문에 접근성과 관련해서도 사용하기 좋다고 생각했습니다.</p>
<h2 id="이슈">이슈</h2>
<p>단점이라면 jquery 라이브러리 자체의 무거운 점을 빼면 문제가 없을 줄 알았으나 구글에서 지원하는 lighthouse에서 해당 위젯을 사용한 페이지를 검사 해보면 접근성과 관련하여 문제가 발견할 수 있었습니다</p>
<ul>
<li>ARIA input fields do not have accessible names</li>
<li>Elements with an ARIA [role] that require children to contain a specific [role] are missing some or all of those required children.</li>
</ul>
<p>입력 필드에 엑세스 가능한 이름이 없고 role의 하위 요소에 필수로 등록되어야 할 하위 목록이 없다는 경고인데
위젯으로 생성되는 버튼 태그인 span에서 문제가 발생하여 원인을 찾아 해결하였습니다.</p>
<p><em>해당 작업은 Vue를 사용하여 Vue관련 속성이 같이 들어가 있지만 ui select 이슈와 무관하기 때문에 신경쓰지 않으셔도 됩니다.</em></p>
<pre><code class="language-html">// 위젯 적용 전 마크업
&lt;select ref=&quot;deliveryMemo&quot;&gt;
  &lt;option v-for=&quot;(item, index) in memoList&quot; :key=&quot;index&quot; :value=&quot;item.id&quot;&gt;{{ item.name }}&lt;/option&gt;
&lt;/select&gt;

&lt;div id=&quot;delivery-memolist-wrap&quot;&gt;&lt;/div&gt;</code></pre>
<p>ui select 위젯을 적용하면 위에 마크업에서 아래 마크업처럼 dom에 자동으로 html tag가 추가됩니다. 또한 aria속성과 class 등 여러 속성들이 기본 값으로 들어가게 됩니다.</p>
<p>기존에 select는 display:none 처리가 되고 span으로 만들어진 버튼 태그와 ul과 li로 만들어진 리스트가 select와 option 태그를 대체합니다.</p>
<pre><code class="language-html">// ui select 위젯 적용 후 마크업
&lt;select ref=&quot;deliveryMemo&quot;&gt;
  &lt;option v-for=&quot;(item, index) in memoList&quot; :key=&quot;index&quot; :value=&quot;item.id&quot;&gt;{{ item.name }}&lt;/option&gt;
&lt;/select&gt;

// 위젯으로 인한 버튼이 생성
&lt;span tabindex=&quot;0&quot; id=&quot;delivery-memo-list-button&quot; role=&quot;combobox&quot; aria-expanded=&quot;true&quot; aria-autocomplete=&quot;list&quot; aria-owns=&quot;delivery-memo-list-menu&quot; aria-haspopup=&quot;listbox&quot; class=&quot;ui-selectmenu-button ui-button ui-widget ui-selectmenu-button-open ui-corner-top&quot; aria-activedescendant=&quot;ui-id-16&quot; aria-labelledby=&quot;ui-id-16&quot; aria-disabled=&quot;false&quot;&gt;
  &lt;span class=&quot;ui-selectmenu-icon ui-icon ui-icon-triangle-1-s&quot;&gt;&lt;/span&gt;
  &lt;span class=&quot;ui-selectmenu-text&quot;&gt;경비(관리)실에 맡겨주세요&lt;/span&gt; // 현재 선택된 list
&lt;/span&gt;

// select의 option을 토대로 list가 생성됨
&lt;div id=&quot;delivery-memolist-wrap&quot;&gt;
  &lt;div class=&quot;ui-selectmenu-menu ui-front ui-selectmenu-open&quot;&gt;
    &lt;ul aria-hidden=&quot;false&quot; aria-labelledby=&quot;delivery-memo-list-button&quot; id=&quot;delivery-memo-list-menu&quot; role=&quot;listbox&quot; tabindex=&quot;0&quot; class=&quot;ui-menu ui-corner-bottom ui-widget ui-widget-content&quot; aria-activedescendant=&quot;ui-id-16&quot; aria-disabled=&quot;false&quot; style=&quot;width: 222px;&quot;&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-11&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper&quot;&gt;배송시 요청사항을 선택해주세요&lt;/div&gt;
      &lt;/li&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-12&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper&quot;&gt;문 앞에 놓아주세요&lt;/div&gt;
      &lt;/li&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-13&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper&quot;&gt;경비(관리)실에 맡겨주세요&lt;/div&gt;
      &lt;/li&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-14&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper&quot;&gt;택배함에 넣어주세요&lt;/div&gt;
      &lt;/li&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-15&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper&quot;&gt;직접 받겠습니다&lt;/div&gt;
      &lt;/li&gt;
      &lt;li class=&quot;ui-menu-item&quot;&gt;
        &lt;div id=&quot;ui-id-16&quot; tabindex=&quot;-1&quot; role=&quot;option&quot; class=&quot;ui-menu-item-wrapper ui-state-active&quot;&gt;직접입력&lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<pre><code class="language-jsx">// select에 ui selectmenu 위젯을 적용
&lt;script&gt;
$(this.$refs.deliveryMemo).selectmenu({
  // select의 option list 생성 위치
  appendTo : &#39;#delivery-memolist-wrap&#39;,
})
&lt;/script&gt;</code></pre>
<h2 id="selectmenu의-웹-접근성-관련-문제점-해결">selectmenu의 웹 접근성 관련 문제점 해결</h2>
<p>위에 위젯으로 인하여 생긴 태그를 보면 role=&quot;combobox&quot;으로 지정된 span 태그와 관련된 수정을 몇개 해주면 해결이 가능합니다.</p>
<h3 id="1-combobox에-aria-label을-추가해줘야-합니다">1. combobox에 aria-label을 추가해줘야 합니다.</h3>
<p>입력 필드에 대한 엑세스 가능한 이름을 지정해주는 것 입니다.</p>
<pre><code class="language-jsx">combobox버튼.setAttribute(&#39;aria-label&#39;, &#39;버튼에 대한 설명&#39;)</code></pre>
<h3 id="2-rolecombobox-의-경우-자기-자신이나-하위-태그에-input-text가-없으면-접근성에-위배-됩니다">2. role=&quot;combobox&quot; 의 경우 자기 자신이나 하위 태그에 input text가 없으면 접근성에 위배 됩니다.</h3>
<p> role=&quot;combobox&quot; 지정 시 input text가 필수로 사용 되어야 하기 때문에 접근성 관련하여 문제가 생긴 것입니다. 따라서 새로 추가한 input text에 aria 맞추어 list와 연결하기 위해 option list인 ul태그를 <strong>aria-controls</strong>로 지정해줘야 합니다. 또한 list의 값이 자동으로 들어가기 때문에 <strong>aria-autocomplete</strong> 속성으로 자동완성 기능을 지원하는 것을 명시해야 합니다.</p>
<pre><code class="language-html">// 변경된 마크업
// input이 추가되고 aria-label이 추가되어 접근성에 위배되지 않는다.
&lt;span tabindex=&quot;0&quot; id=&quot;delivery-memo-list-button&quot; role=&quot;combobox&quot; aria-label=&quot;배송메모 리스트 버튼&quot; aria-expanded=&quot;true&quot; aria-autocomplete=&quot;list&quot; aria-owns=&quot;delivery-memo-list-menu&quot; aria-haspopup=&quot;listbox&quot; class=&quot;ui-selectmenu-button ui-button ui-widget ui-selectmenu-button-open ui-corner-top&quot; aria-activedescendant=&quot;ui-id-16&quot; aria-labelledby=&quot;ui-id-16&quot; aria-disabled=&quot;false&quot;&gt;
  &lt;span class=&quot;ui-selectmenu-icon ui-icon ui-icon-triangle-1-s&quot;&gt;&lt;/span&gt;
  &lt;span class=&quot;ui-selectmenu-text&quot;&gt;직접입력&lt;/span&gt;
  &lt;input type=&quot;text&quot; aria-autocomplete=&quot;list&quot; aria-controls=&quot;delivery-memo-list-menu&quot; /&gt;
&lt;/span&gt;</code></pre>
<pre><code class="language-jsx">let button = document.querySelector(&#39;#delivery-memo-list-button&#39;)
let memoList = document.querySelector(&#39;#delivery-memo-list-menu&#39;)

// btn - selectmenu로 생성된 버튼
// memolist - selectmenu로 생성된 list ul태그
const selcAriaAdd = (btn, memolist) =&gt; {
  let inputTag = document.createElement(&#39;input&#39;)
  inputTag.setAttribute(&#39;type&#39;, &#39;text&#39;)
  inputTag.setAttribute(&#39;aria-autocomplete&#39;, &#39;list&#39;)
  inputTag.setAttribute(&#39;aria-controls&#39;, memolist)

  // 위젯에서 기본적으로 true으로 들어가 있지만 목적을 명시하기 위해 listbox로 변경하였습니다.(필수 X)
  // listbox는 ARIA 1.1에 추가된 속성 이기 때문에 브라우저 및 스크린 리더기의 지원 범위가 달라 확인 후 사용 해야 합니다.
  btn.setAttribute(&#39;aria-haspopup&#39;, &#39;listbox&#39;) 
  btn.setAttribute(&#39;aria-label&#39;, &#39;배송메모 리스트 버튼&#39;)
  btn.appendChild(inputTag)
}
selcAriaAdd(button,memoList)</code></pre>
<h2 id="참고-사이트">참고 사이트</h2>
<p><a href="https://api.jqueryui.com/selectmenu/#option-classes">Selectmenu Widget</a></p>
<p><a href="https://developers.google.com/web/tools/lighthouse?hl=ko">Lighthouse | Tools for Web Developers | Google Developers</a></p>
<p><a href="https://www.digitala11y.com/combobox-role/">WAI-ARIA: Role=Combobox * Digital A11Y</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML5] history API 브라우저 이동]]></title>
            <link>https://velog.io/@function_dh/HTML5-history-API-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9D%B4%EB%8F%99</link>
            <guid>https://velog.io/@function_dh/HTML5-history-API-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9D%B4%EB%8F%99</guid>
            <pubDate>Fri, 18 Dec 2020 06:11:57 GMT</pubDate>
            <description><![CDATA[<h2 id="설명">설명</h2>
<p>history API는 브라우저의 세션 기록을  조작할 수 있는 메소드를 담고 있는 객체입니다. 기본적으로 뒤로가기, 앞으로 가기, 페이지 이동 등을 조작할 수 있으며 SPA와 관련하여 html5에서 새롭게 추가된 메소드를 알아보도록 하겠습니다.</p>
<h3 id="1-historyback">1. history.back()</h3>
<p>해당 메소드는 브라우저에서 뒤로가기 버튼을 클릭 한 것과 같은 기능입니다.</p>
<pre><code class="language-jsx">history.back()</code></pre>
<h3 id="2historyforward">2.history.forward()</h3>
<p>뒤로 갈 수 있다면 앞으로도 갈 수 있어야겠죠? 앞으로 가기 버튼과 같은 기능입니다.</p>
<pre><code class="language-jsx">history.forward()</code></pre>
<h3 id="3-historygo">3. history.go()</h3>
<p>해당 메소드는 원하는 위치로 페이지를 이동 시킬 수가 있습니다. 인자값으로는 이동 할만큼 숫자를 넣어주면 됩니다. 페이지가 이동 되는 기준은 현재페이지 부터 양수를 넣으면 앞으로 음수를 넣으면 뒤로 페이지가 이동됩니다.</p>
<pre><code class="language-jsx">// 인자값이 없거나 0을 넣으면 페이지가 새로고침 됩니다.
history.go()
history.go(0)

// 뒤로 가기
history.go(-1)

// 앞으로 가기
history.go(1)</code></pre>
<p>이제부터 html5에 새로 추가된 url 변경 메소드입니다.</p>
<pre><code class="language-jsx">history.pushState({데이터 객체}, 페이지 제목 변경, 바꿀 주소)
history.replaceState(동일)</code></pre>
<p>해당 메소드를 사용하면 현재 페이지의 url을 변경할 수 있으며 페이지가 갱신되지는 않지만 실제로 페이지 이동으로 인식됩니다.</p>
<h3 id="1-historypushstate">1. <strong>history.pushState()</strong></h3>
<p><strong>history.pushState</strong>의 경우 새로운 주소목록을 추가 하기 때문에 이전 url의 주소가 남아있어 브라우저의 뒤로가기 버튼이 활성화 됩니다.</p>
<pre><code class="language-jsx">// 현재 url이 test라고 가정 했을 때 
// 이전 url은 test, 현재 url은 test/pushpage로 변경된다.
history.pushState({ data: &#39;testData1&#39; }, null, &#39;/pushpage&#39;)</code></pre>
<h3 id="2-historyreplacestate">2. <strong>history.replaceState()</strong></h3>
<p><strong>history.replaceState</strong>는 동일한 기능을 하지만 주소목록에 추가하지 않기 때문에</p>
<p>뒤로가기 버튼이 활성화 되지 않습니다.</p>
<pre><code class="language-jsx">// 현재 주소목록만 바뀌기 때문에 test에서 test/replacepage로 바뀌고
// 이전 url인 test은 남지 않는다.
history.replaceState({ data: &#39;testData2&#39; }, null, &#39;/replacepage&#39;)</code></pre>
<p>메소드의 파라미터에 대해 설명하면 </p>
<p>첫번째 인자는 데이터 객체를 전달 가능하며 <strong>history.state</strong>에 저장되어 사용할 수 있습니다.</p>
<p>두번째 인자는 해당 페이지의 제목 변경이 가능한데 브라우저에서 기능이 구현되어 있지 않아 null을 전달하면 됩니다.</p>
<p>세번째 인자는 변경할 url 주소를 넣어주면 됩니다. 단순한 경로 이동도 가능하고 querystring도 추가할 수 있기 때문에 유용하게 사용가능 합니다.</p>
<p>history API 메소드를 사용하여 페이지 이동을 한후 기존 데이터를 활용하여 무언가를 하고 싶다면 popstate 이벤트로 확인이 가능합니다. 이벤트는 기존 window.addEventListener로 연결하면 됩니다.</p>
<p>메소드가 실행 할때는 동작하지 않고 브라우저가 뒤로가기, 앞으로 가기를 했을 때 발생합니다. 하지만 history.state에 이전 state가 저장되어 있기 때문에 이전 페이지의 데이터를 다시 활용할 수 있습니다! </p>
<h2 id="예시">예시</h2>
<pre><code class="language-jsx">&lt;button type=&quot;button&quot; id=&quot;pushState&quot;&gt;pushState&lt;/button&gt;
&lt;button type=&quot;button&quot; id=&quot;replaceState&quot;&gt;replaceState&lt;/button&gt;

&lt;script&gt;
  document.querySelector(&#39;#pushState&#39;).addEventListener(&#39;click&#39;, () =&gt; {
    history.pushState({ data: &#39;testData1&#39; }, null, &#39;/pushpage&#39;)
  })
  document.querySelector(&#39;#replaceState&#39;).addEventListener(&#39;click&#39;, () =&gt; {
    history.replaceState({ data: &#39;testData2&#39; }, null, &#39;/replacepage&#39;)
  })
  window.addEventListener(&#39;popstate&#39;, function () {
    // 버튼에 따라 저장값, { data: &#39;testData1&#39; }
    // 저장값, { data: &#39;testData2&#39; }을 확인 할 수 있습니다.
    console.log(&#39;저장값&#39;, history.state); 
  });
&lt;/script&gt;</code></pre>
<h2 id="참고-사이트">참고 사이트</h2>
<p><a href="https://www.zerocho.com/category/HTML&amp;DOM/post/599d2fb635814200189fe1a7">(HTML&amp;DOM) History API - 주소를 내 마음대로!</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/API/History_API">History API</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] Swiper 사용법 - 슬라이드 구현]]></title>
            <link>https://velog.io/@function_dh/JavaScript-Swiper-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@function_dh/JavaScript-Swiper-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%93%9C-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Mon, 07 Dec 2020 04:00:09 GMT</pubDate>
            <description><![CDATA[<h2 id="설치">설치</h2>
<p>swiper를 사용하려면 여러 방법이 존재합니다. </p>
<ol>
<li><p>cdn으로 사용하는 방식</p>
</li>
<li><p>실제 swiper js파일을 swiper github에서 다운 받아 삽입하는 방식</p>
</li>
<li><p>npm으로 설치하여 사용하는 방식</p>
</li>
</ol>
<p>프로젝트에 따라서 원하는 방식으로 공식 문서에서 제공하는 설치법에 따라 설치하시면 됩니다.</p>
<p><a href="https://swiperjs.com/get-started/">Getting Started With Swiper</a></p>
<h2 id="기본-구조">기본 구조</h2>
<p>swiper는 슬라이드 라이브러리로 다양한 메소드와 설정을 지원해서 사용하기 매우 편한 점이 있습니다. 무엇보다 하위 브라우저(ie9)에서도 문제없이 사용 가능하기 때문에 크로스 브라우징 측면에서도 뛰어난 부분이 있습니다.</p>
<p>또한 사용법도 매우 간단합니다. 아래와 같이 태그에 class만 넣어주고 스크립트에서 슬라이드로 사용할 태그를 지정만 해주면 됩니다. 주의 할 점은 swiper 사용시 필수로 아래 구조와 같이 <strong>클래스 명</strong>을 지정해줘야 합니다.</p>
<p><strong>swiper-container &gt; swiper-wrapper &gt; swiper-slide</strong> 구조로 되어 있습니다.</p>
<pre><code class="language-html">// 마크업
&lt;div id=&quot;test-slide&quot; class=&quot;swiper-container&quot;&gt;
  &lt;ul class=&quot;swiper-wrapper&quot;&gt;
    &lt;li class=&quot;swiper-slide&quot;&gt;슬라이드1&lt;/li&gt;
    &lt;li class=&quot;swiper-slide&quot;&gt;슬라이드2&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

// 필수X (아래 옵션정리-추가설정-pagination 부분 참고)
&lt;div class=&quot;pagination&quot;&gt;&lt;/div&gt;  </code></pre>
<pre><code class="language-jsx">// 스크립트
const slide = new Swiper(&#39;#test-slide&#39;, {
  // 다양한 옵션 설정, 
  // 아래에서 설명하는 옵션들은 해당 위치에 들어갑니다!!
  slidesPerView : &#39;auto&#39;,
  spaceBetween : 6, 
})</code></pre>
<h2 id="옵션-정리">옵션 정리</h2>
<p>swiper 옵션 설정을 변수에 담아서 사용 할 수도 있습니다. 변수에 담아서 사용 시에 설정을 재활용하기 편하다는 장점이 있습니다.</p>
<pre><code class="language-jsx">// 일반 swiper 설정 예시
const slide = new Swiper(&#39;#test-slide&#39;, {
  slidesPerView : &#39;auto&#39;,
  spaceBetween : 6, 
})

// 변수에 담아서 활용 예시
var slideSetting = {
  slidesPerView : &#39;auto&#39;,
  spaceBetween : 6,
  loop : false,
}
const slide = new Swiper(&#39;#test-slide&#39;, slideSetting)</code></pre>
<h3 id="기본-설정">기본 설정</h3>
<p>기본적으로 슬라이드를 사용할 때 거의 필수로 설정하는 설정 요소들 입니다. 한 화면에 보여줄 슬라이드의 갯수 설정, 슬라이드 간의 사이 여백, 반복 여부 등 기본적인 설정을 정리 해놨습니다.</p>
<pre><code class="language-jsx">slidesPerView : &#39;auto&#39;, // 한 슬라이드에 보여줄 갯수
spaceBetween : 6, // 슬라이드 사이 여백
loop : false, // 슬라이드 반복 여부
loopAdditionalSlides : 1, // 슬라이드 반복 시 마지막 슬라이드에서 다음 슬라이드가 보여지지 않는 현상 수정
pagination : false, // pager 여부
autoplay : {  // 자동 슬라이드 설정 , 비 활성화 시 false
  delay : 3000,   // 시간 설정
  disableOnInteraction : false,  // false로 설정하면 스와이프 후 자동 재생이 비활성화 되지 않음
},
navigation: {   // 버튼 사용자 지정
    nextEl: &#39;.swiper-button-next&#39;,
    prevEl: &#39;.swiper-button-prev&#39;,
},</code></pre>
<h3 id="추가-설정">추가 설정</h3>
<p>자주 사용하지 않지만 상황에 따라 쉽게 슬라이드를 커스텀 할 수 있는 옵션 요소들 입니다. </p>
<pre><code class="language-jsx">freeMode : false, // 슬라이드 넘길 때 위치 고정 여부
autoHeight : true, // true로 설정하면 슬라이더 래퍼가 현재 활성 슬라이드의 높이에 맞게 높이를 조정합니다.
a11y : false, // 접근성 매개변수(접근성 관련 대체 텍스트 설정이 가능) - api문서 참고!
resistance : false, // 슬라이드 터치에 대한 저항 여부 설정
slideToClickedSlide : true, // 해당 슬라이드 클릭시 슬라이드 위치로 이동
centeredSlides : true // true시에 슬라이드가 가운데로 배치
allowTouchMove : true, // false시에 스와이핑이 되지 않으며 버튼으로만 슬라이드 조작이 가능
watchOverflow : true // 슬라이드가 1개 일 때 pager, button 숨김 여부 설정
slidesOffsetBefore : number, // 슬라이드 시작 부분 여백
slidesOffsetAfter : number, // 슬라이드 시작 부분 여백

pagination : {   // 페이저 버튼 사용자 설정
  el : &#39;.pagination&#39;,  // 페이저 버튼을 담을 태그 설정
  clickable : true,  // 버튼 클릭 여부
  type : &#39;bullets&#39;, // 버튼 모양 결정 &quot;bullets&quot;, &quot;fraction&quot; 
  renderBullet : function (index, className) {  // className이 기본값이 들어가게 필수 설정
    return &#39;&lt;a href=&quot;#&quot; class=&quot;&#39; + className + &#39;&quot;&gt;&#39; + (index + 1) + &#39;&lt;/a&gt;&#39;
  },
  renderFraction: function (currentClass, totalClass) { // type이 fraction일 때 사용
    return &#39;&lt;span class=&quot;&#39; + currentClass + &#39;&quot;&gt;&lt;/span&gt;&#39; +
           &#39;&lt;span class=&quot;&#39; + totalClass + &#39;&quot;&gt;&lt;/span&gt;&#39;;
  }
},</code></pre>
<h3 id="초기화-설정">초기화 설정</h3>
<p>초기화 설정 방법은 2가지가 있습니다.  swiper가 초기화 될 때, 초기화 된 후 설정을 할 수가 있습니다. </p>
<p>on을 매개 변수로 전달 할 시에 swiper가 생성될 때 즉 초기화 될 때 설정을 해줄 수가 있고 다른 방법으로 아래 코드처럼 따로 사용 시에는 swiper가 이미 만들어지고 나서 동작하기 때문에 초기화 된 후 동작합니다. </p>
<p>초기화 설정과 관련하여 수많은 설정이 있기 때문에 아래 참고사이트에서 필요한 부분을 찾아서 사용하는 것이 좋습니다.</p>
<pre><code class="language-jsx">// 매개 변수로 사용시 swiper가 초기화 될 때 동작합니다.
var mySwiper = new Swiper(&#39;.swiper-container&#39;, {
  on : {
    init : function () {
      console.log(&#39;swiper 초기화 될때 실행&#39;);
    },
    imagesReady : function () { // 모든 내부 이미지가 로드 된 직후 이벤트가 시작됩니다.
      console.log(&#39;슬라이드 이미지 로드 후 실행&#39;);
    },
  },
};

// swiper가 초기화 된 후 동작합니다.
var mySwiper = new Swiper(&#39;.swiper-container&#39;, {
  // ...
};
mySwiper.on(&#39;init&#39;, function () {
  console.log(&#39;slide가 초기화 설정을 마친 후 실행&#39;);
});</code></pre>
<h3 id="반응형-설정">반응형 설정</h3>
<p>swiper는 breakpoints라는 객체로 반응형 설정이 가능합니다. 아래 설정은 브라우저의 가로 크기가 768px 이하 일 때  동작하는 예시입니다.</p>
<pre><code class="language-jsx">breakpoints : { // 반응형 설정이 가능 width값으로 조정
  768 : {
    slidesPerView : 1,
  },
},

//5.3.0부터 &quot;비율&quot;(너비 / 높이)로 설정이 가능해졌습니다.
var swiper = new Swiper(&#39;.swiper-container&#39;, {
  slidesPerView: 1,
  spaceBetween: 10,
  breakpoints: {
    &#39;@0.75&#39;: {
      slidesPerView: 2,
      spaceBetween: 20,
    },
    &#39;@1.00&#39;: {
      slidesPerView: 3,
      spaceBetween: 40,
    },
    &#39;@1.50&#39;: {
      slidesPerView: 4,
      spaceBetween: 50,
    },
  }
});</code></pre>
<h2 id="메소드">메소드</h2>
<p>swiper이름.메소드() 형식으로 사용할 수 있습니다.</p>
<pre><code class="language-jsx">// ex) mySlider.autoplay.start()
.slideTo(index, speed, runCallbacks) // 해당 슬라이드로 이동
.slidePrev(index, speed, runCallbacks) // 이전 슬라이드로 이동
.slideNext(index, speed, runCallbacks) // 다음 슬라이드로 이동
.autoplay.start(); // 자동 재생 시작
.autoplay.stop(); // 자동 재생 정지
.destroy() // 슬라이드 제거</code></pre>
<h2 id="참고-사이트">참고 사이트</h2>
<p>swiper는 api 문서에 정리가 매우 잘되어 있기 때문에 추가적인 설정이 필요하면 api 문서를 참고해서 찾는게 가장 빠르고 정확합니다.</p>
<p><a href="https://swiperjs.com/api/">Swiper API</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 레이어 팝업 스크롤 막기]]></title>
            <link>https://velog.io/@function_dh/JS-%EB%A0%88%EC%9D%B4%EC%96%B4-%ED%8C%9D%EC%97%85-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@function_dh/JS-%EB%A0%88%EC%9D%B4%EC%96%B4-%ED%8C%9D%EC%97%85-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Thu, 03 Dec 2020 05:58:18 GMT</pubDate>
            <description><![CDATA[<h2 id="설명">설명</h2>
<p>팝업이 열릴 때 브라우저의 스크롤을 방지하기 위해  흔하게 사용하는 방법으로 css를 활용하여 body에 overflow:hidden을 적용 시킵니다. 방법도 간편하고 스타일로 제어할 수 있기 때문에 많이 사용하지만 ios safari에서 동작하지 않는 문제가 있기 때문에 추가적으로 작업을 해야합니다.</p>
<pre><code class="language-jsx">// 기존 팝업 스크롤 방지
const body = document.querySelector(&#39;body&#39;);
body.style.overflow = &#39;hidden&#39;</code></pre>
<h3 id="positionfixed를-활용한-방법">position:fixed를 활용한 방법</h3>
<p>여러 방법이 있지만 현재 가장 활용도가 높은 방법은 position을 활용하여 현재 이동한 스크롤의 높이값 만큼 body를 고정 시키는 방법입니다.</p>
<p>스크롤의 높이값을 구하는 건 window.pageYOffset를 활용하여 현재 페이지의 스크롤을 내린 만큼 값을 구할 수 있습니다. 추가로 설명을 하자면 window.pageYOffset은 scrollY와 똑같은 기능을 하지만 하위 브라우저도 지원을 하기 때문에 크로스 브라우징을 생각한다면 pageYOffset을 활용하는 것이 좋습니다.</p>
<pre><code class="language-jsx">// 현재 페이지에서 스크롤을 내린만큼 값을 저장
let scrollPosition = 0;
scrollPosition = window.pageYOffset;</code></pre>
<p>해당 스크롤의 위치값을 구했다면 body의 position:fixed를 주고 위치 값을 지정할 때 top에 스크롤의 위치값 만큼 값을 빼줍니다. 값을 빼주지 않으면 top:0으로 되어 스크롤이 최상단으로 올라가게 처리 됩니다!</p>
<pre><code class="language-jsx">// 저장된 스크롤의 위치값 만큼 빼주자
const body = document.querySelector(&#39;body&#39;);
body.style.top = `-${scrollPosition}px`;</code></pre>
<p>마무리로 body의 width:100% 설정(position:fixed가 설정 되면서 기존 display:block의 width값이 사라지기 때문)과 팝업을 닫을 때 설정 했던 css 속성 값을 삭제해주면 아래와 같은 코드가 됩니다.</p>
<h2 id="예시">예시</h2>
<pre><code class="language-jsx">const body = document.querySelector(&#39;body&#39;);
let scrollPosition = 0;

// 팝업 오픈
function enable() {
  scrollPosition = window.pageYOffset;
  body.style.overflow = &#39;hidden&#39;;
  body.style.position = &#39;fixed&#39;;
  body.style.top = `-${scrollPosition}px`;
  body.style.width = &#39;100%&#39;;
}
// 팝업 닫기
function disable() {
  body.style.removeProperty(&#39;overflow&#39;);
  body.style.removeProperty(&#39;position&#39;);
  body.style.removeProperty(&#39;top&#39;);
  body.style.removeProperty(&#39;width&#39;);
  window.scrollTo(0, scrollPosition);
}</code></pre>
<p>팝업을 닫을 때 보통 선언했던 css 속성을 초기화 시켜 원래 상태로 되돌리는데 removeProperty 메소드를 활용하면 초기화 시키지 않고 해당 스타일을 쉽게 제거할 수 있습니다. 그리고 마지막에 스크롤의 위치를 이동 시킬 수 있는 window.scrollTo(X축, Y축) 메소드를 활용하여 팝업 오픈때 저장한 스크롤의 위치값 만큼 스크롤을 이동시켜 body의 position이 해제 됐을 때 최상단으로 이동하는 걸 방지할 수 있습니다.</p>
<h2 id="이슈">이슈</h2>
<p>스크롤 잠금이 활성화 된 상태에서 브라우저 창의 크기를 변경하면 처음 저장 했던 스크롤의 높이와 달라 지기 때문에 스크롤 위치가 올바르게 복원되지 않는 단점이 존재합니다. 하지만 그 외에 사용 했을 때 문제점을 따로 발견하지는 못했습니다.</p>
<h2 id="참고-사이트">참고 사이트</h2>
<p><a href="https://markus.oberlehner.net/blog/simple-solution-to-prevent-body-scrolling-on-ios/">Simple Solution to Prevent Body Scrolling on iOS</a></p>
]]></description>
        </item>
    </channel>
</rss>