<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Web Programmer</title>
    <link>https://itinerant.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 22 Jul 2026 23:57:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>S0PH1A</managingEditor>
    <image>
      <title>Web Programmer</title>
      <url>https://tistory1.daumcdn.net/tistory/2867788/attach/9599a0ec6b214643a25f5adfa2a0ee9f</url>
      <link>https://itinerant.tistory.com</link>
    </image>
    <item>
      <title>AI 만으로 사이트 만들기? 가능할까?</title>
      <link>https://itinerant.tistory.com/212</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;A-Z까지 전부 웹 사이트를 AI로 구현할 수 있을까?&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&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; text-align: start;&quot;&gt;요즘 화두는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;AI&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&quot;신입 개발자보다 AI 하나가 더 일을 잘한다.&quot;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&quot;AI를 잘 사용하는 개발자 채용.&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 AI로 코드 리뷰도 해보고, 기능 요구 정도는 해봤지만, 프로젝트 개발 처음부터 끝까지 AI만으로 가능할까?&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 style=&quot;color: #212529; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;AI 선택&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gemini, ChatGPT, Cursor, Claude 등 많겠지만, 내가 선택한 AI는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Claude Code&lt;/b&gt;&lt;/span&gt;다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code 유로이지만 &lt;b&gt;프로그래밍에 강점&lt;/b&gt;이 있고, &lt;b&gt;Terminal에서 명령&lt;/b&gt;할 수 있다는 장점으로 인해 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Cursor도 Cursor CLI가 있는데 이때는 있는지 몰랐다; )&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; text-align: start;&quot;&gt;(예,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/anthropics/claude-code/tree/main/plugins/frontend-design&quot;&gt;frontend-design&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #212529; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #007bff; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발 조건&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;내가 절대 손댈 수 없도록!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 프로젝트 진행 전제 조건은 딱 &lt;b&gt;4가지&lt;/b&gt; 이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;IDE은 절대로 열지 않을 것. = &amp;nbsp;코드를 절대 보지 말 것.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SQL 쿼리도 AI에게 맡길 것.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모든 것을 AI에게 질문할 것.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;나는 *비 개발자*라고 생각할 것.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;AI로만 모든 걸 해결하겠다는 의지&lt;/b&gt;&lt;/u&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, &lt;b&gt;&lt;/b&gt;&lt;u&gt;&lt;b&gt;사용 언어 및 라이브러리와 툴들은 &lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;내가 사용해 본적 없는 것&lt;/span&gt;으로 선택&lt;/b&gt;&lt;/u&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;하지만 AI만을 이용해 개발하기로 했기 때문에 일부러 잘 모르는 것을 선택해 보았다. 개발 중 오류가 나도 AI에게 질문할 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Next.js&lt;/b&gt; (React.js)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Supabase&lt;/b&gt; (SQL은 할 줄 알지만, Supabase사용은 처음이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작할 때, 개발 조건을 다음과 같이 설정했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;디자인은 Shadcn이용해서 &lt;b&gt;&quot;Modern &amp;amp; Simple&quot; &lt;/b&gt;하게.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;FSD&lt;/b&gt;(Feature-Sliced-Design) 패턴 구조에 맞게.&lt;/li&gt;
