<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>d_hyeon.log</title>
        <link>https://velog.io/</link>
        <description>FRONTEND DEV.</description>
        <lastBuildDate>Sun, 15 Sep 2024 11:43:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>d_hyeon.log</title>
            <url>https://velog.velcdn.com/images/d_hyeon/profile/9e449738-a193-418e-a964-63b6d6955648/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. d_hyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/d_hyeon" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js로 검색엔진 최적화 해보기]]></title>
            <link>https://velog.io/@d_hyeon/Next.js%EB%A1%9C-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@d_hyeon/Next.js%EB%A1%9C-%EA%B2%80%EC%83%89%EC%97%94%EC%A7%84-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 15 Sep 2024 11:43:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/bf92b7aa-be93-4e53-8a7e-e844bcd97ee2/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>웹 개발을 공부하면서 느끼는 점은 사용자의 경험이 제일 중요하다는 것이다.
사이트 이용을 하는데 어려움이 있고, 속도가 느리다면 사용자의 이탈율은 당연이 높을 것이다.</p>
<p>하지만 사용자 경험보다 더 중요한 것은 검색 사이트 상위 노출이라고 생각한다.
아무리 사용자 경험과 로딩 속도를 개선해도 사이트를 상위 노출시키지 못한다면 내가 개발한 홈페이지를 보여줄 수가 없다.</p>
<p>그래서 이번 프로젝트에는 기업의 홈페이지가 검색 사이트 상위 노출을 할 수 있도록 검색엔진 최적화에 중점을 두었다.</p>
<h3 id="검색엔진-최적화seo란">검색엔진 최적화(SEO)란?</h3>
<blockquote>
<p>검색엔진 최적화(SEO)란 관련성이 높은 트래픽을 더 많이 유도하기 위해 검색엔진에서의 웹사이트 페이지 검색 가능성을 향상시키는 프로세스이다. - <a href="https://developers.google.com/search/docs?hl=ko">구글 검색 센터</a></p>
</blockquote>
<h3 id="간단한-seo-체크리스트">간단한 SEO 체크리스트</h3>
<h4 id="title-meta-태그">Title, Meta 태그</h4>
<pre><code>&lt;meta name=&quot;description&quot; content=&quot;...&quot; /&gt;
&lt;meta name=&quot;keywords&quot; content=&quot;...&quot; /&gt;
&lt;title&gt;제목&lt;/title&gt;</code></pre><p>meta 태그는 페이지의 설명을 제공하거나 주요 키워드를 설정하는 태그이고,
title 태그는 페이지의 제목을 설정한다. description과 title은 브라우저 탭에 표시되며, 검색 엔진 결과에서도 제목으로 사용된다. 
이 두 태그는 SEO에 가장 중요한 태그이다.</p>
<h4 id="alt-속성">Alt 속성</h4>
<pre><code>&lt;img src=&quot;image.jpg&quot; alt=&quot;이미지 설명 대체 텍스트&quot; /&gt;</code></pre><p>alt 속성은 이미지에 대한 설명을 적는 속성이고, 이미지 태그를 사용할 때 웹접근성을 위해 꼭 필요하다.
검색 엔진은 이미지의 내용이나 의미를 이해할 수 없으므로, alt 속성을 사용해서 대체 텍스트를 넣는다면 검색 엔진이 페이지의 콘텐츠를 더 잘 이해하고, 이미지가 검색 결과에 나타나게 할 수 있다.</p>
<h4 id="시맨틱-태그">시맨틱 태그</h4>
<pre><code>&lt;header&gt;
&lt;nav&gt;
&lt;section&gt;</code></pre><blockquote>
<p>검색 엔진은 의미론적 마크업을 페이지의 검색 순위에 영향을 줄 수 있는 중요한 키워드로 간주한다. - <a href="https://developer.mozilla.org/ko/docs/Glossary/Semantics">MDN</a></p>
</blockquote>
<p>시맨틱 태그는 웹 페이지의 구조를 명확하게 정의하는 HTML 요소이며 웹접근성을 향상시킨다.
시맨틱 태그의 활용은 시각 장애가 있는 사용자가 화면 판독기로 페이지를 탐색할 때 의미론적 마크업을 안내판으로 사용할 수 있다.</p>
<h4 id="https-적용">HTTPS 적용</h4>
<p>구글은 https를 적용한 사이트를 상위 노출시킨다.
https를 적용시킨 사이트에 높은 접수를 주는 가장 주된 이유는 보안이라고 한다.</p>
<p>또한 구글은 보안이 가장 핵심적인 원칙이라고 하니 https는 필수적으로 적용시켜야 한다.
<a href="https://korea.googleblog.com/2018/07/chrome-security.html">https://korea.googleblog.com/2018/07/chrome-security.html</a></p>
<h4 id="ssr-적용">SSR 적용</h4>
<p>React는 기본적으로 CSR이기 때문에 페이지의 내용을 자바스크립트를 사용하여 동적으로 로드한다. 이로 인해 초기 페이지 로드 시 컨텐츠가 제대로 표시가 안되어 검색 엔진이 제대로 크롤링하기 어려울 수 있다.</p>
<p>NextJS를 활용하면 다른 설정 없이 SSR로 페이지를 불러오기 때문에 SEO에 더 유용하다.  </p>
<h3 id="검색-상위-노출-확인">검색 상위 노출 확인</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/4d782c9a-50a7-464d-86b9-9d43dec1346a/image.png"/>
도움을 받은 블로그보다 많은 체크리스트를 적용하지 않았지만 상위에 노출되는 걸 확인할 수 있었다!

<hr/>

<p><strong>도움 받은 블로그</strong></p>
<ol>
<li><a href="https://itconquest.tistory.com/entry/HTTPS%EC%99%80-SEO%EC%9D%98-%EA%B4%80%EA%B3%84-SSL%EC%9D%B4-%EA%B2%80%EC%83%89%EA%B2%B0%EA%B3%BC%EC%97%90-%EC%A3%BC%EB%8A%94-%EC%98%81%ED%96%A5">https://itconquest.tistory.com/entry/HTTPS와-SEO의-관계-SSL이-검색결과에-주는-영향</a></li>
<li><a href="https://velog.io/@rnrn99/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-SEO-%EA%B8%B0%EC%B4%88">https://velog.io/@rnrn99/리액트-개발자를-위한-SEO-기초</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커를 활용한 CI/CD 파이프라인]]></title>
            <link>https://velog.io/@d_hyeon/%EB%8F%84%EC%BB%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</link>
            <guid>https://velog.io/@d_hyeon/%EB%8F%84%EC%BB%A4%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</guid>
            <pubDate>Sun, 15 Sep 2024 10:12:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/c4d1551e-5ce2-46ad-9ab9-6d3f64ad4e56/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>저번 프로젝트를 진행하면서 큰 이슈가 있었다.
