<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Yung_Developer</title>
    <link>https://yung-developer.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 8 May 2026 17:59:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>곤이씨</managingEditor>
    <item>
      <title>mkcert - localhost를 https 환경으로 만들기</title>
      <link>https://yung-developer.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;webpack의 dev server를 이용해 localhost로 개발을 진행하다보면 https로 인해 스트레스를 받는 상황이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(웹 서버가 http를 이용한 접근을 제한하거나 쿠키의 Secure 옵션이 설정되어 있는 경우 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 localhost가 http로 동작하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제가 발생했을 때 서버의 설정을 바꾸기 보다는 localhost의 설정을 바꿔 문제를 간단하게 해결할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;mkcert&lt;/b&gt;&lt;/span&gt;를 이용하면 localhost를 https로 동작시킬 수 있습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1631109317791&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd lik&quot; data-og-description=&quot;A simple zero-config tool to make locally trusted development certificates with any names you'd like. - GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted developmen...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/FiloSottile/mkcert&quot; data-og-url=&quot;https://github.com/FiloSottile/mkcert&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mazsX/hyLxG8BUDz/SqzK0F2Se4OjUFvwUzK4wK/img.png?width=1690&amp;amp;height=962&amp;amp;face=0_0_1690_962&quot;&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mazsX/hyLxG8BUDz/SqzK0F2Se4OjUFvwUzK4wK/img.png?width=1690&amp;amp;height=962&amp;amp;face=0_0_1690_962');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted development certificates with any names you'd lik&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A simple zero-config tool to make locally trusted development certificates with any names you'd like. - GitHub - FiloSottile/mkcert: A simple zero-config tool to make locally trusted developmen...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 공식 페이지를 보고 mkcert를 설치해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료됐다면 https를 적용할 프로젝트의 최상위 디렉토리에서 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;mkcert -install&lt;/span&gt;&lt;/b&gt;을 실행시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;54&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1xoeB/btrfjWW1QnQ/tv2etLu4Mb413KshnMCat1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1xoeB/btrfjWW1QnQ/tv2etLu4Mb413KshnMCat1/img.png&quot; data-alt=&quot;mkcert 설치 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1xoeB/btrfjWW1QnQ/tv2etLu4Mb413KshnMCat1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1xoeB%2FbtrfjWW1QnQ%2Ftv2etLu4Mb413KshnMCat1%2Fimg.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;54&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;mkcert 설치 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;mkcert localhost 127.0.0.1&lt;/span&gt;&lt;/b&gt;을 실행해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 &lt;b&gt;*-key.pem&lt;/b&gt; 파일과 &lt;b&gt;*.pem&lt;/b&gt; 파일이 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 webpack의 devServer 설정에 다음과 같은 코드를 추가하면 설정이 끝나게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1631854240500&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;devServer: {
  ... // 기존 설정들
  https: {
    key: &quot;./*-key.pem&quot;, // 생성된 파일의 이름을 입력해주세요
    cert: &quot;./*.pem&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 로컬 서버를 실행시키면 다음과 같이 https 설정이 정상적으로 적용된 것을 확인할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;310&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tp9d7/btrfm3ades2/lF2MPKjzbvUknfbBrAsBm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tp9d7/btrfm3ades2/lF2MPKjzbvUknfbBrAsBm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tp9d7/btrfm3ades2/lF2MPKjzbvUknfbBrAsBm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftp9d7%2Fbtrfm3ades2%2FlF2MPKjzbvUknfbBrAsBm1%2Fimg.png&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;310&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 개발/웹</category>
      <category>https</category>
      <category>localhost</category>
      <category>localhost https</category>
      <category>mkcert</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/112</guid>
      <comments>https://yung-developer.tistory.com/112#entry112comment</comments>
      <pubDate>Wed, 8 Sep 2021 23:03:17 +0900</pubDate>
    </item>
    <item>
      <title>Github Actions를 이용한 CI/CD for 우테코</title>
      <link>https://yung-developer.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;레벨 4의 첫 미션인 성능 최적화 미션은 AWS S3와 Cloudfront로 배포해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 CI/CD를 적용하지 않으면 코드의 변경사항이 생길 때마다 매번 수동으로 빌드해야 합니다. 그리고 드래그 앤 드랍으로 S3 버킷에 파일을 업데이트 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Github Actions를 이용해 CI/CD를 적용한다면 이러한 불편함을 단번에 해소할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 순서로 Gihub Actions를 적용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;1. S3와 Cloundfront 생성 및 연동&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;2. S3 ACL(액세스 제어 목록) 편집&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;3. 개인 AWS 계정 새 액세스키 발급&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;4. Github Settings에서 Secrets 추가&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;5. Github Actions workflow 생성&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;6. 자동 배포 결과 확인&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. S3와 Cloundfront 생성 및 연동&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, S3에 버킷을 만들고 Cloudfront를 연결해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 엘라의 포스팅을 참고합시다   (Thanks Ella)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1630077600816&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React 앱을 S3+CloudFront로 배포하기(1) - S3 Bucket 생성&quot; data-og-description=&quot;지금부터 S3와 CloudFront로 React application을 배포했던 과정을 정리해볼까 한다.&amp;nbsp;순서는 다음과 같다. S3 Bucket 생성 CloudFront 설정 도메인 구입하여 설정 S3 Bucket 생성하기 1. S3 버킷 생성하기 AWS S3..&quot; data-og-host=&quot;hjuu.tistory.com&quot; data-og-source-url=&quot;https://hjuu.tistory.com/25?category=1019424&quot; data-og-url=&quot;https://hjuu.tistory.com/25&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DnVKo/hyLosDJ3eU/z7YUkRC0jea7SrJHmBp5q0/img.png?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480,https://scrap.kakaocdn.net/dn/Npn9f/hyLorLBzY5/oKNnnoauHkevoiNYLKkJd1/img.png?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480,https://scrap.kakaocdn.net/dn/PhAIZ/hyLpiGfViO/xJthRrAvPymBGstLOj6Mr1/img.png?width=1358&amp;amp;height=424&amp;amp;face=0_0_1358_424&quot;&gt;&lt;a href=&quot;https://hjuu.tistory.com/25?category=1019424&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hjuu.tistory.com/25?category=1019424&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DnVKo/hyLosDJ3eU/z7YUkRC0jea7SrJHmBp5q0/img.png?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480,https://scrap.kakaocdn.net/dn/Npn9f/hyLorLBzY5/oKNnnoauHkevoiNYLKkJd1/img.png?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480,https://scrap.kakaocdn.net/dn/PhAIZ/hyLpiGfViO/xJthRrAvPymBGstLOj6Mr1/img.png?width=1358&amp;amp;height=424&amp;amp;face=0_0_1358_424');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React 앱을 S3+CloudFront로 배포하기(1) - S3 Bucket 생성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지금부터 S3와 CloudFront로 React application을 배포했던 과정을 정리해볼까 한다.&amp;nbsp;순서는 다음과 같다. S3 Bucket 생성 CloudFront 설정 도메인 구입하여 설정 S3 Bucket 생성하기 1. S3 버킷 생성하기 AWS S3..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hjuu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. S3 ACL(액세스 제어 목록) 편집&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 S3의 ACL(액세스 제어 목록)을 편집해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 잠깐 왜 ACL을 편집해야할까..?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의문이 드실 수도 있는데요. 그 이유는 우테코 AWS 계정이 보안상의 이유로 &lt;span style=&quot;color: #ee2323;&quot;&gt;aws access key&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;secret access key&lt;/span&gt;를 공개하지 않기 때문입니다. (한 명이라도 보안 키를 노출하는 순간... 아찔하네요)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 배포를 위한 인스턴스에서 AWS CLI를 사용하려면 &lt;span style=&quot;color: #ee2323;&quot;&gt;aws access key&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;secret access key&lt;/span&gt;가 필요합니다. 하지만 저희는 사용할 수가 없죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떻게 백엔드 크루들은 자동 배포를 할 수 있었냐하면 우테코 AWS 계정에서 생성된 인스턴스를 사용했기 때문입니다. 같은 계정에서 생성된 인스턴스에서 별도의 권한을 부여하면 key가 없어도 S3의 읽기/쓰기가 가능해집니다. (물론, 퍼블릭 액세스 차단을 해제하면 읽기/쓰기가 가능해지지만 모두에게 가능해지는만큼 보안적으로 위험해지겠죠...?! &lt;s&gt;그리고 슬랙 알람이 뜰 겁니다. 거기에는 크루들의 무수한 갈고리가 ㅠㅠ)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;But, 팀 단위의 미션도 아닌 개인 미션에서 모두가 인스턴스를 사용하는 것은 다소 무리가 있어보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 생각해낸 방안이 S3의 접근 권한을 편집하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작에 앞서, 이 방법을 사용하려면 개인 AWS 계정이 필요합니다. 계정이 없다면 회원가입을 진행해주시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 하나씩 단계별로 S3의 접근 권한을 편집해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 우테코 AWS 계정에서 자신이 만든 S3 버킷으로 들어가 &lt;span style=&quot;color: #ee2323;&quot;&gt;권한&lt;/span&gt;을 클릭 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;513&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ1vA4/btrdlzX0FH5/Y9TQm1XWFitnxHDP2CeefK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ1vA4/btrdlzX0FH5/Y9TQm1XWFitnxHDP2CeefK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ1vA4/btrdlzX0FH5/Y9TQm1XWFitnxHDP2CeefK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ1vA4%2FbtrdlzX0FH5%2FY9TQm1XWFitnxHDP2CeefK%2Fimg.png&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;513&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 스크롤을 내려 객체 소유권의 &lt;span style=&quot;color: #ee2323;&quot;&gt;편집&lt;/span&gt; 버튼을 클릭 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2041&quot; data-origin-height=&quot;305&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crQZ1B/btrdlBayXNF/FyuI7PX72CkUsSbRy8tdd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crQZ1B/btrdlBayXNF/FyuI7PX72CkUsSbRy8tdd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crQZ1B/btrdlBayXNF/FyuI7PX72CkUsSbRy8tdd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrQZ1B%2FbtrdlBayXNF%2FFyuI7PX72CkUsSbRy8tdd1%2Fimg.png&quot; data-origin-width=&quot;2041&quot; data-origin-height=&quot;305&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 화면이 뜨면 객체 소유권을 &lt;span style=&quot;color: #ee2323;&quot;&gt;버킷 소유자 선호&lt;/span&gt;로 변경하고 저장 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 객체는 객체를 업로드한 계정이 권한을 가집니다. 객체 소유권 변경은 다른 계정에서 &lt;span style=&quot;color: #0a3069;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;--acl bucket-owner-full-control&lt;/span&gt;로 업로드 &lt;/span&gt;되는 모든 객체에 대한 권한을 S3 버킷 소유자가 가지도록 만듭니다. 객체의 소유 권한을 우테코 AWS 계정이 가지고 있어야 Cloudfront가 정상적으로 S3의 객체를 읽어올 수 있기 때문에 꼭 변경해줍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;662&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nC3ya/btrdrmiAoTK/KnOq1OspdNh4K7uf3rSgYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nC3ya/btrdrmiAoTK/KnOq1OspdNh4K7uf3rSgYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nC3ya/btrdrmiAoTK/KnOq1OspdNh4K7uf3rSgYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnC3ya%2FbtrdrmiAoTK%2FKnOq1OspdNh4K7uf3rSgYk%2Fimg.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;662&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 스크롤을 쭉 내려서 ACL(액세스 제어 목록)의 &lt;span style=&quot;color: #ee2323;&quot;&gt;편집&lt;/span&gt; 버튼을 클릭 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1104&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH9zYw/btrdpMoy8rf/cKCbhpcrEaYQ6KY1yi6quK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH9zYw/btrdpMoy8rf/cKCbhpcrEaYQ6KY1yi6quK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH9zYw/btrdpMoy8rf/cKCbhpcrEaYQ6KY1yi6quK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH9zYw%2FbtrdpMoy8rf%2FcKCbhpcrEaYQ6KY1yi6quK%2Fimg.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1104&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 화면이 뜨면 &lt;span style=&quot;color: #ee2323;&quot;&gt;피부여자 추가 버튼&lt;/span&gt;을 누르고 모든 체크 박스를 체크해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;378&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/96xyC/btrdpCsEFVP/4YBCAO8KyIL8w6nX4EUaD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/96xyC/btrdpCsEFVP/4YBCAO8KyIL8w6nX4EUaD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/96xyC/btrdpCsEFVP/4YBCAO8KyIL8w6nX4EUaD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F96xyC%2FbtrdpCsEFVP%2F4YBCAO8KyIL8w6nX4EUaD1%2Fimg.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;378&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 피부여자는 S3에 접근 권한을 허용할 다른 AWS 계정을 말합니다. 피부여자 입력란에 개인 AWS 계정 정식 ID를 입력해주면 됩니다. 이때 정식 ID는 AWS 로그인 시에 필요한 ID가 아닙니다. 시크릿 모드로 새 창을 열어준 다음 개인 AWS 계정으로 로그인 합시다. (동시에 두 개의 계정으로 로그인 하기 위함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 aws 계정으로 로그인한 창에서 정식 ID을 확인하려면 우선, 상단의 계정 드롭다운을 눌렀을 때 나오는 &lt;span style=&quot;color: #ee2323;&quot;&gt;내 보안 자격 증명&lt;/span&gt;을 클릭 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;850&quot; width=&quot;320&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xKTaE/btrdqRixncB/z3zRI69Myq9vfzS6Lgs0e0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xKTaE/btrdqRixncB/z3zRI69Myq9vfzS6Lgs0e0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xKTaE/btrdqRixncB/z3zRI69Myq9vfzS6Lgs0e0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxKTaE%2FbtrdqRixncB%2Fz3zRI69Myq9vfzS6Lgs0e0%2Fimg.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;850&quot; width=&quot;320&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 나온 페이지에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;계정 ID&lt;/span&gt;를 클릭하면 정규 사용자 ID를 확인할 수 있습니다. 이걸 복사해서 우테코 AWS 계정 창으로 돌아와 &lt;span style=&quot;color: #ee2323;&quot;&gt;피부여자&lt;/span&gt;에 붙여넣기하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;변경 사항 저장&lt;/span&gt;을 눌러줍시다. 그러면 접근 권한 설정은 모두 끝났습니다. 이제 내 개인 계정으로 우테코 AWS 계정의 S3에 접근할 수 있게 됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2033&quot; data-origin-height=&quot;838&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/18Tq3/btrdo5hK8rn/KBBZKWTjCUKz81LIUKKOg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/18Tq3/btrdo5hK8rn/KBBZKWTjCUKz81LIUKKOg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/18Tq3/btrdo5hK8rn/KBBZKWTjCUKz81LIUKKOg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F18Tq3%2Fbtrdo5hK8rn%2FKBBZKWTjCUKz81LIUKKOg1%2Fimg.png&quot; data-origin-width=&quot;2033&quot; data-origin-height=&quot;838&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 개인 AWS 계정 새 액세스 키 발급&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 기존 액세스 키가 있다면 건너뛰셔도 괜찮습니다. 하지만 키를 분실하셨다면 새로 발급해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 AWS 개정 창에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;액세스 키(액세스 키 ID 및 비밀 액세스 키)&lt;/span&gt;를 눌러주세요. 그리고 &lt;span style=&quot;color: #ee2323;&quot;&gt;새 액세스 키 발급&lt;/span&gt;을 눌러줍니다. 이렇게 생성된 키는 다운로드 받아서 보관하시는 걸 추천합니다. 닫기 버튼을 누르면 다시는 볼 수가 없답니다... ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 키는 Github Actions에서 AWS CLI가 사용하게 됩니다. 이제 우리는 개인 계정의 액세스 키를 이용하여 우테코 AWS 계정의 S3를 수정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2043&quot; data-origin-height=&quot;1100&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baWYqz/btrdpp8an8N/6Zrev1qSrJYLpEo9X9lgmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baWYqz/btrdpp8an8N/6Zrev1qSrJYLpEo9X9lgmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baWYqz/btrdpp8an8N/6Zrev1qSrJYLpEo9X9lgmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaWYqz%2Fbtrdpp8an8N%2F6Zrev1qSrJYLpEo9X9lgmK%2Fimg.png&quot; data-origin-width=&quot;2043&quot; data-origin-height=&quot;1100&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. Github Settings에서 Secrets 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Github Settings에 앞서 발급 받은 액세스 키를 Secrets에 추가할 겁니다. 본인이 적용할 프로젝트의 저장소에 들어가서 상단 메뉴 중&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;Settings&lt;/span&gt;를 클릭 합니다. 그 다음 &lt;span style=&quot;color: #ee2323;&quot;&gt;Secrets&lt;/span&gt;를 클릭 합니다. 그리고 &lt;span style=&quot;color: #ee2323;&quot;&gt;New repository secret&lt;/span&gt;을 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1152&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPfAFI/btrdr4ogKu0/IKYknIUtZpvegqYlFxDqbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPfAFI/btrdr4ogKu0/IKYknIUtZpvegqYlFxDqbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPfAFI/btrdr4ogKu0/IKYknIUtZpvegqYlFxDqbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPfAFI%2Fbtrdr4ogKu0%2FIKYknIUtZpvegqYlFxDqbK%2Fimg.png&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;1152&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Value에 앞서 발급 받은 액세스 키를 입력하면 됩니다. AWS CLI로 S3 버킷을 업데이트 하기 위해서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;AWS_DEFAULT_REGION&lt;/span&gt; 세 가지가 필요합니다. 앞의 두 키는 앞서 발급 받은 AWS 키를 입력하면 됩니다. AWS_DEFAULT_REGION은 &lt;span style=&quot;color: #16191f;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ap-northeast-2&lt;/span&gt;를 입력해주세요. &lt;span style=&quot;color: #16191f;&quot;&gt;Asia Pacific (Seoul)&lt;/span&gt;을 의미합니다. 그러면 이제 모든 준비가 끝났습니다. 본격적으로 Github Actions를 적용해봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Github Actions workflow 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 상단 메뉴 중 &lt;span style=&quot;color: #ee2323;&quot;&gt;Actions&lt;/span&gt;를 클릭 합니다. 스크롤을 내려 &lt;span style=&quot;color: #ee2323;&quot;&gt;Manual workflow&lt;span style=&quot;color: #333333;&quot;&gt;의&lt;/span&gt; Set up this workflow&lt;/span&gt;를 클릭 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;1037&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NKKHy/btrdmBn3V2Y/fIyB7U3z4jbQp232tuMpD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NKKHy/btrdmBn3V2Y/fIyB7U3z4jbQp232tuMpD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NKKHy/btrdmBn3V2Y/fIyB7U3z4jbQp232tuMpD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNKKHy%2FbtrdmBn3V2Y%2FfIyB7U3z4jbQp232tuMpD0%2Fimg.png&quot; data-origin-width=&quot;1999&quot; data-origin-height=&quot;1037&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같은 페이지가 나오게 됩니다. 여기서 본인이 workflow 파일을 수정하여 본인이 원하는 github event에 특정한 동작을 수행시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2551&quot; data-origin-height=&quot;973&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDkJO/btrdmAWXea5/2dSQ2k3Yz0SNDMQiZAkXi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDkJO/btrdmAWXea5/2dSQ2k3Yz0SNDMQiZAkXi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDkJO/btrdmAWXea5/2dSQ2k3Yz0SNDMQiZAkXi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDkJO%2FbtrdmAWXea5%2F2dSQ2k3Yz0SNDMQiZAkXi1%2Fimg.png&quot; data-origin-width=&quot;2551&quot; data-origin-height=&quot;973&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드를 다음과 같이 수정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1630082574637&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: push_builder # 이름은 자유롭게 설정

on:
  push: # 이벤트 설정 - push 이벤트 때 동작
    branches: main # 브랜치 설정 - main 브랜치에서 push 이벤트가 일어나면 동작

jobs:
  deploy:
    runs-on: ubuntu-latest # 인스턴스 OS
    steps: 
    - name: Checkout source code
      uses: actions/checkout@v2 # 워크플로에서 액세스할 수 있도록 에서 저장소를 체크아웃
    
    - name: Setup node
      uses: actions/setup-node@v1 # 노드 설치
      with:
        node-version: '14'
        
    - run: npm install
    
    - name: Setup yarn
      run: npm install -g yarn
      
    - name: Install Dependencies
      run: yarn
      
    - name: Build
      run: yarn build
      env: # build 할 때 필요한 환경 변수 추가, Settings의 Secrets에서 추가 가능
        SOME_API_KEY: ${{ secrets.SOME_API_KEY }}

    - name: S3 Deploy
      run: aws s3 sync ./dist s3://perf-basecamp-yungo1846/ --acl bucket-owner-full-control # 본인 S3 이름 입력
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script 명령어는 본인 프로젝트 설정에 맞게 변경하면 됩니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이 끝났다면 오른쪽 상단의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Start Commit&lt;/span&gt;을 누르고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Commit directly to the&amp;nbsp;&lt;b&gt;main&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;branch&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;옵션으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Commit new file&lt;/span&gt;을 눌러줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 자동 배포 결과 확인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋이 새로 생기면서 main 브랜치에 .github이라는 폴더 안에 workflow 파일이 생성 됩니다. 그리고 다시 상단 메뉴의 Actions로 이동하여 Github Actions의 인스턴스가 동작하는 것을 확인할 수 있습니다. 다음과 같이 작업이 모두 끝났다면 정상적으로 배포가 완료된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;853&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kNijj/btrdoovdYAL/10XUTBJTmNre30NICEVRHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kNijj/btrdoovdYAL/10XUTBJTmNre30NICEVRHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kNijj/btrdoovdYAL/10XUTBJTmNre30NICEVRHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkNijj%2FbtrdoovdYAL%2F10XUTBJTmNre30NICEVRHK%2Fimg.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;853&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 S3로 들어가 파일이 제대로 업로드 됐는지 확인해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1979&quot; data-origin-height=&quot;607&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4L13y/btrdqjfnrus/FgLBHT8GS2iLJJkSDUEJqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4L13y/btrdqjfnrus/FgLBHT8GS2iLJJkSDUEJqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4L13y/btrdqjfnrus/FgLBHT8GS2iLJJkSDUEJqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4L13y%2Fbtrdqjfnrus%2FFgLBHT8GS2iLJJkSDUEJqK%2Fimg.png&quot; data-origin-width=&quot;1979&quot; data-origin-height=&quot;607&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3에 빌드 결과물이 정상적으로 업로드 됐네요! 우리는 이제 수동 빌드와 드래그 앤 드랍에서 벗어났습니다. 이상 인스턴스 없이 S3 액세스 권한을 편집하여 CI/CD를 적용하는 방법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 모두 즐거운 코딩하세요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+) 웹팩 빌드 시 환경 변수가 제대로 들어가지 않는 문제는 다음 포스팅을 참고하시면 도움이 됩니다.&lt;/p&gt;
&lt;figure id=&quot;og_1630129484377&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;github actions를 이용해 빌드 + 배포된 서버에서 &amp;#96;process.env.[환경변수]&amp;#96;를 사용하려면 어떻게 해야 할&quot; data-og-description=&quot;파이어베이스를 사용하기 위해 아래와같이 설정해주고,.env 파일에 firebaseConfig에서 사용할 환경변수들을 설정해주었습니다.env 파일은 깃헙 저장소에 올라가면 안되기때문에 .gitignore에 .evn를 추&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@heyoon/github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%B9%8C%EB%93%9C-%EB%B0%B0%ED%8F%AC%EB%90%9C-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98process.env.%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A0%A4%EB%A9%B4-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94&quot; data-og-url=&quot;https://velog.io/@heyoon/github-actions를-이용해-빌드-배포된-서버에서-환경변수process.env.환경변수를-사용하려면-어떻게-해야-할까요&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kiFpL/hyLo2RdC5V/7tSP66tipWgvIBuUUosSXK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/8a3wx/hyLpeqAseb/KZaeEBNtaGwsvp9uycce9k/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot;&gt;&lt;a href=&quot;https://velog.io/@heyoon/github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%B9%8C%EB%93%9C-%EB%B0%B0%ED%8F%AC%EB%90%9C-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98process.env.%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A0%A4%EB%A9%B4-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@heyoon/github-actions%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%B9%8C%EB%93%9C-%EB%B0%B0%ED%8F%AC%EB%90%9C-%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98process.env.%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A0%A4%EB%A9%B4-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C%EC%9A%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kiFpL/hyLo2RdC5V/7tSP66tipWgvIBuUUosSXK/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/8a3wx/hyLpeqAseb/KZaeEBNtaGwsvp9uycce9k/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;github actions를 이용해 빌드 + 배포된 서버에서 `process.env.[환경변수]`를 사용하려면 어떻게 해야 할&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;파이어베이스를 사용하기 위해 아래와같이 설정해주고,.env 파일에 firebaseConfig에서 사용할 환경변수들을 설정해주었습니다.env 파일은 깃헙 저장소에 올라가면 안되기때문에 .gitignore에 .evn를 추&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/acl-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ACL(액세스 제어 목록) 개요&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/about-object-ownership.html#ensure-object-ownership&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;S3 객체 소유권을 사용하여 업로드 된 객체의 소유권 제어&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/cli-configure-envvars.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;환경 변수를 사용하여 AWS CLI 구성&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 일기/우아한테크코스-FE</category>
      <category>ACL</category>
      <category>AWS</category>
      <category>CloudFront</category>
      <category>S3</category>
      <category>우테코</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/111</guid>
      <comments>https://yung-developer.tistory.com/111#entry111comment</comments>
      <pubDate>Sat, 28 Aug 2021 02:03:33 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크코스 프론트 : Lv.3 댓글 모듈 다라쓰 인사이트</title>
      <link>https://yung-developer.tistory.com/110</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 댓글 모듈 iframe 개발기&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1629636494922&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;댓글 모듈 iframe 개발기&quot; data-og-description=&quot;GitHub - woowacourse-teams/2021-darass:   웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &amp;quot;다라   웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &amp;quot;다라쓰&amp;quot;. Contribute to woowacourse-teams/..&quot; data-og-host=&quot;yung-developer.tistory.com&quot; data-og-source-url=&quot;https://yung-developer.tistory.com/108?category=950552&quot; data-og-url=&quot;https://yung-developer.tistory.com/108&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ujMki/hyLljTl4iE/Hq2ULEDrhKSkzlyYYSSO6K/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/NIn1M/hyLlc7KaPS/j460PdoMJjhz1qZbfeXBPK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/jRj4Y/hyLli7Yng2/oklFFD12TII1UqPEZ2Z9gK/img.png?width=1150&amp;amp;height=1171&amp;amp;face=0_0_1150_1171&quot;&gt;&lt;a href=&quot;https://yung-developer.tistory.com/108?category=950552&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yung-developer.tistory.com/108?category=950552&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ujMki/hyLljTl4iE/Hq2ULEDrhKSkzlyYYSSO6K/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/NIn1M/hyLlc7KaPS/j460PdoMJjhz1qZbfeXBPK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/jRj4Y/hyLli7Yng2/oklFFD12TII1UqPEZ2Z9gK/img.png?width=1150&amp;amp;height=1171&amp;amp;face=0_0_1150_1171');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;댓글 모듈 iframe 개발기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - woowacourse-teams/2021-darass:   웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &quot;다라   웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &quot;다라쓰&quot;. Contribute to woowacourse-teams/..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yung-developer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. React에서 contentEditable 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;figure id=&quot;og_1629636553501&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React에서 contentEditable 사용하기&quot; data-og-description=&quot;&amp;nbsp;contentEditable은 HTML 요소를 수정 가능하게 만들어주는 속성이다. 예를 들어, div에 contenteditable 속성을 true로 설정해주면 div 요소도 input처럼 입력을 할 수 있게 된다. &amp;nbsp;contentEditable을 사용하는..&quot; data-og-host=&quot;yung-developer.tistory.com&quot; data-og-source-url=&quot;https://yung-developer.tistory.com/109&quot; data-og-url=&quot;https://yung-developer.tistory.com/109&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uvmS4/hyLk9JXfmC/MnN46UTkKXXKQRgHjgmFa0/img.png?width=448&amp;amp;height=200&amp;amp;face=0_0_448_200,https://scrap.kakaocdn.net/dn/tfHIE/hyLk7L7XKh/PTkXamj60C7YBCVE5QME8k/img.png?width=448&amp;amp;height=200&amp;amp;face=0_0_448_200,https://scrap.kakaocdn.net/dn/cYwAJL/hyLljFM3KT/3kbqM0OOo427KSjM5eosh0/img.png?width=446&amp;amp;height=254&amp;amp;face=0_0_446_254&quot;&gt;&lt;a href=&quot;https://yung-developer.tistory.com/109&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yung-developer.tistory.com/109&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uvmS4/hyLk9JXfmC/MnN46UTkKXXKQRgHjgmFa0/img.png?width=448&amp;amp;height=200&amp;amp;face=0_0_448_200,https://scrap.kakaocdn.net/dn/tfHIE/hyLk7L7XKh/PTkXamj60C7YBCVE5QME8k/img.png?width=448&amp;amp;height=200&amp;amp;face=0_0_448_200,https://scrap.kakaocdn.net/dn/cYwAJL/hyLljFM3KT/3kbqM0OOo427KSjM5eosh0/img.png?width=446&amp;amp;height=254&amp;amp;face=0_0_446_254');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React에서 contentEditable 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;contentEditable은 HTML 요소를 수정 가능하게 만들어주는 속성이다. 예를 들어, div에 contenteditable 속성을 true로 설정해주면 div 요소도 input처럼 입력을 할 수 있게 된다. &amp;nbsp;contentEditable을 사용하는..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yung-developer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. git flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여러 개발자와의 협업에서 깃 브랜치 전략을 필수이다. 커밋과 브랜치를 컨벤션 없이 개개인의 스타일로 작성하게 되면 나중에 무엇이 어떤 순서로 진행됐는지 알기가 너무 어렵다. 또한, 배포 브랜치와 개발 브랜치를 분리해야 이슈가 터졌을 때도 빠르게 대응할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우리 팀은 깃 브랜치 전략으로 git flow를 선택했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;master 브랜치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바로 배포 가능한 상태여야 한다.&lt;/li&gt;
&lt;li&gt;에러가 없는 상태여야 한다.&lt;/li&gt;
&lt;li&gt;삭제하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;release 브랜치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 코드를 통합하기 위한 브랜치&lt;/li&gt;
&lt;li&gt;QA 진행&lt;/li&gt;
&lt;li&gt;베타 버전&lt;/li&gt;
&lt;li&gt;(2021-07-06 추가 사항) release 브랜치는 배포 직전에만 백엔드와 프론트엔드 디렉토리를 합쳐서 오류가 나지 않는지 확인하는 용도로 사용하고, 문제가 없다면 삭제하는 방향이 긍정적으로 보임. 이 release 브랜치가 매번 유지된다면 merge 하는데 꽤 고통 받을 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;develop/fe, develop/be&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자들의 통합 브랜치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주의) Merge할 때 issue 번호 끝에 남기기&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) feat: 로그인 기능 (#2)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;feature/{fe or be}/{개발 기능}/{이슈번호}&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개인이 기능 단위로 개발하기 위한 브랜치&lt;/li&gt;
&lt;li&gt;ex) feature/be/login_in_naver/1, feature/fe/signup_error_fix/2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우리 팀 브랜치 전략에서 주목할만한 부분은 develop을 fe와 be로 구분지었다는 것이다. fe와 be는 사용하는 언어와 환경이 모두 다르기 때문에 각 파트에서의 코드 변경이 서로 영향을 주지 않는다. 때문에 develop 브랜치를 fe와 be로 분리하면 fe에서 작업을 하다가 be에서 새로운 커밋을 쌓았다고 해서 pull을 받을 필요가 없어진다. 또한, 각 파트 별로 커밋 로그를 확인하기가 더 쉬워지고 리셋을 하거나 포스 푸쉬를 하더라도 사이드 이펙트가 줄어든다는 장점이 있다. 물론, 깃 사용에 익숙하다면 이러한 분리가 무의미하게 느껴질 수도 있지만 팀원 모두가 대규모 협업이 처음이라 이런 식의 분리가 우리에게는 꽤나 효율적이었다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커밋 메세지 형태&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;feat: 기능 추가&lt;/li&gt;
&lt;li&gt;docs: 문서 관련&lt;/li&gt;
&lt;li&gt;refactor: 리팩토링&lt;/li&gt;
&lt;li&gt;chore: 의존성 추가, 환경 설정, 빌드 관련&lt;/li&gt;
&lt;li&gt;style: 코드 스타일 변경 (띄어쓰기, 개행 등)&lt;/li&gt;
&lt;li&gt;fix: 버그 수정&lt;/li&gt;
&lt;li&gt;test: 테스트 코드 관련&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot; width=&quot;677&quot; height=&quot;897&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brULm8/btrcVkMbL4a/wIz22rVTXKrNy4hPqGK8TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brULm8/btrcVkMbL4a/wIz22rVTXKrNy4hPqGK8TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brULm8/btrcVkMbL4a/wIz22rVTXKrNy4hPqGK8TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrULm8%2FbtrcVkMbL4a%2FwIz22rVTXKrNy4hPqGK8TK%2Fimg.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot; width=&quot;677&quot; height=&quot;897&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 시멘틱 버저닝(git tag &amp;amp; github release)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;배포 이후에도 코드는 계속해서 수정된다. 배포 당시에는 발견하지 못했지만, 이후에 잘못된 동작을 하거나 예외적인 상황에 대한 에러 처리가 빠진 것을 뒤늦게 확인할 수가 있다. 또한 기능이 추가되거나 변경이 요구될 때가 있다. 이러한 때에는 코드를 수정해야 한다. 코드를 수정했다면 개발자 또는 사용자가 이를 확인하고 구별할 수 있어야 한다. 코드에서 이러한 차이를 구별할 수 있게 해주는 것이 바로 버전이다. 버전은 프로그램을 수정하거나 개선할 때마다 코드를 구분하려고 부여된 식별자를 의미하며 보통 숫자를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;시멘틱 버저닝은 버전 번호를 어떻게 정하고 올려야 하는지를 명시하는 규칙과 요구사항을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;버전은 X.Y.Z를 따른다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메이저 업데이트(X): 하위 호환성을 보장하지 않는 변경&lt;/li&gt;
&lt;li&gt;마이너 업데이트(Y): 하위 버전에 대해서도 호환성을 보장하는 범위의 변경&lt;/li&gt;
&lt;li&gt;패치 업데이트(Z): API에 영향이 없는 버그 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;깃에서는 코드 배포를 관리를 돕는 태그 기능을 제공한다. &lt;b&gt;태그는 특정 커밋의 해시 값을 가리키는 꼬리표를 의미&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 명령어로 현재 HEAD가 가리키는 커밋을 기준으로 태그를 생성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1629725581929&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git tag -a 1.0.0 // 태그 추가&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;깃헙에서는 태그를 공유할 수 있는 기능을 제공한다. 깃헙의 Releases를 확인하면 해당 프로젝트의 태그와 버전을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1629725986101&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push origin 태그이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;204&quot; data-origin-height=&quot;135&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBZZQA/btrcZt4bBoO/VjK7oldYoSTjxnSEkdR8kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBZZQA/btrcZt4bBoO/VjK7oldYoSTjxnSEkdR8kK/img.png&quot; data-alt=&quot;github Releases&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBZZQA/btrcZt4bBoO/VjK7oldYoSTjxnSEkdR8kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBZZQA%2FbtrcZt4bBoO%2FVjK7oldYoSTjxnSEkdR8kK%2Fimg.png&quot; data-origin-width=&quot;204&quot; data-origin-height=&quot;135&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;github Releases&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Node.js의 Releases를 참고하여 Releases를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;741&quot; width=&quot;755&quot; height=&quot;587&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1raMU/btrc1T899wJ/lRWgAYl6xnKrkKHWwU1UAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1raMU/btrc1T899wJ/lRWgAYl6xnKrkKHWwU1UAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1raMU/btrc1T899wJ/lRWgAYl6xnKrkKHWwU1UAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1raMU%2Fbtrc1T899wJ%2FlRWgAYl6xnKrkKHWwU1UAK%2Fimg.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;741&quot; width=&quot;755&quot; height=&quot;587&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 다른 영역이 클릭 됐을 때 드롭다운 닫히게 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1629726127339&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [isShowOptionBox, setShowOptionBox] = useState(false);
const ref = useRef(false);
ref.current = isShowOptionBox;

const onShowOptionBox = (event: MouseEvent) =&amp;gt; {
  event.stopPropagation();
  setShowOptionBox(state =&amp;gt; !state);
};

const onHideOptionBox = () =&amp;gt; {
  if (ref.current) setShowOptionBox(false);
};

useEffect(() =&amp;gt; {
  window.addEventListener(&quot;click&quot;, onHideOptionBox);
  return () =&amp;gt; {
    window.removeEventListener(&quot;click&quot;, onHideOptionBox);
  };
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다른 영역이 클릭됐는지는 특정 영역만 렌더링 하는 컴포넌트에서 알 방법이 없다. 따라서 window에 클릭 이벤트를 달아주어야 한다. 이 때, 특정 상태값을 이벤트 핸들러가 참조하게 하고 싶다면 ref를 사용하여 객체로 관리해주면 편하다. (&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt;를 객체로 해도 된다) 그렇다면 왜 객체로 관리해야하는 것일까? 만약 이벤트 핸들러에 객체를 사용하지 않고 boolean 타입을 사용하면 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt;를 출력하면 항상 false가 뜨게 된다. 이는 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;5&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt; 가 원시 값이기 때문에 발생하는 문제이다. &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;7&quot; data-reactroot=&quot;&quot;&gt;setShowOptionBox&lt;/span&gt;에 의해 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;9&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt;가 변경된다면 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;11&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt;는 값이기에 다른 메모리 주소에 값이 저장되고 식별자는 변경된 메모리 주소를 가리킨다. 이벤트 핸들러는 최초에 함수가 등록되는 시점에서의 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;13&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt; 식별자가 가리키는 메모리 주소를 가리키고 이를 유지한다(함수 스코프). 이 때문에 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;15&quot; data-reactroot=&quot;&quot;&gt;isShowOptionBox&lt;/span&gt;의 값이 변경되더라도 계속해서 false만 가리키는 것이다. 따라서 가장 최신 값을 유지하고 싶다면 객체를 이용하여 이벤트 핸들러가 참조값을 가리키도록 바꾸어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 모바일 마우스 호버 인터랙션 제거&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1629728082183&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@media (hover: hover) and (pointer: fine) {
  &amp;amp;:hover {
    color: ${PALETTE.WHITE};
    background-color: ${PALETTE.RED_800};
  }
} // 호버를 적용할 요소에 미디어 쿼리를 추가한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;모바일 터치는 터치할 때마다 마우스의 포인터가 이동하는 것으로 이해하면 쉽다. 따라서 터치가 일어났을 때 CSS의 호버가 동작한다. 하지만 터치가 끝나고 다른 곳을 터치하지 않으면 포인터의 위치는 변화가 없으므로 계속해서 호버된 채로 남아있게 된다. 때문에 모바일에서는 호버를 지우는 것이 필요할 수 있다. 우리 서비스의 경우 좋아요 상태에 따라 호버 색상과 기본 색상이 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋아요 없을 시&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 색상: 검은색&lt;/li&gt;
&lt;li&gt;호버 색상: 파란색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좋아요 있을 시&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 색상: 파란색&lt;/li&gt;
&lt;li&gt;호버 색상: 검은색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 인터랙션은 데스크탑에서는 의도한대로 잘 나오지만 모바일에서는 그렇지 않다. 모바일은 앞서 말한 터치 문제가 있기 때문에 좋아요 없을 시 클릭을 하면 좋아요가 있는 상태로 변경된다. 그리고 마우스 포인터는 좋아요 버튼 위에 있기 때문에 최초에 좋아요가 없는 것 상태와 동일한 색상이 렌더링 되게 된다. 이러한 문제를 해결하려면 PC에만 호버 스타일을 적용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. CI/CD&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번 협력 미션에서 처음으로 말로만 듣던 CI/CD를 적용해 보았다. 백엔드 파트는 이미 젠킨스와 Github Action을 이용하여 CI/CD를 적용하고 있었다. 프론트는 백엔드 팀원인 제이온의 도움을 받아 Github Action을 사용하여 CI/CD를 도입해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;Github Action을 이용하여 특정 브랜치에 PR이 올라오거나 머지 혹은 푸쉬가 이루어졌을 때 자동으로 테스트와 배포가 진행되도록 했다. 이를 통해 리팩토링이나 기능 추가로 발생하는 오류들을 사전에 발견할 수 있었다. 또한, 매번 배포가 이뤄저야하는 상황마다 빌드를 하고 파일을 직접 S3에 올리는 수고를 피할 수 있어 좋았다. CI/CD는 개발 효율을 극대화시켜주는 프로세스라고 느꼈다. 주기적인 배포가 필요한 프로젝트를 하는 상황이라면 무조건 도입해야겠다. 젠킨스와 Github Action도 공부해봐야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;8. Sentry 에러 모니터링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;프론트 단에서 발생하는 에러는 대부분의 경우 서버가 아닌 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라인트&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 사이드에서 발생하기 때문에 개발자가 무슨 에러가 어디에서 일어났는지 파악하기가 어렵다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;때문에 에러 모니터링 서비스인 센트리를 도입하여 배포 환경에서 발생하는 에러를 추적하고 관리하도록 하였디.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;440&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx5m1j/btrcSsyhi7S/Bn0DR4HpndiDdZGBbZPrKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx5m1j/btrcSsyhi7S/Bn0DR4HpndiDdZGBbZPrKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx5m1j/btrcSsyhi7S/Bn0DR4HpndiDdZGBbZPrKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx5m1j%2FbtrcSsyhi7S%2FBn0DR4HpndiDdZGBbZPrKk%2Fimg.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;440&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;위의 사진처럼 센트리를 이용하면 해당 에러가 어떤 컴포넌트에서 발생했는지 그 위치와 에러의 정보를 파악할 수 있다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;. 하지만 source map처럼 코드의 정확한 위치가 아닌 빌드된 코드의 위치를 나타내고 있다. 이는 센트리에서 제공하는 에러 바운더리를 사용했기 때문으로 추측된다. 여유가 된다면 직접 에러바운더리에서 센트리에 에러 로그를 보내도록 커스텀 해봐야겠다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 일기/우아한테크코스-FE</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/110</guid>
      <comments>https://yung-developer.tistory.com/110#entry110comment</comments>
      <pubDate>Sun, 22 Aug 2021 21:54:28 +0900</pubDate>
    </item>
    <item>
      <title>React에서 contentEditable 사용하기</title>
      <link>https://yung-developer.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;contentEditable은 HTML 요소를 수정 가능하게 만들어주는 속성&lt;/b&gt;이다. 예를 들어, div에 contenteditable 속성을 true로 설정해주면 div 요소도 input처럼 입력을 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;contentEditable을 사용하는 이유는 웹 에디터를 쉽게 만들기 위함이다. &lt;span style=&quot;color: #212529;&quot;&gt;contentEditable을 사용하면 브라우저가 자체적으로 클립보드, 드래그&amp;amp;드롭, 실행 취소, 서식과 같은 기능을 전부 제공해준다. 또한, 특정 내용을 편집 모드로 수정하기 쉽다는 장점이 있다. 편집 모드 변경 시 input이나 textarea로 수정할 필요없이 간단하게 contentEditable=true 속성만 추가해줘도 편집 모드로 변경할 수 있다. 때문에 div를 input으로 바꿨을 때 발생할 수 있는 &lt;b&gt;스타일 변경을 신경쓰지 않아도 되어 편리하다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;200&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p8d8w/btrcViU1MEr/ep00lT0HqaQbRkWt4T4cY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p8d8w/btrcViU1MEr/ep00lT0HqaQbRkWt4T4cY1/img.png&quot; data-alt=&quot;div element&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p8d8w/btrcViU1MEr/ep00lT0HqaQbRkWt4T4cY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp8d8w%2FbtrcViU1MEr%2Fep00lT0HqaQbRkWt4T4cY1%2Fimg.png&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;200&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;div element&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;254&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qv5tN/btrcNLcNRHB/k57SgDNQB79ErMMlsQL1R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qv5tN/btrcNLcNRHB/k57SgDNQB79ErMMlsQL1R0/img.png&quot; data-alt=&quot;div contentEditable={true}&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qv5tN/btrcNLcNRHB/k57SgDNQB79ErMMlsQL1R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqv5tN%2FbtrcNLcNRHB%2Fk57SgDNQB79ErMMlsQL1R0%2Fimg.png&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;254&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;div contentEditable={true}&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&amp;nbsp;하지만 &lt;b&gt;contentEditable은 React에서 사용할 때 꽤나 문제가 많았다. input과 동작 방식이 다르기 때문&lt;/b&gt;이다. 먼저, 입력 시 change event가 아니라 input event가 동작한다. 또한, input이 아니기 때문에 value 값이 없다. 이 때문에 제어 컴포넌트처럼 리액트가 DOM을 제어할 수가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&amp;nbsp;따라서 비제어 컴포넌트 방식으로 contentEditable을 관리해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629621259200&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useContentEditable = (initialContent: string) =&amp;gt; {
  const $contentEditable = useRef&amp;lt;HTMLDivElement | null&amp;gt;(null);
  const [content, _setContent] = useState(initialContent);

  const onInput = (event: ChangeEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    _setContent(event.target.innerText);
  };

  const setContent = (newContent: string) =&amp;gt; {
    if ($contentEditable.current) {
      $contentEditable.current.innerText = newContent;
      _setContent(newContent);
    }
  };

  useEffect(() =&amp;gt; {
    setContent(initialContent);
  }, []);

  return { content, setContent, onInput, $contentEditable };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&amp;nbsp;contentEditable DOM에 접근하기 위한 ref와 상태를 저장하기 위한 useState를 추가한다. 상태에는 contentEditable의 innerText가 저장된다. 비제어 컴포넌트이기 때문에 상태 값을 저장할 필요가 없다고 생각될 수 있지만 입력값을 즉각적으로 validation 하기 위해 추가하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&amp;nbsp;contentEditable은 input은 아니지만 element.focus() 메서드를 사용하여 포커스를 줄 수 있다. 하지만 이렇게 포커스를 주게 되는 경우, &lt;b&gt;contentEditable에 작성된 내용이 있어도 포커스가 맨 앞쪽에 위치하게 된다. 일반적으로 input에 포커스를 주면 작성된 내용의 가장 뒤로 가는 것과는 대조적&lt;/b&gt;이다. 따라서 별도의 focus 함수를 만들어 사용해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629622183646&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const focusContentEditableTextToEnd = (element: HTMLElement) =&amp;gt; {
  if (element.innerText.length === 0) {
    element.focus();

    return;
  }

  const selection = window.getSelection();
  const newRange = document.createRange();
  newRange.selectNodeContents(element);
  newRange.collapse(false);
  selection?.removeAllRanges();
  selection?.addRange(newRange);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;만약 contentEditable의 innerText의 길이가 0이라면 작성된 내용이 없다는 것이므로 focus를 사용해도 된다. 하지만 작성된 내용이 있는 경우에는 포커스를 가장 뒤쪽에 붙이기 위해 현재 Caret(커서)의 위치를 변경해줘야 한다. caret의 위치를 찾고 contentEditable의 range(드래그 된 영역)를 맨 끝으로 이동시킨다. 마지막으로 기존 range를 삭제하고 새로운 range를 추가함으로써 contentEditable 내용 끝에 커서를 위치 시킬 수 있다.&lt;/p&gt;</description>
      <category>웹 개발/웹</category>
      <category>contenteditable</category>
      <category>contentEditable focus</category>
      <category>Focus</category>
      <category>에디터</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/109</guid>
      <comments>https://yung-developer.tistory.com/109#entry109comment</comments>
      <pubDate>Sun, 22 Aug 2021 17:59:06 +0900</pubDate>
    </item>
    <item>
      <title>댓글 모듈 iframe 개발기</title>
      <link>https://yung-developer.tistory.com/108</link>
      <description>&lt;figure id=&quot;og_1629558366127&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - woowacourse-teams/2021-darass:     웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &amp;quot;다라&quot; data-og-description=&quot;  웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &amp;quot;다라쓰&amp;quot;. Contribute to woowacourse-teams/2021-darass development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/woowacourse-teams/2021-darass&quot; data-og-url=&quot;https://github.com/woowacourse-teams/2021-darass&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WqAs1/hyLk4Vn9Wx/aFvjv6NB7qkFzeD9QV1aMk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/9WFFs/hyLjJyxZmq/7RS4q5l1OZweq8sI4ovFK1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=159_102_299_255,https://scrap.kakaocdn.net/dn/6f2nV/hyLljdWaHH/FPNaZPYJHziWpdX2kO5K3K/img.jpg?width=460&amp;amp;height=460&amp;amp;face=285_190_376_289&quot;&gt;&lt;a href=&quot;https://github.com/woowacourse-teams/2021-darass&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/woowacourse-teams/2021-darass&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WqAs1/hyLk4Vn9Wx/aFvjv6NB7qkFzeD9QV1aMk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/9WFFs/hyLjJyxZmq/7RS4q5l1OZweq8sI4ovFK1/img.jpg?width=460&amp;amp;height=460&amp;amp;face=159_102_299_255,https://scrap.kakaocdn.net/dn/6f2nV/hyLljdWaHH/FPNaZPYJHziWpdX2kO5K3K/img.jpg?width=460&amp;amp;height=460&amp;amp;face=285_190_376_289');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - woowacourse-teams/2021-darass:   웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &quot;다라&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  웹 페이지 어디든 간편하게 추가하는 댓글 모듈 서비스 &quot;다라쓰&quot;. Contribute to woowacourse-teams/2021-darass development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;웹 페이지 어디든 쉽고 간편하게 추가하는 댓글 모듈 서비스 &quot;다라쓰&quot;를 만들면서 겪었던 많은 어려움 중 iframe을 사용하면서 마주했던 이슈들에 대해 회고하는 시간을 가져보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #575757;&quot;&gt;다라쓰에서 iframe을 사용한 이유는 간단하다. 기존 페이지에 영향을 주지 않으면서 우리 서비스를 적용해야하기 때문이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt; &amp;nbsp;iframe은 inline frame의 약자로&amp;nbsp;&lt;span style=&quot;color: #575757;&quot;&gt;iframe을 이용하면 해당 웹 페이지 안에 어떠한 제한 없이 또 다른 하나의 웹 페이지를 삽입할 수 있다.&lt;span&gt; iframe은 부모 document 안에 새로운 document가 생성된다. 그리고 이 둘은 서로 독립적이다. 부모에 적용된 스타일을 자식은 적용 받지 않는다. 따라서 모듈 서비스처럼 기존 서비스에 영향을 주거나 받지 않아야하는 경우에 사용하기 적합하다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span&gt;&amp;nbsp;만약 iframe을 사용하지 않고 부모 요소에 바로 element를 추가하는 식으로 코드를 작성한다면 어떤 일이 일어날까?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span&gt;&amp;nbsp;바로, 우리가 작성한 스타일이 제대로 그려질 것이라는 보장할 수 없게 된다. 부모 요소에서 우선 순위가 높은 스타일이 우리가 작성한 스타일을 덮어씌울 수도 있다. 무엇보다도 부모 요소에서 어떤 영역에 댓글 모듈이 그려질지 알 수가 없기 때문에 반응형으로 코드를 짤 수가 없다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span&gt;&amp;nbsp;예를 들어, 부모 요소의 뷰포트가 1920px이지만 댓글 모듈은 화면의 가운데 1000px에 해당하는 영역으로 그려진다고 가정해보자. 댓글 모듈을 iframe으로 만들지 않으면 댓글 모듈이 실제로 그려지는 영역은 1000px이지만 1920px에 맞는 반응형 화면을 그리게 될 것이다. 따라서 댓글 모듈은 부자연스럽게 크게 그려질 것이고 짤리는 영역이 생길 것이다. 하지만 iframe을 사용하면 iframe이 그려지는 영역이 곧 iframe의 뷰포트 사이즈가 되기 때문에 반응형으로 만들기가 수월해진다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span style=&quot;color: #575757;&quot;&gt;&lt;span&gt;&amp;nbsp;이제 iframe을 사용하면서 겪었던 문제점과 그것들을 어떻게 해결해나갔는지에 대해 알아보자.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. iframe 높이 동적으로 변경하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;iframe은 height 속성을 사용하여 iframe의 높이를 설정할 수 있다. 높이를 설정해주지 않으면 컨텐츠의 높이에 맞게 동적으로 높이가 설정될 것 같지만 그렇지 않다. height을 설정해놓지 않는 경우, 크롬 기준 높이는 자동으로 150px로 설정된다. 컨텐츠의 높이가 150px이 넘어가게 되면 스크롤이 등장하게 되고 굉장히 부자연스러워진다. 때문에 iframe의 높이를 컨텐츠의 높이로 일치 시켜줘야한다. 하지만 댓글 모듈처럼 댓글의 추가 혹은 삭제로 인해 컨텐츠의 높이가 계속해서 달라진다면 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;댓글이 0개인 경우가 가장 기본값이라고 판단하여 iframe의 height 을 596px로 고정하면 처음에는 다음과 같이 자연스럽게 iframe이 출력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;564&quot; width=&quot;624&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NP5XS/btrcMVmy1mP/uya4dE84cAH3Tm3OC77zEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NP5XS/btrcMVmy1mP/uya4dE84cAH3Tm3OC77zEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NP5XS/btrcMVmy1mP/uya4dE84cAH3Tm3OC77zEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNP5XS%2FbtrcMVmy1mP%2Fuya4dE84cAH3Tm3OC77zEk%2Fimg.png&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;564&quot; width=&quot;624&quot; height=&quot;371&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 댓글이 추가되어 컨텐츠의 높이가 변경되면 다음과 같이 스크롤이 생기게 된다. 다라쓰를 블로그에 달고자 하는 사용자의 입장에서 댓글을 아래와 같이 스크롤링으로 확인해야한다면 사용하고 싶지 않을 것이다. 이는 치명적인 결함이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;542&quot; width=&quot;642&quot; height=&quot;372&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CFwG6/btrcMWeL354/5ZnkICukIHpLH47WrglfcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CFwG6/btrcMWeL354/5ZnkICukIHpLH47WrglfcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CFwG6/btrcMWeL354/5ZnkICukIHpLH47WrglfcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCFwG6%2FbtrcMWeL354%2F5ZnkICukIHpLH47WrglfcK%2Fimg.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;542&quot; width=&quot;642&quot; height=&quot;372&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 컨텐츠의 높이가 변경될 때 마다 iframe의 height도 동적으로 변경되야한다. 이를 위해서는 iframe과 부모 window가 서로 통신을 해야한다. 이를 위해 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/postMessage&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;window.postMessage&lt;/a&gt;를 사용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629608416248&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// deploy-script (부모 window = iframe을 사용하는 쪽)
window.addEventListener(&quot;message&quot;, ({ data: { type, data } }) =&amp;gt; {
  if (!type) return;

  if (type === POST_MESSAGE_TYPE.SCROLL_HEIGHT) {
    resizeElementHeight({ element: $replyModuleIframe, height: data });
    return;
  }
});


// reply-module (content window = iframe에 그려지는 페이지)
// 부모 window에 컨텐츠의 높이를 보내는 함수
const postScrollHeightToParentWindow = () =&amp;gt; {
  window.parent.postMessage(
    { type: POST_MESSAGE_TYPE.SCROLL_HEIGHT, data: document.querySelector(&quot;#root&quot;)?.scrollHeight },
    &quot;*&quot;
  );
};

// 반응형으로 html font size가 바뀌었을 때 높이를 iframe에 메세지 형태로 전달
const onResize = () =&amp;gt; {
  let throttle: NodeJS.Timeout | null;

  const runThrottle = () =&amp;gt; {
    if (!throttle) {
      throttle = setTimeout(() =&amp;gt; {
        throttle = null;
        postScrollHeightToParentWindow();
      }, 600);
    }
  };
  return runThrottle;
};

window.addEventListener(&quot;resize&quot;, onResize());

// 댓글에 따라 iframe 높이 변경
useEffect(() =&amp;gt; {
  postScrollHeightToParentWindow();
}, [comments]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;reply module은 iframe에 그려지는 페이지이다. reply module의 높이가 변할 때마다 부모 window에 postMessage로 변경된 높이를 보내고 있다. deploy-script는 부모 window로 iframe을 사용하는 쪽이다. deploy-script는 높이를 변경하는 message 이벤트가 발생할 때 iframe의 height을 변경시킨다. 이를 통해 댓글의 추가나 삭제 그리고 iframe의 뷰포트 변경에 따른 반응형 페이지의 높이도 동적으로 변경할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;670&quot; width=&quot;591&quot; height=&quot;417&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3W59B/btrcMHCp77R/errSfJFNiWgMwJIS3afK71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3W59B/btrcMHCp77R/errSfJFNiWgMwJIS3afK71/img.png&quot; data-alt=&quot;스크롤 없이 댓글 모듈이 정상적으로 나오는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3W59B/btrcMHCp77R/errSfJFNiWgMwJIS3afK71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3W59B%2FbtrcMHCp77R%2FerrSfJFNiWgMwJIS3afK71%2Fimg.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;670&quot; width=&quot;591&quot; height=&quot;417&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스크롤 없이 댓글 모듈이 정상적으로 나오는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. iframe에서 모달 띄우기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;iframe에서 모달을 띄우려면 어떻게 해야할까? iframe에서 일반적인 방법으로 모달창을 띄우게 되면 iframe에 해당하는 영역만 dimmed 처리된다. 또한 모달창이 iframe의 정중앙에는 나올 수 있지만 부모 창의 중앙에는 나오지 않는다. 이는 일반적인 모달과는 상당히 거리가 있어보이고 사용자에게 어색함을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 iframe에서 모달을 정상적으로 띄우기 위해 모달만을 위한 iframe을 별도로 분리하였다. 두 개의 iframe으로 분리한 이유는 뷰포트의 영역을 각기 다르게 주기 위함이다. 댓글 모듈은 width: 100%, height: 동적으로 변경, 모달은 width: 100vw, height: 100vh로 설정하였다. 또한, 모달 iframe은 초기에 display 속성이 none이였다가 이후 모달이 뜨는 상황에 display 속성이 block으로 변경된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;모달 iframe은 기존 댓글 모듈 코드에서 webpack의 entry point를 index.tsx와 modal.tsx로 나누어 index.html과 modal.html로 분리되도록 하였다. 이로써 댓글 모듈 iframe과 모달 iframe의 각각의 html을 바라볼 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1629636228975&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Code Splitting | 웹팩&quot; data-og-description=&quot;웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.&quot; data-og-host=&quot;webpack.kr&quot; data-og-source-url=&quot;https://webpack.kr/guides/code-splitting/&quot; data-og-url=&quot;https://webpack.kr/guides/code-splitting/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nKDPg/hyLk7k37PC/GgPEK7hpxSjAez5NFPtonK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://webpack.kr/guides/code-splitting/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://webpack.kr/guides/code-splitting/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nKDPg/hyLk7k37PC/GgPEK7hpxSjAez5NFPtonK/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Code Splitting | 웹팩&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹팩은 모듈 번들러입니다. 주요 목적은 브라우저에서 사용할 수 있도록 JavaScript 파일을 번들로 묶는 것이지만, 리소스나 애셋을 변환하고 번들링 또는 패키징할 수도 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;webpack.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629613645638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const config = {
  entry: { replyModule: &quot;./src/index.tsx&quot;, modal: &quot;./src/Modal.tsx&quot; },
  output: {
    path: path.resolve(__dirname, &quot;dist&quot;),
    filename: `[name]-${Package.version.replace(&quot;^&quot;, &quot;&quot;)}.js`
  },
  ...
  plugins: [
    new HtmlWebpackPlugin({
      filename: &quot;index.html&quot;,
      chunks: [&quot;replyModule&quot;],
      template: &quot;./public/index.html&quot;
    }),
    new HtmlWebpackPlugin({
      filename: &quot;modal.html&quot;,
      chunks: [&quot;modal&quot;],
      template: &quot;./public/modal.html&quot;
    }),
  ],
  ...
}

module.exports = config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글 모듈에서 모달을 띄우기 위한 흐름은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글 모듈 iframe에서 모달을 띄우는 이벤트 발생 -&amp;gt; 부모 window에 모달을 띄우라는 메시지 전송 -&amp;gt; 부모 window에서 모달 iframe에 모달을 띄우라는 메시지 전송 -&amp;gt; 모달 iframe에서 메시지를 받아 모달을 화면에 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달을 닫는 흐름은 띄우는 것과 반대이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629614039765&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// reply-module (댓글 모듈 iframe)
// postOpenLikingUsersModal 함수 실행시 부모 window에 모달을 띄워달라는 메세지를 보냄
const postOpenLikingUsersModal = (likingUsers: Comment[&quot;likingUsers&quot;]) =&amp;gt; {
  window.parent.postMessage({ type: POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL, data: likingUsers }, &quot;*&quot;);
};

// deply-script (부모 window)
window.addEventListener(&quot;message&quot;, ({ data: { type, data } }) =&amp;gt; {
    if (!type) return;

	// iframe 컨텐츠 높이 변경시 iframe 높이 변경
    if (type === POST_MESSAGE_TYPE.SCROLL_HEIGHT) {
      resizeElementHeight({ element: $replyModuleIframe, height: data });
      return;
    }

	// 좋아요 모달 열기
    if (type === POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL) {
      $modalIframe.contentWindow.postMessage({ type: POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL, data }, &quot;*&quot;);
      showElement($modalIframe);
      return;
    }

	// 모달 닫기
    if (type === POST_MESSAGE_TYPE.CLOSE_MODAL) {
      $replyModuleIframe.contentWindow.postMessage({ type: POST_MESSAGE_TYPE.CLOSE_MODAL }, &quot;*&quot;);
      hideElement($modalIframe);
      return;
    }
 	
    // 컨펌 모달 열기
 	if (type === POST_MESSAGE_TYPE.OPEN_CONFIRM) {
      $modalIframe.contentWindow.postMessage({ type: POST_MESSAGE_TYPE.OPEN_CONFIRM, data }, &quot;*&quot;);
      showElement($modalIframe);
      return;
    }

	// 컨펌 모달 닫기
    if (type === POST_MESSAGE_TYPE.CLOSE_CONFIRM) {
      $replyModuleIframe.contentWindow.postMessage({ type: POST_MESSAGE_TYPE.CLOSE_CONFIRM }, &quot;*&quot;);
      hideElement($modalIframe);
      return;
    }
    ...
  });


// Modal.tsx (모달 iframe)
// 부모 window가 보낸 메시지를 받아 모달을 화면에 렌더링
const isValidMessageType = (type: string) =&amp;gt;
  [POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL, POST_MESSAGE_TYPE.OPEN_CONFIRM].some(_type =&amp;gt; _type === type);

const Modal = () =&amp;gt; {
  const [data, setData] = useState&amp;lt;{ type: string; data: any }&amp;gt;();

  useEffect(() =&amp;gt; {
    window.addEventListener(&quot;message&quot;, ({ data }: MessageEvent) =&amp;gt; {
      if (!isValidMessageType(data.type)) return;

      if (POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL) setData(data);
      if (POST_MESSAGE_TYPE.OPEN_CONFIRM) setData(data);
    });
  }, []);

  if (data?.type === POST_MESSAGE_TYPE.OPEN_LIKING_USERS_MODAL) return &amp;lt;LikingUsersModal users={data.data as User[]} /&amp;gt;;
  if (data?.type === POST_MESSAGE_TYPE.OPEN_CONFIRM) return &amp;lt;ConfirmModal message={data.data as string} /&amp;gt;;

  return null;
};

ReactDOM.render(
  &amp;lt;&amp;gt;
    &amp;lt;GlobalStyles /&amp;gt;
    &amp;lt;Modal /&amp;gt;
  &amp;lt;/&amp;gt;,
  document.getElementById(&quot;root&quot;)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1171&quot; width=&quot;1105&quot; height=&quot;1124&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ8din/btrcM0ocVz3/msKmWzvG4mpeJ74IrykS80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ8din/btrcM0ocVz3/msKmWzvG4mpeJ74IrykS80/img.png&quot; data-alt=&quot;모달이 정상적으로 뜨는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ8din/btrcM0ocVz3/msKmWzvG4mpeJ74IrykS80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ8din%2FbtrcM0ocVz3%2FmsKmWzvG4mpeJ74IrykS80%2Fimg.png&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1171&quot; width=&quot;1105&quot; height=&quot;1124&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모달이 정상적으로 뜨는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 개발/웹</category>
      <category>iframe</category>
      <category>다라쓰</category>
      <category>댓글 모듈</category>
      <category>모달</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/108</guid>
      <comments>https://yung-developer.tistory.com/108#entry108comment</comments>
      <pubDate>Sun, 22 Aug 2021 00:26:55 +0900</pubDate>
    </item>
    <item>
      <title>[React] 리스트와 Key</title>
      <link>https://yung-developer.tistory.com/107</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트에 익숙한 사람이라면 반복되는 컴포넌트 혹은 엘리먼트에는 고유한 키 값이 필요함을 알고 있다. 동시에 키값으로 인덱스 값을 주는 것이 안티패턴이라는 사실도 알고 있다. 그렇다면 왜 키값으로 인덱스를 주는 것이 안티패턴일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1624278324791&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;재조정 (Reconciliation) &amp;ndash; React&quot; data-og-description=&quot;A JavaScript library for building user interfaces&quot; data-og-host=&quot;ko.reactjs.org&quot; data-og-source-url=&quot;https://ko.reactjs.org/docs/reconciliation.html#keys&quot; data-og-url=&quot;https://ko.reactjs.org/docs/reconciliation.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eiyWDv/hyKD006Lpj/g8X0d0OVeskKzGThXx3E50/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/reconciliation.html#keys&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.reactjs.org/docs/reconciliation.html#keys&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eiyWDv/hyKD006Lpj/g8X0d0OVeskKzGThXx3E50/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;재조정 (Reconciliation) &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A JavaScript library for building user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.reactjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;148&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KB94C/btq7MbnuHaq/L0abmnV2oSDxi95dgCLgEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KB94C/btq7MbnuHaq/L0abmnV2oSDxi95dgCLgEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KB94C/btq7MbnuHaq/L0abmnV2oSDxi95dgCLgEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKB94C%2Fbtq7MbnuHaq%2FL0abmnV2oSDxi95dgCLgEk%2Fimg.png&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;148&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;131&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddqUFA/btq7NypFUHw/kN0U8mhmLuOPaHhPISrNVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddqUFA/btq7NypFUHw/kN0U8mhmLuOPaHhPISrNVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddqUFA/btq7NypFUHw/kN0U8mhmLuOPaHhPISrNVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddqUFA%2Fbtq7NypFUHw%2FkN0U8mhmLuOPaHhPISrNVk%2Fimg.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;131&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;리액트에서 Key는 리액트가 어떤 항목을 변경, 추가 또는 삭제할 때 트리를 매번 새로 그리는 것이 아니라 기존과 비교하여 효율적으로 트리를 변경할 수 있도록 돕는 역할이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;이제 키 값이 왜 사용되는지는 알겠다. 그러면 왜 키 값으로 index가 아닌 고유한 값을 사용해야하는 것일까? &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리액트 공식문서에 있는 codepen을 통해 간단한 실험을 해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;b&gt;먼저, 키 값을 인덱스로 받을 때의 상황이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/redirect-to-codepen/reconciliation/index-used-as-key&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.reactjs.org/redirect-to-codepen/reconciliation/index-used-as-key&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 의도한 것과는 다르게 input이 해당 ID를 따라가지 못하고 제자리에 머물러있는 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;312&quot; data-filename=&quot;key_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czjV7B/btq7U2bD3CF/6u3DRKU9OmgkylFgDeAKo1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czjV7B/btq7U2bD3CF/6u3DRKU9OmgkylFgDeAKo1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czjV7B/btq7U2bD3CF/6u3DRKU9OmgkylFgDeAKo1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/czjV7B/btq7U2bD3CF/6u3DRKU9OmgkylFgDeAKo1/img.gif&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;312&quot; data-filename=&quot;key_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;b&gt;다음으로 고유한 키 값을 받을 때의 상황이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.reactjs.org/redirect-to-codepen/reconciliation/no-index-used-as-key&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ko.reactjs.org/redirect-to-codepen/reconciliation/no-index-used-as-key&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;312&quot; data-filename=&quot;key_no_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIXME/btq7UrbVRMD/tBuJzFPlyGm8SJLRDretbk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIXME/btq7UrbVRMD/tBuJzFPlyGm8SJLRDretbk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIXME/btq7UrbVRMD/tBuJzFPlyGm8SJLRDretbk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/nIXME/btq7UrbVRMD/tBuJzFPlyGm8SJLRDretbk/img.gif&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;312&quot; data-filename=&quot;key_no_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 원하는 대로 input이 ID를 따라가는 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 이런 차이가 생기는 걸까?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면&lt;b&gt;&amp;nbsp;키값으로 인덱스를 사용하는 경우 키값의 위치가 변하지 않기 때문에 예상과는 다른 결과를 렌더링하게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;먼저 리액트의 동작원리를 간단하게 짚고 넘어가보자. 리액트에서는 키값이 바뀌지 않으면 새로운 인스턴스를 생성하지 않는다. 즉, 이전에 만들어졌던 컴포넌트 혹은 DOM 엘리먼트를 그대로 재활용한다(=초기화하지 않는다는 의미로 useState가 유지된다). 배열에 새로운 요소가 추가되어 리렌더링이 일어나면 리액트는 배열을 돌면서 각 컴포넌트를 실행시킨다. 이때 앞서 키 값이 변하지 않았다면 이전에 만들어두었던 컴포넌트 혹은 DOM 엘리먼트를 재사용한다. 단. 컴포넌트나 DOM에 들어가는 Props 혹은 content 등이 바뀌면 바뀐 내용으로 리렌더링이 일어난다. 즉, 리액트는 메모리 상에 기존 DOM 엘리먼트는 그대로 존재하고 바뀐 내용만 반영한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 예제에서 다른 모든 요소들은 바뀌는데 input만 위치가 그대로인 이유는 바로 input element가 가지는 특징 때문이다. 예제의 input은 비제어 컴포넌트로 리액트의 제어를 받지 않는다. 따라서 input 스스로 값을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;키값을 인덱스로 했을 때의 상황을 차근차근 정리해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;371&quot; width=&quot;401&quot; height=&quot;192&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d77pym/btq7SFIlxp0/apyZmoJamdDK0HgzxYwJY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d77pym/btq7SFIlxp0/apyZmoJamdDK0HgzxYwJY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d77pym/btq7SFIlxp0/apyZmoJamdDK0HgzxYwJY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd77pym%2Fbtq7SFIlxp0%2FapyZmoJamdDK0HgzxYwJY1%2Fimg.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;371&quot; width=&quot;401&quot; height=&quot;192&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;가장 처음 렌더링 된 화면은 위의 컴포넌트가 렌더링 된 결과이다. 이후 input에 'first'라는 값을 입력하고 추가 버튼을 누르면 배열에 ID가 2인 요소가 추가된다. 리액트는 상태의 변경을 감지하고 ID가 1인 요소와 ID가 2인 요소를 내림차순으로 렌더링한다(Add New to Start 버튼은 배열의 가장 맨 앞에 요소를 추가한다는 의미).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;781&quot; width=&quot;400&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7LZpo/btq7Q8EDs00/jiMTqJUR55feJOfK4DTQOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7LZpo/btq7Q8EDs00/jiMTqJUR55feJOfK4DTQOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7LZpo/btq7Q8EDs00/jiMTqJUR55feJOfK4DTQOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7LZpo%2Fbtq7Q8EDs00%2FjiMTqJUR55feJOfK4DTQOK%2Fimg.png&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;781&quot; width=&quot;400&quot; height=&quot;401&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트는 새롭게 생성된 배열을 반영하여 map을 돈다. Add New to Start 버튼으로 추가된 요소는 배열의 가장 앞에 추가되기 때문에 ID 2 요소가 index 0번이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;index를 키값으로 줬기 때문에 리액트는 key가 0인 컴포넌트에 들어오는 props가 달라졌음에도 불구하고 기존에 ID 1이 만들어놓은 컴포넌트와 DOM 엘리먼트를 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이때 key가 0인 컴포넌트는 처음에 'first'가 입력된 input 역시 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ID 1은 기존에 없던 index 1번을 키값으로 하기 때문에 새롭게 DOM 엘리먼트를 추가한다. 이 때문에 ID 1의 input은 비어있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음으로 &lt;b&gt;키값으로 인덱스를 사용했을 때 상태값이 개발자가 의도한대로 관리되지 않는 경우&lt;/b&gt;를 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1624289226870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

const ListItem = ({ initName }) =&amp;gt; {
  const [name, setName] = useState(initName);

  return &amp;lt;div&amp;gt;{name}&amp;lt;/div&amp;gt;;
};

const App = () =&amp;gt; {
  const [crews, setCrews] = useState([
    { id: 1, name: &quot;지그&quot; },
    { id: 2, name: &quot;피터&quot; },
    { id: 3, name: &quot;크리스&quot; },
  ]);
  const [newCrewName, setNewCrewName] = useState(&quot;&quot;);
  const [idCounter, setIdCounter] = useState(crews.length + 2);

  const addNewCrew = (e) =&amp;gt; {
    e.preventDefault();
    setCrews((state) =&amp;gt; [{ id: idCounter, name: newCrewName }, ...state]);
    setIdCounter((state) =&amp;gt; ++state);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;form onSubmit={addNewCrew}&amp;gt;
        새로운 크루
        &amp;lt;input
          value={newCrewName}
          onChange={(e) =&amp;gt; {
            setNewCrewName(e.target.value);
          }}
        /&amp;gt;
      &amp;lt;/form&amp;gt;
      &amp;lt;br /&amp;gt;
      &amp;lt;div&amp;gt;크루들 이름&amp;lt;/div&amp;gt;
      {crews.map((crew, index) =&amp;gt; (
        &amp;lt;ListItem key={index} initName={crew.name} /&amp;gt;
      ))}
    &amp;lt;/&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;240&quot; data-filename=&quot;state_key_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KKdRm/btq7OBGgAuU/c9xXKF5Zu3lRsUMUlK3aVK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KKdRm/btq7OBGgAuU/c9xXKF5Zu3lRsUMUlK3aVK/img.gif&quot; data-alt=&quot;key 값에 index를 넣는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KKdRm/btq7OBGgAuU/c9xXKF5Zu3lRsUMUlK3aVK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/KKdRm/btq7OBGgAuU/c9xXKF5Zu3lRsUMUlK3aVK/img.gif&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;240&quot; data-filename=&quot;state_key_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;key 값에 index를 넣는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;키 값에 index를 넣는 경우, 새로운 크루를 배열의 가장 앞에 추가했음에도 지그, 피터, 크리스 순서가 변경되지 않는다. 또한 '곤이'를 추가하여도 '크리스'라는 이름만 추가되고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;useState는 인스턴스가 새로 생성되거나 setState로 상태를 바꾸기 전까지 state 값이 변경되지 않는다. 키 값으로 index를 사용한 경우 배열에 요소가 추가됐을 때 이전 키 값의 위치는 변하지 않는다. 때문에 initname으로 다른 값이 들어가더라도 key 값이 변하지 않았기 때문에 useState가 state를 그대로 유지하고 기존 state 값을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;어떤 값을 입력하더라도 마지막 크루의 이름이 추가되는 이유는 무엇 때문일까? 배열에 요소가 추가될 때 새로운 크루가 배열의 가장 앞에 위치하기 때문에 가장 마지막 요소였던 크리스는 항상 배열의 뒤쪽에 위치한다. 때문에 새로운 컴포넌트의 props로 크리스가 넘어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;240&quot; data-filename=&quot;state_key_no_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOk4MA/btq7P7ZavRO/MGJ4X8YYf9jn3i660HnKZ1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOk4MA/btq7P7ZavRO/MGJ4X8YYf9jn3i660HnKZ1/img.gif&quot; data-alt=&quot;key 값에 유니크한 id를 넣는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOk4MA/btq7P7ZavRO/MGJ4X8YYf9jn3i660HnKZ1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bOk4MA/btq7P7ZavRO/MGJ4X8YYf9jn3i660HnKZ1/img.gif&quot; data-origin-width=&quot;343&quot; data-origin-height=&quot;240&quot; data-filename=&quot;state_key_no_index.gif&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;key 값에 유니크한 id를 넣는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이상으로 키 값으로 index를 주면 안되는 이유에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 키 값으로 마땅하게 줄 유니크한 값이 없는 상황이라면 불가피하게 index를 줄 수 밖에 없다. 다음과 같은 상황에서는 키 값으로 index를 주더라도 문제가 발생하지 않는다. DOM 엘리먼트의 변경이 불필요하기 때문이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;목록의 순서가 변경되지 않는다.&lt;/li&gt;
&lt;li&gt;목록이 정렬되거나 필터링되지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>웹 개발/React</category>
      <category>React key</category>
      <category>리액트 리스트 key</category>
      <category>리액트 리스트 키</category>
      <category>리액트 키</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/107</guid>
      <comments>https://yung-developer.tistory.com/107#entry107comment</comments>
      <pubDate>Tue, 22 Jun 2021 00:32:12 +0900</pubDate>
    </item>
    <item>
      <title>React Router의 Hash Router와 Browser Router</title>
      <link>https://yung-developer.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. React Router의 Hash Router와 Browser Router&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트 라우터에는 자주 사용되는 두 가지의 라우터가 있다. 바로 Hash Router와 Browser Router이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 2-2, 2-3 미션에는 Hash Router를 사용하였고 2-4 단계 미션에는 Browser Router를 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 두 라우터의 차이를 짚어보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hash Router&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소에 해쉬(#)가 붙는다.&lt;/li&gt;
&lt;li&gt;검색 엔진이 읽지 못한다.&lt;/li&gt;
&lt;li&gt;별도의 서버 설정을 하지 않더라도 새로고침 시 오류가 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Browser Router&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;History API를 사용한다.&lt;/li&gt;
&lt;li&gt;별도의 서버 설정을 하지 않으면 새로고침 시 404 에러가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Hash Router는 검색 엔진 최적화가 지원되지 않기 때문에 사용하지 않는 것이 좋다. 그럼에도 불구하고 이전 두 개의 미션에서 Hash Router를 쓴 이유는 바로 경로 찾기 문제 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Browser Router를 별도의 서버 설정 없이 사용하면 새로고침이나 올바르지 않은 URL 접근을 리디렉션 할 때 404 에러가 발생한다. 이는 우리가 만든 앱이 SPA이기 때문에 발생한다. SPA는 기본적으로 하나의 entry point를 가진다. 도메인에 접근하면 클라이언트는 서버로부터 index.html과 JS, CSS 파일 등을 전달받고 이를 실행시킨다. 기본적으로 한번의 페이지 로드만이 존재하고 이후부터는 History API에 의해 렌더링 될 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Brower Router를 사용할 때 도메인의 루트 주소에서의 새로고침은 전혀 문제되지 않지만 다른 path에서의 새로고침이나 리디렉션이 불가능한 이유는 바로 여기에 있다. 우리가 다른 path에서 새로고침을 하게 되면 브라우저는 도메인 주소를 보고 우리의 서버를 찾아간 뒤 path를 보고 path 명과 같은 디렉토리를 찾는다. 하지만 우리의 앱은 서버사이드 렌더링이 아니기에 path명과 같은 폴더를 가지지 않을 뿐더러 그 안에 index.html이 존재하지도 않는다. 결국, 브라우저는 렌더링에 필요한 파일을 서버로부터 찾지 못했기 때문에 404 에러를 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 Hash Router는 어떻게 새로고침이나 리디렉션의 문제로부터 자유로울 수 있는 것일까? 그것은 바로 브라우저가 서버에 요청을 보낼 때, # 이전의 도메인 주소로 요청을 보내기 때문이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Hash Router는 # 이후에 path가 오도록 만든다. Hash Router를 사용하게 되면 다음과 같은 URL 주소를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623288793752&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;www.your-domain.com/#/path/1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 Hash Router를 사용하면 # 뒤에 path가 오기 때문에 새로고침과 리디렉션을 해도 모두 올바른 entry point로 요청이 갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 Browser Router에서는 새로고침과 리디렉션 404 에러를 해결할 수 있는 방법이 없는 것일까? 그것은 아니다. 앞서 언급했듯이 서버에서 별도의 설정을 하면 된다. '호곡, 서버 설정이라니 너무 어려운 것 아닐까?' 라고 생각할 수 있지만 의외로 간단하게 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;정적 페이지 배포에 자주 쓰이는 netlify를 예시로 어떻게 서버 설정을 할 수 있는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트 프로젝트의 public 폴더에 'netlify.toml'이라는 파일을 생성한 뒤 다음과 같은 코드를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623288808393&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[[redirects]]   
  from = &quot;/*&quot;   
  to = &quot;/index.html&quot;   
  status = 200&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이렇게 작성하면 netlify 서버에서 다른 path로 들어온 모든 요청에 대해 index.html로 리디렉션 해준다. 따라서 Browser Router를 사용하더라도 새로고침과 리디렉션 시 우리가 원하는 entry point로 진입하고 React Router가 path를 읽어드려 우리가 원하던 페이지를 렌더링 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>웹 개발/React</category>
      <category>Browser Router</category>
      <category>Hash Router</category>
      <category>react router</category>
      <category>리액트 라우터</category>
      <category>브라우저 라우터</category>
      <category>해쉬 라우터</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/106</guid>
      <comments>https://yung-developer.tistory.com/106#entry106comment</comments>
      <pubDate>Thu, 10 Jun 2021 10:32:44 +0900</pubDate>
    </item>
    <item>
      <title>우아한테크코스 프론트 : Lv.2-4 지하철 노선도 미션 인사이트</title>
      <link>https://yung-developer.tistory.com/105</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. React Router의 Hash Router와 Browser Router&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트 라우터에는 자주 사용되는 두 가지의 라우터가 있다. 바로 Hash Router와 Browser Router이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 2-2, 2-3 미션에는 Hash Router를 사용하였고 2-4 단계 미션에는 Browser Router를 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 두 라우터의 차이를 짚어보면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hash Router&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소에 해쉬(#)가 붙는다.&lt;/li&gt;
&lt;li&gt;검색 엔진이 읽지 못한다.&lt;/li&gt;
&lt;li&gt;별도의 서버 설정을 하지 않더라도 새로고침 시 오류가 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Browser Router&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;History API를 사용한다.&lt;/li&gt;
&lt;li&gt;별도의 서버 설정을 하지 않으면 새로고침 시 404 에러가 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Hash Router는 검색 엔진 최적화가 지원되지 않기 때문에 사용하지 않는 것이 좋다. 그럼에도 불구하고 이전 두 개의 미션에서 Hash Router를 쓴 이유는 바로 경로 찾기 문제 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Browser Router를 별도의 서버 설정 없이 사용하면 새로고침이나 올바르지 않은 URL 접근을 리디렉션 할 때 404 에러가 발생한다. 이는 우리가 만든 앱이 SPA이기 때문에 발생한다. SPA는 기본적으로 하나의 entry point를 가진다. 도메인에 접근하면 클라이언트는 서버로부터 index.html과 JS, CSS 파일 등을 전달받고 이를 실행시킨다. 기본적으로 한번의 페이지 로드만이 존재하고 이후부터는 History API에 의해 렌더링 될 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Brower Router를 사용할 때 도메인의 루트 주소에서의 새로고침은 전혀 문제되지 않지만 다른 path에서의 새로고침이나 리디렉션이 불가능한 이유는 바로 여기에 있다. 우리가 다른 path에서 새로고침을 하게 되면 브라우저는 도메인 주소를 보고 우리의 서버를 찾아간 뒤 path를 보고 path 명과 같은 디렉토리를 찾는다. 하지만 우리의 앱은 서버사이드 렌더링이 아니기에 path명과 같은 폴더를 가지지 않을 뿐더러 그 안에 index.html이 존재하지도 않는다. 결국, 브라우저는 렌더링에 필요한 파일을 서버로부터 찾지 못했기 때문에 404 에러를 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 Hash Router는 어떻게 새로고침이나 리디렉션의 문제로부터 자유로울 수 있는 것일까? 그것은 바로 브라우저가 서버에 요청을 보낼 때, # 이전의 도메인 주소로 요청을 보내기 때문이다. Hash Router는 # 이후에 path가 오도록 만든다. Hash Router를 사용하게 되면 다음과 같은 URL 주소를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623242446757&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;www.your-domain.com/#/path/1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 Hash Router를 사용하면 # 뒤에 path가 오기 때문에 새로고침과 리디렉션을 해도 모두 올바른 entry point로 요청이 갈 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 Browser Router에서는 새로고침과 리디렉션 404 에러를 해결할 수 있는 방법이 없는 것일까? 그것은 아니다. 앞서 언급했듯이 서버에서 별도의 설정을 하면 된다. '호곡, 서버 설정이라니 너무 어려운 것 아닐까?' 라고 생각할 수 있지만 의외로 간단하게 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;정적 페이지 배포에 자주 쓰이는 netlify를 예시로 어떻게 서버 설정을 할 수 있는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리액트 프로젝트의 public 폴더에 'netlify.toml'이라는 파일을 생성한 뒤 다음과 같은 코드를 입력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623243557342&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[[redirects]]   
  from = &quot;/*&quot;   
  to = &quot;/index.html&quot;   
  status = 200&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이렇게 작성하면 netlify 서버에서 다른 path로 들어온 모든 요청에 대해 index.html로 리디렉션 해준다. 따라서 Browser Router를 사용하더라도 새로고침과 리디렉션 시 우리가 원하는 entry point로 진입하고 React Router가 path를 읽어드려 우리가 원하던 페이지를 렌더링 할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Redux Toolkit / Redux Saga / Redux Saga Test Plan&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redux Toolkit&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리덕스는 단 한개의 액션을 생성하는 데도 많은 양의 코드를 필요로 한다. 이러한 문제를 해결하고자 리덕스 툴킷이라는 공식 툴이 등장했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리덕스 툴킷을 쓰면서 확실히 작성해야하는 코드 양이 줄어듬을 느꼈다. 특히 툴킷의 'createSlice'를 이용하면 액션과 액션 생성 함수 그리고 리듀서까지 한번에 작성할 수 있어 편리했다. createSlice를 쓰면 Ducks 패턴을 따르게 되어 리덕스 관련 코드를 한 곳에서 관리할 수 있어 좋았다. 또한 툴킷 내부에서 immer를 사용하여 불변성을 보장해주기 때문에 객체를 관리하기 훨씬 수월했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redux Saga&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Redux Saga는 Redux Thunk와 같이 자주 사용되는 비동기 처리를 위한 Redux 미들웨어이다. Saga는 ES6에서 도입된 제네레이터(generator) 함수를 사용한다는 특징이 있다. 제네레이터는 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수이다. 일반 함수를 호출하면 제어권이 함수에게 넘어가고 함수 코드를 실행한다. 즉, 함수 호출자는 함수를 호출한 이후 함수 실행을 제어할 수 없다. 반면 제네레이터 함수는 함수 실행을 함수 호출자가 제어할 수 있다. 다시 말해, 함수 호출자가 함수 실행을 일시 중지시키거나 재개시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번 미션을 하면서 Saga가 Thunk 보다 훨씬 편리하고 유용하다는 느낌은 받지 못했다. 오히려 Saga만을 위한 액션과 리듀서를 생성해야한다는 점이 귀찮게 느껴지기도 했다. 특히 Saga와 타입스크립트를 같이 사용하는데에 꽤나 애를 먹었다. 하지만 Saga는 Thunk가 가지지 못하는 명확한 장점들이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;먼저, 'TakeLatest'와 'TakeEvery'를 통해 비동기 처리를 효율적으로 관리할 수 있다. 'TakeLatest' 사용하면 기존 요청을 모두 취소하고 가장 마지막에 들어온 요청만 처리하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;두번째는 특정 액션이 디스패치 될 때를 구독할 수 있다는 것이다. 하나의 사가를 여러개의 액션에서 활용할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;마지막으로 Saga만을 위한 테스트를 진행할 수 있다. Redux Saga Test Plan을 이용하면 Saga의 동작과 그에 따른 리듀서의 상태를 테스트 할 수 있어 테스트 단위를 작게 가져갈 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623284604751&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function* addStationSaga(action: PayloadAction&amp;lt;AddStationPayload&amp;gt;) {
  yield put(pending());
  const response: HttpResponse&amp;lt;Station&amp;gt; = yield call(stationAPI.addStation, action.payload);

  if (response.error) {
    yield put(error(response.error));
    return;
  }

  const stations: Station[] = yield select(selectStations);
  yield put(setStations([Object.assign(response.data, { lines: [] }), ...stations]));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1623287120857&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// addStationSaga test
it('지하철 역 목록을 성공적으로 추가한다.', async () =&amp;gt; {
  return expectSaga(addStationSaga, { type: addStationAsync.type, payload: newStation.name })
    .withReducer(stationReducer)
    .put(pending())
    .provide([
      [call(stationAPI.addStation, newStation.name), { data: newStation }],
      [select(selectStations), stationList],
    ])
    .put(setStations([Object.assign(newStation, { lines: [] }), ...stationList]))
    .hasFinalState({ stations: [Object.assign(newStation, { lines: [] }), ...stationList], error: '' })
    .run();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. px보다는 rem&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;웹 퍼블리싱을 해본 경험이 거의 전무하다보니 이전까지는 주먹구구식으로 정적 페이지를 만들었다. 내가 보고 있는 모니터에서만 가장 자연스러운 형태로 작업을 진행했고 그 결과 다른 디바이스에서는 화면이 굉장히 어색한 형태로 나타났다. 이번 미션도 기능 구현에만도 많은 시간이 필요해서 화면을 반응형으로 만들지는 못했다. 다만, 최소한 px 대신 rem을 사용하여 조금이라도 반응형처럼 만들어보았다. rem을 사용하니 미디어 쿼리로 html의 font-size를 조절해주는 것만으로도 레이아웃이 자연스럽게 변경됐다. 앞으로는 px이 꼭 필요한 경우를 제외하고는 rem을 사용하도록 해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 디렉토리명에 @ 붙이기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인치가 알려준 꿀팁으로 디렉토리나 파일 명에 '@'를 붙이면 디렉토리 가장 상단에 위치시킬 수 있다!&lt;/p&gt;</description>
      <category>개발 일기/우아한테크코스-FE</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/105</guid>
      <comments>https://yung-developer.tistory.com/105#entry105comment</comments>
      <pubDate>Wed, 9 Jun 2021 15:46:44 +0900</pubDate>
    </item>
    <item>
      <title>Redux / React Redux / Redux Thunk</title>
      <link>https://yung-developer.tistory.com/104</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Redux / React Redux / Redux Thunk&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-1. Redux&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Redux는 전역 상태 관리를 위한 라이브러리이다. 기존에는 하나의 상태를 여러 곳에서 사용하기 위해서는 최상단의 부모 컴포넌트에서 자식 컴포넌트에게 Props로 상태를 전달해줘야했다. 만약 자식 컴포넌트가 부모 컴포넌트로부터 깊은 곳에 위치해있다면 중간에 상태를 사용하지 않는 컴포넌트에게도 Props를 전달해야하는 Prop drilling이 발생하게 된다. 리액트가 아무리 바닐라 자바스크립트보다 편하다고 해도 이렇게 수고스러운 작업이 반가울리 없다. 따라서 사람들은 모든 컴포넌트에서 전역으로 상태에 접근하기 위한 도구의 필요성을 느꼈고 여러 전역 상태 관리 라이브러리들이 등장하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Redux 기본 개념&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 액션(Action)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 상태에 특정 변화가 필요할 때 발생시키는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 액션 생성 함수(Action Creator)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 파라미터를 받아 액션을 생성하는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 리듀서(Reducer)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222426;&quot;&gt;현재의 상태와, 전달 받은 액션에 따라 새로운 상태를 만들어서 반환하는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 스토어(Store)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 상태를 저장하는 장소로 보통 앱 하나에 하나의 스토어를 가지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 디스패치(Dispatch)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222426;&quot;&gt;디스패치는 스토어의 내장함수 중 하나로 액션을 발생 시키는 것&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 구독(Subscribe)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 액션이 디스패치 될 때 마다 등록해놓은 함수가 실행되도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-2. React Redux&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;React Redux는 Redux와 React를 같이 사용하기 위한 리덕스 공식 라이브러리이다. Context API처럼 최상위에 전역 상태를 선언해놓고 dispatch를 통해 상태가 변경되면 하위 컴포넌트가 모두 재렌더링 되도록 하는 방식으로 동작한다. 이 때문에 React Redux를 사용하면 따로 Subscribe 함수를 쓸 필요가 없다. 또한 useSelector, useDispatch 등의 편리한 hooks을 제공해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-3. Redux Thunk&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Redux Thunk는 비동기 처리를 위한 Redux Middleware 중 가장 많이 사용되는 라이브러리이다. 미들웨어는 리덕스의 액션과 스토어 사이에서 추가적인 작업을 할 수 있도록 돕는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623143171254&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) =&amp;gt; (next) =&amp;gt; (action) =&amp;gt; {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
 
    return next(action);
  };
}
 
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
 
export default thunk;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Redux Thunk는 클로저와 커링을 사용하여 필요한 인자를 받는다. 여기서 next는 다음 미들웨어가 있는 경우 다음 미들웨어를, 미들웨어가 없다면 reducer를 실행시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;현재까지 이해한 바에 따르면 applyMiddleware(thunk)가 적용되면 내부적으로 다음과 같이 동작할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623143197367&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;thunk({dispatch, getState})(nextMiddleware || combinedReducer)(action)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;Thunk&lt;/b&gt;&lt;span style=&quot;color: #222222;&quot;&gt;란, 특정 작업을 나중에 하도록 미루기 위해서 함수 형태로 감싼 것을 말한다. 아래와 같이 'getCart' 함수를 Thunk 함수로 생성해보자. 그리고 dispatch(getCart())를 실행하면 앞서 살펴본 'thunk' 함수의 가장 마지막 action에 'getCart()'가 들어갈 것이다. 그리고 'getCart()'는 thunk 함수였기 때문에 아직도 함수 형태이다. 따라서 'createThunkMiddleware' 내에서 action의 type이 함수이므로 우리가 만든 getCart에 'dispatch'가 인자로 들어올 것이다(&lt;span style=&quot;color: #222222;&quot;&gt;'&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;createThunkMiddleware' 네번째 라인).&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이후 getCart가 리턴한 익명함수 로직이 실행된다. 익명함수 내부에서 실행되는 dispatch들은 인자로 객체를 받기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;'&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;createThunkMiddleware'&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에서 바로 next(action)이 실행된다. (여기서 다음 미들웨어가 수행되고 최종적으로 리듀서에 액션이 들어가게 되어 스토어에 상태가 반영된다)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623143282449&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getCart = () =&amp;gt; async (dispatch: Dispatch&amp;lt;CartAction&amp;gt;) =&amp;gt; {
  dispatch({ type: LOADING });
  try {
    const response = await axios.get(URL.CART);
    if (response.status !== STATUS_CODE.GET_SUCCESS) {
      throw new Error('장바구니 정보를 불러오는데 실패하였습니다.');
    }
 
    dispatch({ type: LOADING_SUCCESS, payload: FORMAT_DATA.CART(response.data) });
  } catch (error) {
    dispatch({ type: LOADING_FAILURE, loadingError: error });
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>웹 개발/React</category>
      <category>react redux</category>
      <category>redux</category>
      <category>Redux Thunk</category>
      <category>리덕스</category>
      <category>리덕스 썽크</category>
      <category>리액트 리덕스</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/104</guid>
      <comments>https://yung-developer.tistory.com/104#entry104comment</comments>
      <pubDate>Tue, 8 Jun 2021 18:08:40 +0900</pubDate>
    </item>
    <item>
      <title>9. 프로토타입</title>
      <link>https://yung-developer.tistory.com/103</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;with 크리스 (소요시간: 1시간)&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;워밍업 - 변수 네이밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kettanaito/naming-cheatsheet&quot;&gt;kettanaito/naming-cheatsheet&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내 질문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q1.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;화살표 함수는 왜 프로토타입 객체를 가지지 않을까요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 화살표 함수는 contstructor를 가지지 않기 때문에 생성자 함수가 아니다. 프로토타입은 생성자 함수가 생성되는 시점에 더불어 생성된다. 따라서 화살표 함수는 프로토타입 객체를 가지지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q2.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 객체 전용 메서드들은 Object.someMethod(instance) 이런 식으로 써야할까요? instance.someMethod() 이런 식으로 사용할 수 있으면 편할텐데 말이죠   (ex. Array.forEach([1, 2]) (x) / [1, 2].forEach (o))&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 어떤 생성자 함수이든 prototype은 반드시 객체이기 때문에 Object.prototype이 언제나 프로토타입 체인의 최상단에 존재하기 된다. 따라서 객체에서만 사용할 메서드는 다른 여느 데이터 타입처럼 프로토타입 객체 안에 정의할 수가 없다. 객체에서만 사용할 메서드를 Object.prototype 내부에 정의한다면 다른 데이터 타입도 해당 메서드를 사용할 수 있기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q3.&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/59409762/120885750-77be9380-c625-11eb-87f4-4ee047fc2fad.png&quot; alt=&quot;https://user-images.githubusercontent.com/59409762/120885750-77be9380-c625-11eb-87f4-4ee047fc2fad.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 내에서 메서드를 생성할 때, 일반적인 메서드 생성과 화살표 함수로 메서드를 생성하는 경우에는 프로토타입 체인이 위와 같은 차이를 보입니다. 그 이유에 대해서 같이 이야기해봐요.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 화살표 함수는 동적 바인딩이 아닌 렉시컬 바인딩을 한다. 때문에 클래스로 생성된 인스턴스에서 '&lt;b&gt;proto&lt;/b&gt;'로 프로토타입 체인에 접근할 때 this가 인스턴스를 가리키지 않는 문제가 발생한다. 따라서 클래스 내에서 화살표 함수로 메서드를 선언하게 되면 프로토타입에 메서드가 생성되지 않고 인스턴스 내에서 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q4.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로토타입에 메서드나 프로퍼티를 추가한 경험이 있다면 공유해봐요  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 레벨 1때 카일과 미션을 진행하며 Custom DOM Library를 만든 경험.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yung-developer.tistory.com/87?category=950552&quot;&gt;Custom DOM Library 만들기&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;크리스 질문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q1.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바와 같은 일반적인 언어에서는 기본으로 적용하고 있지 않은 Prototype 패턴을 왜 자바스크립트에서는 기본으로 도입하고 있는 것일까요? 웹 환경에서 Prototype 패턴이 필요한 이유로 무엇이 있는 것인지 논의해봅시다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 자바스크립트는 프로토타입을 기반으로 객체의 상속을 구현하여 불필요한 중복을 제거한다. 프로토타입은 어떤 객체의 상위 객체의 역할을 하는 다른 객체로서 다른 객체에 공유 프로퍼티를 제공한다. 프로토타입을 상속받은 하위 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다. 하지만 기존 클래스 문법으로도 충분했을텐데 Brendan Eich는 왜그랬을까..?  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크리스 답변)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트가 웹 환경에서 돌아가기 때문에 프로토타입 패턴을 도입한 것 같다. 브라우저가 제공하는 환경은 네이티브 보다 제한적이기 때문에 제한된 리소스를 충분히 활용하기 위해 프로토타입 패턴을 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다른 언어에서는 상속을 구현할 때 인스턴스에서 상속받은 메서드들을 매번 새로 만들어서 가지고 있는지..? (백엔드 크루들에게 물어보자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q2.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ES6 의 class가 프로토타입을 이용해서 클래스 개념을 '흉내내는' 방법이 무엇인지 서로 설명해보고 피드백 해봅시다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 흉내낸다의 의미가 무엇인지 잘 모르겠따... 클래스는 기존 프로토타입 기반 패턴의 문법적 설탕이라고도 볼 수 있다. 하지만 클래스와 생성자 함수 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않는다. 예를 들어 클래스는 new 연산자 없이 호출하면 에러가 발생한다. 또한 클래스는 상속을 지원하는 extends와 super 키워드를 제공한다. 따라서 클래스를 객체를 생성하는 새로운 메커니즘으로 볼 수도 있다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크리스 답변)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;369&quot; data-filename=&quot;image (4).png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnoEKy/btq6GomPvjH/tlo3H5txXAWIxnRt0Z6jJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnoEKy/btq6GomPvjH/tlo3H5txXAWIxnRt0Z6jJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnoEKy/btq6GomPvjH/tlo3H5txXAWIxnRt0Z6jJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnoEKy%2Fbtq6GomPvjH%2Ftlo3H5txXAWIxnRt0Z6jJk%2Fimg.png&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;369&quot; data-filename=&quot;image (4).png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ES6 에서 채택한 방법&lt;/li&gt;
&lt;li&gt;핵심은 서브클래스 prototype 객체가 별도의 객체이되, &lt;b&gt;proto&lt;/b&gt; 속성이 슈퍼클래스의 prototype 객체를 가리키게 만들고 서브클래스의 프로토타입에는 불필요한 프로퍼티가 남아있지 않게 만드는 것이다.&lt;/li&gt;
&lt;li&gt;클래스의 super 또한 이를 바탕으로 흉내낼 수 있다. super 로 접근하려는 것이 일반 프로퍼티라면 바로 접근하면 되고, 메서드라면 슈퍼클래스의 prototype 상의 메서드가 실행될 때 this 를 서브클래스의 this 로 바인딩해서 수행하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Q3.&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로토타입은 강력한 기능이지만 실제 코드에는 사용하지 않는 경우가 많습니다. 저희가 작성한 코드 중 프로토타입을 적용할 수 있는 부분이 있다면 한번 가져와서 어떻게 적용할 수 있는지, 적용했을 경우 어떤 이점을 얻을 수 있는지 이야기해봅시다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A. 내 질문 4번과 동일&lt;/p&gt;</description>
      <category>스터디/하브루타 스터디</category>
      <author>곤이씨</author>
      <guid isPermaLink="true">https://yung-developer.tistory.com/103</guid>
      <comments>https://yung-developer.tistory.com/103#entry103comment</comments>
      <pubDate>Sun, 6 Jun 2021 17:28:40 +0900</pubDate>
    </item>
  </channel>
</rss>