&lt;li&gt;최대한 컴포넌트를 &lt;b&gt;최소화&lt;/b&gt;. 분리.&lt;/li&gt;
&lt;li&gt;supabase에 적용해야할 &lt;b&gt;쿼리는 따로 파일&lt;/b&gt;로 작성. (fixup쿼리 포함)&lt;/li&gt;
&lt;li&gt;`accept edits on` 모드로 해서 무조건 변경된 코드 자동 승인.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code가 Token을 소모해서 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 질문에 1토큰이 아니기 때문에 1시간 정도 사용 시 모든 토큰이 소모되고 4~5시간 후에 토큰이 초기화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;퇴근 후 하루 1시간 개발을 목표&lt;/span&gt;로 진행&lt;/b&gt;&lt;/u&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;프로젝트 제목과 방향성만 제공하고 전체적인 틀(메뉴 및 구성)은 AI에게 맡겼고, 추가적으로 필요할 것 같은 기능은 그때 그때 개발되는 화면을 보면서 요청했다.&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;pre id=&quot;code_1768476702982&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 질의 예시
이체 내역을 엑셀로 내보낼수 있도록 기능을 추가해줘.
- 이체 내역 페이지에서 버튼 추가.
- xlsx, json 파일 형식 선택 가능.
- 내보낼 이체 내역 기간 선택.
  - 월 단위
  - yyyy-mm-dd ~ yyyy-mm-dd 기간 선택 (단, 최대 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;번역 뿐만 아니라 Supabase 에 대한 설정 조차 `어디서 테이블을 추가할 수 있어?`, `패스워드 초기화 어떻게 해?` 등 다 AI에게 물어봐서 해결했다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;과연 결과는 어떻게 되었을까.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;---&lt;/p&gt;
&lt;h3 style=&quot;color: #212529; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;⛳ &lt;/span&gt;결과&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;결과물 github : (현재 Private로 전환해두었습니다.)&lt;/li&gt;
&lt;li&gt;결과물 : &lt;a href=&quot;https://daily-money-neon.vercel.app/&quot;&gt;https://daily-money-neon.vercel.app/&lt;/a&gt;&lt;br /&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;구글 로그인은 기능 구현은 되어 있으나 Supabase에 설정을 안해서 동작 안합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이틀(Title) 은 내가 폰트 넣어서 직접 진행했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹/앱 반응형으로 개발되었으며, 디자인은 AI의 취향에서 색상과 그라이데이션만 제거했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(자꾸 자주색 + 그라데이션으로 디자인해줘서, 하늘색으로 바꿨다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageslideblock alignCenter&quot; data-image=&quot;[{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/bWjxp1/dJMcagdfn79/aQp0D2dOU5tVrQUlKdoRk0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/2wUad/dJMcafyDZZV/QHU7CKS1QSN83hDn0zejk1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/dEg3HE/dJMcagdfn78/aD33K87wrrUgJR8oAf9pm1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/ccxXCW/dJMcadALMU1/90ZK4eWpCVjdHhG6QA49AK/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/VH6kt/dJMcaaKRYGx/FAgeDQiqt5zdUljznwKds0/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/EPpp2/dJMcaaKRYGw/wbE4N9cQwKXWQMer2LijM1/img.png&amp;quot;},{&amp;quot;src&amp;quot;:&amp;quot;https://blog.kakaocdn.net/dn/Vfqp0/dJMcadALMU0/dK7OxKpwWUaUvL04XnZ8Q1/img.png&amp;quot;}]&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span class=&quot;image-wrap selected&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWjxp1/dJMcagdfn79/aQp0D2dOU5tVrQUlKdoRk0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bWjxp1/dJMcagdfn79/aQp0D2dOU5tVrQUlKdoRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWjxp1/dJMcagdfn79/aQp0D2dOU5tVrQUlKdoRk0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWjxp1%2FdJMcagdfn79%2FaQp0D2dOU5tVrQUlKdoRk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2wUad/dJMcafyDZZV/QHU7CKS1QSN83hDn0zejk1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/2wUad/dJMcafyDZZV/QHU7CKS1QSN83hDn0zejk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2wUad/dJMcafyDZZV/QHU7CKS1QSN83hDn0zejk1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2wUad%2FdJMcafyDZZV%2FQHU7CKS1QSN83hDn0zejk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dEg3HE/dJMcagdfn78/aD33K87wrrUgJR8oAf9pm1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/dEg3HE/dJMcagdfn78/aD33K87wrrUgJR8oAf9pm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dEg3HE/dJMcagdfn78/aD33K87wrrUgJR8oAf9pm1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdEg3HE%2FdJMcagdfn78%2FaD33K87wrrUgJR8oAf9pm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccxXCW/dJMcadALMU1/90ZK4eWpCVjdHhG6QA49AK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/ccxXCW/dJMcadALMU1/90ZK4eWpCVjdHhG6QA49AK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccxXCW/dJMcadALMU1/90ZK4eWpCVjdHhG6QA49AK/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccxXCW%2FdJMcadALMU1%2F90ZK4eWpCVjdHhG6QA49AK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VH6kt/dJMcaaKRYGx/FAgeDQiqt5zdUljznwKds0/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/VH6kt/dJMcaaKRYGx/FAgeDQiqt5zdUljznwKds0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VH6kt/dJMcaaKRYGx/FAgeDQiqt5zdUljznwKds0/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVH6kt%2FdJMcaaKRYGx%2FFAgeDQiqt5zdUljznwKds0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EPpp2/dJMcaaKRYGw/wbE4N9cQwKXWQMer2LijM1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/EPpp2/dJMcaaKRYGw/wbE4N9cQwKXWQMer2LijM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EPpp2/dJMcaaKRYGw/wbE4N9cQwKXWQMer2LijM1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEPpp2%2FdJMcaaKRYGw%2FwbE4N9cQwKXWQMer2LijM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;span class=&quot;image-wrap &quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vfqp0/dJMcadALMU0/dK7OxKpwWUaUvL04XnZ8Q1/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/Vfqp0/dJMcadALMU0/dK7OxKpwWUaUvL04XnZ8Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vfqp0/dJMcadALMU0/dK7OxKpwWUaUvL04XnZ8Q1/img.png&quot; loading=&quot;lazy&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfqp0%2FdJMcadALMU0%2FdK7OxKpwWUaUvL04XnZ8Q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1442&quot;/&gt;&lt;/span&gt;&lt;button class=&quot;btn btn-prev&quot;&gt;&lt;span class=&quot;ico-prev&quot;&gt;이전&lt;/span&gt;&lt;/button&gt;&lt;button class=&quot;btn btn-next&quot;&gt;&lt;span class=&quot;ico-next&quot;&gt;다음&lt;/span&gt;&lt;/button&gt;&lt;/div&gt;
  &lt;div class=&quot;mark&quot;&gt;&lt;span data-index=&quot;0&quot;&gt;0&lt;/span&gt;&lt;span data-index=&quot;1&quot;&gt;1&lt;/span&gt;&lt;span data-index=&quot;2&quot;&gt;2&lt;/span&gt;&lt;span data-index=&quot;3&quot;&gt;3&lt;/span&gt;&lt;span data-index=&quot;4&quot;&gt;4&lt;/span&gt;&lt;span data-index=&quot;5&quot;&gt;5&lt;/span&gt;&lt;span data-index=&quot;6&quot;&gt;6&lt;/span&gt;&lt;/div&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 style=&quot;color: #212529; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;♀️&amp;nbsp;느낀점&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;나보다 낫네&lt;b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 결과물이 잘 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역, 기능, 디자인적으로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;조금&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;부족한 부분은 있지만 내가 원하는 바 큰 동작은 정상적으로 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(처음부터 120% 완벽하게 프로젝트를 만들고 런칭하는 회사가 어딨겠느냐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유지 보수 또한 AI에게 맡긴다고 한다면 코드 결과물은 중요한 부분이 아닐테니, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;웹 개발에 대한 지식이 없더라도&lt;span&gt; &lt;/span&gt;&lt;/span&gt;디자인AI + Claude 조합으로 충분히 원하는 바 구현이 가능할 것 같아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 기능에 대한 주석도 잘 적어주고, 테이블 구조가 변경되거나 트리거가 필요하면 그에 맞는 fix-up 쿼리도 작성해줘서 편리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;입사 후 처음으로 완성한 사이드 프로젝트?!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;사이드 프로젝트 한 번 해보고 싶다&quot; 생각하고 몇 번 시도는 했지만, 리서칭하는데 시간이 너무 소모돼고 처음부터 완벽하게 구조를 잡고 개발하려는 의지가 강해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;혼자 너무 고민하다&lt;span&gt;&amp;nbsp;지쳐서 &lt;/span&gt;&lt;/span&gt;끝까지 마무리하지 못하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;흐지부지하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;멈춘 프로젝트가 수십 개이다. (시작이 반이라지만)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면에 AI를 사용하니 내가 직접 검색해 보고 적용해보며 오류를 검사해 볼 필요 없이, AI에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요구&lt;/b&gt;하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;확인&lt;/b&gt;만 하면 되기 때문에 개발 속도가 월등히 빨랐다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;blockquote data-ke-style=&quot;style2&quot;&gt;무서운 AI&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI로 리뷰나 오류 검증하고 질문하는 건 구글링보다 빨라서 편리했는데, 이젠 프로젝트를 거의 완벽하게 만들어준다니,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 중에 웹 프론트 개발자가 가장 AI에 의해 대체되기 쉬운 분야 같다. ( 아, 내 자리... &amp;zwj;♀️)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안과 디자인같은 부분을 제외하면 비 개발자도 AI로 사이트하나 뚝딱 가능할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트 끝나자마자, &quot;재밌네 이거, 이참에 앱도 만들어볼까..?&quot;라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #212529; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;zwj;♀️ 아쉬운 부분&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표 한가지 꼽자면...&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컴포넌트의 분리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Form의 경우 하나의 파일로 나왔다. Shadcn을 사용했기에 input, button 등을 각각의 파일로 분리할 필요는 없으나,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sheet 파일에서는 각 요소의 조합만 했어도 좀더 구조가 간단해 보이지 않을까? 라는 생각을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Form-Header, Form-Footer와 같은 부분은 변경이 적으니 파일을 분리하고, React.memo로 랩핑하여 재랜더링되지 않도록 한다던지 말이다. 왜냐하면 하나의 파일에 모든 구현부가 있고, dom에 css나 id를 따로 선언하지 않기에 코드에서 해당 컴포넌트를 찾기가 어려웠다.&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-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-26 오후 7.59.55.png&quot; data-origin-width=&quot;2504&quot; data-origin-height=&quot;1548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YPwcD/dJMcadt0djJ/oGy6GEDPyFWHOyo7Kfr77K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YPwcD/dJMcadt0djJ/oGy6GEDPyFWHOyo7Kfr77K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YPwcD/dJMcadt0djJ/oGy6GEDPyFWHOyo7Kfr77K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYPwcD%2FdJMcadt0djJ%2FoGy6GEDPyFWHOyo7Kfr77K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;371&quot; data-filename=&quot;스크린샷 2026-01-26 오후 7.59.55.png&quot; data-origin-width=&quot;2504&quot; data-origin-height=&quot;1548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;Claude Code를 사용하면서 장단점 하나씩 뽑자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;장점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&quot;~를 하려고하는데 어떻게하면 좋을까?&quot; 라고 질문을 하면 몇 가지 방안을 알려주면서 권장하는 방안과 각 방안별 장단점을 설명해주면서 사용자에게 선택하라고 했다. &lt;br /&gt;Cursor에서는 겪어보지 못했던 친절함이었다.(내가 Cursor사용을 잘 못하는 걸 수도 있다.)&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Plugin을 추가해서 질의하면 더 좋은 답변을 해준다. (예, frontend-design, server&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;단점&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;알다시피 Token 소모 속도다. Pro보다 더 높은 단계로 구매하면 되지만... &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 돈...&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;왜 자꾸 그라데이션을 넣어서 디자인해주는 건지 모르겠다...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;27시간&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.5h, 5h은 아마도 한번 다 쓰고 초기화되길 기다렸다가 11시쯤 초기화되면 한 번 더 한 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-26 오후 8.42.27.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;1396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhIbjA/dJMcaajNxHe/AcS3rWXi7B43OIX1wHBsC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhIbjA/dJMcaajNxHe/AcS3rWXi7B43OIX1wHBsC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhIbjA/dJMcaajNxHe/AcS3rWXi7B43OIX1wHBsC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhIbjA%2FdJMcaajNxHe%2FAcS3rWXi7B43OIX1wHBsC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;1246&quot; data-filename=&quot;스크린샷 2026-01-26 오후 8.42.27.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;1396&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>WEB/React</category>
      <category>Ai</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/212</guid>
      <comments>https://itinerant.tistory.com/212#entry212comment</comments>
      <pubDate>Mon, 26 Jan 2026 20:23:43 +0900</pubDate>
    </item>
    <item>
      <title>[ReactNative] React Native Cli 프로젝트 시작하기</title>
      <link>https://itinerant.tistory.com/210</link>
      <description>&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;React Native Expo 가 아닌 &lt;b&gt;React Native Cli 를 이용한 프로젝트 생성&lt;/b&gt; 방법에 대한 글 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갑자기 앱 한번 만들어 보고 싶어서, React 를 할 줄 알기에 React Native 를 다짜고짜 시작했지만 프로젝트 생성하고 실행하는데 이렇게 다사다난할 줄 꿈에도 몰랐습니다. (3일 걸렸... ㅋ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 실행 방법은 React Native 공식 문서에 나와있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React Native 환경 설정 방법 : &lt;a href=&quot;https://reactnative.dev/docs/set-up-your-environment&quot;&gt;https://reactnative.dev/docs/set-up-your-environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;React Native Cli 실행 방법 : &lt;a href=&quot;https://reactnative.dev/docs/getting-started-without-a-framework?package-manager=npm&quot;&gt;https://reactnative.dev/docs/getting-started-without-a-framework?package-manager=npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 이 블로그 글에서 도움을 받았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React Native 설정 (IOS) : &lt;a href=&quot;https://velog.io/@soyoungdl/React-native-settingiOS#-react-native-cli&quot;&gt;https://velog.io/@soyoungdl/React-native-settingiOS#-react-native-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;맥(Mac)에 React Native 개발 환경 구축하기 : &lt;a href=&quot;https://deku.posstree.com/ko/react-native/install-on-mac/#react-native-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%EB%B0%8F-%ED%99%95%EC%9D%B8&quot;&gt;https://deku.posstree.com/ko/react-native/install-on-mac/#react-native-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%EB%B0%8F-%ED%99%95%EC%9D%B8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 오류 발생시 해결 방법은 참고한 글 링크를 같이 첨부하였습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;( &lt;u&gt;&lt;b&gt;2024.08.17&lt;/b&gt;&lt;/u&gt;&lt;b&gt; 기준 버전&lt;/b&gt;입니다. )&lt;br /&gt;MacOS 사용자이고, &lt;a href=&quot;https://brew.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HomeBrew&lt;/a&gt; 가 설치되어 있어야 합니다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Node 와 watchman 을 설치합니다.&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1723825918402&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ brew install node
$ brew install watchman&lt;/code&gt;&lt;/pre&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. Ruby 업그레이드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ruby는 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;gem이라는 의존성 관리 도구를 통해 cocoapad 를 설치하기 위해 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 MacOS 는 &lt;b&gt;Ruby&lt;/b&gt; 가 설치되어 있습니다. &lt;b&gt;2.6 버전&lt;/b&gt;이 설치되어 있습니다. 다음 명령어로 버전을 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723826081890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ruby -v&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W4RYE/btsI7llPooK/diDeyynjzsyEvkSGuI5dQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W4RYE/btsI7llPooK/diDeyynjzsyEvkSGuI5dQk/img.png&quot; data-alt=&quot;루비 버전 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W4RYE/btsI7llPooK/diDeyynjzsyEvkSGuI5dQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW4RYE%2FbtsI7llPooK%2FdiDeyynjzsyEvkSGuI5dQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1266&quot; height=&quot;78&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;78&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;하지만 최신 React Native 에서는 해당 버전으로는 실행이 불가능합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확하게는 Ruby로 최신 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Bundler 설치할 때 Ruby 버전이 낮아서 오류가 발생&lt;/b&gt;&lt;/span&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 참고한 블로그에서 2.7 버전을 사용하시길래 다운받아서 실행해봤지만 Ruby 버전 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;3.0 이상&lt;/span&gt;을 요구했습니다. )&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOFXu/btsI62NI7K0/bOgJ2UtFSX6KwtU6Ka0GE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOFXu/btsI62NI7K0/bOgJ2UtFSX6KwtU6Ka0GE0/img.png&quot; data-alt=&quot;Ruby 버전이 낮아 발생한 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOFXu/btsI62NI7K0/bOgJ2UtFSX6KwtU6Ka0GE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOFXu%2FbtsI62NI7K0%2FbOgJ2UtFSX6KwtU6Ka0GE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1818&quot; height=&quot;298&quot; data-origin-width=&quot;1818&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ruby 버전이 낮아 발생한 오류&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;따라서 Ruby 를 업그레이드 해 줍니다. 찾아보니 Ruby 도 Npm 처럼 버전을 컨트롤할 수 있는 환경이 존재하더라구요. 바로 &lt;a href=&quot;https://github.com/rbenv/rbenv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;rbenv&lt;/span&gt;&lt;/b&gt;&lt;/a&gt; 입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723826639348&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ brew install rbenv&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Ruby 가장 마지막 버전을 확인하고 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;마지막 버전&lt;/b&gt;&lt;/span&gt;으로 다운 받았습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723826874302&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 설치 가능한 마지막 안정 버전 리스트 확인
$ rbenv install -l

# Ruby 버전 설치
$ rbenv install 3.3.4

# 다운 받은 버전을 전역으로 설정
$ rbenv global 3.3.4&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;Ruby 버전을 확인해 보시고 변경되지 않았다면 터미널을 껏다 켜시면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 gem 을 이용해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;bundler&lt;/b&gt;&lt;/span&gt; 를 설치해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723827737631&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gem install bundler&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBp2kY/btsI5ObPnqk/Jbdiim5CVNbFHzo77cHyw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBp2kY/btsI5ObPnqk/Jbdiim5CVNbFHzo77cHyw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBp2kY/btsI5ObPnqk/Jbdiim5CVNbFHzo77cHyw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBp2kY%2FbtsI5ObPnqk%2FJbdiim5CVNbFHzo77cHyw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1252&quot; height=&quot;450&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;450&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. XCode 설치 및 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apps.apple.com/us/app/xcode/id497799835?mt=12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Mac App Store&lt;/b&gt;&lt;/a&gt; 에서 XCode 를 다운받습니다. (매우 오래 걸림)&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;Xcode &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&amp;rarr; Settings... &lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&amp;rarr; Locations 에서 &quot;&lt;b&gt;Command Line Tools&lt;/b&gt;&quot; 를 설정해 줍니다. &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&lt;b&gt;최신 버전&lt;/b&gt;으로 선택해 주면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/erqFs1/btsI5P2PsXI/gk7ZJolHGthuVGnXUe5Nok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/erqFs1/btsI5P2PsXI/gk7ZJolHGthuVGnXUe5Nok/img.png&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;606&quot; data-is-animation=&quot;false&quot; style=&quot;width: 37.6086%; margin-right: 10px;&quot; data-widthpercent=&quot;38.05&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/erqFs1/btsI5P2PsXI/gk7ZJolHGthuVGnXUe5Nok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FerqFs1%2FbtsI5P2PsXI%2Fgk7ZJolHGthuVGnXUe5Nok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZyR3g/btsI7GQNEio/xZo8O3l6sLUEG4kOKRDJ8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZyR3g/btsI7GQNEio/xZo8O3l6sLUEG4kOKRDJ8k/img.png&quot; data-origin-width=&quot;1688&quot; data-origin-height=&quot;1118&quot; data-is-animation=&quot;false&quot; style=&quot;width: 61.2286%;&quot; data-widthpercent=&quot;61.95&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZyR3g/btsI7GQNEio/xZo8O3l6sLUEG4kOKRDJ8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZyR3g%2FbtsI7GQNEio%2FxZo8O3l6sLUEG4kOKRDJ8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1688&quot; height=&quot;1118&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Command Line Tools 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 &lt;b&gt;IOS 시뮬레이터&lt;/b&gt;를 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Xcode&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&amp;rarr; Settings...&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: left;&quot;&gt;&amp;rarr;  &lt;b&gt;Platforms&lt;/b&gt; 에서 IOS 를 설치해 줍니다. (이것도 오래 걸립니다...)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8zLFU/btsI7sd4m1E/ErhmlGjV1O8USmQDSXd1hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8zLFU/btsI7sd4m1E/ErhmlGjV1O8USmQDSXd1hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8zLFU/btsI7sd4m1E/ErhmlGjV1O8USmQDSXd1hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8zLFU%2FbtsI7sd4m1E%2FErhmlGjV1O8USmQDSXd1hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1690&quot; height=&quot;1104&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;1104&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 프로젝트 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 react-native-cli 를 전역(global)로 설치한 적이 있는 경우, 예기치 않은 오류가 발생할 수 있으니 제거해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723827889615&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm uninstall -g react-native-cli @react-native-community/cli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 react-native-cli 를 이용해 프로젝트를 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723827920796&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx @react-native-community/cli@latest init 프로젝트명&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;( 저는 FirstApp 로 프로젝트를 생성했습니다. )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성되면서 터미널에 표시되는 로그를 확인해 보니 전 다음과 같이 오류가 발생했었습니다. 감사하게도 관련해서 &quot;&lt;b&gt;Run instructions for IOS&lt;/b&gt;&quot;를 확인하라고 로그에 같이 적혀있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lfbq4/btsI6B38oJ8/DxZPxibPj7aD66p5V4mGBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lfbq4/btsI6B38oJ8/DxZPxibPj7aD66p5V4mGBk/img.png&quot; data-alt=&quot;React native Cli 프로젝트 설치 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lfbq4/btsI6B38oJ8/DxZPxibPj7aD66p5V4mGBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLfbq4%2FbtsI6B38oJ8%2FDxZPxibPj7aD66p5V4mGBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;580&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React native Cli 프로젝트 설치 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactnative.dev/docs/getting-started-without-a-framework?package-manager=npm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;를 타고 들어가서, 표시된 내용대로&lt;b&gt; IOS 폴더&lt;/b&gt;에 가서&lt;b&gt; bundle을 설치&lt;/b&gt;하고,&lt;b&gt; cocoapod를 설치&lt;/b&gt;하라고 하네요. 그대로 해주면 됩니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPPXy/btsI6hybgeP/6yqcDBGQwYLcAqhbnrEmZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPPXy/btsI6hybgeP/6yqcDBGQwYLcAqhbnrEmZ0/img.png&quot; data-alt=&quot;React Native Cli 프로젝트 설치 오류 해결 방법&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPPXy/btsI6hybgeP/6yqcDBGQwYLcAqhbnrEmZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPPXy%2FbtsI6hybgeP%2F6yqcDBGQwYLcAqhbnrEmZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2296&quot; height=&quot;902&quot; data-origin-width=&quot;2296&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Native Cli 프로젝트 설치 오류 해결 방법&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1723828338389&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ cd ios
$ bundle install
$ bundle exec pod install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 전 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;cocoapod 를 설치하다가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;오류가 또 발생하게 됩니다.  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vsmhn/btsI6kob7Le/H6K2sIMANZ3aO0Q8auJT6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vsmhn/btsI6kob7Le/H6K2sIMANZ3aO0Q8auJT6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vsmhn/btsI6kob7Le/H6K2sIMANZ3aO0Q8auJT6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvsmhn%2FbtsI6kob7Le%2FH6K2sIMANZ3aO0Q8auJT6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;276&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GudP7/btsI5vjhl0t/T7Gtbmd5kPKBg2C1LIGK4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GudP7/btsI5vjhl0t/T7Gtbmd5kPKBg2C1LIGK4k/img.png&quot; data-alt=&quot;cocoapod 설치 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GudP7/btsI5vjhl0t/T7Gtbmd5kPKBg2C1LIGK4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGudP7%2FbtsI5vjhl0t%2FT7Gtbmd5kPKBg2C1LIGK4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1410&quot; height=&quot;1022&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;cocoapod 설치 오류&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;b&gt;&lt;u&gt;bundle이 node_modules 를 설치하지 않고 pod 를 실행하려고 해서 오류가 발생&lt;/u&gt;&lt;/b&gt;했다고 합니다. 따라서 &lt;b&gt;node_moduels 를 먼저 설치&lt;/b&gt;해주면 된다고 합니다. [&lt;a href=&quot;https://github.com/react-native-community/cli/issues/2189&quot;&gt;참고&lt;/a&gt;]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/23tkT/btsI7T3sPSx/K3pFq6ARSbfEhGoWVnzzu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/23tkT/btsI7T3sPSx/K3pFq6ARSbfEhGoWVnzzu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/23tkT/btsI7T3sPSx/K3pFq6ARSbfEhGoWVnzzu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F23tkT%2FbtsI7T3sPSx%2FK3pFq6ARSbfEhGoWVnzzu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1846&quot; height=&quot;336&quot; data-origin-width=&quot;1846&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ios 폴더를 빠져 나와 &lt;b&gt;프로젝트 루트 폴더&lt;/b&gt;로 이동한 후에 npm install 을 하여 node_modules 를 생성해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723828927190&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install

# 설치 완료되면 다시 ios 폴더로 가서 cocoapod 설치
$ cd ios
$ bundle exec pod install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 프로젝트 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 설정이 끝났습니다! 프로젝트 루트 폴더에서 생성한 프로젝트를 실행합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723829147011&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx react-native run-ios&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwIcD4/btsI6d3uLMc/lPZ8U476JAUWGoRyp6KQu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwIcD4/btsI6d3uLMc/lPZ8U476JAUWGoRyp6KQu1/img.png&quot; data-alt=&quot;프로젝트 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwIcD4/btsI6d3uLMc/lPZ8U476JAUWGoRyp6KQu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwIcD4%2FbtsI6d3uLMc%2FlPZ8U476JAUWGoRyp6KQu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1604&quot; height=&quot;242&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로젝트 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 실행하는 것이라 그런지, 아니면 제 노트북에 문제인지는 모르겠으나 &lt;b&gt;Building the app...&lt;/b&gt; 여기서 &lt;u&gt;엄청 오래 걸렸&lt;/u&gt;습니다. 따라서 멈춘줄 알고 껏다가 다시 실행했는데요. (이러지 말았어야 했습니다...) 그랬더니 다음과 같이 오류가 발생하더라구요?!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N3eeb/btsI7LR0I23/rbX0QhCdWPhdtZYh8vmzy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N3eeb/btsI7LR0I23/rbX0QhCdWPhdtZYh8vmzy1/img.png&quot; data-alt=&quot;프로젝트 실행 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N3eeb/btsI7LR0I23/rbX0QhCdWPhdtZYh8vmzy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN3eeb%2FbtsI7LR0I23%2FrbX0QhCdWPhdtZYh8vmzy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;746&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로젝트 실행 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1723829468451&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;error Failed to build ios project.
ios project. &quot;xcodebuild&quot; exited
&quot;xcodebuild&quot; exited with error code '65'. To debug build logs further, consi
der building your app with Xcode.app, by opening
'FirstApp.xcworkspace.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfx4GU/btsI5AZiOzS/ySx4rAeVHtCI3xw4ePJ3y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfx4GU/btsI5AZiOzS/ySx4rAeVHtCI3xw4ePJ3y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfx4GU/btsI5AZiOzS/ySx4rAeVHtCI3xw4ePJ3y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfx4GU%2FbtsI5AZiOzS%2FySx4rAeVHtCI3xw4ePJ3y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;225&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 코드 65&lt;/b&gt;는 유명한 오류더라구요. 원인을 빠르게 찾을 수 있었습니다. &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;Xcode 캐시를 삭제&lt;/b&gt;&lt;/span&gt;하고 다시 실행해 주면 됩니다. [&lt;a href=&quot;https://positiveko-til.vercel.app/til/react-native/error65.html&quot;&gt;참고&lt;/a&gt;]&lt;/p&gt;
&lt;pre id=&quot;code_1723829701251&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Xcode의 Derived Data를 삭제
$ rm -rf ~/Library/Developer/Xcode/DerivedData

# React Native 재 실행
$ npx react-native run-ios&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;1410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN5yNT/btsI7sE9fH1/TBAkCDU2MWFv0j4TlUQUvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN5yNT/btsI7sE9fH1/TBAkCDU2MWFv0j4TlUQUvK/img.png&quot; data-alt=&quot;React Native Cli 로 프로젝트 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN5yNT/btsI7sE9fH1/TBAkCDU2MWFv0j4TlUQUvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN5yNT%2FbtsI7sE9fH1%2FTBAkCDU2MWFv0j4TlUQUvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2450&quot; height=&quot;1410&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;1410&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;React Native Cli 로 프로젝트 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&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;5-1. 프로젝트 실행 시 시뮬레이터에서 발생한 오류&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 React Native 프로젝트 실행하면서 IOS 시뮬레이터에서 다음과 같은 오류도 발생했었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnGEM/btsI6kIq6Ad/gBYdtvqRX1SZVgCHSNafqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnGEM/btsI6kIq6Ad/gBYdtvqRX1SZVgCHSNafqk/img.png&quot; data-alt=&quot;시뮬레이터 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnGEM/btsI6kIq6Ad/gBYdtvqRX1SZVgCHSNafqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmnGEM%2FbtsI6kIq6Ad%2FgBYdtvqRX1SZVgCHSNafqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;796&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시뮬레이터 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1723830089509&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;No bundle URL present.

Make sure you're running a packager server or have included a .jsbundle file in your application bundle.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Native 프로젝트에서 &lt;b&gt;JavaScript 번들(즉, 앱의 JS 코드)을 찾을 수 없을 때 발생&lt;/b&gt;한다고 합니다. 이 문제는 일반적으로 &lt;b&gt;&lt;u&gt;Metro 번들러가 실행되지 않&lt;/u&gt;&lt;/b&gt;거나, &lt;b&gt;앱이 JavaScript 번들을 찾을 수 없을 때&lt;/b&gt; 발생하게 됩니다.&amp;nbsp;(chatgpt 발췌)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Native 앱이 실행될 때, &lt;b&gt;Metro&lt;/b&gt; 번들러가 JavaScript 코드를 제공해 주는데, 이 번들러가 실행되지 않았거나 중지되었을 경우 이 오류가 발생할 수 있다고 합니다. 따라서 &lt;b&gt;Metro 번들러를 수동으로 실행&lt;/b&gt;해  오류를 해결할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1723830194121&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npx react-native start&lt;/code&gt;&lt;/pre&gt;</description>
      <category>App/ReactNative</category>
      <category>react-native-cli</category>
      <category>reactnative</category>
      <category>reactnative settings</category>
      <category>reactnative 설정</category>
      <category>reactnative 설치</category>
      <category>reactnative 프로젝트</category>
      <category>ReactNativeCLI</category>
      <category>rn</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/210</guid>
      <comments>https://itinerant.tistory.com/210#entry210comment</comments>
      <pubDate>Sat, 17 Aug 2024 03:08:10 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode][JS] 테스트를 위한 TreeNode 인스턴스 생성</title>
      <link>https://itinerant.tistory.com/209</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저는 LeetCode 알고리즘 문제를 풀 때 문제를 로컬에서 코드 작성 후 테스트 케이스를 통과하는 경우 LeetCode 사이트에 붙여 넣어 확인하곤 했습니다. &lt;b&gt;Binary&lt;span style=&quot;color: #000000;&quot;&gt;Tree&lt;/span&gt; 문제&lt;/b&gt; 같은 경우, 테스트 케이스는 리스트로 주어지지만 코드를 작성할 때는 &lt;b&gt;TreeNode 인스턴스&lt;/b&gt;를 이용해서 해결을 해야합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;리스트로 된 테스트 케이스[칼럼 1]를 TreeNode를 이용해서 [컬럼 2]처럼 인스턴스를 생성하고 [컬럼 3]처럼 되어야지 테스트가 가능합니다.&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 38px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;테스트 케이스&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;TreeNode 인스턴스 생성&lt;/td&gt;
&lt;td style=&quot;width: 39.7674%; height: 20px;&quot;&gt;TreeNode 인스턴스 결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;[2,1,3,null,null,0,1]&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;const tree = new TreeNode(2);&lt;br /&gt;tree.left = new TreeNode(1);&lt;br /&gt;tree.left.left = null;&lt;br /&gt;tree.left.right = null;&lt;br /&gt;tree.right = new TreeNode(3);&lt;br /&gt;tree.right.left = new TreeNode(0);&lt;br /&gt;tree.right.right = new TreeNode(1);&lt;/td&gt;
&lt;td style=&quot;width: 39.7674%; height: 18px;&quot;&gt;TreeNode&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;val:&amp;nbsp;2,&lt;br /&gt;&amp;nbsp;&amp;nbsp;left:&amp;nbsp;TreeNode&amp;nbsp;{&amp;nbsp;val:&amp;nbsp;1,&amp;nbsp;left:&amp;nbsp;null,&amp;nbsp;right:&amp;nbsp;null&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;right:&amp;nbsp;TreeNode&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val:&amp;nbsp;3,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;left:&amp;nbsp;TreeNode&amp;nbsp;{&amp;nbsp;val:&amp;nbsp;0,&amp;nbsp;left:&amp;nbsp;null,&amp;nbsp;right:&amp;nbsp;null&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;right:&amp;nbsp;TreeNode&amp;nbsp;{&amp;nbsp;val:&amp;nbsp;1,&amp;nbsp;left:&amp;nbsp;null,&amp;nbsp;right:&amp;nbsp;null&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 리스트를 TreeNode로 변경함에 있어서 불편함을 느껴 LeetCode에서 코딩을 하곤 했는데, 문제의 난이도가 어려워질수록 LeetCode에서 직접 코드를 작성하고 테스트하는 것에 대한 불편함이 더 커져서 아예 &lt;b&gt;TreeNode를 만드는 모듈&lt;/b&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;  Binary Tree를 위한 Tree 생성 모듈 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TreeNode 인스턴스 생성 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TreeNode 인스턴스를 생성하는 함수는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;LeetCode에서 문제&lt;/span&gt;&amp;nbsp;화면 우측 코드 작성하는 부분 최상단에 주석으로 알려주고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715849767905&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function TreeNode(val, left, right) {
  this.val = val === undefined ? 0 : val;
  this.left = left === undefined ? null : left;
  this.right = right === undefined ? null : right;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tree 생성 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TreeNode를 이용해서 전달받은 리스트를 원하는 형식으로 만들어야 합니다. 위에 테이블처럼 left, right에 TreeNode 인스턴스를 생성해서 넣어줘야 합니다.&amp;nbsp;트리의 깊이는 테스트 케이스마다 다르기 때문에 반복문을 통해서 &lt;b&gt;동적으로 트리를 생성&lt;/b&gt;할 수 있도록 해야 했고, &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Iterators_and_generators&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이터레이터를&lt;/a&gt; 이용해서 리스트의 다음 값을 갖고 와서 TreeNode를 인스턴스를 생성하고 넣어줬습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715849978546&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function createTreeNode(root) {
  if (!root) return null;

  const values = root[Symbol.iterator]();
  const tree = new TreeNode(values.next().value);
  const queue = new Set().add(tree);

  for (const node of queue) {
    for (const position of [&quot;left&quot;, &quot;right&quot;]) {
      const value = values.next().value;
      if (value != null) queue.add((node[position] = new TreeNode(value)));
    }
  }

  return tree;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;전체 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1715850090759&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function TreeNode(val, left, right) {
  this.val = val === undefined ? 0 : val;
  this.left = left === undefined ? null : left;
  this.right = right === undefined ? null : right;
}

function createTreeNode(root) {
  if (!root) return null;

  const values = root[Symbol.iterator]();
  const tree = new TreeNode(values.next().value);
  const queue = new Set().add(tree);

  for (const node of queue) {
    for (const position of [&quot;left&quot;, &quot;right&quot;]) {
      const value = values.next().value;
      if (value != null) queue.add((node[position] = new TreeNode(value)));
    }
  }

  return tree;
}

module.exports = { createTreeNode };&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  테스트 코드에서의 활용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만든 TreeNode 생성 모듈을 추가하여 테스트를 진행하도록 하였습니다. 아무래도 LeetCode에서 실행되어야 하는 설루션 함수에 직접 사용하기에는 LeetCode에서는 필요 없는 부분이기 때문에 테스트 코드 실행 시 설정하였습니다.&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;a href=&quot;https://leetcode.com/problems/evaluate-boolean-binary-tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LeetCode 2331번 문제&lt;/a&gt;를 테스트하는 테스트 코드입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1715850338154&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 문제 해결하는 솔루션 함수 추가
const solution = require(&quot;../../leetcode/2331&quot;);
// TreeNode 생성하는 모듈 추가
const { createTreeNode } = require(&quot;../../utils/TreeNode&quot;);

// 테스트
describe(&quot;Evaluate Boolean Binary Tree&quot;, () =&amp;gt; {
  test(&quot;#1&quot;, () =&amp;gt; {
    expect(solution(createTreeNode([2, 1, 3, null, null, 0, 1]))).toEqual(true);
  });
  test(&quot;#2&quot;, () =&amp;gt; {
    expect(solution(createTreeNode([0]))).toEqual(false);
  });
});&lt;/code&gt;&lt;/pre&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 매번 LeetCode에서 코드를 작성하고 해결하는데 중간 테스트해 보는 것에 불편함을 느꼈는데, TreeNode를 만드는 모듈을 생성함으로써 로컬에서도 코드를 작성하고 테스트해 볼 수 있어서 알고리즘을 해결하는데 좀 더 수월해졌습니다.  &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://stackoverflow.com/questions/75473505/explanation-of-class-definition-for-binary-trees-in-leetcode&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/75473505/explanation-of-class-definition-for-binary-trees-in-leetcode&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/209</guid>
      <comments>https://itinerant.tistory.com/209#entry209comment</comments>
      <pubDate>Thu, 16 May 2024 18:13:10 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm][JS] 프로그래머스 - 광물 캐기</title>
      <link>https://itinerant.tistory.com/208</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jiyeon-dev/js_algorithm/blob/main/programmers/172927.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[소스코드]&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/172927&quot;&gt;&lt;b&gt;[프로그래머스 - 광물 캐기]&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;마인은 곡괭이로 광산에서 광석을 캐려고 합니다. 마인은 다이아몬드 곡괭이, 철 곡괭이, 돌 곡괭이를 각각 0개에서 5개까지 가지고 있으며, 곡괭이로 광물을 캘 때는 피로도가 소모됩니다. 각 곡괭이로 광물을 캘 때의 피로도는 아래 표와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJJ3IE/btsGNB5zlAM/31OkbnkmEK3YtFelXNhrzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJJ3IE/btsGNB5zlAM/31OkbnkmEK3YtFelXNhrzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJJ3IE/btsGNB5zlAM/31OkbnkmEK3YtFelXNhrzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJJ3IE%2FbtsGNB5zlAM%2F31OkbnkmEK3YtFelXNhrzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;466&quot; height=&quot;232&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 철 곡괭이는 다이아몬드를 캘 때 피로도 5가 소모되며, 철과 돌을 캘때는 피로도가 1씩 소모됩니다. 각 곡괭이는 종류에 상관없이 광물 5개를 캔 후에는 더 이상 사용할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;마인은 다음과 같은 규칙을 지키면서 최소한의 피로도로 광물을 캐려고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;사용할 수 있는 곡괭이중 아무거나 하나를 선택해 광물을 캡니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;한 번 사용하기 시작한 곡괭이는 사용할 수 없을 때까지 사용합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;광물은 주어진 순서대로만 캘 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;광산에 있는 모든 광물을 캐거나, 더 사용할 곡괭이가 없을 때까지 광물을 캡니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;곡괭이를 하나 선택해서 광물 5개를 연속으로 캐고&lt;/b&gt;&lt;/span&gt;, 다음 곡괭이를 선택해서 광물 5개를 연속으로 캐는 과정을 반복하며, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;더 사용할 곡괭이가 없거나 광산에 있는 모든 광물을 캘 때까지 과정을 반복&lt;/b&gt;&lt;/span&gt;하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;마인이 갖고 있는 곡괭이의 개수를 나타내는 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;picks와 광물들의 순서를 나타내는 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;minerals가 매개변수로 주어질 때, 마인이 작업을 끝내기까지 필요한 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;최소한의 피로도를 return&lt;/b&gt;&lt;/span&gt; 하는 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;제한사항&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;picks는 [dia, iron, stone]과 같은 구조로 이루어져 있습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0 &amp;le; dia, iron, stone &amp;le; 5&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dia는 다이아몬드 곡괭이의 수를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;iron은 철 곡괭이의 수를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;stone은 돌 곡괭이의 수를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;곡괭이는 최소 1개 이상 가지고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;5 &amp;le;&amp;nbsp;minerals의 길이 &amp;le; 50&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;minerals는 다음 3개의 문자열로 이루어져 있으며 각각의 의미는 다음과 같습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;diamond : 다이아몬드&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;iron : 철&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;stone : 돌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;   힌트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;광물을 5개씩 나눠서 광물 종류별 개수 확인&lt;/li&gt;
&lt;li&gt;`곡괭이 수 &amp;lt; 광물 수` 인 경우에 대한 처리 필요 ( 곡괭이 사용 가능한 광물까지만 계산 )&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;◎ 도전 1&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;1) 광물을 5개씩 나눠서 광물 개수 확인&lt;br /&gt;2) diamond &amp;gt; iron &amp;gt; stone 순으로 정렬 ( diamond가 많을수록 diamond 곡괭이를 사용하도록 하기 위해 )&lt;br /&gt;3) 반복문을 돌면서 diamond &amp;gt; iron &amp;gt; stron 순으로 곡괭이 사용&lt;/blockquote&gt;
&lt;pre id=&quot;code_1713526018824&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(picks, minerals) {
  // 1) 5개씩 나눠서 광물 계산
  const weightArr = [];
  for (let i = 0; i &amp;lt; minerals.length; i = i + 5) {
    const weight = { diamond: 0, iron: 0, stone: 0 };
    minerals.slice(i, i + 5).forEach((mineral) =&amp;gt; {
      weight[mineral] += 1;
    });
    weightArr.push(weight);
  }

  // 2) 광물 diamond &amp;gt; iron &amp;gt; stone 순서로 정렬
  weightArr.sort((a, b) =&amp;gt; {
    return (
      b[&quot;diamond&quot;] - a[&quot;diamond&quot;] ||
      b[&quot;iron&quot;] - a[&quot;iron&quot;] ||
      b[&quot;stone&quot;] - a[&quot;stone&quot;]
    );
  });

  // 3) 피로도 계산
  let answer = 0;
  while (Math.max(...picks, 0) !== 0 &amp;amp;&amp;amp; weightArr.length) {
    const weight = weightArr.shift();  // 광물 리스트를 맨 앞부터 반환
    const pick = picks.findIndex((val) =&amp;gt; val &amp;gt; 0);  // 선택한 곡괭이
	
    // 곡괭이 피로도 계산
    Object.entries(weight).forEach(([key, val]) =&amp;gt; {
      if (key === &quot;diamond&quot;)
        answer += val * (pick === 1 ? 5 : pick === 2 ? 25 : 1);
      else if (key === &quot;iron&quot;) answer += val * (pick === 2 ? 5 : 1);
      else if (key === &quot;stone&quot;) answer += val * 1;
    });
    
    // 곡괭이 다 사용한 경우 다음 곡괭이로 변경
    picks[pick] -= 1;
  }

  return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;테스트 케이스 8번을&lt;/b&gt;&lt;/span&gt; 제외한 모든 테스트 케이스가 성공하였습니다. 테스트 케이스 8번은 왜 안되었을까 고민을 해봐도 답을 모르겠어서 질문하기 부분을 확인해 보니 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;곡괭이 수가 광물 묶음 수보다 적은 경우&lt;/b&gt;&lt;/span&gt;&quot;에 대한 처리가 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 2번 코드도 곡괭이 수 가 광물 묶음 수보다 더 적은 경우였지만, 이 예제는 diamond - iron - stone 순으로 정렬을 해도 정렬 전과 후가 똑같기 때문에 오류를 발견하지 못했었습니다. 따라서 이 부분에 대한 처리를 추가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;◎ 도전 2&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;곡괭이 수보다 광물의 묶음 수가 더 많은 경우에 대한 예외 처리를 추가하였습니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;1) 광물을 5개씩 나눠서 광물 개수 확인&lt;br /&gt;&lt;b&gt;1-1) 곡괭이 전체 개수와 광물 묶음 수가 동일한 경우 반복문 정지(break)하여 더 이상 광물을 확인하지 않도록 설정&lt;/b&gt;&lt;br /&gt;2) diamond &amp;gt; iron &amp;gt; stone 순으로 정렬 ( diamond가 많을수록 diamond 곡괭이를 사용하도록 하기 위해 )&lt;br /&gt;3) 반복문을 돌면서 diamond &amp;gt; iron &amp;gt; stron 순으로 곡괭이 사용&lt;/blockquote&gt;