우리 팀은 지속적인 배포를 하지 않고 프로젝트를 작업했다.
그리고 발표 전날에 배포하니까 배포 오류 때문에 발표를 망친 기억이 있다..</p>
<p>그래서! 이번에 하는 개인 프로젝트는 처음부터 CI/CD 파이프라인을 설계해서 지속적인 코드 통합과 배포 자동화로 그런 문제가 없도록 하는 것이 첫번째 목표였다.</p>
<h3 id="파이프라인">파이프라인</h3>
<p><img src="https://velog.velcdn.com/images/d_hyeon/post/4b69589a-9b44-4428-9032-ef826aca357a/image.png" alt="">
파이프라인은 매우 간단하게 설계하였다.</p>
<ul>
<li>Code Push</li>
<li>Workflow에서 Type, Build Test</li>
<li>Docker Push</li>
<li>EC2 ssh 접속</li>
<li>Docker Pull &amp; Run</li>
</ul>
<p>아직 부족한 부분이 많다고 생각한다. 
서버 부하와 속도를 위한 S3 사용, 도커 캐시를 활용한 빌드 속도 향상 등 더 보완할 점이 많지만 이번에는 기본적인 CI/CD가 어떻게 진행되는지 배워가는 생각으로 작성하였다.</p>
<h3 id="도커-사용-이유">도커 사용 이유</h3>
<p>개인 프로젝트의 규모가 크진 않지만 실무에서 배포해야할 때는 용량이 크고, 비용 절감과 서버 부하를 줄이기 위해 꼭 사용해야할 기술이라 생각하여 도커를 사용하였다.
그리고 <a href="https://aws.amazon.com/ko/docker/">aws 공식 문서</a>에서도 권장하는 방법이기도 하다.</p>
<p><img src="https://velog.velcdn.com/images/d_hyeon/post/2dd9f7bb-238d-422f-a77c-257e5c660b48/image.png" alt=""></p>
<h3 id="workflow">Workflow</h3>
<pre><code class="language-yml">name: Frontend CD with Docker Hub
on:
  push:
    branches:
      - master # master에 push되었을 때 yml 실행

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: &#39;18&#39; # Node.js 버전 설정

      - name: Install dependencies
        run: npm install

      - name: Run TypeScript type check
        run: npm run type-check # 타입 체크 스크립트 실행

      - name: Run build
        run: npm run build # 빌드 스크립트 실행

      - name: Login to DockerHub
        if: success() # 타입 체크와 빌드 테스트가 완료되면 실행
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Get Env
        run: |
          touch ./.env.local
          echo &quot;${{secrets.ENV}}&quot; &gt; ./.env.local

      - name: Build and Release Docker Image
        run: |
          docker build -t ${{ secrets.DOCKERHUB_REPO }} .
          docker tag ${{ secrets.DOCKERHUB_REPO }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        id: deploy
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          script: |
            sudo docker rm -f $(docker ps -aqf &quot;name=^${{ secrets.DOCKERHUB_REPO }}&quot;) || true
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker run -d -p 3000:3000 --name ${{ secrets.DOCKERHUB_REPO }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker image prune -f
</code></pre>
<p>파이프라인을 설계한 Workflow이다.
이렇게 봐서는 잘 모르겠지만 천천히 위에서부터 보면 이해하기 쉽다.</p>
<h4 id="1-workflow-실행">1. Workflow 실행</h4>
<pre><code class="language-yml">name: Frontend CD with Docker Hub
on:
  push:
    branches:
      - master # master에 push되었을 때 yml 실행</code></pre>
<p>이 Workflow는 master 브랜치에 push 되었을 때 실행된다.
이렇게 코드를 작성한 이유는 개인 프로젝트라 PR을 작성하지 않기 때문에 간단한 push만으로 실행되게 하였다.</p>
<h4 id="2-node-설치-및-테스트">2. Node 설치 및 테스트</h4>
<pre><code class="language-yml">jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: &#39;18&#39; # Node.js 버전 설정

      - name: Install dependencies
        run: npm install

      - name: Run TypeScript type check
        run: npm run type-check # 타입 체크 스크립트 실행

      - name: Run build
        run: npm run build # 빌드 스크립트 실행</code></pre>
<p>타입과 빌드 테스트를 위해 노드를 설치하고 실행한다.
테스트를 하지 않고 배포를 하게 된다면 배포된 애플리케이션에서 에러가 발생할 수 있기 때문에 테스트 후 다음 코드를 실행해주었다.</p>
<h4 id="3-도커-로그인-및-빌드">3. 도커 로그인 및 빌드</h4>
<pre><code class="language-yml">- name: Login to DockerHub
        if: success() # 타입 체크와 빌드 테스트가 완료되면 실행
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Get Env
        run: |
          touch ./.env.local
          echo &quot;${{secrets.ENV}}&quot; &gt; ./.env.local

      - name: Build and Release Docker Image
        run: |
          docker build -t ${{ secrets.DOCKERHUB_REPO }} .
          docker tag ${{ secrets.DOCKERHUB_REPO }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest</code></pre>
<p>도커 push를 위한 로그인 action을 작성해주었다.
도커 로그인 후 env 파일을 만들고 Github에 저장되어 있는 환경변수를 가져온다.
이후 도커를 빌드할 때 Dockerfile을 사용하여 Docker 이미지를 빌드하고 Push 한다.</p>
<h4 id="4-ec2-접속-및-배포">4. EC2 접속 및 배포</h4>
<pre><code class="language-yml">      - name: Deploy to server
        uses: appleboy/ssh-action@master
        id: deploy
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          script: |
            sudo docker rm -f $(docker ps -aqf &quot;name=^${{ secrets.DOCKERHUB_REPO }}&quot;) || true
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker run -d -p 3000:3000 --name ${{ secrets.DOCKERHUB_REPO }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker image prune -f</code></pre>
<p>배포를 위해서 ec2로 ssh 접속해주었다.
그리고 기존에 있던 컨테이너를 삭제 후 최신 이미지를 Pull 해주고 컨테이너를 실행하면 배포가 완료된다!</p>
<hr/>
배포 자동화를 하지 않고 진행했을 때는 main 브랜치의 코드가 업데이트 될때마다 배포를 해야했고, 그렇게 되면 너무 시간이 오래 걸리는 작업이었다.

<p>이번에 CI/CD를 설계하면서 배포까지의 시간을 없애고, 빌드와 타입 테스트까지 Workflow로 진행을 하니까 작업 속도가 빨라진다는 게 확실히 느껴졌다.</p>
<p>이후 시간이 생긴다면 ECS, S3 등 빠르고 가벼운 배포를 위해 다른 기술들도 적용해봐야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 API CORS 오류 해결]]></title>
            <link>https://velog.io/@d_hyeon/%EB%84%A4%EC%9D%B4%EB%B2%84-API-CORS-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@d_hyeon/%EB%84%A4%EC%9D%B4%EB%B2%84-API-CORS-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Sat, 14 Sep 2024 14:52:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/6caba7a9-33ee-4255-b53b-b717c1a1bef0/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>이번에 진행하는 프로젝트에서는 <strong>네이버 뉴스 api</strong>를 활용하기로 하였다.
하지만 전에도 한 번 검색 api를 사용했었을 때도 <strong>CORS 오류</strong>가 나왔다.
당연히 이번에도 CORS 오류가 났기 때문에 트러블 슈팅의 과정을 정리해 보자 한다.</p>
<h3 id="문제1">문제1</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/df488164-49f7-4a77-91ea-d637a7c0b795/image.png"/>

<p>네이버 api를 그대로 사용하게 된다면 나오게 되는 CORS 오류이다.
분명 headers 설정도 잘 해주었고 요청 URL도 정상인데 왜 오류가 날까...</p>
<p>그래서 서칭을 해보았고, 나와 같은 문제를 겪은 개발자 분들이 이미 네이버 개발 센터에 문의를 올려서 답변을 기다리고 있었다.</p>
<p>하지만 돌아오는 <strong>답변은 없었기 때문에</strong> 문제가 뭔지 모르겠다</p>
<h3 id="문제1-해결-방법">문제1 해결 방법</h3>
<pre><code class="language-javascript">const nextConfig = {
  reactStrictMode: false,

  ...
  async rewrites() {
      return [
        {
          source: &#39;/:path*&#39;,
          destination: &#39;https://openapi.naver.com/:path*&#39;
        }
    ]
  }
}

module.exports = nextConfig;</code></pre>
<p>이 코드는 어떤 경로로든 요청을 보내면, 그 요청은 네이버 API로 전달되는 코드이다.</p>
<p><a href="https://nextjs.org/docs/pages/api-reference/next-config-js/rewrites">rewrites</a>되는 요청이 네이버 API에 직접 접근하는 것처럼 보이지만, 실제로는 Next.js 서버에서 해당 API로 <strong>프록시 요청</strong>을 처리하게 된다.</p>
<p>결론은 <strong>rewrites</strong>를 사용하여 CORS 문제를 피할 수 있다.</p>
<p>하지만 해결된 줄 알았던 CORS 문제는 404페이지를 작업하면서 <strong>또다른 문제</strong>를 직면할 수 있었다.</p>
<h3 id="문제2">문제2</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/324a2a68-58b7-431a-9790-7480ffa71fe0/image.png"/>

<blockquote>
<p>위 코드의 활용법을 제대로 알지 못하고 적용한 오류이다.
어떤 경로로든 요청을 보내면 네이버 API로 URL을 바꿔버리기 때문에 404로 가는 요청도 <a href="https://openapi.naver.com/details%EA%B0%80">https://openapi.naver.com/details가</a> 되어버린다.</p>
</blockquote>
<h3 id="문제2-해결-방법">문제2 해결 방법</h3>
<pre><code class="language-javascript">const nextConfig = {
  reactStrictMode: false,

  ...
  async rewrites() {
      return [
      {
        source: &#39;/v1/:path*&#39;,
        destination: &#39;https://openapi.naver.com/v1/:path*&#39;,
      },
    ]
  }
}

module.exports = nextConfig;</code></pre>
<p>이런 식으로 /v1으로 요청을 보냈을 때만 rewrites 되도록 코드를 수정하면 된다.</p>
<p>나랑 똑같은 문제를 겪고 트러블슈팅을 기록한 블로그이다. 도움 주셔서 감사합니다😊
<a href="https://happytape.tistory.com/63">https://happytape.tistory.com/63</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 지도 이동 시 렌더링 오류 해결]]></title>
            <link>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%A7%80%EB%8F%84-%EC%9D%B4%EB%8F%99-%EC%8B%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%A7%80%EB%8F%84-%EC%9D%B4%EB%8F%99-%EC%8B%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 15 Aug 2024 06:35:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/6caba7a9-33ee-4255-b53b-b717c1a1bef0/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>이번 프로젝트 이전에 카카오 지도를 사용할 때는 아주 간단한 작업만 진행하거나, 선임분께서 작업한 코드를 복붙하는 식으로 진행했었다.</p>
<p>그래서 이번 프로젝트에서는 카카오 지도의 기능 중 마커와 클러스터러를 사용해보며 겪었던 오류에 대해 남겨보기로 하였다.</p>
<h3 id="문제1">문제1</h3>
<p><img src="https://velog.velcdn.com/images/d_hyeon/post/0757ab0e-cb6c-4ad3-9be9-b6f590217f65/image.png" alt=""></p>
<p>바로 처음 겪었던 문제는 <strong>react-kakao-maps-sdk</strong>를 사용할지 말지 정하는 문제였다.
<strong>업데이트 주기</strong>도 괜찮고 기능도 매우 간편해보이지만 <strong>다운로드 수</strong>가 은근 적기도 하였고, 나는 카카오 지도의 기능을 다 쓰지 않기 때문에 불필요한 기능을 위해 <strong>프로젝트 용량</strong>을 늘리기 싫었다.</p>
<p>결론은 react-kakao-maps-sdk를 <strong>쓰지 않기</strong>로 하였다!</p>
<h3 id="문제2">문제2</h3>
<p>다음으로 나타난 문제는 카카오 지도를 확대, 축소, 이동 시 처음 지정했던 좌표의 지도가 겹쳐보이는 오류가 나타나기 시작하였다.</p>
<p>바로 서칭을 해보았고, 해결 방법은 총 2가지가 있었다.</p>
<p>*<em>1. <a href="https://devtalk.kakao.com/t/topic/136060">React의 strict mode 때문에 2번씩 실행되어 겹쳐보인다.</a> *</em>
React의 strict mode 때문에 코드가 2번 실행되어 겹쳐보이는 문제가 생겼고, 이 문제에 대한 해결방법은 매우 쉽다.</p>
<pre><code class="language-javascript">/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  reactStrictMode: false,
};
export default nextConfig;</code></pre>
<p>strict mode를 false로 해두면 된다.</p>
<p><strong>2. <a href="https://devtalk.kakao.com/t/map/134182">카카오 지도가 렌더링을 2번 하기 때문에 겹쳐보인다.</a></strong>
이 문제를 겪었던 이유는 웹에서 사용자의 위치가 변경되었을 때 <strong>지도의 중앙 위치</strong>를 변경하기 위해서 위치 값 변경시 지도를 <strong>재렌더링</strong> 시키는 방법으로 코드를 작성했다.</p>
<p>하지만 당연하게도 재렌더링을 했기 때문에 이전에 있던 위치의 지도가 겹쳐보이는 현상이 나타나게 되었다.</p>
<p>해결 방법은 공식문서를 찾아보며 알게되었다.</p>
<p>카카오 지도에는 <a href="https://apis.map.kakao.com/web/documentation/#StaticMap_setCenter">지도의 중심 좌표를 변경하는 메서드</a>가 따로 있기 때문에 setCenter를 사용해주면 된다!</p>
<pre><code class="language-javascript">// 기존 코드
useEffect(() =&gt; {
  const container = document.getElementById(&#39;map&#39;);
  const options = {
    center: new window.kakao.maps.LatLng(location.lat, location.lng),
    level: 3,
  };

  new window.kakao.maps.Map(container, options);
}, [location]);

// 변경된 코드
useEffect(() =&gt; {
  const container = document.getElementById(&#39;map&#39;);
  const options = {
    center: new window.kakao.maps.LatLng(location.lat, location.lng),
    level: 3,
  };

  const newMap = new window.kakao.maps.Map(container, options);
  setMap(newMap);
}, []);

useEffect(() =&gt; {
  if (map &amp;&amp; location) {
    const moveLatLon = new window.kakao.maps.LatLng(location.lat, location.lng);
    map.setCenter(moveLatLon);
  }
}, [location, map]);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[RN 폰트 추가하기]]></title>
            <link>https://velog.io/@d_hyeon/RN-%ED%8F%B0%ED%8A%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@d_hyeon/RN-%ED%8F%B0%ED%8A%B8-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 07 Aug 2024 05:39:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/5b70cfa1-d0ec-45b1-8082-a7e95f52b671/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>RN으로 프로젝트를 진행하면서 제일 처음 궁금했던 점이다.
RN은 html 파일도 없고, css 파일도 만들지 못하는데 어떻게 폰트를 적용할까? 라는 생각이 들었고, 검색해보니 expo 공식문서에 자세히 나와있었다.
<a href="https://docs.expo.dev/develop/user-interface/fonts/">https://docs.expo.dev/develop/user-interface/fonts/</a></p>
<h3 id="폰트-적용-방법">폰트 적용 방법</h3>
<p>RN 프로젝트에 커스텀 폰트를 적용하는 방법은 2가지가 있다.</p>
<ol>
<li><strong>assets/fonts에 폰트 파일을 추가하는 방법</strong></li>
<li>Google Font 패키지를 설치하는 방법
나는 첫 번째 방법인 assets에 폰트 파일을 추가하는 방법으로 진행을 하였다.</li>
</ol>
<h3 id="폰트-적용-설정">폰트 적용 설정</h3>
<blockquote>
<ol>
<li>expo-font 설치</li>
</ol>
</blockquote>
<pre><code>npx expo install expo-font</code></pre><blockquote>
<ol start="2">
<li>_layout.tsx 설정</li>
</ol>
</blockquote>
<pre><code class="language-javascript">const [loaded, error] = useFonts({
  Pretendard: require(&#39;../assets/fonts/Pretendard-Medium-subset.otf&#39;),
  &#39;Pretendard-Bold&#39;: require(&#39;../assets/fonts/Pretendard-Bold-subset.otf&#39;),
});
if (!loaded &amp;&amp; !error) return null;</code></pre>
<blockquote>
<ol start="3">
<li>폰트 적용</li>
</ol>
</blockquote>
<pre><code class="language-javascript">// 인라인
&lt;Text style={{ fontFamily: &#39;Pretendard-Bold&#39; }}&gt;Pretendard Bold&lt;/Text&gt;</code></pre>
<pre><code class="language-javascript">// styled-components
const Text = styled.Text`
  font-family: &#39;Pretendard&#39;;
`;</code></pre>
<h3 id="ttf-보다-otf">TTF 보다 OTF</h3>
<p>expo 공식문서에서는 otf 파일이 ttf 파일이 용량이 더 작고, 때로는 otf가 렌더링이 더 잘되기 때문에 otf 사용을 지향하라고 나와있습니다. </p>
<blockquote>
<p><strong>How to choose between OTF and TTF</strong>
If the font you&#39;re using have both OTF and TTF versions, prefer OTF. The .otf files are smaller than .ttf files. Sometimes, OTF also renders slightly better in certain contexts.
출처: <a href="https://docs.expo.dev/develop/user-interface/fonts/">expo 공식문서</a></p>
</blockquote>
<img src="https://velog.velcdn.com/images/d_hyeon/post/b04c523c-509f-4891-87f2-c76c771cd576/image.png"/>

<p>출처: <a href="https://docs.expo.dev/develop/user-interface/fonts/">expo 공식문서</a>
<a href="https://velog.io/@d_hyeon/%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-13ceitvz">저번 글</a>에서 폰트 포맷 별 용량의 차이를 비교해 보았었다.
otf &gt; woff &gt; woff2 순으로 용량이 작기 때문에 woff2를 사용하고 싶어도 android에서 지원을 하지 않는다는 문제가 있다.
따라서 RN 프로젝트에서는 OTF 파일을 사용하는 것이 좋을 것 같다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RN에서 calc 사용하는 법]]></title>
            <link>https://velog.io/@d_hyeon/RN%EC%97%90%EC%84%9C-calc-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@d_hyeon/RN%EC%97%90%EC%84%9C-calc-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Tue, 06 Aug 2024 09:22:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/8457262d-35c0-4aab-b68c-69c6fece34d0/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>평소 width 값을 계산할 때 calc를 자주 썼었다.</p>
<p>이번에 RN 개인 포폴을 진행하면서 calc를 적용할 일이 생겨서 적용을 해봤다. Web에서는 제대로 적용이 되었지만 Android 애뮬레이터에서는 적용이 안되는 모습이 보였다.</p>
<p>그래서 구글링을 해보았고, RN에서는 calc를 지원하지 않는다고 스택오버플로우에서 말하였다.
<a href="https://stackoverflow.com/questions/66420791/error-when-using-calc-with-react-native-and-styled-components">https://stackoverflow.com/questions/66420791/error-when-using-calc-with-react-native-and-styled-components</a></p>
<h3 id="그럼-어떻게-calc를-대체해야-할까">그럼 어떻게 calc를 대체해야 할까?</h3>
<p>내가 사용하는 방법이 정답은 아니다.
나는 React Native의 Dimensions Api를 활용하였다.
Dimensions Api는 애플리케이션 창의 너비와 높이를 얻을 수 있는 Api로, 간단한 코드만으로 사용 가능하다.</p>
<h3 id="dimensions-api-적용법">Dimensions Api 적용법</h3>
<pre><code class="language-javascript">import {Dimensions} from &#39;react-native&#39;;

const windowWidth = Dimensions.get(&#39;window&#39;).width;
const windowHeight = Dimensions.get(&#39;window&#39;).height;</code></pre>
<p>Dimensions로 화면의 크기를 가져올 수는 있지만 유동적인 화면에서는 사용하지 못하는 것이 단점이다.</p>
<pre><code class="language-javascript">import {useWindowDimensions} from &#39;react-native&#39;;

const {height, width} = useWindowDimensions();</code></pre>
<p>이를 보완한 코드는 useWindowDimensions 훅으로, 화면 크기나 글꼴 크기가 변경되면 모든 값을 자동으로 업데이트한다.</p>
<p>위에서 설명한 Dimensions Api를 활용하여 기존 코드의 문제점을 파악 후 보완하여 코드를 변경하였다.</p>
<h3 id="기존-코드">기존 코드</h3>
<pre><code class="language-javascript">const Card = styled.View`
  width: calc(50% - 21px);
`</code></pre>
<h3 id="변경된-코드">변경된 코드</h3>
<pre><code class="language-javascript">export default function CardItem() {
  const { width } = useWindowDimensions();
  const cardWidth = width / 2 - 21;

  return &lt;Card width={cardWidth}/&gt;
}

const Card = styled.View&lt;{ width: number }&gt;`
  width: ${({ width }) =&gt; `${width}px`};
`</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pressable vs Button vs Touchable]]></title>
            <link>https://velog.io/@d_hyeon/Pressable-vs-Button-vs-Touchable</link>
            <guid>https://velog.io/@d_hyeon/Pressable-vs-Button-vs-Touchable</guid>
            <pubDate>Thu, 18 Jul 2024 12:18:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/4751f21f-7403-4b7e-b969-efe88dc4bb65/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>리액트 네이티브를 공부하면서 궁금한 점이 생겼다.
html은 button 태그가 있지만 RN(React Native)에서는 Pressable, Button, Touchable 같은 다양한 상호작용 감지 컴포넌트가 존재한다.</p>
<p>그럼 어떤 상황에 어떤 컴포넌트를 써야하는지에 대한 궁금증이 생기게 되었고,
이번 주제에서 각 컴포넌트의 특징과 차이에 대해 얘기해보자 한다.</p>
<h3 id="button">Button</h3>
<p><a href="https://reactnative.dev/docs/button">Button</a> 컴포넌트는 onPress 같은 간단한 상호작용만 지원하고, 기본 스타일이 있다는 특징이 있다.</p>
<p>기본 스타일을 제공한다는 것은 iOS, Android에서 별도의 스타일링 없이 일관된 스타일을 제공한다는 장점일 수도 있지만, 간단한 상호작용과 기본 스타일 제공으로 제한된 커스터마이징이라는 것이 단점이다.</p>
<pre><code>&lt;Button
  onPress={onPressLearnMore}
  title=&quot;Learn More&quot;
  color=&quot;#841584&quot;
  accessibilityLabel=&quot;Learn more about this purple button&quot;
/&gt;</code></pre><h3 id="touchable">Touchable</h3>
<p>Touchable 컴포넌트는 사용자의 터치 동작에 반응하고 상호작용을 하고, 시각적인 퍼포먼스를 제공하는 컴포넌트이다.
Touchable 컴포넌트는 <a href="https://reactnative.dev/docs/touchableopacity">TouchableOpacity</a>, <a href="https://reactnative.dev/docs/touchablehighlight">TouchableHighlight</a>, <a href="https://reactnative.dev/docs/touchablewithoutfeedback">TouchableWithoutFeedback</a> 등이 있다.</p>
<p>터치 동작에 반응하여 시각적인 효과를 주고싶을때 주로 사용하는 컴포넌트라고 한다.</p>
<pre><code>&lt;TouchableOpacity style={styles.button} onPress={onPress}&gt;
  &lt;Text&gt;Press Here&lt;/Text&gt;
&lt;/TouchableOpacity&gt;</code></pre><h3 id="pressable">Pressable</h3>
<p><a href="https://reactnative.dev/docs/pressable">Pressable</a> 컴포넌트는 터치 동작으로 반응하면 다양한 상호작용을 할 수 있는 것이 특징이다. 또한 스타일링 또한 자유로워서 RN에서 권장하는 터치 기반 컴포넌트이다.</p>
<blockquote>
<p>If you&#39;re looking for a more extensive and future-proof way to handle touch-based input, check out the Pressable API. 
<a href="https://reactnative.dev/docs/touchableopacity">RN 공식 문서</a></p>
</blockquote>
<p><img src="https://reactnative.dev/docs/assets/d_pressable_pressing.svg
" width="60%"/>
<a href="https://reactnative.dev/docs/pressable">사진 출처</a>
Pressable 컴포넌트는 다양한 단계에서 터치 상호작용을 할 수 있으며 감지 원리는 다음과 같다.</p>
<ul>
<li>onPressIn: press가 활성화되면 호출한다.</li>
<li>onPressOut: press가 비활성화되면 호출한다.</li>
<li>onPress: onPressOut 후 호출한다.</li>
<li>onLongPress: 500ms 이상 손가락을 누르면 호출한다.</li>
</ul>
<p>이 외에도 다양한 prop이 있으니 <a href="https://reactnative.dev/docs/pressable">공식 문서</a>에서 확인이 가능하다.</p>
<h3 id="결론">결론</h3>
<p>react를 기본적으로 알고 RN을 시작하면 러닝커브가 낮다고 해서 괜찮을 줄 알았다.
확실히 다른 언어들보단 러닝커브가 낮다고 생각하지만 react와 다른 점이 생각보다 많기 때문에 간단한 구현이라도 많은 시간 구글링과 공식문서를 찾아보면서 시간이 지나간 것 같다.</p>
<p>Pressable을 처음부터 써서 프로젝트를 진행하기에 큰 문제는 없지만 Button과 Touchable 컴포넌트 간에 차이는 확실히 알고 넘어가야한다고 생각하기 때문에 이번 포스트를 작성했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 폰트 최적화]]></title>
            <link>https://velog.io/@d_hyeon/%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-13ceitvz</link>
            <guid>https://velog.io/@d_hyeon/%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-13ceitvz</guid>
            <pubDate>Tue, 04 Jun 2024 09:16:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/94add8a4-638b-4076-bd24-eb66b7c7ed53/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>웹 퍼블리셔를 하다가 웹 개발을 배우며 느끼는 점은 <strong>화면 구현</strong> 보다는 <strong>최적화</strong>가 더 중요해졌다고 생각이 든다. 렌더링이 더 빨라지게, 이미지의 용량이 더 적어지게, 폰트의 용량이 더 적어지게 등 다양한 웹 최적화 방법이 있다.</p>
<p>이번 프로젝트에서 초기 셋팅을 하던 중 <code>서브셋 폰트</code>라는 말을 처음 듣게 되었고, 서브셋 폰트 검색을 시작으로, 최적화에 더 적합한 폰트의 형식 또한 알아가보았다.</p>
<h3 id="폰트-형식">폰트 형식</h3>
<ol>
<li><p><strong>TTF(True Type Font)</strong> </p>
<ul>
<li>애플 + 마이크로소프트에서 개발</li>
<li>비트맵 형식의 폰트 파일을 대체하기 위해 제어점이 3개인 2차 베지어 곡선의 형태로 개발되었다.</li>
<li>운영 체제가 광범위하게 지원이 된다.</li>
</ul>
</li>
<li><p><strong>OTF(Open Type Font)</strong></p>
<ul>
<li>어도비 + 마이크로소프트에서 개발</li>
<li>제어점이 3개인 TTF 보다 4개로 발전되었으며, 더욱 섬세한 작업이 가능하다.</li>
<li>TTF의 모든 기능을 사용 가능하며, 고해상도의 출력 작업이 필요할 때 사용한다.</li>
</ul>
</li>
<li><p><strong>WOFF</strong></p>
<ul>
<li>모질라 + 오페라 + 타입키트 + 마이크로소프트에서 개발</li>
<li>TTF, OTF와 동일하게 동작하지만, 더 작은 크기를 갖고 있다. </li>
<li>2012년에 W3C에 권장 사항으로 등록되었다</li>
</ul>
</li>
<li><p><strong>WOFF2</strong></p>
<ul>
<li><p>WOFF의 후속 버전이다. WOFF보다 파일을 압축해서 더 작은 크기를 갖고 있다.</p>
</li>
<li><p><em>결론은 WOFF2가 가장 파일 크기가 작고, 거의 모든 브라우저에서 호환이 가능하기 때문에 WOFF2 형식을 사용하는 것이 렌더링과 최적화 부분에서 가장 좋은 선택이다.*</em></p>
</li>
</ul>
</li>
</ol>
<h3 id="서브셋-폰트📕">서브셋 폰트📕</h3>
<p>서브셋 폰트란 웹 폰트에서 원하는 문자만 남기고 나머지 문자는 삭제한 폰트를 말한다.
갢, 갏, 갪, 갫 같은 글자는 사용하지 않기 때문에 삭제시켜도 문제가 없다.</p>
<p><strong>하지만 페이지에서 삭제된 글자를 출력하려 하면 제대로 출력 할 수 없다는 문제가 있다.</strong></p>
<blockquote>
<p><strong>파일 크기 비교</strong>
<img style="margin:0; margin-top: 10px;" src="https://velog.velcdn.com/images/d_hyeon/post/76bed8f7-a50c-4f6b-a31e-d146029028d8/image.png"/></p>
</blockquote>
<ul>
<li>ttf: 1130KB</li>
<li>서브셋 ttf: 932KB</li>
<li>서브셋 woff2: 173KB</li>
</ul>
<h3 id="서브셋-폰트-메이커🔨">서브셋 폰트 메이커🔨</h3>
<p>필요 없는 글자를 삭제시킨 서브셋 폰트가 있다.
하지만 만약 <span style="color: red;">삭제시킨 글자가 필요하다면</span> 어떻게 해야할까??</p>
<p><strong>답은 서브셋 폰트 메이커로 삭제시킨 글자를 추가한 서브셋 폰트를 만들면 된다.</strong></p>
<p>웹에서 사용할 수 있는 폰트는 매우매우 많지만 서브셋 폰트를 만들어서 지원하는 폰트는 많지 않기 때문에 서브셋 폰트 메이커를 사용하여 직접 서브셋 폰트를 만들어야한다.</p>
<h3 id="결론">결론</h3>
<p>원래 폰트를 적용할 때는 폰트의 형식과 상관없이 그냥 cdn으로 가져와서 사용했었다.
이번 포스트를 작성하면서 웹에서 적합한 폰트 형식이 어떤 건지 알게 되었다.
그리고 서브셋 폰트를 활용하면 기존 폰트보다 용량이 작아지기 때문에 서버의 부담도 덜 수 있다.</p>
<p>다음에는 폰트가 어떤 방법으로 웹에 렌더링 되는지 알아보는 시간을 가져야겠다😀</p>
<h4 id="참조">참조</h4>
<ul>
<li><a href="https://yozm.wishket.com/magazine/detail/2107/">https://yozm.wishket.com/magazine/detail/2107/</a></li>
<li><a href="https://songsong.dev/entry/%EC%84%9C%EB%B8%8C%EC%85%8B%ED%8C%85%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EA%B2%BD%EB%9F%89%ED%99%94-%ED%95%98%EA%B8%B0">https://songsong.dev/entry/%EC%84%9C%EB%B8%8C%EC%85%8B%ED%8C%85%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EA%B2%BD%EB%9F%89%ED%99%94-%ED%95%98%EA%B8%B0</a></li>
<li><a href="https://d2.naver.com/helloworld/4969726">https://d2.naver.com/helloworld/4969726</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 로그인 REST API]]></title>
            <link>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-REST-API</link>
            <guid>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-REST-API</guid>
            <pubDate>Fri, 31 May 2024 08:05:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/9d0e0a33-7595-40b1-82ee-601b95853763/image.png" alt=""></p>
<h3 id="서론">서론</h3>
<p>협업 프로젝트에서 로그인 파트를 맡게 되었다.
그 중 카카오 로그인을 구현하게 되었는데 카카오 로그인은 <code>REST API</code>와 <code>SDK</code> 방법이 있다고 한다. 이번 포스트에서는 REST API 방법에 대해 기록해본다.</p>
<h3 id="카카오-로그인이란">카카오 로그인이란?</h3>
<blockquote>
<p>카카오 로그인은 OAuth 2.0 기반의 소셜 로그인 서비스입니다. 카카오 로그인을 사용하면 사용자가 카카오톡 또는 카카오계정으로 손쉽게 서비스에 로그인할 수 있습니다. 서비스는 서비스 ID 및 비밀번호를 입력받고 검증하는 과정을 직접 구현하지 않고도 사용자에 대한 인증과 인가를 간편하고 안전하게 처리할 수 있습니다. <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common#intro-kakaologin">카카오API</a></p>
</blockquote>
<h3 id="rest-api">REST API</h3>
<p>REST API 방법은 카카오 REST API를 직접 호출하여 로그인 기능을 구현하는 방식이다.
REST API로 카카오 로그인을 구현하게 된다면, REST API에서 제공하는 카카오 로그인 페이지에서 로그인 하거나, &quot;<span style="background-color: #FEE500">카카오톡으로 로그인</span>&quot; 버튼을 클릭해서 카카오톡 앱으로 로그인 하여 사용이 가능하다.</p>
<p>REST API 방법을 사용하기 위해서는 사전에 설정해야할 것들이 있다.
설정에 관해서는 <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite">카카오 로그인 설정하기</a>를 참고하는 것이 좋다.</p>
<h3 id="rest-api-로그인-과정">REST API 로그인 과정</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/ed54b543-8af9-4eb5-8fcf-857995021732/image.png"/>

<p>위의 이미지는 <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#before-you-begin-process">카카오 API</a>에서 제공하는 로그인 과정을 나타낸 시퀀스 다이어그램이다.</p>
<p><strong>순서를 나열해보면</strong>
0. 프론트에서 로그인 버튼을 눌러서 로그인 URL로 이동한다.</p>
<ol>
<li>로그인 URL로 진입하면 백에서 카카오로 <code>인가 코드 요청</code>을 보낸다.</li>
<li>카카오 서버는 프론트로 <code>로그인 요청 화면</code>을 띄워준다.</li>
<li>사용자는 카카오 로그인을 한다.</li>
<li>카카오는 인가를 위한 사용자 동의를 요청하는 화면을 띄워준다.</li>
<li>사용자는 동의를 한다.</li>
<li>카카오 서버는 Redirect URI로 <code>인가 코드</code>를 보낸다.</li>
<li>백은 카카오에서 받은 인가코드로 <code>토큰을 요청</code>한다.</li>
<li>카카오는 백에서 받은 인가코드로 <code>토큰을 발급</code>해준다.</li>
<li>백은 카카오에서 받은 토큰으로 <code>사용자 정보 조회</code>를 한 후 로그인 완료 처리를 한다.</li>
</ol>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-javascript">export default function Login() {
  const REST_API_KEY = &quot;REST_API_KEY&quot;;
  const REDIRECT_URI = &quot;REDIRECT_URI&quot;;
  const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=code`;

  const loginHandler = () =&gt; {
    window.location.href = kakaoURL;
  };

  return (
    &lt;button type=&quot;button&quot; onClick={loginHandler}&gt;
      로그인 하기
    &lt;/button&gt;
  );
}</code></pre>
<p>예시 코드를 적용해보면 기존에 설정한 Redirect URI로 인가 코드가 들어오는 걸 볼 수 있다.
이후 코드는 백엔드 서버에서 카카오로 <a href="https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-token">토큰 받기 요청</a>을 보내고 받은 토큰으로 정보 조회를 하면 된다.</p>
<h3 id="결론">결론</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/cdc68fee-3835-4237-adb4-b4a42aa02297/image.png" style="margin: 0 auto;"/>

<p>이번 협업 프로젝트에서 카카오 로그인을 맡게 되면서 여러 이슈가 있었지만 일단 <span style="color: red">책임</span>에 대한 이슈가 컸던 것 같다.</p>
<p><strong>프론트</strong>에서 인가 코드를 받게 되면 사용자에게 <strong><span style="color: red">인가 코드가 노출</span></strong>하게 되어 보안에 취약하고, 
<strong>백</strong>에서 인가 코드를 받으면 사용자에게 보여지지 않기 때문에 <strong>보안에 대한 장점</strong>을 볼 수 있다.</p>
<p>여러 개발자 분들에게 여쭤보니 로그인 구현을 프론트가 하는지 백이 하는지 <strong>확실한 정답</strong>은 없는 것 같다.
이후 백엔드 개발자와 프로젝트를 하면서 어떻게 구현을 하는 것이 좋은 지 알아가면 좋을 것 같다😀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카카오 로그인 쿠키 공유 해결]]></title>
            <link>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%BF%A0%ED%82%A4-%EA%B3%B5%EC%9C%A0</link>
            <guid>https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%BF%A0%ED%82%A4-%EA%B3%B5%EC%9C%A0</guid>
            <pubDate>Fri, 31 May 2024 05:51:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/6805f95f-dd27-4369-b0d1-4529cbebf941/image.png" alt=""></p>
<h3 id="문제">문제</h3>
<p>카카오 로그인 방식은 <a href="https://velog.io/@d_hyeon/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-REST-API">REST API</a> 방식으로 진행을 하였다.
카카오 api 공식문서에서 제공해준 방법으로 백엔드에서 인가 코드와 토큰을 받고,
프론트에 넘겨주는 방법으로 진행을 하였다.</p>
<p>그런데 여기서 <strong><span style="color: red">문제</span></strong>가 생겼다.</p>
<p>프론트 배포 주소는 vercel로 배포하여 <code>배포주소.vercel.app</code>이었고,
백엔드 배포 주소는 <code>배포주소.click</code>으로 서로 다른 도메인 으로 쿠키를 전달 받아야했었다.</p>
<p>며칠간 검색을 하고서 알게 된 사실은 <strong>&quot;웹 브라우저는 도메인 단위로 쿠키를 관리하며, 서로 다른 도메인끼리는 쿠키 공유가 불가능하다&quot;</strong>는 것이었다.</p>
<h3 id="💻방법1">💻방법1</h3>
<p>먼저 프론트와 백의 도메인을 맞추기로 하였다.
프론트 배포 주소: <code>frontend.배포주소.click</code>
백엔드 배포 주소: <code>backend.배포주소.click</code></p>
<p>이후 백엔드에서 쿠키를 <code>frontend.배포주소.click</code>으로 전달했더니 값이 잘 오질 않았다.</p>
<h3 id="💻방법2">💻방법2</h3>
<p>백엔드에서 <code>frontend.배포주소.click</code>으로 쿠키를 전달했던걸 <code>배포주소.click</code>에 전달하는 방법으로 코드를 변경하고, 다시 실행했더니 이번에는 프론트에 쿠키가 잘 받아와졌다!</p>
<h3 id="왜❓">왜❓</h3>
<p><code>frontend.배포주소.click</code>으로 쿠키 전달은 안되고, 
<code>배포주소.click</code>으로 쿠키 전달은 잘 된걸까?</p>
<p><strong>&quot;쿠키는 도메인이 같더라도 서브 도메인끼리는 공유가 불가능하다&quot;</strong> 라는 것이다.
상위 도메인으로부터 서브 도메인으로 쿠키 전달은 가능하지만
<code>frontend.배포주소.click</code>과 <code>backend.배포주소.click</code>같이 서브 도메인이 다른 도메인끼리는 아예 다른 도메인으로 취급을 해버린다. 그렇기 때문에 방법1은 실패했던 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. Framer Motion 기초]]></title>
            <link>https://velog.io/@d_hyeon/1.-Framer-Motion-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@d_hyeon/1.-Framer-Motion-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 17 May 2024 10:49:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/b4a9147d-2bd9-4954-8254-9ba5d84e6d90/image.png" alt=""></p>
<h3 id="framer-motion이란">Framer Motion이란?</h3>
<p>framer motion이란 단순하면서도 강력한 React <strong>애니메이션 라이브러리</strong>이다.
현재 React 19버전은 지원하지 않고 있다고 한다.
<a href="https://www.framer.com/motion/">https://www.framer.com/motion/</a></p>
<h3 id="시작하기">시작하기</h3>
<pre><code class="language-node">npm install framer-motion</code></pre>
<h3 id="사용법">사용법</h3>
<p>framer motion으로 기본적인 애니메이션을 넣는 방법은 단순한 편이다.</p>
<pre><code class="language-javascript">// js
import { motion } from &quot;framer-motion&quot;;
import styles from &quot;./index.module.css&quot;;

export default function AnimateFirst() {
  return &lt;motion.div className={styles.div}&gt;박스1&lt;/motion.div&gt;;
}</code></pre>
<pre><code class="language-css">/* css */
.div {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100px;
  height: 100px;
  background-color: red;
  color: #fff;
  font-weight: 700;
  margin: 100px;
}</code></pre>
<blockquote>
<p>import로 불러온 후 애니메이션을 추가할 태그를 motion 컴포넌트로 변경해주면 준비가 끝난다.</p>
</blockquote>
<h3 id="기본-옵션">기본 옵션</h3>
<pre><code class="language-HTML">  &lt;motion.div
    className={styles.div}
    initial={{ y: &quot;-300px&quot; }}
    animate={{ y: 0 }}
    transition={{
      duration: 3,
    }}
   &gt;
      박스1
  &lt;/motion.div&gt;</code></pre>
<p>motion 컴포넌트의 기본 옵션으로는 <strong>initial</strong>과 <strong>animate</strong>가 있다.</p>
<ul>
<li><p><strong>initial</strong>은 마운트 되었을 때 애니메이션을 설정하는 옵션이다. 만약 마운트 되었을 때 애니메이션을 넣고싶지 않다면 false로 설정해두면 된다.</p>
</li>
<li><p><strong>animate</strong>는 애니메이션이 끝나는 지점이다.
initial과 animate에 여러 값을 설정할 수 있기 때문에 사용자는 쉽게 원하는 애니메이션을 만들 수 있다.</p>
</li>
</ul>
<blockquote>
<p><strong>Refer</strong>: initial을 설정하지 않고 animate 값만 넣어도 애니메이션 실행이 된다.</p>
</blockquote>
<pre><code class="language-javascript">transition={{
        // type: &quot;spring&quot;, // 애니메이션이나 트랜지션의 종류를 정의할 때 사용
        // duration: 3, // 애니메이션이 실행되는 시간
        // delay: 0.5, // 지연 시간 설정
        // stiffness: 80, // 탄력도
        // mass: 0.3, // 질량을 뜻함. 질량이 클 수록 애니매이션이 느리고, 작을수록 빠름
        // damping: 8, // 진동을 줄이는 데 사용. damping이 없다면 요소가 계속 진동을 함
        // when: &quot;beforeChildren&quot;, // 부모의 애니메이션이 시작 후 자식 애니메이션 진행
        // staggerChildren: 0.4, // 자식들이 0.4초 주기로 애니메이션 실행 ex) 0.4 -&gt; 0.8 -&gt; 1.2
           // ...
}}</code></pre>
<ul>
<li><strong>transition</strong>은 애니메이션을 적용할 때 사용되는 애니메이션 유형을 적용하는 옵션이다.
더 많은 옵션을 알고싶다면 공식문서로 가서 옵션을 확인해보면 좋다.</li>
</ul>
<hr/>

<h3 id="결론">결론</h3>
<img style="margin: 0 auto" src="https://velog.velcdn.com/images/d_hyeon/post/3c46270e-db96-42a6-8269-db0d17bb803a/image.png"/>

<p>프론트엔드를 배우기 전에는 제이쿼리로 쉽게 애니메이션을 만들었는데 리액트에서는 fadeToggle 조차 힘들게 만드는 것 같다. 그래서 이번에 알게된 framer motion은 리액트에서도 애니메이션을 쉽게 만들 수 있게 해주는 라이브러리이기 때문에 다음에 할 프로젝트나 개인 프로젝트에서 무조건 사용해보고 싶다.</p>
<p>오늘 작성한 내용은 framer motion의 기능 중 아주 기초만 메모했다.
공식 문서를 보면 scroll 이벤트, 3D, 커스텀 훅 등 여러 기능들이 많아서 기대가 된다👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴파운드 컴포넌트 패턴]]></title>
            <link>https://velog.io/@d_hyeon/%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@d_hyeon/%EC%BB%B4%ED%8C%8C%EC%9A%B4%EB%93%9C-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 17 May 2024 09:39:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/d_hyeon/post/11c577ba-daf9-4fcb-bed5-cbed308053b0/image.png" alt=""></p>
<h2 id="서론">서론</h2>
<p>매일 하는 팀미팅을 진행하면서 한 가지 단어를 알게 되었다.
<strong>디자인 패턴</strong>이라는 단어이다.</p>
<p>디자인 패턴이 뭐지? 디자인 구조를 잘 잡기 위한 디자인의 기술 중 하나인가? 라는 생각이 들었고 바로 구글링을 해보았다.</p>
<p><strong>디자인 패턴</strong>은 &quot;기존 환경 내에서 반복적으로 일어나는 문제들을 어떻게 풀어나갈 것인가에 대한 일종의 솔루션&quot;이라고 한다.</p>
<p>그리고 디자인 패턴은 자바스크립트에서만 활용하는 것이 아닌 다른 프로그래밍 언어에서도 다양한 디자인 패턴이 있었고, 그 중에서 React 디자인 패턴에 대해 검색해보았다.</p>
<p>React에서 많이 사용하는 디자인 패턴 중 하나는 <strong>Compound Components 패턴</strong>이라고 한다. Compound Components 패턴은 기존에 사용하던 컴포넌트에 Prop을 내려주던 방법과는 다른 생소한 방법이라 현업에서 사용하는 지 멘토님께 여쭤보았고 멘토님의 대답은 자주 사용하는 패턴이라 하여 <strong>Compound Components 패턴</strong>에 대해 알아보고 얕게나마 사용해보았다.</p>
<h3 id="compound-components-패턴">Compound Components 패턴</h3>
<p>단어가 너무 길어서 <strong>컴파운드 패턴</strong>이라고 말하겠다.</p>
<blockquote>
<p>컴파운드 패턴은 &quot;<strong>하나의 컴포넌트를 여러 가지 집합체로 분리한 뒤 분리된 각 컴포넌트에서 사용하는 쪽에서 조합해 사용하는 방식</strong>&quot;이라고 한다. </p>
</blockquote>
<p>말만 들으면 너무 어렵다..
그래서 팀원 분께 여쭤보았고, 아토믹 패턴을 참고하면 좋다고 말씀하셨다.</p>
<blockquote>
<h3 id="아토믹-패턴">아토믹 패턴</h3>
<img src="https://velog.velcdn.com/images/d_hyeon/post/bc750183-ef4d-47e9-a1f3-d698c3fe3e03/image.png"/>
<strong>아토믹 패턴</strong>을 딥하게 들어가지 않기 위해 개념만 말하면 코드의 가독성과 확장성을 위해 사용하는 디자인 패턴이며 원자, 분자, 유기체, 템플릿, 페이지로 나누어져 있다.
</blockquote>
<pre>
1. 원자: 제일 작은 단위를 말하며 button, label, input과 같은 기본적인 HTML 태그들을 말한다.
2. 분자: label, input, button이 모여서 form을 만드는 것을 분자라고 한다.
3. 유기체: 검색 분자, 메뉴 분자 같은 기능들이 모아져서 만들어진 것을 유기체라 한다.
4. 템플릿: 유기체들이 모인 상태들이며 아직 디자인이나 사용자 관점에서 개선되지 않은 상태를 말한다.
5. 페이지: 템플릿을 사용자 관점에서 ui, ux, 기능들을 개선시킨 것을 말한다.
</pre>
<p>아 그럼 컴파운드 패턴은 작은 원자 단위로 컴포넌트를 나누고 레고 같이 컴포넌트를 조합해 나가는 방식이구나 라는 생각이 들었다.</p>
<h3 id="기존-문제점">기존 문제점</h3>
<p>기존에 사용하던 코드 작성법은 컴포넌트에 prop을 전달해주고 그 값을 받아서 화면을 구성하는 방법을 사용했었다.</p>
<p>하지만 이런 방법은 prop의 수가 적다면 괜찮겠지만 전달 받아야할 prop의 수가 많아질수록 가독성이 안 좋아지고, 컴포넌트가 무거워지게 된다는 단점이 있다.</p>
<pre><code class="language-html">  &lt;form&gt;
    &lt;Label htmlFor=&quot;username&quot;&gt;Username:&lt;/Label&gt;
    &lt;Input
      id=&quot;username&quot;
      type=&quot;text&quot;
      value={username}
      onChange={(event) =&gt; setUsername(event.target.value)}
    /&gt;
  &lt;/form&gt;</code></pre>
<p>Label과 Input이라는 컴포넌트를 만들고 코드의 재사용성을 위해 id, type, value, onChange라는 데이터를 prop으로 받는 코드이다.</p>
<p>지금은 괜찮지만 넘겨야할 prop이 많아질수록 가독성과 유지보수하기 힘든 상황이 생길 것이다.</p>
<h3 id="개선-코드">개선 코드</h3>
<p>컴파운드 패턴은 최상위 컴포넌트에서 context로 데이터를 관리하고 하위 요소에게 전달되는 형식으로 진행된다.</p>
<pre><code class="language-javascript">const FormContext = createContext();

export function Form({ children }) {
  const [inputValue, setInputValue] = useState({
    id: &quot;&quot;,
    password: &quot;&quot;,
  });

  const setValue = (name, value) =&gt; {
    setInputValue({
      ...inputValue,
      [name]: value,
    });
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    console.log(inputValue);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;FormContext.Provider value={{ inputValue, setValue }}&gt;
        {children}
      &lt;/FormContext.Provider&gt;
    &lt;/form&gt;
  );
}

function FormInput({ type, id }) {
  const { inputValue, setValue } = useContext(FormContext);
  const handleInput = (e) =&gt; {
    setValue(id, e.target.value);
  };
  return (
    &lt;input
      id={id}
      type={type}
      value={inputValue[id]}
      onChange={handleInput}
    /&gt;
  );
}

function FormLabel({ htmlFor, children }) {
  return (
    &lt;label htmlFor={htmlFor}&gt;
      {children}
    &lt;/label&gt;
  );
}

Form.Input = FormInput;
Form.Label = FormLabel;</code></pre>
<p><strong>로그인 폼을 만드는 예제이고, id Input, password Input을 만들기 위해 코드를 추가했다.</strong>
재사용성을 위해 <strong>아토믹 패턴</strong>에서 봤던 분자 단위로 모든 컴포넌트를 나누어서 코드를 작성한다.</p>
<pre><code class="language-javascript">import { Form } from &quot;./compound&quot;;

export default function Compound() {
  return (
    &lt;Form&gt;
      &lt;Form.Label htmlFor=&quot;id&quot;&gt;ipnut : &lt;/Form.Label&gt;
      &lt;Form.Input type=&quot;text&quot; id=&quot;id&quot; /&gt;
      &lt;Form.Label htmlFor=&quot;password&quot;&gt;password : &lt;/Form.Label&gt;
      &lt;Form.Input type=&quot;password&quot; id=&quot;password&quot; /&gt;
    &lt;/Form&gt;
  );
}</code></pre>
<p>위와 같은 코드로 이전 코드에서 export 했던 Form을 불러오고 코드를 작성하면 가독성이 기존 코드 작성 방법보다 가독성이 향상된다는 장점이 있다.</p>
<p>prop을 사용하긴 하지만 prop 활용의 수가 더 적어질 수 있다.</p>
<h3 id="단점">단점</h3>
<p>컴파운드 패턴의 좋은 부분만 보여줬지만 무조건 긍정적인 효과만 주는 것이 아니다.</p>
<p>컴파운드 패턴의 주요 장점으로 가독성을 말했지만 Form 컴포넌트의 내부를 볼 때 전체를 다 봐야하기 때문에 사람에 따라 <strong>가독성</strong>이 떨어질 수 있다는 단점이 있다.</p>
<p>그리고 <strong>코드의 양</strong>이 기존보다 많아지고, <strong>구현 난이도</strong>가 올라가기 때문에 작업 속도가 느려질 수 있다는 단점이 있다.</p>
<h3 id="결론">결론</h3>
<img style="margin: 0 auto" src="https://velog.velcdn.com/images/d_hyeon/post/65098d4d-a790-48c7-8cdc-02c2eeb0f562/image.png"/>

<p>항상 prop을 내려주는 방법으로 코드를 작성했었다.
이번에 컴파운드 패턴을 알아보고, 사용해 보면서 코드 작성의 고정관념에 갇혀 공부하고 있다고 생각이 들었다.</p>
<p>그리고 현업에서도 자주 사용하는 방법이라고 하니 지금부터라도 사용을 해보는 습관을 갖고 한 가지 방법의 코드 작성이 아니라 다른 방법도 찾아보고 상황에 맞게 사용해야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제어 컴포넌트와 비제어 컴포넌트]]></title>
            <link>https://velog.io/@d_hyeon/%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@d_hyeon/%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Thu, 16 May 2024 09:25:25 GMT</pubDate>
            <description><![CDATA[<h2 id="제어-컴포넌트">제어 컴포넌트</h2>
<ul>
<li>제어 컴포넌트는 form의 사용자 입력값을 제어 컴포넌트가 제어하는 것을 말한다.</li>
<li>데이터 관리를 react에서 한다.</li>
<li>state onChange로 값을 관리하기 때문에 input에 값을 입력할 때마다 데이터가 갱신되고, 리렌더링이 발생한다.</li>
</ul>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code class="language-javascript">function App() {
  const [inputValue, setInputValue] = useState(&quot;&quot;);
  return (
    &lt;form&gt;
      &lt;label htmlFor=&quot;test&quot;&gt;input : &lt;/label&gt;
      &lt;input
        name=&quot;test&quot;
        type=&quot;text&quot;
        value={inputValue}
        onChange={(e) =&gt; setInputValue(e.target.value)}
      /&gt;
    &lt;/form&gt;
  );
}</code></pre>
<h2 id="비제어-컴포넌트">비제어 컴포넌트</h2>
<ul>
<li>비제어 컴포넌트는 form의 사용자 입력값을 제어하지 않는다.</li>
<li>데이터 관리를 DOM에서 한다.</li>
<li>따로 state를 관리하지 않기 때문에 값을 입력할 때 리렌더링이나 데이터가 갱신되지 않는다.</li>
<li>특정 구간에 DOM에서 데이터를 갱신한다</li>
</ul>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code class="language-javascript">function App() {
  const inputRef = useRef();
  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    console.log(inputRef.current.value);
  };
  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;label htmlFor=&quot;test&quot;&gt;input : &lt;/label&gt;
      &lt;input name=&quot;test&quot; type=&quot;text&quot; ref={inputRef} /&gt;
    &lt;/form&gt;
  );
}</code></pre>
<hr/>

<h3 id="상황에-따른-비교">상황에 따른 비교</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/d_hyeon/post/cd9ca887-3a49-45f1-b473-c7e605541657/image.png" alt=""></p>
</blockquote>
<ul>
<li>제어 컴포넌트는 값이 변경될 때마다 데이터가 갱신되기 때문에 즉각적인 유효성 검사가 된다는 점이 장점이다.</li>
<li>비제어 컴포넌트는 즉각적인 유효성 검사는 못하지만 값이 변경되더라도 불필요한 렌더링이 되지 않기 때문에 즉각적인 데이터 반영이 필요하지 않는 컴포넌트에서 사용하기 좋다.</li>
</ul>
<h4 id="참고">참고</h4>
<p><a href="https://www.youtube.com/watch?v=PBgQKK6nelo">https://www.youtube.com/watch?v=PBgQKK6nelo</a>
<strong>[10분 테코톡] 세인의 제어 컴포넌트와 비제어 컴포넌트</strong>를 참고하여 작성하였습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 위임과 버블링]]></title>
            <link>https://velog.io/@d_hyeon/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%9C%84%EC%9E%84%EA%B3%BC-%EB%B2%84%EB%B8%94%EB%A7%81</link>
            <guid>https://velog.io/@d_hyeon/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%9C%84%EC%9E%84%EA%B3%BC-%EB%B2%84%EB%B8%94%EB%A7%81</guid>
            <pubDate>Thu, 09 May 2024 13:43:41 GMT</pubDate>
            <description><![CDATA[<h3 id="이벤트-위임">이벤트 위임</h3>
<ul>
<li>이벤트 위임은 하위 요소들의 공통 상위 요소를 갖는 요소에게 이벤트를 주는 방법이다.
상위 요소에 이벤트를 설정하면 어떤 하위 요소를 클릭하더라도 상위 요소의 이벤트를 위임 받아 설정된 이벤트가 작동될 수 있다.</li>
</ul>
<h3 id="이벤트-버블링">이벤트 버블링</h3>
<ul>
<li>버블링은 이벤트 핸들러가 걸린 요소를 클릭했을때 해당 요소를 시작으로 부모 요소, body, html 최상단 요소까지 핸들러가 반복되는 현상을 말한다. addEventListener의 capture가 false로 되어있거나 공백으로 채워있다면 버블링이 작동한다.</li>
</ul>
<h3 id="예시">예시</h3>
<blockquote>
<p><strong>HTML Code</strong></p>
</blockquote>
<pre><code class="language-html">    &lt;ul class=&quot;parent&quot;&gt;
      &lt;li class=&quot;one&quot;&gt;자식1&lt;/li&gt;
      &lt;li class=&quot;two&quot;&gt;자식2&lt;/li&gt;
      &lt;li class=&quot;three&quot;&gt;자식3&lt;/li&gt;
    &lt;/ul&gt;</code></pre>
<blockquote>
<p><strong>JS Code</strong></p>
</blockquote>
<pre><code class="language-javascript">    document
      .querySelector(&quot;.one&quot;)
      .addEventListener(&quot;click&quot;, function (event) {
      console.log(event.target.innerText);
    });
    document
      .querySelector(&quot;.two&quot;)
      .addEventListener(&quot;click&quot;, function (event) {
      console.log(event.target.innerText);
    });
    document
      .querySelector(&quot;.three&quot;)
      .addEventListener(&quot;click&quot;, function (event) {
      console.log(event.target.innerText);
    });</code></pre>
<ul>
<li>이벤트 위임과 버블링을 사용하지 않고 li의 텍스트를 출력하는 방법이다.</li>
<li>코드의 양이 많고 반복적인 작업이 있다는 것이 특징이다.</li>
<li>새로운 li 요소가 추가된다면 코드를 추가적으로 작성해야한다.</li>
</ul>
<blockquote>
<p><strong>JS Code</strong></p>
</blockquote>
<pre><code class="language-javascript">    const parent = document.querySelector(&quot;.parent&quot;);
    function handleClick(event) {
      if (event.target.tagName === &quot;LI&quot;) {
        console.log(event.target.innerText);
      }
    }
    parent.addEventListener(&quot;click&quot;, handleClick);</code></pre>
<ul>
<li>이벤트 위임과 버블링을 활용하여 코드를 고친 예시이다.</li>
<li>ul의 이벤트 위임으로 li를 클릭하면 이벤트를 할당한다.</li>
<li>li가 클릭되면 이벤트가 버블링되어 부모 요소인 ul에서 이벤트를 감지하고 처리가 가능하다.</li>
<li>위 코드와 같은 기능을 하지만 가독성이 좋고 코드의 양이 줄어들었다.</li>
</ul>
<hr/>

<h3 id="완성본">완성본</h3>
<blockquote>
<ul>
<li><strong>실행 화면</strong>
<img src="https://velog.velcdn.com/images/d_hyeon/post/95a3be41-a1b7-4e18-aa5e-f93737b9e5db/image.gif" alt=""></li>
</ul>
</blockquote>
<ul>
<li><strong>Console</strong>
<img src="https://velog.velcdn.com/images/d_hyeon/post/e5229b49-6beb-4b11-b25a-7a1da8b072e8/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[useMutation, useQuery의 차이]]></title>
            <link>https://velog.io/@d_hyeon/useMutation-useQuery%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@d_hyeon/useMutation-useQuery%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Wed, 08 May 2024 16:20:39 GMT</pubDate>
            <description><![CDATA[<h3 id="usemutation">useMutation</h3>
<ul>
<li>데이터를 추가(POST), 수정(PUT), 삭제(DELETE)할 때 사용한다.</li>
<li>비동기 함수를 실행하여 성공과 실패 여부를 확인하고, 이에 따라 화면을 구성한다.</li>
<li>useMutation은 직접 호출을 해야만 서버로 요청을 보낼 수 있다.</li>
<li>invalidateQueries를 활용하여 데이터가 수정된다면 화면에 바로 업데이트 된다.</li>
<li>onSuccess, onError, onSettled를 활용하여 요청 후 실행할 콜백 함수를 정할 수 있다.</li>
</ul>
<h3 id="usequery">useQuery</h3>
<ul>
<li>데이터를 가져올(GET) 때 사용한다.</li>
<li>useQuery의 반환 값인 isPending, isLoading으로 상태를 파악하여 화면 구성이 가능하다.</li>
<li>컴포넌트가 렌더링될 때 자동으로 서버에 요청을 보낸다. 컴포넌트가 마운트 될 때 한 번만 실행한다.</li>
</ul>
<blockquote>
<p><strong>Query Key</strong>
query key는  tanstack query에서 매우 중요한 부분을 차지한다.
key를 사용해서 데이터를 캐싱, 관리하기 때문에 신중한 관리가 필요하다.</p>
</blockquote>
<ul>
<li>key 값으로 데이터를 관리하기 때문에 key 값은 유니크한 값으로 지정해야하며 다른 key 값과 중복해서 사용하면 안된다.</li>
<li>key 값을 배열로 사용하여 의도를 명확하게 하고, 여러 값을 조합하여 key를 유연하게 관리할 수 있다.</li>
<li>key 값은 값이 변경될 때 데이터를 다시 불러오기 때문에 필터 같이 데이터가 자주 변하는 기능에는 변수로 key 값을 지정해줘야 한다.</li>
<li>key 값의 유지 보수와 충돌 방지를 위해 개별 파일을 사용하여 관리하는 것이 유용하다.</li>
<li>key를 객체로 관리하면 순서에 상관이 없지만 배열로 관리하면 값이 같더라도 순서가 다르면 다른 key로 인식하기 때문에 순서에 유의해야한다.<pre><code class="language-javascript">// 예시
// 다음 세 가지는 모두 같은 쿼리로 인식한다
useQuery({ queryKey: [&#39;posts&#39;, { username, userEmail }], ... });
useQuery({ queryKey: [&#39;posts&#39;, { userEmail, username }], ... });
useQuery({ queryKey: [&#39;posts&#39;, { userEmail, username, other: undefined }], ... });
// 다음 세 가지는 모두 다른 쿼리로 인식한다
useQuery({ queryKey: [&#39;posts&#39;, username, userEmail], ... });
useQuery({ queryKey: [&#39;posts&#39;, userEmail, username], ... });
useQuery({ queryKey: [&#39;posts&#39;, undefined, userEmail, username], ... });</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[서버 상태와 클라이언트 상태]]></title>
            <link>https://velog.io/@d_hyeon/%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%83%81%ED%83%9C</link>
            <guid>https://velog.io/@d_hyeon/%EC%84%9C%EB%B2%84-%EC%83%81%ED%83%9C%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%83%81%ED%83%9C</guid>
            <pubDate>Tue, 07 May 2024 16:26:52 GMT</pubDate>
            <description><![CDATA[<h3 id="1-서버-상태와-클라이언트-상태">1. 서버 상태와 클라이언트 상태</h3>
<p><strong>서버 상태</strong>란 서버에서 관리되는 데이터를 의미한다.
데이터베이스, 웹 서버 메모리에 저장되고 상태가 관리되는 특징이 있다.
보통 벡엔드에서 받아오는 api json 파일이 백엔드 서버에서 관리된다.
예시로는 사용자 목록, 유저 데이터 등 데이터베이스에서 관리되는 상태를 서버 상태라고 한다.</p>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code class="language-javascript">{
  &quot;user&quot;: {
    &quot;id&quot;: 1,
    &quot;username&quot;: &quot;example_user&quot;,
    &quot;email&quot;: &quot;user@example.com&quot;,
    &quot;bio&quot;: &quot;Software engineer interested in web development.&quot;,
    &quot;followers&quot;: 500,
    &quot;following&quot;: 200
  }
}</code></pre>
<p><strong>클라이언트 상태</strong>란 클라이언트에서 관리하고 유지되는 데이터를 말한다.
웹 페이지에서 모달이 열렸는지, 버튼이 눌렸는지 등 웹 페이지에서 보여지는 데이터를 클라이언트 상태라고 한다. 
<strong>클라이언트 상태</strong>는 브라우저에 저장되는 쿠키, 로컬이나 세션 스토리지 등 다양한 방법으로 관리된다.
당장 생각이 든 예시는 fetch된 데이터를 변수에 담아주고 관리한다면 이 변수도 클라이언트 상태의 예시일 것 같다.</p>
<h3 id="2-tanstack-query가-상태-관리에-좋은-이유">2. TanStack Query가 상태 관리에 좋은 이유</h3>
<p><strong>1. 캐시 제어가 가능하다.</strong>
<strong>TanStack Query</strong>는 캐시 제어가 가능하다. 
캐시란 데이터를 미리 복사해 놓는 임시 장소를 말한다. 저장 공간이 작지만 데이터를 가져오는 속도가 매우 빠르다는 장점이 있다. 
사용자가 같은 홈페이지를 반복해서 이용한다면 데이터를 다시 요청하지 않는한 캐시에 데이터를 저장하고 저장해놓은 데이터를 보여주는 &quot;캐싱&quot;이라는 방법을 활용할 수 있다.</p>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code class="language-javascript">// 데이터 가져오는 비동기 함수 설정
const fetchData = async () =&gt; {
  const response = await fetch(&#39;api/data&#39;);
  if (!response.ok) {
    throw new Error(&#39;Failed to fetch data&#39;);
  }
  return response.json();
};
// fetchData 사용
const Test = () =&gt; {
    const { data, isLoading, isError } = useQuery(&#39;myData&#39;, fetchData);
}</code></pre>
<p><strong>useQuery</strong>는 <strong>첫 번째</strong> 인자로 쿼리 키를 받고, <strong>두 번째</strong> 인자로는 데이터를 가져오는 fetchData를 받는다. fetchData에서 성공적으로 데이터를 가져온다면 data라는 변수로 값이 저장된다.
여기서 첫 번째 인자로 받은 쿼리 키를 활용하여 캐시된 데이터를 확인, 사용이 가능하다.
데이터를 가져올 때 myData라는 쿼리 키에 대한 캐시가 존재한다면 캐시에 저장되어 있는 데이터를 활용하여 업데이트가 된다.</p>
<p><strong>2. 오프라인 지원</strong>
<strong>TanStack Query</strong>는 오프라인 상태에서도 웹 동작이 가능하고, 선캐시를 통해 빠른 로딩으로 사용자의 경험을 향상시킬 수 있다.
예시로 사용자가 오프라인 상태에서 접근하게 된다면 TanStack query는 자동으로 웹 스토리지에 저장된 캐시 데이터를 활용하여 UI를 업데이트하고 상호작용이 가능하게 한다.</p>
<h4 id="결론">결론</h4>
<p>TanStack Query를 활용하여 데이터를 관리한다면 서버 상태나 클라이언트 상태를 관리하기 훨씬 편해지고 사용자 경험면에서 매우 만족스러운 결과를 가져올거라고 생각한다. 다음 프로젝트에서 꼭 사용해봐야겠다! 그리고 TanStack Query가 오프라인 상태에서도 데이터를 불러올 수 있는데 오프라인이라는 상태를 어떻게 감지하는지 검색해봐야겠다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Suspense란?]]></title>
            <link>https://velog.io/@d_hyeon/Suspense%EB%9E%80</link>
            <guid>https://velog.io/@d_hyeon/Suspense%EB%9E%80</guid>
            <pubDate>Tue, 07 May 2024 08:26:33 GMT</pubDate>
            <description><![CDATA[<p>suspense는 React v18에 새로 업데이트 된 기능이다.</p>
<p><a href="https://react.dev/reference/react/Suspense">공식 문서</a>에서 suspense에 대한 설명은 <strong>&quot;Suspense is lets you display a fallback until its children have finished loading.&quot;</strong> 라고 설명이 나와있다.</p>
<p>즉 suspense를 사용하면 컴포넌트 렌더링이 끝날 때까지 다른 컴포넌트를 먼저 렌더링을 시켜주는 기능이라고 할 수 있다. </p>
<p>프로젝트를 하면서 로딩처리와 에러처리를 귀찮다고 생각했던 나에게 딱 맞는 기능인 것 같다.</p>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code>&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;SomeComponent /&gt;
&lt;/Suspense&gt;</code></pre><p>Suspense 태그 안에 있는 SomeComponent의 렌더링이 완료되기 전까지 Loading이란 컴포넌트가 나오게 된다.</p>
<p>정말 단순하면서 유용한 기능이라고 생각된다.</p>
<p>그리고 마침 배우고 있는 TanStack Query(React Query)에도 사용을 해봤다.</p>
<blockquote>
<p><strong>Code</strong></p>
</blockquote>
<pre><code class="language-javascript">// TanStack Query 설정
  const result = useQuery({
    queryKey: [&quot;posts&quot;],
    queryFn: getPosts,
    suspense: true, // suspense 값을 true로 설정해줘야 사용이 가능하다.
  });</code></pre>
<pre><code class="language-javascript">// suspense 사용
function HomePage() {
  return (
    &lt;Suspense fallback={&lt;div&gt;로딩중...&lt;/div&gt;}&gt;
      &lt;DataTest /&gt; // DataTest의 렌더링이 끝날 때까지 &quot;로딩중&quot;이라는 텍스트가 나온다.
    &lt;/Suspense&gt;
  );
}</code></pre>
<h4 id="완성">완성!</h4>
<p><img src="https://velog.velcdn.com/images/d_hyeon/post/f2a7c3c7-1484-4581-b2aa-2d8af19f5284/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[KEY 값을 써야하는 이유]]></title>
            <link>https://velog.io/@d_hyeon/key</link>
            <guid>https://velog.io/@d_hyeon/key</guid>
            <pubDate>Wed, 21 Feb 2024 03:02:17 GMT</pubDate>
            <description><![CDATA[<h3 id="1-리액트에서-배열을-렌더링할-때-key를-써야-하는-이유">1. 리액트에서 배열을 렌더링할 때 key를 써야 하는 이유</h3>
<p><strong>💻REACT KEY</strong>
React는 두 트리에서 일치하는 것을 확인하고 재렌더링 합니다. 하지만 이런 식으로 렌더링 하는 방식은 효율적이지 않습니다.</p>
<pre><code class="language-javascript">&lt;ul&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
  &lt;li&gt;Connecticut&lt;/li&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>React는 key 속성을 지원합니다. 자식들이 key를 가지고 있다면, React는 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지 확인합니다. 예를 들어, 위 비효율적인 예시에 key를 추가하여 트리의 변환 작업이 효율적으로 수행되도록 수정할 수 있습니다.</p>
<pre><code class="language-javascript">&lt;ul&gt;
  &lt;li key=&quot;2015&quot;&gt;Duke&lt;/li&gt;
  &lt;li key=&quot;2016&quot;&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
  &lt;li key=&quot;2014&quot;&gt;Connecticut&lt;/li&gt;
  &lt;li key=&quot;2015&quot;&gt;Duke&lt;/li&gt;
  &lt;li key=&quot;2016&quot;&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[비동기와 Virtual DOM]]></title>
            <link>https://velog.io/@d_hyeon/react</link>
            <guid>https://velog.io/@d_hyeon/react</guid>
            <pubDate>Mon, 19 Feb 2024 05:42:50 GMT</pubDate>
            <description><![CDATA[<h3 id="1-예시의-코드를-실행할-때-콘솔에-출력될-값과-이유">1. 예시의 코드를 실행할 때, 콘솔에 출력될 값과 이유</h3>
<pre><code class="language-javascript">// 예시
// 1번
let num = 1;

// 2번
setTimeout(() =&gt; {
  num = 2;
}, 0);

// 3번
num = 3;

// 4번
console.log(num); // num = 3;</code></pre>
<ul>
<li>1번에서는 num이라는 변수를 선언하고 할당합니다. 
2번에서 setTimeout이라는 비동기 함수를 사용하여 num = 2 로 재할당 하지만 비동기 함수이기 때문에 
3번인 num = 3로 재할당 후 
4번 console.log(num)으로 3이 출력됩니다. 
2번을 제외한 나머지 코드들은 동기 코드이기 때문에 비동기 함수인 2번 코드가 제일 마지막으로 실행됩니다.</li>
</ul>
<h3 id="2-리액트에서-virtual-dom이-무엇인지-사용하는-이유">2. 리액트에서 Virtual DOM이 무엇인지, 사용하는 이유</h3>
<p><strong>💻리액트 Virtual DOM</strong>
리액트 Virtual DOM은 실제 DOM과 똑같이 생긴 가상 DOM을 생성합니다. 리액트는 렌더링 이전 화면 구조를 나타내는 가상 DOM과 렌더링 이후에 보이게 될 화면 구조를 나타내는 가상 DOM을 갖고 있습니다.</p>
<p>여기서 리액트는 기존 DOM와 변경된 페이지 DOM의 변경사항을 비교해서 변경된 요소만 따로 변경을 하여 작업속도가 빠르다는 장점이 있습니다. 그리고 변경된 모든 작업들을 한 번에 모아서 변경을 해주는 Batch Update를 통해 성능을 최적화 할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 메소드]]></title>
            <link>https://velog.io/@d_hyeon/HTTP</link>
            <guid>https://velog.io/@d_hyeon/HTTP</guid>
            <pubDate>Tue, 30 Jan 2024 08:30:25 GMT</pubDate>
            <description><![CDATA[<h3 id="http-메소드">HTTP 메소드</h3>
<p><strong>❓ HTTP 메소드란</strong>
HTTP 메소드란 간단히 서버에서 요청과 응답이 이루어지는 방식을 말합니다.</p>
<p><strong>💻 HTTP 메소드의 종류</strong></p>
<blockquote>
<ol>
<li>GET
GET 메서드는 특정 리소스의 표시를 요청합니다. GET을 사용하는 요청은 오직 데이터를 받기만 합니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li>HEAD
HEAD 메서드는 GET 메서드의 요청과 동일한 응답을 요구하지만, 응답 본문을 포함하지 않습니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="3">
<li>POST
POST 메서드는 특정 리소스에 엔티티를 제출할 때 쓰입니다. 이는 종종 서버의 상태의 변화나 부작용을 일으킵니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="4">
<li>PUT
PUT 메서드는 목적 리소스 모든 현재 표시를 요청 payload로 바꿉니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="5">
<li>DELETE
DELETE 메서드는 특정 리소스를 삭제합니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="6">
<li>CONNECT
CONNECT 메서드는 목적 리소스로 식별되는 서버로의 터널을 맺습니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="7">
<li>OPTIONS
OPTIONS 메서드는 목적 리소스의 통신을 설정하는 데 쓰입니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="8">
<li>TRACE (en-US)
TRACE 메서드는 목적 리소스의 경로를 따라 메시지 loop-back 테스트를 합니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="9">
<li>PATCH
PATCH 메서드는 리소스의 부분만을 수정하는 데 쓰입니다.</li>
</ol>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>