&lt;pre id=&quot;code_1713526454326&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(picks, minerals) {
  // 전체 곡괭이 수
  const pickCnt = picks.reduce((acc, cur) =&amp;gt; acc + cur, 0);
  
  // 1) 5개씩 나눠서 광물 계산
  const weightArr = [];
  for (let i = 0; i &amp;lt; minerals.length; i = i + 5) {
    const weight = { diamond: 0, iron: 0, stone: 0 };
    minerals.slice(i, i + 5).forEach((mineral) =&amp;gt; {
      weight[mineral] += 1;
    });
    weightArr.push(weight);
    
    // 1-1) 전체 곡괭이 수와 광물 묶음 수 동일한 경우 종료
    if (weightArr.length === pickCnt) break;
  }

  // 2) 광물 diamond &amp;gt; iron &amp;gt; stone 순서로 정렬
  weightArr.sort((a, b) =&amp;gt; {
    return (
      b[&quot;diamond&quot;] - a[&quot;diamond&quot;] ||
      b[&quot;iron&quot;] - a[&quot;iron&quot;] ||
      b[&quot;stone&quot;] - a[&quot;stone&quot;]
    );
  });

  // 3) 피로도 계산
  let answer = 0;
  while (weightArr.length) {
    const weight = weightArr.shift();  // 광물 리스트를 맨 앞부터 반환
    const pick = picks.findIndex((val) =&amp;gt; val &amp;gt; 0);  // 선택한 곡괭이
	
    // 곡괭이 피로도 계산
    Object.entries(weight).forEach(([key, val]) =&amp;gt; {
      if (key === &quot;diamond&quot;)
        answer += val * (pick === 1 ? 5 : pick === 2 ? 25 : 1);
      else if (key === &quot;iron&quot;) answer += val * (pick === 2 ? 5 : 1);
      else if (key === &quot;stone&quot;) answer += val * 1;
    });
    
    // 곡괭이 다 사용한 경우 다음 곡괭이로 변경
    picks[pick] -= 1;
  }

  return answer;
}&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;전체 곡괭이 수와 비교하는 if 문을 추가하고, while에서는 반복 조건문에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;곡괭이에&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;아래는 8번에 대해 성공하는지 테스트해볼 수 있는 예제입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 36px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.5%; height: 18px;&quot;&gt;picks&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;metarials&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; height: 18px;&quot;&gt;result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.5%; height: 18px;&quot;&gt;[0, 1, 1]&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 18px;&quot;&gt;[&quot;diamond&quot;, &quot;diamond&quot;, &quot;diamond&quot;, &quot;stone&quot;, &quot;stone&quot;, &quot;stone&quot;, &quot;iron&quot;, &quot;iron&quot;, &quot;iron&quot;, &quot;iron&quot;, &quot;diamond&quot;, &quot;diamond&quot;, &quot;diamond&quot;, &quot;diamond&quot;, &quot;diamond&quot;]&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; height: 18px;&quot;&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;</description>
      <category>Algorithm</category>
      <category>2단계</category>
      <category>광물 캐기</category>
      <category>광물캐기</category>
      <category>프로그래머스</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/208</guid>
      <comments>https://itinerant.tistory.com/208#entry208comment</comments>
      <pubDate>Fri, 19 Apr 2024 20:47:01 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 실행 컨텍스트</title>
      <link>https://itinerant.tistory.com/207</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실행 컨텍스트의 동작 원리에 대해 간단히 알아봅시다. 실행 컨텍스트는 &lt;b&gt;자바스크립트의 동작 원리&lt;/b&gt;를 담고 있는 핵심 개념입니다.&amp;nbsp; 실행 컨텍스트를 이해하게 되면 다음 3가지에 대 해 알 수 있게 됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;호이스팅&lt;/b&gt;&lt;/span&gt;이 발생하는 이유&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;클로저&lt;/b&gt;&lt;/span&gt; 동작 방식. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;스코프&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;비동기&lt;/b&gt;&lt;/span&gt; 처리 방식&lt;/li&gt;
&lt;/ol&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;실행&amp;nbsp; 컨텍스트란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 컨텍스트는 &quot;&lt;b&gt;실행할 코드에 대한 환경 정보들을 모아둔 객체&lt;/b&gt;&quot;입니다. 즉, &lt;u&gt;&lt;b&gt;함수가 실행되는 환경에 대한 정보&lt;/b&gt;&lt;/u&gt;를 갖고 있다고 생각하면 됩니다. 함수는&lt;b&gt; 변수, 매개변수, 내장 함수, this에&lt;/b&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;실행 컨텍스트 동작 방식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 &lt;b&gt;싱글 스레드&lt;/b&gt;로 되어 있으며, 해야 할 작업들을 하나의 스택에 쌓아 보관합니다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이 스택을 자바스크립트에서는 &lt;b&gt;콜 스택&lt;/b&gt;이라 말합니다. 자바스크립트는 &lt;b&gt;함수의 호출이 발생(실행)&lt;/b&gt;하게 되면 콜 스택에 이 함수에 대한 정보를 갖는 객체(=실행 컨텍스트)를 생성하여 &lt;b&gt;푸시(push)합니다.&lt;/b&gt; 그리고 &lt;b&gt;함수 실행이 완료&lt;/b&gt;가 되면 해당 실행 컨텍스트를 &lt;b&gt;팝(pop)&lt;/b&gt;하게 됩니다. &lt;/span&gt;가장 위에 있는 코드부터 실행되기 때문에 전체 코드의 환경과 순서를 보장하게 됩니다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;하지만 이로 인해 &lt;b&gt;블로킹(blocking)&lt;/b&gt;이 발생합니다. 블로킹이란 앞 함수가 실행되는 동안 뒤에 다른 함수들을 실행하지 못해 멈춰있는 상태를 말합니다. (동기 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실행 컨텍스트에 저장되는 정보&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3가지 정보가 저장됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Variable Environment&lt;/li&gt;
&lt;li&gt;Lexical Environment&amp;nbsp;&lt;/li&gt;
&lt;li&gt;this binding&lt;/li&gt;
&lt;/ol&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;Variable Environment&lt;/b&gt;&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Lexical Environment ⭐️&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Variable Environment를 복사해서 생성됨.&lt;/li&gt;
&lt;li&gt;변경 사항이 계속 적용됨.&lt;/li&gt;
&lt;li&gt;현재 컨텍스트 내의 식별자들에 대한 정보, 외부 환경 정보를 갖고 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%;&quot;&gt;저장 정보&lt;/td&gt;
&lt;td style=&quot;width: 31.8217%;&quot;&gt;키워드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Environment Record&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%;&quot;&gt;매개 변수의 이름, 함수 선언, 변수명 등&lt;/td&gt;
&lt;td style=&quot;width: 31.8217%;&quot;&gt;호이스팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Outer Environment Reference&lt;/td&gt;
&lt;td style=&quot;width: 34.8449%;&quot;&gt;현재 호출된 함수가 선언될 당시의 Lexical Environment를 참조.&lt;br /&gt;( 부모 정보가 담긴다고 생각하면 쉬움 )&lt;/td&gt;
&lt;td style=&quot;width: 31.8217%;&quot;&gt;클로저 / 스코프&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;This binding&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;this 식별자가 바라보는 객체&lt;/li&gt;
&lt;li&gt;지정되지 않은 경우 전역 객체(Window)를 가리킴.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;호이스팅&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lexical Environment의 Environment Record는 변수를 &lt;b&gt;호이스팅 할 때 변수 명만 끌어올리고 할당 과정은 원래 자리에 그대로&lt;/b&gt; 둡니다. 따라서 호이스팅이 발생하는 겁니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;변수의 선언 / 초기화 / 할당&lt;/b&gt;&lt;br /&gt;- &lt;b&gt;선언&lt;/b&gt; : 변수를 생성. 해당 식별자를 등록하여 스코프가 참조할 대상을 만듦.&lt;br /&gt;- &lt;b&gt;초기화&lt;/b&gt; : 메모리에 변수를 저장하기 위한 공간 확보. 기본값으로 undefined가 할당됨.&lt;br /&gt;- &lt;b&gt;할당&lt;/b&gt; : = 연산자로 값을 할당.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 함수의 선언문은 호이스팅 돼서 함수 선언 전에 호출이 가능하지만, 함수의 표현식은 함수 선언 전에 호출되지 않게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712942303138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(b);      // undefined
console.log(func);   // f func() { ... }
console.log(func2);  // undefined

var b = bbb;

// 함수의 선언문
function func() {
	console.log(&quot;a&quot;);
};

// 함수의 표현식
var func2 = function () {
	console.log(&quot;b&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;아래와 같이 함수의 선언문은 함수 전체를 끌어올리지만 함수의 표현식은 변수 명만 끌어올리기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712942676189&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var b;              // 변수는 선언부만 끌어올림
function func() {}  // 함수 선언은 전체를 끌어올림
var func2;

b = bbb;

// 함수의 표현식
func2 = function () {
	console.log(&quot;b&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스코프와 스코프 체인&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스코프(Scope)는 식별자에 대한 유효범위&lt;/b&gt;를 말합니다. A 함수 내에 선언한 변수, 함수는 A 내에서만 접근 가능할 수 있게 됩니다. A 함수 외부에 선언한 변수, 함수는 외부뿐만 아니라 A 함수 내에서도 접근이 가능합니다.&amp;nbsp;&lt;b&gt;식별자 유효범위를 안에서부터 바깥으로 차례대로 검색하는 것을 스코프 체인&lt;/b&gt;이라고 합니다. 이를 가능하게 하는 것은 LexicalEnvironment의 outerEnvironmentReference 때문입니다.&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;함수가 선언될 당시의 LexicalEnvironment를 참조하고 있는 outerEnvironmentReference는 연결리스트(Linked List) 형태를 띱니다. 예를 들어 A 함수 안에 B 함수안에 C 함수로 구성되어 있다면 C 함수의 outerEnvironmentReference에는 B의 LexicalEnvironment, B의 outerEnvironmentReference에 A의 LexcicalEnvironment를 참조하고 있기 때문에 C함수에서는 가장 가까운 순서대로 B함수 &amp;rarr; A 함수로 접근이 가능하게 됩니다. 따라서 여러 스코프에서 동일한 식별자를 선언하는 경우 스코프 체인 상에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;가장 가까운&lt;span&gt;&amp;nbsp;식별자에만 접근 가능하게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712943477553&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var a = 1;
var outer = function () {
  var inner = function () {
    console.log(a);  // 3
    
    // 호이스팅되어 inner에서는 a 호출 시 전역이 아닌 가장 가까운 스코프안에 있는 이 값을 바라봄
    var a = 3;
  }  // outerEnvironmentReference에 outer 함수의 LexicalEnvironment 저장
  inner();
  console.log(a);  // 1
};  // outerEnvironmentReference에 전역 LexicalEnvironment 저장
outer();
console.log(a);  // 1&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;클로저&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[링크]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클로저란 이미 생명주기가 끝난 외부 함수(실행 컨텍스트가 종료된 함수)의 변수를 참조하는 함수&lt;/b&gt;를 말합니다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;어떻게 접근이 가능할까요? 바로 LexcialEnvrionment의 outerEnvironmentReference에 외부 함수의 정보가 담겨 있기 때문입니다. outerEnvironmentReference에는 함수가 선언될 당시의 LexicalEnvrionment 가 담긴다고 말했습니다. 따라서 이를 이용해 접근이 가능하게 됩니다.&lt;/span&gt;&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;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;클로저 활용 사례&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트 리스너&lt;/li&gt;
&lt;li&gt;정보 은닉&lt;/li&gt;
&lt;li&gt;부분 적용 함수&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메모리 누수&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있으면 그 값은 수집 대상에 포함하지 않아 메모리상에 계속 남아 있게 됩니다.&lt;/b&gt; 외부 함수의 변수를 클로저가 참조하고 있기 때문에 메모리상에 계속 남아 있게 되어 메모리 누수가 발생하게 됩니다. ( 2019년부터는 V8엔진의 경우 내부 함수에서 실행되는 변수를 제외하고는 GC 하도록 되었다고 합니다. )&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;식별자에 기본형 데이터 null 또는 undefined를 할당&lt;/b&gt;하여 이 식별자는 더 이상 사용하지 않으니 GC가 메모리를 해제하도록 할 수 있습니다.&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;&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;* 코어 자바스크립트 - 정재남&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;* 모던 자바스크립트 Deep Dive - 이웅모&lt;/p&gt;</description>
      <category>WEB/JavaScript</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/207</guid>
      <comments>https://itinerant.tistory.com/207#entry207comment</comments>
      <pubDate>Sat, 13 Apr 2024 02:58:54 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm][JS] 프로그래머스 - 두 원 사이의 정수 쌍</title>
      <link>https://itinerant.tistory.com/206</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181187&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[프로그래머스 - 두 원 사이의 정수 쌍]&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x축과 y축으로 이루어진 2차원 직교 좌표계에 중심이 원점인 서로 다른 크기의 원이 두 개 주어집니다. 반지름을 나타내는 두 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;r2가 매개변수로 주어질 때, 두 원 사이의 공간에 x좌표와 y좌표가 모두 정수인 점의 개수를 return 하도록 solution 함수를 완성해 주세요.&lt;br /&gt;※ 각 원 위의 점도 포함하여 셉니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xqOox/btsGupq3wQR/ZbZEKrDFS9rf58XntOgfU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xqOox/btsGupq3wQR/ZbZEKrDFS9rf58XntOgfU0/img.png&quot; data-alt=&quot;예제&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xqOox/btsGupq3wQR/ZbZEKrDFS9rf58XntOgfU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxqOox%2FbtsGupq3wQR%2FZbZEKrDFS9rf58XntOgfU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;656&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;656&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;문제 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;   힌트&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;원의 방정식 &lt;span style=&quot;color: #000000;&quot;&gt;x^2 + y^2 = r^2&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1 사분면만 계산 후 * 4&amp;nbsp;&lt;/b&gt;&lt;/span&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;◎ 도전 1&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 처음 생각한 방법은 아래와 같았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;x, y 축을 반복문을 돌면서 x^2 + y^2 이 r1^2 보다 같거나 크고 r2^2 보다 같거나 작으면 count 증가&lt;/blockquote&gt;
&lt;pre id=&quot;code_1712583949407&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(r1, r2) {
  let count = 0;
  for (let x = 1; x &amp;lt; r2; x++) {
    for (let y = 0; y &amp;lt; r2; y++) {
      let dot = x ** 2 + y ** 2;
      if (r1 ** 2 &amp;lt;= dot &amp;amp;&amp;amp; dot &amp;lt;= r2 ** 2) {
        count++;
      }
    }
  }
  return count * 4 + 4;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이법은 맞지만, 이중 반복문이다 보니 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;O(n^2)으로 인해&lt;/b&gt;&lt;/span&gt; &lt;b&gt;7~10 테스트 케이스에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;시간초과&lt;/span&gt;&lt;/b&gt;가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;◎ 도전 2&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 풀이 보다 더 빠른 알고리즘을 이용했어야 했습니다. 그래서 반복문 하나로만 해결할 수는 없을까 생각해 보았습니다. 해결 방법은 생각보다 간단했습니다.&lt;b&gt; 두 원 사이의 Y축의 높이만 더하면&lt;/b&gt; 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;X축을 돌면서 y축의 최대 높이(maxH) - 최소 높이(minH)를 뺀 길이을 count에 추가&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 보면 다음과 같습니다. r1 = 2, r2 = 6일 때 x축을 돌면서 초록색 높이의 값만 더해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQpeQ/btsGurPOeZu/MPEwh37kEKzrw11fp10q7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQpeQ/btsGurPOeZu/MPEwh37kEKzrw11fp10q7K/img.png&quot; data-alt=&quot;그림 해설&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQpeQ/btsGurPOeZu/MPEwh37kEKzrw11fp10q7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQpeQ%2FbtsGurPOeZu%2FMPEwh37kEKzrw11fp10q7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;510&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;582&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;그러면 y축의 최대 높이와 최소 높이는 어떻게 구할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y축의 높이는 원의 방정식을 이용해서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;y = sqrt(r^2 - x^2)&lt;/b&gt;&lt;/span&gt;로 구할 수 있습니다. 최대 높이의 경우 해당 값보다 작거나 같아야 하기 때문에 버림을 사용하고, 최소 높이의 경우 해당 값보다 크거나 같아야 하므로 올림을 해줍니다. 그리고 최소 높이의 경우 x 가 r1 보다 커지는 경우 마이너스가 되어 NaN 값이 나오게 되므로 음수가 되는 경우 0이 되도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 코드로 작성하면 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712585132940&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(r1, r2) {
  let count = 0;
  for (let x = 1; x &amp;lt;= r2; x++) {
    const maxH = Math.floor(Math.sqrt(r2 ** 2 - x ** 2));
    const minH = Math.ceil(Math.sqrt(r1 ** 2 - x ** 2)) || 0;   
    count += maxH - minH + 1;
  }
  return count * 4;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 되면, 시간복잡도가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;O(n)&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&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;&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;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;걸린 시간 : 2시간&lt;/li&gt;
&lt;li&gt;Y 축의 길이만 계산하면 된다는 방법을 찾아내는데 오래 걸렸다 :(&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <category>2단계</category>
      <category>프로그래머스</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/206</guid>
      <comments>https://itinerant.tistory.com/206#entry206comment</comments>
      <pubDate>Mon, 8 Apr 2024 23:22:16 +0900</pubDate>
    </item>
    <item>
      <title>[React] Children Props 와 렌더링 최적화</title>
      <link>https://itinerant.tistory.com/205</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 최대 관심사는 &lt;b&gt;[성능]&lt;/b&gt;이다. 그래서 최근 Reflow와 Repaint 관련 글을 보다가 브라우저 렌더링으로 관심사가 넘어가게 되었고 더 나아가 리엑트 렌더링과 최적화 관련해 글을 보던 중 한 가지 궁금한 점을 발견했다.&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; text-align: start;&quot;&gt;내 궁금증은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://www.youtube.com/watch?v=1YAWshEGU6g&quot;&gt;[10분 테코톡] 앨버의 리액트 렌더링 최적화]&lt;/a&gt;로부터 시작되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 최적화 하는 방법으로는 `useMemo`, `React.memo`, `useCallback`을 사용할 수 있다. 다만, 이 작업은 값비싼 비용이 발생 하기 때문에 많이 사용하게되면 성능에 오히려 좋지 않다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;u&gt;Childrend Props를 이용해서 리렌더링을 최적화할 수 있다.&lt;/u&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;불필요한 렌더링이 발생하지 않도록 처음부터 코드를 잘 작성하자. 근본적인 코드를 개선하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중, 가장  궁금했던 내용은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;부모 컴포넌트가 변경되더라도 children props로 전달된 자식 컴포넌트는 리렌더링 되지 않는다&lt;/b&gt;&lt;/span&gt;는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 리렌더링 되는 조건은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상태(State)&lt;/b&gt;가 변경된 경우&lt;/li&gt;
&lt;li&gt;부모로 부터 전달받은 &lt;b&gt;속성(Props)이&lt;/b&gt; 변경된 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, children도 props인데 왜 리렌더링 되지 않을까?&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;Children Props로 자식 컴포넌트 전달해보기&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 확인해보자. ParentComponent의 Children Props로 ChildComponent를 전달하는 테스트 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1710767003771&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메인
function TestPage() {
  return (
    &amp;lt;ParentComponent&amp;gt;
      &amp;lt;ChildComponent value='string' /&amp;gt;
    &amp;lt;/ParentComponent&amp;gt;
  );
}

// 자식 컴포넌트
function ChildComponent({ value }: Value) {
  console.log(&quot;[Child Component] render&quot;);
  return &amp;lt;p&amp;gt;{value}&amp;lt;/p&amp;gt;;
}

// 부모 컴포넌트
function ParentComponent({ children }) {
  console.log(&quot;[Parent Component] render&quot;);
  const [state, setState] = useState(false);

  const changeState = () =&amp;gt; {
    setState(!state);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={changeState}&amp;gt;update state&amp;lt;/button&amp;gt;
      {children}
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 실행한 후 update state 버튼을 눌러 상태를 변경해 보면, 콘솔에는 Parent Component만 메시지가 나타난다. 즉, Parent Component만 리렌더링 되고 Child Component는 리렌더링 되지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;275&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpFK6l/btsFRtVM4u1/BDieTl5sI0fntDA07Td6H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpFK6l/btsFRtVM4u1/BDieTl5sI0fntDA07Td6H1/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpFK6l/btsFRtVM4u1/BDieTl5sI0fntDA07Td6H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpFK6l%2FbtsFRtVM4u1%2FBDieTl5sI0fntDA07Td6H1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;275&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;275&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;나는 왜 자식 컴포넌트도 렌더링 된다고 생각했을까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 다음과 같은 이유로 Parent Component가 리렌더링되면 Child Component도 리렌더링 될 것이라 생각했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Children은 Props 속성 중 하나&lt;/b&gt;이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;현재, 부모 컴포넌트의 상태나 props가 자식 컴포넌트에 전달되지 않고, 자식 컴포넌트가 그 정보에 의존하지 않다.&lt;/li&gt;
&lt;li&gt;하지만, JSX 코드는 Babel 로 컴파일하면 React.createElement로 변환되고 &lt;b&gt;React.createElement&lt;/b&gt;는 결국 React Element &lt;b&gt;객체(Object)&lt;/b&gt;를 반환한다.&lt;/li&gt;
&lt;li&gt;객체는 변경 가능(mutable)한 값이기 때문에 Parent Component가 리렌더링 되면서&lt;b&gt; 참조 값이 변경&lt;/b&gt;되어 새로 Child 컴포넌트를 생성할 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JavaScript로 변경해서 원인을 찾아보자&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JSX는 결국 CreateElement로 변환되기 때문에 변경해서 확인해보기로 했다. 특정 JSX가 어떻게 변경되는지 확인해보고 싶다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://babeljs.io/repl/#?presets=react&amp;amp;code_lz=GYVwdgxgLglg9mABACwKYBt1wBQEpEDeAUIogE6pQhlIA8AJjAG4B8AEhlogO5xnr0AhLQD0jVgG4iAXyJA&quot;&gt;온라인 Babel 컴파일러&lt;/a&gt;&lt;span&gt;를 통해 쉽게 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xy459/btsFUdYZmgh/s8Cj5SvPc2SvKEEYKxrRm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xy459/btsFUdYZmgh/s8Cj5SvPc2SvKEEYKxrRm1/img.png&quot; data-alt=&quot;온라인 Babel 컴파일러에서 실행한 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xy459/btsFUdYZmgh/s8Cj5SvPc2SvKEEYKxrRm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxy459%2FbtsFUdYZmgh%2Fs8Cj5SvPc2SvKEEYKxrRm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1085&quot; height=&quot;730&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;온라인 Babel 컴파일러에서 실행한 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일된 코드에서 자질구래한 코드가 많아 확인하기 좀 어렵다면, 직접 CreateElement를 이용해 컴포넌트를 생성하는 방식으로 변경해 보면 좀더 이해하기 쉽다. 다음 코드는 TestPage 컴포넌트만 CreateElement로 변경한 코드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lq7nb/btsFQYWqLqy/IzSSxykxGTis0c1oNslLm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lq7nb/btsFQYWqLqy/IzSSxykxGTis0c1oNslLm1/img.png&quot; data-alt=&quot;좀 더 간략히 작성한 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lq7nb/btsFQYWqLqy/IzSSxykxGTis0c1oNslLm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flq7nb%2FbtsFQYWqLqy%2FIzSSxykxGTis0c1oNslLm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;187&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;187&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;&lt;b&gt;찾았다! 이유!&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;변경해보니 child component는 변경이 안 되는 이유를 확실히 알 수 있었다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;자식 컴포넌트 생성은 ParentComponent가 아닌 TestPage에서 생성 후 전달&lt;/b&gt;해 주고 있었다. 왼쪽은 내가 생각했던 컴포넌트 생성 구조였고, 실제로는 오른쪽과 같았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mBb0q/btsFRdlm5GL/ladUMbkkKtKrWMD278DBTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mBb0q/btsFRdlm5GL/ladUMbkkKtKrWMD278DBTK/img.png&quot; data-alt=&quot;컴포넌트 생성 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mBb0q/btsFRdlm5GL/ladUMbkkKtKrWMD278DBTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmBb0q%2FbtsFRdlm5GL%2FladUMbkkKtKrWMD278DBTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;355&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;355&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;이렇게 보면 더 알아보기 쉽다. 아래 두 함수는 결국 같다. 확실히 TestPage에서 자식 컴포넌트도 생성되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1710770027482&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1.
function TestPage() {
  const child = ChildComponent({ value: &quot;string&quot; });
  return &amp;lt;ParentComponent children={child} /&amp;gt;;
}

// 2.
function TestPage() {
  return (
    &amp;lt;ParentComponent&amp;gt;
      &amp;lt;ChildComponent value='string' /&amp;gt;
    &amp;lt;/ParentComponent&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Child Component의 참조 값을 갖고 있는 ParentComponent는 변경되더라도 다음과 같은&amp;nbsp;&lt;b&gt;이유&lt;/b&gt;로 변경되지 않았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Child Component를 생성한 &lt;b&gt;부모 컴포넌트&lt;/b&gt;인 Test Page 컴포넌트는 &lt;b&gt;리렌더링되지 않&lt;/b&gt;았다.&lt;/li&gt;
&lt;li&gt;Child Component는 상수다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Child Component는 &lt;b&gt;Parent Component의 상태와 속성에 의존적이지 않기 때문&lt;/b&gt;에 변경되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Children Props 관련된 자세한 내용은 아래 글들을 통해 좀더 자세히 확인해볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.developerway.com/posts/react-elements-children-parents&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The mystrey of React Element, children, parents and re-renders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/swc-project/swc/issues/7318&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React Constant Element transformation support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 ChildComponent는 ParentComponent에서 생성되지도 않는데 그 사실을 잊고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Children Props는 속성(props)인데 객체 값이니깐 무조건 변경되겠다는 착각을 했었다.&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: #333333; text-align: start;&quot;&gt;&lt;/span&gt;이유를 찾아보니, ChildComponent를 생성하는 위치가 달랐던... 생각보다 너무 간단했고, children 속성은 어려운 친구 &amp;zwj;♀️였다. 덕분에 리액트 렌더링 과정에 대해서도 다시 공부하게되는 시간을 갖게 되어서 좋았다.&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;</description>
      <category>WEB/React</category>
      <category>Children</category>
      <category>Parents</category>
      <category>렌더링</category>
      <category>렌더링최적화</category>
      <category>리액트</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/205</guid>
      <comments>https://itinerant.tistory.com/205#entry205comment</comments>
      <pubDate>Mon, 18 Mar 2024 23:47:47 +0900</pubDate>
    </item>
    <item>
      <title>더 이상 토이 프로젝트를 만들지 않는 이유 (부제. 좋은 컴포넌트 만들기)</title>
      <link>https://itinerant.tistory.com/204</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 고민&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;나는 4번째 토이 프로젝트를 끝내고, 회고를 작성하면서  프로젝트에 대해 다시 생각해 보았다. &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&quot;무엇을 잘했고 무엇을 못했을까?&quot;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;가장 먼저 생각난 것은 내 코드의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;유지보수의 불편함&lt;/b&gt;&lt;/span&gt;이었다. 그만큼 내가 &lt;i&gt;&lt;u&gt;결과물에 집착해서 개발을 했다는&lt;/u&gt;&lt;/i&gt; 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토이 프로젝트는 나를 성장시키기 위한 &lt;b&gt;도구&lt;/b&gt;일 뿐이다. 나는 현재 프론트 엔드 분야로 취업에 목말라 공장처럼 프로젝트를 찍어 내고 있었고, 그 결과 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;좋은&lt;/b&gt;&lt;/span&gt;&quot; 코드를 만드는 것보다 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/span&gt;&quot;에만 집중한 코드를 만들고 있었다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;( 아... 이래서 지금, 모든 회사에서 서류 탈락하고 있구나? ㅋ )&lt;/i&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;나는 &quot;&lt;b&gt;재사용 가능한&lt;/b&gt;&quot;, &quot;&lt;b&gt;유지보수가 편리한&lt;/b&gt;&quot; 컴포넌트를 만들어야 한다는 것을 안다. 하지만 코딩을 할 때는 이 이론은 머릿속에서 점점 잊혀 가고 결과에만 집중한 코드를 생산하게 되고, 결국 이 코드는 나중엔 큰 쓰레기 로 나에게 다시 돌아온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 고민을 하고 난 후 나는 더 많은 프로젝트를 생성하는 대신, 기존 프로젝트를 개선해 나가기로 결심했다. 그리고 컴포넌트에 대한 다양한 영상을 찾아보았다.&lt;/p&gt;
&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;blockquote data-ke-style=&quot;style2&quot;&gt;내가 본 영상들이 나쁘다는 것은 아니며, 내가 봤던 강의들(강사들)이 별로다라는 것도 아니다. &lt;br /&gt;영상들을 통해 프로젝트 구조, 다양한 라이브러리들, 다양한 훅들을 사용해보면서 다양한 접근 방법을 배웠 고 성장했다고 말할 수 있다.&amp;nbsp;&lt;/blockquote&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;로 배웠다고 말할 수 있다. 나는 Vue를 알고 있기 때문에 유튜브로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;프로젝트를 만드는 영상&lt;/u&gt;들을 보면서 리액트를 배웠다. 일반적인 리액트 책들에서는 TODO 리스트, 게시판 같이 간단한 것만 예제로 나오지만, 유튜브에는 노션, Trello, 쇼핑몰 등 더 큰 규모의 사이트들을 만들기 때문에 &quot;&lt;i&gt;회사에서도 이런 구조로, 이런 방식으로 코딩하겠구나&lt;/i&gt;&quot; 생각했다.&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;&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;b&gt;배민 유튜브 [10분 테코톡]&lt;/b&gt;이였다. 그 후 5~8개 정도의 컴포넌트 관련 영상을 봤다.&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;사용자가 제품을 어려움 없이 잘 사용해야 한다. 하지만 실제론 그렇지 않다. 그렇기 때문에 많은 변화를 겪는다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;제품은 변화를 겪으면서 올바른 방향으로 성장한다. 변경은 정해져 있는 것이 아니기 때문에 변경을 예측하지 말고 대응해야 한다.&lt;/b&gt;&lt;br /&gt;- SLASH 22 Effective component 지속 가능한 성장과 컴포넌트&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국엔, 그러기 위해서는 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;컴포넌트를 잘 활용해야 한다.&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 컴포넌트의 분리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 분리하는 이유는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;코드의 복잡도를 낮추고 모듈화하여 재사용이 가능하도록 하는 것&lt;/b&gt;&lt;/span&gt;에 있다. 컴포넌트가 중복되거나 적당히 커지면 분리하는 것이 아닌 &lt;b&gt;&lt;u&gt;컴포넌트를 분리하는 데는 이유&lt;/u&gt;&lt;/b&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;# 변경에 유현한 컴포넌트&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;- SLASH 22 Effective component 지속 가능한 성장과 컴포넌트&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Headless 기반의 추상화하기 ( 변하는 것 VS 변하지 않는 것 )&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스타일(UI)은 없는 컴포넌트&lt;/li&gt;
&lt;li&gt;변하는 것 = UI /&amp;nbsp;변하지 않는 것&amp;nbsp; = Data&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;동작(상호작용)을 Hooks으로 추상화하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한 가지 역할만 하기 또는 한 가지 역할만 하는 컴포넌트의 조합(Composition)으로 구성하기&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;도메인 분리하기 (도메인을 포함하는 컴포넌트와 그렇지 않은 컴포넌트 분리하기 )&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;일반적인 인터페이스로 분리하기 &amp;rarr; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이유? 컴포넌트의 역할을 이해하기 쉽다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사람들은 일반적으로 기존에 알고 있는 지식(배경지식)을 바탕으로 파악한다. 그래서 사람마다 배경지식이 다르기 때문에 일반적인 단어로 표현해야 의도를 들어내기 쉽다.&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그리고 대부분의 영상이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;b&gt;Headless&lt;/b&gt; Component Pattern&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Composition&lt;/b&gt; Component Pattern&lt;/li&gt;
&lt;li&gt;Custom &lt;b&gt;Hooks&lt;/b&gt; 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상은 봤지만, 범용적인 예제를 확인해 보고 싶었다. 그리고 그 예시는 생각보다 쉽게 찾을 수 있었 다. 바로 내가 모든 프로젝트에서 사용했던 &lt;a href=&quot;https://ui.shadcn.com/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shadcn/ui&lt;/a&gt;이다. 공식 홈페이지를 보면 &quot;&lt;i&gt;이건 라이브러리가 아니고, 재사용 가능한 컴포넌트이기 때문에 원하는 곳에 복붙 해서 입맛에 맞게 설정해서 사용하라&quot;라고&lt;/i&gt; 설명되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ETRL7/btsFP58zUD7/hlw8I7LlPXMMkWpzTpww5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ETRL7/btsFP58zUD7/hlw8I7LlPXMMkWpzTpww5k/img.png&quot; data-alt=&quot;shadcn/ui 홈페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ETRL7/btsFP58zUD7/hlw8I7LlPXMMkWpzTpww5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FETRL7%2FbtsFP58zUD7%2Fhlw8I7LlPXMMkWpzTpww5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;350&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;shadcn/ui 홈페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 shadcn/ui에서 제공하는 tabs ui를 만드는 방법을 보면, 제공하는 컴포넌트들을 조합해서 입맛에 맞게 다양하게 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;탭을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;만들 수 있도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cY7seo/btsFRj6aGMM/WsIuCxuRerCaJAapsRTvd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cY7seo/btsFRj6aGMM/WsIuCxuRerCaJAapsRTvd0/img.png&quot; data-alt=&quot;shadcn/ui tabs 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cY7seo/btsFRj6aGMM/WsIuCxuRerCaJAapsRTvd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcY7seo%2FbtsFRj6aGMM%2FWsIuCxuRerCaJAapsRTvd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;900&quot; height=&quot;601&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;shadcn/ui tabs 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiibWg/btsFP1Mgxie/LWgUDZ5nB8hKJGJQciJXY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiibWg/btsFP1Mgxie/LWgUDZ5nB8hKJGJQciJXY1/img.png&quot; data-alt=&quot;shadcn/ui tabs 사용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiibWg/btsFP1Mgxie/LWgUDZ5nB8hKJGJQciJXY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiibWg%2FbtsFP1Mgxie%2FLWgUDZ5nB8hKJGJQciJXY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;861&quot; height=&quot;270&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;shadcn/ui tabs 사용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Shadcn/ui는 &lt;a href=&quot;https://www.radix-ui.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Radix ui&lt;/a&gt;위에 스타일을 입힌 ui이다. 따라서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따로 스타일을 적용하지 않아도 UI가 아름답게 꾸며져 있다. 그렇기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;shadcn/ui에서 스타일을 수정하긴 쉽지 않다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Radix UI &amp;rarr; Headless Pattern&lt;/li&gt;
&lt;li&gt;Shadcn/ui&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; Composition&lt;/span&gt; Pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 나는 Headless와 Composition 패턴을 모두 보았고, 그게 이 방식인지 몰랐었을 뿐이었다.  &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 결론 -&amp;nbsp; 리팩토링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트를 만들어 보면서 다양한 에러/문제를 경험하고 헤쳐나가는 것도 좋은 성장 방법 중 하나이지만, 이번에는 더 이상 새로운 프로젝트를 만드는 것이 아닌 기존 프로젝트를 바꿔나가는 것이  좋을 것 같다는 생각이 들었다. 회사에 입사해서도 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;매번&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;b&gt;너무 결과물에만 집중하지 말고 다양한 방식을 보며 다양한 것을 배우고 성장해 나가는 것에 목표를 두고 싶기에 시도해 보는 것&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 당분간은 새로운 프로젝트 개발보다는 리액트라는 라이브러리에 또는 프런트엔드에 대해 좀 더 깊게 공부하는 시간을 갖도록 공부 방향을 바꿔볼 것이다.&lt;/p&gt;
&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;</description>
      <category>생각</category>
      <category>관심사분리</category>
      <category>리액트</category>
      <category>리팩터링</category>
      <category>리팩토링</category>
      <category>사이드프로젝트</category>
      <category>컴포넌트</category>
      <category>토이프로젝트</category>
      <category>패턴</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/204</guid>
      <comments>https://itinerant.tistory.com/204#entry204comment</comments>
      <pubDate>Mon, 18 Mar 2024 01:33:43 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트 [File Drive] 회고</title>
      <link>https://itinerant.tistory.com/203</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는&amp;nbsp;&lt;a href=&quot;https://github.com/jiyeon-dev/file_drive&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;파일 드라이브&lt;/b&gt;&lt;/a&gt;라는 클라우드 웹을&amp;nbsp; 만들었다. 지난 &lt;a href=&quot;https://itinerant.tistory.com/201&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;토이 프로젝트 회고&lt;/a&gt;때 말한 것 처럼 이번 토이 프로젝트는 &lt;b&gt;스프링 부트&lt;/b&gt;를 이용해 백엔드 서버를 만들고 &lt;b&gt;리액트&lt;/b&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;&quot;파일 드라이브&quot; 라는 걸 만들기로 결심한 이유는, 여느때와 같이 유튜브를 보다가 &lt;u&gt;Next.js로 FileDrive라는 웹 사이트를 만드는 &lt;a href=&quot;https://www.youtube.com/watch?v=27hMNWcsa-Y&amp;amp;t=541s&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;영상&lt;/a&gt;&lt;/u&gt;을 보았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMuBsl/btsFlU8eMvP/IygXQXxq2DIoTt2D5ki9RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMuBsl/btsFlU8eMvP/IygXQXxq2DIoTt2D5ki9RK/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=27hMNWcsa-Y&amp;amp;amp;t=541s&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMuBsl/btsFlU8eMvP/IygXQXxq2DIoTt2D5ki9RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMuBsl%2FbtsFlU8eMvP%2FIygXQXxq2DIoTt2D5ki9RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;443&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=27hMNWcsa-Y&amp;amp;t=541s&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 앞에 완성된 프로젝트가 어떻게 돌아가는 지 조금 보다가 만들어보면 재밌겠다  생각이 들었고, 바로 프로젝트 구상을 했다. 저분의 기능에 &lt;b&gt;폴더 관리 기능&lt;/b&gt;을 추가하기로 했다. 그리고, 저 영상 은, 다 보게 되면 왠지 저 분의 코드 구조를 따라하고 따라 칠 것만 같아서 영상은 더 이상 보지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성은 &lt;b&gt;스프링 부트와 MySQL&lt;/b&gt;를 백엔드로 하고 업로드할 파일들은 &lt;b&gt;파일 스토리지 서버&lt;/b&gt;에 저장하는 게 파일 URL이 반환되어 파일 관리 및 개발에 용이하기에 FireBase의 &lt;b&gt;Storage&lt;/b&gt;를 사용하기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 개발 기간이 좀 오래걸렸다. 아니 다시 말하면, 그냥 완료까지 시간이 걸렸다. 검지 손가락을 다치는 바람에 2주간은 손가락을 접을 수가 없어 중간에 커밋을 안하는 기간이 많이 있었다. 게다가 마지막에는 주먹구구식으로 날코딩을 하면서 프로젝트를 마무리 지었다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래서 처음~마지막 커밋은 &lt;b&gt;2주 정도&lt;/b&gt;지만 실 개발 일 수는 얼마 되지 않는다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;( 지금도 붕대 못풀었지만... 손 끝 마디는 구부릴수 있다.   )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 구상&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위에 유튜브 영상 화면 캡쳐된 것을 보고 UI를 구상했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 라이브러리&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프론트엔드&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://ui.shadcn.com/&quot;&gt;Shadcn/ui&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tailwindcss.com/&quot;&gt;TailwindCSS&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/pmndrs/zustand&quot;&gt;Zustand&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://tanstack.com/&quot;&gt;TanStack (React Query)&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.blocknotejs.org/&quot;&gt;Blocknote&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;✍️ : 노션처럼 생긴 깔끔한 에디터이다. 아직 베타버전이지만 사용하는데 문제없다.&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://zod.dev/&quot;&gt;Zod&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 타입스크립트 스키마 정의. CUD 작업 시 전달 타입 스키마 정의할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://sonner.emilkowal.ski/&quot;&gt;Sonner&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: toast 메시지 표시.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.react-hook-form.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React-hook-form&lt;/a&gt;: form hook&lt;/li&gt;
&lt;li&gt;FireBase Store&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java17&lt;/li&gt;
&lt;li&gt;Spring Boot 3.2.3&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/projects/spring-data-jpa&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JPA&lt;/a&gt; + MySQL&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/projects/spring-security&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jwtk/jjwt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JWT (jjwt)&lt;/a&gt;: Java Json Web Token&lt;/li&gt;
&lt;li&gt;FireBase Store&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://swagger.io/specification/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Swagger(openapi)&lt;/a&gt;: API 명세서&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 고충&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;크게 기억나는 몇 가지만 적어보았다.&lt;/blockquote&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;V Vite&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1)  Cors&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수년의 경험?!으로 &lt;b&gt;Proxy&lt;/b&gt;를 설정하면 된다는 건 알고 있었다. 그래서 React에서 Proxy를 설정하는 방법을 찾아보니 여러 가지가 나왔다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방법 1 : package.json에 proxy 설정&lt;/li&gt;
&lt;li&gt;방법 2 : http-proxy-middleware 라이브러리 설치 후 src/setupProxy.js 에서 proxy 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 적어도 2시간 넘게 서버 API 포트도 바꿔보고, GET URL 바꿔보고 별의별 짓을 다해봤는데 안됬다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;테스트 컨트롤러 만들어서 그냥 검색창에서 URL 호출도 해보고 curl로도 해보고 다 잘 나오는데 프론트에서 하면 안됬다.   문제의 원인도 아는데 찾아도 답이 안나오는 이 답답함...&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Fpws/btsFnY9XoMC/IzWW6KK26OZYTR76JuC4A1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Fpws/btsFnY9XoMC/IzWW6KK26OZYTR76JuC4A1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Fpws/btsFnY9XoMC/IzWW6KK26OZYTR76JuC4A1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Fpws%2FbtsFnY9XoMC%2FIzWW6KK26OZYTR76JuC4A1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;318&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;알고보니 Vite에서는 프록시 설정 방법이 달랐던 것이었다. ( ) Vite는 &lt;b&gt;vite.config.ts&lt;/b&gt; 에서 설정을 해줘야 한다! [&lt;a href=&quot;https://ko.vitejs.dev/config/server-options.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;]&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709425143099&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// vite.config.ts
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      &quot;/api&quot;: &quot;http://localhost:8080&quot;,
    },
  },
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json 에서 하던 host, port 변경이나 proxy 설정 등은 다 vite.config.ts 에서 한다는 점 꼭 기억해야겠다.  &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;  Spring boot&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Firebase Storage 에 File 업로드는 잘 했지만&amp;nbsp; 파일 다운로드 URL을 못 갖고 온다구요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 객체를 서버에 넘겨주면 스프링에서는 아래 코드와 같이 해당 파일을 Storage에 올리고 생성된 파일 링크를 반환하여 디비에 넣어주도록 코드를 설계했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bngS2e/btsFyM1jhkG/Af1TpQiPAlGifZnu5r3nK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bngS2e/btsFyM1jhkG/Af1TpQiPAlGifZnu5r3nK1/img.png&quot; data-alt=&quot;Firebase Storage 파일 업로드 코드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bngS2e/btsFyM1jhkG/Af1TpQiPAlGifZnu5r3nK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbngS2e%2FbtsFyM1jhkG%2FAf1TpQiPAlGifZnu5r3nK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;239&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Firebase Storage 파일 업로드 코드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 링크를 반환하도록 한 이유는 전체 파일 리스트를 조회할 때 이미지의 경우 미리보기를 해줘야 하는데 그때마다 &lt;b&gt;매번 FireBase 를 조회헤서 URL을 갖고 오는 건 리소스 낭비라고 생각&lt;/b&gt;했기 때문이었다. 그런데 &lt;b&gt;getMediaLink는 내가 알고 있는 파일 다운로드 링크가 아니었다&lt;/b&gt;. 반환되는 URL로 접속해보면 접근 금지 메세지가 뜬다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 100%;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getMediaLink에서 반환되는 URL과 실제로 내가 알아야하는 다운로드 링크의 경로는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getMediaLink시 반환되는 경로: https://storage.googleapis.com/download/storage/v1/b/[스토리지 버킷  주소]/o/file?generation=[생성시간]&amp;amp;alt=media&lt;/li&gt;
&lt;li&gt;다운로드 파일 경로: https://firebasestorage.googleapis.com/v0/b/[스토리지 버킷  주소]/o/file?alt=media&amp;amp;token=[액세스 토큰]&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;자바에서는 Storage에 있는 이미지(파일)의 URL을 갖고 올 수 없다!&lt;/b&gt; 다운로드 링크는 Client-side에서만 제공되고 Server-side에서는 제공이 안된다고 한다. &lt;b&gt;&lt;a href=&quot;https://cloud.google.com/storage/docs/samples/storage-generate-signed-url-v4?hl=ko#storage_generate_signed_url_v4-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;signUrl&lt;/a&gt;로&amp;nbsp;갖고 올 수는 있으나 다운 받을 수 있는 기간을 정해놓고 URL을 생성&lt;/b&gt;하게 된다. 즉, 파일을 서버에 올린 후 URL을 디비에 저장해봤자 시간이 지나면 URL이 만료되었기 때문에 못쓴다.( &amp;zwj;♀️) 생각지도 못한 문제였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kt4j5/btsFyd5TVVE/PVjWvbFjfumhYJ8nhyTRw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kt4j5/btsFyd5TVVE/PVjWvbFjfumhYJ8nhyTRw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kt4j5/btsFyd5TVVE/PVjWvbFjfumhYJ8nhyTRw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKt4j5%2FbtsFyd5TVVE%2FPVjWvbFjfumhYJ8nhyTRw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;224&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 파일을 저장하는 디비 구조를&amp;nbsp; 바꿨다. 스토리지 다운로드 링크를 저장하는 방식이 아닌 스토리지에 저장된 &lt;b&gt;파일의 경로를 저장&lt;/b&gt;하도록 하고, 프론트(React)에서 해당 경로로 스토리지를 조회해 이미지를 불러오는 형식으로 바꿨다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxr4xk/btsFt3JzyTe/dzpslzN2CuWZQXl3eAhwg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxr4xk/btsFt3JzyTe/dzpslzN2CuWZQXl3eAhwg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxr4xk/btsFt3JzyTe/dzpslzN2CuWZQXl3eAhwg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxr4xk%2FbtsFt3JzyTe%2FdzpslzN2CuWZQXl3eAhwg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;412&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  React&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;1) JWT 토큰 정보 저장하기&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 토큰 정보 전체를 세션에 저장하는 건 위험한 짓일 수도 있다. 대부분 로컬 스토리지와 세션에 나눠서 저장하거나 쿠키에 저장하는 방식을 많이 구현한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;쿠키&lt;/b&gt;에 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 쿠키 저장 방식은 XSS, CSRF 공격에 모두 취약하다.&lt;/li&gt;
&lt;li&gt;하지만 httpOnly, secure, SameSite옵션을 이용하면 자바스크립트 코드 상에서 접근이 불가능하기 때문에 XSS공격에 안전하고(localStorage에 비해 안전해진다), HTTP 요청에만 포함되어 보내지므로 안전해진다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;자동으로 http request에 담아서 보내기 때문에 CSRF 공격에는 취약하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LocalStorage + Session&lt;/b&gt;에 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LocalStorage: 자바스크립트 코드상에서 접근이 가능하기 때문에 XSS 공격에 취약하지만, &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;자바스크립트 코드에 의해 헤더에 담기므로&amp;nbsp;&lt;/span&gt;CSRF 공격에는 안전하다.&lt;/li&gt;
&lt;li&gt;Session : 안전하지만 탭을 닫으면 정보가 사라진다. XSS 공격에 취약하지만 CSRF 공격에 대해서는 쿠키보다 안전하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 나중에 수정하기 쉽도록 토큰 갖고오는 부분을 모듈화했고 일단 세션에 다 저장해버렸다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710425655426&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Token, TokenType } from &quot;@/types&quot;;

const AuthToken = {
  /**
   * 토큰 저장
   * @param data 토큰
   */
  setToken: (data: Token) =&amp;gt; {
    AuthToken.removeTokenAll();
    sessionStorage.setItem(TokenType.ACCESS, data.accessToken);
    sessionStorage.setItem(TokenType.REFRESH, data.refreshToken);
  },
  /**
   * 특정 토큰 조회
   * @param key 토큰 키
   * @returns {string} 토큰 값
   */
  getToken: (key: TokenType): string =&amp;gt; {
    return sessionStorage.getItem(key) || &quot;&quot;;
  },
  /**
   * 토큰 전제 조회
   * @returns {Token} 토큰 값
   */
  getTokenAll: (): Token =&amp;gt; {
    return {
      [TokenType.ACCESS]: sessionStorage.getItem(TokenType.ACCESS) || &quot;&quot;,
      [TokenType.REFRESH]: sessionStorage.getItem(TokenType.REFRESH) || &quot;&quot;,
    };
  },
  /**
   * 토큰 전체 삭제
   */
  removeTokenAll: () =&amp;gt; {
    sessionStorage.removeItem(TokenType.ACCESS);
    sessionStorage.removeItem(TokenType.REFRESH);
  },
};

export default AuthToken;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 쿠키 저장방식으로 변경해야지 생각만하고 안바꿨다. (이 글 쓰다가 알아챘다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DwU15/btsFOZsuMX5/AaaO2fOWatc4AHJPsrRv1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DwU15/btsFOZsuMX5/AaaO2fOWatc4AHJPsrRv1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DwU15/btsFOZsuMX5/AaaO2fOWatc4AHJPsrRv1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDwU15%2FbtsFOZsuMX5%2FAaaO2fOWatc4AHJPsrRv1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;240&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) JWT 토큰이 만료시 Refresh 토큰으로 서버에서 정보 다시 갖고 오기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인시 서버에서는 Refresh, AccessToken을 프론트로 전달해 줬다. 그리고 AccessToken이 만료되면 Refresh 토큰으로 다시 서버로 부터 정보를 갱신해 오는 것으로 구성했다.&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 라는 API를 호출할 때 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;인증이 필요한 경우 &lt;/span&gt;Header에 AccessToken 정보를 같이 보내고, 서버에서는 해당 Token을 확인한 후 인증 정보가 만료가 되면 인증이 만료되었고 프론트에 보내고, 프론트는 서버에 AccessToken과 Refresh Token을 같이 서버로 보내 갱신된 토큰을 세션에 저장 후 다시 A API를 호출하고 싶었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgmHjT/btsFMJ5IvFK/ZHNfWq1duK23QnYFwee71K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgmHjT/btsFMJ5IvFK/ZHNfWq1duK23QnYFwee71K/img.png&quot; data-alt=&quot;전체적인 흐름&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgmHjT/btsFMJ5IvFK/ZHNfWq1duK23QnYFwee71K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgmHjT%2FbtsFMJ5IvFK%2FZHNfWq1duK23QnYFwee71K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;419&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;419&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;프론트에서 헤더에 Token을 넣고 토큰 만료시 신규 토큰 발급 후 재 조회하는 부분을 모듈로 빼는 것이 효과적일 것 같아서&amp;nbsp; &quot;&lt;b&gt;authFetch&lt;/b&gt;&quot;라는 모듈을 만들었고, 인증정보가 필요한 API를 호출시 이 모듈을 호출하도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1710423901682&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const authFetch = async (
  url: string,
  options: RequestInit,
  retry?: boolean // 재귀호출인지 체크
): Promise&amp;lt;globalThis.Response | null&amp;gt; =&amp;gt; {
  // 헤더에 토큰 정보 추가
  const authOptions = setAuthToken(options);
  // API 호출
  const response = await fetch(url, authOptions);

  // 토큰 만료 및 토큰 에러 발생 &amp;rarr; 리프레시 토큰으로 토큰 재 조회
  if (!retry &amp;amp;&amp;amp; response.status === 403) {
    const result = await getNewToken();  // 토큰 재 조회
    if (result) authFetch(url, options, true); // 다시 호출
  }

  // 인증 오류가 아닌 알수 없는 오류가 발생한 경우
  if (!response.ok) {
    toast.error(MESSAGE.E01, { id: &quot;auth-fetch&quot; });
    return null;
  }

  return response;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 만료라 표시될 경우 같은 &lt;b&gt;함수를 재호출(재귀호출)&lt;/b&gt;하도록 했다. 만일 서버쪽 문제로 계속 토큰이 만료되어 잘못하면 무한 루프에 빠질 수 있기 때문에 &lt;b&gt;retry&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;이렇게 모듈화하면서 인증이 필요한 API 는 이 모듈을 호출하도록하였기 때문에 API 방식이 변경될 때 수정이 용이해졌기 때문에 유지보수가  편해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 새로 알게된 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  React&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) useSearchParams 의 활용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일명 검색을 하거나 파일 타입을 변경하는 경우 새로고침을 해도 검색어나 파일 타입을  그대로 표시하고 싶었기 때문에 상태 값으로 저장하기 보단 get parameter로 검색 정보를 표시하도록 구현하기로 했다. 현재 URL이 &quot;/files?type=pdf&quot; 인 경우 a가 포함된 파일명 검색을 하면 &quot;/files?type=pdf&amp;amp;searchTerm=a&quot; 이렇게 변경되길 원했다. get parameter에 타입 값과 검색어 키는 조건에 따라 있을 수도, 없을 수도 있기 때문에 동적으로 추가 해줄 필요가 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 아래와 같이 react-router-dom에 있는 useLocation과 useNavigate훅을 이용해서 구현했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709705595917&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 검색어 조회하는 경우
const location = useLocation();
const navigate = useNavigate();

const onSubmit = (values) =&amp;gt; {
  // 현재 URL 에서 get parameter 정보
  const currentSearch = new URLSearchParams(location.search);
  // 새로운 검색어 추가
  currentSearch.set(&quot;searchTerm&quot;, values.searchTerm);
  // 페이지 이동
  navigate(`${location.pathname}?${currentSearch.toString()});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 곳에서 react-router-dom의 useSearchParams훅을 이용해서 get parameter 정보를 갖고 올 일이 있었는데, 그 때 사용하는 방식이 다음과 같았다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709705727555&quot; class=&quot;abnf&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const [searchParams] = useSearchParams();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이때, &quot;이거 useState 같은 상태 관리 훅 같은데 ?&quot; 라는 느낌이 들어서 관련&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://reactrouter.com/en/main/hooks/use-search-params&quot;&gt;문서&lt;/a&gt;를 찾아보았다. 아니나 다를까, 공식문서에도 useState와 같이 &lt;b&gt;setSearchParams를 이용해서 업데이트 할 수 있다&lt;/b&gt;라고 명시되어 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnxJ1h/btsFwDrpRel/IShyP5mUGhuromJi4xkmzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnxJ1h/btsFwDrpRel/IShyP5mUGhuromJi4xkmzk/img.png&quot; data-alt=&quot;공식 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnxJ1h/btsFwDrpRel/IShyP5mUGhuromJi4xkmzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnxJ1h%2FbtsFwDrpRel%2FIShyP5mUGhuromJi4xkmzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;480&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공식 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 바로 코드에 적용했다. 문서 하단에 &lt;b&gt;setSearchParams 함수는 navigate와 같은 역할을 한다&lt;/b&gt;고 적혀 있었기 때문에 따로 navigate를 호출할 필요도 없 어 코드도 더 간결해졌다.&lt;/p&gt;
&lt;pre id=&quot;code_1709706208693&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [searchParams, setSearchParams] = useSearchParams();

const onSubmit = (values) =&amp;gt; {
    searchParams.set(&quot;searchTerm&quot;, values.searchTerm);
    setSearchParams(searchParams);
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 해결 못한 부분  &lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MySQL에서 Like 를 이용한 한글 검색&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 명 검색 시 파일 명 일부만 입력하더라도 파일들이 조회도록 구현하고 싶었다. 따라서 &lt;b&gt;Like&lt;/b&gt;를 이용해 구현했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709723408574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JPA Repository
Page&amp;lt;File&amp;gt; findAllByTypeAndNameContainingIgnoreCase(FileType type, String name, Pageable pageable);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709723354133&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM 테이블명 WHERE name Like '%파일%';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 문자열은 잘 검색되었지만 &lt;b&gt;한글은 검색이 안되&lt;/b&gt;었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방안 모색1. JPA 와 MySQL에서 찾아보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 JPA 쪽 문제인것 같아서 인코딩 관련하여 이것저것 찾아봤지만, MYSQL에는 한글로 정상적으로 삽입은 되어 있었다. 따라서 JPA 문제는 아니라 보고, MySQL Workbench를 열어서 직접 쿼리를 날려보았다. 그 결과 &lt;b&gt;MySQL 에서도 검색이 안된&lt;/b&gt;다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 인코딩이 문제인가? 싶어서 character_set을 조회해봤지만 &lt;b&gt;utf8mb4&lt;/b&gt;로 되어 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxVXvH/btsFAsvP8FW/j5SftWORFALMc0zAHmucqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxVXvH/btsFAsvP8FW/j5SftWORFALMc0zAHmucqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxVXvH/btsFAsvP8FW/j5SftWORFALMc0zAHmucqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxVXvH%2FbtsFAsvP8FW%2Fj5SftWORFALMc0zAHmucqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;251&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&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;pre id=&quot;code_1709723865450&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM file_drive.file WHERE name = &quot;파일.pdf&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGmMfF/btsFwA9AlpS/oWlOMWDW7olHqkMKklJTp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGmMfF/btsFwA9AlpS/oWlOMWDW7olHqkMKklJTp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGmMfF/btsFwA9AlpS/oWlOMWDW7olHqkMKklJTp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGmMfF%2FbtsFwA9AlpS%2FoWlOMWDW7olHqkMKklJTp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;328&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방안 모색2. GPT 에게 물어보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 도움이 될까 싶어서 ChatGPT에 물어봤다. 그랬더니 &lt;b&gt;Collcation을 utfmb4_unicode_ci 로 변경&lt;/b&gt;하라는 답변 을 받았고, 현재 내 collection은 &lt;b&gt;utf8mb4_0900_ai_ci&lt;/b&gt;이라서 알려준 쿼리로 변경을 실행해 보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1709724001981&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER TABLE [테이블명] MODIFY COLUMN [컬럼명] VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, Like 뿐만아니라 이젠 비교 연산자도 검색이 안되었다 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 그래서 다시 원상 복구 했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방안 모색3. Brew로 MySQL 서버 버전 다운그레이드해보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 MySQL을 사용해 본적이 없었기에 혹시나 버전이 올라가면서 문제가 생겼나싶어 버전을 내려보았다. 기존에 brew를 이용해서 설치했었기 때문에 삭제한 후 brew에 존재하는 버전인 &lt;b&gt;8.0&lt;/b&gt;을 설치해보았다. 역시나 안되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 또 삭제하고 더 이전 버전인 &lt;b&gt;5.7&lt;/b&gt; 버전도 설치해 보았고, 이것도 역시 안되었다.   &lt;b&gt;my.cnf&lt;/b&gt;에 인코딩 설정을 안해줘서 그런가 싶어서 5.7 버전일 때 character_set 을 조회해 보니 설정을 안해줘도 다 &lt;b&gt;utf8&lt;/b&gt;로 되어 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqzpZM/btsFB6lhBe4/TSADUB41ZbiRDx0B31MLH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqzpZM/btsFB6lhBe4/TSADUB41ZbiRDx0B31MLH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqzpZM/btsFB6lhBe4/TSADUB41ZbiRDx0B31MLH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqzpZM%2FbtsFB6lhBe4%2FTSADUB41ZbiRDx0B31MLH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;162&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 한 6시간은 고민한 것 같다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방안 모색4. MySQL 홈페이지에서 다운받아서 설치해보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 brew 가 아닌 MySQL 홈페이지에 가서 직접 Community Server를 다운 받아 설치해보기로 했다. 8.0.32 버전을 다운 받아보았고 역시나 안되었다...&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; text-align: start;&quot;&gt;도무지 감을 잡지 못했 고,&lt;span&gt;&amp;nbsp;일단 포기했다   &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 글을 읽는 누군가가 방법을 알아내신다면 꼭 댓글이나 메일 부탁드립니다. &lt;/blockquote&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 느낀 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;    Junit5와 Swagger&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 회사에서 Junit을 사용하긴 했으나 대부분 Junit4였다. Junit5를 사용해 본 적이 거의 없었다. 게다가 문법이 많이 바뀌었다보니 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정말&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Service 테스트하는데 어떻게 Mock 데이터를 만들고 테스트를 해야할지 멘붕의 연속이었다. 테스트 코드 작성하다가 피똥싸는 줄 알았다.&amp;nbsp;결국엔 몇개 만들다가 포기하고 Swagger에서 API 테스트할 수 있도록 설정해버렸다. ( Swagger도 openapi로 바뀌면서 약간 문법이 바뀌었다. 이건 변수 명이 바뀐 정도라 어렵진 않아서 다행이었다. 휴 )&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  React-Hook-Form&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 프로젝트때는 직접 비슷한 모듈을 만들어 사용했고, 처음으로 form hook을 사용해봤는데 편리했다. 다음 프로젝트에서는 직접 폼 훅을 만들어 보는 것도 재밌을 것 같다 : )&lt;/p&gt;
&lt;hr data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;손가락이 다쳐서 중간에 쉬는 기간이 길어지다보니 점점 이 프로젝트에 대한 흥미를 잃었다. 게다가 취업을 준비하면서 계속해서 서류탈락하다보니 자괴감과 점점 자신감을 잃고 있어, 더욱더 개발에 흥미를 잃었다. 그래서 끝으로 갈수록 완성에 목표를 두고 코드를 깨끗하지 못하게 작성했다. ( 비슷한 컴포넌트 반복해서 생성... ) 그래도 스프링부트를 이용해서 백엔드까지 개발했다는 점은 칭찬해주고 싶다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 일단 잠시 신규 프로젝트 개발에 집중하지 않고 언어 자체에 대해, CS에 대한 공부를 이어가려고 한다.&lt;span&gt; 이번 프로젝트를 하면서 계속해서 구글링하는 내 자신을 발견했다.&amp;nbsp; &lt;/span&gt;&lt;b&gt;정확한 매커니즘을 모른 채 개발하는 무의미한 개발은 더 이상 필요 없다&lt;/b&gt;고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;다만, 이 프로젝트의 프론트엔드는 추후 짬짬이 소리소문없이 리팩토링해 나갈 것이다.&lt;span&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  잘한 점 3가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스프링부트&lt;/b&gt;를 이용한 API 서버를 만들었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT&lt;/b&gt; 를 이용한 &lt;b&gt;인증&lt;/b&gt;을 적용했다.  &lt;/li&gt;
&lt;li&gt;&lt;b&gt;Zod&lt;/b&gt;와 &lt;b&gt;React-hook-form&lt;/b&gt;을 이용해서 API 전송 전 데이터 &lt;b&gt;검증 ️&lt;/b&gt;을 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  아쉬운 점 2가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Junit&lt;/b&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;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;제가 작성한 코드에 코드 리뷰(평가)도  받고 싶은데 언제든지 제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/jiyeon-dev/file_drive&quot;&gt;깃헙&lt;/a&gt;에 남겨주세요! 환영입니다!  &lt;/blockquote&gt;</description>
      <category>WEB/React</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/203</guid>
      <comments>https://itinerant.tistory.com/203#entry203comment</comments>
      <pubDate>Thu, 14 Mar 2024 23:51:53 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 환경변수로 yml 중요 정보 숨기기</title>
      <link>https://itinerant.tistory.com/202</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개인 Spring Boot 프로젝트 중 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Github에&lt;span&gt; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;yml, properties 파일에 있는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;중요한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;정보(예, 데이터베이스 계정 정보)와 같은 것들은 어떻게 숨겨야하는지 찾아 보았고, 저는 환경변수(.env)를 이용해서 정보를 숨기기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TMI&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이 방법을 선택했냐면, 엄청 중요한 이야기는 아니구요...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;찾아보니 가장 많이 사용하시는 방법이&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Jasypt&lt;/b&gt;를 이용한 방법이었습니다. 제 생각엔 이 방법의 단점이 암호화 키를 갖고 있어야 한다는 점인데, 그 암호화 키는 어디다 저장해서 갖고 있어야 할까요?  &amp;nbsp;&amp;nbsp;( 어떤 분들은 Github  Secrets 에 보관하시더라구요. ) 그리고 자바에 코드를 또 작성해야하구요. 사실 개인 프로젝트라 정보 털려도 괜찮은데 정보 노출되면 깃헙에서는 보안 알림오니...이렇게 까지하 긴 귀찮았습니다  &amp;zwj;♀️&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 환경변수에 담기로 했습니다. 이 방법의 단점은 .env를 갖고 있어야 한다는 점일 것 같아요. 파일을 백업 안해서 잃어버리면 절대 찾을 수 없으니 난리나겠죠 ? 그리고 장점은 설정이 간단한 것도 있지만 docker 같은 곳에 배포 때도 쉽게 .env 를 사용 가능하다고 하네요.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;환경변수로 정보 숨기기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;# 설정 방법&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) env 파일 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 &quot;.env&quot; 파일을 만들고, 숨길 정보를 입력했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709319641643&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .env
MYSQL_USERNAME=계정
MYSQL_PASSWORD=비밀번호
MYSQL_PORT=포트&lt;/code&gt;&lt;/pre&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;2) .gitignore 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하단에 .env 파일은 github에 올리지 않겠다고 선언합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709319713247&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;### env ###
.env&lt;/code&gt;&lt;/pre&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;3) application.yml 수정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env에 선언한 변수 명에 맞게&lt;b&gt; ${변수명}&lt;/b&gt; 형태로 변경해주면 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709319769397&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:${MYSQL_PORT}/file_drive
    username: ${MYSQL_USERNAME}
    password: ${MYSQL_PASSWORD}&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;이제 Github에 전체 소스 코드를 올리셔도 환경변수 정보는 같이 배포되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;# 환경변수로 바꾼 후&amp;nbsp;&lt;b&gt;IntelliJ&lt;/b&gt;에서 실행방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보를 환경변수에 선언했기 때문에 평소와 같이 IntelliJ에서 실행하면 스프링이 숨겨진 값을 알 수 없기 때문에 실행이 안됩니다. 실행 설정을 변경해야 합니다.&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;Run/Debug Configurations&lt;/b&gt;를 열고, &lt;b&gt;Modify options&lt;/b&gt;를 누른 후 &lt;b&gt;Environment variables&lt;/b&gt;를 선택해 줍니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pHDRD/btsFsZ7u2LF/ambgCqkbJU8rEN3WZtRj41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pHDRD/btsFsZ7u2LF/ambgCqkbJU8rEN3WZtRj41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pHDRD/btsFsZ7u2LF/ambgCqkbJU8rEN3WZtRj41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpHDRD%2FbtsFsZ7u2LF%2FambgCqkbJU8rEN3WZtRj41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;533&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI02BL/btsFmj7vuT6/DziKjXXhGMDNvkyq5kwb31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI02BL/btsFmj7vuT6/DziKjXXhGMDNvkyq5kwb31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI02BL/btsFmj7vuT6/DziKjXXhGMDNvkyq5kwb31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI02BL%2FbtsFmj7vuT6%2FDziKjXXhGMDNvkyq5kwb31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;502&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Environment variables 입력 창에 .env에 선언했던 정보들을&amp;nbsp;&lt;b&gt;키=값;키=값&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태로 입력하시거나, 우측 버튼을 눌러 표에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;좀더 편하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;입력이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/slvmU/btsFpSOMNWe/74zSWxoKQUaiytGOszTHa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/slvmU/btsFpSOMNWe/74zSWxoKQUaiytGOszTHa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/slvmU/btsFpSOMNWe/74zSWxoKQUaiytGOszTHa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FslvmU%2FbtsFpSOMNWe%2F74zSWxoKQUaiytGOszTHa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;529&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실행하시면 됩니다!&lt;/p&gt;</description>
      <category>JAVA/Spring</category>
      <category>application</category>
      <category>application.properties</category>
      <category>application.yml</category>
      <category>env</category>
      <category>spring</category>
      <category>spring boot</category>
      <category>SpringBoot</category>
      <category>복호화</category>
      <category>숨기기</category>
      <category>암호화</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/202</guid>
      <comments>https://itinerant.tistory.com/202#entry202comment</comments>
      <pubDate>Sat, 2 Mar 2024 04:29:45 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트 [Ducky] 회고</title>
      <link>https://itinerant.tistory.com/201</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 &lt;a href=&quot;https://itinerant.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;단어장 웹&lt;/a&gt;에 이어서 이번에는 &lt;b&gt;&lt;a href=&quot;http://ducky-alpha.vercel.app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;더키&lt;/a&gt;&lt;/b&gt;라는 블로그 웹을 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 큰 프로젝트다 보니 많은 리액트 라이브러리를 사용했다. 물론 별도의 라이브러리 없이 직접 만들면 좋겠지만 나는 편리하고 좋은 라이브러리를 찾아서 사용하는 것도 좋은 방법이라 생각한다. 미천한 나의 실력으로 직접 만든 불안정한 라이브러리를 사용하여 사이드 이슈를 많이 발생시키는 것보다 많은 사용 하며 업데이트고 관리되는 라이브러리를 찾아 사용하는 것도 프로젝트를 구현하는 데 있어서 좋은 방법이라 생각한다. (구차한 변명 같네ㅎ)&lt;/p&gt;
&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;6일&lt;/b&gt; 걸렸다(첫 커밋 ~ 마지막 커밋). 아무래도 이전 프로젝트에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파이어베이스 인증을 제외한 간단한 쿼리 사용법에 대해 배웠다 보니 이번에는 좀 더 수월히 개발할 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 구상&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 종이에 큰 그림만 끄적였다. 처음 메인 화면과 생성할 메뉴 &quot;저장소&quot;, &quot;글감&quot; 두 가지만 만들기로 결정했다. 나중에 &quot;글감&quot;은 Post 게시물로 이름을 변경했다. 그리고 메인화면은 사실 트위터나 Reddit 같은 화면으로 구현하려고 했는데, 모든 페이지가 글(게시물)을 작성하는 페이지다 보니 굳이 넣어야 하나 싶어서 제외했다. ( 사진은... 커피를 쏟아서 없다...  )&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 라이브러리&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Typescript&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ui.shadcn.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shadcn/ui&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TailwindCSS&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pmndrs/zustand&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Zustand&lt;/a&gt;&amp;nbsp;  &lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TanStack (React Query)&lt;/a&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.blocknotejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Blocknote&lt;/a&gt; ✍️ : 노션처럼 생긴 깔끔한 에디터이다. 아직 베타버전이지만 사용하는데 문제없다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zod.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Zod&lt;/a&gt; : 타입스크립트 스키마 정의. CUD 작업 시 전달 타입 스키마 정의할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sonner.emilkowal.ski/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sonner&lt;/a&gt; : toast 메시지 표시.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hello-pangea/dnd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;hello-pangea/dnd&lt;/a&gt; : 드래그 앤 드롭 라이브러리. &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;칸반보드에서&lt;span&gt; 카드나 리스트 옮길 때 사용.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://usehooks-ts.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;usehooks-ts&lt;/a&gt; : outside 클릭 이벤트 훅(useOnClickOutside)을 사용.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 고충&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;크게 기억나는 몇 가지만 적어보았다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Firebase&lt;/b&gt;&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 사용자 인증 정보 조회 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 부분은 경험이 없어서 firebase authentication 부분 정보를 많이 찾아봤다. 로그인한 후 유저 정보를 갖고 올 때 `&lt;b&gt;auth.currentUser&lt;/b&gt;` 로 로그인한 유저 정보를 갖고 올 수 있는데 파이어베이스 매서드 를 이용하는 것이라 매번 firebase에 조회를 하면 파이어베이스 조회 수가 증가하여 금전적으로 문제가 생길 것 같았다. (물론 아무도 접속 안하겠지만^^)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 정보를 zustand와 같은 상태 관리 라이브러리에 저장할까 생각했지만, 리랜더링될 때 정보가 사라지기 때문에 이는 맞지 않았다. 더 찾아보니 파이어베이스는 로그인을 하면 인증 정보를 사용자의 &lt;b&gt;캐시에 저장하고 그 정보를 갖고 오는 것&lt;/b&gt;이라고 한다. 그래서 auth.currentUser를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 사용자 탈퇴&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서 : &lt;a href=&quot;https://firebase.google.com/docs/auth/ios/manage-users?hl=ko#delete_a_user&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;사용자 삭제하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제하려는 사용자가 작성한 코멘트나 게시물들을 모두 다른 이름으로 바꾸고, 계정을 삭제하려고 아래 명령어를 입력하니 오류가 발생했다. &lt;b&gt;(auth/requires-recent-login)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709045958565&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;const user = auth.currentUser;
await user?.delete();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 계정 삭제, 비밀번호 변경과 같은 민간한 작업을 하려면 로그인한 적이 있어야 한다고 한다. (&lt;a href=&quot;https://firebase.google.com/docs/auth/web/manage-users?hl=ko#re-authenticate_a_user&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사용자 삭제 는 대부분 로그인한 유저만 탈퇴(삭제)가 가능하기 때문에 이미 로그인되어 있는데 왜 안되지?싶다. 공식문서상에는 &lt;b&gt;재인증&lt;/b&gt;을 하라고 했기 때문에 재인증 기능을 로그인할 때 넣어줬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDakSq/btsFjJrwQqy/fxY6m2RVwbxtPuzgjk3Yz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDakSq/btsFjJrwQqy/fxY6m2RVwbxtPuzgjk3Yz1/img.png&quot; data-alt=&quot;사용자 재인증&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDakSq/btsFjJrwQqy/fxY6m2RVwbxtPuzgjk3Yz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDakSq%2FbtsFjJrwQqy%2FfxY6m2RVwbxtPuzgjk3Yz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;262&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;262&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;왜 이곳에 넣었냐면, auth.currentUser에는 사용자의 패스워드 정보가 없기 때문에 인증정보를 갖고 올 수 없다. 그래서 이미 로그인 이후에는 재인증이 불가능하기 때문에 로그인할 때 바로 재인증하도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3)  배열 필드 요소 변경&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 페이지에서 리스트와 카드의 관계를 어떻게 구현할 지 가장 큰 고민이었다. 파이어베이스에서 하위 컬렉션 접근이 안된다는 것을 알았기 때문에 리스트(kanban)에  cards라는 배열 필드를 추가하고, 카드가 추가되면 cards에 추가하는 형식으로 구현했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1319&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LmJEA/btsFin90egZ/Aa3Tur8000JkeQQZiXVbb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LmJEA/btsFin90egZ/Aa3Tur8000JkeQQZiXVbb0/img.png&quot; data-alt=&quot;칸반에서 리스트와 카드 관계 구조화&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LmJEA/btsFin90egZ/Aa3Tur8000JkeQQZiXVbb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLmJEA%2FbtsFin90egZ%2FAa3Tur8000JkeQQZiXVbb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1319&quot; height=&quot;617&quot; data-origin-width=&quot;1319&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;칸반에서 리스트와 카드 관계 구조화&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이 방식의 단점이 있었다.&lt;b&gt; 배열 update 기능을 지원하지 않기 때문&lt;/b&gt;에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;카드 정보가 변경되면 해당 카드만 찾아서 변경이 불가능했다.&lt;span&gt; 문서에서는 &lt;u&gt;&lt;b&gt;기존 데이터를 삭제하고 추가&lt;/b&gt;&lt;/u&gt;하는 방식으로 하라고 설명되어 있었다. (&lt;a href=&quot;https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;) 게다가 &lt;b&gt;배열에서의 삭제는 값이 정확히 일치&lt;/b&gt;해야만 삭제가 되기 때문에 &lt;u&gt;트랙젝션을 걸고 리스트에서 변경하려는 카드 정보를 조회한 후 카드를 삭제하고 새 카드를 추가하는 방식&lt;/u&gt;으로 카드를 업데이트했다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709047844619&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await runTransaction(db, async (transaction) =&amp;gt; {
  // 리스트 조회
  const listRef = doc(db, &quot;kanban&quot;, listId);
  const listDoc = await transaction.get(listRef);

  // 현재 카드 정보 조회
  const curCard = listDoc.get(&quot;cards&quot;).find((card: Card) =&amp;gt; card.id === id);

  // 변경된 새 카드
  const newCard = { ...curCard, ...values };

  transaction.update(listRef, { cards: arrayRemove(curCard) }); // 현재 카드 삭제
  transaction.update(listRef, { cards: arrayUnion(newCard) }); // 현재 카드 추가

  card = newCard;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  실시간으로 변경된 정보 갖고 오기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 저장소 페이지는 여러 명의 사람이 동시에 작업할 수 있기 때문에 실시간으로 변경되는 정보를 갖고 올 수 있어야 했다. 파이어베이스에는 감사하게도 `&lt;a href=&quot;https://firebase.google.com/docs/firestore/query-data/listen?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;onSnapshot&lt;/b&gt;&lt;/a&gt;`이라는 메서드를 이용해서 실시간으로 변경되는 정보를 갖고 올 수 있다. 그래서 아래와 같이 구현을했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709046720427&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [lists, setLists] = useState&amp;lt;List[]&amp;gt;([]);

// 변경된 리스트/카드 정보 갖고오기
const fetchData = useCallback(() =&amp;gt; {
  const q = query(
    collection(db, &quot;kanban&quot;),
    where(&quot;boardId&quot;, &quot;==&quot;, &quot;ducky&quot;),
    orderBy(&quot;order&quot;, &quot;asc&quot;)
  );
  const unsubscribe = onSnapshot(q, (doc) =&amp;gt; {
    const data = doc.docs.map(
      (item) =&amp;gt; ({ ...item.data(), id: item.id } as List)
    );
    setLists(data);
  });
  return unsubscribe;
}, []);

useEffect(() =&amp;gt; {
  const unsubscribe = fetchData();
  return () =&amp;gt; unsubscribe();
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 일반적인 회사에서는 파이어베이스보단 자체적인 백엔드 서버에 디비를 두고 REST API를 통한 통신을 많이 하기 때문에 이 방법이 아닌 &lt;b&gt;다른 방법으로는 어떻게 할까&lt;/b&gt; 고민을 해봤다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;폴링(polling)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주기적으로 서버에 API를 호출하는 것이다. 예전에 회사에서 새로운 메세지가 왔을 때 표시하기 위해 setInterval로 3초마다 서버에서 데이터를 갖고 오도록 구현한 적이 있다. TanStack 에서는 어떻게 구현하는지 궁금해서 찾아보니 useQuery에 `&lt;b&gt;&lt;a href=&quot;https://tanstack.com/query/v4/docs/framework/react/reference/useQuery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;refetchInterval&lt;/a&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;웹 소켓 방식&lt;/b&gt;도 이용할 수 있을 것 같다. 네트워크 소켓 통신에 대한 내 지식이 짧아서 잘 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⌨️ Input에 한글 입력 시 두 번 전송되는 오류&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코멘트 작성 시 Enter 키를 입력하면 자동으로 코멘트가 생성되도록 구현했다. 하지만 아래 영상을 보면 &lt;b&gt;한글&lt;/b&gt;을 입력하면 &lt;b&gt;두 번 전송&lt;/b&gt;된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;코멘트 한글 입력 오류.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbsCDe/btsFnWXMrAy/smyTP3JcMK0nkz3JmtCgw1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbsCDe/btsFnWXMrAy/smyTP3JcMK0nkz3JmtCgw1/img.gif&quot; data-alt=&quot;한글 입력 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbsCDe/btsFnWXMrAy/smyTP3JcMK0nkz3JmtCgw1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bbsCDe/btsFnWXMrAy/smyTP3JcMK0nkz3JmtCgw1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;405&quot; data-filename=&quot;코멘트 한글 입력 오류.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;405&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;Save 버튼을 눌러 코멘트를 전송하는 것은 문제 없이 한번만 생성되지만 Input 안에서 &lt;b&gt;Keydown Enter로 전송하면 두 번 전송&lt;/b&gt;되었다. 그것도 영어는 말고 한글만. 잠시 이 오류는 Save 버튼으로만 코멘트를 생성할 수 있도록 막아두고(눈감고 ) 다른 페이지 작업을 하고 있었는데 InputTags컴포넌트에서도 한글 입력 시 마지막 글자로 태그가 또 생성되는 문제가 발생했다! &quot;&lt;u&gt;이거 같은 원인이겠구나 고쳐야 겠다&quot;&lt;/u&gt;라고 다짐했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-02-285.31.49-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lRsC9/btsFjHU2oLe/eVKu4tqGWCR4GaokhGyzsK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lRsC9/btsFjHU2oLe/eVKu4tqGWCR4GaokhGyzsK/img.gif&quot; data-alt=&quot;태그 한글 입력 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lRsC9/btsFjHU2oLe/eVKu4tqGWCR4GaokhGyzsK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/lRsC9/btsFjHU2oLe/eVKu4tqGWCR4GaokhGyzsK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;129&quot; data-filename=&quot;2024-02-285.31.49-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;129&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;원인을 찾아보니 React만의 문제는 아니고 &lt;b&gt;자바스크립트의 문제&lt;/b&gt;였다. [&lt;a href=&quot;https://minjung-jeon.github.io/IME-keyCode-229-issue/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;관련 기사&lt;/a&gt;] 한글은 영어와는 다르게 한 글자가 자음과 모음 조합으로 구성되어 있다. 그래서 우리가 한글를 입력했을 때 글자가 끝난 건지 컴퓨터는 알 수 없기 때문에&lt;b&gt; IsComposing (조합중)&lt;/b&gt;이라는 속성 값이 &lt;b&gt;true&lt;/b&gt;로 표시된다. 따라서 keydown 이벤트 발생했을 때 조합 중인 경우 아무것도 처리 하지 말라고 아래와 같이 조건을 추가하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709109441625&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;target.addEventListener(&quot;keydown&quot;, event =&amp;gt; { 
  if (event.isComposing || event.keyCode === 229) return;
  // do something...
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서는 event 객체 안에 isComposing 이라는 값이 없다. 왜 인지 찾아보니, 우리가 JSX 에서 &lt;b&gt;컴포넌트에&amp;nbsp; on[이벤트]로 등록한 이벤트 핸들러는 실제 브라우저의 고유 이벤트와 동일한 동작을 하지만 조금 다르다.&lt;/b&gt; 리액트에서의 이벤트는 &lt;b&gt;합성 이벤트&lt;/b&gt;(&lt;span style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot;&gt;&lt;b&gt;SynthethicEvent&lt;/b&gt;)라고 불리며 이 이벤트들은 &lt;b&gt;이벤트 버블링 단계에서 호출&lt;/b&gt;된다. (&lt;a href=&quot;https://ko.legacy.reactjs.org/docs/events.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;관련 문서&lt;/a&gt;) 즉, SynthethicEvent는 동일한 인터페이스를 갖고 있을 뿐 같은 브라우저 DOM 이벤트와 같은 객체가 아니다! 그래서 브라우저의 돔 이벤트를 막기 위해서는 &lt;b&gt;event.nativeEvent&lt;/b&gt;를 이용하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709110343738&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function CommentInput() {
  const onTextareaKeyDown = (e: React.KeyboardEvent) =&amp;gt; {
    if (e.nativeEvent.isComposing) return;
    // do something...
  }
  
  return (
    &amp;lt;textarea onKeyDown={onTextareaKeyDown} /&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 느낀 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ȸ TanStack&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트나 상세 페이지와 같이 서버에서 갖고 온 데이터를 캐싱하여 사용하면 좋을 곳은 TanStack 쿼리를 이용해서 데이터를 갖고 오도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고  게시물 쪽에는 리스트는 이번에도 useInfiniteQuery를 이용해서 무한 스크롤 방식으로 구현했고,&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;게시물 생성/수정 시 &lt;/span&gt;useMutation를 처음 사용해보았다. 나는 다른 페이지에서는 useMutation이 내가 만든 useAction 커스텀 훅과 같은 기능이기  때문에&amp;nbsp; useMutation 을 쉽게 적용해서 사용할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  리액트  최적화&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이 부분은 사실, 현재 코드에서 더 최적화가 가능한지 모르겠다. 모든 개발자가 고민하는 부분이 아닌가 싶다. 추후에 진짜 프로젝트에 사용해보면서 코드 리뷰 받아보면 좀 더 좋아지지 않을까? &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로딩화면 같은 경우 API 호출할 때 마다 보여지기 때문에 계속 재사용되기에 루트 페이지(레이아웃)에 넣고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;zustand로 전역에서  show/hide 할까 고민 했고, 로딩 컴포넌트를 &lt;a href=&quot;https://ko.legacy.reactjs.org/docs/portals.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;createPortal&lt;/b&gt;&lt;/a&gt;을 이용해서 div#root 밑에 넣어줬다. 그러면서 로딩이 필요한 컴포넌트에서는 로딩 컴포넌트를 추가해서 조작하도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709051357006&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{isLoading &amp;amp;&amp;amp; &amp;lt;LoadingSpinner /&amp;gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;createPortal은 위치만 분리되어 있을 뿐 부모 컴포넌트에 영향을 받지만&lt;/b&gt;, 내 로딩 컴포넌트는 상태나 Prop이 따로 없기에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;부모 컴포넌트가 리랜더링 되더라도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;리렌더링되지 않기에 괜찮다고 생각했다. 물론 리랜더링 방지로 &lt;b&gt;React.memo&lt;/b&gt;도 있지만 createPortal로 선언하면 DOM 구조에서 항상 밑에 보여지므로 더 좋은 선택인 것 같다.&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; &amp;nbsp; 타입스크립트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 CUD(생성,수정,삭제)시 &lt;b&gt;zod&lt;/b&gt;를 통해 &lt;b&gt;타입을 검증&lt;/b&gt;하도록 구현해보았다. 유튜브 보다가 알게된 라이브러리인데, 이걸 사용하면 전달되는 데이터의 &lt;b&gt;유효성 검증&lt;/b&gt;도 가능하기 때문에 API에 전달하기 전 데이터가 올바른지 체크할 수 있어서 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  스타일&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 shadcn/ui 와 tailwindCSS 를 이용하였다. 반응형, 테마에 따른 설정도 해야하기 때문에 클래스가 매우 많이 길어서 JSX 코드를 보기에는 많이 불편하지만 스타일 클래스들에 익숙해지니 크게 신경은 안 쓰였다.(안쓰였다고 생각하자... )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 프로젝트에서는 css moudle도 사용해서 세세한 작업을 진행하였지만 이번에는 TailwindCSS만으로 해결했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 프로젝트에 비해 좀 많이 커진 블로그 웹사이트다 보니 시간이 오래 걸릴 줄 알았는데, 라이브러리를 많이 사용해서 그런지 생각보다 시간이 많이 투자되진 않았다. 웹에 대한 경험은 있기에 &quot;이렇게 구현하면 될 것 같은데, 무슨 메소드를 사용하면 되지?&quot;, &quot;이 명령어 쓰면 될 것 같은데 어떻게 썼었지?&quot; 하고 구글링을 많이 해보는 편이지만  해법을 찾는데 시간이 그리 많이 소요되는 편은 아닌 것 같다. 물론 방법을 못찾아서 우회하기도 한다. 그러면서 커가는게 아닐까...? &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;또 다른 프로젝트를 한다면 그때는 Spring Boot 를 이용해서 REST API를 만들고 프론트로는 React를 사용하는 형식의 프로젝트를 진행해 봐야겠다. 리액트를 배워가면서 점점 자바가 머릿속에서 잊혀져가는 중이다...  &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;  잘한 점 3가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이어베이스 &lt;b&gt;인증&lt;/b&gt;부분까지 경험 해봤다.  &lt;/li&gt;
&lt;li&gt;구현 방식에 대해 좀 더 다양하게 생각해보려고 노력했다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Theme 변경 기능을 위해 next/useTheme 훅을 분석 후 useTheme 커스텀 훅을 만들어서 적용한 점.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;시스템 테마를 기본으로 적용하고 LocalStorage에 저장하도록 했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  아쉬운 점 2가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 코드&lt;/b&gt;를 작성하면서 개발하지 않은 점. ( 아 이번에도 못했다  )&lt;/li&gt;
&lt;li&gt;한 메서드에 너무 많은 기능을 넣은 것 같다. if-else 너무 사용한 것 같아 복잡해 보인다. 변수/메서드 명 잘 짓자! - 너무 클린하지 못한 코드  &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;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;제가 작성한 코드에 코드 리뷰(평가)도  받고 싶은데 언제든지 제 &lt;a href=&quot;https://github.com/jiyeon-dev/ducky&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃헙&lt;/a&gt;에 남겨주세요! 환영입니다!  &lt;/blockquote&gt;</description>
      <category>WEB/React</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/201</guid>
      <comments>https://itinerant.tistory.com/201#entry201comment</comments>
      <pubDate>Wed, 28 Feb 2024 02:02:20 +0900</pubDate>
    </item>
    <item>
      <title>토이 프로젝트 [Vocab] 회고</title>
      <link>https://itinerant.tistory.com/200</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로 전향하기 위해 최근 리액트를 배우고 있다. &lt;a href=&quot;https://m.yes24.com/Goods/Detail/62597469&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[리액트를 다루는 기술]&lt;/a&gt;이라는 책 한권을 완독했고, 게시판 만들기보다 더 재미난걸 만들어 보고 싶어 고민하다가 &lt;a href=&quot;https://react-vocab.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;u&gt;&lt;b&gt;단어장&lt;/b&gt;&lt;/u&gt;&lt;/a&gt;이라는 웹을 만들어 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 알지만 리액트뿐만 아니라 처음 접하는 라이브러리가 많았기 때문에 원하는 걸 구현하려면 어떻게 사용하면 되는지 문서를 찾느라 고생했다. 그래서 코딩하는 시간보다 구글링하는 시간이 더 많았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 기간은 총 &lt;b&gt;7일&lt;/b&gt; 걸렸다(첫 커밋 ~ 마지막 커밋).&amp;nbsp; 순수 개발시간은 아마 30시간정도 되지 않을까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 구상&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게시판처럼 리스트 조회/수정/삭제가 있으면서 간단한 걸 만들어 보고 싶었다.&amp;nbsp;초기 UI 구상은 피그마를 사용하면 참 좋겠지만 할 줄 모르니... 아래와 같이 종이에 끄적거렸다. 처음엔 이랬다는 것을 기록에 남기고자 종이를 버리기 전에 찍어 두었다. 다시 보니 결과물이랑 완전히 다르진 않은 것 같다.  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCsVFe/btsE0jUfPXd/gKThBL6OuPQ1nzXI1a5uJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCsVFe/btsE0jUfPXd/gKThBL6OuPQ1nzXI1a5uJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCsVFe/btsE0jUfPXd/gKThBL6OuPQ1nzXI1a5uJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCsVFe%2FbtsE0jUfPXd%2FgKThBL6OuPQ1nzXI1a5uJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;403&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;403&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 고충&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;크게 기억나는 몇 가지만 적어보았다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Firebase&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;워낙 간단한 프로젝트라서 SpringBoot로 REST-API를 금방 만들 수는 있지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프론트에 집중을 한 프로젝트이기 때문에 이번에 파이어베이스를 처음 사용해보기로 했다. 덕분에 파이어베스 사용방법 찾느라 전체 개발시간의 50%는 쓴 것 같다 ㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;총평&lt;/b&gt;: 백엔드 서버/디비 개발 없이 개발을 쉽게 해주기 때문에 API는 고민할 필요가 없어 거부감 없고 간단하긴 했지만, 웹에서는 지원안하는 기능이 많았다. 그래서 많이 아쉬웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) Firestore 생성 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작부터 대 난관이었다. Firestore Database를 생성하려는데 자꾸 알수 없는 오류가 생겼다면서 생성이 안되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링해보니 계속 &quot;만들기&quot; 누르면 된다던데 저는 안되고... 새로고침하고 다시 시도해도 안되고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;진짜 무슨 신 이 그냥 프론트 개발자 하지 말라고 계시를 주는줄 알았다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca3Hh2/btsE0JkTjLK/bhd25VNBXcqE3vRKUO3ly1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca3Hh2/btsE0JkTjLK/bhd25VNBXcqE3vRKUO3ly1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca3Hh2/btsE0JkTjLK/bhd25VNBXcqE3vRKUO3ly1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca3Hh2%2FbtsE0JkTjLK%2Fbhd25VNBXcqE3vRKUO3ly1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;562&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㅋㅋㅋㅋㅋㅋㅋㅋㅋ 몇분째 난리 치다가 크롬 캐시 삭제해볼까 했는데 떠 있는 탭들을 닫으면 다시 찾기 힘드니 그냥 &lt;b&gt;&lt;u&gt;사파리로 접속해보니 생성&lt;/u&gt;&lt;/b&gt;되어 있었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) Firestore 하위 컬랙션 문제&lt;/b&gt;&lt;/h4&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDGkVZ/btsEQToqZxT/mHPhhvVA859hXAwMn6B710/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDGkVZ/btsEQToqZxT/mHPhhvVA859hXAwMn6B710/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDGkVZ/btsEQToqZxT/mHPhhvVA859hXAwMn6B710/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDGkVZ%2FbtsEQToqZxT%2FmHPhhvVA859hXAwMn6B710%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;162&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 category안에 chapter 컬랙션을 생성하고, chapter 컬랙션 안에 vocabulary 컬랙션을 추가해서 관리하면 되겠다 싶었고, 아래와 같이 초기 데이터를 넣어줬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otAvV/btsE1CstdJf/eIoErndsjkkrzS4ZmhX74k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otAvV/btsE1CstdJf/eIoErndsjkkrzS4ZmhX74k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otAvV/btsE1CstdJf/eIoErndsjkkrzS4ZmhX74k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotAvV%2FbtsE1CstdJf%2FeIoErndsjkkrzS4ZmhX74k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1124&quot; height=&quot;470&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;335&quot;&gt;&lt;a href=&quot;https://firebase.google.com/docs/firestore/query-data/get-data?_gl=1*97wmsk*_up*MQ..*_ga*MTU1Mjc2NC4xNzA3OTg1MzEw*_ga_CW55HF8NVT*MTcwNzk4NTMxMC4xLjAuMTcwNzk4NTMxMC4wLjAuMA..#java_17&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VPj7r/btsEVjlBRfn/ZRTdbhjqrtZ8QmHCI3wgh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVPj7r%2FbtsEVjlBRfn%2FZRTdbhjqrtZ8QmHCI3wgh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;878&quot; height=&quot;335&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/a&gt;&lt;figcaption&gt;이미지 클릭시 해당 문서로 이동합니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqDk4/btsE7vFBeki/dsQ7XPhdaSEcK0jjBfB0e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqDk4/btsE7vFBeki/dsQ7XPhdaSEcK0jjBfB0e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqDk4/btsE7vFBeki/dsQ7XPhdaSEcK0jjBfB0e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqDk4%2FbtsE7vFBeki%2FdsQ7XPhdaSEcK0jjBfB0e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1156&quot; height=&quot;373&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) Firestore 조건 검색과 정렬&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/firestore/docs/query-data/order-limit-data?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;데이터 정렬 및 제한&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회할 때 조건 검색하고 결과에 대한 정렬(orderBy)도 하고 싶었는데, 이와같이 orderBy를 두 번 써봤지만 이것도 안된다. (where절을 두번 써도 안된다... ㅎ)&lt;/p&gt;
&lt;pre id=&quot;code_1707993944040&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const q = query(
  collection(db, &quot;chapter&quot;),
  where(&quot;categoryId&quot;, &quot;==&quot;, categoryId),
  orderBy(&quot;categoryId&quot;, &quot;asc&quot;),
  orderBy(&quot;order&quot;, &quot;asc&quot;)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 반환되는 양이 많거나 무겁지는 않으니 데이터를 where절로 조건 검색 후 따로 sort()를 이용해서 정렬하는 방식으로 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;( 2/25 추가 글 &amp;gt; &lt;b&gt;색인&lt;/b&gt;을 사용하면 된다... ㅎ )&lt;/u&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; 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;Vue2 를 사용해본 사람으로써, 자식에서 부모컴포넌트로 데이터를 전송할 때 진짜 참 쉬웠는데... 리액트는 부모에서 자식으로만 속성 값을 전달할 수 있다보니 처음엔 어떻게 해야하나 고민이 많았었다.&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;헤더에 있는 메뉴나 다크모드와 같은 props로 넘기기 불편하거나 광범위하게 사용하는 데이터들은&amp;nbsp;&lt;b&gt;저장소&lt;/b&gt;를 사용했다. 이번 프로젝트에서는 &lt;b&gt;Context API&lt;/b&gt;를 이용했다. Main 파일에 Context 등록이 여러 개 되어 있어 컨텍스트 안에 컨텍스트 안에... 반복되다보니 &lt;b&gt;Redux&lt;/b&gt;와 비슷한데 사용이 간단한 &lt;a href=&quot;https://docs.pmnd.rs/zustand/getting-started/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Zustand&lt;/b&gt;&lt;/a&gt;로 바꾸려고 했다. 하지만 두가지 이유로 변경하지 않았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FireStore만으로도 벅찬데 Zustand를 검색하면서 하려니 벅참.  &lt;/li&gt;
&lt;li&gt;상태값 바꾸니 구독하고 있는 모든 컴포넌트가 다 재랜더링되는 문제가 발생. &amp;rarr; 이건 나중에 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;&lt;a href=&quot;https://github.com/pmndrs/zustand?tab=readme-ov-file#using-subscribe-with-selector&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Selector&lt;/a&gt;를 이용하면 해결 된다는 걸 알았다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #212529;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;가장 친숙한 걸로 만들었다는 점이 아쉽긴하다. 그래서 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;다음 토이 프로젝트때는 &lt;b&gt;Zustand&lt;/b&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  리액트  최적화&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;b&gt;총평:&lt;/b&gt; 최적화관련해서는 그 라이브러리(또는 언어)에 대해 좀더 깊게 공부해야하기 때문에 어느 언어든지 간에 어려운 것 같다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) useEffect&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이프사이클은 이해했고, useEffect 를 통해 마운트된 후 작업을 할 수 있으며, 두 번째 인자를 이용해서 언제 실행될지 설정가능하다는 것까지 알았다. 하지만 useEffect 를 사용하는게 공짜가 아닐 뿐더러 잘못 구독하고 있는 경우 불필요한 랜더링을 발생하기 때문에 useEffect 사용하는데 고민이 많았다. &quot;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 기능을 구현하는데&lt;span&gt; &lt;/span&gt;&lt;/span&gt;useEffect를 사용하지 않고 구현할 수 있는 방법이 있을까?&quot;를 먼저 고민하고 사용하도록 했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) useCallback, useMemo, React.memo&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것들 또한 남용 시 오히려 성능을 저하시키고 메모리를 잡아먹기 때문에 고민이 많았다. &lt;b&gt;&quot;과도한 남용&quot;이 어디까지인지&lt;/b&gt;를 모르겠어서 고민이 많았다. 물론 매우 작은 프로젝트라 사용하든지 말든지 성능의 차이는 크게 차이나진 않지만 사용해보고 싶었고,&amp;nbsp; 이 프로젝트에 useMemo 한 번 사용했다. 그리고 useCallback 사용하기보다 굳이 재랜더링이 필요 없는 함수들은 컴포넌트 함수 밖에 선언했다.&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; &amp;nbsp; 타입스크립트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트도 처음 사용해 본 것이었는데, 리액트에 타입스크립트 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;함께 &lt;/span&gt;많이 사용하기 때문에 간단하게 영상 몇 개랑 문서를 보고 프로젝트를 시작했다. 전달되는 값이나 리턴되는 값의 타입이 무엇인지는 알긴 하는데 리액트 함수들이나 firestore 메소드들에 대한 리턴 값을 잘 모르겠어서 힘들었다. 물론 any로 다 때려박으면 되지만 그러면 타입스크립트를 하는 이유가 없어지기에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;타입을 찾기 위해 노력했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;타입스크립트를 쓰니 잘못 선언하면 빌드 시 오류나서 귀찮긴 했지만 특히 객체 같은 경우 안에 속한 키들이 자동으로 보이기 때문에 좋긴 했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;이 프로젝트에 타입스크립트가 완벽히 타입을 잘 맞춰했다고는 장담하지 못한다. 그렇기 때문에 다음 프로젝트도 &lt;b&gt;계속해서 타입스크립트를 사용해보도록&lt;/b&gt; 할 것이다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&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 style=&quot;color: #000000; text-align: start;&quot; 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;나는 (다행히...) CSS(SCSS)에 대한 어느 정도 기본 지식은 있었고 styled-Components와 tailwindCSS 둘 중 고민했는데, 작년에 인기있었던 &lt;a href=&quot;https://ui.shadcn.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;shadcn/ui&lt;/b&gt;&lt;/a&gt;를 사용해보고 싶어서 &lt;b&gt;tailwindCSS&lt;/b&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;클래스가 무진장 길어짐. &amp;rarr; 그래서 ui 컴포넌트로 따로 분리하게 됨.&lt;/li&gt;
&lt;li&gt;상세(세세)한 설정이 어렵다. &amp;rarr; 원하는 크기로 수정이 불편하다. 애니메이션도 불편했다.&lt;/li&gt;
&lt;li&gt;속성 찾느라 시간 다감. &amp;rarr; 다행히 어떤 css를 사용하면 된다는 건 알았기 때문에 검색은 쉬웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 중간에 styledComponent도 같이 사용할까 고민 많이 했지만... 몇 개만 css module로 해결하고 나머지는 tailwindCSS로 다 했다. (만세)  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;# 결론&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;아쉬운 점이 더 많지만 처음이기 때문이라고 자기 위안하기로 했다.   다음 토이 프로젝트엔 더 다양하게 적용보고 더 깊게 배울 수 있도록 해야겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  잘한 점 2가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 라이브러리를 많이 사용했는데 &lt;b&gt;원하는 대로 구현&lt;/b&gt;을 다 한 점. ( 물론 결과보다 과정이 중요하긴 하지만... )&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반응형&lt;/b&gt;으로 작업한 점.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  아쉬운 점 4가지&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;테스트 코드&lt;/b&gt;를 작성하면서 개발하지 않은 점. ( 테스트 코드 작성하는 법은 배우긴 했는데 실제 적용하기가 너무 어려웠다. )&lt;/li&gt;
&lt;li&gt;react-router-dom과 tanstack(react-query)를 좀 더 활용해보고 싶었는데 못했다. ( loader, action, mutate 등 )&lt;/li&gt;
&lt;li&gt;context API 만 사용한 점...&lt;/li&gt;
&lt;li&gt;firebase 인증 기능을 사용하지 않은 점 ( 로그인 기능을 구현하지 않았기 때문에 사용하지 않았다. )&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;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;제가 작성한 코드에 코드 리뷰(평가)도  받고 싶은데 언제든지 제 &lt;a href=&quot;https://github.com/jiyeon-dev/vocab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃헙&lt;/a&gt;에 남겨주세요! 환영입니다!  &lt;/blockquote&gt;</description>
      <category>WEB/React</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/200</guid>
      <comments>https://itinerant.tistory.com/200#entry200comment</comments>
      <pubDate>Tue, 20 Feb 2024 04:10:58 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] MacOS ventura 13 에서 MySQL Workbench 가 실행되지 않을때 해결 방법</title>
      <link>https://itinerant.tistory.com/199</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[MySQL]&amp;nbsp;MacOS&amp;nbsp;ventura&amp;nbsp;13&amp;nbsp;에서&amp;nbsp;MySQL&amp;nbsp;Workbench&amp;nbsp;가&amp;nbsp;실행되지&amp;nbsp;않을때&amp;nbsp;해결&amp;nbsp;방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존에 workbench 는 몇 버전이었는지 기억은 안납니다.&lt;/span&gt;&lt;br /&gt;원래 업데이트 같은거 잘 안하는 스타일인데,,,,,&lt;br /&gt;테스트를 위해 ios 시뮬레이터 돌릴려고, xcode 설치를 위해 mac os 업데이트 했다가&amp;nbsp;&lt;br /&gt;workbench 실행 안되서 2시간 날린... &lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ventura 13 으로 macOS 업데이트 후 기존에 설치되어 있던 Workbench 를 실행하면 &quot;&lt;b&gt;MySQLWorkbench 응용 프로그램이 예기치 않게 종료되었습니다.&lt;/b&gt;&quot; 메세지가 나오면서 Workbench 가 실행되지 않았습니다. 무시해도 계속 무한반복으로 떴습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 메세지를 구글링해보니 2021년 글 기준 MySQL 8.0.20 또는 MySQL 8.0.22 버전을 다운받으면 된다고 하던데 전 안되더라구요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcCSwj/btrWCHjOgHD/jwIcYnFIgjaOacwceXgN3k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcCSwj/btrWCHjOgHD/jwIcYnFIgjaOacwceXgN3k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcCSwj/btrWCHjOgHD/jwIcYnFIgjaOacwceXgN3k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcCSwj%2FbtrWCHjOgHD%2FjwIcYnFIgjaOacwceXgN3k%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;274&quot; height=&quot;184&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;184&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;그래서 이건 mac os 버전의 문제인 것 같아서 macOS 버전과 함께 구글링을 해보니 해당 버전에 맞는 MySQL 를 설치하는게 답이더라구요! 그래서 진행과정을 공유합니다.&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;기존 워크 벤치 삭제&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이전 mac 버전이었을 때 설치했던 workbench 를 삭제해 줍니다.&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_1674094725173&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ rm -rf ~/Library/Application Support/MySQL/Workbench&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;[참고] &lt;a href=&quot;https://dev.mysql.com/doc/workbench/en/wb-mac-uninstalling.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/doc/workbench/en/wb-mac-uninstalling.html&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;MySQL Community 설치&lt;/b&gt;&lt;/span&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;a href=&quot;https://dev.mysql.com/downloads/mysql/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dev.mysql.com/downloads/mysql/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL Community Server 를 설치합니다. 현재 macOS 버전에 맞는 MySQL Community 를 설치해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Ventura 13&lt;/span&gt;&amp;nbsp;이기 때문에 macOS 13 버전을 다운받아주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uTKRd/btrWEQmh5Lx/8TrGzQkzAGYYVyWbkDnVt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uTKRd/btrWEQmh5Lx/8TrGzQkzAGYYVyWbkDnVt1/img.png&quot; data-alt=&quot;MySQL Community Server 8.0.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uTKRd/btrWEQmh5Lx/8TrGzQkzAGYYVyWbkDnVt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuTKRd%2FbtrWEQmh5Lx%2F8TrGzQkzAGYYVyWbkDnVt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;739&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MySQL Community Server 8.0.32&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;MySQL Community Server 설치 페이지에 보면 macOS 버전을 선택하는 옵션이 따로 있는 것을 보면,&lt;br /&gt;MacOS 에 맞춰서 Community Server 재설치만 했다면, Workbench 를 삭제하지 않아도 실행이 가능했을 수도 있습니다.&lt;br /&gt;( 이미 기존 Workbench 를 삭제한 후라서 확인은 안해봤기 때문에 추측입니다. ㅎ )&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Download 누르면 로그인하거나 회원가입하라고 나오는데, 하단에 &quot;&lt;b&gt;No thanks, just start my download&lt;/b&gt;&quot; 누르면 가입 및 로그인 없이 다운가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mv6f5/btrWHoCU8h5/oEzUrzcKSO9NimryobtWsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mv6f5/btrWHoCU8h5/oEzUrzcKSO9NimryobtWsK/img.png&quot; data-alt=&quot;No thanks,&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mv6f5/btrWHoCU8h5/oEzUrzcKSO9NimryobtWsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmv6f5%2FbtrWHoCU8h5%2FoEzUrzcKSO9NimryobtWsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;318&quot; data-origin-width=&quot;851&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;No thanks,&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;MySQL Workbench 설치&lt;/b&gt;&lt;/span&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;span&gt; &lt;a href=&quot;https://downloads.mysql.com/archives/workbench/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://downloads.mysql.com/archives/workbench/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL Workbench 를 설치합니다. &lt;u&gt;현재(2023.01.19)기준 8.0.32 버전&lt;/u&gt;이 가장 최신 버전인데, 해당 버전으로 설치했더니 workbench 를 열거나 접속등은 잘 되는데, 가장 중요한 &lt;u&gt;SELECT 를 실행하면 Workbench가 강제 종료&lt;/u&gt;되어 버립니다  &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AgOCn/btrWELk3ZR4/uhKfGj10vWTSoAbPmHi391/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AgOCn/btrWELk3ZR4/uhKfGj10vWTSoAbPmHi391/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AgOCn/btrWELk3ZR4/uhKfGj10vWTSoAbPmHi391/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAgOCn%2FbtrWELk3ZR4%2FuhKfGj10vWTSoAbPmHi391%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;227&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;398&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: #006dd7;&quot;&gt;&lt;b&gt;8.0.30 버전&lt;/b&gt;&lt;/span&gt;을 설치 했습니다. ( 해당 버전은 아직까진 정상적으로 잘 작동합니다. )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1499&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkNO6/btrWDcRe93k/Ojn5ff7kQE5CThy6t4gLE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkNO6/btrWDcRe93k/Ojn5ff7kQE5CThy6t4gLE0/img.png&quot; data-alt=&quot;MySQL Workbench 8.0.30&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkNO6/btrWDcRe93k/Ojn5ff7kQE5CThy6t4gLE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkNO6%2FbtrWDcRe93k%2FOjn5ff7kQE5CThy6t4gLE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1499&quot; height=&quot;591&quot; data-origin-width=&quot;1499&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MySQL Workbench 8.0.30&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;이제 다운 받은 dmg 파일 열면, &quot;&lt;b&gt;'MySQLWorkbench'은(는) Apple에서 악성 소프트웨어가 있는지 확인할 수 없기 때문에 열 수 없습니다&lt;/b&gt;&quot; 라는 메세지가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1F4e/btrWH09Mi6a/7KtDP1C8921rVumBBVjsg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1F4e/btrWH09Mi6a/7KtDP1C8921rVumBBVjsg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1F4e/btrWH09Mi6a/7KtDP1C8921rVumBBVjsg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1F4e%2FbtrWH09Mi6a%2F7KtDP1C8921rVumBBVjsg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;320&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;632&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;rarr; 개인정보 보호 및 보안` 에서 스크롤 내리다 보면 MySQL~ 쓰여있는 부분이 있습니다. Open Anyway 누르고 비밀번호 입력하면됩니다. ( 이미지는 제가 깜빡하고 캡쳐를 못해서, 다른 곳에서 캡쳐했습니다. )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWdZUS/btrWD5qDaDQ/r0Q5QEuC9uxyXAFEa96Bo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWdZUS/btrWD5qDaDQ/r0Q5QEuC9uxyXAFEa96Bo1/img.png&quot; data-alt=&quot;이미지 출처: https://www.youtube.com/watch?v=kXz3aWqMFtE&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWdZUS/btrWD5qDaDQ/r0Q5QEuC9uxyXAFEa96Bo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWdZUS%2FbtrWD5qDaDQ%2Fr0Q5QEuC9uxyXAFEa96Bo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;398&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처: https://www.youtube.com/watch?v=kXz3aWqMFtE&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;(2024.03.01 추가 작성)&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  이제&amp;nbsp; 최신 버전도 실행됩니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 고물 맥북은 Ventura 버전이고, MySQL 쓸 일이 없어 삭제했다가, 오늘, 가장 최신 버전인 &lt;b&gt;8.0.36&lt;/b&gt;을 다운 받아봤는데, 되요!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 안되시는 분들은 &lt;b&gt;mysql server&lt;/b&gt;는 설치했는 지 확인해 보시고 안하셨으면 추가로 설치하시면 됩니다!&lt;/p&gt;
&lt;pre id=&quot;code_1709288994730&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 터미널에서 mysql 입력하여 mysql server 설치되어 있는지 확인
$ mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 되어 있다면 &quot;ERROR 1045 (28000): Access denied for user '사용자 아이디'@'localhost' (using password: NO)&quot; 로 표시 됩니다. 만약, 설치가 안되어 있다면 공식 홈페이지에서도 설치 가능하지만 &lt;b&gt;brew&lt;/b&gt;로도&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;간단하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;설치 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 다만 워크벤치와 MySQL Server 버전이 안맞아서 워크벤치에서 경고 메세지가 나타날 수 있습니다. )&lt;/p&gt;
&lt;pre id=&quot;code_1709289105176&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ brew install mysql&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;저는 원인이 컴퓨터 껏다 키면 &lt;b&gt;mysql 서버가 죽어서&lt;/b&gt;, mysql workbench 실행해서 서버 접속하려 하면 튕기더라구요!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mysql 서버를 재실행&lt;/b&gt;해주세요. 아래 둘 중 하나 명령어 입력하시면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1709390290293&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// mysql 서버 시작
$ mysql.server start

// mysql 서버 재시작
$ mysql.server restart&lt;/code&gt;&lt;/pre&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;/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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;912&quot;&gt;&lt;a href=&quot;https://forums.mysql.com/read.php?152,707242,717627#msg-717627&quot; target=&quot;_blank&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m9Ux8/btsFsclEVBt/kXOnfzDC3MKz6ddFnBSOkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm9Ux8%2FbtsFsclEVBt%2FkXOnfzDC3MKz6ddFnBSOkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1562&quot; height=&quot;912&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;912&quot;/&gt;&lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DataBase/MySQL</category>
      <category>13</category>
      <category>8.0.20</category>
      <category>8.0.22</category>
      <category>db</category>
      <category>mysql</category>
      <category>Ventura</category>
      <category>Workbench</category>
      <category>워크벤치</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/199</guid>
      <comments>https://itinerant.tistory.com/199#entry199comment</comments>
      <pubDate>Thu, 19 Jan 2023 11:58:22 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Vue-cli 와 webpack-bundle-analyzer</title>
      <link>https://itinerant.tistory.com/193</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Vue-cli 와 webpack-bundle-analyzer&lt;/b&gt;&lt;/h2&gt;
&lt;hr style=&quot;margin: 20px auto 0px; border: none; cursor: pointer !important; z-index: 1; font-size: 0px; line-height: 0; background: url('../image/divider-line.svg') center -304px / 200px 420px no-repeat; width: 200px; height: 19px; padding: 18px 20px 17px;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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; &amp;zwj;  webpack-bundle-analyzer&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/webpack-bundle-analyzer&quot;&gt;&lt;b&gt;webpack-bundle-analyzer&lt;/b&gt;&lt;/a&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;700&quot; height=&quot;341&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;935&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFJezP/btq7SLh9A49/r2QMqROZiD6uQOA7LPjRt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFJezP/btq7SLh9A49/r2QMqROZiD6uQOA7LPjRt0/img.png&quot; data-alt=&quot;webpack-bundle-analyzer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFJezP/btq7SLh9A49/r2QMqROZiD6uQOA7LPjRt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFJezP%2Fbtq7SLh9A49%2Fr2QMqROZiD6uQOA7LPjRt0%2Fimg.png&quot; width=&quot;700&quot; height=&quot;341&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;935&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;webpack-bundle-analyzer&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;⛏️&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;설정하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 build 를 하게되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자동으로 실행&lt;/b&gt;되며, pluginOptions.webpackBundleAnalyzer에 옵션을 추가할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1624360809363&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// vue.config.js
pluginOptions: {
    webpackBundleAnalyzer: {
        // 이곳에 webpack-bundle-analyzer 옵션을 추가하면 된다.
        analyzerMode: &quot;static&quot;  // 결과를 파일로 저장
    }
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일은 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;dist/report.html&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue-cli-service build 옵션 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;--report&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;옵션을 사용하면 analyzerMode: &quot;static&quot; 옵션을 사용하지 않고도 결과를 파일로 저장할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1624360809363&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm run build --report&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고: vue-cli-service-build]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build&quot;&gt;https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고: webpack-bundle-analyzer 옵션]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin&quot;&gt;https://github.com/webpack-contrib/webpack-bundle-analyzer#options-for-plugin&lt;/a&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 analyzerMode를 disabled 하여 프로덕션 빌드시에는 분석을 실행하지 않고, build-report 옵션을 이용했을 때에만 실행하도록 설정해서 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1624360809364&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// package.json
{
  &quot;scripts&quot;: {
    &quot;serve&quot;: &quot;vue-cli-service serve&quot;,
    &quot;build&quot;: &quot;vue-cli-service build&quot;,
    &quot;build:report&quot;: &quot;vue-cli-service build --report&quot;  // 추가
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624360809364&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm run build:report&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;</description>
      <category>WEB/Vue.js</category>
      <category>analyzerMode</category>
      <category>build</category>
      <category>vue</category>
      <category>vue-cli</category>
      <category>vue3</category>
      <category>Webpack</category>
      <category>webpack-bundle-analyzer</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/193</guid>
      <comments>https://itinerant.tistory.com/193#entry193comment</comments>
      <pubDate>Wed, 23 Jun 2021 01:24:50 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Vue.js 에서 css variable 사용하기</title>
      <link>https://itinerant.tistory.com/192</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Vue.js 에서 css variable 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;단순히 공통 속성을 사용하기 위해서는 css variable이 아닌 IE에서도 사용 가능한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sass-lang.com/documentation/variables&quot;&gt;scss(sass)를 통한 공통 속성을 정의&lt;/a&gt;하여 재사용하는 것이 좋으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue class 또는 style 바인딩을 통해서도 충분히 구현 가능한 부분이기에 vue style binding에 css variable 연결하는 방식은 그냥 이런 방법도 있다는 것을 인지만 하는 것이 좋을 것 같다.&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;CSS Variable&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;css variable&lt;/b&gt;&lt;/span&gt;은 style css 내에 &lt;b&gt;사용자 속성을 지정하여 재사용 가능하도록 하는 객체&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재, &lt;u&gt;&lt;b&gt;IE는 지원하지 않기&lt;/b&gt;&lt;/u&gt; 때문에 IE를 지원하는 사이트를 개발 중이라면 사용에 주의해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`&lt;a href=&quot;https://www.npmjs.com/package/css-vars-ponyfill&quot;&gt;&lt;b&gt;css-vars-ponyfill&lt;/b&gt;&lt;/a&gt;` 라이브러리를 이용하면 IE에서도 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;478&quot; width=&quot;700&quot; height=&quot;361&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIGaAM/btq7Ovs2zju/fJywqU2MbSf3ZCUokjAAtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIGaAM/btq7Ovs2zju/fJywqU2MbSf3ZCUokjAAtK/img.png&quot; data-alt=&quot;css variable 브라우저 호환성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIGaAM/btq7Ovs2zju/fJywqU2MbSf3ZCUokjAAtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIGaAM%2Fbtq7Ovs2zju%2FfJywqU2MbSf3ZCUokjAAtK%2Fimg.png&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;478&quot; width=&quot;700&quot; height=&quot;361&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;css variable 브라우저 호환성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고]&amp;nbsp;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties&quot;&gt;https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties&lt;/a&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;span&gt;⛏️&amp;nbsp;&lt;/span&gt;&lt;b&gt;사용방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/CSS/:root&quot;&gt;:root &lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&amp;nbsp;안에 사용할 속성을 선언하여 HTML의 루트인 &amp;lt;html&amp;gt;태그 내 어디서든 사용 가능하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 :root(루트)에 선언 안해도 되며, 속성 이름은 &lt;b&gt;대소문자를 구분&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1624332912913&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:root {
  --primary-color: blue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용을 할 때는&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt; var()&lt;/b&gt;&amp;nbsp;&lt;/span&gt; 함수 안에 사용자 속성 값을 넣어주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1624333274007&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;span {
  background-color: var(--primary-color);
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span&gt;⭐&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;Vue 에서 Style 바인딩 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue에서 style을 바인딩하는 방법은 클래스 바인딩과 마찬가지로 `v-bind`에 &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;size18&quot;&gt;&lt;b&gt;1) 객체(Object) 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1624333651949&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :style=&quot;styleObject&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624333666389&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 배열(Array) 방식&lt;/b&gt; ( &amp;rArr; 대부분 사용 안함)&lt;/p&gt;
&lt;pre id=&quot;code_1624333901786&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :style=&quot;[baseStyles]&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1624333942050&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data: {
  baseStyles: {
    color: 'red',
    fontSize: '13px'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://vuejs.org/v2/guide/class-and-style.html#Binding-Inline-Styles&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vuejs.org/v2/guide/class-and-style.html#Binding-Inline-Styles&lt;/a&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;⭐ &lt;/span&gt;Vue 에서 Css Variable 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 소개한 css variable 방식을 사용할 수도 있지만, &lt;b&gt;vue style bind&lt;/b&gt;를 이용하면 사용자 속성을 style스크립트 안이 아닌 &lt;b&gt;vue script 안에서 선언한 값을 style 에서 사용&lt;/b&gt; 할 수 있다!!&lt;/p&gt;
&lt;pre id=&quot;code_1624337940059&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :style=&quot;cssVariable&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 속성 이름은 `--`을 붙이지 않아도 되지만,&amp;nbsp; 이름을 css 명령어로 명명한 경우 vue style을 바인딩도 해버리기 때문에 조심해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1624337976361&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;computed: {
    cssVariable() {
      return {
        &quot;--color&quot;: &quot;blue&quot;,
        &quot;font-weight&quot;: &quot;bold&quot;,  // css 명령어를 속성명으로 지정한 경우
        fontSize: &quot;20px&quot;,
      };
    },
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 속성명 중간에 대문자 있는 경우 하이픈(-)으로 변경 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1624338022254&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div {
  color: var(--color);
  font-size: var(fontsize);  /* 또는 var(font-size); */
  
  /* 선언하지 않아도 css 명령어기 때문에 vue style 바인딩된다. */
  /* font-weight: var(font-weight); */ 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codesandbox.io/s/vue2-css-variable-wsxcm?file=/src/components/CssVariableStyle.vue&quot;&gt;예제 코드&lt;/a&gt;&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;&lt;span&gt;  &lt;/span&gt;장점&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;계산된 결과 값 적용하는 경우&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃을 계산하기 위해 수학을 사용하는 경우 css 변수에서 숨기는 것이 좋다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 스타일에서 고정 수치 값을 사용하는 것이 아닌 &lt;b&gt;주위의 요소에 영향을 많이 받는(동적) 스타일을 지정&lt;/b&gt;한다면 자바스크립트 내에서 document.getElementById 와 같은 요소 선택자를 통한 스타일 지정이 아닌, css-variable + vue style 바인딩을 이용해서 계산 값을 지정해 주는 방법이 훨씬 효율 적일 것이라 생각한다.&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;size14&quot;&gt;[참고]&amp;nbsp;&lt;a href=&quot;https://shayneo.com/blog/binding-css-variables-with-vue/&quot;&gt;https://shayneo.com/blog/binding-css-variables-with-vue/&lt;/a&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>WEB/Vue.js</category>
      <category>Binding</category>
      <category>CSS</category>
      <category>css variable</category>
      <category>Style</category>
      <category>style 바인딩</category>
      <category>variable</category>
      <category>vue</category>
      <category>바인딩</category>
      <category>스타일</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/192</guid>
      <comments>https://itinerant.tistory.com/192#entry192comment</comments>
      <pubDate>Tue, 22 Jun 2021 20:51:15 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Vue-cli 빌드 후 로컬에서 실행하기</title>
      <link>https://itinerant.tistory.com/191</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Vue-cli 빌드 후 로컬에서 실행하기&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue-cli spa 프로젝트를 &lt;span style=&quot;letter-spacing: 0px;&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;&amp;nbsp;&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;1. 프로젝트 빌드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623638701731&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm run build
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 프로젝트 실행&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1623638744123&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ npm install -g serve

$ serve -s dist&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://cli.vuejs.org/guide/deployment.html#previewing-locally&quot;&gt;https://cli.vuejs.org/guide/deployment.html#previewing-locally&lt;/a&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;  ERROR 발생시 해결&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. error Unexpected console statement no-console&lt;/h4&gt;
&lt;pre id=&quot;code_1623638593527&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// vue.config.js
module.exports = {
  devServer: {
    overlay: false // remove `error  Unexpected console statement  no-console`
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://www.inflearn.com/questions/15993&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.inflearn.com/questions/15993&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; 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;</description>
      <category>WEB/Vue.js</category>
      <category>build</category>
      <category>no-console</category>
      <category>Serve</category>
      <category>vue</category>
      <category>vue-cli</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/191</guid>
      <comments>https://itinerant.tistory.com/191#entry191comment</comments>
      <pubDate>Mon, 14 Jun 2021 23:40:36 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Global Component 만들기 (컴포넌트 전역 등록하기)</title>
      <link>https://itinerant.tistory.com/190</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Global&amp;nbsp;Component&amp;nbsp;만들기&amp;nbsp;(컴포넌트&amp;nbsp;전역&amp;nbsp;등록하기)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;들어가기에 앞서...&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 자주 사용하는 컴포넌트를 만들고, 매번 해당 컴포넌트를 사용하는 곳마다 import 하여 사용하는 것은 불편할 뿐더러 컴포넌트의 파일 위치가 바뀐다면 해당 컴포넌트를 사용하는 모든 파일마다 수정을 해줘야 하기 때문에 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 자주 사용하는 컴포넌트를 전역(Global)로 등록하여 사용한다.&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;  간단한 글로벌 버튼 컴포넌트 만들기&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;&lt;a href=&quot;https://vuetifyjs.com/en/&quot;&gt;Vuetify&lt;/a&gt; 라는 UI Framework를 이용해서 버튼(v-btn)을 생성하였다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 버튼 컴포넌트 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`버튼`을 컴포넌트로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$attrs는 부모로 부터 전달받은 props 를 모두 담고 있으며, &lt;b&gt;$listeners&lt;/b&gt;는 부모로 부터 전달받은 &lt;span style=&quot;color: #304455;&quot;&gt;이벤트 리스너&lt;/span&gt;들을 모두 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;- &lt;a href=&quot;https://kr.vuejs.org/v2/guide/components-custom-events.html#%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90-%EB%B0%94%EC%9D%B8%EB%94%A9-%ED%95%98%EA%B8%B0&quot;&gt;[참고] $listeners 관련 문서&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623291459264&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @/components/GlobalButton.vue
&amp;lt;template&amp;gt;
  &amp;lt;v-btn depressed v-bind=&quot;$attrs&quot; v-on=&quot;$listeners&quot;&amp;gt;
    &amp;lt;slot&amp;gt;&amp;lt;/slot&amp;gt;
  &amp;lt;/v-btn&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &quot;GlobalButton&quot;,
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 컴포넌트 정의&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;@/plugins/global-components.js&lt;/span&gt; 파일을 만들어서 그 안에 정의한 후 main.js에 등록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일을 분리하지 않고 처음부터 main.js 에 바로 등록해도 상관 없지만, 전역으로 등록하려는 컴포넌트가 많아 질 경우 main.js 파일이 복잡해 질 수 있으므로 분리하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;꼭 plugins 폴더 내에 생성해줄 필요는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  Vue.component&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;size14&quot;&gt;&lt;a href=&quot;https://vuejs.org/v2/api/#Vue-component&quot;&gt;- [참고] Vue.component 관련 문서&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623292041210&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @/plugins/global-components.js
import Vue from &quot;vue&quot;;
import GlobalButton from &quot;@/components/GlobalButton.vue&quot;;

Vue.component(&quot;g-button&quot;, GlobalButton);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;main.js&lt;/span&gt; 에 플러그인 추가&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623292176518&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.js
import Vue from &quot;vue&quot;;
import App from &quot;./App.vue&quot;;
import &quot;@/plugins/global-components&quot;;  // 추가

Vue.config.productionTip = false;

new Vue({
  render: (h) =&amp;gt; h(App)
}).$mount(&quot;#app&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 컴포넌트 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1)은 전역에 등록했기 때문에 따로 파일 내에서 import 하고 components에 등록할 필요 없이 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)는 전역 컴포넌트가 아니기 때문에 스크립트 내에서 component 등록을 해줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1623292426998&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// App.vue
&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;g-button outlined&amp;gt;Global Button&amp;lt;/g-button&amp;gt;  // (1)
    &amp;lt;my-button&amp;gt;My Button&amp;lt;/my-button&amp;gt;             // (2)
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import MyButton from &quot;@/components/MyButton.vue&quot;;

export default {
  name: &quot;App&quot;,
  components: { MyButton },
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
button {
  margin: 5px;
}
&amp;lt;/style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 결과&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 코드   &lt;a href=&quot;https://codesandbox.io/s/small-architecture-7o4cn?file=/src/App.vue&quot;&gt;https://codesandbox.io/s/small-architecture-7o4cn?file=/src/App.vue&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://kr.vuejs.org/v2/guide/components-registration.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kr.vuejs.org/v2/guide/components-registration.html&lt;/a&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;</description>
      <category>WEB/Vue.js</category>
      <category>Component</category>
      <category>global</category>
      <category>vue</category>
      <category>vue.js</category>
      <category>전역</category>
      <category>전역컴포넌트</category>
      <category>컴포넌트</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/190</guid>
      <comments>https://itinerant.tistory.com/190#entry190comment</comments>
      <pubDate>Thu, 10 Jun 2021 19:53:56 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Vue 관련 문서 모음  </title>
      <link>https://itinerant.tistory.com/189</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Vue 관련 문서 모음&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vue.js&lt;/b&gt;&amp;nbsp;&lt;b&gt;(Nuxt.js)&lt;/b&gt;&amp;nbsp;를 공부하는데 도움될 만 한 사이트 모음&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 공식 사이트 ]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Vue.js 공식 사이트 (한글)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;(2.0)&lt;/b&gt; &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://kr.vuejs.org/v2/guide/index.html&quot;&gt;https://kr.vuejs.org/v2/guide/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(3.0)&lt;/b&gt; &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://v3.ko.vuejs.org/guide/introduction.html&quot;&gt;https://v3.ko.vuejs.org/guide/introduction.html&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue API (한글)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://kr.vuejs.org/v2/api/index.html&quot;&gt;https://kr.vuejs.org/v2/api/index.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vuex 공식 문서 (한글)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://vuex.vuejs.org/kr/&quot;&gt;https://vuex.vuejs.org/kr/&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue-loader&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://vue-loader-v14.vuejs.org/kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vue-loader-v14.vuejs.org/kr/&lt;/a&gt;&amp;nbsp; &amp;nbsp;(한글)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vue-loader.vuejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://vue-loader.vuejs.org/&lt;/a&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(영문)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue Style-guide (영문)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://vuejs.org/v2/style-guide/&quot;&gt;https://vuejs.org/v2/style-guide/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue Forum (영문)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://forum.vuejs.org/&quot;&gt;https://forum.vuejs.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Nuxt.js 공식 사이트 (한글)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://ko.nuxtjs.org/&quot;&gt;https://ko.nuxtjs.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 강의, 블로그 ]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;캡틴 판교 vue.js 강의 문서&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://joshua1988.github.io/vue-camp/textbook.html&quot;&gt;https://joshua1988.github.io/vue-camp/textbook.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue-School Tutorial (영문)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://vueschool.io/articles/category/vuejs-tutorials/page/3/&quot;&gt;https://vueschool.io/articles/category/vuejs-tutorials/page/3/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 기타 패키지 ]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Vue property decorator&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kaorun343/vue-property-decorator&quot;&gt;https://github.com/kaorun343/vue-property-decorator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue Class Component&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://class-component.vuejs.org/&quot;&gt;https://class-component.vuejs.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vuex module decorators&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/championswimmer/vuex-module-decorators&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/championswimmer/vuex-module-decorators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Vue Session&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/victorsferreira/vue-session&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/victorsferreira/vue-session&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ Vue Test ]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Vue testingHandBook&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://lmiller1990.github.io/vue-testing-handbook/&quot;&gt;https://lmiller1990.github.io/vue-testing-handbook/&lt;/a&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Cracking Vue.js(Vue Test Utils) (국문)&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://joshua1988.github.io/vue-camp/testing/vue-test-util.html#vue-test-utils&quot;&gt;https://joshua1988.github.io/vue-camp/testing/vue-test-util.html#vue-test-utils&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;b&gt;Jest Documents&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://jestjs.io/docs/en/getting-started.html&quot;&gt;https://jestjs.io/docs/en/getting-started.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[ 그 외 ]&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Vue 관련 리소스 리스트&amp;nbsp;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://jestjs.io/docs/en/getting-started.html&quot;&gt;https://jestjs.io/docs/en/getting-started.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>WEB/Vue.js</category>
      <category>document</category>
      <category>nuxt</category>
      <category>vue</category>
      <category>공부</category>
      <category>링크</category>
      <category>문서</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/189</guid>
      <comments>https://itinerant.tistory.com/189#entry189comment</comments>
      <pubDate>Thu, 27 May 2021 20:03:17 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] vscode 에서 vue 디버깅</title>
      <link>https://itinerant.tistory.com/188</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] vscode 에서 vue 디버깅&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&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;1. vscode 에서 디버깅 모드 설정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 vscode에서 DEBUG 화면에 들어가서 [&lt;u&gt;톱니바퀴모양&lt;/u&gt;]을 누른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;launch.json&lt;/span&gt;&lt;/b&gt; 파일이 자동으로 열리게 되며, 여기서 디버깅 모드 설정을 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;443&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA81Vp/btq5SSI0pRd/APVkZI0N99ckICemhMn0i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA81Vp/btq5SSI0pRd/APVkZI0N99ckICemhMn0i1/img.png&quot; data-alt=&quot;vscode debugging 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA81Vp/btq5SSI0pRd/APVkZI0N99ckICemhMn0i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA81Vp%2Fbtq5SSI0pRd%2FAPVkZI0N99ckICemhMn0i1%2Fimg.png&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;443&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;vscode debugging 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  launch.json 파일&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1622080752983&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// launch.json
{
	// Use IntelliSense to learn about possible attributes.
	// Hover to view descriptions of existing attributes.
	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
	&quot;version&quot;: &quot;0.2.0&quot;,
	&quot;configurations&quot;: [
		// chrome
		{
			&quot;type&quot;: &quot;chrome&quot;,
			&quot;request&quot;: &quot;launch&quot;,
			&quot;name&quot;: &quot;Launch Chrome against localhost&quot;,
			&quot;url&quot;: &quot;http://localhost:8080&quot;,
			&quot;webRoot&quot;: &quot;${workspaceFolder}/src&quot;,
			&quot;sourceMapPathOverrides&quot;: {
				&quot;webpack:///src/*.vue&quot;: &quot;${webRoot}/*.vue&quot;,
				&quot;webpack:///./src/*.ts&quot;: &quot;${webRoot}/*.ts&quot;,   // typescript 인 경우
				&quot;webpack:///./src/*.js&quot;: &quot;${webRoot}/*.js&quot;    // javascript 인 경우
			}
		},
	]
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;type : chrome, firefox 등 테스트할 장소라고 생각하면 쉽다.&lt;/li&gt;
&lt;li&gt;name : configurations에 여러 개의 구성이 추가될 수 있는데 이를 구분하기 위한 이름이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이곳에 이름이 표시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;40&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; data-alt=&quot;configuation name&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkAEMk%2Fbtq5SbIOMEg%2FNzWQn4PIbmHq1w1knZ3Abk%2Fimg.png&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;40&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;configuation name&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;url : 웹 서버 URL을 적어주면 된다.&lt;/li&gt;
&lt;li&gt;webRoot : 현재 프로젝트 소스 코드 경로이다. 기본적으로 Vue는 `/src` 폴더에 존재한다.&lt;/li&gt;
&lt;li&gt;sourceMapPathOverrides : 파일들을 webRoot 경로로 변경해준다.&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;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 디버깅 모드 실행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 구성을 선택해 주고, 초록색 &lt;span&gt;▶️&lt;/span&gt; 버튼을 눌러 실행해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;40&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; data-alt=&quot;select configuration&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkAEMk/btq5SbIOMEg/NzWQn4PIbmHq1w1knZ3Abk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkAEMk%2Fbtq5SbIOMEg%2FNzWQn4PIbmHq1w1knZ3Abk%2Fimg.png&quot; data-origin-width=&quot;329&quot; data-origin-height=&quot;40&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;select configuration&lt;/figcaption&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;&lt;b&gt;3. 서버 실행&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 웹을 실행시켜주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1622081786943&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// nuxt
$ npm run dev

// vue-cli
$ npm run serve&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://code.visualstudio.com/docs/editor/debugging#_launch-configurations&quot;&gt;https://code.visualstudio.com/docs/editor/debugging#_launch-configurations&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;a href=&quot;https://stackoverflow.com/questions/63972838/debugging-in-vs-code-does-not-work-for-typescript-vue-app&quot;&gt;https://stackoverflow.com/questions/63972838/debugging-in-vs-code-does-not-work-for-typescript-vue-app&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;a href=&quot;https://kr.vuejs.org/v2/cookbook/debugging-in-vscode.html&quot;&gt;https://kr.vuejs.org/v2/cookbook/debugging-in-vscode.html&lt;/a&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;오류&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지만 작업을 하면, Nuxt.js 에서는 원하는대로 디버깅이 되었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@vue-cli에서 디버깅을 하려고 하니 breakpoint를 잡았지만 point가 작동되지 않았다.&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;size18&quot;&gt;&lt;b&gt;1. vue config 파일에 webpack 옵션 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622082125820&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// vue.config.js
module.exports = {
  configureWebpack: {
    devtool: &quot;source-map&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;size18&quot;&gt;&lt;b&gt;2. launch.json 파일에서 sourceMapPathOverrides 옵션 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622082184104&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	// Use IntelliSense to learn about possible attributes.
	// Hover to view descriptions of existing attributes.
	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
	&quot;version&quot;: &quot;0.2.0&quot;,
	&quot;configurations&quot;: [
		{
			&quot;type&quot;: &quot;chrome&quot;,
			&quot;request&quot;: &quot;launch&quot;,
			&quot;name&quot;: &quot;Launch Chrome against localhost&quot;,
			&quot;url&quot;: &quot;http://localhost:8080&quot;,
			&quot;webRoot&quot;: &quot;${workspaceFolder}/src&quot;,
			&quot;sourceMapPathOverrides&quot;: {
				&quot;webpack:///./src/*&quot;: &quot;${webRoot}/*&quot;,
				&quot;webpack:///src/*&quot;: &quot;${webRoot}/*&quot;,
				&quot;webpack:///*&quot;: &quot;*&quot;,
				&quot;webpack:///./~/*&quot;: &quot;${webRoot}/node_modules/*&quot;
			}
		}
	]
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://github.com/microsoft/vscode-recipes/issues/201&quot;&gt;https://github.com/microsoft/vscode-recipes/issues/201&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;[참고] &lt;a href=&quot;https://stackoverflow.com/questions/50765353/visual-studio-code-breakpoint-appearing-in-wrong-place/50826961#50826961&quot;&gt;https://stackoverflow.com/questions/50765353/visual-studio-code-breakpoint-appearing-in-wrong-place/50826961#50826961&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>WEB/Vue.js</category>
      <category>Debug</category>
      <category>Debugging</category>
      <category>vscode</category>
      <category>vue</category>
      <category>vue-cli</category>
      <category>디버그</category>
      <category>디버깅</category>
      <category>버그</category>
      <category>설정</category>
      <category>오류</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/188</guid>
      <comments>https://itinerant.tistory.com/188#entry188comment</comments>
      <pubDate>Thu, 27 May 2021 11:27:19 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] Eslint + Prettier 오류 (⏎&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot;)</title>
      <link>https://itinerant.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[Vue.js] Eslint + Prettier ⏎&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot;&amp;middot; 오류&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h1&gt;1. 오류&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue-cli 로 프로젝트를 만들고 실행하니 다음과 같은 줄바꿈을 하라는 오류가 나왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;508&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfplz0/btq5ObwlTR2/h9y3aEKkF2B3z0Sc8O4Ec1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfplz0/btq5ObwlTR2/h9y3aEKkF2B3z0Sc8O4Ec1/img.png&quot; data-alt=&quot;source code 상 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfplz0/btq5ObwlTR2/h9y3aEKkF2B3z0Sc8O4Ec1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfplz0%2Fbtq5ObwlTR2%2Fh9y3aEKkF2B3z0Sc8O4Ec1%2Fimg.png&quot; data-origin-width=&quot;1912&quot; data-origin-height=&quot;508&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;source code 상 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;544&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhUHDe/btq5RDL8PNQ/csiF7M5RX9jgkhjmeMIGm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhUHDe/btq5RDL8PNQ/csiF7M5RX9jgkhjmeMIGm1/img.png&quot; data-alt=&quot;console 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhUHDe/btq5RDL8PNQ/csiF7M5RX9jgkhjmeMIGm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhUHDe%2Fbtq5RDL8PNQ%2FcsiF7M5RX9jgkhjmeMIGm1%2Fimg.png&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;544&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;console 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;2. 해결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;.eslintrc.js&lt;/span&gt; 에 prettier 규칙 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;a href=&quot;https://velog.io/@nittre/Prettier-prettier.htmlWhitespaceSensitivity-%EC%86%8D%EC%84%B1&quot;&gt;[Prettier] htmlWhitespaceSensitivity 속성&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622038210093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .eslintrc.js
rules: {
  &quot;prettier/prettier&quot;: [
    &quot;error&quot;,
    {
      htmlWhitespaceSensitivity: &quot;ignore&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;그리고, vscode eslint 설정에 &lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;autoFixOnSave&lt;/span&gt; 옵션을 true로 변경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;변경한 파일을 저장할 때마다 자동으로 eslint를 실행시켜 문제(오류)가 있으면 수정후 저장해주는 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;a href=&quot;https://www.python2.net/questions-536098.htm&quot; rel=&quot;noopener&quot;&gt;https://www.python2.net/questions-536098.htm&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고] &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&quot;&gt;vscode eslint autoFixOnSave 옵션 사용법&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1622038564055&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// settings.json
&quot;editor.codeActionsOnSave&quot;: {
  &quot;source.fixAll.eslint&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 자동 저장하는 것이 싫다면?! 수동으로 바꿔주거나, 커맨드(콘솔)창에 lint --fix 명령어를 실행하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1622038966300&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ yarn vue-cli-service lint --fix&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;&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;</description>
      <category>WEB/Vue.js</category>
      <category>ESLint</category>
      <category>htmlWhitespaceSensitivity</category>
      <category>lint</category>
      <category>Prettier</category>
      <category>vue</category>
      <category>vue-cli</category>
      <category>⏎</category>
      <category>삽질</category>
      <author>S0PH1A</author>
      <guid isPermaLink="true">https://itinerant.tistory.com/187</guid>
      <comments>https://itinerant.tistory.com/187#entry187comment</comments>
      <pubDate>Wed, 26 May 2021 23:31:41 +0900</pubDate>
    </item>
  </channel>
</rss>