<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>_0422의 생각</title>
    <link>https://0422.tistory.com/</link>
    <description>모든 글은 주관적인 생각이며, 사실과 다를 수 있습니다.
재미있게 읽으셨다면 구독, 좋아요 부탁드립니다.</description>
    <language>ko</language>
    <pubDate>Sun, 12 Apr 2026 21:04:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>_0422</managingEditor>
    <image>
      <title>_0422의 생각</title>
      <url>https://tistory1.daumcdn.net/tistory/4609848/attach/8ec27ffc0cd743a3acb75cd10b1fc5ed</url>
      <link>https://0422.tistory.com</link>
    </image>
    <item>
      <title>2025 회고</title>
      <link>https://0422.tistory.com/393</link>
      <description>&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;연말엔 굉장히 정신이없었어서 어쩌다보니 년도를 넘겨 회고를 쓰게 됐다.&lt;br&gt;2025년은 굉장히 도전적이었고, 그만큼 얻은 것도 많고, 잃은 것도 있는 한 해였다.&lt;br&gt;그러나 돌이켜보면 그만큼 많은 경험을 했다는 뜻인것 같다.&lt;br&gt;&lt;br&gt;&lt;br&gt;돌아보면 이번 년도는 대부분 회사와 함께 했다.&lt;br&gt;1월 24일에 인턴 합류소식을 듣고 2월 3일부터 일하기 시작한게 지금까지 이어졌고, 정규직으로 전환되며 내년에도 함께할 수 있게 되었다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2QEcm/dJMcabiyZC8/kVokyMX4tz8TRrK6Dgbdyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2QEcm/dJMcabiyZC8/kVokyMX4tz8TRrK6Dgbdyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2QEcm/dJMcabiyZC8/kVokyMX4tz8TRrK6Dgbdyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2QEcm%2FdJMcabiyZC8%2FkVokyMX4tz8TRrK6Dgbdyk%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;329&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;지난 목표로 돌아보기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;24년도 회고에서 세웠던 목표는&lt;b&gt; 졸업준비, 앱 더 출시하기, 개발도서 읽기&lt;/b&gt; 였다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;졸업준비&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 학기도 잘 마무리했고, 졸업시험도 통과했다.&lt;br&gt;이제 정말 졸업식만을 눈앞에 두고 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;앱 더 출시하기&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;Dev-Feed를 홍보하고, 감정구슬일기라는 앱을 하나 더 내면서 도전해보고자 했다.&lt;br&gt;Dev-Feed는 광고비만 왕창 소진했고, 감정구슬일기는 아직 기능이 많이 부족한데, 너무 오랜기간 방치되었다...&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;개발 도서 읽고 개발 철학에 대해 고민해보기&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 개발자 원칙, 실용주의 프로그래머. 쏙쏙 들어오는 함수형 코딩 세권을 읽는 것이 목표였다.&lt;br&gt;실용주의 프로그래머, 쏙쏙들어오는 함수형 코딩을 읽었고, 지금 일하는데 굉장히 많은 도움이 되었다.(&lt;s&gt;개발자 원칙은 못읽었다.&lt;/s&gt;)&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;추가로 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000061351981?utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=googleSearch&amp;amp;gt_network=g&amp;amp;gt_keyword=&amp;amp;gt_target_id=dsa-1787880729500&amp;amp;gt_campaign_id=9979905549&amp;amp;gt_adgroup_id=132556570510&amp;amp;gad_source=1&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;이펙티브 엔지니어&lt;/span&gt;&lt;/a&gt; 라는 책을 읽었는데, 더 좋은 프로덕트를 만들기 위해서는&amp;nbsp;&lt;b&gt;생산성을 높혀야한다는 점&lt;/b&gt;에서 굉장히 많은 공감을 할 수 있었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot;&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;성장&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;돌아보면 많은 부분에서 성장했던 한해였다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;Local Jobs(당근알바) 팀&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;거의 10월까지 알바팀에서 일했다.&lt;br&gt;25년 이웃알바의 대부분의 신기능들에 대해 기여했다.&lt;br&gt;일하면서 당근알바의 큰 기능들을 도맡아서 사용자들에게 새로운 기능을 제공하고, 개선하는 작업들이 재미있었다.&amp;nbsp;&lt;br&gt;알바팀에서는 일하면서 성장하는 방법과 일을 더 잘하는 방법, 그리고 알바 팀의 생각과 일하는 방식을 배울 수 있었다.&lt;br&gt;나만의 &lt;a href=&quot;https://0422.tistory.com/392&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;일하는 방식&lt;/span&gt;&lt;/a&gt;도 확립할 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;5월에 했던 이웃알바 캠페인은 회사에서 홀로 프로젝트를 온전히 개발한 첫번째 경험이었고,&lt;br&gt;7월의 당근이네는 귀여운 프로덕트로 유저의 반응을 이끌어 냈던 정말 기억에 남을 경험이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;캠페인을 하면서 전반적인 하나의 프로덕트에서의 유저 경험을 고민할 수 있었고&lt;br&gt;당근이네에서 UX 시스템을 설계하며 인터페이스에 대해 고민하면서 좀더 좋은 추상화를 고민할 수 있게된 것 같다.&lt;br&gt;이외에도 크고 작은 기능들을 개발하고 실험하면서 데이터에서 인사이트를 공유해 개선해 나가는 과정이 굉장히 즐거웠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;9월쯤 커머스팀으로 이동할때쯤에는 당근 알바의 주요 화면들 대부분에 내가 작성한 코드들이 들어가 작동한다는 점이 엄청나게 뿌듯함을 느끼게했다. 일은 많아도, 일하는게 참 재밌었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이전에 엘리스에서 일할 때는 단순히 좋은 코드를 작성하는 것을 목표로 했다면,&lt;br&gt;이제는 유저의 경험을 생각할 수 있게 되었고, 데이터를 바라보는 방법을 알게되었다. 그리고 왜 좋은 코드를 작성해야하는지를 몸소 느끼게 되었다.&lt;br&gt;알바팀에서 일하면서 프론트엔드 개발자에서 &lt;b&gt;프로덕트 엔지니어&lt;/b&gt;로, 하고싶은 것이 좀더 명확해졌던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;커머스팀&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;커머스 팀은 얼리스테이지의 프로덕트를 만들어야하는 팀이다.&lt;br&gt;항상 마음 한켠에 창업을 생각하고 있었기에, 그리고 추천을 받았기에 팀을 이동해서 일하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;일하면서는 나 홀로 프로덕트 메인테이닝을 하게 되며 기존에 알바팀의 세팅들이 얼마나 많은 고민들을 통해 나온 것들인지를 새롭게 느낄 수 있었다. 폴더 구조부터 에러 로깅과 핸들링까지.. 전반적인 세팅과 최신 라이브러리로 마이그레이션을 진행하면서 기술적으로 성장할 수 있었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이후에는 &lt;span style=&quot;color: #333333;&quot;&gt;전반적인 프로덕트에 대한 기획, 논의, 개발, 개발 후 개선까지 이어지는 전 과정에 나의 생각이 담기고 배포되는 것들을 보면서 이전보다 더 재밌게 일하고 있다. &lt;/span&gt;그리고 생산성이 얼마나 중요한 것인지를 계속해서 상기하면서 반복되는 것들을 개선해 좋은 프로덕트를 만들어 나가고자 노력하고 있다.&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;AI&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;23년도 초쯤에 gpt를 코딩하는데 써보면서 농담으로 깡통이라고 놀렸었던 기억이 난다.&lt;br&gt;어느새 일하는데 자연스럽게 ai를 사용하고 있다보니, 돌아보면 새삼 참 놀라울 따름이다.&lt;br&gt;cursor의 tab 자동완성을 보고 놀랐던 기억도 나고, mcp가 뭔지 찾아보며 놀랐던 기억도 난다.&lt;br&gt;그러다 클로드가 만든 결과를 보고, 이제 내가 설자리는 없나? 고민했는데, 이 모든게 이번 년도에 경험한 것들이라니 믿기지가 않는다.&lt;br&gt;열심히 공부해서 따라잡아야지... 계속해서 배우고 적용해나가면, 더 높은 생산성을 가진 메이커로 성장할 수 있을거라고 믿는다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYuc8y/dJMcadgkxNc/t4eZrG1X562lKOJMwkJvH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYuc8y/dJMcadgkxNc/t4eZrG1X562lKOJMwkJvH1/img.png&quot; data-alt=&quot;작년에 36588번 tab을 눌렀다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYuc8y/dJMcadgkxNc/t4eZrG1X562lKOJMwkJvH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYuc8y%2FdJMcadgkxNc%2Ft4eZrG1X562lKOJMwkJvH1%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;1884&quot; height=&quot;717&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;작년에 36588번 tab을 눌렀다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;도전&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;나에게 이번 년도의 가장 큰 도전은 인터뷰였다. 약한 인터뷰때문에 &lt;span style=&quot;color: #333333;&quot;&gt;빈번히 좋은 기회를 놓쳤었다. &lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이런 부분들 만회하기위해 주변 분들의 조언을 받아 부딪혀보자는 식으로 실제로 면접을 진행하면서 훈련했다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;과정에서 꽤나 많은 인사이트를 얻을 수 있었고, 덕분에 정규직으로도 전환할 수 있었다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어느정도 자신감도 가질 수 있게된 것 같다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;좋은 동료 분들&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 것은 주변 분들이 정말 많이 도와주셔서 이룰 수 있었다고 생각한다.&lt;br&gt;정말 중간에 좀 쉬어야하나 생각도 했었지만, 주변에 많은 분들이 도와주셨고 덕분에 버티고, 해낼 수 있었다.&lt;br&gt;이 글을 통해 다시 한 번 감사하다는 말씀을 전하고 싶다.&lt;br&gt;그리고 나 역시 그런 힘이 되는 좋은 동료가 되고 싶다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2026년에는&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2026년에는 지금까지 배운 것들을 기반으로 한발짝 더 나아가서 도전하고, 경험들을 좋은 결과로 전환해내고 싶다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;커머스&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 당근 커머스팀의 프로덕트를 잘 키우고 싶다.&lt;br&gt;이전부터 해보고 싶었던 초기 상태의 프로덕트 개발이다.&lt;br&gt;이용자를 늘리고, 매출을 늘려 프로덕트를 잘 성장시키고싶고, 이번 년도는 정말 여기에 온 힘을 다하고 싶다.&lt;br&gt;그러기 위해서는 타팀과의 소통도 잘해야하고, AI를 통해 부족한 리소스를 채워나갈 수 있어야한다.&lt;br&gt;&lt;b&gt;소통&lt;/b&gt;을 잘 하기 위한 방법을 고민하고, &lt;b&gt;생산성을 높이기 위한 방법들&lt;/b&gt;(AI)을 더욱 리서치하고, 실험해봐야한다.&lt;br&gt;이런 것들은 일을 하면서 여러가지를 시도해볼 생각이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;또, 초기 프로덕트를 성공시키기위한 &lt;b&gt;인사이트&lt;/b&gt;들도 학습해나가고자 한다.&lt;br&gt;현재는 린 분석을 읽고 있다. 우선&lt;b&gt; 린시리즈&lt;/b&gt;를 다 읽는게 상반기 목표이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;건강&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 정말 운동해야한다. 건강검진 결과를 보니 정말 운동이 필요하다.&lt;br&gt;헬스를 다니고는 있으나 이걸로는 부족하다.&lt;br&gt;주말에 가끔 하던 3km(~5km) 달리기를 루틴에 넣고 꼭 수행해야겠다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;취미로의 독서&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이번년도는 민음사 소설을 한권도 읽지 못했다. (읽은 소설이 자몽살구클럽 한권...)&lt;br&gt;하지만, 소설이라는 글의 형태로 된 간접 경험이 주는 교훈을 놓치고 싶지 않다.&lt;br&gt;내년에는 꼭... 몇권이라도 읽으리라. 우선 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000000620198&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;설국&lt;/span&gt;&lt;/a&gt;을 샀다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;어떤 대상을 추구한다면, 그것에 가치를 부여하는 사람은 나이다. 그래서 내가 생각을 바꾸면 그 대상은 언제라도 무가치해질 수 있다. 따라서&amp;nbsp;인간이&amp;nbsp;혼자서&amp;nbsp;독립적으로&amp;nbsp;지향하는&amp;nbsp;대상은&amp;nbsp;언제라도&amp;nbsp;무의미로&amp;nbsp;사라져버릴&amp;nbsp;수&amp;nbsp;있다.&lt;br&gt;&lt;br&gt;하지만, 나에 의해 유일하게 가치를 잃지 않는 것이 하나 존재한다. 그건 바로 타인이다. 타인은 나처럼 주관적 판단을 하며, 이에 따라 의미를 부여한다. 그래서 타인은 내가 의미 없다 생각하는 것들에 의미를 부여할 수 있다. 그러면 내가 추구했던 것들은 더이상 무의미의 혼돈으로 빠지지않고 의미를 유지할 수 있게 된다.&lt;br&gt;&lt;br&gt;내게 모든 것이 무의미해져도, 타인만큼은 독자적으로 발휘하는 자유로 내린 가치판단으로, 그 의미를 지킨다.&lt;br&gt;그러므로 안정적인 가치란 타인에게까지 연장될 수 있는 가치, 타인이 스스로 동의할 수 있게 하는 가치다.&lt;br&gt;노인과 바다에서 노인이 보여준 의지는 청세치를 잡겠다는 의지이기도 하나, 자신을 걱정하는 소년을 생각하는 마음에서 나온 의지이기도 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 우리는 서로가 서로를 무의미의 혼돈속에서 지켜주는 연인, 친구, 동료가 되어야한다&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://0422.tistory.com/377&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;24년도 회고 글&lt;/span&gt;&lt;/a&gt;을 쓰며, 노인과 바다의 소년과 같은 사람이 되겠다는 생각을 가졌었고, 그래서 당근에서 마놀린이라는 이름을 사용하게되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;글에는 적지 않았지만 작년과 비슷하거나 그 이상으로 중간 중간 많은 일들이 많은 1년이었다.&lt;br&gt;이전의 경험들과 생각들, 그리고 주변 사람들이 없었다면 정말 해낼 수 없었을 것이다.&lt;br&gt;주변 분들에게 감사함을 전하며 앞으로도 마놀린으로 남을 수 있어 기쁘다.&lt;br&gt;&amp;nbsp;&lt;br&gt;2026년은 정말 하고 싶은 것도 많고, 기대되는 해이다.&lt;br&gt;새해복 많이 받으시길!&lt;/p&gt;</description>
      <category>회고/회고</category>
      <category>2025</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/393</guid>
      <comments>https://0422.tistory.com/393#entry393comment</comments>
      <pubDate>Fri, 2 Jan 2026 20:02:19 +0900</pubDate>
    </item>
    <item>
      <title>내가 프로덕트 팀에서 일하는 방법</title>
      <link>https://0422.tistory.com/392</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjND4f/btsQypnWaxF/GwDgWgpv4ca9mHCJEAZyH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjND4f/btsQypnWaxF/GwDgWgpv4ca9mHCJEAZyH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjND4f/btsQypnWaxF/GwDgWgpv4ca9mHCJEAZyH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjND4f%2FbtsQypnWaxF%2FGwDgWgpv4ca9mHCJEAZyH0%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;1920&quot; height=&quot;840&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당근알바 팀에서 인턴으로 일한지도 어느덧 7개월이 넘었다. 이제 정말 퇴사까지 한달 정도를 앞두고, 정리를 하나씩 해보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기간은 정말 많은 것들을 배우고 느낄 수 있는 시간이었지만, 누군가 나에게 이 과정에서 가장 큰 수확이 무엇이냐 묻는다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕트 팀에서의 나만의 워크플로우를 확립한 것이라 대답할 것이다.&lt;/p&gt;
&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;h2 data-ke-size=&quot;size26&quot;&gt;프로덕트 팀의 특성과 요구되는 역량에 대해&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 팀이냐에 따라 다를 수 있겠지만 내가 느낀 프로덕트 팀의 특성을 정리해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕트 팀은 사용자에게 제품을 전달한다. 따라서 사용자가 잘 사용하고 좋아하는 제품을 만들어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/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;h4 data-ke-size=&quot;size20&quot;&gt;로그에 대해&amp;nbsp;&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 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;h4 data-ke-size=&quot;size20&quot;&gt;개발에 대해&amp;nbsp;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡하거나 대규모 기능의 경우 QA를 진행하고 배포하게되지만, 대부분 작업의 퀄리티는 개발자가 보증할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분은 QA 맥락 공유 자체가 리소스가 되기에 이런 경우가 많다.&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;b&gt;자주 변경되므로, 더 읽기 쉬운 코드를 작성해야 한다. (Easy To Change!)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드만으로 맥락을 담지 못한다면 그것은 이후에 반드시 나나 다른 사람의 실수로 이어진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사람은 실수를 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르고, 정확하게, 그리고 데이터를 적절히 수집하도록.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 프로덕트 팀의 기능을 구현하는 개발자에게 요구되는 역량이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 일하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짝 프로그래밍이 효과적인 이유 중 하나는 한명은 큰 관점에서 문제를 바라보고, 한 명은 작은 코드 관점에서 문제를 바라보기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 시간과 자원의 문제로 일을 나눠서 하는 것은 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 스펙에 대한 올바른 이해&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 파악하면서 누락된 것은 없는지&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 더 필요한 것들에 대해&amp;nbsp;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;자신이 뭘 원하는지 정확히 아는 사람은 아무도 없다. 프로그래머는, 즉 우리의 일은 사람들이 자신이 원하는 바를 깨닫도록 돕는다. 이게 우리의 가치가 가장 빛나는 부분이다.&lt;br /&gt;&lt;br /&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 데이터 기록을 위한 고민&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 개발을 위한 이정표 작성하기&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 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;h4 data-ke-size=&quot;size20&quot;&gt;6. 태스크 체크리스트 작성하기&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 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;/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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 엣지케이스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 타입상의 엣지케이스를 넘어 스키마나 api상 데이터에서 문제가 발생할 수 있는 경우를 생각하고 처리한다.&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;2. 코드의 일관성&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 data-ke-size=&quot;size16&quot;&gt;변수 이름을 넘어 한 줄의 띄어쓰기에 대해서도 코드를 작성하는 일관성을 지키기위해 노력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발을 마치고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 작성이 끝났고, 올바르게 동작하는 것 같다면 이제 50% 완료된 것이다.&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;1. 테스트와 검수&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI에 대해서는 pixel pro와 같은 브라우저 확장 프로그램으로 figma에서 export한 이미지를 overlay해서 검수한다.&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;h4 data-ke-size=&quot;size20&quot;&gt;2. 셀프리뷰&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 작성한 코드를 PR을 살펴보며 어떤 것들을 내가 변경했는지 확인하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관성이 잘 챙겨졌는지, 더 개선할 수 있는 부분들은 없는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github pull requests extension을 사용하면 IDE에서도 보기 쉽게 확인할 수 있다.&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피드백이 가능하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기능 개발이 끝나고&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 회고&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 자동화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하면서 불편했던 것들을 자동화 할 수 있다면, 이슈로 만들고 다양한 방식으로 자동화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 스크립트부터 어드민 DevTool, eslint custom rule, webpack plugin등 방법에 종속되지 않고 다양한 시도를 해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;발전 방향에 대해&lt;/h2&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 도구를 사용해보면서 개발할때의 과정을 대체해보는 과정을 수행해보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 시행 착오가 많지만, 이 부분에 대해서도 학습을 통해 발전시켜볼 것이다.&lt;/p&gt;</description>
      <category>회고/당근</category>
      <category>개발자</category>
      <category>당근</category>
      <category>당근알바</category>
      <category>인턴</category>
      <category>프로덕트</category>
      <category>프론트엔드</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/392</guid>
      <comments>https://0422.tistory.com/392#entry392comment</comments>
      <pubDate>Sat, 13 Sep 2025 14:18:48 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 6월 회고 (18 ~ 21주차)</title>
      <link>https://0422.tistory.com/391</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ytYU7/btsOUzz7vi4/5usswCEDCnewyCVYPnNLVk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ytYU7/btsOUzz7vi4/5usswCEDCnewyCVYPnNLVk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ytYU7/btsOUzz7vi4/5usswCEDCnewyCVYPnNLVk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FytYU7%2FbtsOUzz7vi4%2F5usswCEDCnewyCVYPnNLVk%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;527&quot; height=&quot;395&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;6월이 끝났다. 4주간 회고글을 못썼는데 한 번에 정리해보고자한다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 목표로 했던 것들을 기반으로 작업했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무별 시간을 작성하고, 각 작업별 분류를 통해 인지-개선하는 과정을 이어가고자 쭉 이어왔는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점점 챙겨야 하는 부분들이 생기게 되면서, 전환이 잦아졌고 로우 데이터를 작성하는 것이 어려워지게 되었다.&lt;/p&gt;
&lt;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;지금까지 데이터를 봤을때는 구현이 초기에는 60%였으나 50%대로 내려온 상태이고, 이전의 과정을 통해서 워크플로우를 확립하게 되어 이제 데이터 작성은 작성함을 통해 얻는 것보다, 그로 인해 몰입이 깨지는게 더 크다고 생각하여 이 부분은 더이상 유지하지 않게 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잘한 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계문서를 먼저 작성하고, 이후 실제 코드를 작성하는 과거의 나와 페어 프로그래밍 하는 과정과 셀프리뷰시에 말을 하면서 하는 과정을 통해 챙겨지는 부분이 많아서 코드 퀄리티 개선에 많은 도움이 되었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 설계 문서 작성시에 어떤 데이터를 수집해야할지를 고민하고, 이를 배포 이후에 확인하는 과정을 통해 의사결정에 도움이 되는 데이터를 확보할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터에 대한 관심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 그로스 해킹을 읽으면서 어떻게 하면 더 나은 제품을 만들 수 있을지,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 데이터를 해석해야할지 등을 학습하고 있다. 아직은 데이터 분석에 어려움이 있지만 계속해서 발전시켜나갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선이 필요한 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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 data-ke-size=&quot;size16&quot;&gt;이전까지의 워크플로우에서는 내가 작성한 코드가 잘 동작하는지에 대한 테스트케이스를 작성하고, 확인하는 과정만을 거쳤다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 이 과정을 수행하기전에 내가 바꾼 코드로 인해 발생하는 사이드 이펙트가 있지는 않는지 확인하는 과정이 필요하다는걸 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셀프 코드리뷰 후에 작성한 코드가 어떤 것이고, 이것들로 인해 사이드 이펙트가 발생하지는 않는지 확인하는 과정이 더 필요하다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스펙 분석 단계에서 누락되는 케이스 제거하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지는 피그마를 읽는 방향을 신경쓰지 않고 스펙을 분석하다보니 UI 흐름에 따라 읽게 되었고, 이로 인해 놓치는 부분이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분을 최대한 없애기 위해 스펙 문서에 What's Changed 항목을 추가했다.&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;/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 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;/h2&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;/li&gt;
&lt;li&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;li&gt;코드 작성시 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li&gt;아침에 30분씩 CSS학습&lt;/li&gt;
&lt;li&gt;기능에 대한 논의는 모두 기록할 것/기능 논의는 가능한 경우 한번에 처리할 것&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>회고/당근</category>
      <category>frontend</category>
      <category>당근</category>
      <category>알바</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/391</guid>
      <comments>https://0422.tistory.com/391#entry391comment</comments>
      <pubDate>Sat, 28 Jun 2025 15:12:59 +0900</pubDate>
    </item>
    <item>
      <title>Safari의 iframe은 부모와 같은 스레드를 사용한다. (Lottie 스레드 블로킹)</title>
      <link>https://0422.tistory.com/390</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfV9T2/btsOnueqxB3/c3uxaofXCmwNSvgAiCRWF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfV9T2/btsOnueqxB3/c3uxaofXCmwNSvgAiCRWF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfV9T2/btsOnueqxB3/c3uxaofXCmwNSvgAiCRWF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfV9T2%2FbtsOnueqxB3%2Fc3uxaofXCmwNSvgAiCRWF0%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;427&quot; height=&quot;372&quot; data-origin-width=&quot;1090&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;스크롤 랜딩과 같은 페이지를 구성하고 있는데, 이 페이지의 섹션1은 무한 재생돠는 로띠를 갖고 있고, 섹션2는 iframe으로 임베드된 유튜브 영상을 보여주고 있는 형태다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이상하게도, 처음 페이지에 들어왔을 때 safari에서 로띠가 잠깐 버벅거리는 현상이 자꾸 발생했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;Lottie 동작원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js#L190&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js#L190&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748927552919&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;lottie-web/player/js/animation/AnimationManager.js at bede03d25d232826e0c9dca1733d542d8a7754fb &amp;middot; airbnb/lottie-web&quot; data-og-description=&quot;Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/ - airbnb/lottie-web&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js#L190&quot; data-og-url=&quot;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bA3VnN/hyY5aH1gtD/QEJrhW8H7SC5u33SfYC16k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cz1ijT/hyY5h1rwu0/2zg7ibfI0KFApzIphN2XJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js#L190&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/airbnb/lottie-web/blob/bede03d25d232826e0c9dca1733d542d8a7754fb/player/js/animation/AnimationManager.js#L190&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bA3VnN/hyY5aH1gtD/QEJrhW8H7SC5u33SfYC16k/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cz1ijT/hyY5h1rwu0/2zg7ibfI0KFApzIphN2XJ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;lottie-web/player/js/animation/AnimationManager.js at bede03d25d232826e0c9dca1733d542d8a7754fb &amp;middot; airbnb/lottie-web&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/ - airbnb/lottie-web&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lottie는 requestAnimationFrame으로 부드러운 애니메이션을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 버벅이는 것은 rAF가 제대로 실행되지 않는 것이라 할 수 있고, 스레드 블로킹이 발생했다는 가설을 세우게 됐다.&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;performance 탭으로 확인&lt;/h3&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/cvl1yM/btsOnwiZ2ru/OhoIkfL3KB1F6zJxlRviL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvl1yM/btsOnwiZ2ru/OhoIkfL3KB1F6zJxlRviL0/img.png&quot; data-origin-width=&quot;2298&quot; data-origin-height=&quot;910&quot; data-is-animation=&quot;false&quot; width=&quot;499&quot; height=&quot;198&quot; style=&quot;width: 66.7898%; margin-right: 10px;&quot; data-widthpercent=&quot;67.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvl1yM/btsOnwiZ2ru/OhoIkfL3KB1F6zJxlRviL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvl1yM%2FbtsOnwiZ2ru%2FOhoIkfL3KB1F6zJxlRviL0%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;2298&quot; height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKoxpS/btsOmxpoX2a/WnkAuGQoAe6hziRD8LnBJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKoxpS/btsOmxpoX2a/WnkAuGQoAe6hziRD8LnBJ0/img.png&quot; data-origin-width=&quot;1534&quot; data-origin-height=&quot;1266&quot; data-is-animation=&quot;false&quot; width=&quot;464&quot; height=&quot;383&quot; style=&quot;width: 32.0474%;&quot; data-widthpercent=&quot;32.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKoxpS/btsOmxpoX2a/WnkAuGQoAe6hziRD8LnBJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKoxpS%2FbtsOmxpoX2a%2FWnkAuGQoAe6hziRD8LnBJ0%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;1534&quot; height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬과 safari모두 스크립트 평가로 63 ~ 80ms동안 작업을 수행함을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 작업이 스레드를 블로킹하는가?는 여전히 의문이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실험 환경을 구성해주었다.&lt;/p&gt;
&lt;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;h4 data-ke-size=&quot;size20&quot;&gt;Child.html&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10초뒤에 스레드를 블로킹하도록 스크립트를 구성했고, 이걸 vercel로 배포했다.&lt;/p&gt;
&lt;pre id=&quot;code_1748928256315&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Child Frame&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;  iframe 문서&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;10초 뒤 무한 루프를 실행합니다.&amp;lt;/p&amp;gt;

  &amp;lt;script&amp;gt;
    setTimeout(() =&amp;gt; {
      console.log(&quot;  [iframe] 무한 루프 시작!&quot;);
      while (true) {
        // 아무 작업도 하지 않는 무한 루프
        Math.random(); // Safari에서 최적화 제거 방지용
      }
    }, 10000); // 10초 후 실행
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&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;h4 data-ke-size=&quot;size20&quot;&gt;Parent.html&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식요소를 iframe으로 열어주는 html을 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1748928238098&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Parent Page (localhost:3000)&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt; &amp;zwj;  부모 문서&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;이 페이지는 1초마다 로그를 찍습니다.&amp;lt;/p&amp;gt;

  &amp;lt;script&amp;gt;
    setInterval(() =&amp;gt; {
      console.log(&quot;✅ [부모] 인터벌 실행됨&quot;, new Date().toISOString());
    }, 1000);
  &amp;lt;/script&amp;gt;

  &amp;lt;!-- iframe은 다른 오리진의 child.html --&amp;gt;
  &amp;lt;iframe src=&quot;parent.html의 주소&quot; style=&quot;width:600px; height:200px; border:1px solid #ccc;&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&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;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사이트가 같은 경우 (localhost)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘다 localhost로 수행한 경우에는 크롬과 safari모두 block되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1748928461050&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Parent Page (localhost:3000)&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt; &amp;zwj;  부모 문서&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;이 페이지는 1초마다 로그를 찍습니다.&amp;lt;/p&amp;gt;

  &amp;lt;script&amp;gt;
    setInterval(() =&amp;gt; {
      console.log(&quot;✅ [부모] 인터벌 실행됨&quot;, new Date().toISOString());
    }, 1000);
  &amp;lt;/script&amp;gt;

  &amp;lt;!-- iframe src를 localhost의 child.html로 변경 --&amp;gt;
  &amp;lt;iframe src=&quot;http://localhost:4000/child.html&quot; style=&quot;width:600px; height:200px; border:1px solid #ccc;&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6qOR/btsOnebMTtR/Pz8FA4vvkwuc6KOn67EUe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6qOR/btsOnebMTtR/Pz8FA4vvkwuc6KOn67EUe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6qOR/btsOnebMTtR/Pz8FA4vvkwuc6KOn67EUe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6qOR%2FbtsOnebMTtR%2FPz8FA4vvkwuc6KOn67EUe1%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;3456&quot; height=&quot;1996&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1996&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;h4 data-ke-size=&quot;size20&quot;&gt;site가 다른 경우 (child 배포)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;child.html을 vercel로 배포한뒤에 iframe으로 열었더니 크롬과 safari에서 다른 결과가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vtxRd/btsOoQU2FoN/FoGhHA7tKLEqxEzSEj4JKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vtxRd/btsOoQU2FoN/FoGhHA7tKLEqxEzSEj4JKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vtxRd/btsOoQU2FoN/FoGhHA7tKLEqxEzSEj4JKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvtxRd%2FbtsOoQU2FoN%2FFoGhHA7tKLEqxEzSEj4JKk%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;3456&quot; height=&quot;1720&quot; data-origin-width=&quot;3456&quot; data-origin-height=&quot;1720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬은 스레드가 블록되지 않지만, safari는 블록된다.&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Chrome vs Safari&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크롬은 정책상, iframe으로 사이트를 열 때 새로운 스레드를 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748928679698&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Site Isolation Design Document&quot; data-og-description=&quot;Site Isolation Design Document This design document covers technical information about how Site Isolation is built. For a general overview of Site Isolation, see https://www.chromium.org/Home/chromium-security/site-isolation. Chrome's multi-process archite&quot; data-og-host=&quot;www.chromium.org&quot; data-og-source-url=&quot;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&quot; data-og-url=&quot;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.chromium.org/developers/design-documents/site-isolation/#requirements&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Site Isolation Design Document&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Site Isolation Design Document This design document covers technical information about how Site Isolation is built. For a general overview of Site Isolation, see https://www.chromium.org/Home/chromium-security/site-isolation. Chrome's multi-process archite&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.chromium.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사파리는 그렇지 않고, 같은 스레드를 사용해 js를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해서, embed된 유튜브 스크립트를 평가할때 크롬의 경우에는 블로킹이 발생하지 않고, safari는 블로킹이 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPA의 특성을 활용해서 임베드 스크립트를 이전 페이지에서 미리 받아와서 평가하도록 만들어서 해결했다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>chrome</category>
      <category>iframe</category>
      <category>lottie</category>
      <category>Safari</category>
      <category>블로킹</category>
      <category>스레드</category>
      <category>크로스브라우징</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/390</guid>
      <comments>https://0422.tistory.com/390#entry390comment</comments>
      <pubDate>Tue, 3 Jun 2025 14:35:35 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 이웃알바 캠페인 회고 (16주차, 17주차)</title>
      <link>https://0422.tistory.com/389</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;16주차,17주차가 끝났다.&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;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dD32ll/btsOmJiArBf/9fiC9LD7ubAT8woFE9tYU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dD32ll/btsOmJiArBf/9fiC9LD7ubAT8woFE9tYU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dD32ll/btsOmJiArBf/9fiC9LD7ubAT8woFE9tYU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdD32ll%2FbtsOmJiArBf%2F9fiC9LD7ubAT8woFE9tYU1%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;533&quot; height=&quot;300&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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;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://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC/opt&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC/opt&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1748784999735&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;당근 이웃알바&quot; data-og-description=&quot;도움이 필요했던 순간 알려주고 1만원 받기&quot; data-og-host=&quot;daangn-jobs-campaign.karrotwebview.com&quot; data-og-source-url=&quot;https://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC/opt&quot; data-og-url=&quot;https://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZYJJq/hyY1cmROey/L0fAYaYDmQkM5jDwN8ylQ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/h81Ek/hyY06UuU88/stepdgmApkFTrqBbpZZEw0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC/opt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://daangn-jobs-campaign.karrotwebview.com/indvjobsBDC/opt&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZYJJq/hyY1cmROey/L0fAYaYDmQkM5jDwN8ylQ0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/h81Ek/hyY06UuU88/stepdgmApkFTrqBbpZZEw0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;당근 이웃알바&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;도움이 필요했던 순간 알려주고 1만원 받기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;daangn-jobs-campaign.karrotwebview.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;436&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCTl9n/btsOnrgi3QQ/WqCyNBoul656SiAv7tcopK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCTl9n/btsOnrgi3QQ/WqCyNBoul656SiAv7tcopK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCTl9n/btsOnrgi3QQ/WqCyNBoul656SiAv7tcopK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCTl9n%2FbtsOnrgi3QQ%2FWqCyNBoul656SiAv7tcopK%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;436&quot; height=&quot;208&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3000명이 넘는 사람이 이벤트에 참여했고, 페이지에 들어온 사람은 100배 이상 더 많다.&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;/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 data-ke-size=&quot;size16&quot;&gt;이것이 결과적으로 풀지 않아도 되는 문제들을 많이 풀게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 과정에서 많은 학습을 할 수 있었지만...&amp;nbsp; 다 지나고 보니 비효율적으로 개발을 한 것 같아 아쉬움이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영웅이 되려하지말고 프로가 되어라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 장인에는 &lt;b&gt;영웅이 되려하지 말고 프로가 되어라&lt;/b&gt;는 말이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 꽤나 실감했던 2주가 아니었나 싶다. 마감일이 명확하면 명확할 수록 기한을 맞추는 것은 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;될 것 같은데?&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;확실히 된다&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;&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;&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;&amp;nbsp;&lt;b&gt;어떠한 경우에&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&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;h4 data-ke-size=&quot;size20&quot;&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;적어도 iOS Safari와 Chrome에서는 둘다 정상적으로 동작해야한다.&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;예를들어 이번 캠페인에서는 영상과 Lottie에서 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;safari는 loop 영상을 재생할때 처음과 끝부분이 뚝 끊기는 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 iOS 저전력 모드에서는 자동재생이 되지 않는 문제가 존재한다.&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;h4 data-ke-size=&quot;size20&quot;&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;실제로는 QA시에 생각도 못한 이슈들이 터져나오기에, 이를 위한 시간을 고려해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스티브가 중간에 이 문제에 대해서 말씀해주셨었는데, 금요일 QA에서 나온 문제들을 해결하다보니, 이게 얼마나 잘못된 추정인지 실제로 체감할 수 있었다.&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;결과적으로는 &lt;b&gt;2주간 3개의 피쳐 개발&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;학습과 회고&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvOqbs/btsOm8vMo1q/u9EwDMbQ2nYRUuWLhCyxN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvOqbs/btsOm8vMo1q/u9EwDMbQ2nYRUuWLhCyxN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvOqbs/btsOm8vMo1q/u9EwDMbQ2nYRUuWLhCyxN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvOqbs%2FbtsOm8vMo1q%2Fu9EwDMbQ2nYRUuWLhCyxN1%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;189&quot; height=&quot;254&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 작업들을 스위칭해가면서 이어가다보니, 이번 2주동안은 어떤 활동을 했는지에 대한 daily 문서 데이터 작성이 많이 누락되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 체력이 부족해지고, 주말에도 QA항목을 처리하려다보니 회고를 위한 체력이 부족해졌다.&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;또한 이전부터 이어가고 있는 css학습을 할 수 없었다.&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;계속해서 이런 방식을 이어가는 것은 지속가능성이 없다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 다른 프로젝트를 할때도 기능 분석과 확정을 통해 비효율을 제거하는 것으로 방향을 잡아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UX의 의도와 관계없이 퍼널이 길어질수록 전환율은 감소한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터와 상호작용하는 UX를 제공하고자 했는데, 이 의도와 관계없이 목표로 하는 액션이 뒤에 있으면 뒤에 있을수록 전환율은 감소한다는걸 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 전환을 위한 버튼 하나가 들어갈때마다 이탈율은 눈에 띄게 증가한다. 다음 플로우를 위한 버튼 하나당 유저가 거의 반토막이 난다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/455601033&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Je9FR/hyY4dkGh3q/zKKEED1MNlq3ispmwgSsk1/img.jpg?width=940&amp;amp;height=1920&amp;amp;face=0_0_940_1920,https://scrap.kakaocdn.net/dn/5xfyf/hyY0xZqLdH/bF6GZRULV3AdStr2QIiCmK/img.jpg?width=940&amp;amp;height=1920&amp;amp;face=0_0_940_1920&quot; data-video-width=&quot;430&quot; data-video-height=&quot;879&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1757&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/455601033?service=daum_tistory&quot; width=&quot;430&quot; height=&quot;879&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음주부터는 별 일이 없다면 프로덕트 개발에 집중할 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 목표는 이전과 동일하다. daily data 작성에 조금 더 신경을 쓰자!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;스펙에 대한 이정표 만들기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;셀프 검증시 파괴에 집중해서 검증할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;기능에 대한 논의는 모두 기록 할 것 +&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능 논의는 모아서 한번에 처리하기&lt;/li&gt;
&lt;/ol&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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/389</guid>
      <comments>https://0422.tistory.com/389#entry389comment</comments>
      <pubDate>Tue, 3 Jun 2025 13:57:21 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 -15주차</title>
      <link>https://0422.tistory.com/388</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vttBj/btsN18oIbdL/LANiciDnD2NKUDFFuuC9OK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vttBj/btsN18oIbdL/LANiciDnD2NKUDFFuuC9OK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vttBj/btsN18oIbdL/LANiciDnD2NKUDFFuuC9OK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvttBj%2FbtsN18oIbdL%2FLANiciDnD2NKUDFFuuC9OK%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;447&quot; height=&quot;335&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;p data-ke-size=&quot;size16&quot;&gt;15주차가 끝났다.&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;h2 data-ke-size=&quot;size26&quot;&gt;되돌아보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXa6VG/btsN27bnQow/LQ2O9kqU0mnNAEK2VVBK90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXa6VG/btsN27bnQow/LQ2O9kqU0mnNAEK2VVBK90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXa6VG/btsN27bnQow/LQ2O9kqU0mnNAEK2VVBK90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXa6VG%2FbtsN27bnQow%2FLQ2O9kqU0mnNAEK2VVBK90%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;499&quot; height=&quot;356&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;748&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;이번주에는 아침 CSS 학습은 거의 하지 못한 것 외에는 목표로 잡은 것들을 잘 챙기고 있다.&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;UI 검증으로 얻는 신뢰감&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pixel pro를 사용한 검증을 통해 팀 내에서 신뢰를 얻을 수 있다는 것을 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 하는 일은 늘 실수가 발생한다. 이걸 방지하기 위한 시스템, 루틴이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로도 이 검수 과정을 꼭 유지할 필요가 있다고 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 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;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;&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;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 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;현재 작성된 스펙 자체에서 발생할 수 있는 엣지케이스가 있는지, 혹은 사용자의 UX를 좀더 개선할 수 있는지를 먼저 고민해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 경험에 대해 좀 더 생각하고 스펙에 대해 왜를 던져볼때 좀더 기능이 개선될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아침 CSS 학습&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번주뿐아니라 아마 다음주까지도 CSS 학습은 어려울 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 좀더 집중하는 형태나 주말에 부족한 부분을 학습해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;스펙에 대한 이정표 만들기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;셀프 검증시 파괴에 집중해서 검증할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;기능에 대한 논의는 모두 기록 할 것 +&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능 논의는 모아서 한번에 처리하기&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>회고/당근</category>
      <category>당근</category>
      <category>당근알바</category>
      <category>인턴</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <category>후기</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/388</guid>
      <comments>https://0422.tistory.com/388#entry388comment</comments>
      <pubDate>Sun, 18 May 2025 17:27:38 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 12, 14주차</title>
      <link>https://0422.tistory.com/387</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKHyyy/btsNTazTclC/qOJGfV3sFKA17Haikbuxjk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKHyyy/btsNTazTclC/qOJGfV3sFKA17Haikbuxjk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKHyyy/btsNTazTclC/qOJGfV3sFKA17Haikbuxjk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKHyyy%2FbtsNTazTclC%2FqOJGfV3sFKA17Haikbuxjk%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;428&quot; height=&quot;321&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용에 문제가 있는 경우 삭제, 수정조치 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12, 14주차가 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13주차는 워크샵을 다녀오면서 거의 1주일간 일을 하지 않아서 따로 회고에 넣지 않았다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXRnXg/btsNRPw1VjE/SliO9WuAfPh2ZA1HlJlZgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXRnXg/btsNRPw1VjE/SliO9WuAfPh2ZA1HlJlZgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXRnXg/btsNRPw1VjE/SliO9WuAfPh2ZA1HlJlZgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXRnXg%2FbtsNRPw1VjE%2FSliO9WuAfPh2ZA1HlJlZgK%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;583&quot; height=&quot;328&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;626&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;h3 data-ke-size=&quot;size23&quot;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모하는 습관&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 싱크미팅뿐만 아니라 스키마 논의나 스펙논의에도 가끔씩 참여하게 되었는데, 이럴때 특히 메모하는게 중요한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해야 회의에서 나온 내용들을 놓치지 않고 챙길 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI 검증은 한번에&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pixel pro를 사용해서 개발-검증 1회로 시간을 낭비하지 않고 검수하는 것을 유지해가고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 작성시 일관성과 정확성에 대해 의식을 갖고 챙기기&lt;/h3&gt;
&lt;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;/h3&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;아침 CSS학습&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;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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;기능에 대한 논의는 모두 기록할 것&lt;/span&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;느낀점&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;새로운 셀프리뷰 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;12주차에 스티브와 리뷰를 대면으로 진행했는데, &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;/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;/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;b&gt; 다른사람과 대면으로 리뷰하는 것과 거의 비슷한 느낌&lt;/b&gt;을 받을 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;더불어 이걸 녹화를 해서 ai에게 전달하여 피드백을 받아보려고했는데, 아직은 영상에 대한 처리는 어려웠다.&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;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/91hbR/btsNTH44xOz/v89wVqkMNJYR9hfgIysGi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/91hbR/btsNTH44xOz/v89wVqkMNJYR9hfgIysGi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/91hbR/btsNTH44xOz/v89wVqkMNJYR9hfgIysGi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F91hbR%2FbtsNTH44xOz%2Fv89wVqkMNJYR9hfgIysGi1%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;479&quot; height=&quot;345&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;864&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;피드백은 부가적인 것이고, 말하기 셀프리뷰를 통해 보다 좋은 퀄리티의 코드를 보장할 수 있게 될 것 같다.&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;코드를 넘어서 제품을 개선하기 위해&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인턴생활도 3개월이 됐고, 어느정도 맥락도 쌓였다보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 3개월은 코드를 넘어서 제품에 대한 고민도 해보라는 조언을 받게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 로그를 추가할때, 어떻게 하면 로그로부터 더 효과적인 인사이트를 얻어낼 수 있을지를 고민해서 적용해보고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 후에 데이터를 분석하면서 인사이트를 공유하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선이 필요한 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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 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://medium.com/@kpak/%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EA%B3%BC-%EB%B9%84%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%A9%EB%B2%95-2%EA%B0%80%EC%A7%80-6f16992dd4e0&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 그 &lt;b&gt;암묵적 가정이나 추론을 해야하는 경우가 생기기 때문에&lt;/b&gt; 어려운 것 같다.&lt;/p&gt;
&lt;figure id=&quot;og_1746959760113&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;엔지니어들과 비엔지니어들의 커뮤니케이션 방법 2가지&quot; data-og-description=&quot;가끔 엔지니어와 비엔지니어가 커뮤니케이션하는 것을 보면 아쉬운 점들이 있다. 내 경험상 이 두 그룹의 사람들이 커뮤니케이션할때 중요한 것 2가지를 얘기해 본다.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@kpak/%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EA%B3%BC-%EB%B9%84%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%A9%EB%B2%95-2%EA%B0%80%EC%A7%80-6f16992dd4e0&quot; data-og-url=&quot;https://medium.com/@kpak/%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EA%B3%BC-%EB%B9%84%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%A9%EB%B2%95-2%EA%B0%80%EC%A7%80-6f16992dd4e0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjS6z1/hyYPoBvExL/awaDPxdVqOQDGcNcPggugK/img.jpg?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cP9sk3/hyYRpzPT1H/uHSPKz3XH8W0bGn2E7YW0K/img.jpg?width=1358&amp;amp;height=955&amp;amp;face=0_0_1358_955,https://scrap.kakaocdn.net/dn/b4bE8D/hyYPnQaxyv/SYXV0kvj36A0kvjKKTDkr1/img.jpg?width=1024&amp;amp;height=576&amp;amp;face=416_145_576_319&quot;&gt;&lt;a href=&quot;https://medium.com/@kpak/%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EA%B3%BC-%EB%B9%84%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%A9%EB%B2%95-2%EA%B0%80%EC%A7%80-6f16992dd4e0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@kpak/%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EA%B3%BC-%EB%B9%84%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%93%A4%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%EC%BC%80%EC%9D%B4%EC%85%98-%EB%B0%A9%EB%B2%95-2%EA%B0%80%EC%A7%80-6f16992dd4e0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjS6z1/hyYPoBvExL/awaDPxdVqOQDGcNcPggugK/img.jpg?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cP9sk3/hyYRpzPT1H/uHSPKz3XH8W0bGn2E7YW0K/img.jpg?width=1358&amp;amp;height=955&amp;amp;face=0_0_1358_955,https://scrap.kakaocdn.net/dn/b4bE8D/hyYPnQaxyv/SYXV0kvj36A0kvjKKTDkr1/img.jpg?width=1024&amp;amp;height=576&amp;amp;face=416_145_576_319');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;엔지니어들과 비엔지니어들의 커뮤니케이션 방법 2가지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;가끔 엔지니어와 비엔지니어가 커뮤니케이션하는 것을 보면 아쉬운 점들이 있다. 내 경험상 이 두 그룹의 사람들이 커뮤니케이션할때 중요한 것 2가지를 얘기해 본다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실하게 아는 것, 확실하게 모르는 것, 추론한 것 세가지를 분류하고 이게 어떤 것인지 알려줘야하는데 마케팅 레포지토리를 처음 다뤄보기도 하고 하다보니 확실하게 안다고 할 수 있는 부분이 거의 없었고, 그래서 소통하는데 더 어려움이 있었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 이런경우네는 이 부분은 추측한 부분(혹은 모르는 부분)이라고 말해두고 전부 리스트업해서 이후에 체크하는 비동기 형태로 가는게 효율적일 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 정리하면 아래와 같다.&lt;/p&gt;
&lt;div style=&quot;text-align: left;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;스펙에 대한 이정표 만들기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것 + 추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;셀프 리뷰는 말하면서 진행할 것&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;기능에 대한 논의는 모두 기록 할 것 +&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;기능 논의는 모아서 한번에 처리하기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <category>회고/당근</category>
      <category>Front-end</category>
      <category>당근</category>
      <category>당근알바</category>
      <category>인턴</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <category>후기</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/387</guid>
      <comments>https://0422.tistory.com/387#entry387comment</comments>
      <pubDate>Sun, 11 May 2025 19:43:10 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고- 11주차</title>
      <link>https://0422.tistory.com/386</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDzVdi%2FbtsMFLAUWjW%2FJCWXOByofdJMgKtZagWqK1%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;527&quot; height=&quot;395&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;p data-ke-size=&quot;size16&quot;&gt;11주차가 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번주는 크게 느낀 것은 따로 없으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전주에 느낀바로 사이클을 잃으면 성장하기 어렵기 때문에 계속해서 지속한다는 관점에서 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;되돌아보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;544&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JsQGR/btsNsmVG5lD/KSRRsiYSrQZBbCWssoSK1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JsQGR/btsNsmVG5lD/KSRRsiYSrQZBbCWssoSK1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JsQGR/btsNsmVG5lD/KSRRsiYSrQZBbCWssoSK1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJsQGR%2FbtsNsmVG5lD%2FKSRRsiYSrQZBbCWssoSK1K%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;515&quot; height=&quot;325&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;544&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주는 구현뿐 아니라 데이터 분석도 했었어서 전반적으로 구현의 비율이 많이 낮아지게 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스펙에 대한 이정표 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 잘 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙을 분석하고 작성하고, 또 뭔가 변경되었을때 문서를 수정하면서 놓치는 부분이 없도록 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 변경이 되거나 의사결정이 필요한 부분이 생기는 경우, 이런 부분을 좀 한번에 처리할 수 있지 않을까? 라는 생각도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 빨리 처리하는 것도 너무 중요하지만, 급하게 처리하려다보니 모아서 처리할 수 있는 부분도 나눠서 논의하게 된 것 같아서 아쉬움이 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메모하는 습관 유지하기&lt;/h3&gt;
&lt;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;UI 검증은 한번에&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발-검증 1회로 전반적으로 시간을 낭비하지 않고 검수하는데 성공했다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 챙기려고 많이 노력했고, 잘 챙겨 졌다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가정과 사실 기록하고, 암묵적 가정을 하지 말 것&amp;nbsp;&lt;/h3&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;/h3&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;text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;아침 CSS학습&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;잘 진행중이다!&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선이 필요한 점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한번에 논의하기&lt;/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 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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 정리하면 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;  text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;스펙에 대한 이정표 만들기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;셀프 검증시 파괴에 집중해서 검증할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;기능에 대한 논의는 모두 기록 할 것 + &lt;b&gt;기능 논의는 모아서 한번에 처리하기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>회고/당근</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/386</guid>
      <comments>https://0422.tistory.com/386#entry386comment</comments>
      <pubDate>Sun, 20 Apr 2025 21:06:40 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 8,9,10주차</title>
      <link>https://0422.tistory.com/385</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2oFPI/btsNi1ElueN/ud6cOhJ8EZkuQxlShEntuk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2oFPI/btsNi1ElueN/ud6cOhJ8EZkuQxlShEntuk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2oFPI/btsNi1ElueN/ud6cOhJ8EZkuQxlShEntuk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2oFPI%2FbtsNi1ElueN%2Fud6cOhJ8EZkuQxlShEntuk%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;585&quot; height=&quot;439&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;8,9,10주차가 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴일, 휴가로 며칠씩 쉬다보니 모아서 써야지 하다보니 벌써 3주가 지나버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 3주(15일)동안 실제 일한건 11일정도라 사실상 2주차 회고가 아닌가... 싶기도하다.&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;h2 data-ke-size=&quot;size26&quot;&gt;되돌아보기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3주간 진행했던 기능 개발이 이전보다 간단했어서 그런 것도 있겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 잘 되었다고 생각한다. 마지막에 한번 체크하는 형태로 전환하면서 UI에 대한 검증 시간이 크게 감소했다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분도 이전보다는 훨씬 나아지지 않았다 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pull request extension을 통해 1차로 셀프 검증하고, cursor agent를 통해 git commit을 넣어 확인하는 형태로 챙길 수 있었다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요것도 이전보다 좀더 나아졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람과 공유할때는 excalidraw를 사용해서 공유하고, 혼자 정리할때는 메모장에 그려보는 형태로 정리할 수 있었다.&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;h4 data-ke-size=&quot;size20&quot;&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 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;h4 data-ke-size=&quot;size20&quot;&gt;아침에 30분씩 CSS학습&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS완벽가이드를 통해 학습하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 30페이지씩 읽는 걸 목표로 하고 있으며, 업무일 11일 모두 읽어 330페이지를 읽고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 아침에 공부하는게 유효하다고 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습한 것을 바로 사용해봄으로써 진짜 나의 지식이 되는 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잘한 점&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설계/문서화 시간&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3주간 그래도 이전보다는 설계/논의, 문서화 비율이 많이 올라왔다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스펙을 좀 더 분석하고, 노션문서를 더 상세하게 적으면 적을 수록,&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 짜는동안 의식적으로 코드 품질에 대해 생각할 수 있게 되고, 결과적으로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;품질을 더 보증할 수 있게 된다는걸 느꼈다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; 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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XLNGe/btsNjDiFeFo/09NZSW4YDqwnKqFn7Fl7z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XLNGe/btsNjDiFeFo/09NZSW4YDqwnKqFn7Fl7z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XLNGe/btsNjDiFeFo/09NZSW4YDqwnKqFn7Fl7z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXLNGe%2FbtsNjDiFeFo%2F09NZSW4YDqwnKqFn7Fl7z1%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;767&quot; height=&quot;148&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&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;&lt;span style=&quot;font-size: 1.62em; letter-spacing: -1px; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif;&quot;&gt;개선이 필요한 점&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회고를 지속하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3주간, 절대적인 업무시간이 적었기에 몰아서 해야지하고 넘겨왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로 인해서 피드백 사이클이 늦어지고, 목적 의식이 흐릿해지는 부분이 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 이전에 학습했던 것들이 이어지지 않게되고, 개선되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 의식적으로 챙겨할 부분들을 생각하고, 셀프 피드백하면서 더 개선해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 주에 별 내용이 없더라도 피드백 주기는 지켜야한다.&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;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;하지만, 설계도 형태로 &quot;여기에는 이것&quot; 보다는,&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;/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;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;더 좋은 가치를 전달하기위해 더 좋은 개발자가 되어야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표를 정리하면 아래와 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;스펙에 대한 이정표 만들기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;메모장을 준비하고, 메모하는 습관 유지하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;내용이 없어도 회고하는 사이클 지키기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 목표도 여전히 지켜나갈 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;셀프 검증시 파괴에 집중해서 검증할 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;기능에 대한 논의는 모두 기록 할 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고/당근</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/385</guid>
      <comments>https://0422.tistory.com/385#entry385comment</comments>
      <pubDate>Sat, 12 Apr 2025 16:28:51 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 6,7주차</title>
      <link>https://0422.tistory.com/384</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HwsoL/btsMTkjXsSN/yijsGjCjHuVzVgLvPyXnak/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HwsoL/btsMTkjXsSN/yijsGjCjHuVzVgLvPyXnak/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HwsoL/btsMTkjXsSN/yijsGjCjHuVzVgLvPyXnak/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHwsoL%2FbtsMTkjXsSN%2FyijsGjCjHuVzVgLvPyXnak%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;467&quot; height=&quot;350&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용에 문제가 있는 경우, 삭제, 수정조치하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6,7주차가 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6주차 주말에는 너무 정신이 없어서 작성하지 못해서 이번주에 한번에 회고하고자 한다.&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;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;도구를 통해 UI정확성 확인하고, 수치값으로 한번 더 확인하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 PixelParallel보다 좋은 Pixel Perfect Pro라는 확장 익스텐션으로 변경했고, 작업중에 지속적으로 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전히 UI에 대한 정확성을 100% 챙길수는 없었지만 그런 부분이 줄어들었다.&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;100%가 될 수 없었던 이유는, Figma와 브라우저 렌더링 방식이 100% 동일하지 않았기때문이다.&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;1. 폰트크기가 같아도 브라우저와 피그마간에 미묘한 크기차이가 발생하고, 이로 인해 줄바꿈이 발생하는 경우 확인하기가 어려워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. border의 경우 1px이 들어가고 빠지고에 따라 뒷부분에 렌더링되는 요소들이 1px밀리기도 한다.&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;UI 퍼블리싱 -&amp;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;/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 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;또한 너무 큰 부분에 대해 설계해서 디테일한 부분에 대한 설계가 부족했고, 이러한 부분들때문에 디테일하게 놓치는 부분들이 발생했다. DBC가 잘 안이뤄졌다는 증거이기도 하다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6,7주차에 잘한 점이라면.... 어떻게든 포기하지 않고 맡은 테스크에 대해 책임감을 갖고 개발을 했다는 점이랄까...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘한점 보단 개선할 점을 왕창 느낄 수 있었던 두 주였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cursor rule 및 MCP도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀게만 느껴졌던 cursor rule과 MCP를 도입해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;figma mcp와 browser tools mcp, sequential thinking을 도입했고 아래와 같이 사용해봤다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;figma UI가 코드로 잘 구현되었는지 검증&lt;/li&gt;
&lt;li&gt;코드 일관성/엣지케이스 검증&lt;/li&gt;
&lt;/ol&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;figma UI 검증&lt;/b&gt; : 뭔가 리뷰를 해주긴 하지만, 유효하지는 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 일관성 검사&lt;/b&gt; : 생각보다 유효했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엣지케이스 검증&lt;/b&gt; : 그렇게 효과가 있진 않지만, 그래도 안하는 것 보단 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선이 필요한 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6,7주차에는 신규 기능 개발을 하게됐고, 개선할 점들을 너무나도 많이 느낄 수 있었던 주차였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우연에 맡기는 프로그래밍, 그로 인한 불안감&lt;/h3&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;/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;적극적으로 내가 무슨 코드를 작성했는지 생각하고, 개선하자.&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;였다. (복잡한 타입 다루기, CSS디버깅 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 것에 대해 해결을 어떻게든 해내긴하지만, 과정에서 이전 작업에 대한 맥락을 잃는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 맥락에서 뭘 하고 있는지 잊게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 문제를 해결했다는 그 효용감이 큰 맥락에서 디테일을 잃게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;6,7주차 동안, 계속해서 내면의 불안감과 싸워야했다.&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;이 근본적인 원인이 우연에 맡기는 프로그래밍때문&lt;/b&gt;임을 알게 됐다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;무엇인가 잘못 됐다면, 그것을 말로 설명할 수 없다 하더라도 불안하고 초조함을 느끼게 된다.&lt;br /&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;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;작업시 암묵적 가정을 갖고 작업한다는 걸 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 상황적 우연에서 패턴을 찾아 상관없는 부분에 대한 디버깅을 하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잘되는 듯한 답을 찾는 것과 올바른 답을 찾는 것은 다르며,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확고한 사실에 근거하지 않은 가정은 프로젝트에서 재앙의 근원이 된다.&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;개선방안&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실용주의 프로그래머에서는 불안감이 느껴진다면, 아래의 방법을 통해 어떤 일을 하고 있는지 자세하게 정리해보라 제안한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;코드에 대한 그림을 그려보라.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가정을 항상 기록해라. (문서화) -&amp;gt; 근거를&amp;nbsp;&lt;/b&gt;&lt;b&gt;신뢰하기 어렵다면 최악의 상황을 가정하라&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추측만 하지말고 직접 시험하라.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 요소들을 내 개발 환경에 적용해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 맥락을 잃지 않기 위해 어려운 문제를 줄여나가야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적어도 CSS문제는 공부해서 해결할 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아침에 30분씩이라도 CSS 책을 통해 이론을 쌓아 CSS에 대해 확인하고 디버깅하는 시간을 줄이려고한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;셀프 검증시의 문제&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이상하리만큼 셀프 검증시에는 보이지 않던 문제들이 코드리뷰나 QA시에서는 나타났다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;개선&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 초점을 맞추면 문제가 보려하기보다는 잘 되는 코드를 더 나아지게 만들려는 경향성을 갖게 되기 때문에 문제를 찾기가 어렵다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이에 코드 검증시 DBC기반으로 입력/출력에 대해 생각해보되 방향성 자체를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내 코드를 개선한다&lt;/b&gt;가 아니라,&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;남이 쓴 코드라고 생각하고, 이걸 박살내려면 어떻게 해야하지?&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 고민해볼 필요가 있다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무엇보다 기능개발이 완료됐다고 작업이 완료된 것이 아니라는 것을 명심할 필요가 있다고 느꼈다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그건 10%만 완료된 것이다. 나머지 90%는 그것을 검증하는 것이다.&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;/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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능에 대한 논의는 구두로 하더라도 반드시 문서를 남기자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;6,7주차에는 있는 기능을 유지/보수하는 것과 신규 기능 개발을 하는 것이 얼마나 많은 차이가 있는지를 느낄 수 있는 주였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 부족한 부분도 많이 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분들을 개선할 때 더욱 성장할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;UI 검증은 반복적이지 않도록 되도록 한번에 할 것&lt;/li&gt;
&lt;li&gt;코드 작성시 코드 일관성과 정확성에 대해 의식을 갖고 챙길 것&lt;/li&gt;
&lt;li&gt;불안감이 느껴진다면, 그림을 그려서 이유를 찾을 것&lt;/li&gt;
&lt;li&gt;가정과 사실을 기록하고, 암묵적 가정을 하지 말 것&lt;/li&gt;
&lt;li&gt;추측하지 말고, 시험할 것&lt;/li&gt;
&lt;li&gt;아침에 30분(+)씩 CSS 학습&lt;/li&gt;
&lt;li&gt;셀프 검증시 파괴에 집중해서 검증할 것&lt;/li&gt;
&lt;li&gt;기능에 대한 논의는 모두 기록 할 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/384</guid>
      <comments>https://0422.tistory.com/384#entry384comment</comments>
      <pubDate>Sun, 23 Mar 2025 14:39:46 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 5주차</title>
      <link>https://0422.tistory.com/383</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDzVdi/btsMFLAUWjW/JCWXOByofdJMgKtZagWqK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDzVdi%2FbtsMFLAUWjW%2FJCWXOByofdJMgKtZagWqK1%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;527&quot; height=&quot;395&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용에 문제가 있는 경우 삭제, 수정조치 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5주차가 끝났다. 이번 주는 대체 휴일로 하루를 쉬어서 4일동안 일했기도했고 저번주에 설계한 것들을 바탕으로 리팩터링을 하거나 코드보다 스펙에 대해 이해하고 설계하는 시간이 대부분 이었어서 뭔가 따로 세운 목표를 적용해볼 기회가 좀 적었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;되돌아보기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;뽀모도로 휴식시간에 다음 계획 더 잘 작성해보기&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이 부분은 좀 더 개선된 것 같다. 여전히 가끔은 놓치기도 하지만....&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;어떤 테스크를 한다 -&amp;gt; 어떤 테스크중에 어떤 부분을 한다 형태로 좀 더 구체적으로 작성하려고 노력했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;그리고 GPT사용해서 자체적으로 만든 업무 기록표를 넣으면 보고서를 만들도록 프롬포트를 만들어뒀는데, 이게 생각보다 유용해서 좀더 정확한 정보/인사이트를 얻기 위해서 앞으로 더 잘 작성하기위한 동기부여가 되는 것 같다.&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;스크린샷 2025-03-09 오전 9.36.16.png&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vCxok/btsMDYbheZu/dtKKUU2wLWndkQO3KrKBs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vCxok/btsMDYbheZu/dtKKUU2wLWndkQO3KrKBs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vCxok/btsMDYbheZu/dtKKUU2wLWndkQO3KrKBs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvCxok%2FbtsMDYbheZu%2FdtKKUU2wLWndkQO3KrKBs1%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;617&quot; height=&quot;317&quot; data-filename=&quot;스크린샷 2025-03-09 오전 9.36.16.png&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;UI 퍼블리싱시 정확성을 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;설계-코드작성-확인&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;을 작은 단위로 진행하여 정확성 향상시키기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이 부분은 노력은 했지만, 결과적으로는 잘 되지 않았다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;특히 개발 중간에 스펙이 바뀌는 경우가 있었는데, 이때 이전 사용하던 스펙을 그대로 쓸 수 있었기에 이것들을 다시 확인하지 않게 되어 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이전주에 느낀 것처럼, 상황이 바뀌면 다시 다 확인해봐야하는데 그 부분을 놓쳤다.&lt;/span&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;화면 기반의 추상화를 진행하고 간단 명료한 인터페이스 짜기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;이번주에는 로직관련 작업을 따로 하지는 않아서 코드로 짜지는 못했다. 하지만, 설계시에 고려해서 설계를 할 수 있었다.&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;CSS 이론 학습하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 mdn문서를 시간남을때마다 보고는 있지만, 조만간에 책 한권을 전부 읽는 형태로 진행해볼 생각이다.&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;AI를 활용해서 생산성을 높히기 위한 방법이나 툴 도입하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT를 통해서 내 업무 데이터를 기반으로 얻을 만한 것들을 챙겨보게 됐다!&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;또한 index.html이 굉장히 복잡하다는 문제가 있었는데, next처럼 동작하는 프레임워크를 만들었을 때의 경험을 살려서 이러한 문제를 index.html을 빌드하는 형태로 개선을 진행했다.&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개선이 필요한 점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;UI 정확성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 정확성이 문제다. 개선이 너무나 필요하다.&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;요구사항이 변했다면 다시 모든 것을 확인해야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업했다가 디자인 스펙이 바뀌어서 다시 작업을 해야했는데, 요소가 추가되면서 패딩값이 달랐던 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제가 지속되면 나의 생산성은 물론, 팀의 생산성이 낮아질 수 있다.&lt;/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;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;다음 주에는 UI작업시 &lt;b&gt;PixelParallel&lt;/b&gt;이라는 chrome extensions을 도입해서 UI에 대한 정확성을 높이고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;이 도구를 사용하면 피그마 작업물을 png로 export하고, 이걸 브라우저에 띄워서 겹쳐서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f1f1f; text-align: start;&quot;&gt;가장 직관적이고 시각적으로 확인할 수 있는 부분이라, 이걸로 1차적으로 확인하고, 값을 확인하면서 2번 검수해보려고한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;느낀 점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;번들을 분석하다 이슈와 관련없는&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;데이터와 AI&lt;/h3&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 data-ke-size=&quot;size16&quot;&gt;업무 일지에 대한 리포트를 만들어내는 걸 보고 더 많은 곳에 사용할 수 있을 거라고 생각하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&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;1. 뽀모도로 휴식시간에 다음 계획 더 잘 작성해보기&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. UI 퍼블리싱시 정확성을 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;설계-코드작성-확인&lt;/b&gt;을 작은 단위로 진행하여 정확성 향상시키기&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. 화면 기반의 추상화를 진행하고 간단 명료한 인터페이스 짜기&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;4. CSS 이론 학습하기&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;5.&lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI를 활용해서 생산성을 높히기 위한 방법이나 툴 도입하기 -&amp;gt; 테스크 쪼개기/시간관리 쪽으로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번주에는 여기에 더해 몇 가지를 더해보려고하는데 AI리포트 기반으로 얻은 것을 적용해보고자한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lPl4a/btsMFEhM0Lw/kFKm7PdWYTVbwVKsD0OPj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lPl4a/btsMFEhM0Lw/kFKm7PdWYTVbwVKsD0OPj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lPl4a/btsMFEhM0Lw/kFKm7PdWYTVbwVKsD0OPj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlPl4a%2FbtsMFEhM0Lw%2FkFKm7PdWYTVbwVKsD0OPj1%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;881&quot; height=&quot;254&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;우선 내 기록을 돌아보면 현재, &lt;b&gt;구현의 비중이 높고, 설계/논의 비중이 낮은 편이다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;설계시간을 조금 더 높히면 재구현이나 리팩터링 하는 시간을 좀 더 줄일 수 있지 않을까? 라는 생각이 들어서&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의도적으로 설계에 대한 시간을 늘려보고자한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;어느정도 마무리되었다고 생각했을때,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;엣지케이스가 없는지, 더 나은 방법은 없는지에 대해 30분정도를 더 고민해보고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;또한 설계시에 DBC(Design By Contract) 에서 사용하는 것들을 일부 적용해볼 생각이다. (feat. 실용주의 프로그래머)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;유효한 입력 범위는 무엇인가?&lt;/li&gt;
&lt;li&gt;경계 조건은 무엇인가?&lt;/li&gt;
&lt;li&gt;함수가 뭘 전달한다고 약속하고, 무엇을 약속하지 않는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 추가적으로 잡은 목표는 두가지다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;span&gt;PixelParallel를 통해 UI 정확성 확인하고, 수치값으로 한번 더 확인하기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;2. 의도적으로 설계에 대한 시간 늘리기 (30분 더 고민해보기/ 설계시 DBC 개념 일부 적용해보기)&lt;/span&gt;&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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/383</guid>
      <comments>https://0422.tistory.com/383#entry383comment</comments>
      <pubDate>Sun, 9 Mar 2025 21:48:11 +0900</pubDate>
    </item>
    <item>
      <title>패키지 업데이트를 위한 yarn.lock에 대한 이해</title>
      <link>https://0422.tistory.com/382</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xBBD0/btsMAtuqrha/0PK9HoQw6LYKyuffvkmAFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xBBD0/btsMAtuqrha/0PK9HoQw6LYKyuffvkmAFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xBBD0/btsMAtuqrha/0PK9HoQw6LYKyuffvkmAFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxBBD0%2FbtsMAtuqrha%2F0PK9HoQw6LYKyuffvkmAFK%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;250&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지 &lt;b&gt;패키지 관리&lt;/b&gt;에 대해서는 뭔가 부분적으로 이해하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 경험 자체가 패키지를 설치하거나, 만든 패키지를 배포하기 위한 버전 관리였기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험은 버전에 대해 심화적으로 살펴볼 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 버전정보와 관련한 것들은 틸드, 캐럿에 대해서만 어느정도 이해하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이번에 Typescript를 비롯한 여러 패키지의 버전을 업그레이드하는 경험을 할 수 있었고, 이때 학습한 것들을&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;yarn.lock파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json은 어떤 패키지를 사용하는지 대략적인 정보만을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn.lock파일은 package.json에 기록된 라이브러리가 실제로 어떤 버전으로 설치된 것인지를 기록한 파일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;덕분에 여러 개발자가 같은 프로덕트를 개발할때 동일한 라이브러리를 설치할 수 있게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;딱 여기까지가 알고 있던 내용이었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뭔가 버전 업데이트를 한다고 하면, lock파일을 좀더 깊게 살펴볼 필요가 있었다.&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;왜 package.json에 정확한 버전을 표기하지 않을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json을 들여다보면, 버전 정보를 정확하게 고정하지 않고 ^와 ~를 통해 버전을 정의한다.&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;그런데 왜 이런식으로 설계 됐을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그냥 정확한 버전으로 package.json에 정의해버리면 어떤 문제가 있을까?&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;아래와 같이 패키지를 사용한다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1740989477147&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;dependencies&quot;: {
  &quot;packageA&quot;: &quot;1.2.3&quot;,
  &quot;packageB&quot;: &quot;1.3.0&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 packageA의 react는 16를 사용하고, packageB의 react는 18을 사용한다면 참조시 충돌이 일어나서 제대로 동작하지 않을 수 있다.&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;2. 중복 설치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 상황처럼 메이저 버전이 아니라, 패치버전만 다르다고 가정해보자.&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;/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;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;yarn upgrade를 하면 하위호환성을 지키는 형태로, 업그레이드가 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 패치나 마이너 업데이트는 인터페이스를 변경하지 않고, 버그를 잡는 업데이트이기 때문에 한번에&amp;nbsp; 자동으로 업그레이드 할 수 있다면 한번에 관리하기 용이할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 고정된 버전으로 관리한다면, 일일히 해당 패키지를 확인하고 마이너버전, 패치버전에 맞게 하나씩 고정 버전으로 올려줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별게 아닌 것 같지만 수십개라면 굉장히 비용이 드는 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;yarn.lock 살펴보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 yarn.lock파일을 살펴보자. yarn.lock은 아래와 같이 구성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740990578094&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@some-package@^1.0.0: //package.json에 정의된 버전
version: &quot;1.0.0&quot; // 실제 설치된 버전
resolved &quot;some url/HASH_VALUE&quot; // 패키지가 다운된 URL+해시값
integrity &quot;hash value&quot; // 해시값
dependencies: 
	&quot;의존 패키지&quot;: &quot;^3.0.0&quot; //some-package의 package.json에 정의된 버전&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 이름이 package.json에 정의된 버전이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 틸드와 캐럿을 통해 표기한 버전으로, 정확히 어떤 버전이 설치됐는지를 보기위해서는 version필드를 살펴보아야한다.&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;resolved필드는 해당 패키지를 설치한 URL+SHA-1해시값을 표기하는데, 뒤의 해시값이 해당 파일이 변조되지 않았는지를 검사한다. 해당 URL에서 .tgz파일을 받아오는데, 이 파일의 내용을 해시한 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;integrity도 동일하게 .tgz를 해시한 값을 나타내는데 sha-512으로 해시한 값이다. 취약점이 개선된 해시함수를 써서 더 강력하다.(SHA-1은 취약점이 있어서 취약한 해시함수다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실질적인 해시값 검증은 integrity가 하고, resolved는 하나의 url이라고 볼 수 있다.&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;그리고 이 패키지가 사용하는 패키지들이 dependencies에 표기되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 dependencies들은 @some-package@^1.0.0이 의존하고 있는 패키지들의 목록이며, 해당 패키지 package.json에 정의된 버전 정보가 기록되어있다. 따라서 이 버전은 정확한 버전이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 버전을 보려면 &lt;b&gt;의존 패키지@^3.0.0&lt;/b&gt;으로 기록된 부분을 재귀적으로 확인하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만약 같은 라이브러리의 다른 두 가지 메이저 버전이 설치됐다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn.lock에서는 아래와 같이 표기되게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740991974465&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;some-dash@^5.17.20:
  version &quot;5.17.20&quot;
  resolved &quot;https://registry.yarnpkg.com/some-dash/-/some-dash-5.17.20.tgz&quot;
  integrity sha512-xxxx
  dependencies:
    other-lib &quot;^1.2.3&quot;

some-dash@^4.18.0:
  version &quot;4.18.0&quot;
  resolved &quot;https://registry.yarnpkg.com/lodash/-/some-dash-4.18.0.tgz&quot;
  integrity sha512-yyyy
  dependencies:
    other-lib &quot;^1.3.0&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;p data-ke-size=&quot;size16&quot;&gt;보통은 의도하지 않았을 것이다. 의존성 자체는 하나를 바라보게 하는게 장기적으로 봤을 때 안정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우 해결하는 방법은 두 가지로 볼 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해당 메이저 버전을 사용하는 라이브러리 탐색하여 그 라이브러리를 업데이트하는 방법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;some-dash를 5버전으로 통일한다고 하면, 아래와 같이 진행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. some-dash@4.18.0을 dependencies로 가지는 패키지를 yarn.lock에서 탐색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 찾은 패키지가 some-dash 5버전을 사용하는 버전이 있는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그 버전으로 업데이트&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;package.json의 resolutions 필드 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn.lock이 아닌, package.json의 resolutions필드에 버전정보를 강제해서 모든 패키지가 이 버전을 사용하게 하는 방법이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740992274830&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;dependencies&quot;: {
    ...
  },
  &quot;resolutions&quot;: {
    &quot;some-dash&quot;: &quot;5.18.0&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>package.json</category>
      <category>Upgrade</category>
      <category>yarn</category>
      <category>yarn.lock</category>
      <category>라이브러리</category>
      <category>버전</category>
      <category>업그레이드</category>
      <category>업데이트</category>
      <category>의존성충돌</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/382</guid>
      <comments>https://0422.tistory.com/382#entry382comment</comments>
      <pubDate>Mon, 3 Mar 2025 17:59:32 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 4주차</title>
      <link>https://0422.tistory.com/381</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJlJO6/btsMA5ftmSA/EnUuAaABFIcMQqBQWYOOp0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJlJO6/btsMA5ftmSA/EnUuAaABFIcMQqBQWYOOp0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJlJO6/btsMA5ftmSA/EnUuAaABFIcMQqBQWYOOp0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJlJO6%2FbtsMA5ftmSA%2FEnUuAaABFIcMQqBQWYOOp0%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;539&quot; height=&quot;404&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;p data-ke-size=&quot;size16&quot;&gt;벌써 한달차가 끝나버렸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;돌아보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이전 주 목표를 기반으로 돌아보고, 느낀 것들을 적어보려고 한다.&lt;/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;1. 뽀모도로 유지 및 좀 더 잘 작성하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 주와 비슷하게 유지되었다. 다만, 좀더 익숙해져서 다음 일을 계획하거나 기록하는 것은 조금 개선됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직까지 완벽하게 일하다가 돌아와서 5분간 다음 할일을 계획하는 수준은 아니어서 이 부분에 대해서는 계속해서 의식적으로 인지해서 개선할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;2. 문제가 안풀릴 땐 다시 상단부터 다시 좁혀나가기 + &lt;span style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot;&gt;코드/맥락 파악시 그림을 적극 이용하기&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번 주에 리팩토링을 진행할때 적극적으로 활용할 수 있었던 부분이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;추상화 방식 자체가 잘못되었을 수도 있겠다는 생각에 좀더 상단으로 돌아가 생각을 했고, 보다 나아진 추상화 인터페이스를 생각해낼 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3. 문제에 새로운 상황이 더해졌을 때 확인해보고 기존 뷰를 빠르게 버리고 새로 시작하기&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;하지만, 대부분의 프론트엔드 코드에서 추상화된 인터페이스를 소비하는 것은 UI다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;결국 데이터를 다룬다고 해도 컴포넌트에서 해당 코드를 소비한다면 인터페이스 자체는 UI에 맞춰져 있어야한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;4. AI를 활용해서 생산성을 높히기 위한 방법이나 툴 도입하기 -&amp;gt; 테스크 쪼개기/시간 관리 쪽으로&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 부분은 아직까지 깊게 고민해보지 못했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;노션 AI를 사용해서 오늘 하루 어떤 일을 했는지 요약하는 정도로 도입했다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;h3 style=&quot; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;잘한점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개발자 작업 환경 개선을 위해&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 더 나아진 개발자 작업 환경을 구성하기 위해 노력한 점이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나은 타입 추론을 해서 코드량을 줄이기위해 typescript를 업데이트하고, 반복되는 컨벤션에 대해 lint룰을 만들고, 관리되기 어려운 코드를 다루는 방식을 변경하는 등 진행하면서 팀의 생산성을 높히기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험은 코드베이스가 작은 환경에서는 하기 어려운 경험이기에 너무나도 소중한 경험이 아닌가 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이런 경험을 하면서 배우는 것들도 꽤 되어서 &lt;b&gt;누군가가 해야하고, 그것이 모두에게 도움이 된다면 내가 한다&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;/h4&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;UX자체를 개선하는 것도 중요하지만, 이런 식으로 자원이나 환경을 개선하는 것도 유의미할 수 있겠다는 생각을 하게한 경험이었다.&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;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;정확성&lt;/h4&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 tailwind로 개인 프로젝트를 진행하거나, 이미 잘 만들어진 디자인 컴포넌트를 사용해서 퍼블리싱을 해서 일까, 스펙과 동일하게 UI를 구성했다고 생각했으나 마진이나 패딩이 다른 부분들이 꽤 많이 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리뷰가 아니었다면, 전혀 인지하지 못했을 것이다. 이런 부분은 다시는 일어나서는 안되는 일이라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;무엇이 잘못 됐는 지를 몰랐다. 그건 너무 큰 일이다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 나는 다른 부분은 부족할지 몰라도, UI 퍼블리싱에 있어서는 정확하게는 완벽하지는 않더라도 어느 정도는 맞출 수 있다고 자신했다. 그래서 따로 UI퍼블리싱을 할때 설계를 하지도 않았고, 좀 더 빠르게 만들어 내는 것에 집중하려했지만, 그렇지 않다는 것을 알게 됐다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;span style=&quot;text-align: left;&quot;&gt;이번 주에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;정확하게&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 좀 더 집중을 해보려고한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 UI를 모두 체크하기란 쉽지않은 일이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이런 부분을 개선하기 위해서 UI자체를 작은 단위로 설계한 뒤에 만들고 한번 확인할 때 완벽하게 끝낸다는 생각으로 임하려고 한다.&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;&lt;b&gt;UI를 설계하고 코드로 작성하고, 확인하는 과정을 하나의 사이클로 만들고, 이 과정을 반복하면서 UI퍼블리싱에 대한 정확성을 가져가고자한다.&lt;/b&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;CSS 지식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리뷰를 받으면서 또 느낀 것이, CSS를 이론적으로 정말 잘 모르는구나를 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스펙대로 화면을 구현할 수는 있으나, 좀더 최적화되고 나은 방식으로 CSS를 만드는 방식을 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 css코드에 필요 없는 스타일링 코드가 많이 들어가게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제는 단순히 이렇게 바라보면 별개 아닌 것 처럼 느껴질 수 도 있지만, 이게 잘 안되면 js로 코드를 짜게되고, 이러면 실제 사용자 환경에서 프로덕트가 리소스를 더 잡아먹는 작업을 하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분을 개선하려면&amp;nbsp;CSS에 대한 이론 지식을 공부할 필요가 있다고 느꼈다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주엔 작성한 것 처럼 크게 UI퍼블리싱과 CSS에 대한 부분, 그리고 추상화에 대한 부분을 다시 고민할 수 있었다.&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;데이터를 받아오기는 하지만, 데이터를 소비하는 곳은 UI다.&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;프로덕트 화면에서 추상화는 데이터 기반이 아닌 UI기반&lt;/b&gt;으로 이뤄져야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전반적으로 이번주 목표를 정리하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 뽀모도로 휴식시간에 다음 계획 더 잘 작성해보기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. UI 퍼블리싱시 정확성을 위해 &lt;b&gt;설계-코드작성-확인&lt;/b&gt;을 작은 단위로 진행하여 정확성 향상시키기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 화면 기반의 추상화를 진행하고 간단 명료한 인터페이스 짜기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. CSS 이론 학습하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. AI를 활용해서 생산성을 높히기 위한 방법이나 툴 도입하기 -&amp;gt; 테스크 쪼개기/시간관리 쪽으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 회고글을 적는 것은 나의 성장을 위한 것도 있겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀의 목표를 달성하기 위해서이고, 팀 내부에서도 나의 역할을 다하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 역할은 여러가지가 있을 수 있겠지만 최우선은 주어진 테스크들을 빠르고, 정확하게 이해하고, 이를 효율적으로 해결해내는 것이라고 생각한다. 이 역할은 가장 기초적인 역량이다. 어디에서나 필요하다.&lt;/p&gt;
&lt;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 한달쯤 되어가다보니 이전 주에 세웠던 목표들을 제외하면 이전 목표들이 흐릿해져가는 경향이 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 다시 새겨보며 맥락을 계속해서 가져가고자한다.&lt;/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;h4 data-ke-size=&quot;size20&quot;&gt;목표&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문제가 무엇인지 정확히 파악할 것 (그 전까지 코드를 보기보다는 문제를 더 잘 정의할 것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 문제를 파악할때, 문제 해결에 필요한 것들이 무엇이 있을지 생각해볼 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 시간 측정에 대한 정확도를 높힐 것(뽀모도로 방법론 - 계획시간을 더 잘 쓸 것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 코드 컨벤션을 파악해서 쪼갠 문제 푸는 시간을 줄일 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 어떤 것이 확인이 필요한지 생각을 문서로 정리하고, 마친 뒤 빠진 것은 없는지 다시 확인할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 할 수 있는 것과 없는 것을 빠르게 확인하고, 할 수 없는 부분에 대해 물어봐서 낭비되는 시간을 줄일 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 문제가 안풀릴 때, 새로운 상황이 추가됐을 때는 빠르게 전체적으로 확인해보고 필요 시 기존 뷰를 빠르게 버리고 새롭게 시작할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 코드/맥락 파악시-소통시에는 그림을 적극적으로 사용할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. AI를 활용해서 생산성을 높히기 위한 방법/툴 도입할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. UI 퍼블리싱시 정확성을 위해 설계-코드작성-확인을 작은 단위로 진행하며 정확성 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 프로덕트 컴포넌트에서는 화면 기반의 추상화, 간단명료한 인터페이스를 구성할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. CSS이론을 학습해서 최적화할 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고/당근</category>
      <category>당근</category>
      <category>당근알바</category>
      <category>인턴</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/381</guid>
      <comments>https://0422.tistory.com/381#entry381comment</comments>
      <pubDate>Mon, 3 Mar 2025 14:56:34 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 3주차</title>
      <link>https://0422.tistory.com/380</link>
      <description>&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;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deKEON/btsMtthbMiF/99SwHz6i9OXmei0MO5zNt1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deKEON/btsMtthbMiF/99SwHz6i9OXmei0MO5zNt1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deKEON/btsMtthbMiF/99SwHz6i9OXmei0MO5zNt1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeKEON%2FbtsMtthbMiF%2F99SwHz6i9OXmei0MO5zNt1%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;500&quot; height=&quot;375&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;960&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;p data-ke-size=&quot;size16&quot;&gt;점점 시간이 빨리 간다. 3주차가 지나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;돌아보기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;아마 이 목표는 다음 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;그래도 하다보니 5번 중에 한번은 맞게 예측하는 경우도 생기는 것 같다.&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;우선 이건 한 주만에 해결되는 문제는 아니다. 우선은 측정을 꾸준히 잘 해볼 필요가 있다.&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;&amp;nbsp;보다 꼼꼼하게&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 것들, 체크해봐야 할 것들을 노션이나 PR에 쓰면서 이해하고 체크하니 확실히 효율성이 올라갔다.&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;또한 만들어야 하는 기능 Figma가 있다면 그 부분을 정확하게 캡처하고, 개발한 내용을 캡처해서 표로 비교해보는게 직방이라는 것을 알게 됐다. 간편하되, 꼼꼼함도 챙길 수 있는 방법이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;도움을 빨리 구하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 주보다 물어보기까지 걸리는 시간 낭비가 많이 줄었다. 혼자 할 수 있는 것들과, 판단할 수 없는 context가 필요한 것들을 어느 정도 구분할 수 있게 됐다. 또, history를 따라가야하는 경우, 관련 용어 등을 미리 물어보고, 확인해서 commit이나 PR을 확인하면 좋다.&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;뽀모도로 타이머 도입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 측정을 위해서 25-5 뽀모도로를 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 5분 쉬는게 아니라(화장실은 가기도하지만), 5분간 다음 뭐할지를 생각하고 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 완벽하게 지키지는 못했지만, 그래도 하루가 끝나고 일주일이 지났을 때 내가 무엇에 얼마만큼의 시간을 투자했는지 확인할 수 있게 됐다. 이게 시간 예측의 첫 발판이 아닐까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꾸준히 이어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 내 의견을 전달하기 위해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 말로 설명하는 것보다 그림을 그려서 설명하면 훨씬 직관적이고 의사소통이 간결해진단 걸 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 그려서 설명하면 시간이 더 걸리는 것 같지만, 사실은 소통 문제를 해결해주기 때문에 시간을 훨씬 아껴주는 방법이다.&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;2. 빠른 이해를 위해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 머릿속에서 하는 것보다는 손으로 그리고 써야 효율이 좋아진다. 보통 복잡한 기능을 만들 때, 먼저 그림을 그리고 만들고는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 작성되어 있는 코드도, 이전에 누군가 고민해서 작성한 코드다. 그러므로, 코드란건 이미 누군가 그린 그림이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 기능을 유지보수하거나 고칠때는 그 고민을 따라가며 그림으로 시각화하다보면 거의 3배는 빨리 이해할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 이해는 추가적인 고민을 할 수 있게 만들고, 더 다양한 조건을 체크할 수 있게한다. 결국은 프로덕트의 완성도를 올릴 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선이 필요한 것들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제가 안풀릴 땐, 차분하게 돌아보기&lt;/h4&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;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxn3Hh/btsMskrE1uj/82lfJrSx4s6AlsFff5SQgK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxn3Hh/btsMskrE1uj/82lfJrSx4s6AlsFff5SQgK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxn3Hh/btsMskrE1uj/82lfJrSx4s6AlsFff5SQgK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxn3Hh%2FbtsMskrE1uj%2F82lfJrSx4s6AlsFff5SQgK%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;239&quot; height=&quot;239&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&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;이런 상황은 크게 두 가지가 있었다.&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;1. 윗단계에서 잘못 분류한 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 정확하게 분류해서 좁혔다면 그럴 일은 없겠지만, 사람인지라 윗 단계에서 고려하지 못한 것들이 있을 수 있다는 것을 항상 생각해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;좁힌 상태에서 생각하는 것은 다른 것을 생각하지 못하게 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 많은 시간을 썼다 싶다면, 가설을 폐기하고 다시 구조를 잡고 다른 조건은 없는지 재접근해볼 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 전환해서 확인해야 효율성을 높힐 수 있다.&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;2. 새로운 상황이 추가된 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 상태에서 새로운 것이 추가되었다면, 기존 뷰를 위에서부터 다시 바라봐야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좁힌 상태에서 고쳐나가려고 하다보면 하나를 고치면 다른 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 유틸 함수를 위한 타입 설계를 할때 사용하는 곳에 맞춰서 타입을 좁혀 놓았는데, 사용하는 곳이 더 넓은 타입을 요구하는 경우, 파일마다 대응하려고 하면 아래와 같은 상황을 맞이할 수 있게 될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;leaking-pipes.gif&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5bIrn/btsMuy9Nk6h/mOaKwB84F2lcW7FkLB9SK1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5bIrn/btsMuy9Nk6h/mOaKwB84F2lcW7FkLB9SK1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5bIrn/btsMuy9Nk6h/mOaKwB84F2lcW7FkLB9SK1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b5bIrn/btsMuy9Nk6h/mOaKwB84F2lcW7FkLB9SK1/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;576&quot; height=&quot;322&quot; data-filename=&quot;leaking-pipes.gif&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;322&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;h3 data-ke-size=&quot;size23&quot;&gt;느낀점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주에 AI관련해서 팀원분들이나 다른 분들이 어떻게 사용하고 계시는지를 어깨너머로 알 수 있었는데 AI툴을 좀 더 잘 써봐야겠다는 생각이 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;얼마나 쓰느냐가 아니라 어떻게 쓰느냐가 중요하다. (우선 gpt를 유로로 전환했다...)&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;외에도 시간 측정이나, 업무 테스크 분류/체크에 AI를 도입할 수도 있을 것 같다.&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;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번주에 굉장히 바보같이 일했다면, 이번주는 점점 줄어들고 있는 것 같긴 하다.&amp;nbsp;&lt;/p&gt;
&lt;p 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;그래서 생산성을 향상시키기 위한 이번주 목표를 정리하자면...&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 뽀모도로 유지 및 좀 더 잘 작성하기 (예측의 시작이 될 수 있는 부분이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 문제가 안풀릴 땐 다시 상단부터 다시 좁혀나가기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 문제에 새로운 상황이 더해졌을 때 확인해보고 기존 뷰를 빠르게 버리고 새로 시작하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 코드/맥락 파악시 그림을 적극 이용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. AI를 활용해서 생산성을 높히기 위한 방법이나 툴 도입하기 -&amp;gt; 테스크 쪼개기/시간 관리 쪽으로&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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/380</guid>
      <comments>https://0422.tistory.com/380#entry380comment</comments>
      <pubDate>Sun, 23 Feb 2025 18:55:38 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 2주차</title>
      <link>https://0422.tistory.com/379</link>
      <description>&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;1600&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ8eOB/btsMjRQxaYB/a0bBH6VDLBmcIhRJK8khZk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ8eOB/btsMjRQxaYB/a0bBH6VDLBmcIhRJK8khZk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ8eOB/btsMjRQxaYB/a0bBH6VDLBmcIhRJK8khZk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ8eOB%2FbtsMjRQxaYB%2Fa0bBH6VDLBmcIhRJK8khZk%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;465&quot; height=&quot;349&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;내용에 문제가 있는 경우 삭제, 수정조치하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;순식간에 2주차가 지나갔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&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;  text-align: justify;&quot;&gt;돌아보기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&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; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;문제가 무엇인지 정확히 파악할 것 (섣불리 코드를 먼저 살펴보지 말 것)&amp;nbsp;&lt;/h4&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전보다 확실히 문제를 파악하는 시간이 단축된 것 같다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서 가장 도움이 됐던 두 가지는&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 해결해서&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 하는 작업이 어떤 것을 개선시키고 어떤 가치를 가져오는지 파악하고자 할때 문제를 좀더 잘 정의할 수 있게 됐다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&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;h4 style=&quot; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&gt;Context Switch 비용 아끼기 (문제를 파악하기전,&amp;nbsp; 문제 해결 전에 무엇이 필요한지 생각해볼 것)&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게까지 명확하게 작성하고 들어간 것은 아니지만, 그래도 어느정도 배치형태로 이뤄질 수 있었다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 대부분의 세팅을 이번주에 완료해서일까, 아니면 익숙해져서일까?&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이건 의도한 것은 아니지만 이전 주보다 확실히 환경구성이나 탐색하는 속도가 빨라졌다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 결과적으로 이슈를 해결하는 시간은 단축되고 있으니...&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 Context switch만 잘된다면, 굳이 배치형태로 해결할 필요가 없으므로 이건 좀더 두고 봐야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;  text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;시간 측정 정확도 높이기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머를 사용해서 시간 측정을 진행하고 있으나, 딱 30분 단위로 진행했더니 다음 어떤 것을 할지 결정해야하는 시간이 들어서 명확하게 시간 측정이 어려웠다.&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;&amp;nbsp;25-5형태로 25분 몰입하고 5분 다음 할 일을 계획하는 형태로 전환&lt;/b&gt;해 볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 직접 이걸 타이머로 관리하려니 가끔 놓치는 경우가 있어서, Flow라는 맥북 앱을 사용해볼 것이다.&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;  text-align: justify;&quot;&gt;느낀 것들&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&gt;말하기&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&gt;이번주에 주로 느낀 것은 &lt;b&gt;말하기&lt;/b&gt;를 좀더 많이 연습 해봐야 한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&gt;내가 어떤 생각을 하든, 글을 어떻게 쓰든, 어쨌든 소통은 말하기에 의해 이뤄지므로 내가 생각한 것을 올바르게 전달할 수 있어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;  text-align: justify;&quot;&gt;예전 면접볼 때 부터 말하기에 약하다는 것을 느끼고 있었는데 밥을 먹거나 커피챗을 할때 그런 말씀을 해주셔서 좀 더 필요성을 느끼게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 분과 관련해서 이야기를 나눴는데 공통적으로 하시는 말씀이 어쨌든 발화량을 늘려나가면서 익숙해지는 수 밖에 없다는 것이다.&lt;/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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DX에 대한 의견 제시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전주와 마찬가지로 계속해서 의견제시를 지속하고 있다. 다만 이번주는 DX에 관한 의견제시를 꽤 했고, 거절도 몇 번 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 나는 거절이 더 중요하다고 생각하고, 이런 경험을 꼭 하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거절에 대한 근거를 이해하고, 나의 경험으로 만들어갈때, 더 넓은 관점을 갖고, 앞으로 성장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거절의 근거를 학습하고, 계속해서 더 나은 방향이라 생각하는 의견을 제시하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선이 필요한 것들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 추산시간을 2시간으로 잡았는데 5시간씩 잡고 있게 되는 경우가 있었다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이건 익숙해지면 해결되는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 해결해줄 문제라고 할수도 있겠지만 인지하고 있는 것과 인지하지 못하고 있는 것은 큰 차이를 가져올 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 코드를 작성하는 시간, 진짜 말 그대로 타이핑하는 시간을 줄일 수 있다는 것을 알게 되서, 앞으로는 좀 더 빠르게 문제를 해결할 수 있을 것 같다. (cursor는 신이구나)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;보다 꼼꼼하게&lt;/h4&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 생각한 것보다 더 꼼꼼하게 확인할 필요가 있다는 걸 느꼈다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이건 그냥 피처를 개발할때도 마찬가지고, 나아가&amp;nbsp;패키지를 바꾼다거나 업데이트하면, 고려해야할 것들이 무수히 많다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 일을 할때는 생각 이상의 세심함과 꼼꼼함이 필요하다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 세심함이 체화된 사람은 아니므로, 세심함을 하나의 형식으로 관리해야한다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 것이 변하는지, 어떻게 변하는지, 어떻게 확인할 지를 작성하고, 스스로 체크해야 내 작업의 퀄리티를 보증할 수 있게 된다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 어떤 것을 확인할지,어떻게 변할지를 한눈에 파악하기란 어렵다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 우선은 과정에서 생각한 것들을 내 머릿속에서 다른 곳으로 이동시킬 필요가 있다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 기록을 다시 확인하면서, 혹시 다른 경우는 없는지 파악하는 형태로 관리 방법을 바꿔볼 것이다.&lt;/p&gt;
&lt;p style=&quot; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot; text-align: start;&quot; data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;물어봐도 되나? 아닌가? 고민하고 코드 확인하고, 하다보면 그게 다 낭비되는 시간이라는 걸 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결단력 있게 빠르게 실행하고 진행해야 낭비되는 시간이 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번주에 양으로 승부해볼 작정이라고 했었는데, 이번주는 정말 양으로 승부했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 바보같이 일하고 있다고 느끼기도 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 어떻게 보면 바보같이 일하고 있다고 느끼는 것 자체가 나아지는 과정일 수도 있겠지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 계속해서 어떻게 하면 더 개선할 수 있을지 고민해야할 필요가 있다.&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;p data-ke-size=&quot;size16&quot;&gt;1. 시간 측정 25-5 뽀모도로 형태로 전환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 코드가 위치해야하는 곳 파악 (컨벤션 파악)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 확인해야하는 것들을 작업을 하면서, 떠오르는 대로 문서로 정리하고, 작업을 마친 뒤 빠진 것은 없는지 다시 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 할 수 있는 부분 빠르게 확인하고, 할 수 없는 부분에 대해 물어보기&lt;/p&gt;</description>
      <category>회고/당근</category>
      <category>당근</category>
      <category>당근알바</category>
      <category>인턴십</category>
      <category>프론트엔드</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/379</guid>
      <comments>https://0422.tistory.com/379#entry379comment</comments>
      <pubDate>Sun, 16 Feb 2025 20:58:24 +0900</pubDate>
    </item>
    <item>
      <title>당근 인턴 회고 - 1주차</title>
      <link>https://0422.tistory.com/378</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uxVBF/btsMbYO3RyV/8tkp2kPwxumexa8JW6rVAK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uxVBF/btsMbYO3RyV/8tkp2kPwxumexa8JW6rVAK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uxVBF/btsMbYO3RyV/8tkp2kPwxumexa8JW6rVAK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuxVBF%2FbtsMbYO3RyV%2F8tkp2kPwxumexa8JW6rVAK%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;455&quot; height=&quot;455&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;내용에 문제가 있는 경우 삭제, 수정조치하겠습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;드디어 당근에서 일해보다&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;나에게 당근은 꼭 일하고 싶은 회사였다.&lt;br /&gt;기술적인 부분도 분명 있지만, 무엇보다 프로덕트 자체가 로컬 플랫폼이기에, 삭막해져가는 사회에 동네라는 가치를 만들고 사람들과 사람들을 이어준다는 가치에 크게 공감하고 있기 때문이다. 이건 있다 설명할 당근에서의 이름인 Manolin과도 이어진다.&lt;br /&gt;&lt;br /&gt;또한, 내가 유튜브, 인스타 다음으로 많이 사용하는 앱이기도 하기 때문에 제품에 대한 애정과 오너십을 가질 수 있겠다는 생각에서 재작년 12월부터 계속해서 공고가 올라올때마다 도전하고 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;세 번의 면접 불합격&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 그전까지 인턴 포지션이 열릴 때마다 지원했다.&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;1742&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz0KFV/btsMchVdJzr/ozVbmjbNKqYvzkKRzkxK7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz0KFV/btsMchVdJzr/ozVbmjbNKqYvzkKRzkxK7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz0KFV/btsMchVdJzr/ozVbmjbNKqYvzkKRzkxK7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz0KFV%2FbtsMchVdJzr%2FozVbmjbNKqYvzkKRzkxK7K%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;1742&quot; height=&quot;384&quot; data-origin-width=&quot;1742&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 되돌아보면 불합격도 하나의 피드백이었던 것 같다.&lt;br /&gt;면접과 서류를 복기하고 어떤 것들이 부족했는지 스스로 되돌아보며 부족한 점들을 채워갔다.&lt;br /&gt;&amp;nbsp;&lt;br /&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;이 부족하구나를 알게 됐다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그리고 이것들을 채워가기위해 노력했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;합격&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU1XPJ/btsMaSCckEU/wFIKmMt04cnEUDNXPYJMXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU1XPJ/btsMaSCckEU/wFIKmMt04cnEUDNXPYJMXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU1XPJ/btsMaSCckEU/wFIKmMt04cnEUDNXPYJMXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU1XPJ%2FbtsMaSCckEU%2FwFIKmMt04cnEUDNXPYJMXk%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;520&quot; height=&quot;291&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;3개월 체험형 인턴이었지만 합격했을 때, 대학 합격 했을 때 보다 기뻤다.&lt;br /&gt;&lt;b&gt;당근에서 일 해 볼 수 있다는 사실&lt;/b&gt;과 더불어&amp;nbsp;&lt;b&gt;그간의 노력이 헛되지 않았음이 증명된 것 같아서&lt;/b&gt; 너무 기뻤다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;Manolin&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 영어 이름을 가져본적이 없어서 새로 이름을 지어야했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;항상 당근에 들어오게 된다면 노인과 바다에서 이름을 따오고자 다짐했었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;노인과 바다는 불합리, 부조리에 맞서는 인간의 의지를 다루고 있는데,&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;노인이 청새치를 잡으려는 과정이 마치 사람의 인생 그 중에서도 창업과도 비슷하다고 느꼈다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;생각하지 못했던 것들에서 문제가 생기고, 내 뜻과 다르게 일어나는 이해하기 힘든 불행한 일들이 많이 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;청새치는 잡기 힘들고, 과정에서 손이 다 망가지기도 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 힘들게 잡은 청새치라 해도, 상어가 나타나면 뼈만 남게될 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 우리는 다시 사자 꿈을 꿀 수 있는 존재다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이런 불합리 속에서도 강한 의지를 보여주는 두 사람 중 한 명을 고르고자 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이중에서 노인(Santiago)이 아닌, 소년(Manolin)을 선택한 이유는 두가지 이유가 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;첫째로는 당근에서 다른 분들께 많은 것들을 배우고자 했다. 일하는 방식도 있겠지만, 제품에 대한 의지와 그것들이 표현되는 방법들을 배워가고싶었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두번째는 마놀린이야 말로 노인이 강한 의지를 유지할 수 있는 이유였기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 대상, 의미, 가치를 추구한다면, 그것에 가치를 부여하는 사람은 나이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 내가 생각을 바꾸면 그것은 언제라도 무가치해질 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;내가 중요하다고 생각하는 것들은 언제라도 내가 중요하다고 생각하지 않게 되면 중요하지 않은 게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 인간이 혼자서 독립적으로 지향하는 대상은 언제라도 무의미로 사라져버릴 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 나에 의해 유일하게 가치를 잃지 않는 것이 하나 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그건 바로 타인이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;타인은 나처럼 주관적 판단을 하며, 이에 따라 의미를 부여한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;타인은 내가 의미 없다 생각하는 것들에 의미를 부여해줄 수 있는 유일한 존재다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나의 의미가 타인에게 전달되고 공감을 이끌어낼 수 있다면,&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;내가 추구했던 것들은 더 이상 무의미하지 않은 것이 되어 의미를 유지할 수 있게된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;나의 의미가 타인에게 전달되고, 공감이 있는 순간 그 의지는 더욱 강해지고 함께 나눌 수 있는 것이 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;나는 사람들이 서로가 서로의 마놀린이 될 때, 허무주의가 사라지고 더 따뜻한 세상이 될것이라 믿는다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;추가로&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;당근은 동네 이웃을 연결한다는 점에서 이웃들이 서로가 마놀린이 될 수 있도록 하는 서비스라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 그런 서비스를 만드는 과정을 꼭 경험하고 싶었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;꼭 일해보고 싶었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;첫 일주일&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일주일동안 팀원들과 이야기도 나누고, 필요한 것들을 배우고, 나에게 배정된 일들을 수행했고, 그 과정을 돌아보려 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;팀&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우선 팀에 조금씩 적응해 나가고 있다. &lt;br /&gt;영어이름으로 부르는게 처음엔 정말 어색했지만, 이젠 좀 많이 익숙해졌다.&lt;br /&gt;&lt;br /&gt;스몰토크를 잘하지 못해서... 걱정이었는데 잘 챙겨 주셔서 다행이었다. 특히 밥 먹으면서 보드게임을 했던게 재밌어서 기억에 남는다. &lt;br /&gt;스카우트라는 보드게임이었는데 굉장히 재밌었다!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;문화&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문화에 대한 세션도 들었는데, &lt;b&gt;토론식&lt;/b&gt;으로 진행했어서 당근이 현재에 이르기까지 문화에 대해 어떤 고민을 해왔는지 짧은 시간동안 간접적으로 고민할 수 있었다.&lt;br /&gt;&lt;br /&gt;이전까지는 항상 지원자 입장에서 회사 문화에 대해 생각했다면, 회사입장에서 문화에 대하 고민할 수 있게 됐다.&lt;br /&gt;무엇보다 한국 it회사들의 기본적 문화가 어디에서 왔는지도 알 수 있어서 너무 의미 있는 경험이었다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;넷플릭스의 문화 : 자유와 책임 (한국어 번역본)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;넷플릭스의 문화 : 자유와 책임 (한국어 번역본) - Download as a PDF or view online for free&quot; data-og-host=&quot;www.slideshare.net&quot; data-og-source-url=&quot;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKh5nz/hyYcfqUTDf/2303BZY6mrsHkmDJLO7Mj0/img.jpg?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480&quot; data-og-url=&quot;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.slideshare.net/slideshow/freedom-responsibility-culture/49159844&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKh5nz/hyYcfqUTDf/2303BZY6mrsHkmDJLO7Mj0/img.jpg?width=640&amp;amp;height=480&amp;amp;face=0_0_640_480');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;넷플릭스의 문화 : 자유와 책임 (한국어 번역본)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;넷플릭스의 문화 : 자유와 책임 (한국어 번역본) - Download as a PDF or view online for free&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.slideshare.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;업무 회고&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;첫날은 온보딩 문서를 읽고, 세팅을 하느라 정신이 없어서 뭔가 하진 못했던 것 같다.&lt;br /&gt;두번째날부터 싱크를 통해 이슈를 할당받기 시작했고, 자잘한 것들을 포함하면 6개정도의 이슈를 해결했다. &lt;br /&gt;&lt;br /&gt;과정에서 느낀 것들, 잘한 것들, 개선이 필요한 부분들을 되돌아 보려고한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;느낀 것들&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. &lt;a href=&quot;https://about.daangn.com/company/pr/archive/%EB%8B%B9%EA%B7%BC-%EB%88%84%EC%A0%81-%EA%B0%80%EC%9E%85%EC%9E%90-4000%EB%A7%8C-%EB%AA%85-%EB%8F%8C%ED%8C%8C/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MAU 2000만인 앱의 서비스&lt;/a&gt;를 배포한다는 책임감, 자부심&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 방금 작성한 코드가 MAU 2000만인 앱의 일부로 배포된다는 점이 엄청난 책임감을 갖게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할당된 테스크를 한다는 건 인턴이 할 수 있는 일이지만, 테스크로 만들어진 결과, 즉 서비스는 모든 이용자에게 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 했던 어떤 개발 작업물보다 많은 사람들에게 전달된다.&lt;/p&gt;
&lt;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;그래서 첫 이슈를 해결하는데 작동 되는지를 20번도 넘게 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 마지막에 문제가 있다는걸 찾아낼 수 있었고, 고쳐서 배포했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 다행이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 가져본 어떤 것보다 무거운 책임감을 가질 수 있었고, 앞으로도 가져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경험들을 인턴인데도 할 수 있음이 굉장히 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무거운 책임감 덕분에 첫 배포 후 에러 없이 잘 작동할 때의 만족감과 자부심도 이전까지와는 비교할 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 즐거웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 오너십(팔로워십)&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 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 일을 왜 해야하는지&lt;/b&gt; 아는 것이 오너십을 갖게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 팔로워의 오너십이야말로 팔로워십이 아닐까 라는 생각도 하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이 테스크를 해야하는지 이해하고, 테스크에 대해 더 나은 의견과 방향을 제시하는것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이야말로 팔로워십이 아닐까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 자동화/ 코드베이스&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 data-ke-size=&quot;size16&quot;&gt;코드베이스를 봤을 때도 역시 얼마나 많은 고민이 담겼는지 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 다른 분들이 봤을 때 빠르게 이해할 수 있고, 유지보수하기 좋은 코드를 작성하기위해 노력해야겠다 다짐할 수 있었다.&lt;/p&gt;
&lt;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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;조금 더 빠른 적응&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;이슈에 대한 개선 의견 제시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈를 그냥 해결하기보다는 도메인이나 UX에 대한 질문, 의견을 제시했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 이런 행동들이 조금 더 향상된 결과를 가져왔던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;b&gt;팀이 나에게 기대하는 것을 해내는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 뭔가 일을 찾아서 개선해내는 것도 중요하지만, 우선 나에게 주어진 일을 잘 해내는 것이 우선이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 프로세스를 더 빠르게 해낼 필요가 있다.&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;/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 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;문제를 정확히 파악하지 못한 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 안다고 생각했지만, 실제로는 알지 못하는 상태가 됐던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 뭔지 정확히 모르는데, 문제를 해결하려 하는 건 말도 안되는 욕심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 과정을 겪으며 테스크 해결에 걸리는 시간이 지체된다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;코드를 보기전에 내가 풀려는 문제가 무엇인지 정의&lt;/b&gt;해보고, 이를 위해 필요한 도메인에 대한 것들, 배경지식들을 완전히 글이나 그림으로 정리할 필요가 있다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Context Switch 비용 아끼기 (문제를 파악하기전,&amp;nbsp; 문제 해결 전에 무엇이 필요한지 생각해볼 것)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석 중이나 개발 중에 추가적인 셋팅이 필요하다거나, 새로운 라이브러리를 학습할 필요가 있다거나 하는 경우가 있었는데, 이때 많은 시간이 지체되는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 멀티테스킹이 안되는 사람인가보다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상황이 일어날때마다 context 전환이 일어나며 맥락을 잃었고, 다시 돌아왔을 때 어디까지 했었는지 까먹어서 다시 수행하기도 했었다.&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;현재 생각으로는 작업이나 분석전에 필요한 것들을 미리 생각해보고, 최대한 모아서 batch 형식으로 쳐내는 것&lt;/b&gt;을 떠올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 주에는 이런 방식을 도입해보고, 좀 더 빠르게 문제를 해결하기 위해 노력해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 측정 정확도 높이기&lt;/h4&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1주차에 세운 개인적인 목표를 적으며 마무리 해보고자 한다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 힘들게 얻은 기회이고, 한정된 3개월이라는 인턴 기간동안 당근을, 그리고 당근 알바팀을 최대한 많이 경험해 보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경험은 양이 될 수 도 있고 질이 될 수 도 있지만, 현재로써는 어떤 것이 질 좋은 경험인지 알 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 양으로 승부해볼 작정이다. 일하는게 생각한 것보다 더 즐겁기도 하고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대한 많은 일들을, 빠르게 해내고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 되는 한 많이 일하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/378</guid>
      <comments>https://0422.tistory.com/378#entry378comment</comments>
      <pubDate>Sun, 9 Feb 2025 02:02:48 +0900</pubDate>
    </item>
    <item>
      <title>2024 회고</title>
      <link>https://0422.tistory.com/377</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3yVoY/btsLAtXEdH6/wThqRQfB1r5TdtxoR3DNSK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3yVoY/btsLAtXEdH6/wThqRQfB1r5TdtxoR3DNSK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3yVoY/btsLAtXEdH6/wThqRQfB1r5TdtxoR3DNSK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3yVoY%2FbtsLAtXEdH6%2FwThqRQfB1r5TdtxoR3DNSK%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;360&quot; height=&quot;360&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 2024년의 마지막날이다.&lt;br /&gt;되돌아보면 이번년도는 꽤 많은 일이 있던 1년이었다.&lt;br /&gt;연초부터 창업 프로젝트가 좌초되고, 수 많은 탈락을 겪기도하고&lt;br /&gt;하필 내가 인턴을 시작했을때 업무강도나 역대급으로 강해지거나... 하는 식이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;1340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCAPgy/btsLA0AYnqs/zXNAVZtGuLuNEXA164rnnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCAPgy/btsLA0AYnqs/zXNAVZtGuLuNEXA164rnnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCAPgy/btsLA0AYnqs/zXNAVZtGuLuNEXA164rnnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCAPgy%2FbtsLA0AYnqs%2FzXNAVZtGuLuNEXA164rnnK%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;2478&quot; height=&quot;260&quot; data-origin-width=&quot;2478&quot; data-origin-height=&quot;1340&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;2128&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0LNG5/btsLASv7RW1/tl1eWh4O7i6SakElaZsguk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0LNG5/btsLASv7RW1/tl1eWh4O7i6SakElaZsguk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0LNG5/btsLASv7RW1/tl1eWh4O7i6SakElaZsguk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0LNG5%2FbtsLASv7RW1%2Ftl1eWh4O7i6SakElaZsguk%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;2128&quot; height=&quot;439&quot; data-origin-width=&quot;2128&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상반기 목표로 하반기 돌아보기&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회사/팀/프로덕트에 기여하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 정말 잘해냈다고 생각한다. 짧은 기간이었음에도 불구하고, 많은 양의 업무를 해낼 수 있었다.&lt;br /&gt;더불어 회사의 lint룰이나, multi-starter cli를 만들어 생산성을 향상시키는 등의 기여를 할 수 있었고, 덕분에 좋은 피드백도 받을 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2258&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/da3juI/btsLC577oRk/tSIv0yt5kKmMpVCUACujIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/da3juI/btsLC577oRk/tSIv0yt5kKmMpVCUACujIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/da3juI/btsLC577oRk/tSIv0yt5kKmMpVCUACujIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fda3juI%2FbtsLC577oRk%2FtSIv0yt5kKmMpVCUACujIK%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;2258&quot; height=&quot;580&quot; data-origin-width=&quot;2258&quot; data-origin-height=&quot;580&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;h4 data-ke-size=&quot;size20&quot;&gt;회사에서 한 경험을 잘 기록하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 완전히 잘한 것은 아니지만, 휘발되지 않을 정도의 정보는 기록해두어서 나의 경험으로 만들어내는데에는 성공한 것 같다. 경험을 블로그에 작성해보기도했고....&lt;br /&gt;다만, 다음 인턴이나, 직무에서는 직무 경험을 좀 더 잘 기록해보고자 한다.&lt;br /&gt;문서화를 좀 더 잘해 보고 싶다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체적인 흐름잡기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 어떤 프로세스로 일하는지를 잘 파악할 수 있었다.&lt;br /&gt;다만, 이 프로세스를 익혀가는 과정이 처음이었고, 잘못 이해해서 시간이 꽤 걸렸었는데, 이 부분은 더 개선이 필요하다.&lt;br /&gt;이 시간을 단축시킬 수록 내가 기여할 수 있는 부분이 커지게 된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개발자로 처음 일하는 것이다보니 처음에는 코드가 중요하고, 배포과정이 중요하다고 생각했었는데, 이러한 부분보다는 회사 서비스 도메인에 대한 이해가 훨씬 중요하다는걸 알게됐다.&lt;br /&gt;팀에서 팀원들이 어떤 용어를 어떤 용도로 사용하는지 파악하고, 서비스 도메인에서 이게 얼마나 중요한지를 파악하는게 가장 우선순위가 높다는 것을 알 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;독서량 유지하기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qi8TU/btsLAcajt0m/CU1zNaX7hMrJy7UUVBp7x0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qi8TU/btsLAcajt0m/CU1zNaX7hMrJy7UUVBp7x0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qi8TU/btsLAcajt0m/CU1zNaX7hMrJy7UUVBp7x0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqi8TU%2FbtsLAcajt0m%2FCU1zNaX7hMrJy7UUVBp7x0%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;225&quot; height=&quot;349&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하반기 독서량은 두 권으로 얼마되지 않는다.&lt;br /&gt;하지만, 물고기는 존재하지 않는다가 굉장히 많은 가치를 나에게 전달해 주었고, 덕분에 2024년의 혼란스러운 일들을 마음 속으로 정리해낼 수 있었다. 정리해낸 내용은 아래에 좀더 적어보려고한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하반기 돌아보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;새로운 도전&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 학습한 것들, 지금까지의 React 경험을 살려 React Native로 관광데이터 활용 공모전에 참여했다.&lt;br /&gt;새로운 부분도 굉장히 많았지만, 이전까지의 경험을 잘 살릴 수 있었고, 프로젝트 관리도 어느정도 잘 해내서 최우수상이라는 굉장히 좋은 결과를 얻어낼 수 있었다.&amp;nbsp;&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/cecDtF/btsLABt8tI0/ShUhIJvBPis5BAR9gLCP1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cecDtF/btsLABt8tI0/ShUhIJvBPis5BAR9gLCP1K/img.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1511&quot; style=&quot;width: 53.8794%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;54.51&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cecDtF/btsLABt8tI0/ShUhIJvBPis5BAR9gLCP1K/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcecDtF%2FbtsLABt8tI0%2FShUhIJvBPis5BAR9gLCP1K%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;1512&quot; height=&quot;1511&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6ymqk/btsLCDEdrMQ/XD5Y8xIk2SRKxs6TyC6PCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6ymqk/btsLCDEdrMQ/XD5Y8xIk2SRKxs6TyC6PCK/img.png&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;1121&quot; style=&quot;width: 44.9578%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;45.49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6ymqk/btsLCDEdrMQ/XD5Y8xIk2SRKxs6TyC6PCK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6ymqk%2FbtsLCDEdrMQ%2FXD5Y8xIk2SRKxs6TyC6PCK%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;936&quot; height=&quot;1121&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;사실 최우수상이라는 결과보다는 이것을 이뤄내는 과정에서의 성장이 더욱 컸다고 생각한다.&lt;br /&gt;과정에서 프로젝트를 이전보다 더 잘 관리할 수 있었고, React Native로 앱개발을 하고, 원스토어지만 배포까지 해보면서 많은 것들을 학습하고, 성장했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;나아가 이 경험을 살리고 조금 더 발전시켜서 이전부터 만들고 싶었던 개발자 블로그 앱을 만들어낼 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dev-Feed&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLnIm5/btsLCt2ISS2/A5tKfwMHfEEABmhUKhc0Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLnIm5/btsLCt2ISS2/A5tKfwMHfEEABmhUKhc0Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLnIm5/btsLCt2ISS2/A5tKfwMHfEEABmhUKhc0Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLnIm5%2FbtsLCt2ISS2%2FA5tKfwMHfEEABmhUKhc0Qk%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;658&quot; height=&quot;393&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dev-Feed를 기획, 디자인, 개발, 배포까지 전 과정을 혼자서 진행해보면서 많은 경험과 학습을 할 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아이폰과 안드로이드 유저의 UX차이(바텀시트 등)를 고려해서 어떻게 해야 사용자에게 좀더 나은 경험을 제공할 수 있을까부터&lt;br /&gt;Velog에서의 전환율을 기반으로 인스타그램 광고를 진행하고, GA를 사용한 인스타그램 광고 전환율을 확인해보기도했다.&lt;br /&gt;또 유저 피드백을 받고, 어떻게 개선할지 고민해본 첫 프로젝트기도했다.&lt;br /&gt;&lt;br /&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/nZKaG/btsLBIzsoi8/s2qVg3sOdX4jWgSNNj15z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZKaG/btsLBIzsoi8/s2qVg3sOdX4jWgSNNj15z1/img.png&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;794&quot; style=&quot;width: 47.7547%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;48.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZKaG/btsLBIzsoi8/s2qVg3sOdX4jWgSNNj15z1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZKaG%2FbtsLBIzsoi8%2Fs2qVg3sOdX4jWgSNNj15z1%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;1016&quot; height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MDmZQ/btsLCsbEEVi/OJTbJCdqXEWkKWKcSnUzzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MDmZQ/btsLCsbEEVi/OJTbJCdqXEWkKWKcSnUzzK/img.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;922&quot; style=&quot;width: 51.0825%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MDmZQ/btsLCsbEEVi/OJTbJCdqXEWkKWKcSnUzzK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMDmZQ%2FbtsLCsbEEVi%2FOJTbJCdqXEWkKWKcSnUzzK%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;1262&quot; height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2820&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buxtH5/btsLB3QYYYH/1hVyYkiHnkriLMMGE6Ye0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buxtH5/btsLB3QYYYH/1hVyYkiHnkriLMMGE6Ye0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buxtH5/btsLB3QYYYH/1hVyYkiHnkriLMMGE6Ye0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuxtH5%2FbtsLB3QYYYH%2F1hVyYkiHnkriLMMGE6Ye0k%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;2820&quot; height=&quot;498&quot; data-origin-width=&quot;2820&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&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/bl0zfX/btsLAW6g76K/Yc1z8kbcnRaLu13cPN0Tdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl0zfX/btsLAW6g76K/Yc1z8kbcnRaLu13cPN0Tdk/img.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;384&quot; style=&quot;width: 50.5922%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl0zfX/btsLAW6g76K/Yc1z8kbcnRaLu13cPN0Tdk/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl0zfX%2FbtsLAW6g76K%2FYc1z8kbcnRaLu13cPN0Tdk%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;764&quot; height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwL6YQ/btsLC4amojY/8gHwsRhoyIP5zkRvf5sr1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwL6YQ/btsLC4amojY/8gHwsRhoyIP5zkRvf5sr1k/img.png&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;662&quot; style=&quot;width: 48.245%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;48.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwL6YQ/btsLC4amojY/8gHwsRhoyIP5zkRvf5sr1k/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwL6YQ%2FbtsLC4amojY%2F8gHwsRhoyIP5zkRvf5sr1k%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;1256&quot; height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 Dev-Feed는 나의 첫 100명이 사용하는 앱이 되었고, 2.5달러라는 &lt;span style=&quot;color: #333333;&quot;&gt;정말 작은 &lt;/span&gt;돈이지만, 첫 매출을 낸 나의 프로덕트가 되었다.&lt;br /&gt;이것 역시 끝이아닌, 또 다른 시작이 될 것이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내년 목표&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;졸업준비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 졸업을 해야한다. 1학기를 남겨두고 있어서 졸업을 준비해야한다. 졸업시험을 봐야해서 준비할 생각이다.&lt;br /&gt;기회가 된다면 남은 한학기도 인턴 대체를 하면서 경험을 더 쌓아가고 싶다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;앱 더 출시하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dev-Feed를 출시하면서 앱을 만들어 내는 능력을 꽤 많이 키울 수 있었다.&lt;br /&gt;또한 나는 개발 하는 행위 자체를 굉장히 좋아한다. 그래서 취미활동을 하듯이 개발할 수 있다.&lt;br /&gt;그래서 그냥 취미로 앱을 더 많이 개발하고자한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;프로그래밍 좀비님을 보면서 사용자가 엄청 많지는 않아도, 작은 앱들을 많이 개발하다보면 용돈 벌이 정도도 할 수 있다는 것을 알게 됐다.&lt;br /&gt;다른게 아닌 취미활동으로 이걸 꾸준히 하다보면 언젠간 좋은 결과가 나올 것이라고 믿는다.&lt;br /&gt;&lt;a href=&quot;https://soulduse.tistory.com/138&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://soulduse.tistory.com/138&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;개인앱 300개 만들고 퇴사합니다.&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;23년 2월 7일 작성한 포스팅 끝자락에 작성한 글이 현실이 되었습니다.&amp;nbsp;그리고&amp;nbsp;어찌&amp;nbsp;운이좋아서&amp;nbsp;수익이&amp;nbsp;폭증하게&amp;nbsp;되면&amp;nbsp;웃으며&amp;nbsp;회사를&amp;nbsp;졸업하는&amp;nbsp;시기가&amp;nbsp;더&amp;nbsp;빨라질수도&amp;nbsp;있지&amp;nbsp;않을까?&amp;nbsp;이&quot; data-og-host=&quot;soulduse.tistory.com&quot; data-og-source-url=&quot;https://soulduse.tistory.com/138&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bn8bK6/hyXSuQayng/kxvGN2HoiMSykR73AzWB7K/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/KqNkL/hyXWuVf6y1/WzuNWeeVlzDFdKZnWFKkCk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/F6t73/hyXWClsUlf/ElOqYkjK6E5z0dPaPAEYDK/img.png?width=912&amp;amp;height=530&amp;amp;face=0_0_912_530&quot; data-og-url=&quot;https://soulduse.tistory.com/138&quot;&gt;&lt;a href=&quot;https://soulduse.tistory.com/138&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://soulduse.tistory.com/138&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bn8bK6/hyXSuQayng/kxvGN2HoiMSykR73AzWB7K/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/KqNkL/hyXWuVf6y1/WzuNWeeVlzDFdKZnWFKkCk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/F6t73/hyXWClsUlf/ElOqYkjK6E5z0dPaPAEYDK/img.png?width=912&amp;amp;height=530&amp;amp;face=0_0_912_530');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개인앱 300개 만들고 퇴사합니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;23년 2월 7일 작성한 포스팅 끝자락에 작성한 글이 현실이 되었습니다.&amp;nbsp;그리고&amp;nbsp;어찌&amp;nbsp;운이좋아서&amp;nbsp;수익이&amp;nbsp;폭증하게&amp;nbsp;되면&amp;nbsp;웃으며&amp;nbsp;회사를&amp;nbsp;졸업하는&amp;nbsp;시기가&amp;nbsp;더&amp;nbsp;빨라질수도&amp;nbsp;있지&amp;nbsp;않을까?&amp;nbsp;이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;soulduse.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;또한 과정에서 기술적으로나, 소프트적으로나 더 많이 성장할 수 있을 것이라고 믿는다.&lt;br /&gt;그래서 이번년도 하반기에 2개를 냈으니 내년에는 총 4개의 앱을 더 출시 해볼 생각이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개발 도서 읽고, 개발 철학에 대해 고민해보기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘리스 인턴십을 하고, 어떤게 좋은 코드인지 고민하고 나만의 결론을 내리기까지 굉장히 많은 시간이 걸렸다. &lt;a href=&quot;https://0422.tistory.com/375&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;(https://0422.tistory.com/375)&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이러한 결론이 내려지지 않으면, 혼란스럽기에 코드를 제대로 작성하기 어렵고, 속도를 늦춘다.&lt;br /&gt;개발 도서를 좀더 많이 읽다보면 이런 고민들을 더 빨리 해결할 수 있이다.&lt;br /&gt;또한, 읽다보면 더 다양한 고민들을 하고, 나만의 답변들을 쌓아갈 수 있을 것이라고 생각한다.&lt;br /&gt;이런 답변들은 더 많고 다양한 문제를 해결할때, 큰 도움이 될 것이라 믿는다&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 상반기까지 아래 책을 읽을 생각이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;1. 개발자 원칙&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 실용주의 프로그래머&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 쏙쏙들어오는 함수형 코딩&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2024년 정리하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 2024년은 다사다난했고, 어려운 일들이 많았고, 마음이 꺾일뻔 한적도 꽤 많았다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;하지만, 덕분에 그럼에도 버텨내는 법을 알게되었다. 어떻게 보면 2024년은 어려움에도 버텨내는 방법을 알려준 해다.&lt;br /&gt;과정에서 읽은 책들이 정말 많은 도움이 되었다.&lt;br /&gt;그래서 이 책들로 2024년을 마무리하고자한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인간은 파괴될지언정, 패배하지 않는다 - 노인과 바다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인생의 뜻하지않는 불행, 부조리를 이겨내는 방법은 그럼에도 해내겠다는 강한 의지다.&lt;br /&gt;의지를 가진 인간은 파괴될지언정 패배하지 않는다. 무엇이든, 이겨낼 수 있다.&lt;br /&gt;의지를 잃지 않는다면, 인생의 부조리는 잠깐의 문제일 뿐이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;의미를 지키기위한 방법 - 물고기는 존재하지 않는다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물고기는 책의 인물이 한 생을 바쳤던 하나의 분야이자, 관심대상이다. 전 생을 바쳐 연구했다. 하지만 어류라는 종은 사실 존재하지도 않았고, 이런 의지가 우생학으로 이어져 잘못된 길로 나아가게된다. 물고기는 인물에게&amp;nbsp;하나의 의미이자, 의지였다. 하지만 그것으로 인해 완전히 잘못된 방향으로 나아가게되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;노인과 바다를 읽으면서 강력한 의지는 안정감을 주니까. 행복할 수 있을 것이라고, 그것이 행복이라 믿었다.&lt;br /&gt;의미는&amp;nbsp;여러가지가&amp;nbsp;있지만,&amp;nbsp;&amp;nbsp;나는&amp;nbsp;언젠가부터&amp;nbsp;자연스럽게&amp;nbsp;한&amp;nbsp;개인의&amp;nbsp;주관적&amp;nbsp;삶이&amp;nbsp;하나의&amp;nbsp;의미로&amp;nbsp;유지될&amp;nbsp;수&amp;nbsp;있고,&amp;nbsp;그것이&amp;nbsp;더욱&amp;nbsp;강력한&amp;nbsp;의지를&amp;nbsp;줄&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것이라&amp;nbsp;믿었던&amp;nbsp;것&amp;nbsp;같다.&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 개인의 인생에서 강력하고도 완벽한 하나의 의미란 존재하지 않으며, 추구해서도 안된다.&lt;br /&gt;상대적으로도&amp;nbsp;절대적,&amp;nbsp;객관적인&amp;nbsp;진실은&amp;nbsp;없지만,&amp;nbsp;주관적으로도&amp;nbsp;그런&amp;nbsp;하나의&amp;nbsp;절대적&amp;nbsp;의미는&amp;nbsp;존재하지&amp;nbsp;않는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개인에게 강력한 절대적 의미가 없다면, 강력한 의지도 얻을 수 없다.&lt;br /&gt;물론&amp;nbsp;순간순간&amp;nbsp;좋은&amp;nbsp;것들,&amp;nbsp;하겠다는&amp;nbsp;의지가&amp;nbsp;생길&amp;nbsp;순&amp;nbsp;있지만&amp;nbsp;그것은&amp;nbsp;언제라도&amp;nbsp;사라질&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;휘발성&amp;nbsp;짙은&amp;nbsp;것들이다.&lt;br /&gt;&lt;b&gt;그렇다면 인간은 패배할 수 밖에 없는가?&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;어떤 대상을 추구한다면, 그것에 가치를 부여하는 사람은 나이다. 그래서 내가 생각을 바꾸면 그 대상은 언제라도 무가치해질 수 있다.&lt;br /&gt;따라서&amp;nbsp;인간이&amp;nbsp;혼자서&amp;nbsp;독립적으로&amp;nbsp;지향하는&amp;nbsp;대상은&amp;nbsp;언제라도&amp;nbsp;무의미로&amp;nbsp;사라져버릴&amp;nbsp;수&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;하지만, 나에 의해 유일하게 가치를 잃지 않는 것이 하나 존재한다. 그건 바로 타인이다. 타인은 나처럼 주관적 판단을 하며, 이에 따라 의미를 부여한다. 그래서 타인은 내가 의미 없다 생각하는 것들에 의미를 부여할 수 있다. 그러면 내가 추구했던 것들은 더이상 무의미의 혼돈으로 빠지지않고 의미를 유지할 수 있게 된다.&lt;br /&gt;&lt;br /&gt;내게 모든 것이 무의미해져도, 타인만큼은 독자적으로 발휘하는 자유로 내린 가치판단으로, 그 의미를 지킨다.&lt;br /&gt;그러므로 안정적인 가치란 타인에게까지 연장될 수 있는 가치, 타인이 스스로 동의할 수 있게 하는 가치다.&lt;br /&gt;노인과 바다에서 노인이 보여준 의지는 청세치를 잡겠다는 의지이기도 하나, 자신을 걱정하는 소년을 생각하는 마음에서 나온 의지이기도 했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서 우리는 서로가 서로를 무의미의 혼돈속에서 지켜주는 연인, 친구, 동료가 되어야한다.&lt;br /&gt;새해 복 많이 받으시길!&lt;/p&gt;</description>
      <category>회고/회고</category>
      <category>2024</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/377</guid>
      <comments>https://0422.tistory.com/377#entry377comment</comments>
      <pubDate>Tue, 31 Dec 2024 15:12:39 +0900</pubDate>
    </item>
    <item>
      <title>로깅과 관심사 분리</title>
      <link>https://0422.tistory.com/376</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;여러 번의 리젝과 끝없는 기다림 끝에 개인 프로젝트인 dev-feed을 playstore와 appstore에 배포하는데 성공했다...!&lt;br&gt;&amp;nbsp;&lt;br&gt;그러나 배포가 끝이 아니다.&lt;br&gt;나는 앞으로도 컨텐츠를 추가할 생각이다.&lt;br&gt;그중에 하나가&lt;b&gt;&amp;nbsp;인기 블로그&lt;/b&gt;를 제공하는 것인데, 우선 데이터를 모아보고 싶어 analytics로 해당 데이터들을 모으고자 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;dev-feed는 react-native로 만들었기에 @react-native-firebase/analytics를 사용하여 google-analytics를 통해 어떤 블로그를 구독했는지를 수집하기로 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그런데, 조금 있다가, &lt;b&gt;인기 글&lt;/b&gt;도 제공하고 싶어졌다.&lt;br&gt;그래서 뒤늦게 코드를 추가하려다보니, 글조회, 구독 등등... 전반적인 앱의 핵심 기능과 로깅이 붙어있는걸 발견할 수 있었다.&lt;br&gt;이렇게 관리해서는 이후에 다른 로깅을 추가하고 싶어질때, 추가하는 속도가 현저히 떨어지는 것이 불보듯 뻔했다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0YZCZ/btsKPSQt6fD/mbLynOrNc9FDRUzTznpU9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0YZCZ/btsKPSQt6fD/mbLynOrNc9FDRUzTznpU9k/img.png&quot; data-alt=&quot;이전 로직&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0YZCZ/btsKPSQt6fD/mbLynOrNc9FDRUzTznpU9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0YZCZ%2FbtsKPSQt6fD%2FmbLynOrNc9FDRUzTznpU9k%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;397&quot; height=&quot;220&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이전 로직&lt;/figcaption&gt;
&lt;/figure&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;관심사 분리는 레스토랑과 비슷하다고 생각한다.&lt;br&gt;내가 요리를 잘하고, 몸이 컴퓨터 연산속도 만큼 날래서 혼자서 1인 레스토랑을 오픈했다고 했을때,&amp;nbsp;&lt;br&gt;물론 나혼자 요리를 다하고, 서빙도하고, 테이블 정리도 하고, 계산도 할 수 있을 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만, 더 많은 요리가 추가되고, 요리가 서빙나갈때, 챙겨야하는 것들, 테이블 정리시에 주의해야할것들, 계산시 할인, 단골 혜택 부여 등등...&lt;br&gt;점점 신경써야하는 것들이 많아지게되면 혼자서는 실수가 많아지고,레스토랑이 아무리 잘되어도 레스토랑을 더 확장하기 어려울 것이다.&lt;br&gt;특히나 그런 절차가 조금씩 바뀌기 시작하면... 실수를 할 수 밖에 없게된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 보통 규모가 있는 레스토랑은 직원들에게 역할을 부여하고, 직원들은 그 역할을 잘 수행한뒤, 다른 역할을 지닌 사람에게 일을 넘긴다.&lt;br&gt;예컨데 요리사는 요리만한다. 계산을 하지도 않고, 테이블을 치우지도 않는다.&lt;br&gt;&amp;nbsp;&lt;br&gt;코드도 마찬가지다. 많은 로직을 다짜내고 어떻게든 성공적으로 돌아가면 그 코드 덩어리 하나로 애플리케이션이 동작할 수 있다.&lt;br&gt;하지만 요리사도 사람이고, 프로그래머도 사람이다. 더 많은 기능, 수정사항을 반영하기 시작하면 실수를 할 수 밖에 없다.&lt;br&gt;이런 문제를 해결하기위해서는 관심사를 분리하고, 해당 코드가 걸맞는 역할만을 하게 만드는 것이 옳다고 할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&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;또한 로깅의 의존성 문제도 있었다. GA4를 내가 잘 사용하지 못하는 것일 수 도 있지만, 아무리 탐색탭을 통해 보고서를 만들어도,&lt;br&gt;기간별 데이터를 쉽게 조회하거나, 로우데이터 자체를 얻기는 힘들었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZXKCN/btsKQAInMhN/kr9zeFc99TQgsTR4eUUmJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZXKCN/btsKQAInMhN/kr9zeFc99TQgsTR4eUUmJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZXKCN/btsKQAInMhN/kr9zeFc99TQgsTR4eUUmJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZXKCN%2FbtsKQAInMhN%2Fkr9zeFc99TQgsTR4eUUmJK%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;713&quot; height=&quot;462&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GA4 사용법을 배워나가고는 있지만, 정말 안되겠다 싶을때, 로그서버로 대체해볼 생각도 있다.&lt;br&gt;그러려면 퍼져있는 analytics코드를 한곳에 모아줄 필요가 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드 작성하기&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;type설계하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 로깅할 이벤트를 타입으로 작성해주었고, 이벤트마다 필요한 파라미터를 정의해주었다.&lt;br&gt;LogEvent를 통해 params에 정의된 key값을 이벤트 종류로 받아올 수 있다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCCeJQ/btsKP9j6j4y/fgmett9jF965lOhk1z9bXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCCeJQ/btsKP9j6j4y/fgmett9jF965lOhk1z9bXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCCeJQ/btsKP9j6j4y/fgmett9jF965lOhk1z9bXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCCeJQ%2FbtsKP9j6j4y%2Ffgmett9jF965lOhk1z9bXK%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;1238&quot; height=&quot;508&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;508&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 모으기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;analytics를 사용해서 로깅하는 코드를 log.ts 모두 모아주었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2PenS/btsKQABBK3x/la3xYr1KFJ7OJPWL32fSR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2PenS/btsKQABBK3x/la3xYr1KFJ7OJPWL32fSR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2PenS/btsKQABBK3x/la3xYr1KFJ7OJPWL32fSR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2PenS%2FbtsKQABBK3x%2Fla3xYr1KFJ7OJPWL32fSR0%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;447&quot; height=&quot;384&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 만든 함수를 객체 하나에 묶어 내보내주었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eu0i6G/btsKP9j6BDY/CYJhnoeUmjfTnA2tCL616K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eu0i6G/btsKP9j6BDY/CYJhnoeUmjfTnA2tCL616K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eu0i6G/btsKP9j6BDY/CYJhnoeUmjfTnA2tCL616K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feu0i6G%2FbtsKP9j6BDY%2FCYJhnoeUmjfTnA2tCL616K%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;633&quot; height=&quot;178&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이러면 로깅 플랫폼을 GA4에서 자체 로그서버로 바꿨을때, analytics부분만 변경해주면 쉽게 변경할 수 있다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;관심사 분리하기&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이게 고민했던 부분이다.&lt;br&gt;로깅과 비즈니스 로직을 어떻게 구분할까?&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;페이지 전환&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 전환의 경우 root의 NavigationContainer에 이벤트를 주고, 이전 경로와 변경이 일어났을때, 로깅하게 만들면 페이지 전환은 더이상 신경쓸 필요가 없다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9aP1r/btsKQbP7Op3/1VkiX2A77oKzKoLRNjRqj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9aP1r/btsKQbP7Op3/1VkiX2A77oKzKoLRNjRqj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9aP1r/btsKQbP7Op3/1VkiX2A77oKzKoLRNjRqj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9aP1r%2FbtsKQbP7Op3%2F1VkiX2A77oKzKoLRNjRqj0%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;548&quot; height=&quot;295&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다른 로깅&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 앞서 로깅하려고 했던 블로그 구독 로깅, 글 조회로깅은 어떻게 비즈니스 로직과 구분할 수 있을까&lt;br&gt;이런 로깅은 공통적으로 사용자의 상호작용, 그중에서도 클릭에 의해 발생하는 이벤트를 기록하는 것이다.&lt;br&gt;따라서나는 이를 LogClick이라는 컴포넌트로 만들고, 이벤트 종류에 따라 다르게 동작하게 만들어줄 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이전에 설계한 타입을 기반으로 LogClick에 전달해줄 props타입을 정의해준다.&lt;br&gt;간단하게 로깅할 이벤트 종류를 뜻하는 logType과 그 타입에 맞는 data를 props로 받도록 해주었다.&lt;br&gt;type으로 정의해두었기에, typescript에 의해 추론이 가능해진다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export type LogParamsType = {
&amp;nbsp;&amp;nbsp;subscribe: string;
&amp;nbsp;&amp;nbsp;unsubscribe: string;
&amp;nbsp;&amp;nbsp;viewPost: Article;
&amp;nbsp;&amp;nbsp;screen: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;screenName: string;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;screenClass: string;
&amp;nbsp;&amp;nbsp;};
};

export type LogEvent = keyof LogParamsType;

//여기까지 이전에 설계한 type

interface LogClickProps&amp;lt;T extends LogEvent&amp;gt; {
&amp;nbsp;&amp;nbsp;logType: T;
&amp;nbsp;&amp;nbsp;data: LogParamsType[T];
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이제 LogClick을 만들어주었다.&lt;br&gt;중요한 것은 어떤 컴포넌트인지와 관계없이, children을 그대로 내보내주되, onPress에 로깅 이벤트를 추가해주어야 한다는 것이다.&lt;br&gt;cloneElement를 통해 children을 복사하고, onPress이벤트를 추가해주는 형태로 구현해주었다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export default function LogClick&amp;lt;T extends LogEvent&amp;gt;({
&amp;nbsp;&amp;nbsp;logType,
&amp;nbsp;&amp;nbsp;data,
&amp;nbsp;&amp;nbsp;children,
}: PropsWithChildren&amp;lt;LogClickProps&amp;lt;T&amp;gt;&amp;gt;) {
&amp;nbsp;&amp;nbsp;if (!isValidElement(children)) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return children;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;const childWithPress = cloneElement(children as ReactElement, {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPress: () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;children?.props.onPress();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Log[logType](data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;return childWithPress;
}&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;이제 비즈니스 로직과 로깅로직이 분리되었다.&lt;br&gt;에러처리는 Errorboundary를 통해 분리되어있으므로,&lt;br&gt;이제 비즈니스 로직은 성공한 경우에 대해서만 다루게되고, 오류는 Errorboundary에 의해, 로깅은 LogClick에 의해 관리되게 된다.&lt;br&gt;요리사가 더이상 서빙과 계산을 하지 않게 되는 것이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkYVzz/btsKPfFJa1D/4ayXp4ZKwll0YFKLDEeiJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkYVzz/btsKPfFJa1D/4ayXp4ZKwll0YFKLDEeiJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkYVzz/btsKPfFJa1D/4ayXp4ZKwll0YFKLDEeiJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkYVzz%2FbtsKPfFJa1D%2F4ayXp4ZKwll0YFKLDEeiJK%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;1223&quot; height=&quot;693&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Android&lt;/b&gt;&lt;br&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.devfeed&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://play.google.com/store/apps/details?id=com.devfeed&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Dev-Feed - Google Play 앱&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;개발 블로그 RSS 구독 서비스&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.devfeed&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zcHV3/hyXzPG1Acp/KfK8vQumyQpzDT8yCAZ060/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bdNau6/hyXDkZDLJ6/en651JmvW4yBJBLbKIPrA1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/iRZ77/hyXzTbBVhd/hQ9CCekNGf9E9pvhjEvp21/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.devfeed&amp;amp;hl=ko&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.devfeed&amp;amp;hl=ko&quot; target=&quot;_blank&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.devfeed&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zcHV3/hyXzPG1Acp/KfK8vQumyQpzDT8yCAZ060/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/bdNau6/hyXDkZDLJ6/en651JmvW4yBJBLbKIPrA1/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/iRZ77/hyXzTbBVhd/hQ9CCekNGf9E9pvhjEvp21/img.png?width=240&amp;amp;height=240&amp;amp;face=0_0_240_240')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Dev-Feed - Google Play 앱&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;개발 블로그 RSS 구독 서비스&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;play.google.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;iOS&lt;/b&gt;&lt;br&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/dev-feed/id6737579223&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://apps.apple.com/kr/app/dev-feed/id6737579223&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;‎Dev-Feed&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;‎개발 블로그, 이제 한 눈에 확인하고, 구독해보세요. Dev-Feed로 나만의 기술블로그 피드를 만들어봐요 Dev-Feed는 사용자가 쉽게 RSS를 구독하고, 구독한 블로그로 접속할 수 있게 돕는 어플리케이&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/dev-feed/id6737579223&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bU58DS/hyXDeLTBeo/vxiuyt2CwgwR1URfeaYADk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/iMxUp/hyXzNa7Nzp/S1VSE5RfFUkrMw7yKCs4M1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://apps.apple.com/kr/app/dev-feed/id6737579223&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/dev-feed/id6737579223&quot; target=&quot;_blank&quot; data-source-url=&quot;https://apps.apple.com/kr/app/dev-feed/id6737579223&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bU58DS/hyXDeLTBeo/vxiuyt2CwgwR1URfeaYADk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/iMxUp/hyXzNa7Nzp/S1VSE5RfFUkrMw7yKCs4M1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;‎Dev-Feed&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;‎개발 블로그, 이제 한 눈에 확인하고, 구독해보세요. Dev-Feed로 나만의 기술블로그 피드를 만들어봐요 Dev-Feed는 사용자가 쉽게 RSS를 구독하고, 구독한 블로그로 접속할 수 있게 돕는 어플리케이&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;apps.apple.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>GA4</category>
      <category>관심사분리</category>
      <category>로깅</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/376</guid>
      <comments>https://0422.tistory.com/376#entry376comment</comments>
      <pubDate>Thu, 21 Nov 2024 10:09:03 +0900</pubDate>
    </item>
    <item>
      <title>좋은 코드를 작성한다는 것, 그를 위한 노력에 대한 고민</title>
      <link>https://0422.tistory.com/375</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;고민의 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전부터 좋은 코드란 무엇인지 고민해왔다.&lt;/p&gt;
&lt;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;좋은 코드를 작성하는 방법이라기보다는, 조금 더 근본적인 이해와 내가 해야하는 노력에 대한 글이다.&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;500&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YkxOu/btsKMLqPHW2/oWC4QbU1B3Syk3f6ULNhy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YkxOu/btsKMLqPHW2/oWC4QbU1B3Syk3f6ULNhy1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YkxOu/btsKMLqPHW2/oWC4QbU1B3Syk3f6ULNhy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYkxOu%2FbtsKMLqPHW2%2FoWC4QbU1B3Syk3f6ULNhy1%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;500&quot; height=&quot;449&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;449&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;/h3&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 data-ke-size=&quot;size23&quot;&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;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 현대 사회에서 불편한 것들, 수요는 빠르게 생기고 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제품을 한번 만들고 마는 것이 아니라, 지속적으로 관리하고, 새로운 것들을 추가해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 통해 개발자는 제품에서 존재하던 오류는 없애고, 이를 통해 지속적으로 가치를 제공하며 새로운 것들을 추가할 수 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보통 좋은 코드라 함은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;유지보수하기 좋은&lt;/b&gt;&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;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;/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;모듈이 10만갠데, 제품에서 발생한 모든 오류를 혼자서 고치고, 새로운 기능을 추가한다는건, 정말 물리적으로 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 여러 명이 유지보수 할 수 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 나말고도 &lt;b&gt;다른 사람이 잘 읽을 수 있어야한다.&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;이 기능을 추가하기보다는&lt;b&gt; 잘 읽을 수 있어야 한다&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 민수는 동쪽으로 100m 이동한 후 우회전하여 200m를 이동하고, 다시 좌회전 하여 20m이동하여 다수의 사람들과 함께 학습할 수 있도록 마련된 장소에 가는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 민수는 학교에 가는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 두번째가 곧바로 이해된다. &lt;b&gt;그렇다면 두번째가 옳은가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 코드에서는 아마 아래처럼 코드를 짤 수 있을 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1731917319692&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function 학교로가기(학생){
	학생.동쪽전환()
    학생.이동(100)
    학생.우회전()
    학생.이동(200)
    학생.좌회전(20)
}

const 민수 = new 학생()
학교로가기(민수)&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;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;AWS CLI SDK로 S3버킷에 빌드된 웹서버 번들파일을 업로드한다.&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;AWS를 자주 사용한 사람이라면, 익숙한 문장일 수 있겠지만 AWS사용을 한번도 해보지 않은 사람은 이 문장이 의미하는 것을 바로 파악할 수 없다. 이건 추상화된 문장이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI, SDK, S3버킷, 웹서버, 빌드, 번들파일이라는 단어는 일상에서 자주 사용되지 않는 단어다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 단어로 추상화된 문장을 배경지식이 없는 사람이 이해하는 것은 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 추상화는 일반적으로 알 수 있는 배경지식을 통해 이뤄져야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;b&gt; 함께 코드를 작성하는 사람들이 가진 배경지식을 기반으로 진행&lt;/b&gt;되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 배경지식을 갖기위해서는 계속해서 제품과 코드에 대해 의견을 나누어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지 않으면 점차적으로 파편화된 배경지식을 갖게되어 특정 사람, 특정 집단만 이해하는 추상화가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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-filename=&quot;406151-PENPAM-124.jpg&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NSiQn/btsKOlxnkqh/adGbSZFRzaIVqmNTjROYy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NSiQn/btsKOlxnkqh/adGbSZFRzaIVqmNTjROYy1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NSiQn/btsKOlxnkqh/adGbSZFRzaIVqmNTjROYy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNSiQn%2FbtsKOlxnkqh%2FadGbSZFRzaIVqmNTjROYy1%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;349&quot; height=&quot;349&quot; data-filename=&quot;406151-PENPAM-124.jpg&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2000&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;h4 data-ke-size=&quot;size20&quot;&gt;사용자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘리베이터 사용자는  추상화된 버튼을 눌러 목적지에 도착하면 된다.(2층을 눌러 2층으로 이동)&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;h4 data-ke-size=&quot;size20&quot;&gt;설계자/유지보수자&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엘리베이터 설계자/유지보수자는 2층 버튼을 눌러 도착하는 추상화를 제공하는 사람이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2층이 몇 미터인지, 어떻게 층을 나눴는지, 도르레를 얼마나 움직여야 하는지, 최대 하중이 얼마인지를 알 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 엘리베이터 설계자는 도르레가 어떻게 설계되었는지는 알 필요가 없다. 물론 알면 더 튼튼한 엘리베이터를 만들 수 있을지도 모른다.&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;/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 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;b&gt;동시에 소비&lt;/b&gt;하기도한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 작성하는 사람 입장에서 만들어진 함수와 라이브러리를 사용한다면, 엘리베이터 사용자의 입장이 될 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 로직을 작성하거나 유지보수하게된다면 엘리베이터 설계자의 입장이될 것이다.&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;개인적인 경험&lt;/h4&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;로직 부분을 컴포넌트에서 작성하는 경우가 많았는데, 해당 부분이 추상화가 되지 않았다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 로직을 모두 분리하자니 파일이 어디에 무엇이 있는지 정리되기가 어려워진다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 개발을 위해서 추상화 하지 않고, 공통 컴포넌트로 분리하려고 하니 불가능한 일에 가까웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 혼란을 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;영역을 분리하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명확하게, 설계 부분, 사용 부분을 분리하고 지킬 필요가 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 설계부를 모두 라이브러리로 분리하면 좋겠지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생했을 때 매번 고쳐서 배포하고, 버전을 올리는것은 다양한 사람들이 유지보수하는 것을 더 어렵게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 공통적으로 사용되는 부분, 완전히 분리가 된 책임을 지는 기능들만 분리하여 라이브러리로 배포하면 된다. 혹은 모노레포로 관리할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, React는 선언형으로 UI를 업데이트한다는 역할을 수행한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 직접 함수형 컴포넌트를 호출하는 것도 가능하지만, React는 컴포넌트가 설명만 반환하기를 원하고, 이것을 지켜야 React가 원하는 사용자의 코드를 줄인다라는 목표를 달성할 수 있게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yNI4S/btsKOmQCbD6/BirKJW1tgxiPOkaxlO7DlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yNI4S/btsKOmQCbD6/BirKJW1tgxiPOkaxlO7DlK/img.png&quot; data-alt=&quot;https://ko.legacy.reactjs.org/docs/design-principles.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yNI4S/btsKOmQCbD6/BirKJW1tgxiPOkaxlO7DlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyNI4S%2FbtsKOmQCbD6%2FBirKJW1tgxiPOkaxlO7DlK%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;494&quot; height=&quot;212&quot; data-origin-width=&quot;1578&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ko.legacy.reactjs.org/docs/design-principles.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUvVh/btsKNcnMeLn/GkTKI5hE022oBXZz9rQx40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUvVh/btsKNcnMeLn/GkTKI5hE022oBXZz9rQx40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUvVh/btsKNcnMeLn/GkTKI5hE022oBXZz9rQx40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUvVh%2FbtsKNcnMeLn%2FGkTKI5hE022oBXZz9rQx40%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;714&quot; height=&quot;93&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;202&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 공통적이지 않은 부분은? 어떻게 처리하는 것이 좋을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;p data-ke-size=&quot;size16&quot;&gt;그리고 나처럼 새롭게 신입개발자로 회사에 합류하려는 사람은 무작정 회사의 컨벤션을 따르는 것이아니라, 컨벤션에 물음을 던지고 어디가 사용부분이고, 설계부분인지 이해할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심적인 부분에 대해 물음을 던지지 못하고, 제대로 이해하지 못한다면 이제까지 개발했던 컨벤션과 충돌한다고 느끼게 될 것이고 나처럼 혼란에 빠지게 될것이다.&lt;/p&gt;
&lt;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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, 팀과 함께 유지보수하기 좋은 코드를 작성하기 위해서는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일반적인 개념으로 추상화할 필요가 있다.&lt;/li&gt;
&lt;li&gt;불가능하다면, 팀 내부에서 모두가 공유하고 있는 배경지식을 기반으로 추상화 해야한다.&lt;/li&gt;
&lt;li&gt;설계자와 사용자에게 적절한 추상화정도는 다르다.&lt;/li&gt;
&lt;li&gt;개발자는 코드의 설계자임과 동시에 사용자가 될 수 있다.&lt;/li&gt;
&lt;li&gt;어디서 설계할지, 어디서 사용할지를 컨벤션으로 정하고 지키는 것이 매우 중요하다.&lt;/li&gt;
&lt;/ol&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;/li&gt;
&lt;li&gt;팀 내부의 컨벤션의 의도를 제대로 이해하기 위해 계속해서 물음을 던지고, 올바른 의도를 이해하여 지킬 것 (무작정 지키지 않을 것)&lt;/li&gt;
&lt;li&gt;추상화 정도와 위치를 항상 고민할 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/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;</description>
      <category>Frontend/ 원리 탐구하기</category>
      <category>react</category>
      <category>고민</category>
      <category>유지보수</category>
      <category>좋은코드</category>
      <category>추상화</category>
      <category>컨벤션</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/375</guid>
      <comments>https://0422.tistory.com/375#entry375comment</comments>
      <pubDate>Mon, 18 Nov 2024 18:24:37 +0900</pubDate>
    </item>
    <item>
      <title>웹 브라우저 이벤트루프는 왜 그런 우선순위로 작업을 실행시킬까 (feat. CPU 스케줄링)</title>
      <link>https://0422.tistory.com/374</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 OS수업을 듣고 있는데, 프로세스 스케줄링 관련 부분을 배우고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 문득, 최근 면접에서 받은, 그리고 제대로 답하지 못했던&lt;b&gt; 이벤트루프는&amp;nbsp;왜&amp;nbsp;그런&amp;nbsp;우선순위로&amp;nbsp;작업을&amp;nbsp;실행시킬까?&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;priority.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uuTh3/btsKHd7dlSh/BgG5p6KneWuK40d7RJrj21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uuTh3/btsKHd7dlSh/BgG5p6KneWuK40d7RJrj21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uuTh3/btsKHd7dlSh/BgG5p6KneWuK40d7RJrj21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuuTh3%2FbtsKHd7dlSh%2FBgG5p6KneWuK40d7RJrj21%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;244&quot; height=&quot;244&quot; data-filename=&quot;priority.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동시성&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Concurrency(동시성)와 Parallelism(병렬성)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성과 병렬성은 같은 단어 같지만, 분명히 다른 단어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성은 &lt;b&gt;동시에 실행되는 것 같이 보이는것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬성은 &lt;b&gt;실제로 여러 작업이 동시에 처리되는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성에 대해 말할 것이다.&lt;/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;과거, 코어가 하나인 CPU에서 동시성을 해결하는 것은 매우 중요했다.&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;JS도 마찬가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Javascript는 &lt;b&gt;싱글 스레드&lt;/b&gt;이다. &lt;b&gt;코어가 하나인 CPU&lt;/b&gt;인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 우리는 로직 수행도 하고, 네트워크 요청도하고, UI업데이트 명령도 내려야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;모든 작업이 동시에 실행되는 것처럼&lt;/b&gt; 보여야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지 요소를 우선순위라는 같은 관점에서 바라보고, &lt;b&gt;왜 이렇게 설계되었는지 &lt;span style=&quot;color: #ef5369;&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 data-ke-size=&quot;size23&quot;&gt;스케줄링, 우선순위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 CPU는 시분할로 작업을 작게 쪼개고, 이걸 작업을 할당해서 순환시켜가며 실행하는 방식으로 동시성을 제어했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 어떻게 할당하냐?가 바로 &lt;b&gt;스케줄링&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동시성을 구현하기 위해서는 아이러니하게도 순서가 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 더 빨리 실행될 필요가 있는 것들, 더 늦게 실행되도 되는 것들, 그리고 총 실행 시간, 스위칭 시간 등등... 수많은 목적들을 고려해서, 목적에 맞게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위에 대해 한가지 예시를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 예로 time slice형태의 스케줄링에서 throughput을 개선시키기위해서는 CPU Intensive 프로세스와 I/O Intensive 프로세스에 따른 우선순위를 고려할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU Intensive 프로세스는 CPU사용률이 높고, I/O 작업량이 적은 프로세스다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I/O Intensive 프로세스는 CPU사용률이 낮고, I/O 작업량이 많은 프로세스다.&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;I/O Intensive한 프로세스는 CPU에서 많이 작업될 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 I/O Intensive한 프로세스는 CPU에 할당되어도 빨리 Block상태가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 context switch를 줄일 수 있고, 전반적인 대기시간을 줄일 있어 throughput을 증대시킬 수 있다.&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;반대로 CPU Intensive한 프로세스는 CPU에서 많이 작업되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 프로세스는 CPU에서 많이 실행되어야한다. 이건 I/O Intensive한 프로세스의 대기시간을 늘려 throughput을 악화시킬 수 있다. 따라서 우선순위가 낮아진다.&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;본론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Microtask, Macrotask, rAF&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;b&gt;microtask &amp;gt; rAF &amp;gt;macrotask&lt;/b&gt;가되겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그럼 이런 우선순위가 나오게된걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 이런 우선순위가 나오게된걸까&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 그런가에 대해 생각해보려면, 이 관점에서 &lt;b&gt;브라우저가 무슨 일을 주로 하는지&lt;/b&gt; 생각해볼 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU스케줄링처럼 동시성을 구현함으로써 얻을&amp;nbsp;&lt;b&gt;목적을 이해할 필요&lt;/b&gt;가 있다.&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;보여주어야한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&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;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, javascript가 실행되는 경우이므로, 1번도 제외된다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Microtask Queue&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise와 MutationObserver가 이 작업에 속한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;requestAnimationFrame&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;request Animation Frame은 브라우저가 &lt;b&gt;리페인트 바로 전에&lt;/b&gt;, 애니메이션을 업데이트할 지정될 함수를 호출하도록 요청하는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MacrotaskQueue&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setTimeout, setInterval이 이에 속한다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용자의 액션에 따라 &lt;b&gt;업데이트된 화면을 보여줄때&lt;/b&gt; 가장 중요한 것은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보여준다&lt;/b&gt;는 부분에서 requestAnimationFrame이 가장 우선순위가 높을 것 같다.&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;그리고 &quot;&lt;b&gt;업데이트&lt;/b&gt;&quot;와 &quot;&lt;b&gt;된&lt;/b&gt;&quot; 두 가지를 고려해서 생각해보자.&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면의 업데이트는 count값을 올리는 것처럼 동기적일 수도 있지만, 서버 데이터를 받아오는 것처럼 &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;된&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 data-ke-size=&quot;size16&quot;&gt;그런데 만약 &lt;b&gt;업데이트의 우선순위&lt;/b&gt;보다 &lt;b&gt;그리는 것의 우선순위&lt;/b&gt;가 더 높다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 업데이트의 경우, 데이터가 최신이 아닌데도, 브라우저는 화면을 그리게 될 것이고, 그럼 유저가 보는 화면에는 문제가 생길 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 화면을 한번 더 그려야, 제대로 보이게 될 것이다. 이건 효율적이지도 않고, 제대로 작동하지 않을 가능성이 있다.&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;그리는 것(requestAnimationFrame)&lt;/b&gt;보다 &lt;b&gt;업데이트(microtask)&lt;/b&gt;의 우선순위가 높아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 자연스럽게 그리는것도, 업데이트라고 보기도 어려운 MacroTask는 자연스럽게 우선순위가 낮아지게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&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;업데이트된&lt;/b&gt;을 담당하는 microtask의 우선순위가 가장 높고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보여준다&lt;/b&gt; 를 담당한다 볼 수 있는 requestAnimationFrame의 우선순위가 다음이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 목적과 조금 거리가 있는 macrotask의 우선순위가 가장 낮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론으로부터 비롯되는 몇가지 사실&lt;/h3&gt;
&lt;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;무한히 많은 microtask가 있다면 macrotask는 starve상태에 빠질 수 있다.&lt;/li&gt;
&lt;li&gt;무한한 재귀를 가진 requestAnimationFrame이 있어도, repaint시점 직전에 &lt;b&gt;1회&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;requestAnimationFrame은 macrotask를 block시킬 수 없다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/ 원리 탐구하기</category>
      <category>macrotask</category>
      <category>microtask</category>
      <category>requestAnimationFrame</category>
      <category>우선순위</category>
      <category>이벤트루프</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/374</guid>
      <comments>https://0422.tistory.com/374#entry374comment</comments>
      <pubDate>Tue, 12 Nov 2024 17:54:00 +0900</pubDate>
    </item>
    <item>
      <title>React native Code Push 적용 및 인앱 업데이트시 UX 개선하기</title>
      <link>https://0422.tistory.com/373</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/53gbp/btsJ4ouOmUb/cNsNF5TprcMF3MJohgZaGK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/53gbp/btsJ4ouOmUb/cNsNF5TprcMF3MJohgZaGK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/53gbp/btsJ4ouOmUb/cNsNF5TprcMF3MJohgZaGK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/53gbp/btsJ4ouOmUb/cNsNF5TprcMF3MJohgZaGK/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;398&quot; height=&quot;852&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;2024 관광데이터 활용 공모전에 프론트엔드 개발자로 참여하여 프로젝트를 진행중이다.&lt;br&gt;앱을 1차적으로 릴리즈하고, 다음 업데이트를 한번 해보니, 스토어 심사를 진행하는 것이 생각보다 시간이 걸리고 불편한 일이라는 것을 느낄 수 있었다.&lt;br&gt;이런 부분들은 이미 알고는 있었으나, 실제로 경험해보니 더욱 체감이 되었다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tc2yt/btsJ25Qp3wC/APLJrX1l3ZYgO0kaWWOtkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tc2yt/btsJ25Qp3wC/APLJrX1l3ZYgO0kaWWOtkK/img.png&quot; data-alt=&quot;원스토어는 빠른편이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tc2yt/btsJ25Qp3wC/APLJrX1l3ZYgO0kaWWOtkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftc2yt%2FbtsJ25Qp3wC%2FAPLJrX1l3ZYgO0kaWWOtkK%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;727&quot; height=&quot;444&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원스토어는 빠른편이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;웹뷰를 사용하면 해결할 수 있었겠지만, 초기에 웹뷰를 사용하지 않고, RN으로만 진행하기로 하였기때문에, 다른 방법을 탐색해야했다.&lt;br&gt;그러다 찾은 것이 Code push다.&lt;br&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;이것을 이해하기 위해서는 RN의 원리에 대해 간단하게 나마 알아보아야한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;React native의 스레드&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;React native는 메인스레드, JS스레드, 백그라운드 스레드(Shadow 스레드)로 구성된다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 메인스레드&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;메인스레드는 UI그리기, 사용자입력처리, 네이티브 이벤트 처리 등 네이티브에서 일어나는 작업들을 수행하는 스레드이다.&lt;br&gt;애니메이션 전환 역시 메인스레드에서 처리된다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Shadow 스레드&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;shadow 스레드는 레이아웃 계산을 맡아 컴포넌트 레이아웃을 계산한다. 그리고, 이걸 메인스레드에 전달한다.&lt;br&gt;Yoga라는 오픈소스 레이아웃 엔진을 사용한다고 한다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. JS스레드&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 스레드가 JS번들을 실행한다. 즉, RN으로 작성한 코드가 직접적으로 실행되는 스레드이다.&lt;br&gt;다만, 이 스레드의 특이한 점은 Hermes라는 JS엔진을 사용한다는 것이다.&lt;br&gt;Hermes는 다른 엔진과 다르게 JS 코드를 미리 바이트코드로 컴파일한다. 그래서 인터프리터가 컴파일을 하지 않는다. 따라서 이게 성능을 개선하는데 도움을 줄 수 있다.&lt;br&gt;그리고 스레드들은 Bridge라는 개념을 통해 필요한 정보를 직렬화하여 비동기 통신한다.(non block)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLKjjm/btsJ2RSWbfy/znIHQtbP4ol5VhF4w9TJo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLKjjm/btsJ2RSWbfy/znIHQtbP4ol5VhF4w9TJo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLKjjm/btsJ2RSWbfy/znIHQtbP4ol5VhF4w9TJo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLKjjm%2FbtsJ2RSWbfy%2FznIHQtbP4ol5VhF4w9TJo1%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;1083&quot; height=&quot;464&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;464&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 그리면 이런 형태로 작동하게되는 것이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;React native 앱이 실행되는 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;1. 앱이 시작하며 메인 스레드가 실행된다. 이때 JS번들을 로드한다.&lt;br&gt;2. JS번들의 로드가 완료되면 메인스레드는 JS번들을 JS스레드로 보낸다.&lt;br&gt;3. React렌더링이 시작된다.&lt;br&gt;4. Shadow 스레드가 레이아웃 계산을 마치면 레이아웃 결과물을 메인스레드로 보낸다.&lt;br&gt;5. 메인 스레드는 Shadow 스레드가 보낸 레이아웃을 렌더링한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇다면 결국, JS번들과 앱의 메인 스레드 코드는 분리되어있다는 것을 알 수 있다.&lt;br&gt;그리고, 보통 RN개발자가 작성하는 코드는 JS번들로 결과물이 나오게된다.&lt;br&gt;그렇다면,&lt;b&gt; JS번들파일을 네트워크를 통해 전달받을 수 있다면, 실시간으로 업데이트 할 수 있게 된다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 구성 및 기본 설정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;설정은 &lt;a href=&quot;https://learn.microsoft.com/en-us/appcenter/distribution/codepush/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;공식 문서&lt;/span&gt;&lt;/a&gt;와 &lt;a href=&quot;https://github.com/microsoft/react-native-code-push&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;github 레포&lt;/span&gt;&lt;/a&gt;를 확인하는게 가장 좋다.&lt;br&gt;특히 공식문서가 매우몹시 친절하게 되어있으므로 따라하기만 하면 적용할 수 있을 것이다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blcFO1/btsJ4cg3gOe/Cmd3BZhNkUrqyXd7kfhqkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blcFO1/btsJ4cg3gOe/Cmd3BZhNkUrqyXd7kfhqkk/img.png&quot; data-alt=&quot;안드로이드 기준 바꿔준 파일들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blcFO1/btsJ4cg3gOe/Cmd3BZhNkUrqyXd7kfhqkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblcFO1%2FbtsJ4cg3gOe%2FCmd3BZhNkUrqyXd7kfhqkk%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;351&quot; height=&quot;226&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;안드로이드 기준 바꿔준 파일들&lt;/figcaption&gt;
&lt;/figure&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;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 appcenter에서 커맨드를 지원해서 이렇게 설정해주면된다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;appcenter codepush release-react -a {user name}/{앱 이름} -d {type(Production or Staging)}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 현재 우리 프로젝트는 yarn workspace를 사용한 모노레포의 형태를 취하고 있기에, 이렇게 사용할 수 없었다.&lt;br&gt;&lt;b&gt;appcenter 레포와 이슈들을 확인했으나, 번들 entry를 설정해주는 args가 없었다.&lt;/b&gt;&lt;br&gt;그래서 따로 쉘파일을 만들어 주었다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;mkdir CodePush

# 번들 생성
npx react-native bundle \
--entry-file=./index.js \
--bundle-output=./CodePush/index.android.bundle \
--assets-dest=./CodePush/ \
--dev=false \
--platform=android

# codepush 업로드
# t에 타겟 버전(android build)
appcenter codepush release \
-a 유저네임/프로젝트 \
-c ./CodePush \
-d 프로덕션 \
-t 타겟버전
``

rm -rf CodePush&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;번들을 직접 만들고, codepush로 해당 번들 폴더를 업로드하고, 삭제하는 것이다.&lt;br&gt;이걸 script에 등록해서 yarn run codepush:android형태로 쓸수 있게 했다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본설정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기본설정은 매우 간단하다&lt;br&gt;App.tsx에서 전체 앱을 CodePush로 감싸주면 된다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;...
import CodePush from 'react-native-code-push';

function App() {
&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;);
}
export default CodePush(App);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이렇게 설정해주면 Codepush는&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;앱이 실행될때, 번들 업데이트가 있는지 확인하며&lt;/li&gt;&lt;li&gt;있다면 다운받고&lt;/li&gt;&lt;li&gt;다음번에 앱이 재실행 됐을때 업데이트되게 된다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 나는 앱이 즉시 업데이트되기를 원했다. 계속해서 개선할 점이 보이고, 그런 부분이 즉시 개선되었으면 했기때문이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;설정 옵션&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Codepush(options)(App)의 형태로 코드 푸쉬가 어떻게 동작할 것인지 옵션을 정해줄 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;deploymentKey, installMode, mandatoryInstallMode, minimumBackgroundDuration, updateDialog, rollbackRetryOptions, checkFrequency의 옵션이 존재한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Codepush 의 d.ts를 읽어보면 내용을쉽게 알 수 있다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9LkZN/btsJ35WLxj1/sznkAESnXeBZ48uKgU9fF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9LkZN/btsJ35WLxj1/sznkAESnXeBZ48uKgU9fF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9LkZN/btsJ35WLxj1/sznkAESnXeBZ48uKgU9fF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9LkZN%2FbtsJ35WLxj1%2FsznkAESnXeBZ48uKgU9fF0%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;968&quot; height=&quot;128&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 installMode를 변경해주고 싶었는데, 왜냐하면 default가 ON_NEXT_RESTART, 즉 다음번에 앱이 실행될때 업데이트 하는 방식이었기때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 이런식으로 옵션을 주고&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const codePushOptions: CodePushOptions = {
&amp;nbsp;&amp;nbsp;checkFrequency: CodePush.CheckFrequency.ON_APP_START,
&amp;nbsp;&amp;nbsp;installMode: CodePush.InstallMode.IMMEDIATE,
};&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이렇게 적용해주었다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export default CodePush(codePushOptions)(App);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이렇게 처리하면, 이제 문제없이 잘 작동할 것이라 생각했다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발생&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 문제가 발생했다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3810&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m0HtO/btsJ20WrtIl/8q0rcNdkvfr2dPNVMCSqc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m0HtO/btsJ20WrtIl/8q0rcNdkvfr2dPNVMCSqc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m0HtO/btsJ20WrtIl/8q0rcNdkvfr2dPNVMCSqc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm0HtO%2FbtsJ20WrtIl%2F8q0rcNdkvfr2dPNVMCSqc0%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;3810&quot; height=&quot;590&quot; data-origin-width=&quot;3810&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것은 바로... 회원가입을 할때 앱 업데이트로 강제 재시작이 되어버리는 것...&lt;br&gt;이걸 어떻게 풀어낼지 고민했다. 회원가입을 한다 하더라도, 최신 업데이트 버전의 앱은 보여주고 싶었기 때문이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;CodePush에서 제공하는 함수들&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 CodePush는 컴포넌트를 Wrapping하는 방식 뿐만 아니라, 직접 세부 동작을 지정할 수도 있게 만들어져 있었다.&lt;br&gt;업데이트가 필요한지 확인하는 함수, 번들을 받아오는 함수, 번들을 기기에설치하는 함수 모두 제공한다.&lt;br&gt;&amp;nbsp;&lt;br&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;/li&gt;&lt;li&gt;있는지 없는지에 따라 최신버전인지 분기&lt;/li&gt;&lt;li&gt;최신버전이 아니라면 번들 다운로드 + 최신 번들 기기에 설치&lt;/li&gt;&lt;li&gt;앱 재시작&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 될 것이다.&lt;br&gt;그리고 그 과정에서 로딩화면을 보여주면 된다!&lt;br&gt;&amp;nbsp;&lt;br&gt;아래와 같이 작성할 수 있다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useEffect, useState } from 'react';
import CodePush from 'react-native-code-push';

export default function useCodePush() {
&amp;nbsp;&amp;nbsp;const [isRecent, setRecent] = useState&amp;lt;boolean&amp;gt;();

&amp;nbsp;&amp;nbsp;const checkUpdate = async () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const update = await CodePush.checkForUpdate(); //업데이트가 있는지 확인
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setRecent(!update); //최신 상태 변경
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (update) { 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const newPackage = await update.download(); //번들 다운로드
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await newPackage.install(CodePush.InstallMode.IMMEDIATE); //즉시 번들 설치
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CodePush.notifyAppReady(); //CodePush에 해당 기기가 최신 버전으로 설치했다고 알림
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CodePush.restartApp(); // 앱 재시작
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;checkUpdate();
&amp;nbsp;&amp;nbsp;}, []);

&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isRecent,
&amp;nbsp;&amp;nbsp;};
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그런데 download메서드가 굉장한걸 지원한다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1HBi/btsJ22NwRsO/gUNjmwkCjPIeCxMq6Huna0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1HBi/btsJ22NwRsO/gUNjmwkCjPIeCxMq6Huna0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1HBi/btsJ22NwRsO/gUNjmwkCjPIeCxMq6Huna0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1HBi%2FbtsJ22NwRsO%2FgUNjmwkCjPIeCxMq6Huna0%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;1216&quot; height=&quot;172&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;172&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;686&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rQzAD/btsJ23ZUlWd/BOHxmkxFwOSJjzRHe3HP30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rQzAD/btsJ23ZUlWd/BOHxmkxFwOSJjzRHe3HP30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rQzAD/btsJ23ZUlWd/BOHxmkxFwOSJjzRHe3HP30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrQzAD%2FbtsJ23ZUlWd%2FBOHxmkxFwOSJjzRHe3HP30%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;686&quot; height=&quot;247&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 번들의 totalBytes와 receivedBytes를 보여주는 것이다.&lt;br&gt;이걸 활용하면 로딩 프로그래스바를 만들어줄 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;최종코드&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useEffect, useState } from 'react';
import CodePush from 'react-native-code-push';

export default function useCodePush() {
&amp;nbsp;&amp;nbsp;const [isRecent, setRecent] = useState&amp;lt;boolean&amp;gt;();
&amp;nbsp;&amp;nbsp;const [downloadProgress, setDownloadProgress] = useState(0);

&amp;nbsp;&amp;nbsp;const checkUpdate = async () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const update = await CodePush.checkForUpdate();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setRecent(!update);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (update) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const newPackage = await update.download(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;({ receivedBytes, totalBytes }) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setDownloadProgress(receivedBytes / totalBytes);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;await newPackage.install(CodePush.InstallMode.IMMEDIATE);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CodePush.notifyAppReady();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CodePush.restartApp();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;checkUpdate();
&amp;nbsp;&amp;nbsp;}, []);

&amp;nbsp;&amp;nbsp;return {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;downloadProgress,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isRecent,
&amp;nbsp;&amp;nbsp;};
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이걸 이제 App에 적용해주자.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function App() {
&amp;nbsp;&amp;nbsp;const { isRecent, downloadProgress } = useCodePush();

&amp;nbsp;&amp;nbsp;if (!isRecent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;UpdateLoading isRecent={isRecent} downloadProgress={downloadProgress} /&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp; ...
&amp;nbsp;&amp;nbsp;);
}
export default Sentry.wrap(App);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;UpdateLoading컴포넌트는 &lt;a href=&quot;https://github.com/TRIP-SPOT/SPOT-Client/blob/main/packages/react-native/src/components/common/UpdateLoading.tsx&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;SPOT 레포 github&lt;/span&gt;&lt;/a&gt;를 참고하면 좋을 것 같다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/peP0U/btsJ3sdNW4w/zs9J7DuUrmgXLNIlDIkylk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/peP0U/btsJ3sdNW4w/zs9J7DuUrmgXLNIlDIkylk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/peP0U/btsJ3sdNW4w/zs9J7DuUrmgXLNIlDIkylk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/peP0U/btsJ3sdNW4w/zs9J7DuUrmgXLNIlDIkylk/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;258&quot; height=&quot;552&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;1284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>codepush</category>
      <category>React Native</category>
      <category>UX개선</category>
      <category>로딩창</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/373</guid>
      <comments>https://0422.tistory.com/373#entry373comment</comments>
      <pubDate>Sat, 12 Oct 2024 11:25:15 +0900</pubDate>
    </item>
    <item>
      <title>엘리스 인턴십 회고</title>
      <link>https://0422.tistory.com/372</link>
      <description>&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;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BIu3u/btsJnhxnm1e/P20QZHkXmTAV2SBMu6DXy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BIu3u/btsJnhxnm1e/P20QZHkXmTAV2SBMu6DXy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BIu3u/btsJnhxnm1e/P20QZHkXmTAV2SBMu6DXy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBIu3u%2FbtsJnhxnm1e%2FP20QZHkXmTAV2SBMu6DXy1%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;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&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;h2 data-ke-size=&quot;size26&quot;&gt;진행한 일들&lt;/h2&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cppTaw/btsJmHipb3f/AKL4Ncw56kQPjggHDvXXQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cppTaw/btsJmHipb3f/AKL4Ncw56kQPjggHDvXXQk/img.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;264&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.7918%; margin-right: 10px;&quot; data-widthpercent=&quot;45.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cppTaw/btsJmHipb3f/AKL4Ncw56kQPjggHDvXXQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcppTaw%2FbtsJmHipb3f%2FAKL4Ncw56kQPjggHDvXXQk%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;410&quot; height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc3Dz1/btsJm7un4wk/B3srrDPQUQI1O7PxylRH4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc3Dz1/btsJm7un4wk/B3srrDPQUQI1O7PxylRH4k/img.png&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;222&quot; data-is-animation=&quot;false&quot; width=&quot;335&quot; height=&quot;179&quot; style=&quot;width: 54.0454%;&quot; data-widthpercent=&quot;54.68&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc3Dz1/btsJm7un4wk/B3srrDPQUQI1O7PxylRH4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc3Dz1%2FbtsJm7un4wk%2FB3srrDPQUQI1O7PxylRH4k%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;416&quot; height=&quot;222&quot;/&gt;&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;p data-ke-size=&quot;size16&quot;&gt;나는 크게 네 가지 일들을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 기존 프로젝트의 우선순위가 높은 버그 픽스들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 커뮤니티 기능 및 UI개선 스프린트, 버그 픽스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 커뮤니티 신규 기능 개발&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 디지털 교과서를 위한 클래스룸 신규기능 개발&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ezqcJo/btsJnqm0iqM/ta0h2zpr7V0KqKVh57Ua80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ezqcJo/btsJnqm0iqM/ta0h2zpr7V0KqKVh57Ua80/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;311&quot; data-filename=&quot;KakaoTalk_Photo_2024-08-31-16-30-42.png&quot; style=&quot;width: 48.1222%; margin-right: 10px;&quot; data-widthpercent=&quot;48.69&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ezqcJo/btsJnqm0iqM/ta0h2zpr7V0KqKVh57Ua80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FezqcJo%2FbtsJnqm0iqM%2Fta0h2zpr7V0KqKVh57Ua80%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;1640&quot; height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dekz5s/btsJnXEBxVu/NfsZUJsbadA9F1Kqxtpfrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dekz5s/btsJnXEBxVu/NfsZUJsbadA9F1Kqxtpfrk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1645&quot; data-origin-height=&quot;296&quot; data-filename=&quot;KakaoTalk_Photo_2024-08-31-16-30-46.png&quot; style=&quot;width: 50.715%;&quot; data-widthpercent=&quot;51.31&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dekz5s/btsJnXEBxVu/NfsZUJsbadA9F1Kqxtpfrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdekz5s%2FbtsJnXEBxVu%2FNfsZUJsbadA9F1Kqxtpfrk%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;1645&quot; height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&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/ck98QD/btsJmbErL7o/mhPZKe8WSoSEOMG5cFv6Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck98QD/btsJmbErL7o/mhPZKe8WSoSEOMG5cFv6Pk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1647&quot; data-origin-height=&quot;720&quot; data-filename=&quot;KakaoTalk_Photo_2024-08-31-16-30-52.png&quot; style=&quot;width: 48.6435%; margin-right: 10px;&quot; data-widthpercent=&quot;49.22&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck98QD/btsJmbErL7o/mhPZKe8WSoSEOMG5cFv6Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck98QD%2FbtsJmbErL7o%2FmhPZKe8WSoSEOMG5cFv6Pk%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;1647&quot; height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VNPP5/btsJn8F2C3z/AqaN9enKCnGWx0b53KSEI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VNPP5/btsJn8F2C3z/AqaN9enKCnGWx0b53KSEI0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1657&quot; data-origin-height=&quot;702&quot; data-filename=&quot;KakaoTalk_Photo_2024-08-31-16-30-49.png&quot; style=&quot;width: 50.1937%;&quot; data-widthpercent=&quot;50.78&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VNPP5/btsJn8F2C3z/AqaN9enKCnGWx0b53KSEI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVNPP5%2FbtsJn8F2C3z%2FAqaN9enKCnGWx0b53KSEI0%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;1657&quot; height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;2달간 도합 172개의 이슈를 해결했다!&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;모듈이 10만개가 넘어가는 거대한 레포지토리에서 작업한다는 것은 토이프로젝트나 신규프로젝트만 진행해서는 절대로 얻을 수 없는 경험이다.&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;새로운 모듈을 만들어 utils에 추가하는 것이 좋을지, 혹은 파일에서 간단하게 구현할지도 고민해야했다.&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 data-ke-size=&quot;size23&quot;&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://youtube.com/shorts/cYhJsz2MibY?si=UQRZjEGfyiLPgoiq&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtube.com/shorts/cYhJsz2MibY?si=UQRZjEGfyiLPgoiq&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/shorts/cYhJsz2MibY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/gqbDS/hyWVYj3zGa/Nakei52l9qm7CvCBs9VsoK/img.jpg?width=405&amp;amp;height=720&amp;amp;face=122_171_390_464,https://scrap.kakaocdn.net/dn/br2gsS/hyWV0hSIQk/9IVi1wLCXKqUxEKge6gUyk/img.jpg?width=405&amp;amp;height=720&amp;amp;face=122_171_390_464&quot; data-video-width=&quot;200&quot; data-video-height=&quot;356&quot; data-video-origin-width=&quot;405&quot; data-video-origin-height=&quot;720&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;유영우 &amp;lsquo;빨라&amp;rsquo; 매드무비 (상)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/cYhJsz2MibY&quot; width=&quot;200&quot; height=&quot;356&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;빨라빨라빨라빨라...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 이례적으로 바쁜 시기였기에, 2-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;&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;/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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모가 워낙 크다보니 프로덕트를 쪼개서 런타임에 합치는 Module federation기술이 적용되어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분은 거의 처음 보았기에 상당히 신기했고, 시간이 될때마다 회사 코드의 설정 파일을 하나씩 까보며 이해하려고 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 규모가 작더라도 토이플젝으로 활용해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;솔직한 피드백&lt;/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 data-ke-size=&quot;size16&quot;&gt;&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;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 팀원들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발하는 데 있어, 좋은 팀원분들을 만난다는게 굉장히 중요하다는 것을 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정이 빡빡하다보니 다들 힘들텐데도 불구하고 항상 긍정적으로, 재미있게, 함께 해쳐나가는 분위기를 만드는 팀원분들에게서 많은 힘을 얻었던 것 같다. 팀원분들에게 감사하다고 전하고 싶다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;잘했던 부분&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선을 위해&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;multi-starter&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 이야기했듯, Module Federation이 적용되어 있어 빌드시간을 단축시킬 수 있었지만, 문제도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 5개 가량되는 각각의 레포지토리를 개발시에 모두 켜줘야한다는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도도 속도지만, 터미널을 5개 켜야한다는 것이 많이 불편했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 코드를 통해 기여하고 싶었지만,,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 구조인지 정확히 파악하기에는 시간이 없었기에 회사 의존성과 관계없이 이런 문제를 해결해보고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 multi-starter라는 cli를 만들어 npm에 배포했고, 이런 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/multi-starter&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/multi-starter&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725090238375&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;multi-starter&quot; data-og-description=&quot;runner for multirepo with js. Latest version: 1.1.5, last published: 2 months ago. Start using multi-starter in your project by running &amp;#96;npm i multi-starter&amp;#96;. There are no other projects in the npm registry using multi-starter.&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/multi-starter&quot; data-og-url=&quot;https://www.npmjs.com/package/multi-starter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sHIlO/hyWV2UgBI6/INYlKhuQLz2OTgxvhXiqkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/multi-starter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/multi-starter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sHIlO/hyWV2UgBI6/INYlKhuQLz2OTgxvhXiqkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;multi-starter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;runner for multirepo with js. Latest version: 1.1.5, last published: 2 months ago. Start using multi-starter in your project by running `npm i multi-starter`. There are no other projects in the npm registry using multi-starter.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;lint룰&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 새로운 컴포넌트를 작성할때, 컨벤션 관련 리뷰를 거의 20개가량 받은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분은 신경을 써도 처음에는 놓치는 부분이 많아 사수분의 시간을 너무 많이 잡아먹는 것 같다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컨벤션과 일치하는 lint룰을 issue를 통해 제시하고, 실제로 프로젝트에 반영시켰다.&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;h4 data-ke-size=&quot;size20&quot;&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 data-ke-size=&quot;size16&quot;&gt;과정에서 util타입을 만들고, 선언하는 실력이 많이 개선되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 활동들은 직접적으로 프로덕트에 도움이 된다고 보기는 어렵지만, 프로덕트를 만드는 사람들에게 도움이 되는 활동들이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이런 활동이 바로 눈에 보이지 않지만 궁극적으로는 프로덕트 개발에 도움을 줄 수 있다고 생각한다.&lt;/p&gt;
&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;1012&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XgrD3/btsJoDMQb2B/SKlzPCBRYwUgm5Cf04NMmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XgrD3/btsJoDMQb2B/SKlzPCBRYwUgm5Cf04NMmK/img.png&quot; data-alt=&quot;함께자라기의 부트스트래핑&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XgrD3/btsJoDMQb2B/SKlzPCBRYwUgm5Cf04NMmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXgrD3%2FbtsJoDMQb2B%2FSKlzPCBRYwUgm5Cf04NMmK%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;330&quot; height=&quot;256&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;786&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;p data-ke-size=&quot;size16&quot;&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/bzq0SY/btsJnHWjidq/qdqbg0MjTeJ1kzLXZ4oCtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzq0SY/btsJnHWjidq/qdqbg0MjTeJ1kzLXZ4oCtk/img.png&quot; data-origin-width=&quot;409&quot; data-origin-height=&quot;220&quot; data-is-animation=&quot;false&quot; style=&quot;width: 43.5768%; margin-right: 10px;&quot; data-widthpercent=&quot;44.09&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzq0SY/btsJnHWjidq/qdqbg0MjTeJ1kzLXZ4oCtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzq0SY%2FbtsJnHWjidq%2Fqdqbg0MjTeJ1kzLXZ4oCtk%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;409&quot; height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dytOGy/btsJnsewKfk/1oKYK3wtYKEoMYPSthgLo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dytOGy/btsJnsewKfk/1oKYK3wtYKEoMYPSthgLo0/img.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;358&quot; data-is-animation=&quot;false&quot; style=&quot;width: 55.2604%;&quot; data-widthpercent=&quot;55.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dytOGy/btsJnsewKfk/1oKYK3wtYKEoMYPSthgLo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdytOGy%2FbtsJnsewKfk%2F1oKYK3wtYKEoMYPSthgLo0%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;844&quot; height=&quot;358&quot;/&gt;&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;h3 data-ke-size=&quot;size23&quot;&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 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;결론부터 이야기하면 if 조건문 하나 잘못쓴 것이 15분~최대 30분가량의 시간을 소비시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발-&amp;gt;QA서버-&amp;gt;프로덕션 배포 &lt;/b&gt;이렇게 이어지는 큰 프로세스 중, 마지막 단계, 혹은 QA서버에서 버그가 발생하는 경우, 프로세스를 처음부터 진행해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 자세히 쓰면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발&lt;/b&gt;: 규모가 크다보니 개발 서버 빌드시간이 굉장히 오래걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;QA서버&lt;/b&gt;: QA서버가 올라가기 전에 프로덕트가 build가 되어야하는데, 이 과정에서 15분가량이 소요된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로덕션&lt;/b&gt;: 혹시라도 프로덕션에서 문제가 발생하면, rollback 배포를 한 후, 다시 배포해야하므로 시간이 거의 30분 가량 소요된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 하나라도 꼼꼼하게 개발하는 것이 중요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 테스트코드를 작성하고, 이를 통해 확인하면 무엇보다 좋겠지만, 마감이 얼마 남지 않은 빠른 개발, 스프린트가 필요한 과정에서 그러한 부분을 적용시키기는 어렵다.&lt;/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;1. 기능에 대한 구현 조건을 작성한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 기능을 구현 할 때마다 체크한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 모든 조건을 완전히 구현한 뒤에, 모든 구현 조건을 다시 확인하고, 빠진 것이 있다면 다시 1번부터 진행한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 진행했더니, 실수나, 빼먹는 조건들로 인해 발생하는 시간 낭비를 많이 줄일 수 있었다.&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체력이 부족하다보니 위에서 말한 피드백을 온전히 피드백으로 받아들이는 것도 어려웠고, 좋은 코드와 구조를 고민하는데도 영향을 미쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막엔 코로나까지... 체력을 높히는게 무엇보다 시급하다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속가능한 개발자는 체력이 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋은 코드란.... 뭘까&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모가 다르기 때문일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 코드와 내가 작성해왔던 코드는 스타일도 다르고, 중시하는 부분도 다소 다르다고 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 코드는 &lt;b&gt;최대한 추상화해서 나를 포함한 코드를 사용하는 개발자가 더 쉽게 사용할 수 있게 하자&lt;/b&gt; 가 최우선이었다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 코드는 &lt;b&gt;누가 봐도 이 코드의 맥락과 구조를 이해할 수 있게 하자&lt;/b&gt; 가 더 우선으로 느껴졌다. (물론 내가 전반적으로 느낀 것이고, 실제로 추구하는 것은 아닐 수 있다.)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도, 어디에 무엇이 있는지 모든 팀원이 파악하기 어렵기때문에 코드를 이해하기 쉽게 파악하는게 더 중요해진 느낌...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가 코드를 읽었을때, 빠르게 이해할 수 있게 코드를 작성하는 느낌이었다.&lt;/p&gt;
&lt;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;/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;더불어 코어한 webpack 플러그인이나 계정을 불러오는 부분 등, 사수분들과 리드분께서 작성한 코드를 보고 있다 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 내가 작성한 코드들이 네이밍이나, 맥락을 파악하는 부분에서 많이 부족하다고 느껴지기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 좋은 코드란 무엇이냐?에 대한 대답을 명확하게 하기가 어렵다.&lt;/p&gt;
&lt;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;b&gt;다른 사람이 읽기 쉬운 것일까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤게 우선순위가 더 높은지는 상황에 따라 다른 것이겠지만, 나는 아직 그 기준이 명확하게 잡혀있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 분명한 것은, 사이드 프로젝트의 규모와 이렇게 대규모의 프로젝트의 규모 차이에서 오는 기준의 변화가 분명히 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 더 많은 경험이 필요하다고 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 다양한 환경에서, 다양한 코드를, 더 많은 사람들과 의견을 나누며 작성해보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 고민하고 고민해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;엘리스의 인턴십은 시기적으로 바쁜시기에 진행하게되어 체력적으로 많이 힘들긴했지만, 그만큼 정말 유의미한 경험을 쌓을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편으로는 내가 믿는, 팀을 위한 문제해결과 개선에 대한 믿음이 강해지기도 했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편으로는 거대한 규모에서 오는 새로운 고민들을 갖게하는 좋은 기회였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속 가능한 개발자가 되기 위해서는 개발 외적으로도 많은 노력이 필요하구나라고 느낀 계기이기도 했다. (운동하자.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제나 처럼 좋은 점은 더 살리고, 부족한 부분들은 개선하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 나는 졸업까지 아직 1년이 남은상태다. 이건 그만큼 더 많은 경험을 쌓고, 위의 과정을 겪으며 성장할 수 있다는 말이다.&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;/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;그리고 특히 바쁘실텐데도 바로 질문에 대답해주시고, 항상 좋은 피드백을 주시던 사수분께&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;감사하다는 말씀을 전하며 글을 마무리하고자한다.&lt;/p&gt;</description>
      <category>회고/회고</category>
      <category>엘리스</category>
      <category>인턴십</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/372</guid>
      <comments>https://0422.tistory.com/372#entry372comment</comments>
      <pubDate>Mon, 2 Sep 2024 10:51:50 +0900</pubDate>
    </item>
    <item>
      <title>yalc로 로컬환경에서 패키지 테스트하기</title>
      <link>https://0422.tistory.com/371</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwGoL/btsIy2AuTxd/cUInND6PHRE5A8cbHMVMTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwGoL/btsIy2AuTxd/cUInND6PHRE5A8cbHMVMTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwGoL/btsIy2AuTxd/cUInND6PHRE5A8cbHMVMTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwGoL%2FbtsIy2AuTxd%2FcUInND6PHRE5A8cbHMVMTK%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;218&quot; height=&quot;218&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 라이브러리를 만들어보며, 지금까지 항상 npm에 임의로 버전을 올리고, 패키지를 다운받은 이후에 테스트했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 인턴을 하다, eslint룰을 추가하고 잘 적용됐는지 확인해볼 일이 생기게 됐고, 회사 사내의 라이브러리였기에 이때도 이렇게 진행할 수는 없었다.&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;yarn link&lt;/h2&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;yarn link를 사용해서 패키지와 프로젝트를 연결하면 프로젝트의 &lt;span style=&quot;color: #409d00;&quot;&gt;node_modules/&amp;lt;package&amp;gt; &lt;/span&gt;와 실제 로컬의 &lt;span style=&quot;color: #409d00;&quot;&gt;&amp;lt;package&amp;gt;&lt;/span&gt;폴더 간의 심볼릭 링크가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말인 즉슨, 수정한 패키지를 프로젝트에 추가하기 위해 &lt;span style=&quot;color: #ee2323;&quot;&gt;추가적인 배포나 설치과정이 필요하지 않다는 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 때 두 가지 패키지 같은 패키지를 의존하는 경우, 두 가지 패키지의 같은 의존성이 테스트 하고자 하는 곳에 두 번 설치될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 문제는 이 &lt;a href=&quot;https://medium.com/zigbang/yalc-npm-%ED%8C%A8%ED%82%A4%EC%A7%80%EB%A5%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EB%8A%94-%EB%8D%94-%EB%82%98%EC%9D%80-%EB%B0%A9%EB%B2%95-26eebae3f355&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;yalc&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yalc는 yarn link와 다르게 node_modules까지 연결시키는게 아니라, 중복 설치, 동일하지만 다른 경로에 있는 패키지 문제가 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yalc는 &lt;span style=&quot;color: #ef5369;&quot;&gt;yalc publish&lt;/span&gt;명령어를 통해서 로컬에 해당 파일을 배포하고, 프로젝트에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;yalc add 패키지@버전&lt;/span&gt;의 형태로 배포된 로컬 파일을 의존성으로 정해줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1720876483602&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i -g yalc
yarn global add yalc&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;h3 data-ke-size=&quot;size23&quot;&gt;준비&lt;/h3&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;yalc-project1&lt;/li&gt;
&lt;li&gt;yalc-project2&lt;/li&gt;
&lt;li&gt;yalc-final&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부러 중첩되는 의존성을 갖도록 yalc-project2가 yalc-project1을,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yalc-final이 yalc-proejct2를 의존성으로 갖도록 구성하였다.&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;yalc-project1 구성/배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게(&lt;span style=&quot;color: #ef5369;&quot;&gt;npm init -y&lt;/span&gt;) index.js에서 변수를 하나 export하도록 패키지를 만들어 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpiGbM/btsIyePcKpa/aELE5eh4FFJjDPZDWWRMRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpiGbM/btsIyePcKpa/aELE5eh4FFJjDPZDWWRMRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpiGbM/btsIyePcKpa/aELE5eh4FFJjDPZDWWRMRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpiGbM%2FbtsIyePcKpa%2FaELE5eh4FFJjDPZDWWRMRK%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;540&quot; height=&quot;220&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;220&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: #ef5369;&quot;&gt;yalc publish&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;599&quot; data-origin-height=&quot;35&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvMHKt%2FbtsIzdIIVhT%2Fg9HHkh97jZKKi071dKRKhk%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;599&quot; height=&quot;35&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;35&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;실제로 배포된 파일을 보자.&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;yalc를 통해 배포된 파일은 &lt;span style=&quot;color: #ef5369;&quot;&gt;~/.yalc/packages&lt;/span&gt;에 저장이된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 경우에는 yalc-project1프로젝트가 1.0.0버전으로 배포되었으므로 아래와 같은 폴더 구조로 배포가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NKGG1/btsIxKBistZ/kAFko7wKOOaCANM6F9kei0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NKGG1/btsIxKBistZ/kAFko7wKOOaCANM6F9kei0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NKGG1/btsIxKBistZ/kAFko7wKOOaCANM6F9kei0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNKGG1%2FbtsIxKBistZ%2FkAFko7wKOOaCANM6F9kei0%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;617&quot; height=&quot;40&quot; data-origin-width=&quot;617&quot; data-origin-height=&quot;40&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;이제 이렇게 배포된 패키지를 yalc-project2에 적용시켜줄 것이다.&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;yalc-project2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 &lt;span style=&quot;color: #ef5369;&quot;&gt;npm init -y&lt;/span&gt;로 패키지를 만들어주었고, yalc-project1패키지에서 사용하는 변수를 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 yalc를 통해 yalc-project1를 yalc-proejct2의 의존성에 추가해줄 것이다.&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;yalc를 통한 의존성 추가/설치&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 yalc를 통해 yalc-project1을 배포했을때의 결과 문자열을 통해 yalc-project2에 의존성을 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;35&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot; data-alt=&quot;yalc-project1의 배포 결과 문자열&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vMHKt/btsIzdIIVhT/g9HHkh97jZKKi071dKRKhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvMHKt%2FbtsIzdIIVhT%2Fg9HHkh97jZKKi071dKRKhk%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;599&quot; height=&quot;35&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;35&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;yalc-project1의 배포 결과 문자열&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 아래와 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1720872467377&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yalc add yalc-project1@1.0.0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdoVxy/btsIxFme8ot/owmsy5WIK3tDAawhksATKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdoVxy/btsIxFme8ot/owmsy5WIK3tDAawhksATKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdoVxy/btsIxFme8ot/owmsy5WIK3tDAawhksATKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdoVxy%2FbtsIxFme8ot%2Fowmsy5WIK3tDAawhksATKk%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;868&quot; height=&quot;50&quot; data-origin-width=&quot;868&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 추가하게되면, 아래와 같이 .yalc폴더와 yalc.lock, node_modules가 생기게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pw8iu/btsIy0W989e/lkEZJilKKzhtWN2jCCmMY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pw8iu/btsIy0W989e/lkEZJilKKzhtWN2jCCmMY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pw8iu/btsIy0W989e/lkEZJilKKzhtWN2jCCmMY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpw8iu%2FbtsIy0W989e%2FlkEZJilKKzhtWN2jCCmMY1%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;259&quot; height=&quot;291&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 package.json을 잘보면 file:./yalc/yalc-project1 과 같이 파일에 직접적인 의존성을 갖는다는 걸 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-13 오후 9.13.57.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4gAXc/btsIyxVfdWh/2lZKmtXKi2yYSkGKNTnhik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4gAXc/btsIyxVfdWh/2lZKmtXKi2yYSkGKNTnhik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4gAXc/btsIyxVfdWh/2lZKmtXKi2yYSkGKNTnhik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4gAXc%2FbtsIyxVfdWh%2F2lZKmtXKi2yYSkGKNTnhik%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;512&quot; height=&quot;249&quot; data-filename=&quot;스크린샷 2024-07-13 오후 9.13.57.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 현재 디렉토리의 .yalc디렉터리 내부의 프로젝트로 연결되는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;index.js&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 yalc-project1의 변수를 출력하는 함수를 하나 만들어 export했다.&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;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/moXBm/btsIzq8VEFG/ok3QcywdTpKvkud9Fj1gqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/moXBm/btsIzq8VEFG/ok3QcywdTpKvkud9Fj1gqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/moXBm/btsIzq8VEFG/ok3QcywdTpKvkud9Fj1gqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmoXBm%2FbtsIzq8VEFG%2Fok3QcywdTpKvkud9Fj1gqk%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;245&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;yalc 로컬 배포&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 yalc publish를 통해 로컬 파일에 배포하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;35&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkd0Si/btsIzAKsZRk/aq4sff0PhhpF3xbJRRT6gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkd0Si/btsIzAKsZRk/aq4sff0PhhpF3xbJRRT6gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkd0Si/btsIzAKsZRk/aq4sff0PhhpF3xbJRRT6gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkd0Si%2FbtsIzAKsZRk%2Faq4sff0PhhpF3xbJRRT6gk%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;580&quot; height=&quot;35&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;35&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;470&quot; data-origin-height=&quot;47&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etnZEt/btsIxeh6cJP/S6rYjEKUWH5KGfM3idTfkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etnZEt/btsIxeh6cJP/S6rYjEKUWH5KGfM3idTfkk/img.png&quot; data-alt=&quot;~/.yalc/packages에 잘 배포됐다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etnZEt/btsIxeh6cJP/S6rYjEKUWH5KGfM3idTfkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetnZEt%2FbtsIxeh6cJP%2FS6rYjEKUWH5KGfM3idTfkk%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;470&quot; height=&quot;47&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;47&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;~/.yalc/packages에 잘 배포됐다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;yalc-final&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 최종 프로젝트에 yalc-project2를 적용시키자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 npm init -y를 통해 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pocpW/btsIyHXD98g/42lYRGPU2SYQBYxq319zOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pocpW/btsIyHXD98g/42lYRGPU2SYQBYxq319zOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pocpW/btsIyHXD98g/42lYRGPU2SYQBYxq319zOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpocpW%2FbtsIyHXD98g%2F42lYRGPU2SYQBYxq319zOk%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;723&quot; height=&quot;156&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yalc add를 통해 의존성을 추가해주자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bslWzu/btsIza6jo8Q/un29SkSyGTigd1hGf9MABK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bslWzu/btsIza6jo8Q/un29SkSyGTigd1hGf9MABK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bslWzu/btsIza6jo8Q/un29SkSyGTigd1hGf9MABK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbslWzu%2FbtsIza6jo8Q%2Fun29SkSyGTigd1hGf9MABK%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;811&quot; height=&quot;37&quot; data-origin-width=&quot;811&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj5lrM/btsIyX0tjrm/V7gx7kT7qXraSpDyK7TRjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj5lrM/btsIyX0tjrm/V7gx7kT7qXraSpDyK7TRjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj5lrM/btsIyX0tjrm/V7gx7kT7qXraSpDyK7TRjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj5lrM%2FbtsIyX0tjrm%2FV7gx7kT7qXraSpDyK7TRjK%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;259&quot; height=&quot;504&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 .yalc에 yalc-project2가 생기고, yalc-project2 프로젝트에 .yalc/yalc-project1이 추가된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러고 index.js를 실행시키면...?&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;894&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E54bb/btsIyE07XyF/1z0sKBAGWKm9WmnLuzSbD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E54bb/btsIyE07XyF/1z0sKBAGWKm9WmnLuzSbD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E54bb/btsIyE07XyF/1z0sKBAGWKm9WmnLuzSbD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE54bb%2FbtsIyE07XyF%2F1z0sKBAGWKm9WmnLuzSbD0%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;894&quot; height=&quot;369&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 node_modules/yalc-proejct1이 없기때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 node_modules/yalc-project2/.yalc/yalc-project1의 형태로 의존성이 들어가있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JUMtI/btsIykaOLYw/z1K9Sa514dxqtamd7Rcaz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JUMtI/btsIykaOLYw/z1K9Sa514dxqtamd7Rcaz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JUMtI/btsIykaOLYw/z1K9Sa514dxqtamd7Rcaz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJUMtI%2FbtsIykaOLYw%2Fz1K9Sa514dxqtamd7Rcaz0%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;249&quot; height=&quot;182&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 yarn 또는 npm install을 해주게되면 node_modules/yalc-project1디렉토리가 생성되며, node_modules에 병합을 해주는 파일(.package-lock.json, yarn-integrity)이 생성된다.&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/cuNooV/btsIzpCcclo/fKjJeuF0bLEhNGJ18LQqt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuNooV/btsIzpCcclo/fKjJeuF0bLEhNGJ18LQqt0/img.png&quot; data-origin-width=&quot;256&quot; data-origin-height=&quot;586&quot; data-is-animation=&quot;false&quot; style=&quot;width: 14.6136%; margin-right: 10px;&quot; data-widthpercent=&quot;14.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuNooV/btsIzpCcclo/fKjJeuF0bLEhNGJ18LQqt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuNooV%2FbtsIzpCcclo%2FfKjJeuF0bLEhNGJ18LQqt0%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;256&quot; height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zmVJB/btsIyDuoIon/FVjtBmjzcEFHTQUXyzeKf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zmVJB/btsIyDuoIon/FVjtBmjzcEFHTQUXyzeKf0/img.png&quot; data-origin-width=&quot;1415&quot; data-origin-height=&quot;562&quot; data-is-animation=&quot;false&quot; style=&quot;width: 84.2236%;&quot; data-widthpercent=&quot;85.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zmVJB/btsIyDuoIon/FVjtBmjzcEFHTQUXyzeKf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzmVJB%2FbtsIyDuoIon%2FFVjtBmjzcEFHTQUXyzeKf0%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;1415&quot; height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼쪽은 package-lock.json, 오른쪽은 .yarn-integirity&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 이제 node index.js가 잘 작동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mO73v/btsIyJuot7X/wp02Nicu8EciOTcveVa8bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mO73v/btsIyJuot7X/wp02Nicu8EciOTcveVa8bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mO73v/btsIyJuot7X/wp02Nicu8EciOTcveVa8bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmO73v%2FbtsIyJuot7X%2Fwp02Nicu8EciOTcveVa8bK%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;640&quot; height=&quot;38&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;38&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>Frontend/문제 해결기</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/371</guid>
      <comments>https://0422.tistory.com/371#entry371comment</comments>
      <pubDate>Sat, 13 Jul 2024 22:16:45 +0900</pubDate>
    </item>
    <item>
      <title>2024 상반기 회고</title>
      <link>https://0422.tistory.com/370</link>
      <description>&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;되돌아보기&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2024년이 반이나 지나갔다.&lt;br /&gt;세상에,이번 6개월은 눈 깜짝할 새에 지나갔다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;2023 하반기 목표 돌아보기&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;독서량 늘리기&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2023하반기에서 책을 많이 읽는 것을 목표로 세웠었는데, 운 좋게도 책읽기와 상상력이라는 교양수업을 들을 수 있었고 덕분에 많은 책들을 읽을 수 있었다. 덕분에 최근에는 새로운 취미로 민음사 책들을 모으고 있다.&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/ba7pUk/btsIpygt9Bg/RKTjOBbE1cTRblrwVRERek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba7pUk/btsIpygt9Bg/RKTjOBbE1cTRblrwVRERek/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1132&quot; data-is-animation=&quot;false&quot; width=&quot;346&quot; height=&quot;363&quot; data-widthpercent=&quot;48.82&quot; style=&quot;width: 48.2569%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba7pUk/btsIpygt9Bg/RKTjOBbE1cTRblrwVRERek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba7pUk%2FbtsIpygt9Bg%2FRKTjOBbE1cTRblrwVRERek%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;1080&quot; height=&quot;1132&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uqVkv/btsIpVvusS3/mgdJx9gPqqIpy5R22NIU10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uqVkv/btsIpVvusS3/mgdJx9gPqqIpy5R22NIU10/img.png&quot; width=&quot;363&quot; height=&quot;363&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.5803%;&quot; data-widthpercent=&quot;51.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uqVkv/btsIpVvusS3/mgdJx9gPqqIpy5R22NIU10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuqVkv%2FbtsIpVvusS3%2FmgdJx9gPqqIpy5R22NIU10%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;1512&quot; height=&quot;1512&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번 상반기를 통해 책은, 특히 민음사 전집과같은 고전은 때로는 약으로, 때로는 예방주사로 작용할 수 있다는걸 알게 됐다.&lt;br /&gt;지식의 전달이라는 관점보다는 경험과 가치를 보다 온전한 형태로 전달받는다는 점에서 더 좋다고 느끼게 됐다.&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;운동하기&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;근력운동은 아니지만, 틈날 때마다, 한강도로 근처라면 따릉이를 빌려서 타고있다.&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;1080&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXmIZY/btsIjw4pq7U/LgiU7NxALALDoIoE1Cb410/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXmIZY/btsIjw4pq7U/LgiU7NxALALDoIoE1Cb410/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXmIZY/btsIjw4pq7U/LgiU7NxALALDoIoE1Cb410/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXmIZY%2FbtsIjw4pq7U%2FLgiU7NxALALDoIoE1Cb410%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;391&quot; height=&quot;269&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;창업과 개발&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;작년말에 마지막으로 창업에 도전한다고 했었다.&lt;br /&gt;그래서 정말 그 어느때보다 열심히, 진심으로 임했고, 정말 런칭 직전까지 갔었지만, 프로젝트가 엎어졌다.&lt;br /&gt;열심히 관리해보려했지만, 역부족이었다. 상상도 못한 이유로 프로젝트가 엎어졌고, 정말 마음이 아팠다.&lt;br /&gt;그것도 그것이었지만, 개발 관련한 인턴이나 직무 결과도 처참하기 그지없었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YrAfX/btsIkqWJHkh/rXjAQjFA3307H6le13mko1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YrAfX/btsIkqWJHkh/rXjAQjFA3307H6le13mko1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YrAfX/btsIkqWJHkh/rXjAQjFA3307H6le13mko1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYrAfX%2FbtsIkqWJHkh%2FrXjAQjFA3307H6le13mko1%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;694&quot; height=&quot;210&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;면접탈도 아닌, 모두 서류탈락&lt;br /&gt;계속 이 일을 해야할까?, 그리고 할 수 있을까? 라고 생각하기도 했다.&amp;nbsp;&lt;br /&gt;그때 읽은 노인과 바다가 나를 다시 일으켜 세웠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;언제나, 무슨 일이든 일어날 수 있다.&amp;nbsp;&lt;br /&gt;10번 시도해서 10번 다 잘 안 될 수도 있다.&lt;br /&gt;하지만 결과가 어찌됐든 나의 의지와 선택이 중요한 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;내가 좋아하고, 내가 잘 할 수 있는건 다른 무엇이 아닌, 개발이다.&lt;br /&gt;무언가 만들고, 개선하는 것이 내가 좋아하고, 잘 할 수 있는 일이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;결과가 어찌 됐든 나는 이 일을 할 것이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;과도기를 넘어서기&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이전 당근마켓 인턴십 불합격 회고를 적을 시절, 나는 꽤나 과도기에 있었다.&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;606&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF21EO/btsImhDYLwQ/yKV0cce7KL90jDrKnnieBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF21EO/btsImhDYLwQ/yKV0cce7KL90jDrKnnieBK/img.png&quot; data-alt=&quot;https://0422.tistory.com/331&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF21EO/btsImhDYLwQ/yKV0cce7KL90jDrKnnieBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF21EO%2FbtsImhDYLwQ%2FyKV0cce7KL90jDrKnnieBK%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;467&quot; height=&quot;195&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;253&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://0422.tistory.com/331&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다 하지는 못했지만, 생각보다 해낸 것들이 많았다.&lt;br /&gt;자잘한것들보다는 큰 것만 적어보았다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;오픈 소스 만들고 배포하기&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;npm에 2개의 패키지를 만들어서 배포했다.&lt;br /&gt;이 작업들을 통해서 굉장히 많은 고민들을 할 수 있었고, 많은 지식들(특히 ts와 모듈시스템!)을 쌓을 수 있었다.&lt;br /&gt;&lt;b&gt;프로덕트든 툴이든, 내가 만들어낸 걸 누군가가 쓴다는건 너무나도 의미있는 경험이다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&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/z46MK/btsIkqvCecy/fv6A5GTPwPPTvoFUvZLkX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z46MK/btsIkqvCecy/fv6A5GTPwPPTvoFUvZLkX0/img.png&quot; data-origin-width=&quot;903&quot; data-origin-height=&quot;293&quot; style=&quot;width: 61.6557%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;62.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z46MK/btsIkqvCecy/fv6A5GTPwPPTvoFUvZLkX0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz46MK%2FbtsIkqvCecy%2Ffv6A5GTPwPPTvoFUvZLkX0%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;903&quot; height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UkmS4/btsIldvv3AO/tCGRJAGKyElOgkJfBEVom1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UkmS4/btsIldvv3AO/tCGRJAGKyElOgkJfBEVom1/img.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;509&quot; style=&quot;width: 37.1815%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;37.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UkmS4/btsIldvv3AO/tCGRJAGKyElOgkJfBEVom1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUkmS4%2FbtsIldvv3AO%2FtCGRJAGKyElOgkJfBEVom1%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;946&quot; height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;학습하는 과정에서 TS공식문서 한글화에 참여해보기도 했다. (레포가 방치된 것 같기는 하다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFKBmr/btsIkhyH7Ia/Rg7QsbmoboSiT5zxJkZrT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFKBmr/btsIkhyH7Ia/Rg7QsbmoboSiT5zxJkZrT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFKBmr/btsIkhyH7Ia/Rg7QsbmoboSiT5zxJkZrT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFKBmr%2FbtsIkhyH7Ia%2FRg7QsbmoboSiT5zxJkZrT0%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;645&quot; height=&quot;129&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;오픈소스 기여하기&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;토스&lt;/b&gt;의 slash 라이브러리에 기여해보기도 했다. (이젠 deprecated됐지만)&lt;br /&gt;과정에서 토스의 박서진님게 리뷰를 받았었는데, 꽤나 놀랐다.&lt;br /&gt;나는 그전까지 편리한 기능을 더한다가 기본 생각이었다. 이런걸 추가하면 더 좋지 않을까? 라는 생각으로 항상 개발에 임했었던 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&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;947&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvubej/btsIlSdnMt3/2FOfhjKVkRrwKLY7yoywc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvubej/btsIlSdnMt3/2FOfhjKVkRrwKLY7yoywc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvubej/btsIlSdnMt3/2FOfhjKVkRrwKLY7yoywc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdvubej%2FbtsIlSdnMt3%2F2FOfhjKVkRrwKLY7yoywc1%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;407&quot; height=&quot;235&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;더 높은 퀄리티의 블로그 글쓰기&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 실패가 더 많긴하지만, 그래도 굉장히 많은 기술적 경험을 쌓을 수 있었기에 올해 블로그 작성 글 수&lt;b&gt;(43개, 작년은 6개월당 66개)&lt;/b&gt;는 줄었어도 전반적인 퀄리티는 올라갔다고 생각한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;창업 프로젝트를 진행하면서 경험했던 웹뷰의 작업 경험, 문제를 해결하기위한 툴을 만든 경험을, 그리고 당근마켓의 사전질문이었던 피드 관련 기능을 직접 구현하며 블로그에 녹여낼 수 있었다. 더불어 라이브러리를 만들며 학습한 모듈 시스템과 ts, rollup, 그리고 Next만들어보기까지. 덕분에 전체적인 블로그 퀄리티는 이전보다 향상되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;앞으로&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변화&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;많은 실패가 있었지만, 계속해서 개선하고자 노력하고, 많이 도전하고, 운이 꽤나 따라준 결과, 학교 현장실습을 통해 인턴 근무를 할 수 있게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; 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;615&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLuNxX/btsIkd4mmIF/LLn3zXMhHe2egmzQCj6yJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLuNxX/btsIkd4mmIF/LLn3zXMhHe2egmzQCj6yJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLuNxX/btsIkd4mmIF/LLn3zXMhHe2egmzQCj6yJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLuNxX%2FbtsIkd4mmIF%2FLLn3zXMhHe2egmzQCj6yJK%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;615&quot; height=&quot;103&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;정말 많이 도전한 끝에 얻어낸 결과이니만큼 진심을 다해 열정적으로 임해볼 것이다. 그래서 지금까지 했던 코딩학원 강사 알바도 그만두게 되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다보니 아무래도 이번 하반기 목표는 &lt;b&gt;회사와 팀, 프로덕트에서 최대한 많은 것들을 경험하고 배워보자&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&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 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;h4 data-ke-size=&quot;size20&quot;&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 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;h4 data-ke-size=&quot;size20&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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 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;h4 data-ke-size=&quot;size20&quot;&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 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 정말 정신없고, 제대로 되는게 없다고 느꼈던, 엉망진창이었던 6개월이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만 이렇게 되돌아보니 생각보다 목표로 한 것들을 잘 하고 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제자리라 생각한 것들조차도 많이 성장해있었음을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 6개월은 노력한 것 대비 성과는 그리 좋지 않았지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 내가 진심인 것에 어느 때보다 열심히 했고, 적어도 쉬지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그거면 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buyd42/btsIrtYSVL4/K6dNyRESG3mXCtAk0ub4V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buyd42/btsIrtYSVL4/K6dNyRESG3mXCtAk0ub4V1/img.png&quot; data-alt=&quot;문득 생각나서 첨부하는 하루키의 에세이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buyd42/btsIrtYSVL4/K6dNyRESG3mXCtAk0ub4V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbuyd42%2FbtsIrtYSVL4%2FK6dNyRESG3mXCtAk0ub4V1%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;340&quot; height=&quot;340&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&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;/p&gt;</description>
      <category>회고/회고</category>
      <category>2024</category>
      <category>상반기</category>
      <category>회고</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/370</guid>
      <comments>https://0422.tistory.com/370#entry370comment</comments>
      <pubDate>Sun, 7 Jul 2024 17:26:58 +0900</pubDate>
    </item>
    <item>
      <title>Next를 만들어보면서 이해해보자 (JSX기반 MiniNext) - pages기반 라우팅 (+중첩라우팅)</title>
      <link>https://0422.tistory.com/369</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bppmbK/btsHIzs6e8k/1RAbKffczu6PdgkVozHgK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bppmbK/btsHIzs6e8k/1RAbKffczu6PdgkVozHgK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bppmbK/btsHIzs6e8k/1RAbKffczu6PdgkVozHgK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbppmbK%2FbtsHIzs6e8k%2F1RAbKffczu6PdgkVozHgK0%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;897&quot; height=&quot;572&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pages 파일 기반 라우팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 pages에 App.tsx만 있어서 하나의 파일에 대해 html을 만들어서 전송했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Next에서는 pages에 있는 모든 파일이 라우팅되어, 해당 경로로 접근하였을때 페이지를 제공해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y79UC/btsHMfV1rnP/tPowaSzVbm49LKzgSEs6U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y79UC/btsHMfV1rnP/tPowaSzVbm49LKzgSEs6U1/img.png&quot; data-alt=&quot;이러면 /와 /gallery 경로가 생성된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y79UC/btsHMfV1rnP/tPowaSzVbm49LKzgSEs6U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY79UC%2FbtsHMfV1rnP%2FtPowaSzVbm49LKzgSEs6U1%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;196&quot; height=&quot;65&quot; data-origin-width=&quot;196&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이러면 /와 /gallery 경로가 생성된다.&lt;/figcaption&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;h3 data-ke-size=&quot;size23&quot;&gt;pageFiles찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 이를 위해서는 pages의 파일이름들을 가져올 수 있어야한다. 그래서 바벨로 트랜스파일링된 dist/pages내부의 모든 tsx파일을 찾아올 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1717139309015&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const PAGES_PATH = 'dist/pages';

export const getPagesFiles = async (pagesPath = PAGES_PATH) =&amp;gt; {
  const files: string[] = await new Promise((resolve, reject) =&amp;gt; {
    readdir(pagesPath, (err, files) =&amp;gt; {
      if (err) reject();
      resolve(files);
    });
  });
  return files;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 getServerSideProps함수를 모두 가져오는 함수와 똑같다. 이걸 통해서 이후에 Page 컴포넌트를 동적으로 가져올 수 있게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1717425744380&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { default: Component } = require(
      path.resolve(`${PAGES_PATH}`, fullFileName) //getPagesFiles를 통해 가져온 파일이름
);&lt;/code&gt;&lt;/pre&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;그런데 이러면 이때 각 Page의 hydration에 필요한 js파일도 달라지게된다. 즉, 클라이언트에서 받을 번들파일을 만들어줘야하는데, 이 역시 페이지마다 동적으로 생성되어야 한다.&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;이전의 index의 경우 아래와 같은 클라이언트 js파일이 있었고, createHTML에서 이 코드를 번들링한 결과를 &amp;lt;script&amp;gt;태그로 넣어주었었다.&lt;/p&gt;
&lt;pre id=&quot;code_1717425813883&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//이전의 public/index.tsx -&amp;gt; rollup을 통해 번들링되어 /dist/public/index.js가 된다.
import * as Page from '@/pages/index';
import { hydrate } from '@core/render';

hydrate(Page.default, document.getElementById('_miniNext'));

//createHTML
export const createHTML = (
  element: MiniReactNode,
  initialServerProps?: Record&amp;lt;string, string&amp;gt;,
) =&amp;gt; {
  const root = `
  &amp;lt;html&amp;gt;
  ...
  &amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;_miniNext&quot;&amp;gt;${_createHTML(element)}&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
  ...
  &amp;lt;script src='index.js'&amp;gt;&amp;lt;/script&amp;gt; //여기서 번들링한 index.js를 보내준다.
  &amp;lt;/html&amp;gt;`;
  return root;
};&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; createHTML의 수정&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이걸 동적으로 처리해주도록 createHTML에서 fileName을 받도록 수정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717465766195&quot; class=&quot;xquery&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;export const createHTML = (
  element: MiniReactNode,
  initialServerProps?: Record&amp;lt;string, string&amp;gt;,
  fileName: string
) =&amp;gt; {
  const root = `
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;MiniNext&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;_miniNext&quot;&amp;gt;${_createHTML(element)}&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script&amp;gt;
    window._miniNextData=${JSON.stringify(initialServerProps)}
  &amp;lt;/script&amp;gt;
  &amp;lt;script src='${fileName}.js'&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/html&amp;gt;`;
  return root;
};&lt;/code&gt;&lt;/pre&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;h3 data-ke-size=&quot;size23&quot;&gt;페이지마다 번들파일 생성하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;페이지별로 번들 엔트리파일 생성하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 createHTML의 script태그를 통해 보내줄 페이지별 번들 엔트리 파일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 public에 정적으로 작성했었는데, 이제는 정적으로 작성할 수 없으므로 동적으로 생성되게 해주어야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkkMYv/btsHMAezu7a/bvantH6CvgnvZRjnSyPWsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkkMYv/btsHMAezu7a/bvantH6CvgnvZRjnSyPWsK/img.png&quot; data-alt=&quot;이렇게 작동하게 만들 것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkkMYv/btsHMAezu7a/bvantH6CvgnvZRjnSyPWsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkkMYv%2FbtsHMAezu7a%2FbvantH6CvgnvZRjnSyPWsK%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;1890&quot; height=&quot;476&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 작동하게 만들 것이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1717462917420&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; import { existsSync, mkdirSync, writeFile } from 'fs';
import path from 'path';
import { getPagesFiles } from './routeMapper';

export const createClientFiles = async () =&amp;gt; {
  const files = await getPagesFiles('src/pages'); // src/pages를 전부 읽어온다.
  
  if (!existsSync(path.resolve(`public`))) //root 디렉토리에public폴더가 없으면 만든다.
    mkdirSync(path.resolve(`public`)); //런타임 이전에 실행되므로 블로킹을 고려하지 않았다.
  
  files.forEach((fullFileName) =&amp;gt; {
    const [fileName] = fullFileName.split('.tsx');

    writeFile( //번들 엔트리 파일을 생성한다.
      path.resolve(`public/${fileName}.tsx`),
      `import * as Page from '@/pages/${fileName}';
import { hydrate } from '@core/render';

hydrate(Page.default, document.getElementById('_miniNext'));
  
  `,
      (err) =&amp;gt; {
        console.log(err);
      }
    );
  });
};

createClientFiles();&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;rollup.config.js&lt;/h4&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;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjgKcy/btsHNrVBEia/Qw1LQDXDKmMHEkIg53qqL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjgKcy/btsHNrVBEia/Qw1LQDXDKmMHEkIg53qqL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjgKcy/btsHNrVBEia/Qw1LQDXDKmMHEkIg53qqL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjgKcy%2FbtsHNrVBEia%2FQw1LQDXDKmMHEkIg53qqL0%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;364&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollup에서 만들어준 pulic경로의 파일들에 대해서 번들을 만들도록 처리해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1717462541714&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const babel = require('@rollup/plugin-babel');
const resolve = require('@rollup/plugin-node-resolve');
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
const path = require('path');

const fs = require('fs');

const files = fs.readdirSync('public'); //public의 모든 파일을 읽어온다.
// 이 파일은 런타임 전에 실행될 것이므로 블로킹을 고려하지 않았다.&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_1717462602467&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const createConfigFile = (fullFileName) =&amp;gt; {
  const [fileName] = fullFileName.split('.tsx');
  return {
    input: `./public/${fullFileName}`, //찾아온 파일
    output: {
      file: `./dist/public/${fileName}.js`, //dist/public을 통해 정적파일로제공한다.
      format: 'es',
    },
    plugins: [
      babel({
        babelHelpers: 'bundled',
        presets: ['@babel/preset-env', '@babel/preset-typescript'],
        plugins: [
          ...
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
    ],
  };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 만든 함수를 통해 config 배열을 생성해서 반환해주도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717462651317&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = files.map((fullFilename) =&amp;gt; createConfigFile(fullFilename));&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 getPagesFiles를 통해 찾은 모든 경로에 대해 get메서드에 대한 응답 데이터를 만들어주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1717425036985&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getRoutingPath = (fileName: string) =&amp;gt; {
  //확장자 없는 파일이름에 따라 라우팅 경로를 만들어준다.
  if (fileName === 'index') return '/'; //index인경우
  return `/${fileName.toLowerCase()}`; //아닌경우
};


export const routeMapper = async (app: Express) =&amp;gt; {
  const files = await getPagesFiles(); // dist/pages내부의 .js파일을 가져온다.
  
  //가져온 모든 pages내부의 파일에 대해서
  files.forEach((fullFileName) =&amp;gt; {
    const [fileName] = fullFileName.split('.js'); //.js를 뜯은 파일이름을 기반으로 라우팅을 해줄 것이다.

    app.get(
      getRoutingPath(fileName),

      async (req: Request, res: Response) =&amp;gt; {
      	//ServerSideProps와 Component를 가져와서 html을 생성하고, hydration을 해준다.
        const serverSideFunctions = await getServerSidePropsFunction();
        const serverSideFunction = serverSideFunctions[fullFileName];
        const serverSideProps = serverSideFunction?.().props;
        const { default: Component } = require(
          path.resolve(`${PAGES_PATH}`, fullFileName)
        );
        const html = createHTML(
          &amp;lt;Component {...serverSideProps} /&amp;gt;,
          serverSideProps,
          fileName //동적으로 js파일을 넘겨준다.
        );
        res.send(html);
      }
    );
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 스크립트 수정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 작성한 로직을 순서대로 작동할 수 있게 만들어주자&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;build.sh&lt;/h4&gt;
&lt;pre id=&quot;code_1717466147617&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;babel src --extensions .ts,.tsx --out-dir dist //babel 트랜스파일링
node dist/core/createClientFiles.js //pages기반으로 public폴더, 파일 생성
rollup --config rollup.config.js //dist/public 파일 번들링&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&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;2046&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l24FK/btsHOmeTUtN/4sUkMNLAJTPjyjbtbwj8Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l24FK/btsHOmeTUtN/4sUkMNLAJTPjyjbtbwj8Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l24FK/btsHOmeTUtN/4sUkMNLAJTPjyjbtbwj8Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl24FK%2FbtsHOmeTUtN%2F4sUkMNLAJTPjyjbtbwj8Uk%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;2046&quot; height=&quot;1162&quot; data-origin-width=&quot;2046&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&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/ctw11f/btsHMzfHsrD/RjUdElCRpujT07zEaqRY0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctw11f/btsHMzfHsrD/RjUdElCRpujT07zEaqRY0k/img.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;734&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.4162%; margin-right: 10px;&quot; data-widthpercent=&quot;44.94&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctw11f/btsHMzfHsrD/RjUdElCRpujT07zEaqRY0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fctw11f%2FbtsHMzfHsrD%2FRjUdElCRpujT07zEaqRY0k%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;1290&quot; height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BQAEt/btsHMx3jZWb/KzrjwKVhlZ0kV3hwpjTJHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BQAEt/btsHMx3jZWb/KzrjwKVhlZ0kV3hwpjTJHK/img.png&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;326&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.421%;&quot; data-widthpercent=&quot;55.06&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BQAEt/btsHMx3jZWb/KzrjwKVhlZ0kV3hwpjTJHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBQAEt%2FbtsHMx3jZWb%2FKzrjwKVhlZ0kV3hwpjTJHK%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;702&quot; height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 단일한 Pages 폴더 요소에 대해서 잘 작동하는 것을 볼 수 있다.&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;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;getPagesFiles&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 pages에 폴더가 들어갈 수 있다.&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/8LW46/btsHOndN71P/PQMTkPqxbMwyl6vKZl0j8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8LW46/btsHOndN71P/PQMTkPqxbMwyl6vKZl0j8k/img.png&quot; style=&quot;width: 43.25161865136174%;&quot; data-widthpercent=&quot;43.76&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;378&quot; data-origin-width=&quot;724&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8LW46/btsHOndN71P/PQMTkPqxbMwyl6vKZl0j8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8LW46%2FbtsHOndN71P%2FPQMTkPqxbMwyl6vKZl0j8k%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;724&quot; height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt5s5m/btsHNxVAAIc/h7nMIbpSj9N56jqRfJBkrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt5s5m/btsHNxVAAIc/h7nMIbpSj9N56jqRfJBkrk/img.png&quot; style=&quot;width: 55.58559065096384%;&quot; data-widthpercent=&quot;56.24&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;234&quot; data-origin-width=&quot;576&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt5s5m/btsHNxVAAIc/h7nMIbpSj9N56jqRfJBkrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt5s5m%2FbtsHNxVAAIc%2Fh7nMIbpSj9N56jqRfJBkrk%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;576&quot; height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 폴더 구조에서 getPagesFiles가 수행되면 이렇게 나와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 readdir에서 폴더를 읽은 경우, 재귀적으로 getPagesFiles를 호출해서 경로를 만들어주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이를 재귀함수로 직접 구현했지만, 찾다보니 readDir의 recursive옵션을 통해 해결할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717561170339&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { readdir } from 'fs';
import path from 'path';
const PAGES_PATH = 'dist/pages';

export const getPagesFiles = async (pathString = PAGES_PATH) =&amp;gt; {
  return await new Promise&amp;lt;string[]&amp;gt;((resolve, reject) =&amp;gt; {
    readdir(path.resolve(pathString), { recursive: true }, (err, files) =&amp;gt; {
      if (err) reject(err);
      //확장자가 없는 경우를 무시한다
      resolve((files as string[]).filter((file) =&amp;gt; file.match('(.js)|(.tsx)')));
    });
  });
};&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;이제 이렇게 만든 getPagesFiles를 적용시켜주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getPagesFiles를 쓰는 곳은 createClientFiles, routeMapper, getServerSidePropsFunctions이다.&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;createClientFiles&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩적으로 폴더를 만들어주는게 핵심이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717733384064&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { existsSync, mkdirSync, writeFile } from 'fs';
import path from 'path';
import { getPagesFiles } from './getPagesFiles';

export const createClientFiles = async () =&amp;gt; {
  const files = await getPagesFiles('src/pages'); //src/pages를 모두 읽어옴
  //프로젝트 루트에 public 폴더가 없는경우 만듦
  const isExists = existsSync(path.resolve('public')); 
  if (!isExists) mkdirSync(path.resolve('public'));

  files.forEach((fullFileName) =&amp;gt; {
  //읽어온 파일에서 src/pages와 tsx제거
    const fileName = fullFileName
      .replace(/src\/pages\//, '')
      .replace(/\.tsx/, '');
      
	//중첩라우팅인경우
    if (fileName.match('/')) {
      const filePaths = fileName.split('/');
      //파일 이름을 제외한, 디렉토리만을 가져온다.
      const dirPath = filePaths.slice(0, -1).join('/');
      //해당 디렉토리가 없다면, 중첩적으로 폴더를만들어준다.
      const isExists = existsSync(path.resolve('public', dirPath));
      if (!isExists) {
        mkdirSync(path.resolve('public', dirPath), {
          recursive: true,
        });
      }
    }
    
    writeFile(
      path.resolve(`public/${fileName}.tsx`),
      `import * as Page from '@/pages/${fileName}';
import { hydrate } from '@core/render';

hydrate(Page.default, document.getElementById('_miniNext'));
  
  `,
      (err) =&amp;gt; {
        if (err) console.log(err);
      }
    );
  });
};

createClientFiles();&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;getServerSidePropsFunction&lt;/h4&gt;
&lt;pre id=&quot;code_1717733620045&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import path from 'path';
import { getPagesFiles } from './getPagesFiles';

type ServerSideFunction = Record&amp;lt;string, Function&amp;gt;;

export const getServerSidePropsFunction = async () =&amp;gt; {
  const result: ServerSideFunction = {};

  const files = await getPagesFiles();
  files.map((file) =&amp;gt; {
    const { getServerSideProps } = require(path.resolve('dist/pages', file));
    result[file] = getServerSideProps;
  });
  return result;
};&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;routeMapper&lt;/h4&gt;
&lt;pre id=&quot;code_1717733703198&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const PAGES_PATH = 'dist/pages';
import { readdir } from 'fs';
import path from 'path';
import { Express, Request, Response } from 'express';
import { getServerSidePropsFunction } from './getServerSidePropsFunction';
import { createHTML } from './createHTML';
import { getPagesFiles } from './getPagesFiles';

const getRoutingPath = (fileName: string) =&amp;gt; {
//index 단일 경우에는 /를, 아닌 경우에는 /와 소문자로 구성된 경로를 반환한다.
  if (fileName === 'index') return '/';
  if (fileName.match('index')) return `/${fileName.replace('index', '')}`;
  return `/${fileName.toLowerCase()}`;
};

export const routeMapper = async (app: Express) =&amp;gt; {
  const files = await getPagesFiles();

  files.forEach((fullFileName) =&amp;gt; {
  //dist/pages와 .js를 제거한다.
    const fileName = fullFileName
      .replace(/dist\/pages\//, '')
      .replace(/.js/, '');
    app.get(
      getRoutingPath(fileName),

      async (req: Request, res: Response) =&amp;gt; {
        const serverSideFunctions = await getServerSidePropsFunction();
        const serverSideFunction = serverSideFunctions[fullFileName];
        const serverSideProps = serverSideFunction?.().props;
        const { default: Component } = require(
          path.resolve('dist/pages', fullFileName)
        );
        const html = createHTML(
          &amp;lt;Component {...serverSideProps} /&amp;gt;,
          serverSideProps,
          fileName
        );
        res.send(html);
      }
    );
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rollup.config.js&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollup.config.js도 재귀적으로 public폴더를 탐색해서 파일 번들링 entry포인트를 찾을 수 있도록 구성해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1717733797799&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const babel = require('@rollup/plugin-babel');
const resolve = require('@rollup/plugin-node-resolve');
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
const path = require('path');

const fs = require('fs');

const files = fs
  .readdirSync('public', { recursive: true })
  .filter((fileName) =&amp;gt; fileName.match(/.tsx/));

const createConfigFile = (fullFileName) =&amp;gt; {
  const [fileName] = fullFileName.split('.tsx');
  return {
    input: `./public/${fullFileName}`,
    output: {
      file: `./dist/public/${fileName}.js`,
      format: 'es',
    },
    plugins: [
      babel({
        babelHelpers: 'bundled',
        presets: ['@babel/preset-env', '@babel/preset-typescript'],
        plugins: [
          [
            '@babel/plugin-transform-react-jsx',
            { importSource: '@core', runtime: 'automatic' },
          ],
          [
            'module-resolver',
            {
              alias: {
                '@core': path.resolve(__dirname, 'src/core'),
                '@/utils': path.resolve(__dirname, 'src/utils'),
                '@/pages': path.resolve(__dirname, 'src/pages'),
              },
            },
          ],
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
    ],
  };
};

module.exports = files.map((fullFilename) =&amp;gt; createConfigFile(fullFilename));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;nestedRoute-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjbGTj/btsHQ8n4EaZ/qI7T0P5BfRj9nOdrKnhg70/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjbGTj/btsHQ8n4EaZ/qI7T0P5BfRj9nOdrKnhg70/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjbGTj/btsHQ8n4EaZ/qI7T0P5BfRj9nOdrKnhg70/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bjbGTj/btsHQ8n4EaZ/qI7T0P5BfRj9nOdrKnhg70/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;388&quot; data-filename=&quot;nestedRoute-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>만들면서 학습하기/Next를 만들면서 이해해보자</category>
      <category>mininext</category>
      <category>nestedroute</category>
      <category>NeXT</category>
      <category>SSR</category>
      <category>라우팅</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/369</guid>
      <comments>https://0422.tistory.com/369#entry369comment</comments>
      <pubDate>Fri, 7 Jun 2024 13:26:01 +0900</pubDate>
    </item>
    <item>
      <title>실존은 본질에 앞선다. (feat. 노인과바다, 사피엔스, 이기적 유전자)</title>
      <link>https://0422.tistory.com/368</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의자는 앉기 위해 만들어진다. 침대는 자기 위해 만들어진다. 옷은 입기 위해 만들어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-99d8a7f2-2415-40de-84f0-4be90ef072b6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;본질이 실존에 앞서는것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-2359a67c-dba5-4376-8bef-24e301f50ff5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-543c4cc4-2358-483e-bc55-37e9a07a926b&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 사람은 무엇을 위해 만들어졌는가? 꽃과 동물은 무엇을 위해 만들어졌는가?&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-64ef67c5-99dd-44e6-905b-659e03e8f933&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 질문에 대답하기 위해 많은 철학, 종교가 탄생했다. &lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-64526dae-be45-4da1-9d0f-797f56ef85df&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;SE-6169a292-8db1-48b6-9dea-1b3b932f29ac&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만, 과학이 발전함에따라 종교의 힘은 점차 축소되고, 결국 남는 건 무의미다.&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-8740a267-619f-48a4-848a-bfc2c74474a1&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리는 그냥 존재할 뿐이다. 본질은 아직 존재하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최근의 경험에 관하여&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 꽤나 창업에 관심이 많다. 새로운 가치를 만들어 내고, 이것을 사람들에게 알려 세상을 더 개선하는 것이 나의 꿈, 최종목표이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 년도 초에 시작한 창업프로젝트에 앱 개발자이자 창업 멤버로 참여해서 3개월간 정말 밥 먹고 일만 할 정도로 일을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모가 꽤나 컸음 에도, 팀원들이 열정적으로 임해 주었기에 프로덕트는 거의 런칭 직전이었다. 하지만, 정부지원프로그램 지원서에 팀원의 이름을 뺀 것, 사업계획서를 보여주지 않고 부당한 계약을 요구하여 프로젝트는 좌초되고 말았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 저작권을 언급하며 블로그 글들을 다 지우라고 요구해왔다. 이러한 법적인 부분을 잘 몰랐기에 무료 법률상담까지 알아보며 정말 속이 타 들어가는 경험을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 정말 화가 나기도 하고, 육체적으로나 정신적으로나 너무나도 지치게 되었다. 이렇게 창업에 대한 꿈을 접어야 하나, 이 꿈을 접으면 어떤 목표를 가지고 살아야 할까?를 진지하게 고민하게 되었는 데, 대학 교양 수업을 통해 마침 읽게 된 책이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노인과바다였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저녁에 기분전환용으로 짧게 읽어볼 생각으로 구매한 노인과 바다를 읽기 시작했는데, 몇 번이나 새어 나오는 눈물을 참아야만 했다. 청새치와 고군분투하는 노인에게 너무나도 감정이입이 됐던 것이다. 노인이 청새치를 잡으려 노력하는 장면들, 계속해서 한번 더를 외치며, 청새치와 싸우는 장면은 지금 생각해도 감동이 밀려온다.&lt;/p&gt;
&lt;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;1512&quot; data-origin-height=&quot;1512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btwCFi/btsHQJVOzEO/hSscWFYOklxyyLLEvrade0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btwCFi/btsHQJVOzEO/hSscWFYOklxyyLLEvrade0/img.png&quot; data-alt=&quot;덕분에 책에는 포스트잇이 덕지덕지 붙게 됐다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btwCFi/btsHQJVOzEO/hSscWFYOklxyyLLEvrade0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtwCFi%2FbtsHQJVOzEO%2FhSscWFYOklxyyLLEvrade0%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;417&quot; height=&quot;417&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;1512&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;책을&amp;nbsp;읽고,&amp;nbsp;나&amp;nbsp;역시&amp;nbsp;나를&amp;nbsp;응원해주는&amp;nbsp;마놀린이&amp;nbsp;곁에&amp;nbsp;있다는&amp;nbsp;것,&amp;nbsp;끝까지&amp;nbsp;투쟁하는&amp;nbsp;노인의&amp;nbsp;모습이&amp;nbsp;얼마나&amp;nbsp;아름다운지를&amp;nbsp;알게&amp;nbsp;되어&amp;nbsp;다시한번&amp;nbsp;의지를&amp;nbsp;갖고,&amp;nbsp;다시&amp;nbsp;일어서서&amp;nbsp;나아갈&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;사람은&amp;nbsp;파괴될지&amp;nbsp;언정,&amp;nbsp;패배하지&amp;nbsp;않는다.&lt;/b&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;420&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbpqvq/btsHPxhVgk2/7j1Fv4YsKhDQg8DpMVtrU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbpqvq/btsHPxhVgk2/7j1Fv4YsKhDQg8DpMVtrU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbpqvq/btsHPxhVgk2/7j1Fv4YsKhDQg8DpMVtrU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbpqvq%2FbtsHPxhVgk2%2F7j1Fv4YsKhDQg8DpMVtrU1%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;420&quot; height=&quot;315&quot; data-origin-width=&quot;420&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;감동받은 경험들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&amp;nbsp;노인과&amp;nbsp;바다를&amp;nbsp;읽은&amp;nbsp;후&amp;nbsp;생각해보니&amp;nbsp;내가&amp;nbsp;영화나&amp;nbsp;책을&amp;nbsp;보고&amp;nbsp;&amp;ldquo;이거&amp;nbsp;참&amp;nbsp;명작이다&amp;rdquo;&amp;nbsp;라고&amp;nbsp;생각하는&amp;nbsp;것들은&amp;nbsp;대부분&amp;nbsp;인간의&amp;nbsp;의지를&amp;nbsp;보여주는&amp;nbsp;작품이&amp;nbsp;많았다는&amp;nbsp;것을&amp;nbsp;알게&amp;nbsp;됐다.&amp;nbsp;예를&amp;nbsp;들어,&amp;nbsp;영화&amp;nbsp;중에는&amp;nbsp;어벤져스의&amp;nbsp;캡틴아메리카가&amp;nbsp;타노스의&amp;nbsp;수많은&amp;nbsp;무리들에&amp;nbsp;홀로&amp;nbsp;맞서는&amp;nbsp;장면,&amp;nbsp;최근에는&amp;nbsp;매드맥스의&amp;nbsp;퓨리오사가&amp;nbsp;고통뿐인&amp;nbsp;세상속에서&amp;nbsp;아무도&amp;nbsp;희망을&amp;nbsp;믿지&amp;nbsp;않을&amp;nbsp;때,&amp;nbsp;홀로&amp;nbsp;희망을&amp;nbsp;찾아&amp;nbsp;계속해서&amp;nbsp;도전하는&amp;nbsp;모습에&amp;nbsp;감동을&amp;nbsp;받았다.&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/C2V1B/btsHQ2U977O/C20MIhSqFW420wgDdZz7Lk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C2V1B/btsHQ2U977O/C20MIhSqFW420wgDdZz7Lk/img.jpg&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot; data-is-animation=&quot;false&quot; style=&quot;width: 36.8899%; margin-right: 10px;&quot; data-widthpercent=&quot;37.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C2V1B/btsHQ2U977O/C20MIhSqFW420wgDdZz7Lk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC2V1B%2FbtsHQ2U977O%2FC20MIhSqFW420wgDdZz7Lk%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;1280&quot; height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnWXX7/btsHPv5rfKA/5n5C6NDcrY3lCdwE6zgpM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnWXX7/btsHPv5rfKA/5n5C6NDcrY3lCdwE6zgpM0/img.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;198&quot; data-is-animation=&quot;false&quot; style=&quot;width: 29.5538%; margin-right: 10px;&quot; data-widthpercent=&quot;30.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnWXX7/btsHPv5rfKA/5n5C6NDcrY3lCdwE6zgpM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnWXX7%2FbtsHPv5rfKA%2F5n5C6NDcrY3lCdwE6zgpM0%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;282&quot; height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQITT/btsHQy1aqes/RXC4WVfxi29BkSo8R3ffAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQITT/btsHQy1aqes/RXC4WVfxi29BkSo8R3ffAk/img.png&quot; data-origin-width=&quot;298&quot; data-origin-height=&quot;198&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.2307%;&quot; data-widthpercent=&quot;31.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQITT/btsHQy1aqes/RXC4WVfxi29BkSo8R3ffAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQITT%2FbtsHQy1aqes%2FRXC4WVfxi29BkSo8R3ffAk%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;298&quot; height=&quot;198&quot;/&gt;&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;h3 data-ke-size=&quot;size23&quot;&gt;경험들의 공통점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;작품들의&amp;nbsp;공통점은&amp;nbsp;주인공이&amp;nbsp;운명,&amp;nbsp;부조리에&amp;nbsp;대항한다는&amp;nbsp;점이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부조리는&amp;nbsp;인간이&amp;nbsp;통제할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;예측불가한&amp;nbsp;현상으로,&amp;nbsp;이는&amp;nbsp;다양한&amp;nbsp;형태로&amp;nbsp;우리의&amp;nbsp;삶에&amp;nbsp;다가온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한&amp;nbsp;맥락에서&amp;nbsp;운명이라는&amp;nbsp;단어보다는&amp;nbsp;부조리라는&amp;nbsp;단어를&amp;nbsp;선택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해할&amp;nbsp;수도,&amp;nbsp;납득할&amp;nbsp;수&amp;nbsp;도&amp;nbsp;없는,&amp;nbsp;하나의&amp;nbsp;현상이기&amp;nbsp;때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노인과&amp;nbsp;바다에서는&amp;nbsp;상어가,&amp;nbsp;어벤져스에서는&amp;nbsp;타노스,&amp;nbsp;퓨리오사는&amp;nbsp;아포칼립스&amp;nbsp;세계관&amp;nbsp;자체가&amp;nbsp;부조리다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만, 노인은, 캡틴은, 퓨리오사는, 이런 부조리에 대항해 자신의 신념을 갖고 맞서 싸운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서&amp;nbsp;소개한&amp;nbsp;인물들은&amp;nbsp;운명과&amp;nbsp;부조리에&amp;nbsp;대항한다는&amp;nbsp;공통된&amp;nbsp;선택을&amp;nbsp;했지만,&amp;nbsp;그&amp;nbsp;결과는&amp;nbsp;모두&amp;nbsp;달랐다.&amp;nbsp;노인은&amp;nbsp;상어에게&amp;nbsp;청새치를&amp;nbsp;빼앗겨&amp;nbsp;뼈만&amp;nbsp;남았고,&amp;nbsp;퓨리오사는&amp;nbsp;열심히&amp;nbsp;찾아간&amp;nbsp;고향에&amp;nbsp;아무것도&amp;nbsp;없었지만&amp;nbsp;캡틴은&amp;nbsp;타노스를&amp;nbsp;물리쳤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도&amp;nbsp;나는&amp;nbsp;동일한&amp;nbsp;감정을&amp;nbsp;그들에게서&amp;nbsp;받을&amp;nbsp;수&amp;nbsp;있었고,&amp;nbsp;공감할&amp;nbsp;수&amp;nbsp;있었다.&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/cmv1CA/btsHP2BGobR/iPR9rpXySJssgc8L5rKgm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmv1CA/btsHP2BGobR/iPR9rpXySJssgc8L5rKgm1/img.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;176&quot; data-is-animation=&quot;false&quot; style=&quot;width: 28.221%; margin-right: 10px;&quot; data-widthpercent=&quot;28.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmv1CA/btsHP2BGobR/iPR9rpXySJssgc8L5rKgm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcmv1CA%2FbtsHP2BGobR%2FiPR9rpXySJssgc8L5rKgm1%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;244&quot; height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTaA9x/btsHPMZ95jC/qNImP63QiZKlWEPJkpPMuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTaA9x/btsHPMZ95jC/qNImP63QiZKlWEPJkpPMuk/img.png&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;176&quot; data-is-animation=&quot;false&quot; style=&quot;width: 38.8617%; margin-right: 10px;&quot; data-widthpercent=&quot;39.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTaA9x/btsHPMZ95jC/qNImP63QiZKlWEPJkpPMuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTaA9x%2FbtsHPMZ95jC%2FqNImP63QiZKlWEPJkpPMuk%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;336&quot; height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct7FQW/btsHPyVpAP0/jx2eWXTGlKFkCx0nE4u9Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct7FQW/btsHPyVpAP0/jx2eWXTGlKFkCx0nE4u9Z0/img.png&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;177&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.5917%;&quot; data-widthpercent=&quot;31.32&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct7FQW/btsHPyVpAP0/jx2eWXTGlKFkCx0nE4u9Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct7FQW%2FbtsHPyVpAP0%2Fjx2eWXTGlKFkCx0nE4u9Z0%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;266&quot; height=&quot;177&quot;/&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;왜 나는 감동받았는가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그렇다면 왜?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜&amp;nbsp;나는&amp;nbsp;일면식도&amp;nbsp;없는&amp;nbsp;인물들에게&amp;nbsp;공감하고,&amp;nbsp;부조리에&amp;nbsp;대항하는&amp;nbsp;인물에게&amp;nbsp;감동을&amp;nbsp;받고,&amp;nbsp;그들처럼&amp;nbsp;되어야겠다&amp;nbsp;다짐하는&amp;nbsp;것일까?&amp;nbsp;나는&amp;nbsp;그것의&amp;nbsp;이유를&amp;nbsp;&lt;b&gt;이기적&amp;nbsp;유전자&lt;/b&gt;와&amp;nbsp;&lt;b&gt;사피엔스&lt;/b&gt;를&amp;nbsp;연결&amp;nbsp;지어&amp;nbsp;찾을&amp;nbsp;수&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 이기적 유전자의 &lt;b&gt;유전자&lt;/b&gt;에 꽂히기 마련이지만, 나는 &lt;b&gt;안정&lt;/b&gt;이라는 단어에 꽂혔다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3GjaW/btsHPbM4apt/QL0QkzPB7lgn5kywEk6hT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3GjaW/btsHPbM4apt/QL0QkzPB7lgn5kywEk6hT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3GjaW/btsHPbM4apt/QL0QkzPB7lgn5kywEk6hT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3GjaW%2FbtsHPbM4apt%2FQL0QkzPB7lgn5kywEk6hT0%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;410&quot; height=&quot;185&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;185&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAbpe/btsHQQN3DA0/H2Y0aWP45ioHx8T5YOfVaK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAbpe/btsHQQN3DA0/H2Y0aWP45ioHx8T5YOfVaK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAbpe/btsHQQN3DA0/H2Y0aWP45ioHx8T5YOfVaK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAbpe%2FbtsHQQN3DA0%2FH2Y0aWP45ioHx8T5YOfVaK%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;773&quot; height=&quot;232&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;232&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;모든 생명체를 넘어서, 모든 물질이 안정을 추구하는 것이다.&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;불안정은 인간에게 불안으로 다가온다.&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;/h3&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;/li&gt;
&lt;li&gt;안정되었다고 &lt;b&gt;믿을 것&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 결코 안정상태가 될 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적으로는 물론이고, 안정상태가 된다고 한들, 그것보다 더 나은 안정상태가 항상 존재하기 마련이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 안정에 도달할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;280&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2wLXR/btsHQRzpu5V/pOFvFlMCtBcJ1rPwp01ua1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2wLXR/btsHQRzpu5V/pOFvFlMCtBcJ1rPwp01ua1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2wLXR/btsHQRzpu5V/pOFvFlMCtBcJ1rPwp01ua1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2wLXR%2FbtsHQRzpu5V%2FpOFvFlMCtBcJ1rPwp01ua1%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;192&quot; height=&quot;276&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;강력한 의지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면&amp;nbsp;왜&amp;nbsp;그들에게서&amp;nbsp;감동을&amp;nbsp;느끼고,&amp;nbsp;매력을&amp;nbsp;느끼는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들은&amp;nbsp;강력한&amp;nbsp;의지로,&amp;nbsp;옳다고&amp;nbsp;믿는&amp;nbsp;신념을&amp;nbsp;지키기&amp;nbsp;위해&amp;nbsp;때로는&amp;nbsp;죽음조차&amp;nbsp;불사한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨&amp;nbsp;일이&amp;nbsp;있어도&amp;nbsp;그것을&amp;nbsp;지킬&amp;nbsp;것이라는&amp;nbsp;&lt;b&gt;의지는&amp;nbsp;변하지&amp;nbsp;않는&amp;nbsp;것&lt;/b&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhT0c/btsHO2o8XP4/bxnfu1NhyVAYAFR1lK5zq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhT0c/btsHO2o8XP4/bxnfu1NhyVAYAFR1lK5zq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhT0c/btsHO2o8XP4/bxnfu1NhyVAYAFR1lK5zq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrhT0c%2FbtsHO2o8XP4%2Fbxnfu1NhyVAYAFR1lK5zq1%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;680&quot; height=&quot;192&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 그들에게서 존경심은 물론, 안정을 느끼기 때문에, 감동을 받는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강력한 의지는 곧 안정이다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 본질을 찾아낼 때다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최후의 확정된 부조리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인생이 도대체 어떻게 흘러갈지는 모르겠지만, 현재로써 가장 확실하게 정해져 있는 것은 나는 죽는다 라는 사실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최종적이고, 절대적인 상태의 안정은 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 계속해서 안정이라 믿을 수 있게 하는 &lt;b&gt;강력한 의지&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 본질은 변하지 않을 강력한 의지를 찾아내는 것, 그것을 지켜나가는 것에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의지를 채우기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떻게 의지를 찾아낼 수 있을까&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 내가 좋아하는 일, 자신에게 의미와 가치가 있는 일들을 지속적으로 찾아가는게 좋다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 하나로 한정 지을 필요 없이 다양한 의미를 찾아보면 자연스럽게 다양한 의지로 가득 채워진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIDW7i/btsHPEgThlZ/m4onp6G0qshgC0arlsnl21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIDW7i/btsHPEgThlZ/m4onp6G0qshgC0arlsnl21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIDW7i/btsHPEgThlZ/m4onp6G0qshgC0arlsnl21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIDW7i%2FbtsHPEgThlZ%2Fm4onp6G0qshgC0arlsnl21%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;280&quot; height=&quot;210&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 목표든, 꿈이든, 가족이든, 사랑이든 아니면 진심으로 좋아하는 하나의 분야든.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 사실을 알게 되면 타인의 의지와 그로 인해 발현된 것들(취향, 선택)을 존중할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예컨데, 나는 스포츠와 서브컬쳐에 그렇게 열정적이지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그것에 열정적인 사람들을 존중할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들의 진심과 의지를 이해할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나아가 누군가가 진심을 다했지만, 부조리로 인해 잘 되지 않았을 때, 그들의 진심과 의지를 인식하고, 진정으로 공감할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 나는 누군가의 진정한 마놀린이 된다. 또한 누군가의 소중한 사람, 즉 의지가 되기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 6개월은 정말 다양한 일이 있었고, 지쳐가기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 그런 경험을 쌓고, 그로 인한 생각들을 하나로 연결시킬 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 경험을 먼저 작성하고, 이후에 결론이 나왔다. 도입부는 맨 마지막에 적었다.&lt;/p&gt;
&lt;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노인과 바다의 일부를 첨부하며 글을 마친다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkP4TI/btsHRbqTdvu/h0KQrRdVGnqfNey6qY1B4k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkP4TI/btsHRbqTdvu/h0KQrRdVGnqfNey6qY1B4k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkP4TI/btsHRbqTdvu/h0KQrRdVGnqfNey6qY1B4k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkP4TI%2FbtsHRbqTdvu%2Fh0KQrRdVGnqfNey6qY1B4k%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;792&quot; height=&quot;163&quot; data-origin-width=&quot;773&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>생각</category>
      <category>노인과바다</category>
      <category>매드맥스</category>
      <category>사피엔스</category>
      <category>실존주의</category>
      <category>이기적유전자</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/368</guid>
      <comments>https://0422.tistory.com/368#entry368comment</comments>
      <pubDate>Thu, 6 Jun 2024 11:42:43 +0900</pubDate>
    </item>
    <item>
      <title>Next를 만들어보면서 이해해보자 (JSX기반 MiniNext) - getServerSideProps, hydration</title>
      <link>https://0422.tistory.com/367</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pGS7R/btsHECD1cVQ/O4PulHt5KeWkJ8JndzUi70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pGS7R/btsHECD1cVQ/O4PulHt5KeWkJ8JndzUi70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pGS7R/btsHECD1cVQ/O4PulHt5KeWkJ8JndzUi70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpGS7R%2FbtsHECD1cVQ%2FO4PulHt5KeWkJ8JndzUi70%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;897&quot; height=&quot;572&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&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;이전에 동일한 JSX를 갖고 html과 React App을 렌더링하고, 이 둘을 연결시켜보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Next(12버전)의 핵심 기능중 하나인 getServerSideProps를 구현하고, 이걸 클라이언트와 연동시켜보고자 한다.&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;getServerSideProps&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Next docs에서 가져온 사진이다.&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;832&quot; data-origin-height=&quot;703&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgnx7C/btsHIW8mic2/ifFyHHmcKthPhHIztXTeF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgnx7C/btsHIW8mic2/ifFyHHmcKthPhHIztXTeF0/img.png&quot; data-alt=&quot;https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgnx7C/btsHIW8mic2/ifFyHHmcKthPhHIztXTeF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgnx7C%2FbtsHIW8mic2%2FifFyHHmcKthPhHIztXTeF0%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;472&quot; height=&quot;399&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;703&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;getServerSideProps는 page컴포넌트에 작성되어야한다.&lt;/li&gt;
&lt;li&gt;함수 이름은 getServerSideProps이며, export되어야한다.&lt;/li&gt;
&lt;li&gt;page컴포넌트에 getServerSideProps에서 반환된 값을 props로 넘겨준다.&lt;/li&gt;
&lt;li&gt;page컴포넌트 자체는 default export되어야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무작정 적용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 위의 예시처럼 App과 Content에 Server Side Props를 넘겨주고, 이걸 useState의 초기값으로 넘겨보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;App.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1717050093931&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Content from '../components/Content';
import Header from '../components/Header';

interface AppProps {
  number?: number;
}
export const getServerSideProps = () =&amp;gt; {
  return {
    props: {
      number: 100,
    },
  };
};

export default function App({ number }: AppProps) {
  return (
    &amp;lt;div style={{ width: '100%' }}&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;Content serverNumber={number} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Content&lt;/h4&gt;
&lt;pre id=&quot;code_1717050162383&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from '@core/useState';

export default function Content({ serverNumber }: { serverNumber?: number }) {
  const [number, setNumber] = useState&amp;lt;number&amp;gt;(serverNumber || 0);

  return (...)
}&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이걸 어떻게 적용시킬지 고민해보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;getServerSideProps를 가져오기&lt;/h4&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;getServerSideProps는 page컴포넌트에 작성되어야한다.&lt;/li&gt;
&lt;li&gt;함수 이름은 getServerSideProps이며, export되어야한다.&lt;/li&gt;
&lt;li&gt;page컴포넌트에 getServerSideProps에서 반환된 값을 props로 넘겨준다.&lt;/li&gt;
&lt;li&gt;page컴포넌트 자체는 default export되어야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 활용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getServerSideProps와 page default 함수는 모두 pages 안에 작성되어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 pages의 파일을 읽어와서, require를 통해 동적으로 모듈을 가져올 수 있다.&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;동적으로 getServerSideProps를 가져와보자.&lt;/p&gt;
&lt;pre id=&quot;code_1717051377104&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { readdir } from 'fs';
import path from 'path';

const PAGES_PATH = 'dist/pages';

type ServerSideFunction = Record&amp;lt;string, Function&amp;gt;;

export const getServerSidePropsFunction = async () =&amp;gt; {
  const result: ServerSideFunction = {};
  const files: string[] = await new Promise((resolve, reject) =&amp;gt; {
  //runtime에 pages 디렉토리를 훑게되므로 블로킹을 고려한다.
    readdir(PAGES_PATH, (err, files) =&amp;gt; {
      if (err) reject();
      resolve(files); //파일목록을 읽어온다. ex) files=[&quot;index.js&quot;]
    });
  });

  files.map((file) =&amp;gt; {
    const { getServerSideProps } = require(
      path.resolve(`${PAGES_PATH}/${file}`) //ex) dist/pages/index.js
    );
    result[file] = getServerSideProps; //객체에 함수를 등록한다.
  });
  /**
  result= {
	&quot;App.js&quot;:getServerSideProps;
  }
  **/
  return result;
  
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pages 내부의 모든 파일을 가져와서 그중에 getServerSideProps라는 모듈을 찾아온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 이걸 fileName프로퍼티의 값으로 붙여서 내보내주게 되면, 해당 객체내부의 함수만 호출하면 serverProps를 가져올 수 있게 된다!&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;이러면 이제 serverProps를 내려줄 수는 있는데 serverProps가 적용은 되지 않은 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9xil/btsHJae4Ixe/stwK5Tvzd4SKMMED8eM820/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9xil/btsHJae4Ixe/stwK5Tvzd4SKMMED8eM820/img.png&quot; data-alt=&quot;값이 0이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9xil/btsHJae4Ixe/stwK5Tvzd4SKMMED8eM820/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9xil%2FbtsHJae4Ixe%2FstwK5Tvzd4SKMMED8eM820%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;604&quot; height=&quot;462&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;값이 0이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 클라이언트에서 hydrate할때 serverProps가 적용되지 않기때문이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;public/index.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수가 클라이언트가 받게되는 js파일이된다.&lt;/p&gt;
&lt;pre id=&quot;code_1717120026521&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from '../src/components/App';
import { hydrate } from '@core/render';

hydrate(&amp;lt;App /&amp;gt;, document.getElementById('_miniNext'));&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이전 hydrate함수&lt;/h4&gt;
&lt;pre id=&quot;code_1717119948274&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const hydrate = (content: MiniReactNode, container: HTMLElement) =&amp;gt; {
  console.log('hydrate 완료');
  const element = makeDOM(content);
  root = container;
  prev = content;
  container.innerHTML = '';
  container.appendChild(element);
  callEffects(); // DOM이 생성된 이후에 useEffect를 실행시킨다.
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 보면 content가 App함수를 호출한 결과인데, public/index.tsx에서 호출할때 따로 ServerProps를 전달해주지 않고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hydrate (ServerSideProps)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 ServerProps를 클라이언트 파일도 접근할 수 있게 만들면된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;how?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 서버사이드에서 html을 생성할때, script에서 window객체에 serverSideProps를 담아서 보내주도록 했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 createHTML의 인자로 serverSideProps를 받을 수 있어야한다.&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;createHTML&lt;/h4&gt;
&lt;pre id=&quot;code_1717120397970&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const createHTML = (
  element: MiniReactNode,
  initialServerProps?: Record&amp;lt;string, string&amp;gt; //serverProps를 인자로 받았다.
) =&amp;gt; {
  const root = `
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;MiniNext&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;_miniNext&quot;&amp;gt;${_createHTML(element)}&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script&amp;gt;
    window._miniNextData=${JSON.stringify(initialServerProps)} //window._miniNextData에 저장한다.
  &amp;lt;/script&amp;gt;
  &amp;lt;script src='index.js' type=&quot;module&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/html&amp;gt;`;
  return root;
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;app.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 app.tsx도 아래처럼 바뀌게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1717120459835&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app.get('/', async (req, res) =&amp;gt; {
  const serverSideObject = await getServerSidePropsFunction();
  const serverSideProps = serverSideObject[&quot;App.js&quot;]().props

  const html = createHTML(App(serverSideProps), serverSideProps);
  res.send(html);
});&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;그럼 이제 window._miniNextData에 넣어준 데이터를 miniReact의 hydrate과정에서 props로 넣어주면된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; hydrate&lt;/h4&gt;
&lt;pre id=&quot;code_1717120546100&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const hydrate = (Component: Function, container: HTMLElement) =&amp;gt; {
  const content = Component(window._miniNextData); //ServerSideProps연동
  console.log('hydrate 완료');
  const element = makeDOM(content);
  root = container;
  prev = content;
  container.innerHTML = '';
  container.appendChild(element);
  callEffects();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 &amp;lt;App/&amp;gt;처럼 호출이 완료된 함수 객체, miniReactNode를 인자로 받았지만, props를 hydrate함수에서 넣어주기위해서 인자 타입을 변경해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 window._miniNextData의 값을 App의 props로 넣어주게된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;public/index.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1717120660699&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from '../src/pages/App';
import { hydrate } from '@core/render';

hydrate(App, document.getElementById('_miniNext')); //&amp;lt;App/&amp;gt;에서 App으로 함수 객체자체를 넘긴다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFy3fX/btsHJCh3zw5/EvkzrckjYmKbZqKOHePti0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFy3fX/btsHJCh3zw5/EvkzrckjYmKbZqKOHePti0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFy3fX/btsHJCh3zw5/EvkzrckjYmKbZqKOHePti0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFy3fX%2FbtsHJCh3zw5%2FEvkzrckjYmKbZqKOHePti0%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;458&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내려준 ServerSideProps가 잘 적용된 모습이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1717121140929&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getServerSideProps = async () =&amp;gt; {
  const number = await new Promise((resolve) =&amp;gt; {
    resolve(100);
  });
  return {
    props: {
      number,
    },
  };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요런식으로 getServerSideProps함수를 변경해도 잘 동작하는 모습이다.&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;+) 실제 Next&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3382&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u0yOJ/btsHIvqKMZL/ZC0pDTGsMilXFEBgiio90K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u0yOJ/btsHIvqKMZL/ZC0pDTGsMilXFEBgiio90K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u0yOJ/btsHIvqKMZL/ZC0pDTGsMilXFEBgiio90K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu0yOJ%2FbtsHIvqKMZL%2FZC0pDTGsMilXFEBgiio90K%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;3382&quot; height=&quot;934&quot; data-origin-width=&quot;3382&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__NEXT_DATA__에 데이터를 담아서 보내준다. 비슷하게 동작한다고 추론할 수 있다.&lt;/p&gt;</description>
      <category>만들면서 학습하기/Next를 만들면서 이해해보자</category>
      <category>hydrate</category>
      <category>mininext</category>
      <category>minireact</category>
      <category>NeXT</category>
      <category>serversideprops</category>
      <category>SSR</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/367</guid>
      <comments>https://0422.tistory.com/367#entry367comment</comments>
      <pubDate>Fri, 31 May 2024 11:06:40 +0900</pubDate>
    </item>
    <item>
      <title>Next를 만들어보면서 이해해보자 (JSX기반 MiniNext) - createHTML, 기본 hydrate 구현하기 (feat. miniReact)</title>
      <link>https://0422.tistory.com/366</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvzGCD/btsHEV2468X/pDVM8c7PU59zkEn0MX0pG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvzGCD/btsHEV2468X/pDVM8c7PU59zkEn0MX0pG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvzGCD/btsHEV2468X/pDVM8c7PU59zkEn0MX0pG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvzGCD%2FbtsHEV2468X%2FpDVM8c7PU59zkEn0MX0pG0%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;897&quot; height=&quot;572&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글에서 호환 가능한 JSX 컴포넌트를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 JSX를 사용해서 SSR에서 반환할 HTML과 클라이언트에서 hydrate, 업데이트할 코드를 작성해볼 것이다.&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;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;jsx함수로 부터 반환된 miniReactNode객체를 서버에서 HTML String으로 변환할 것이다.(클라이언트로 js파일을 여기서 보내 줄 것이다.)&lt;/li&gt;
&lt;li&gt;jsx함수로 부터 반환된 miniReactNode객체를 클라이언트에서는 DOM으로 변환할 것이다.&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트에서는 &lt;a href=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniReact&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전에 만들어둔 miniReact&lt;/a&gt;를 활용하여 useState을 통해 update할 것이다.&lt;/li&gt;
&lt;li&gt;update는 VDOM을 비교하여 바뀐부분만 업데이트한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;받은 클라이언트는 js파일을 통해 hydrate를 하여 miniReact로 동작하는 어플리케이션을 조작할 수 있게될 것이다.&lt;/li&gt;
&lt;/ol&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;HTML생성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 만든 JSX를 통해 HTML String을 생성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MiniReactNode&lt;/p&gt;
&lt;pre id=&quot;code_1716884985623&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type DefaultProps = Record&amp;lt;string, string&amp;gt;;
type Props = DefaultProps &amp;amp; {
  children?: MiniReactNode[];
};

export interface MiniReactNode {
  tagName: string;
  props: Props;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자료구조를 활용해서 props.children을 재귀적으로 돌면서 HTML 문자열을 생성할 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;_createHTML&lt;/h4&gt;
&lt;pre id=&quot;code_1716884955237&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const _createHTML = (element: string | MiniReactNode) =&amp;gt; {
  if (typeof element === 'string' || typeof element === 'number') {
    //element가 text거나 number인 경우 그냥 내보낸다.
    return element;
  }


  let HTMLString = `&amp;lt;${element.tagName} `;  // 태그 열기
  let styleProps = ``;
  if (element.props) { //props를 HTML attribute로 붙인다.
    Object.keys(element.props).forEach((key) =&amp;gt; {
      if (key === 'style') { //style의 경우 여러개가 붙을 수 있어서 따로 처리
        const styleObject = element.props[key];
        Object.entries(styleObject).forEach(([key, value]) =&amp;gt; {
          styleProps += `${key}: ${value}; `;
        });
        HTMLString += `style= &quot;${styleProps}&quot;`;
      } else if (key !== 'children') { //children은 붙이지 않고 이후에 재귀적으로 만들어 붙인다.
        HTMLString += `${key}= &quot;${element.props[key]}&quot; `;
      }
    });
  }
  HTMLString = HTMLString.trimEnd(); //'&amp;lt;tagName ...attribute '&amp;lt;- 마지막 공백을 제거한다
  HTMLString += '&amp;gt;'; //태그를 닫아준다.
  if (element.props.children) {
    //children에 대해 재귀적으로 HTML을 생성해 붙인다.
    element.props.children.forEach((child) =&amp;gt; {
      HTMLString += _createHTML(child);
    });
  }
  HTMLString += `&amp;lt;/${element.tagName}&amp;gt;`; //태그를 완전히 닫아준다.
  return HTMLString; //최종 생성된 HTML을 반환한다.
};&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;h4 data-ke-size=&quot;size20&quot;&gt;createHTML&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 나온 결과물에 html, head, body와 hydrate를 위한 루트요소(_miniNext)를 추가해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 rollup으로 번들링 완료한 클라이언트 js파일을 보내준다.&lt;/p&gt;
&lt;pre id=&quot;code_1716885411788&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const createHTML = (element: MiniReactNode) =&amp;gt; {
  const root = `
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;MiniNext&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;_miniNext&quot;&amp;gt;${_createHTML(element)}&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script src='index.js'&amp;gt;&amp;lt;/script&amp;gt; //클라이언트로 보내줄 js파일 (rollup으로 번들링해준 결과물)
  &amp;lt;/html&amp;gt;`;
  return root;
};&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;app.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이렇게 변경해주고 npm run dev로 서버를 실행시켜주자.&lt;/p&gt;
&lt;pre id=&quot;code_1716885462336&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app.get('/', (req, res) =&amp;gt; {
  const html = createHTML(&amp;lt;App /&amp;gt;);
  res.send(html);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjqtas/btsHEj5zGb4/XJZHV5Aj7ngvuRVJmo9hGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjqtas/btsHEj5zGb4/XJZHV5Aj7ngvuRVJmo9hGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjqtas/btsHEj5zGb4/XJZHV5Aj7ngvuRVJmo9hGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjqtas%2FbtsHEj5zGb4%2FXJZHV5Aj7ngvuRVJmo9hGK%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;1919&quot; height=&quot;360&quot; data-origin-width=&quot;1919&quot; data-origin-height=&quot;360&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;h2 data-ke-size=&quot;size26&quot;&gt;VDOM과 업데이트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 이전에 만들었던 miniReact코드를 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이전 글을 첨부하고, 자세한 설명은 하지 않겠다.&lt;br /&gt;&lt;a href=&quot;https://0422.tistory.com/318&quot;&gt;https://0422.tistory.com/318&lt;/a&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/319&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/319&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/320&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/320&lt;/a&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;createDOM&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가치로 jsx함수를 통해 반환된 MiniReactNode에 대해서 수행하는데, 이번에는 실제 DOM요소를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1716885672143&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { MiniReactNode } from './jsx-runtime';

export const makeDOM = (element: string | MiniReactNode) =&amp;gt; {
  if (typeof element === 'string' || typeof element === 'number') {
    //element가 text거나 number인 경우 textNode로 만든다.
    return document.createTextNode(String(element));
  }

  const DOMElement = document.createElement(element.tagName);
  if (element.props)
    //props를 실제 DOM요소에 적용시킨다.
    Object.keys(element.props).forEach((key) =&amp;gt; {
      if (key === 'style') {
        const styleObject = element.props[key];
        Object.entries(styleObject).forEach(([key, value]) =&amp;gt; {
          (DOMElement as any).style[key] = value;
        });
        return;
      }
      if (key !== 'children') (DOMElement as any)[key] = element.props[key];
    });
  if (element.props.children) {
    //children에 대해 재귀적으로 DOM요소를 만들어 현재 요소에 붙인다.
    element.props.children.forEach((child) =&amp;gt; {
      DOMElement.appendChild(makeDOM(child));
    });
  }
  element.ref = DOMElement;
  return DOMElement; //최종 생성된 DOM요소를 반환한다.;
};&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;useState, useEffect&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 miniReact 코드를 사용했으므로 훅을 직접 구현해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 대한 설명은 &lt;a href=&quot;https://0422.tistory.com/320&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/320&lt;/a&gt; 를 참고하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useState에 대한 설명밖에 없으나, useEffect도 동일한 원리다. 둘다 index기반으로 작동하며, 따라서 rerender시 index를 초기화시켜주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useState&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716886316453&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { rerender } from './render';

const states: any[] = []; //배열 형태의 states
let stateIndex = 0;

export const useState = &amp;lt;T&amp;gt;(initialValue: T) =&amp;gt; {
  const indexBind = stateIndex;
  stateIndex++;
  if (states[indexBind] === undefined) {
    states.push(initialValue);
  }

  const setState = (updateValue: T) =&amp;gt; {
    states[indexBind] = updateValue;
    rerender();
  };
  return [states[indexBind], setState];
};

export const initializeIndex = () =&amp;gt; {
  stateIndex = 0;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useEffect&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716886285421&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type EffectCallbacks = {
  func: () =&amp;gt; void;
  deps: any[];
  haveToCall: boolean;
};

const effectCallbackArray: EffectCallbacks[] = [];
let effectIndex = 0;

export const useEffect = (callback: () =&amp;gt; void, deps: any[]) =&amp;gt; {
  const indexBind = effectIndex; 
  effectIndex++;
  if (effectCallbackArray[indexBind] === undefined) {
    effectCallbackArray.push({
      func: callback,
      deps,
      haveToCall: true,
    });
    return;
  }
  const isChange = deps.some(
    (deps, index) =&amp;gt; effectCallbackArray[indexBind].deps[index] !== deps
  );
  if (isChange) {
    effectCallbackArray[indexBind].deps = deps;
    effectCallbackArray[indexBind].haveToCall = true;
  }
};

export const getEffectArray = () =&amp;gt; effectCallbackArray;
export const initializeEffectIndex = () =&amp;gt; (effectIndex = 0);&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;hydrate&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hydrate는 앞서 craeteHTML에서 만들어줬던 _miniNext div요소 내부요소를 만들어준 DOM으로 교체해주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 miniReact에서의 최초의 render대신 hydrate가 이뤄지는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1716886492116&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const hydrate = (content: MiniReactNode, container: HTMLElement) =&amp;gt; {
  console.log('hydrate 완료');
  const element = makeDOM(content);
  root = container;
  prev = content;
  container.innerHTML = '';
  container.appendChild(element);
  callEffects(); // DOM이 생성된 이후에 useEffect를 실행시킨다.
};&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;update, rerender&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update의 경우 VDOM을 비교해서 DOM을 업데이트하는 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 다루기에는 내용이 너무 많으므로 &lt;a href=&quot;https://0422.tistory.com/319&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;a href=&quot;https://github.com/d0422/learning-by-making/blob/main/apps/miniNext/src/core/updateDOM.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github 링크&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;rerender는 이런식으로 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1716886127149&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let prev: MiniReactNode; //이전의 virtual DOM
let root: Element; //최상단 root요소

export const rerender = () =&amp;gt; {
  initializeIndex(); //리렌더시 useState의 index를 초기화
  initializeEffectIndex(); //useEffect index 초기화
  const updateElement = &amp;lt;App /&amp;gt;;
  updateDOM(root, prev, updateElement);
  callEffects(); //useEffect콜백 호출
  prev = updateElement; //업데이트가 끝나면, 요소 업데이트
};&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컴포넌트를 수정해주었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;App.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1716886710994&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;div style={{ width: '100%' }}&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;Content /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Header.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1716886732818&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Header() {
  return (
    &amp;lt;header&amp;gt;
      &amp;lt;h1
        style={{
          fontSize: '35px',
          textAlign: 'center',
        }}
      &amp;gt;
        MiniNext
      &amp;lt;/h1&amp;gt;
    &amp;lt;/header&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Content.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 useState를 통해 컴포넌트를 업데이트 할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1716886672270&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from '@core/useState';

export default function Content() {
  const [number, setNumber] = useState&amp;lt;number&amp;gt;(0);

  return (
    &amp;lt;div
      style={{
        ...
      }}
    &amp;gt;
      &amp;lt;div
        style={{
          ...
        }}
      &amp;gt;
        &amp;lt;div&amp;gt;Your Number : &amp;lt;/div&amp;gt;
        &amp;lt;div style={{ fontWeight: 700 }}&amp;gt;{number}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;button
        onclick={() =&amp;gt; setNumber(number + 1)}
        style={{
          ...
        }}
      &amp;gt;
        Click This!
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;hydrate적용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 public/index.tsx에 hydrate를 적용하자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;public/index.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1716886808904&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from '../src/components/App';
import { hydrate } from '@core/render';

hydrate(&amp;lt;App /&amp;gt;, document.getElementById('_miniNext'));&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnlfX3/btsHEoMsnqh/QyBvaFYJXm9OU2WebKQi2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnlfX3/btsHEoMsnqh/QyBvaFYJXm9OU2WebKQi2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnlfX3/btsHEoMsnqh/QyBvaFYJXm9OU2WebKQi2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnlfX3%2FbtsHEoMsnqh%2FQyBvaFYJXm9OU2WebKQi2k%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;1127&quot; height=&quot;360&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-05-2818-11-57-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxcCOq/btsHE0KWKRQ/7FOAMc1IqUCSMMvh6RCpv0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxcCOq/btsHE0KWKRQ/7FOAMc1IqUCSMMvh6RCpv0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxcCOq/btsHE0KWKRQ/7FOAMc1IqUCSMMvh6RCpv0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cxcCOq/btsHE0KWKRQ/7FOAMc1IqUCSMMvh6RCpv0/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;338&quot; data-filename=&quot;2024-05-2818-11-57-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hydrate가 정상적으로 완료되었고, state 도 잘 바뀌는 것을 확인할 수 있다!&lt;/p&gt;</description>
      <category>만들면서 학습하기/Next를 만들면서 이해해보자</category>
      <category>hydrate</category>
      <category>mininext</category>
      <category>NeXT</category>
      <category>react</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/366</guid>
      <comments>https://0422.tistory.com/366#entry366comment</comments>
      <pubDate>Tue, 28 May 2024 18:14:23 +0900</pubDate>
    </item>
    <item>
      <title>Next를 만들어보면서 이해해보자 (JSX기반 MiniNext) - 호환 가능한 JSX(feat. Rollup, nodemon, Babel)</title>
      <link>https://0422.tistory.com/365</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSojbo/btsHFq2IFBl/WwqApGRAt3XK0yMM5B9Vz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSojbo/btsHFq2IFBl/WwqApGRAt3XK0yMM5B9Vz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSojbo/btsHFq2IFBl/WwqApGRAt3XK0yMM5B9Vz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSojbo%2FbtsHFq2IFBl%2FWwqApGRAt3XK0yMM5B9Vz0%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;897&quot; height=&quot;572&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;구성하기&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;내가 원하는 것&lt;/h3&gt;
&lt;pre id=&quot;code_1716790321903&quot; class=&quot;coffeescript&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;app.get('/', (req, res) =&amp;gt; {
  const html = createHTML(&amp;lt;App /&amp;gt;);
  res.send(html);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;바로 이런 그림이다. 클라이언트와 동일한게 &amp;lt;App/&amp;gt;을 렌더링하고, 이걸 초기 HTML로 전송해주는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 클라이언트에서는 이렇게 App을 렌더링해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1716820168777&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from '../src/pages/App';
import { hydrate } from '@core/render';

hydrate(&amp;lt;App/&amp;gt;, document.getElementById('_miniNext'));&lt;/code&gt;&lt;/pre&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;이러면 클라이언트와 서버에 대해서 동일한 App컴포넌트를 통해 따로 따로 코드 해당 환경에 맞는 코드를 작성할 필요없이 통합적으로 관리해줄 수 있다.&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JSX코드를 &lt;b&gt;서버&lt;/b&gt;에서 트랜스파일링할 수 있어야한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;클라이언트에서도 역시 JSX가 최종적으로는 일반적인 JS 파일형태로 변환되어 잘 실행될 수 있어야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;언어는 &lt;b&gt;Typescript&lt;/b&gt;를 사용할 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 구동을 위해 &lt;b&gt;Express&lt;/b&gt;, &lt;b&gt;nodemon&lt;/b&gt;을 사용할 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;JSX, typescript 트랜스파일링은 &lt;b&gt;babel&lt;/b&gt;을 통해 구성할 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트의 JSX트랜스파일링은 번들링을 통해 해결할 것인데, index.html이 필요하지 않으므로 &lt;b&gt;Rollup&lt;/b&gt;을 통해 구성해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1716821808608&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y
npm i typescript nodemon -D
npm i express&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;babel과 rollup은 구성하면서 설치할 것이라 일단 이렇게 설치한다.&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: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Babel&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바벨을 통해 서버 코드를 트랜스파일링 해줄 것이다. (JSX사용을 위해)&lt;/p&gt;
&lt;pre id=&quot;code_1716790321905&quot; class=&quot;coffeescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;npm i @babel/cli @babel/core @babel/plugin-transform-react-jsx @babel/preset-env @babel/preset-typescript&lt;/code&gt;&lt;/pre&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;plugin-transform-react-jsx:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&amp;nbsp;JSX를 함수로 변환시켜주는 플러그인이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;preset-typescript:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;babel이 typescript도 읽을 수 있게 해주는 플러그인이다. tsc말고 babel을 통해 바로 트랜스파일링 할 수 있게 구성할 것이다.&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: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;plugin-transform-react-jsx&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원래는 react/jsx-runtime에서 jsx함수를 import하지만, babel.config.js를 통해 이 함수를 커스텀 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나같은 경우 src/core에 핵심 함수들을 작성해두었다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;babel.config.js&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 babel.config.js를 아래와 같이 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1716820669073&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-typescript'], //typescript 트랜스파일링
  plugins: [
    [
      '@babel/plugin-transform-react-jsx', //jsx 트랜스파일링
      { importSource: '@core', runtime: 'automatic' },
    ],
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;App/&amp;gt;과 같이 JSX를 호출하면 jsx()함수가 호출되게 되는데, 이 함수를 어디서 import할지를 결정해주는 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;원래는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;import { jsx, jsxs} from react/jsx-runtime&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이나 importSource를 통해 react대신에 다른 문자를 지정해줄 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위처럼 설정하면 &lt;b&gt;import { jsx, jsxs} from @core/jsx-runtime&lt;/b&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;또,&lt;b&gt; runtime&lt;/b&gt;을&lt;b&gt; automatic&lt;/b&gt;으로 설정하면 &lt;b&gt;jsx&lt;/b&gt;가 ,아니라면(&lt;b&gt;classic&lt;/b&gt;) &lt;b&gt;React.createElement&lt;/b&gt;가 호출된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 automatic으로 설정해주었다.&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;JSX&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSX는 babel을 통해 어떻게 변환될까?&lt;/p&gt;
&lt;pre id=&quot;code_1716821097064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;flex&quot;&amp;gt;내용&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 JSX가 있다면, babel은 이렇게 변환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1716821225566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jsx(&quot;div&quot;, {
    className:'flex',
    children: '내용',
});&lt;/code&gt;&lt;/pre&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;이걸 나는 어쨌던 최종적으로 HTML과 VDOM요소로 변환해주어야하기때문에, 객체형태로 관리해야한다.&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;/p&gt;
&lt;pre id=&quot;code_1716821406189&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type DefaultProps = Record&amp;lt;string, string&amp;gt;;

type Props = DefaultProps &amp;amp; {
  children?: MiniReactNode[];
};

export interface MiniReactNode {
  tagName: string;
  props: Props;
}&lt;/code&gt;&lt;/pre&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;그리고 jsx와 jsxs함수가 설계한 타입을 반환하도록 구현해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;간단하게 작성하면, 입력으로 들어온 것을 객체에 담아만 주면 되므로 매우 간단하다.&lt;/p&gt;
&lt;pre id=&quot;code_1716821500950&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const jsx = (tagName: string, props: Props) =&amp;gt; {
  return {
    tagName,
    props,
  } as MiniReactNode;
};

export const jsxs = jsx;&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;tagName이 함수인 경우는 재귀적으로 호출해서 중첩된 객체를 내보내야한다.&lt;/li&gt;
&lt;li&gt;children이 중첩 배열일 수 있다.&lt;/li&gt;
&lt;li&gt;children이 빈 문자열을 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;children이 없을수도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 경우를 고려하여 최종적으로 이렇게 짜주었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;src/core/jsx-runtime.ts&lt;/h4&gt;
&lt;pre id=&quot;code_1716821713184&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//src/core/jsx-runtime.ts

//어쨌던 children은 배열로 동작하게 만든다.
//이후에 VDOM diffing과 HTML render를 위해서는 배열로 구성하는게 용이하다.
const getChildren = (props: Props) =&amp;gt; {
  if (Array.isArray(props.children)) return [...props.children]; 
  if (props.children === undefined) return [];
  return [props.children];
};

export const jsx = (tagName: Function|string, props: Props) =&amp;gt; {
  props.children = getChildren(props);

  if (typeof tagName === 'function') return tagName(props); // tagName이 함수면 재귀적으로 호출한다.
  props.children = props.children.flat(); // 2차원 배열인 경우 flat시켜 1차원으로 만든다.
  props.children = props.children.filter((child: any) =&amp;gt; {
    if (typeof child === 'number') return true; //빈문자열을 필터링한다. 다만, child가 0인 경우를 고려한다.
    return Boolean(child); ./
  });

  return {
    tagName,
    props,
  } as MiniReactNode;
};

export const jsxs = jsx;&lt;/code&gt;&lt;/pre&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;서버쪽 JSX 테스트&lt;/h3&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;src/pages/App.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 뒤, JSX컴포넌트인 App.tsx를 pages에 만들어주자.&lt;/p&gt;
&lt;pre id=&quot;code_1716822248830&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function App() {
  return &amp;lt;div style={{ width: '100%' }}&amp;gt;테스트&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;src/app.tsx&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버의 메인 코드인 app.tsx를 작성해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1716821963895&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//src/app.tsx

import express from 'express';
import App from './pages/App';
const app = express();
const port = 3000;

app.get('/', async (req, res) =&amp;gt; {
  console.log(&amp;lt;App /&amp;gt;);
});

app.listen(port, () =&amp;gt; {
  return console.log(`Server Start at http://localhost:${port} ☺️`);
});&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l42z2/btsHDe32jmY/KZma35cFYmLeakDJ7KJEZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l42z2/btsHDe32jmY/KZma35cFYmLeakDJ7KJEZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l42z2/btsHDe32jmY/KZma35cFYmLeakDJ7KJEZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl42z2%2FbtsHDe32jmY%2FKZma35cFYmLeakDJ7KJEZk%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;362&quot; height=&quot;190&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이제 babel을 통해 dist폴더에 트랜스파일링하고, 이를 node 명령어를 통해 실행시켜볼 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1716822086828&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx babel src --extensions .ts,.tsx --out-dir dist&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;360&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gn5av/btsHECQciKL/JkoiUYpdoGFc8ArQZnFOl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gn5av/btsHECQciKL/JkoiUYpdoGFc8ArQZnFOl0/img.png&quot; data-alt=&quot;dist결과물&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gn5av/btsHECQciKL/JkoiUYpdoGFc8ArQZnFOl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGn5av%2FbtsHECQciKL%2FJkoiUYpdoGFc8ArQZnFOl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;173&quot; data-origin-width=&quot;360&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;dist결과물&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;경로 이슈&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 node dist/app.js로 실행시키면 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;589&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0RFCn/btsHDggqdMX/a9YHdtJmLN26xv869lzJs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0RFCn/btsHDggqdMX/a9YHdtJmLN26xv869lzJs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0RFCn/btsHDggqdMX/a9YHdtJmLN26xv869lzJs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0RFCn%2FbtsHDggqdMX%2Fa9YHdtJmLN26xv869lzJs0%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;872&quot; height=&quot;589&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;589&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이는@core/jsx-runtime 경로를 찾을 수 없기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Babel은 트랜스파일러라 module resolve를 해주지는 않는다. 해주려면 플러그인을 추가해주어야한다.&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;pre id=&quot;code_1716822412851&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i babel-plugin-module-resolver -D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 트랜스파일링시 경로를 바꿔줄 수 있게된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;babel.config.js&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 babel.config.js를 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1716822447056&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  presets: ['@babel/preset-env', '@babel/preset-typescript'],
  plugins: [
    [
      '@babel/plugin-transform-react-jsx',
      { importSource: '@core', runtime: 'automatic' },
    ],
    [
      'module-resolver',
      {
        alias: {
          '@core': './dist/core', //jsx-runtime의 경로 찾기 모든 ts파일에 대해
          //  트랜스파일링 하기때문에 dist의 core경로에도 jsx-runtime이 생기게 된다.
          '@': './dist', //절대경로도 지정해주었다.
        },
      },
    ],
  ],
};&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;이러면 잘 실행되어 console이 잘 찍히는 것을 확인할 수 있다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pqmDK/btsHDd461MY/4WVx5Gsc2i1se2lh4r527k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pqmDK/btsHDd461MY/4WVx5Gsc2i1se2lh4r527k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pqmDK/btsHDd461MY/4WVx5Gsc2i1se2lh4r527k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpqmDK%2FbtsHDd461MY%2F4WVx5Gsc2i1se2lh4r527k%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;637&quot; height=&quot;147&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;147&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;h4 data-ke-size=&quot;size20&quot;&gt;nodemon, package.json설정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodemon을 이렇게 설정하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1716822859856&quot; class=&quot;json&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;{
  &quot;watch&quot;: [&quot;src&quot;],
  &quot;ext&quot;: &quot;ts,tsx&quot;,
  &quot;exec&quot;: &quot;babel src --extensions .ts,.tsx --out-dir dist &amp;amp;&amp;amp; node dist/app.js&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 src내부의 ts,tsx가 변경될때마다 트랜스파일링, 서버 재실행이 이뤄질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 package.json의 dev에 달아주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1716822922218&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	...
   &quot;scripts&quot;: {
   ...
   &quot;dev&quot;: &quot;nodemon&quot;,
   },
}&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;클라이언트 JSX&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 클라이언트에서도 동일한 JSX파일을 사용할 수 있는지 확인해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서 js파일을 확인할 수 있도록 public폴더를 만들어주고, static설정을 해주었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;app.tsx&lt;/h4&gt;
&lt;pre id=&quot;code_1716823021459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const app = express();
const port = 3000;
//==================여기를 추가했다!=================
app.use(express.static('dist/public')); 
//=================================================

app.get('/', async (req, res) =&amp;gt; {
  console.log(&amp;lt;App /&amp;gt;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 이제 dist/public은 정적파일 폴더로 작동한다. localhost:3000/index.js를 찾는경우 dist/public/index.js가 생긴다는 것이다.&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;그럼 public에 index.js파일을 만들고, 클라이언트에 보내줄 html을 만들고 script를 통해 index.js를 실행시키면 된다.&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;이때 번들링에 사용될 index.tsx에서 JSX를 사용하고, 이게 최종적으로 잘 작동하는지 확인해 보면 JSX컴포넌트는 서버와 클라이언트에서 모두 호출가능한 형태인 것이 된다.&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;public/index.tsx (src외부다)&lt;/h4&gt;
&lt;pre id=&quot;code_1716823272498&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import App from '../src/pages/App';

console.log(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서와 동일하게 App을 가져와 jsx함수로 호출해주었다.&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;Rollup&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 babel로만 트랜스파일링 하게되면 @core/jsx-runtime이 public에서도 접근가능해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 설정을 해줄 수는 있겠지만, 이것보다는 번들러를 사용해서 하나의 번들을 구성해주는게 더 합리적이라 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html대신 app.tsx에서 HTML string을 보내줄 것이므로, 다른 번들러가 아니라 rollup을 통해 번들을 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1716823436503&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i -D rollup @rollup/plugin-babel @rollup/plugin-node-resolve&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;@rollup/plugin-babel :&lt;/b&gt; babel을 사용해서 JSX를 트랜스파일링 해줄 것이다. 따라서 babel을 사용할 수 있도록 구성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@rollup/plugin-node-resolve&lt;/b&gt;: 번들링시 module의 의존성문제를 해결해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b19Fpg/btsHDOqhtmF/C8NQorTahneXo8mrFKLwn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b19Fpg/btsHDOqhtmF/C8NQorTahneXo8mrFKLwn1/img.png&quot; data-alt=&quot;없으면 이런 오류가 발생한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b19Fpg/btsHDOqhtmF/C8NQorTahneXo8mrFKLwn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb19Fpg%2FbtsHDOqhtmF%2FC8NQorTahneXo8mrFKLwn1%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;724&quot; height=&quot;48&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;48&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;p data-ke-size=&quot;size16&quot;&gt;public/index.tsx를 entry로 하여 번들링하면 되므로 이렇게 구성해주었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rollup.config.js&lt;/h4&gt;
&lt;pre id=&quot;code_1716823672654&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const babel = require('@rollup/plugin-babel');
const resolve = require('@rollup/plugin-node-resolve');
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
const path = require('path');

module.exports = [
  {
    input: './public/index.tsx',
    output: {
      file: './dist/public/index.js',
      format: 'es',
    },
    plugins: [
      babel({
        babelHelpers: 'bundled',
        presets: ['@babel/preset-env', '@babel/preset-typescript'],
        plugins: [ //babel.config.js와 동일하다.
          [
            '@babel/plugin-transform-react-jsx',
            { importSource: '@core', runtime: 'automatic' },
          ],
          [
            'module-resolver',
            {
              alias: {
                '@core': path.resolve(__dirname, 'src/core'),
              },
            },
          ],
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
    ],
  },
];&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;이제 rollup으로 클라이언트 파일을 번들링해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1716823874497&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rollup --config rollup.config.js&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;353&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YzgM6/btsHDx3punv/fPaf0mKRFLvKVhvp9VAxa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YzgM6/btsHDx3punv/fPaf0mKRFLvKVhvp9VAxa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YzgM6/btsHDx3punv/fPaf0mKRFLvKVhvp9VAxa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYzgM6%2FbtsHDx3punv%2FfPaf0mKRFLvKVhvp9VAxa0%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;353&quot; height=&quot;159&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 dist에 public과 index.js가 생기는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이걸 app.tsx에서 클라이언트로 보내주기만 하면된다!&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;nodemon 수정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollup커맨드를 같이 실행시켜준다.&lt;/p&gt;
&lt;pre id=&quot;code_1716824119635&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;watch&quot;: [&quot;src&quot;],
  &quot;ext&quot;: &quot;ts,tsx&quot;,
  &quot;exec&quot;: &quot;babel src --extensions .ts,.tsx --out-dir dist &amp;amp;&amp;amp; rollup --config rollup.config.js &amp;amp;&amp;amp; node dist/app.js&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;src/app.tsx&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 '/'에 GET요청을 보내면 index.js를 실행시키는 html문자열을 생성해서 보내보자.&lt;/p&gt;
&lt;pre id=&quot;code_1716824085084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import express from 'express';
import App from './pages/App';

const app = express();
const port = 3000;
app.use(express.static('dist/public'));

app.get('/', async (req, res) =&amp;gt; {
  console.log(&amp;lt;App /&amp;gt;);
  const html = `&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;miniNext&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;miniNext!&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;script src=&quot;index.js&quot; &amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/html&amp;gt;`;
  res.send(html);
});

app.listen(port, () =&amp;gt; {
  return console.log(`Server Start at http://localhost:${port} ☺️`);
});&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;둘다 잘 작동하는 것을 확인할 수 있다.&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;1643&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ljWhv/btsHD914jUH/qaRznDuMEGzLGKvAS8KUC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ljWhv/btsHD914jUH/qaRznDuMEGzLGKvAS8KUC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ljWhv/btsHD914jUH/qaRznDuMEGzLGKvAS8KUC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FljWhv%2FbtsHD914jUH%2FqaRznDuMEGzLGKvAS8KUC0%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;1643&quot; height=&quot;721&quot; data-origin-width=&quot;1643&quot; data-origin-height=&quot;721&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;h4 data-ke-size=&quot;size20&quot;&gt;전체 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716824237434&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;learning-by-making/apps/miniNext at main &amp;middot; d0422/learning-by-making&quot; data-og-description=&quot;✅ 직접 만들면서 학습하는 과정을 담은 레포지토리. ⚛️miniReact(useState구현), JS EventLoop Visualizer, miniNext(SSR,ISR) 직접구현하고, 이를 모노레포(pnpm+turborepo) 형태로 관리합니다. - d0422/learning-by-making&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&quot; data-og-url=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xvjKl/hyV9197Baq/zoDgHDI7fzERIHXsYlOXvK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/d0422/learning-by-making/tree/main/apps/miniNext&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xvjKl/hyV9197Baq/zoDgHDI7fzERIHXsYlOXvK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;learning-by-making/apps/miniNext at main &amp;middot; d0422/learning-by-making&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;✅ 직접 만들면서 학습하는 과정을 담은 레포지토리. ⚛️miniReact(useState구현), JS EventLoop Visualizer, miniNext(SSR,ISR) 직접구현하고, 이를 모노레포(pnpm+turborepo) 형태로 관리합니다. - d0422/learning-by-making&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>만들면서 학습하기/Next를 만들면서 이해해보자</category>
      <category>JSX</category>
      <category>mininext</category>
      <category>NeXT</category>
      <category>react</category>
      <category>SSR</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/365</guid>
      <comments>https://0422.tistory.com/365#entry365comment</comments>
      <pubDate>Tue, 28 May 2024 00:38:18 +0900</pubDate>
    </item>
    <item>
      <title>Next를 만들어보면서 이해해보자 (JSX기반 MiniNext) - 무엇을, 어떻게 만들것인가?</title>
      <link>https://0422.tistory.com/364</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T39Rz/btsHDz7xU0C/fPftkDLtgD3RxZ5OVWc9n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T39Rz/btsHDz7xU0C/fPftkDLtgD3RxZ5OVWc9n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T39Rz/btsHDz7xU0C/fPftkDLtgD3RxZ5OVWc9n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT39Rz%2FbtsHDz7xU0C%2FfPftkDLtgD3RxZ5OVWc9n0%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;897&quot; height=&quot;572&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 리액트가 어떻게 동작하는지, 이해하기 위해서 리액트처럼 동작하는 어플리케이션을 만들어보았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/317&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/317&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716733680075&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;리액트를 만들면서 이해해보자 (1) - JSX와 React.createElement&quot; data-og-description=&quot;이 글을 읽기에 앞서 이 게시글은 리액트처럼 동작 하는 코드를 작성해보는 것이지 리액트와 100% 동일한 코드를 작성하려 하는 것이 아닙니다. JSX jsx는 javascript + xml의 줄임말이다. 이걸 쓰면 html&quot; data-og-host=&quot;0422.tistory.com&quot; data-og-source-url=&quot;https://0422.tistory.com/317&quot; data-og-url=&quot;https://0422.tistory.com/317&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rWdru/hyV9Txkdto/Oy4MdJG6X0ZAKDsakmTIwk/img.png?width=800&amp;amp;height=695&amp;amp;face=0_0_800_695,https://scrap.kakaocdn.net/dn/cMe4kR/hyWdr6Y3MH/Y6mthPOsP9CZC0ytZeAPzK/img.png?width=800&amp;amp;height=695&amp;amp;face=0_0_800_695,https://scrap.kakaocdn.net/dn/fcCUX/hyWdmEBwhv/IFdgr0BwaWoM0lqu7mJv8K/img.png?width=1330&amp;amp;height=471&amp;amp;face=0_0_1330_471&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/317&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0422.tistory.com/317&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rWdru/hyV9Txkdto/Oy4MdJG6X0ZAKDsakmTIwk/img.png?width=800&amp;amp;height=695&amp;amp;face=0_0_800_695,https://scrap.kakaocdn.net/dn/cMe4kR/hyWdr6Y3MH/Y6mthPOsP9CZC0ytZeAPzK/img.png?width=800&amp;amp;height=695&amp;amp;face=0_0_800_695,https://scrap.kakaocdn.net/dn/fcCUX/hyWdmEBwhv/IFdgr0BwaWoM0lqu7mJv8K/img.png?width=1330&amp;amp;height=471&amp;amp;face=0_0_1330_471');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;리액트를 만들면서 이해해보자 (1) - JSX와 React.createElement&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글을 읽기에 앞서 이 게시글은 리액트처럼 동작 하는 코드를 작성해보는 것이지 리액트와 100% 동일한 코드를 작성하려 하는 것이 아닙니다. JSX jsx는 javascript + xml의 줄임말이다. 이걸 쓰면 html&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0422.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 마찬가지다. 이번에는 Next의 SSR과 hydrate과정을 직접 만들어보면서 이해해보고자 한다.&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;배경 : SSR과 CSR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR이란 Server Side Rendering의 줄임말으로, 서버에서 사용자에게 보여줄 페이지를 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;모두 구성&lt;/b&gt;&lt;/span&gt;하여 페이지를 보여주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;모두 구성&lt;/b&gt;&lt;/span&gt;이란걸 한 번 눈여겨 볼 필요가 있다. 그럼 원래는 부분적으로 구성을 해서 보내주기도 한다는건가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다&lt;/b&gt;. React기반의 웹 어플리케이션에서 useEffect를 통해 데이터 패칭을 통해 화면을 보여주는 경우 아래와 같은 순서로 작동하게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버로 부터 HTML수신&lt;/li&gt;
&lt;li&gt;script태그를 통해 리액트 및 어플리케이션 번들파일 수신&lt;/li&gt;
&lt;li&gt;js실행하여 컴포넌트 실행(렌더링) 및 root에 컴포넌트 결과물 붙이기&lt;/li&gt;
&lt;li&gt;useEffect를 통해 데이터 패칭&lt;/li&gt;
&lt;li&gt;컴포넌트 리렌더링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 반드시 네트워크 통신이 2회 일어날 수 밖에 없고, (2번과 4번) 완벽한 페이지 하나를 보내주는게 아니라, 페이지를 조각내고, 완성되지 않은채로, &lt;b&gt;부분적&lt;/b&gt;으로 보내주는 꼴이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 첫 HTML파일은 root밖에 없거나, 혹은 아예 아무것도 없는 상태이다.&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들사이즈가 커지게되면 &lt;b&gt;3번&lt;/b&gt;과정이 일어나는데까지 시간이 걸리게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그말인즉슨, &lt;b&gt;흰 화면&lt;/b&gt;을 보게되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;물론 그렇다. 하지만 &lt;b&gt;그럴수가 없었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR이전의 웹 개발에서는 &lt;b&gt;페이지 이동&lt;/b&gt;이란 html 파일의 교체를 의미했다. 마치 책의 페이지를 넘기듯, index.html에서 login.html으로, 다른 파일을 받아오고, 화면에 그리는 것이다. 실제로 HTML은 하나의 문서(document)다.&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;b&gt;새로운 문서를 받아와야만&lt;/b&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-filename=&quot;chrome-screen-white-s.webp&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/besBie/btsHBSfJrjW/598B8KGxT5LwtedSBzzD40/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/besBie/btsHBSfJrjW/598B8KGxT5LwtedSBzzD40/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/besBie/btsHBSfJrjW/598B8KGxT5LwtedSBzzD40/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbesBie%2FbtsHBSfJrjW%2F598B8KGxT5LwtedSBzzD40%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;311&quot; data-filename=&quot;chrome-screen-white-s.webp&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;799&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그러면 유저별 HTML 파일을 전부 만들어두는 건가요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 맞춤형 HTML을 만들어놓을 수는 없는 법이다. 그래서 이전의 웹서버와 WAS들은 PHP, JSP와 같은 언어를 통해 동적으로 서버에서 HTML을 만들어서 전송해주도록 구성되었다. 지금도 많은 사이트들이 구성되어 있는데, 이러한 언어와 프레임워크들이 최초의 SSR 되시겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;76&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jv0a5/btsHCrhvcWj/1L7GmzFaBzWxyWaE7B7GdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jv0a5/btsHCrhvcWj/1L7GmzFaBzWxyWaE7B7GdK/img.png&quot; data-alt=&quot;main.do와 같은 형태로 도메인이 끝난다면, JSP, PHP으로 구성되어있다고 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jv0a5/btsHCrhvcWj/1L7GmzFaBzWxyWaE7B7GdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJv0a5%2FbtsHCrhvcWj%2F1L7GmzFaBzWxyWaE7B7GdK%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;362&quot; height=&quot;205&quot; data-origin-width=&quot;76&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;main.do와 같은 형태로 도메인이 끝난다면, JSP, PHP으로 구성되어있다고 볼 수 있다.&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;b&gt;정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전의 PHP,JSP를 사용한 SSR을 전통적인 SSR이라 이름붙이면, 이렇게 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 &lt;b&gt;데이터&lt;/b&gt;란, 유동적으로 변할 수 있는, 네트워크에 저장된 데이터를 말한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.7286%; height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 39.0311%; height: 20px;&quot;&gt;전통적 SSR&lt;/td&gt;
&lt;td style=&quot;width: 41.2402%; height: 20px;&quot;&gt;CSR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 19.7286%; height: 20px;&quot;&gt;데이터 패칭&lt;/td&gt;
&lt;td style=&quot;width: 39.0311%; height: 20px;&quot;&gt;데이터를 서버에서 받아오며, 서버에서 데이터로 만든 결과물을 클라이언트에서 받아옴&lt;/td&gt;
&lt;td style=&quot;width: 41.2402%; height: 20px;&quot;&gt;빈 HTML을 받아, 화면을 구성하는 것을 포함한 번들파일과 실제 데이터를 모두 클라이언트에서 받아옴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.7286%; height: 17px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 39.0311%; height: 17px;&quot;&gt;1번의 통신을 통해 데이터를 가져옴&lt;/td&gt;
&lt;td style=&quot;width: 41.2402%; height: 17px;&quot;&gt;한번 번들을 받아오면 정적파일을 다시 받아올 필요가 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.7286%; height: 17px;&quot;&gt;단점&lt;/td&gt;
&lt;td style=&quot;width: 39.0311%; height: 17px;&quot;&gt;페이지 이동마다 깜빡임이 있고, 하나의 페이지마다 정적파일을 한번씩 받아와야하기에 속도가 느림&lt;/td&gt;
&lt;td style=&quot;width: 41.2402%; height: 17px;&quot;&gt;번들이 커지면 첫 화면을 그리는데 많은 시간이 필요함. 총 2회의 통신을 통해 모든 데이터를 가져옴&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;Home화면에서 Feed로 이동하는 흐름을 CSR과 SSR에서 비교하면 이렇게 그릴 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu4nuM/btsHB7p9NXy/QG3RqUiPmJ4eN5pyBYhSgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu4nuM/btsHB7p9NXy/QG3RqUiPmJ4eN5pyBYhSgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu4nuM/btsHB7p9NXy/QG3RqUiPmJ4eN5pyBYhSgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu4nuM%2FbtsHB7p9NXy%2FQG3RqUiPmJ4eN5pyBYhSgK%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;1183&quot; height=&quot;625&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;625&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next: 두마리 토끼를 다 잡을수는 없을까?&lt;/h2&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;1112&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XSveX/btsHDTqmS4r/JTc9s0PGUf6sTpw0DDPVeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XSveX/btsHDTqmS4r/JTc9s0PGUf6sTpw0DDPVeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XSveX/btsHDTqmS4r/JTc9s0PGUf6sTpw0DDPVeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXSveX%2FbtsHDTqmS4r%2FJTc9s0PGUf6sTpw0DDPVeK%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;1112&quot; height=&quot;646&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;646&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Next는 이 상황에 대해 여러가지 방법을 제공한다.&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;HTML을 미리 만들어두고&lt;/b&gt;, 요청이 오면 그걸 보여줘서 초기에 흰화면을 보여주지 않는 방법 (SSG, ISR)&lt;/li&gt;
&lt;li&gt;페이지 이동시 &lt;b&gt;클라이언트에서 &lt;/b&gt;흰화면을 보여주지 않고 SPA처럼 보이게 하는 방법(&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;SSR&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;h2 data-ke-size=&quot;size26&quot;&gt;How?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 어떻게 이런게 가능할까? 나는 이번 프로젝트를 통해 이러한 고민들중 일부를 직접 해결해보고, 이후에는 Next 코드를 살펴보며 이해해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생각해보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방법을 사용하려면 간단하게 생각해도 크게 세 개가 필요하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;서버에서&lt;/b&gt; HTML을 미리 만들어 두는 기능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버에서 만든 HTML을 클라이언트에서 상호작용 가능하도록 &lt;/b&gt;만들어주는 기능 (Hydration)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트&lt;/b&gt;에서 동작할 기능&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;서버에서 동작하는것, 클라이언트에서 동작하는 것&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 동작할 코드, 클라이언트에서 동작할 코드가 있는 것을 볼 수 있는데, 생각해보면 Next를 사용하면서 한번도 따로 작성한 적이 없다. 사실 그럴거였으면 Next가 이렇게 커지지도 않았을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 내가 떠올린 답은 &lt;b&gt;JSX&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;JSX&lt;/b&gt;는 객체로 변환되고, 이를 통해 하나의 코드로 HTML을 생성할수도 있고, VDOM에 들어갈 ReactNode를 생성할 수도 있다.&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;HTML과 클라이언트의 상호작용 (Hydration)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 JSX를 떠올리니 자연스럽게 떠올랐다. 바로 React의 리렌더링을 활용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 HTML이니 DOM은 그대로 있고, 초기에 React VDOM이 생성될때, 기존 HTML을 업데이트 해주면 끝이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만들 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 나는 &lt;b&gt;JSX&lt;/b&gt;와&lt;b&gt; React like&lt;/b&gt;(이전에 만들어둔 함수들을 활용해볼 예정이다.)를 통해 Next like를 만들어 볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게는 &lt;b&gt;ServerSideProps, ServerSideRendering, Hydration&lt;/b&gt;이 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경설정부터 진행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>만들면서 학습하기/Next를 만들면서 이해해보자</category>
      <category>CSR</category>
      <category>NeXT</category>
      <category>next-like</category>
      <category>react</category>
      <category>serversiderendering</category>
      <category>SSR</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/364</guid>
      <comments>https://0422.tistory.com/364#entry364comment</comments>
      <pubDate>Mon, 27 May 2024 15:11:32 +0900</pubDate>
    </item>
    <item>
      <title>라이브러리를 만들어보자 - (3) Rollup+babel로 ESM과 CJS모두 대응되게 만들기</title>
      <link>https://0422.tistory.com/363</link>
      <description>&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;1173&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AnFoA/btsHbt7zycA/M6kmXqwlxc6yV1Gh7xW4hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AnFoA/btsHbt7zycA/M6kmXqwlxc6yV1Gh7xW4hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AnFoA/btsHbt7zycA/M6kmXqwlxc6yV1Gh7xW4hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAnFoA%2FbtsHbt7zycA%2FM6kmXqwlxc6yV1Gh7xW4hK%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;1173&quot; height=&quot;675&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글에서 TS는 내부 코드의 확장자를 변경하지 않는다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 tsconfig.json 두개로 cjs와 esm 모두 대응되게 만든다는 것은 어려운 일이라는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 시작한 이상 여기서 포기하고 싶진 않았다. 그래서 번들러로 코드를 번들링한 결과를 npm에 배포하도록 했다.&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;왜 Rollup?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Webpack과 Vite, Rollup등 다양한 번들러가 있으나 Rollup을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 라이브러리 번들링이기에 HMR이나 개발서버가 필요하지 않았다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결과물 테스트는 Jest와 Storybook을 활용하여 확인한다&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;2.&amp;nbsp; &lt;/span&gt;&lt;/span&gt;Rollup은 ES6형태로 번들링 결과를 반환해서 ESM코드에 대해서 트리쉐이킹을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 사실 2번은 웹팩도 지원하지만, &lt;a href=&quot;https://moiva.io/?npm=rollup+webpack&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rollup이 빌드속도면에서 더 빠르기도하고&lt;/a&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;Rollup&amp;nbsp; 구성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Rollup을 전역으로 깔아주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715405659652&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install --global rollup&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;Rollup은 rollup.config.js를 통해 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 특징이라면, 한번 번들링을 할때 여러번의 번들링을 할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollup.config.js에서 배열 형태로 객체를 구성하여 가능하다.&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;ESM, CJS 번들링 구성&lt;/h3&gt;
&lt;pre id=&quot;code_1715405965468&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default [
  {
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js', //결과파일 경로
      format: 'es', //ESM으로 번들링
    },
  },
  {
    input: './src/index.ts',
    output: {
      file: './dist/cjs/index.cjs', //결과파일 경로
      format: 'cjs', //CJS로 번들링
    },
  },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 번들링 구성을 해주었다. 각 객체마다 한번씩 번들링이되어서 결과물이 dist/esm, dist/cjs 두개로 나오게 되는 것이다.&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;그런데 이걸 실행(rollup --config rollup.config.js)시키면 오류가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuLS4h/btsHnVvclWR/6FkKMjcxrfTKlAyMnmru31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuLS4h/btsHnVvclWR/6FkKMjcxrfTKlAyMnmru31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuLS4h/btsHnVvclWR/6FkKMjcxrfTKlAyMnmru31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuLS4h%2FbtsHnVvclWR%2F6FkKMjcxrfTKlAyMnmru31%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;1397&quot; height=&quot;213&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.ts를 읽는중에 ./useDragIndexCarousel/useDragIndexCarousel을 가져오는걸 실패했기때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 왜 실패했는가? ts파일이라 확장자가 생략되어서 그렇다.&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://www.npmjs.com/package/@rollup/plugin-node-resolve&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@rollup/plugin-node-resolve&lt;/a&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1715412796541&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i @rollup/plugin-node-resolve -D&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;rollup.config.js&lt;/p&gt;
&lt;pre id=&quot;code_1715412863719&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import resolve from '@rollup/plugin-node-resolve';

const extensions = ['.js', '.jsx', '.ts', '.tsx'];
export default [
  {
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js',
      format: 'es',
    },
    plugins: [
      resolve({ 
        extensions, //모듈 확장자를 읽어오고, 추가로 외부 모듈도 읽어올 수 있게 된다.
      }),
    ],
  },
  ...
];&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;그리고 다시 rollup을 실행시키면... 다시 오류가 발생한다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z135Z/btsHl233O5g/u30kX2fMRBKnQtlAPusbjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z135Z/btsHl233O5g/u30kX2fMRBKnQtlAPusbjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z135Z/btsHl233O5g/u30kX2fMRBKnQtlAPusbjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ135Z%2FbtsHl233O5g%2Fu30kX2fMRBKnQtlAPusbjk%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;1529&quot; height=&quot;266&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 진짜 ts문법을 그대로 읽으려고 했기때문이다.&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;그렇다면 ts를 읽어줄 필요가 있다.&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;rollup에서 typescript을 읽을 수 있도록하기&lt;/li&gt;
&lt;li&gt;다른 트랜스파일러를 통해 typescript를 읽고, 그걸 rollup이 번들링하도록 하기&lt;/li&gt;
&lt;li&gt;tsc한 뒤에 rollup하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번은 rollup에서 plugin을 제공하기때문에 굳이 그렇게 구성할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 처음에는 1번을 택해서 &lt;a href=&quot;https://www.npmjs.com/package/@rollup/plugin-typescript&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@rollup/plugin-typescript&lt;/a&gt;를 사용해서 구성했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715406626682&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import typescript from '@rollup/plugin-typescript';
export default [
  {
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js', //결과파일 경로
      format: 'es', //ESM으로 번들링
      plugins: [typescript()] //ts 플러그인 적용
    },
  },
  {
    input: './src/index.ts',
    output: {
      file: './dist/cjs/index.cjs', //결과파일 경로
      format: 'cjs', //CJS로 번들링
      plugins: [typescript()] //ts 플러그인 적용
    },
  },
];&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;하지만, 이왕 ESM과 CJS를 고려하는거, 다양한 브라우저도 다 고려하여 구성하기로 마음먹게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 babel을 통해 ts파일을 js로 트랜스파일링 하도록 처리해주었다. 어떤 상황에서든 라이브러리를 사용할 수 있었으면 하는 마음이다.&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;따라서 babel설정을 해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715411772270&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript @rollup/plugin-babel&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plugin설정을 통해 babel을 설정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715411803267&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import babel from '@rollup/plugin-babel';

const extensions = ['.js', '.jsx', '.ts', '.tsx'];

export default [{
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js',
      format: 'es',
    },
    plugins: [
      babel({
        babelHelpers: 'bundled',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
        extensions,
      }),
    ],
  },
  ...
  ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 babel이 ts를 js로 트랜스파일링한 후에, rollup이 번들링을 하게된다.&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;d.ts도 각 모듈 시스템에 대해 만들어 주기위해 dts plugin을 설치해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715413158574&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i rollup-plugin-dts -D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 peerDependencies를 빌드에 포함시키지 않기위해 peerDepsExternal도 설치해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1715413216460&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i rollup-plugin-peer-deps-external -D&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;rollup.config.js&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 구성해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서대로 esm, esm에 대한 index.d.ts, cjs, cjs에 대한 index.d.cts이다.&lt;/p&gt;
&lt;pre id=&quot;code_1715413274135&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import babel from '@rollup/plugin-babel';
import { dts } from 'rollup-plugin-dts';
import resolve from '@rollup/plugin-node-resolve';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

const extensions = ['.js', '.jsx', '.ts', '.tsx'];
export default [
  {
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js',
      format: 'es',
    },
    plugins: [
      peerDepsExternal(),
      babel({
        babelHelpers: 'bundled',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
    ],
  },
  {
    input: './src/index.ts',
    output: [{ file: 'dist/esm/index.d.ts', format: 'es' }],
    plugins: [dts()],
  },
  {
    input: './src/index.ts',
    output: {
      file: './dist/cjs/index.cjs',
      format: 'cjs',
    },
    plugins: [
      peerDepsExternal(),
      babel({
        babelHelpers: 'bundled',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
    ],
  },
  {
    input: './src/index.ts',
    output: [{ file: 'dist/cjs/index.d.cts', format: 'cjs' }],
    plugins: [dts()],
  },
];&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;압축으로 용량줄이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들 사이즈를 최대한 줄이기 위해 압축도 해주었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;uglify vs babel-minify vs terser&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;압축도 여러가지 선택지가 있었는데 uglify의 후속버전이 terser이고, rollup과 함께 사용하기때문에 추가적인 설정이 필요하지 않은 terser를 선택하게되었다. (babel-minify는 babel설정 파일이 또 필요한 것 같아 보인다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1715413432759&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i @rollup/plugin-terser -D&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;최종 rollup.config.js&lt;/h3&gt;
&lt;pre id=&quot;code_1715413452746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import babel from '@rollup/plugin-babel';
import { dts } from 'rollup-plugin-dts';
import resolve from '@rollup/plugin-node-resolve';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import terser from '@rollup/plugin-terser';

const extensions = ['.js', '.jsx', '.ts', '.tsx'];
export default [
  {
    input: './src/index.ts',
    output: {
      file: './dist/esm/index.js',
      format: 'es',
    },
    plugins: [
      peerDepsExternal(),
      babel({
        babelHelpers: 'bundled',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
      terser(),
    ],
  },
  {
    input: './src/index.ts',
    output: [{ file: 'dist/esm/index.d.ts', format: 'es' }],
    plugins: [dts()],
  },
  {
    input: './src/index.ts',
    output: {
      file: './dist/cjs/index.cjs',
      format: 'cjs',
    },
    plugins: [
      peerDepsExternal(),
      babel({
        babelHelpers: 'bundled',
        presets: [
          '@babel/preset-env',
          '@babel/preset-react',
          '@babel/preset-typescript',
        ],
        extensions,
      }),
      resolve({
        extensions,
      }),
      terser(),
    ],
  },
  {
    input: './src/index.ts',
    output: [{ file: 'dist/cjs/index.d.cts', format: 'cjs' }],
    plugins: [dts()],
  },
];&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;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;terser 적용전후&lt;/h4&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/bMAc6l/btsHnkWGAiJ/jYg7ZWlX9mtwjqZTSj5zy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMAc6l/btsHnkWGAiJ/jYg7ZWlX9mtwjqZTSj5zy0/img.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;399&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.573%; margin-right: 10px;&quot; data-widthpercent=&quot;50.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMAc6l/btsHnkWGAiJ/jYg7ZWlX9mtwjqZTSj5zy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMAc6l%2FbtsHnkWGAiJ%2FjYg7ZWlX9mtwjqZTSj5zy0%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;535&quot; height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c79Xmm/btsHmsg7uiO/yPJr27FRFooucE6lj6OAZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c79Xmm/btsHmsg7uiO/yPJr27FRFooucE6lj6OAZ0/img.png&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;400&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.2642%;&quot; data-widthpercent=&quot;49.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c79Xmm/btsHmsg7uiO/yPJr27FRFooucE6lj6OAZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc79Xmm%2FbtsHmsg7uiO%2FyPJr27FRFooucE6lj6OAZ0%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;533&quot; height=&quot;400&quot;/&gt;&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;42.2kb에서 32.1kb로 압축된 것을 확인할 수 있다!&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/GbVLA/btsHnX7EXPc/Ahw24yIfZeJWaaUMfkXFd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GbVLA/btsHnX7EXPc/Ahw24yIfZeJWaaUMfkXFd1/img.png&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;479&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.3597%; margin-right: 10px;&quot; data-widthpercent=&quot;48.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GbVLA/btsHnX7EXPc/Ahw24yIfZeJWaaUMfkXFd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGbVLA%2FbtsHnX7EXPc%2FAhw24yIfZeJWaaUMfkXFd1%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;672&quot; height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OoHNG/btsHl0LSmCW/iQSBHUJUp3vkGffCUlukKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OoHNG/btsHl0LSmCW/iQSBHUJUp3vkGffCUlukKk/img.png&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;491&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.4775%;&quot; data-widthpercent=&quot;51.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OoHNG/btsHl0LSmCW/iQSBHUJUp3vkGffCUlukKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOoHNG%2FbtsHl0LSmCW%2FiQSBHUJUp3vkGffCUlukKk%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;719&quot; height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;https://bundlephobia.com/package/@rapiders/react-hooks@1.2.2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bundlephobia에서 비교한 것은 거의 차이가 나지 않는다.&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;h2 data-ke-size=&quot;size26&quot;&gt;후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤나 쉽지는 않았지만, 그리고 React-hooks라 보통 번들러와 함께 사용될 것이기때문에 별로 중요하지는 않지만... 어쨌던 목표했던 CJS, ESM을 모두 지원하는 라이브러리를 만들어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게 사용하는 것들이 전혀 당연하지 않다는 것을 다시 한 번 알게된 경험이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정에서 typescript의 모듈 시스템을 깊게 학습할 수 있었고,&amp;nbsp; rollup의 구성과 다양한 플러그인, 번들사이즈와 트리쉐이킹 등 다양한 것들을 학습할 수 있어 뜻깊었다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>Babel</category>
      <category>CJS</category>
      <category>ESM</category>
      <category>rollup</category>
      <category>TS</category>
      <category>라이브러리</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/363</guid>
      <comments>https://0422.tistory.com/363#entry363comment</comments>
      <pubDate>Sat, 11 May 2024 16:52:31 +0900</pubDate>
    </item>
    <item>
      <title>typescript 컴파일러와 모듈 시스템에 대한 이해</title>
      <link>https://0422.tistory.com/362</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/361&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 이해하기 위해서는 &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/modules/theory.html&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714717343658&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;#6 Translate module-reference/Theory.md to korean by d0422 &amp;middot; Pull Request #224 &amp;middot; microsoft/TypeScript-Website-Localizations&quot; data-og-description=&quot;en Theory 리뷰 요청합니다 : @yeonjuan @bumkeyy ref #6 감사합니다. 처음 해보는거라 잘 모르는 부분이 많습니다...! 혹시 이상한 부분이나 잘못된 부분이 있다면 가감없이 말씀해주세요!!!&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&quot; data-og-url=&quot;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QsmMk/hyVZeUW8ii/bueFM5kMYwQSeRkMoGXK91/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/microsoft/TypeScript-Website-Localizations/pull/224&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QsmMk/hyVZeUW8ii/bueFM5kMYwQSeRkMoGXK91/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;#6 Translate module-reference/Theory.md to korean by d0422 &amp;middot; Pull Request #224 &amp;middot; microsoft/TypeScript-Website-Localizations&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;en Theory 리뷰 요청합니다 : @yeonjuan @bumkeyy ref #6 감사합니다. 처음 해보는거라 잘 모르는 부분이 많습니다...! 혹시 이상한 부분이나 잘못된 부분이 있다면 가감없이 말씀해주세요!!!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 위 PR의 번역파일에 추가 설명을 더하고, 내용을 요약한 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;typescript 컴파일러는 모듈에 대해 도대체 무슨 일을 하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typescript의 주요목표는 &lt;b&gt;컴파일시 런타임 오류를 미리 파악하여 방지&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 생각해보면 typescript는 컴파일타임에&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;p data-ke-size=&quot;size16&quot;&gt;그래서 모듈이 포함된 코드를 프로젝트에서 작성하게되면, ts컴파일러가 그냥 코드를 변환하는 작업을 수행하기에는 모호한점들이 발생하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 아래 코드를보자.&lt;/p&gt;
&lt;pre id=&quot;code_1714717710589&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sayHello from &quot;greetings&quot;;
sayHello(&quot;world&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;p data-ke-size=&quot;size16&quot;&gt;1. greeting에 대한 타입스크립트 파일을 직접 로드할까(greeting.ts)? 아니면 TS컴파일러가 생성한 결과물 JS파일을 불러올까(greeting.js)?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. greeting이라는건 파일이름인가? 아니면 디렉토리+파일의 별칭인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. greeting 모듈은 ESM일까? CJS일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이 코드 자체는 ESM으로 변환시킬까? 아니면 CJS로 변환시킬까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.greeting모듈이 분석됐을때, greeting의 어느부분이 어떻게 sayHello로 바인딩되는가?&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;이건 비단 typescript혼자서 결정하거나 대답할 수 있는게 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 결국 사용하는게 node.js인지, 번들러인지, 혹은 브라우저인지에 따라 이게 달라지게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 docs는 트랜스파일링된 코드를 최종적으로 사용하는 곳을 &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 data-ke-size=&quot;size23&quot;&gt;호스트의 종류&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트랜스 파일링된 결과 코드가 Node.js와 같은 런타임에서 직접 실행되는 경우 &lt;b&gt;런타임이 호스트&lt;/b&gt;가 된다.&lt;/li&gt;
&lt;li&gt;런타임이 TypeScript 파일을 변환업이 바로 사용하거나,&quot;트랜스파일링된 JS 코드&quot;가 없는 경우에도 &lt;b&gt;런타임이 호스트&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;번들러가 TypeScript 입력 또는 출력을 소비하고 번들을 생성하는 경우,&amp;nbsp; &lt;b&gt;번들러가 호스트&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;다른 트랜스파일러, 옵티마이저 또는 포맷터가 TypeScript의 트랜스파일링 결과를 실행하는경우에는&lt;b&gt; imports와 exports를 바꾸지 않는한 TypeScript가 신경 쓰는 호스트가 아니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TypeScript 컴파일러 자체&lt;/b&gt;는&amp;nbsp;다른&amp;nbsp;호스트를&amp;nbsp;모델링하는&amp;nbsp;것&amp;nbsp;외에&amp;nbsp;모듈과&amp;nbsp;관련된&amp;nbsp;어떠한&amp;nbsp;동작도&amp;nbsp;제공하지&amp;nbsp;않으므로&amp;nbsp;&lt;b&gt;호스트가&amp;nbsp;아니다&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;typescript 컴파일러가 하는 일&lt;/h3&gt;
&lt;pre id=&quot;code_1714736039838&quot; class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import sayHello from &quot;greetings&quot;;
sayHello(&quot;world&quot;);&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;파일을 유효한 모듈 형식(ESM, CJS)으로 변환해준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;결과 코드에서 import가 실제로 잘 되는지 확인한다. (import한 코드 조각이 유효한지)&lt;/li&gt;
&lt;li&gt;import한 요소의 타입을 할당한다. (ex) sayHello에는 string타입의 인자가 하나 들어오는구나!)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 게시글 및 docs에서는 module관련한 내용만 다루기때문에 3번에 대한 내용은 다루지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2번을 알아가보자.&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;1. typescript 컴파일러는 파일을 유효한 모듈 형식(ESM, CJS)으로 변환해준다.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tsconfig의 module필드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 기본적으로 tsconfig의 module옵션을 통해 typescript컴파일러에게 어떤 형태의 모듈로 ts코드를 js로 트랜스파일링할 것인지를 변경시킬 수 있다.&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;이 module 옵션은 ESM이냐 CJS냐, ESM이면 몇 버전이냐만을 결정하는것 뿐만 아니라, moduleResolution방식에도 영향을 미친다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 module옵션이 CommonJS인데, moduleResolution을 Bundler로 설정할 수는 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OobU1/btsHad5UWi5/vkMK9fKRwB78KWMrWAEiFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OobU1/btsHad5UWi5/vkMK9fKRwB78KWMrWAEiFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OobU1/btsHad5UWi5/vkMK9fKRwB78KWMrWAEiFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOobU1%2FbtsHad5UWi5%2FvkMK9fKRwB78KWMrWAEiFk%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;1119&quot; height=&quot;315&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;315&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;또한 설정한 module버전에따라 ESM의 경우에는 top-level await와 같은 기능에도 영향을 미친다.&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;typescrit 컴파일러의 모듈 종류 자동 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;typescript 컴파일러는 tsconfig뿐만 아니라 해당 파일의 파일확장자와 package.json을 읽어서 모듈 종류를 자동으로 감지하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 NodeJS와 동일한 방식으로 이뤄지므로, NodeJS의 모듈 종류 감지 방법을 먼저 이해해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NodeJS는 ESM과 CJS를 모두 이해한다. 하지만 어떻게 구성하냐에 따라 오류가 발생할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. package.json의 type필드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json의 type필드를 module로 설정하면 근처 파일은 ESM으로 인식한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 아니라면, 즉, type필드의 값이 module이 아니거나 비어있다면 CJS가 되는 것이다.&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;2. .cjs, .mjs으로 설정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째는 package.json보다 우선순위가 높다. 바로 파일확장자를 통해 설정하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 확장자를 cjs로 설정하거나, mjs로 설정하면 각각 CJS, ESM모듈로 해석한다.&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;typescript 컴파일러는?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러는 tsconfig의 module이 &lt;b&gt;node16이나 nodenext&lt;/b&gt;로 되어있을때, package.json의 type필드를 확인해서 모듈 종류를 파악한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 파일 확장자가 .mts,.cjs 인경우 NodeJS와 동일하게 ESM, CJS로 이해하며 각각 트랜스파일링시 확장자가 .mjs, .cjs로 변경되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. typescript 컴파일러는 결과 코드에서 import가 실제로 잘 되는지 확인한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 가장 핵심이다. 얼핏 생각하기로는 ts컴파일러는 타입체크와 모듈 종류를 바꾸는 것에 특화되어있다고 오해하기가 쉬운데, 되돌아보면 import를 잘못하는 경우, tsc를 했을때 ts는 바로 화를 내준다.&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;ts컴파일러는 모듈이 실제로 있는지, import가 잘되는 것인지를 확인해주는 것이다.&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;697&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1cqpO/btsHca0BpY2/1Ya0iWA6SkyuggZWhCYZR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1cqpO/btsHca0BpY2/1Ya0iWA6SkyuggZWhCYZR1/img.png&quot; data-alt=&quot;예시는 직접 tsc를 실행한 것은 아니지만, 화를 낸다는 것을 확인할 수는 있어서 가져왓다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1cqpO/btsHca0BpY2/1Ya0iWA6SkyuggZWhCYZR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1cqpO%2FbtsHca0BpY2%2F1Ya0iWA6SkyuggZWhCYZR1%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;697&quot; height=&quot;230&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시는 직접 tsc를 실행한 것은 아니지만, 화를 낸다는 것을 확인할 수는 있어서 가져왓다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러 옵션의 module은 import, export 등을 CJS, ESM과 같은 다양한 모듈 종류로 변환할 수 있지만, &lt;b&gt;모듈의 경로, 이름, 확장자를 변환하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 아래와 같은 파일이 있다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1714738402021&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { add } from &quot;./math.mjs&quot;;
add(1, 2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 CJS로 변환해버리면 이렇게 변환이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1714738416084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const math_1 = require(&quot;./math.mjs&quot;);
math_1.add(1, 2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈의 식별자(이름, 경로, 확장자)가 유지된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 모듈의 식별자는 컴파일러 옵션에 관계없이 항상 입력파일, 즉 ts파일에서 사용된 것 과 동일하다.&lt;/p&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 변환해주지 않는가?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모듈을 실제로 import하여 소비하는 것인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;호스트 &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈을 실제로 import하여 소비하는 것인 &lt;b&gt;호스트&lt;/b&gt;이기 때문이다. 타입스크립트는 확인만 해줄 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ts코드 -&amp;gt; js코드 -&amp;gt; 소비의 흐름인데, ts코드에서 js코드로 넘어갈때 모듈의 식별자가 바뀌어버린다면, 최종 소비자는 예상치 못하게 오류를 맞이할 수 있고, 이는 타입스크립트의 제작 의도와는 정말 정 반대의 결과가 되어버린다.&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;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;ts는 어떻게 import &quot;모듈&quot;이 정상적인 구문인지 확인하는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 코드가 있다고 하자&lt;/p&gt;
&lt;pre id=&quot;code_1714739404277&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Filename: math.ts
export function add(a: number, b: number) {
  return a + b;
}
// @Filename: main.ts
import { add } from &quot;./math&quot;;
add(1, 2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 ./math가 유효한지 확인하는 방식을 생각해볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TvhDr/btsHa05Zzmx/GfZ3ZZNfVo8Eol4qdB3F51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TvhDr/btsHa05Zzmx/GfZ3ZZNfVo8Eol4qdB3F51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TvhDr/btsHa05Zzmx/GfZ3ZZNfVo8Eol4qdB3F51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTvhDr%2FbtsHa05Zzmx%2FGfZ3ZZNfVo8Eol4qdB3F51%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;934&quot; height=&quot;379&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;379&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;961&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P3c84/btsHbDJd7lu/LoK3j5FSQtjH1pgnmZMHn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P3c84/btsHbDJd7lu/LoK3j5FSQtjH1pgnmZMHn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P3c84/btsHbDJd7lu/LoK3j5FSQtjH1pgnmZMHn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP3c84%2FbtsHbDJd7lu%2FLoK3j5FSQtjH1pgnmZMHn0%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;961&quot; height=&quot;403&quot; data-origin-width=&quot;961&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;실제로는 변환된&lt;b&gt; js파일을 통해 확인하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이해하면 이런 경우도 이해할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1714739583666&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @moduleResolution: node16
// @rootDir: src
// @outDir: dist
// @Filename: src/math.mts
export function add(a: number, b: number) {
  return a + b;
}
// @Filename: src/main.mts
import { add } from &quot;./math.mjs&quot;;
add(1, 2);&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;main.mts파일에서 add함수를 math.&lt;b&gt;mjs&lt;/b&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;1309&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CnnJ1/btsHb6RIzVn/cn5i5gAmmHggGjSwGCIGyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CnnJ1/btsHb6RIzVn/cn5i5gAmmHggGjSwGCIGyk/img.png&quot; data-alt=&quot;이런식으로 작동하는 것이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CnnJ1/btsHb6RIzVn/cn5i5gAmmHggGjSwGCIGyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCnnJ1%2FbtsHb6RIzVn%2Fcn5i5gAmmHggGjSwGCIGyk%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;1309&quot; height=&quot;462&quot; data-origin-width=&quot;1309&quot; data-origin-height=&quot;462&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;자 그럼 이 중간에 파일의 확장자를 마구 바꿔버린다면?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ts컴파일러가 제대로 import구문의 유효성을 검사할 수 없게된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 확장자를 바꾸지 않는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼 라이브러리 코드는 어떻게 유효성 검사함?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 코드는 tsc를 할수가 없다. 보통은 ts파일형태로 배포하지 않기때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 ts컴파일러는 어떻게&amp;nbsp; 라이브러리 코드에 대한 import문이 유효한지 확인할 수 있을까?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;d.ts가 등장할때.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금이 바로 d.ts가 활약할 때다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;d.ts는 수동으로 쓰기도하지만 보통은 tsc --declaration을 통해 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 명령어를 수행시키면 js파일과 d.ts파일이 하나 나오게된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCO7rv/btsHbpEhUqF/jxsfpqUleYeInn0YKKCr7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCO7rv/btsHbpEhUqF/jxsfpqUleYeInn0YKKCr7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCO7rv/btsHbpEhUqF/jxsfpqUleYeInn0YKKCr7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCO7rv%2FbtsHbpEhUqF%2FjxsfpqUleYeInn0YKKCr7k%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;803&quot; height=&quot;559&quot; data-origin-width=&quot;803&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 만들어지는 d.ts파일이기때문에 d.ts파일이 있으면 typescript 컴파일러는 해당 d.ts에 해당하는 .js파일이 있다고 가정해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 module resolution에서 typescript는&amp;nbsp;&lt;b&gt; d.ts파일을 찾고, 이걸 먼저 찾으면 더이상 .js파일을 찾지않는다.&lt;/b&gt; 왜냐하면 둘다 tsc를 통해 만들어지기 때문이다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 제작자라면, 어찌됐던 많은 곳에서 작동되기를 원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;moduleResolution을 bundler나 node10, esnext으로 설정해버리면, 확장자가 안붙는다. (ts 컴파일러는 확장자를 변환하지 않는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 번들러가 없으면 작동하지 않는것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 tsdocs에서는 moduleResolution을 nodenext로 설정하는걸 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 라이브러리 코드에 확장자를 붙여주면, 확장자가 들어가기때문에&amp;nbsp; 번들러가 없는 nodeJS환경에서도 잘 작동되는 것을 확인할 수 있다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 만들다가 컴파일 결과에 도대체 왜 확장자가 안붙는건가? 고민하다가 여기까지 오게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤나 딥다이브해서 ts의 컴파일러에 대한 이해도를 높힐 수 있었고, 번역해서 최초로 오픈소스에 PR도 해볼 수 있어서 굉장히 시간은 많이 들었지만, 그만큼 많이 성장할 수 있었다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>TS</category>
      <category>모듈</category>
      <category>컴파일러</category>
      <category>확장자</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/362</guid>
      <comments>https://0422.tistory.com/362#entry362comment</comments>
      <pubDate>Fri, 3 May 2024 21:50:16 +0900</pubDate>
    </item>
    <item>
      <title>라이브러리를 만들어 배포해보자 - (2) tsconfig와 ESM,CJS</title>
      <link>https://0422.tistory.com/361</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;677&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6DicO/btsG3NsBDjY/z3qEDSTKfxE7A2njGL0ffk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6DicO/btsG3NsBDjY/z3qEDSTKfxE7A2njGL0ffk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6DicO/btsG3NsBDjY/z3qEDSTKfxE7A2njGL0ffk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6DicO%2FbtsG3NsBDjY%2Fz3qEDSTKfxE7A2njGL0ffk%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;1209&quot; height=&quot;677&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글에서 라이브러리를 tsc만으로 컴파일해서 배포했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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/bdA1ju/btsG5r21Hds/WUIsedXDbtY5ONDK4sh5b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdA1ju/btsG5r21Hds/WUIsedXDbtY5ONDK4sh5b1/img.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;439&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.5032%; margin-right: 10px;&quot; data-widthpercent=&quot;59.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdA1ju/btsG5r21Hds/WUIsedXDbtY5ONDK4sh5b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdA1ju%2FbtsG5r21Hds%2FWUIsedXDbtY5ONDK4sh5b1%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;1260&quot; height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdSMr9/btsG5nfdwKB/snzZPqa8Z8PxIP4UhqMYRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdSMr9/btsG5nfdwKB/snzZPqa8Z8PxIP4UhqMYRk/img.png&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;377&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;40.81&quot; style=&quot;width: 40.334%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdSMr9/btsG5nfdwKB/snzZPqa8Z8PxIP4UhqMYRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdSMr9%2FbtsG5nfdwKB%2FsnzZPqa8Z8PxIP4UhqMYRk%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;746&quot; height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;번들러 없이는 아무것도 작동되지 않는...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CJS방식으로 require했을때도, ESM으로 import했을때도 잘 작동되지 않는다.&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;h2 data-ke-size=&quot;size26&quot;&gt;모듈 시스템&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에, 모듈 시스템 중 CJS와 ESM에 대해 살펴보자.&lt;/p&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CJS&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;require와 module.exports를 통해 동작한다. 동기적으로 실행된다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, 메인스레드를 블로킹한다. 즉, 모듈의 코드 실행까지 다음 진행이 차단되는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;exports되는 항목을 마치 object처럼 취급한다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같은 코드가 있다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1714575385968&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;//module.js
let num=20;
function setNum(n){
	num=n;
}

function getNum(){
	return num;
}

module.exports={
	num,
    setNum,
    getNum
}


//main.js
let {num, setNum} =require(&quot;./module.js&quot;);
let {num:num2} =require(&quot;./module.js);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위코드는 CJS에서 아래코드와 동일하게 동작한다.&lt;/p&gt;
&lt;pre id=&quot;code_1714575385970&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;let num=20;
function setNum(n){
	num=n;
}

function getNum(){
	return num;
}

let obj={
	num,
    setNum,
    getNum
}

let {num,setNum}={...obj};
let {num:num2}={...obj};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;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;ESM&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;import, export를 통해 동작하며, 비동기적으로 실행될 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비동기적 실행이란, 가져온 스크립트를 바로 실행하는게 아니라 import구문을 찾아서 스크립트를 파싱한다는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ESM은 구성 -&amp;gt; 인스턴스화 -&amp;gt; 평가의 단계를 거쳐 수행된다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;구성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;엔트리를 기준으로 import를 쭉 타고 올라가며 트리를 만들어낸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHmHf2/btsG499FqEd/vcu5GtMjUt0WOYkWnXwFQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHmHf2/btsG499FqEd/vcu5GtMjUt0WOYkWnXwFQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHmHf2/btsG499FqEd/vcu5GtMjUt0WOYkWnXwFQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHmHf2%2FbtsG499FqEd%2Fvcu5GtMjUt0WOYkWnXwFQK%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;302&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; 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;500&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0eh6e/btsG6cLaAOR/OmwUGsWKBMhdZaJCvDYXCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0eh6e/btsG6cLaAOR/OmwUGsWKBMhdZaJCvDYXCK/img.png&quot; data-alt=&quot;모듈 레코드에 대한 그림&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0eh6e/btsG6cLaAOR/OmwUGsWKBMhdZaJCvDYXCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0eh6e%2FbtsG6cLaAOR%2FOmwUGsWKBMhdZaJCvDYXCK%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;287&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모듈 레코드에 대한 그림&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요 과정에서 fetch url과 모듈 레코드를 매핑한다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래야 한번 fetch가 완료된 모듈을 다시 다운로드 받지 않기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인스턴스화&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;export되는 값을 저장할 메모리 공간을 찾아놓고, export하는 코드와 import하는 코드가 모두 같은 메모리 공간을 가리키도록 연결한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chNvRY/btsG5HdKMAs/qKvb1l5Bn8ADj0Vr9sH9ck/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chNvRY/btsG5HdKMAs/qKvb1l5Bn8ADj0Vr9sH9ck/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chNvRY/btsG5HdKMAs/qKvb1l5Bn8ADj0Vr9sH9ck/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchNvRY%2FbtsG5HdKMAs%2FqKvb1l5Bn8ADj0Vr9sH9ck%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;258&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; 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;853&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HDTXq/btsG4xprJhl/FZH2hkWKr7hgeplTIuvcb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HDTXq/btsG4xprJhl/FZH2hkWKr7hgeplTIuvcb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HDTXq/btsG4xprJhl/FZH2hkWKr7hgeplTIuvcb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHDTXq%2FbtsG4xprJhl%2FFZH2hkWKr7hgeplTIuvcb1%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;726&quot; height=&quot;336&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;평가&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 위 그림에서 검정색 부분에 1을 채워 넣는다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, import로 접근한 메모리 값을 바꿀수는 없다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; 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;/p&gt;
&lt;p style=&quot;text-align: start;&quot; 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;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;왜?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 돌아와서 이전에 설정한 것들을 기반으로 안되는 이유를 분석해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;package.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 이전 게시글에서 살펴본 package.json을 다시 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1714533577200&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@rapiders/react-hooks&quot;, //패키지의 이름
  &quot;version&quot;: &quot;0.0.9&quot;,
  &quot;description&quot;: &quot;react hooks for fast development&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;, //entry point
  &quot;types&quot;: &quot;dist/index.d.ts&quot;, //entry point의 타입
  &quot;homepage&quot;: &quot;https://github.com/d0422/react-hooks&quot;,
  &quot;scripts&quot;: {
    ...
  },
  &quot;keywords&quot;: [
	...
  ],
  &quot;author&quot;: &quot;d0422 &amp;lt;rlfehd2013@naver.com&amp;gt;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;peerDependencies&quot;: { 
	...
  },
  &quot;devDependencies&quot;: {
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 보면 type 필드가 빠져있는 것을 확인할 수 있다.&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;type 필드가 빠지게 되면 프로젝트는 CJS를 따르게된다.&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;1550&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ySBMr/btsG3saSiMP/99f1eFf53sWF31jtUNpzE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ySBMr/btsG3saSiMP/99f1eFf53sWF31jtUNpzE1/img.png&quot; data-alt=&quot;https://nodejs.org/api/packages.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ySBMr/btsG3saSiMP/99f1eFf53sWF31jtUNpzE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FySBMr%2FbtsG3saSiMP%2F99f1eFf53sWF31jtUNpzE1%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;1550&quot; height=&quot;145&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nodejs.org/api/packages.html&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;npm배포된 결과, 즉 다운로드되는 라이브러리도 package.json을 통해 엔트리포인트에 접근하기 때문에&lt;b&gt; dist 내부의 모듈들은 CJS방식을 따라야한다.&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;1029&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDvxt2/btsG3zOx5T6/pVr3yeaK8tkkZNQxxGrKuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDvxt2/btsG3zOx5T6/pVr3yeaK8tkkZNQxxGrKuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDvxt2/btsG3zOx5T6/pVr3yeaK8tkkZNQxxGrKuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDvxt2%2FbtsG3zOx5T6%2FpVr3yeaK8tkkZNQxxGrKuk%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;1029&quot; height=&quot;253&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;253&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;dist내부의 index.js를 보면 &lt;b&gt;ESM방식으로 트랜스파일링 됐다는 것&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;978&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lYYmP/btsG5t0UEJo/ASzVHLcGbS53mZ1NAtgQkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lYYmP/btsG5t0UEJo/ASzVHLcGbS53mZ1NAtgQkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lYYmP/btsG5t0UEJo/ASzVHLcGbS53mZ1NAtgQkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlYYmP%2FbtsG5t0UEJo%2FASzVHLcGbS53mZ1NAtgQkK%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;978&quot; height=&quot;401&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원인은 ts파일이 ESM방식으로 트랜스파일링됐기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 파악&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아하...! 그럼 이제 현상을 정리할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;라이브러리를 CJS 형태로 읽으라고 package.json에 적어둠&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;근데 라이브러리 트랜스파일링을 ESM으로 해둠&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;따라서 CJS로도, ESM으로도 읽을 수 없는 코드가 되어버림&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결을 위한 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면.... 이젠 tsconfig.json을 다시 살펴볼때이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;tsconfig.json&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig의 module과 moduleResolution이 트랜스파일링시 모듈방식을 결정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1714535344813&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;ESNext&quot;, 
    &quot;moduleResolution&quot;: &quot;Bundler&quot; 
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 바로 여기가 문제인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 module이 ESNext로 되어있는데, 이래서 ESM방식으로 트랜스파일링됐던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 module필드의 값을 CJS, CommonJS로 변경해줘보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmHagw/btsG5BqTezu/JH0DfQbf5K46hzYrNDqam0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmHagw/btsG5BqTezu/JH0DfQbf5K46hzYrNDqam0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmHagw/btsG5BqTezu/JH0DfQbf5K46hzYrNDqam0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmHagw%2FbtsG5BqTezu%2FJH0DfQbf5K46hzYrNDqam0%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;818&quot; height=&quot;422&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommonJS로 변경해주면 moduleResolution이 불같이 화를 내는 것을 확인할 수있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tbGoC/btsG4hGH0Ga/SkR1M8LZjd4lsIkSlXQNI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tbGoC/btsG4hGH0Ga/SkR1M8LZjd4lsIkSlXQNI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tbGoC/btsG4hGH0Ga/SkR1M8LZjd4lsIkSlXQNI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtbGoC%2FbtsG4hGH0Ga%2FSkR1M8LZjd4lsIkSlXQNI0%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;1119&quot; height=&quot;315&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;315&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번들러 옵션은 module이 preserve나 es2015이상일때만 쓸수있다고한다.&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;왜 이런것일까? 이걸 생각하다보니 tsconfig의 module, moduleResolution에 대한 이해도가 굉장히 떨어진다는 것을 알 수 있었다.&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;module, moduleResolution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig의 module은 ts파일이 js로 변환될때 어떤 형태로 변환될 것인지를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;moduleResolution은 모듈 해석으로, 컴파일러가 모듈 이름을 읽을때, 어떻게 모듈을 가져올지를 정해주는 것을 말한다.&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;module&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module이 바로 ESM과 CJS를 결정하는 필드다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0CRJ/btsG7oRUqgz/J5W6ixwtbPipk2U02zKdq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0CRJ/btsG7oRUqgz/J5W6ixwtbPipk2U02zKdq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0CRJ/btsG7oRUqgz/J5W6ixwtbPipk2U02zKdq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0CRJ%2FbtsG7oRUqgz%2FJ5W6ixwtbPipk2U02zKdq1%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;550&quot; height=&quot;302&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6, ES2015, ES2020, ESNest가 ESM방식이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommonJS가&amp;nbsp; CJS이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NodeNext/Node16&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 &lt;b&gt;Node16&lt;/b&gt;과 &lt;b&gt;NodeNext&lt;/b&gt;가 무엇인지 헷갈렸었는데, 이는 nodejs환경에서 ESM을 지원하기위한 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;package.json의 type필드가 무엇이냐에 따라 트랜스파일링방식이 바뀐다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 import시 확장자가 반드시 붙어야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIuZp/btsG5Iqb9ek/SUVKZ9Lj2qhV1ooLWt3T3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIuZp/btsG5Iqb9ek/SUVKZ9Lj2qhV1ooLWt3T3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIuZp/btsG5Iqb9ek/SUVKZ9Lj2qhV1ooLWt3T3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIuZp%2FbtsG5Iqb9ek%2FSUVKZ9Lj2qhV1ooLWt3T3k%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;555&quot; height=&quot;274&quot; data-origin-width=&quot;815&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json의 type필드를 바꿔가면서 tsc시켜보면 결과가 다르다는 것을 알 수있다.&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/9Rvc8/btsG5EuCZOK/B9keZWeEUbR7VgpZdctph1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Rvc8/btsG5EuCZOK/B9keZWeEUbR7VgpZdctph1/img.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;234&quot; data-is-animation=&quot;false&quot; style=&quot;width: 68.5299%; margin-right: 10px;&quot; data-widthpercent=&quot;69.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Rvc8/btsG5EuCZOK/B9keZWeEUbR7VgpZdctph1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Rvc8%2FbtsG5EuCZOK%2FB9keZWeEUbR7VgpZdctph1%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;1179&quot; height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sHf0L/btsG5GsoJhN/8KjhCYVrTENvExSqdEFTUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sHf0L/btsG5GsoJhN/8KjhCYVrTENvExSqdEFTUK/img.png&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;460&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.3073%;&quot; data-widthpercent=&quot;30.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sHf0L/btsG5GsoJhN/8KjhCYVrTENvExSqdEFTUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsHf0L%2FbtsG5GsoJhN%2F8KjhCYVrTENvExSqdEFTUK%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;1025&quot; height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽이 module, 오른쪽이 type필드가 비었을때의 tsc결과이다.&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;moduleResolution 방식&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CT8TY/btsG51bVAPE/fNokm3uIR1aAdxKFjYrJ6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CT8TY/btsG51bVAPE/fNokm3uIR1aAdxKFjYrJ6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CT8TY/btsG51bVAPE/fNokm3uIR1aAdxKFjYrJ6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCT8TY%2FbtsG51bVAPE%2FfNokm3uIR1aAdxKFjYrJ6k%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;190&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;190&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPfA3w/btsG3Mtz96a/DPLexkZMMSLnXtrldVsFak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPfA3w/btsG3Mtz96a/DPLexkZMMSLnXtrldVsFak/img.png&quot; data-alt=&quot;https://www.typescriptlang.org/tsconfig/#moduleResolution&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPfA3w/btsG3Mtz96a/DPLexkZMMSLnXtrldVsFak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPfA3w%2FbtsG3Mtz96a%2FDPLexkZMMSLnXtrldVsFak%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;642&quot; height=&quot;447&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.typescriptlang.org/tsconfig/#moduleResolution&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. node16, nodenext는 require(CJS), import(ESM)을 모두 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. node10은 이전에는 node라는 이름이었는데, CJS만 지원한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. bundler는 번들러와 함께 사용할때 사용한다. 가져오기시 상대 경로에 파일 확장자를 요구하지 않는다고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.Classic은 ts1.6버전 이전에 쓰이던거라서 사용하지말라고한다.&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;따라서 NodeNext를 사용하면 ESM을 지원하게 만들 수 있다.&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;/h2&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;CJS로 통일&lt;/li&gt;
&lt;li&gt;ESM으로 통일&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json이 CJS이므로, CJS형태로 통일해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714576914747&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  ...
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;CommmonJS&quot;, 
    &quot;moduleResolution&quot;: &quot;Node10&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;ESM으로 통일하려면 package.json에 type필드를 module로 변경해주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;CJS와 ESM가 모두 지원되는 라이브러리를 만들 수는 없을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 토스의 아래 글을 보게되었고, package.json의 exports필드를 사용하면 호환가능한 라이브러리를 만들 수 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot;&gt;https://toss.tech/article/commonjs-esm-exports-field&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714580067914&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cIwtfE/hyVVxnYBva/AkcNAx2NCIG5ejE5cYgVs0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/z7Da4/hyVZmE5NYM/Bn31rQqxKW7gAA28w4uu4k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/3xC4w/hyVVDV1Bh6/ON1r6cM1wBDqael1Yez6MK/img.png?width=1500&amp;amp;height=1500&amp;amp;face=0_0_1500_1500&quot; data-og-url=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot; data-og-source-url=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot; data-og-host=&quot;toss.tech&quot; data-og-description=&quot;Node.js에는 두 가지 Module System이 존재합니다. 토스 프론트엔드 챕터에서 운영하는 100개가 넘는 라이브러리들은 그것에 어떻게 대응하고 있을까요?&quot; data-og-title=&quot;CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field&quot; data-og-type=&quot;website&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot; data-source-url=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cIwtfE/hyVVxnYBva/AkcNAx2NCIG5ejE5cYgVs0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/z7Da4/hyVZmE5NYM/Bn31rQqxKW7gAA28w4uu4k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/3xC4w/hyVVDV1Bh6/ON1r6cM1wBDqael1Yez6MK/img.png?width=1500&amp;amp;height=1500&amp;amp;face=0_0_1500_1500');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Node.js에는 두 가지 Module System이 존재합니다. 토스 프론트엔드 챕터에서 운영하는 100개가 넘는 라이브러리들은 그것에 어떻게 대응하고 있을까요?&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;toss.tech&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요걸 보고 아이디어를 얻어서 tsconfig를 두개 만들어서 두번 트랜스파일하는 방식을 구성했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;package.json&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type을 module로 바꾸어 ESM을 기본으로 사용하게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 exports필드를 사용해 dist/esm, dist/cjs에 각각 빌드 결과를 배치시키고 cjs확장자를 통해 require시 CJS가 지원되도록 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714580310743&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@rapiders/react-hooks&quot;,
  ...
  &quot;main&quot;: &quot;dist/esm/index.js&quot;,
  &quot;types&quot;: &quot;dist/esm/index.d.ts&quot;,
  &quot;exports&quot;: {
    &quot;.&quot;: {
      &quot;require&quot;: &quot;./dist/cjs/index.cjs&quot;,
      &quot;import&quot;: &quot;./dist/esm/index.js&quot;
    }
  },
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;sh build.sh&quot;,
	...
  },
  ...
  &quot;type&quot;: &quot;module&quot;
}&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;tsconfig.json&lt;/h4&gt;
&lt;pre id=&quot;code_1714580410831&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;include&quot;: [&quot;src&quot;],
  ...
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;./dist/esm&quot;, //esm폴더에 트랜스파일링 결과를 둔다
    &quot;module&quot;: &quot;ESNext&quot;, //ESM
    ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 tsconfig.cjs.json을 만들어서 CJS로 트랜스파일링 할 수 있도록 해주었다.&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;tsconfig.cjs.json&lt;/h4&gt;
&lt;pre id=&quot;code_1714580536465&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;include&quot;: [&quot;src&quot;],
  ...
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;./dist/cjs&quot;, //cjs폴더에 트랜스파일링 결과를 둔다
    &quot;module&quot;: &quot;CommonJS&quot;, //CJS
    &quot;moduleResolution&quot;: &quot;Node10&quot;,
  }
}&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;build.sh&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 스크립트가 너무 길어지는 것 같아서 빌드용 파일을 따로 분리해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714580602175&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm -rf ./dist 
mkdir dist 
tsc --project tsconfig.cjs.json 
tsc --project tsconfig.json&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;이러면 CJS와 ESM 파일을 모두 지원하는 라이브러리 구성이 된...줄 알았으나....&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다 해결됐나 싶었지만,&amp;nbsp;&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;482&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZFCaI/btsG6f8UjXW/fWCIRkTcBQ7qJ9kv8Inz3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZFCaI/btsG6f8UjXW/fWCIRkTcBQ7qJ9kv8Inz3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZFCaI/btsG6f8UjXW/fWCIRkTcBQ7qJ9kv8Inz3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZFCaI%2FbtsG6f8UjXW%2FfWCIRkTcBQ7qJ9kv8Inz3k%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;482&quot; height=&quot;185&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;185&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: #ee2323;&quot;&gt;&lt;b&gt;그렇다... 생각해보니 번들러 없이는 ESM에서는 확장자 생략이 안되는게 당연하다.&lt;/b&gt;&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이해하려면 &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/modules/theory.html&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;안된다&lt;/b&gt;&lt;/span&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용을 번역해서 작성하면 글이 너무 길어지므로, 이 부분은 다음게시글에 작성을 하도록 하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. [javascript] 모듈시스템 -CJS, ESM 동작원리 - &lt;a href=&quot;https://blog.naver.com/pjt3591oo/222834625061&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/pjt3591oo/222834625061&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 내가 보려고 정리한 tsconfig(2) - &lt;a href=&quot;https://velog.io/@milkboy2564/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EC%A0%95%EB%A6%AC%ED%95%9C-tsconfig2#module&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@milkboy2564/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EC%A0%95%EB%A6%AC%ED%95%9C-tsconfig2#module&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Module - Theory - &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution-for-bundlers-typescript-runtimes-and-nodejs-loaders&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution-for-bundlers-typescript-runtimes-and-nodejs-loaders&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. ES modules:A cartoon deep-dive - &lt;a href=&quot;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field - &lt;a href=&quot;https://toss.tech/article/commonjs-esm-exports-field&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://toss.tech/article/commonjs-esm-exports-field&lt;/a&gt;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>CJS</category>
      <category>ESM</category>
      <category>Module</category>
      <category>moduleresolution</category>
      <category>tsconfig</category>
      <category>라이브러리</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/361</guid>
      <comments>https://0422.tistory.com/361#entry361comment</comments>
      <pubDate>Thu, 2 May 2024 01:24:56 +0900</pubDate>
    </item>
    <item>
      <title>라이브러리를 만들어 배포해보자 - (1) tsc만으로 기본 설정하기</title>
      <link>https://0422.tistory.com/360</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be0w1t/btsG4hGzddK/MGJc0CxyzPKRoYQc0TNTC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be0w1t/btsG4hGzddK/MGJc0CxyzPKRoYQc0TNTC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be0w1t/btsG4hGzddK/MGJc0CxyzPKRoYQc0TNTC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe0w1t%2FbtsG4hGzddK%2FMGJc0CxyzPKRoYQc0TNTC0%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;607&quot; height=&quot;495&quot; data-origin-width=&quot;607&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 대학생이기에, 아직 취업을 하지 못하기에 최근에 용돈벌이용으로 외주를 시작했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wP1NL/btsGDJj4coo/aOwv4aMFdjrcPza5N9okeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wP1NL/btsGDJj4coo/aOwv4aMFdjrcPza5N9okeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wP1NL/btsGDJj4coo/aOwv4aMFdjrcPza5N9okeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwP1NL%2FbtsGDJj4coo%2FaOwv4aMFdjrcPza5N9okeK%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;571&quot; height=&quot;217&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;217&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 외주를 진행하다가 문득, 보통 외주를 통해 개발하는 웹페이지는 구성, 기능들이 어느 정도 비슷할 수 있다 생각하게되었다. 특히 랜딩페이지와 같이 사용자 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;그래서 이런 부분들을 빠르게 개발하기위해 자주 반복되는 로직을 react-hook형태로 모아보자는 다짐을 하게되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이번 글에서는 기본적인 설정만 간단하게&lt;s&gt;(아무생각없이...)&lt;/s&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;h2 data-ke-size=&quot;size26&quot;&gt;기본 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;package.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714459550172&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@rapiders/react-hooks&quot;, //패키지의 이름
  &quot;version&quot;: &quot;0.0.9&quot;,
  &quot;description&quot;: &quot;react hooks for fast development&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;, //entry point
  &quot;types&quot;: &quot;dist/index.d.ts&quot;, //entry point의 타입
  &quot;homepage&quot;: &quot;https://github.com/d0422/react-hooks&quot;,
  &quot;scripts&quot;: {
    &quot;test&quot;:&quot;jest&quot;,
    &quot;prepare&quot;: &quot;npm run build&quot;, //npm publish전에 수행된다.
    &quot;build&quot;: &quot;rm -rf ./dist &amp;amp;&amp;amp; tsc&quot;, //기존의 dist를 지우고 tsc로 최신 ts를 js로 트랜스파일링한다.
    &quot;storybook&quot;: &quot;storybook dev -p 6006&quot;,
    &quot;build-storybook&quot;: &quot;storybook build&quot;
  },
  &quot;keywords&quot;: [
    &quot;react&quot;,
    &quot;hooks&quot;
  ],
  &quot;author&quot;: &quot;d0422 &amp;lt;rlfehd2013@naver.com&amp;gt;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;peerDependencies&quot;: { 
	//React와 React-dom을 번들에 포함시키지 않되, 해당 라이브러리를 사용하는 라이브러리이기때문에 
	//아래와 같이 작성한다.
    &quot;react&quot;: &quot;^18.2.0&quot;,
    &quot;react-dom&quot;: &quot;^18.2.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/react&quot;: &quot;^18.2.75&quot;,
    &quot;@types/react-dom&quot;: &quot;^18.2.24&quot;,
    &quot;typescript&quot;: &quot;^5.4.4&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;tsconfig.json&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트를 사용하여 구성하였기때문에 tsconfig를 설정해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714459893282&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;include&quot;: [&quot;src&quot;], //src내용만 트랜스파일링한다.
  &quot;exclude&quot;: [&quot;node_modules&quot;], //node_modules는 포함하지 않음
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;./dist&quot;, //결과파일을 dist에 둔다.
    &quot;module&quot;: &quot;ESNext&quot;, //다음게시글에서 더 자세하게 알아보자
    &quot;target&quot;: &quot;ES2020&quot;, //트랜스파일링완료한 js의 문법
    &quot;lib&quot;: [&quot;ES2020&quot;, &quot;DOM&quot;, &quot;DOM.Iterable&quot;], //js로 컴파일할때 사용할 lib
	//Promise등의 문법을 사용하려면 ES2015이상이 되어야한다.
	//DOM API를 사용하려면 DOM이 필요하다.
    &quot;jsx&quot;: &quot;react&quot;, //jsx 해석을 React.createElement로 해석
    &quot;sourceMap&quot;: true,
    &quot;declaration&quot;: true, //index.d.ts
    &quot;esModuleInterop&quot;: true, //commonJS모듈을 ESM형태로 가져올 수 있도록
    &quot;typeRoots&quot;: [&quot;index.d.ts&quot;, &quot;node_modules/@types&quot;],
    &quot;strictNullChecks&quot;: true,
    &quot;moduleResolution&quot;: &quot;Bundler&quot; //moduleResolution은 모듈을 가져오는 방식을 말한다.
  }
}&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;이렇게 처리한 뒤에, index.ts에서 모든 파일을 import해서 객체형태로 내보내주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714478467462&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//index.ts
import useInput from './useInput/useInput';
import useAnimation from './useAnimation/useAnimation';
import useFocusAnimation from './useFocusAnimation';
import { useDragCarouselIndex } from './useDragIndexCarousel/useDragIndexCarousel';
import useDragIndexCarousel from './useDragIndexCarousel/useDragIndexCarousel';
import useCarousel from './useCarousel/useCarousel';
import useScrollRatio from './useScrollRatio';

export {
  useInput,
  useAnimation,
  useFocusAnimation,
  useDragCarouselIndex,
  useDragIndexCarousel,
  useCarousel,
  useScrollRatio,
};&lt;/code&gt;&lt;/pre&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;이제 빌드를 해보면 dist내에 tsc를 통해 컴파일된 js파일이 생기는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d94oLV/btsG5l2MugS/S1V3RnGePVKl2P9qcxc9ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d94oLV/btsG5l2MugS/S1V3RnGePVKl2P9qcxc9ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d94oLV/btsG5l2MugS/S1V3RnGePVKl2P9qcxc9ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd94oLV%2FbtsG5l2MugS%2FS1V3RnGePVKl2P9qcxc9ak%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;629&quot; height=&quot;547&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;684&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jest와 testing-library를 활용해서 테스트를 구성해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1714479017592&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;//jest.setup.ts
import '@testing-library/jest-dom';&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1714478926608&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//jest.config.ts
import { compilerOptions } from './tsconfig.json';
import { pathsToModuleNameMapper } from 'ts-jest';

module.exports = {
  testEnvironment: 'jsdom', //jsdom으로 설정해야 DOM없이도 렌더링이 가능하다 (진짜 렌더링은 아님)
  transform: {
    '^.+\\.tsx?$': 'ts-jest', 
  },
  preset: 'ts-jest',
  rootDir: '.',
  testMatch: ['**/?(*)+(test).ts', '**/?(*)+(test).tsx'],
  setupFilesAfterEnv: ['./jest.setup.ts'],
  resetMocks: true,
  // tsconfig의 compilerOptions의 paths를 그대로 넘겨주는 것을 확인할 수 있다.
  // 이를 통해 tsconfig에서 지정한 절대경로를 사용할 수 있게된다.
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
    prefix: '&amp;lt;rootDir&amp;gt;',
  }),
};&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;storybook 구성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 Docs용으로 storybook을 사용하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rapiders.github.io/react-hooks&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rapiders.github.io/react-hooks&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1714478547179&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;@storybook/cli - Storybook&quot; data-og-description=&quot;&quot; data-og-host=&quot;rapiders.github.io&quot; data-og-source-url=&quot;https://rapiders.github.io/react-hooks&quot; data-og-url=&quot;https://rapiders.github.io/react-hooks/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://rapiders.github.io/react-hooks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rapiders.github.io/react-hooks&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;@storybook/cli - Storybook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rapiders.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github pages를 통해 main에 push시 자동으로 배포되도록 구성해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 구성된 actions들이 많아서 쉽게 구성할 수 있엇다.&lt;/p&gt;
&lt;pre id=&quot;code_1714478650784&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Workflow name
name:   Storybook 배포

on:
  push: //푸시될때
    branches:
      - 'main' //main브랜치에
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: ✅ 코드 체크 아웃
        uses: actions/checkout@v3

      - name: ✅ 노드 세팅
        uses: actions/setup-node@v3
        with:
          node-version: '20.x'

      - name:   빌드
        run: |
          npm ci
          npm run build-storybook

      - name: Upload artifact
        uses: actions/upload-pages-artifact@v2.0.0
        with:
          path: storybook-static //storybook-static파일을 업로드

  deploy:
    environment:
      name: github-pages
      url: ${{ steps.build-publish.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name:   스토리북 배포
        id: deployment
        uses: actions/deploy-pages@v3 //해당 actions를 사용해서 github page로 배포한다.
        with:
          token: ${{ github.token }}&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;.npmignore&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm배포시 배포되지 않을 파일을 명시해주면된다!&lt;/p&gt;
&lt;pre id=&quot;code_1714479223397&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// .npmignore
node_modules/
src
.gitignore
tsconfig.json
.git
.storybook
jest.config.ts 
jest.setup.ts
build-storybook.log
storybook-static
.github&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;h3 data-ke-size=&quot;size23&quot;&gt;npm publish&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 npm publish를 해서 배포를 해주면 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;npm login을 해주고 , npm publish를 해주면 배포가 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;겪은 이슈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 같은 경우 @rapiders/react-hooks라는 &lt;b&gt;organization형태&lt;/b&gt;로 배포를 시도했기때문에 organization이 없기때문에 발생한 오류였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;npm에서 organization을 만들어주고&lt;/b&gt; npm publish --access public을 통해 배포해주면된다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt8ls7/btsG3AT4td9/9QioUklFYJReXfK2lpv8NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt8ls7/btsG3AT4td9/9QioUklFYJReXfK2lpv8NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt8ls7/btsG3AT4td9/9QioUklFYJReXfK2lpv8NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt8ls7%2FbtsG3AT4td9%2F9QioUklFYJReXfK2lpv8NK%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;621&quot; height=&quot;337&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;504&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;1513&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLvluc/btsGHrBLieg/K0SeKbpCtkeWENUK7SPjK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLvluc/btsGHrBLieg/K0SeKbpCtkeWENUK7SPjK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLvluc/btsGHrBLieg/K0SeKbpCtkeWENUK7SPjK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLvluc%2FbtsGHrBLieg%2FK0SeKbpCtkeWENUK7SPjK1%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;1513&quot; height=&quot;638&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하여 최근 외주 받은 프로젝트에 적용시켜보았다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qY9We/btsG5UQ9a0e/ri0P0kqetPTnuSXj9YgwKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qY9We/btsG5UQ9a0e/ri0P0kqetPTnuSXj9YgwKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qY9We/btsG5UQ9a0e/ri0P0kqetPTnuSXj9YgwKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqY9We%2FbtsG5UQ9a0e%2Fri0P0kqetPTnuSXj9YgwKk%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;302&quot; height=&quot;340&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성한 라이브러리는 번들러가 들어간 환경에서는 매우 잘 작동했으나...&lt;/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;1260&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR94qZ/btsG3BewDWq/IQrS6TCkXd47mYk1PDtkXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR94qZ/btsG3BewDWq/IQrS6TCkXd47mYk1PDtkXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR94qZ/btsG3BewDWq/IQrS6TCkXd47mYk1PDtkXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR94qZ%2FbtsG3BewDWq%2FIQrS6TCkXd47mYk1PDtkXK%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;1260&quot; height=&quot;439&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 아하~ package.json에서 type을 module로 줘서 그렇구나~ 라고 생각해서 해당 설정을 바꾸고 다시 실행시켰는데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kKxcd/btsG3AsXpUV/20BZ4B4AZeAkvBhfIwclUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kKxcd/btsG3AsXpUV/20BZ4B4AZeAkvBhfIwclUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kKxcd/btsG3AsXpUV/20BZ4B4AZeAkvBhfIwclUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkKxcd%2FbtsG3AsXpUV%2F20BZ4B4AZeAkvBhfIwclUk%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;865&quot; height=&quot;437&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;377&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;다음 게시글에서 이러한 문제를 해결해서 라이브러리를 CJS와 ESM에서 모두 동작하도록 만들어 볼 것이다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>hooks</category>
      <category>react</category>
      <category>라이브러리</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/360</guid>
      <comments>https://0422.tistory.com/360#entry360comment</comments>
      <pubDate>Tue, 30 Apr 2024 21:33:39 +0900</pubDate>
    </item>
    <item>
      <title>불굴의 의지로 살아내기 - 노인과 바다</title>
      <link>https://0422.tistory.com/359</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRfc9O/btsF4YIu4ZL/znwdSm8Tk9lfeTkY6Z5En1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRfc9O/btsF4YIu4ZL/znwdSm8Tk9lfeTkY6Z5En1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRfc9O/btsF4YIu4ZL/znwdSm8Tk9lfeTkY6Z5En1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRfc9O%2FbtsF4YIu4ZL%2FznwdSm8Tk9lfeTkY6Z5En1%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;363&quot; height=&quot;619&quot; data-origin-width=&quot;704&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;130페이지정도의 짧은 이 소설이 왜 세기의 명작인지 알 수 있게 되었다. 정말 추천한다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;삶은 꽤나 허무하고, 무의미하다. 결과적으로 노인이 잡은 물고기는 앙상한 뼈만을 남겼다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그러나, 그 고기를 잡기까지의 고통, 그리고 그것을 견뎌내는 노인의 의지는 너무나도 아름답다.&amp;nbsp;&lt;br /&gt;한번만 더...! 한번만 더..! 를 속으로 외치며 삶과 투쟁하는 모습을 보며 정말 숭고함을 느낄 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;i&gt;내가 견디지 못할 것 같아. 아냐, 그럴 리가 없어, 하고 그는 스스로에게 타일렀다. 난 언제까지나 끄떡없을 거야....머리를 맑게 해야해. 머리를 맑게 해서 어떻게 하면 인간답게 고통을 견딜 수 있는지를 알아야 해. 아니면 고기처럼 말이지.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;하지만 한 번만 더 시도해 봐야지.&amp;nbsp;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;(...)&lt;/i&gt;&lt;br /&gt;&lt;i&gt;노인은 다시 한 번 시도해 보았다. &lt;/i&gt;&lt;br /&gt;&lt;i&gt;(...)&lt;/i&gt;&lt;br /&gt;&lt;i&gt;다시 한 번 해 봐야지 하고, 노인은 마음속으로 다짐했다.&lt;/i&gt;&lt;br /&gt;&lt;i&gt;(...)&lt;/i&gt;&lt;br /&gt;&lt;i&gt;그는 다시 한 번 시도해 보았지만 역시 마찬가지였다.&lt;/i&gt;&lt;br /&gt;&lt;i&gt;(...)&lt;/i&gt;&lt;br /&gt;&lt;i&gt;또 한 번 시도해보자.&lt;/i&gt;&lt;br /&gt;&lt;i&gt;(...)&lt;/i&gt;&lt;br /&gt;&lt;i&gt;노인은 모든 고통과 마지막 남아있는 힘, 그리고 오래전에 사라진 자부심을 총 동원해 고기의 마지막 고통과 맞섰다.&lt;/i&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 그가 살라오(스페인어로 가장 운이 없는 사람)인 탓일까, 삶은 부조리하고, 안 좋은 일은 계속해서 일어난다.&lt;br /&gt;어렵게 잡은 물고기의 피는 상어를 불러오고, 그들에 의해 뼈만 남게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그 과정에서도 노인은 희망을 버리지 않고, 도망치지도 않으며, 상어와 끝까지 싸운다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;결국 뼈만 남았지만, 아무것도 남지 않았지만, 노인은 끝까지 싸웠다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;인간은 파괴당할 순 있지만, 패배 당할수는 없다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;결과는 아무래도 좋다. 삶 자체의 의미보다는 내가 삶을 대하는 태도가 중요한 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 자신의 일과 자신의 태도를 믿어주는 사람이 있다는 것도 굉장히 중요한 것 같다.&amp;nbsp;&lt;br /&gt;마놀린은 노인을 걱정하고, 그가 지지 않았다고 응원한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그는 노인이 끝까지 싸웠다는 것을 알고, 믿고 있는 것이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;최근 부조리한 일들을 많이 겪고 있다. 정말 되는 일이 없다고 느낄 정도다. 이때 노인과 바다를 읽게 되어 참 다행이라고 생각했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #2d3b45; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;나의 마놀린에게 감사를 표하며 글을 마친다.&lt;/p&gt;</description>
      <category>독서</category>
      <category>노인과 바다</category>
      <category>헤밍웨이</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/359</guid>
      <comments>https://0422.tistory.com/359#entry359comment</comments>
      <pubDate>Mon, 1 Apr 2024 16:25:13 +0900</pubDate>
    </item>
    <item>
      <title>모노레포 Vite 경로의 마법사를 해부해보자 - vite-tsconfig-paths</title>
      <link>https://0422.tistory.com/358</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;어떻게? 왜 작동하는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 &lt;a href=&quot;https://0422.tistory.com/357&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전글&lt;/a&gt;에서 모노레포에서 tsconfigPath의 마법 같은 경로문제 해결 기능을 보았다.&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;어떻게 tsconfigPath가 경로문제를 해결했는지 확인해보자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바쁜 분은 결론만 보아도 이해에 도움이 될 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;viteFinal&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.storybook/main.ts의 viteFinal은 vite-builder를 통해 storybook을 구성하는 경우에 사용되는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적인 플러그인을 적용시키는게 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 아래 코드는 기존 storybook vite의 config를 가져와서 tsconfigPath plugin을 적용시킨 후에 merge하고, 이를 return하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711197892273&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  viteFinal: async (config) =&amp;gt; {
    return mergeConfig(config, {
      plugins: [tsconfigPath()],
    });
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;tsconfigPath는 어떻게 경로를 가져오는가?&lt;/h2&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://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&quot;&gt;https://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711197892274&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cxztR9/hyVDvbNlho/rNlrPF8dna5MonLbwTsrR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&quot; data-og-source-url=&quot;https://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&quot; data-og-host=&quot;github.com&quot; data-og-description=&quot;Support for TypeScript's path mapping in Vite. Contribute to aleclarson/vite-tsconfig-paths development by creating an account on GitHub.&quot; data-og-title=&quot;vite-tsconfig-paths/src/index.ts at master &amp;middot; aleclarson/vite-tsconfig-paths&quot; data-og-type=&quot;object&quot; data-ke-align=&quot;alignCenter&quot; data-ke-type=&quot;opengraph&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&quot; data-source-url=&quot;https://github.com/aleclarson/vite-tsconfig-paths/blob/master/src/index.ts&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cxztR9/hyVDvbNlho/rNlrPF8dna5MonLbwTsrR0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;vite-tsconfig-paths/src/index.ts at master &amp;middot; aleclarson/vite-tsconfig-paths&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Support for TypeScript's path mapping in Vite. Contribute to aleclarson/vite-tsconfig-paths development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M81Xs/btsF16et318/yztW4hrf7BlcUsXlMsF4OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M81Xs/btsF16et318/yztW4hrf7BlcUsXlMsF4OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M81Xs/btsF16et318/yztW4hrf7BlcUsXlMsF4OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM81Xs%2FbtsF16et318%2FyztW4hrf7BlcUsXlMsF4OK%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;519&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;27번째 줄부터 우리가 사용했던 tsconfigPath 함수의 선언부이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 읽기위해서는 vite Plugin의 양식에 대해 알아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 함수를 통해 반환된 객체가 vitePlugin으로써 적용되는 것이기 때문이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;enforce&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cctz8T/btsF3Ff0ND3/nMhMprVl8zcjzqp7pM4wxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cctz8T/btsF3Ff0ND3/nMhMprVl8zcjzqp7pM4wxK/img.png&quot; data-alt=&quot;https://ko.vitejs.dev/guide/using-plugins.html#enforcing-plugin-ordering&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cctz8T/btsF3Ff0ND3/nMhMprVl8zcjzqp7pM4wxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcctz8T%2FbtsF3Ff0ND3%2FnMhMprVl8zcjzqp7pM4wxK%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;405&quot; height=&quot;345&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ko.vitejs.dev/guide/using-plugins.html#enforcing-plugin-ordering&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enforce는 Vite코어 플러그인보다 먼저 실행할지, 이후에 실행할지를 결정지어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 플러그인은 'pre'로써 Vite의 코어 플러그인보다 먼저 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;configResolved&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bykzsZ/btsF0kLXQSl/pzUdGDyCZrWtUNQNkNsgMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bykzsZ/btsF0kLXQSl/pzUdGDyCZrWtUNQNkNsgMK/img.png&quot; data-alt=&quot;https://ko.vitejs.dev/guide/api-plugin.html#configresolved&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bykzsZ/btsF0kLXQSl/pzUdGDyCZrWtUNQNkNsgMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbykzsZ%2FbtsF0kLXQSl%2FpzUdGDyCZrWtUNQNkNsgMK%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;754&quot; height=&quot;242&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ko.vitejs.dev/guide/api-plugin.html#configresolved&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드의 첫번째 인자를 통해 지금까지 설정된 config값을 읽어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 코드를 읽어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711197892275&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;    async configResolved(config) {
      let projectRoot = config.root //config에서 vite.config.ts가 있는 경로를 가져옴
      let workspaceRoot!: string

      let { root } = opts //opts는 tsconfigPath함수를 호출할때 넣어주는 인자이므로, 우리는 undefined이다
      if (root) { //따라서 root도 undefined이므로
        root = resolve(projectRoot, root)
      } else {
       //여기가 수행된다.
        workspaceRoot = searchForWorkspaceRoot(projectRoot)//이건 vite의 api다.
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;635&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsrUMm/btsF1C535B8/0LnOVeGUz5vGRdfj1ivfKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsrUMm/btsF1C535B8/0LnOVeGUz5vGRdfj1ivfKK/img.png&quot; data-alt=&quot;https://ko.vitejs.dev/guide/api-javascript.html#searchforworkspaceroot&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsrUMm/btsF1C535B8/0LnOVeGUz5vGRdfj1ivfKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsrUMm%2FbtsF1C535B8%2F0LnOVeGUz5vGRdfj1ivfKK%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;482&quot; height=&quot;343&quot; data-origin-width=&quot;892&quot; data-origin-height=&quot;635&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ko.vitejs.dev/guide/api-javascript.html#searchforworkspaceroot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 여기서 searchForWorkspaceRoot를 찾아보면 모노레포의 최상단 root를 가져온다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 workspaceRoot는 모노레포의 최상단이된다.&lt;/p&gt;
&lt;pre id=&quot;code_1711197892277&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;      const tsconfck = await import('tsconfck') // tsconfck라이브러리를 사용해서

      const projects = opts.projects //opts가 undefined이므로 삼항연산자의 뒤쪽을 본다.
        ? opts.projects.map((file) =&amp;gt; {
           ...
          })
          
          //여기를 봐야한다.
        : await tsconfck.findAll(workspaceRoot, { 
            configNames: opts.configNames || ['tsconfig.json', 'jsconfig.json'],
            skip(dir) {
              return dir == 'node_modules' || dir == '.git'
            },
          }) //findAll메서드를 찾아봐야한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfck의 findAll을 찾아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xtmf1/btsF01ywDbj/j0N4r0LEpsvke4LhiAH0eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xtmf1/btsF01ywDbj/j0N4r0LEpsvke4LhiAH0eK/img.png&quot; data-alt=&quot;https://github.com/dominikg/tsconfck/blob/main/docs/api.md&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xtmf1/btsF01ywDbj/j0N4r0LEpsvke4LhiAH0eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxtmf1%2FbtsF01ywDbj%2Fj0N4r0LEpsvke4LhiAH0eK%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;1209&quot; height=&quot;701&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;701&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/dominikg/tsconfck/blob/main/docs/api.md&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;findAll은 해당 디렉토리에서 tsconfig.json파일을 죄다 찾아서 절대 경로를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 workspaceRoot를 넣었으니 전체 모노레포에서 tsconfig.json의 절대경로를 모두 찾아오는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;projects 변수는 모노레포의 모든 tsconfig.json의 절대경로를 갖게된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 진행해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711197892278&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;let hasTypeScriptDep = false

... //중간에 hasTypeScriptDep가 바뀌는 조건문이 있으나 우리 상황하고는 다른 상황이라 뺐다.

const parsedProjects = new Set( //새로운 집합 객체를 만듦
await Promise.all(
  projects.map((tsconfigFile) =&amp;gt; {//모든 tsconfigFile경로에 대해
    if (tsconfigFile === null) {
      debug('tsconfig file not found:', tsconfigFile)
      return null
    }
    return (
      hasTypeScriptDep //false이다.
        ? tsconfck.parseNative(tsconfigFile, parseOptions)
        : tsconfck.parse(tsconfigFile, parseOptions) //이 친구가 수행된다. parse함수를 찾아보자
    ).catch((error) =&amp;gt; {
         ...
    })
  })
)
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;parse는 어떤함수인가?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1TM4K/btsF15mqjeA/OLHmKL7VFu77IyHxQPV2e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1TM4K/btsF15mqjeA/OLHmKL7VFu77IyHxQPV2e1/img.png&quot; data-alt=&quot;https://github.com/dominikg/tsconfck/blob/main/docs/api.md&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1TM4K/btsF15mqjeA/OLHmKL7VFu77IyHxQPV2e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1TM4K%2FbtsF15mqjeA%2FOLHmKL7VFu77IyHxQPV2e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;957&quot; height=&quot;221&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/dominikg/tsconfck/blob/main/docs/api.md&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;위 사진을 보면 parse의 반환값은 TSConfckParseResult이다.&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;943&quot; data-origin-height=&quot;583&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4RSeD/btsF17YRbfL/bAjj9xclavQankbAUcw4B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4RSeD/btsF17YRbfL/bAjj9xclavQankbAUcw4B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4RSeD/btsF17YRbfL/bAjj9xclavQankbAUcw4B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4RSeD%2FbtsF17YRbfL%2FbAjj9xclavQankbAUcw4B1%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;943&quot; height=&quot;583&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;583&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값이 tsconfigFile, tsconfig, solution, referenced, extended을 가진 객체라는걸 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 parsedProjects는 위의 요소를 가진 유일한 객체의 집합이라는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711197892281&quot; class=&quot;gradle&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;resolversByDir = {}
parsedProjects.forEach((project) =&amp;gt; { //아까 가져온 객체들의 집합을 순회한다.
if (!project) {
  return
}
if (project.referenced) { // project의 tsconfig에 있는 referenced를 읽어서 있다면
  project.referenced.forEach((projectRef) =&amp;gt; { 
    parsedProjects.add(projectRef) //tsconfig의 referenced인 tsconfig을 다시 project에추가한다. 
  }) //이래서 Set자료형을 사용한 것이다.
  parsedProjects.delete(project) //오버라이딩을 막기위해 지웠다가 다시 추가한다고한다.
  parsedProjects.add(project) //(코드 주인 피셜, 영어주석은 가독성을 위해 지웠다.)
  project.referenced = undefined
} else {
  ...
}
})
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이렇게 referenced가 있는 tsconfig파일은 &lt;b&gt;재귀적으로 Set객체 내부에 쫙 펴지게 된다.&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 referenced가 없는 tsconfig만 Set에 남게되기때문에 반드시 else문과 만나게된다.&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;그럼 else문을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711198929166&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const resolver = createResolver(project) 
//실제로 파일을 가져오는 resolver를 만들어주는 것 같다. 함수를 좀더 자세히 살펴보자.

if (resolver) { //resolver를 성공적으로 만들었다면
const projectDir = normalizePath(dirname(project.tsconfigFile)) //tsconfigFile의 정규화된 경로를 얻어옴
const resolvers = (resolversByDir[projectDir] ||= []) 
// 얻어온 tsconfigFile경로로 resolversByDir객체에서 얻어와서 이걸 resolvers에 할당함
// 근데 해당 tsconfigFile경로에 해당하는 resolversByDir의 값이 없다면 빈배열을 resolverByDir[projectDir]에 할당하고
// 그걸 resolvers에 할당함
resolvers.push(resolver) //resolvers에 방금 만들어진 resolver를 push함
}&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;1159&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lACzg/btsF2vSMCbc/PoyqxPG7GsQr0Jykf646Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lACzg/btsF2vSMCbc/PoyqxPG7GsQr0Jykf646Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lACzg/btsF2vSMCbc/PoyqxPG7GsQr0Jykf646Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlACzg%2FbtsF2vSMCbc%2FPoyqxPG7GsQr0Jykf646Mk%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;1159&quot; height=&quot;686&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;686&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;h4 data-ke-size=&quot;size20&quot;&gt;createResolver&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 위 객체의 value 배열에 들어갈 resolver를 어떻게 만들어 내는지 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711201944545&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  function createResolver(project: TSConfckParseResult): Resolver | null {
    const configPath = normalizePath(project.tsconfigFile)
   //tsconfigFile의 절대경로위치를 정규화해서 가져온다.
    const config = project.tsconfig as { //tsconfig파일의 내용을 가져온다.
      files?: string[]
      include?: string[]
      exclude?: string[]
      compilerOptions?: CompilerOptions
    }

    const options = config.compilerOptions || {}
    const { baseUrl, paths } = options //compilerOptions에서 제공한 baseUrl과 path다!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 작성한 절대경로에 접근할 수 있는 baseUrl과 paths 가 등장했다! 계속 가보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711202311824&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    type InternalResolver = (
      viteResolve: ViteResolve,
      id: string,
      importer: string
    ) =&amp;gt; Promise&amp;lt;string | undefined&amp;gt;

    const resolveWithBaseUrl: InternalResolver | undefined = baseUrl
      ? (viteResolve, id, importer) =&amp;gt; viteResolve(join(baseUrl, id), importer)
      : undefined 
      //여기서 viteResolve는 vite의 함수이고,
      // id는 해석할 모듈의 경로, importer는 해당 모듈을 가져오는 파일의 경로이다.
      
      //baseUrl값이 없다면 resolveWithBaseUrl은 undefined가 되고,
      //아니라면 함수, id, importer를 받아서 해당 함수를 baseUrl+id한 경로, importer와 함께
      //인자로 해서 호출하는 함수를 할당한다.
     

    let resolveId: InternalResolver
    if (paths) { //지정한 path가 있다면
      const pathMappings = resolvePathMappings(
        paths,
        baseUrl ?? dirname(configPath) //baseUrl이 있으면 그값을
        // 없다면 tsconfig가 있는 디렉토리를 넘긴다.
      )&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;resolvePathMappings이 중요해 보인다. 코드를 읽어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711202584737&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export function resolvePathMappings(
  paths: Record&amp;lt;string, string[]&amp;gt;,
  base: string
) {
  const sortedPatterns = Object.keys(paths).sort(
    (a: string, b: string) =&amp;gt; getPrefixLength(b) - getPrefixLength(a)
  ) //패턴을 Prefix 길이 별로 정렬한다.
  const resolved: PathMapping[] = []
  for (let pattern of sortedPatterns) {
    const relativePaths = paths[pattern] //해당 패턴의 상대경로를 가져온다.
    //ex) @/*: [&quot;src/*&quot;]인경우 relativePaths는 [&quot;src/*&quot;]이 된다.
    pattern = escapeStringRegexp(pattern).replace(/\*/g, '(.+)')
    resolved.push({
      pattern: new RegExp('^' + pattern + '$'),
      //key에 해당하는 값을 정규식 패턴으로 저장한다.
      paths: relativePaths.map((relativePath) =&amp;gt; resolve(base, relativePath)),
      //base에 붙여서 상대 경로를 반환한다.
    })
  }
  return resolved //전체 tsconfig에서 지정한 절대 경로 가 pattern, paths를 가진 객체 배열로 변환된다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기부분이 tsconfig에서 지정한 절대경로를 객체형태로 변환해서 읽는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 얻어온 pathMappings를 어떻게 사용하는지 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711203368349&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// -------------------읽은 부분이다.---------------
if (paths) {
      const pathMappings = resolvePathMappings(
        paths,
        baseUrl ?? dirname(configPath)
      )
      //여기까지 pathMappings을 가져왔다.
// -------------------읽은 부분이다.---------------

      const resolveWithPaths: InternalResolver = async (
        viteResolve,
        id,
        importer
      ) =&amp;gt; {
        for (const mapping of pathMappings) {
          const match = id.match(mapping.pattern) 
          //인자로 받은 id가 아까 pathMapping안에 있던 패턴과 일치하는 지 확인한다.
          if (!match) {
            continue
          }
          for (let pathTemplate of mapping.paths) {
            let starCount = 0
            const mappedId = pathTemplate.replace(/\*/g, () =&amp;gt; {
              const matchIndex = Math.min(++starCount, match.length - 1)
              return match[matchIndex]
            }) //mappedId를 통해 와일드 카드를 실제 경로로 치환해준다
            //ex) src/* -&amp;gt; src/components
            const resolved = await viteResolve(mappedId, importer)
            if (resolved) {
              return resolved
            }
          }
        }
      }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 resolveWithPaths라는 함수가 와일드카드가 포함된 패턴을 실제 경로로 매칭시켜주는 일을 한다는 것을 알 수 있다. 계속 가보자.&lt;/p&gt;
&lt;pre id=&quot;code_1711203718825&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  if (resolveWithBaseUrl) { //baseUrl이 있어서 undefined가 아닌상태라면
        resolveId = (viteResolve, id, importer) =&amp;gt;
        //선언한 resolveWithPaths를 통해 경로를 가져온다.
          resolveWithPaths(viteResolve, id, importer).then((resolved) =&amp;gt; {
          //resovled되지 않았다면, baseUrl에서 찾아온다.
            return resolved ?? resolveWithBaseUrl(viteResolve, id, importer)
          })
      } else {
        resolveId = resolveWithPaths //위에 선언한 resolveWithPaths를 resolveId에 할당한다
      }
    } else {
    	//여기의 else는 path가 없는 경우이다.
      resolveId = resolveWithBaseUrl! //기본 resolveWithBaseUrl을 할당한다.
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지보면 resolveId에 resolveWithPaths나 resolveWithBaseUrl을 통해서 viteResolve를 호출할 함수를 할당해주고있다는 것을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에도 많은 부분이 있었지만 주요 골자가 아니므로 return부로 넘어가자&lt;/p&gt;
&lt;pre id=&quot;code_1711204566390&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; return async (viteResolve, id, importer) =&amp;gt; { //잘보면 함수를 반환한다는걸 알 수 있다.
       ...

      importer = normalizePath(importer)
	  ...
      
      path = await resolveId(viteResolve, id, importer)
      //path를 절대경로를 통해 viteResolve함수를 실행시키는 resolveId함수로 얻어온다.
 
      return [path &amp;amp;&amp;amp; suffix ? path + suffix : path, true]
      //suffix는 Vite의 접미사인데, 요부분은 신경쓰지 않고 코드를 따로 보지 않았다.
      // return [path,true] 라고 봐도 현재 읽고 있는 관점에서는 무방하다고 생각한다.
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 createResolver를 호출하면 &lt;b&gt;path가 담긴 array를 반환하는 resolver함수&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;자 여기까지가 configResolved함수가 호출됐을때 일어나는 일이었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;configResolved 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 통해 모노레포에서 configResolved가 호출되었을때 아래와 같이 작동한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcTCZz%2FbtsF10S3YYJ%2FaNuZkgxzokEJqlmuqQhSK0%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;1331&quot; height=&quot;680&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이건 Vite값이 확정된 후 호출되는 훅이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7ZbL0/btsF0siWLPu/pedDCQXZUHwaqhEgVyNQK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7ZbL0/btsF0siWLPu/pedDCQXZUHwaqhEgVyNQK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7ZbL0/btsF0siWLPu/pedDCQXZUHwaqhEgVyNQK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7ZbL0%2FbtsF0siWLPu%2FpedDCQXZUHwaqhEgVyNQK1%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;747&quot; height=&quot;257&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 resolversByDir는 어디서 사용하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 resolveId이다. 마지막으로 resolvedId메서드를 보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;resolveId&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NBfMT/btsF2OR8iIj/ZfkeBUcqHC0OVhd0Gbsnm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NBfMT/btsF2OR8iIj/ZfkeBUcqHC0OVhd0Gbsnm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NBfMT/btsF2OR8iIj/ZfkeBUcqHC0OVhd0Gbsnm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNBfMT%2FbtsF2OR8iIj%2FZfkeBUcqHC0OVhd0Gbsnm1%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;780&quot; height=&quot;404&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드가 모듈을 불러올때마다 호출되어, 실제로 그 역할을 해내게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1711207587773&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; async resolveId(id, importer, options) {
      if (importer &amp;amp;&amp;amp; !relativeImportRE.test(id) &amp;amp;&amp;amp; !isAbsolute(id)) {
        ...
        //여기가 바로 위에서 그토록 썼던 viteResolve의 본체다
        const viteResolve: ViteResolve = async (id, importer) =&amp;gt;
          (await this.resolve(id, importer, resolveOptions))?.id 
          //vite의 this객체에서 resolve메서드를 사용하는 것으로 보인다.

        let prevProjectDir: string | undefined 
        let projectDir = dirname(importer) //import하려는 모듈의 디렉터리

        loop: while (projectDir &amp;amp;&amp;amp; projectDir != prevProjectDir) { 
        //tsconfig과 만날때까지 폴더를 거슬러 올라가며 경로를 탐색한다.
          const resolvers = resolversByDir[projectDir] //미리만들어둔 객체에서 resolvers를 가져온다.
          if (resolvers)
            for (const resolve of resolvers) {
              const [resolved, matched] = await resolve(
                viteResolve,
                id,
                importer
              )
              if (resolved) {
                return resolved //모듈을 resolve시킨다.
              }
              if (matched) {
                // Once a matching resolver is found, stop looking.
                break loop
              }
            }
          prevProjectDir = projectDir //이전경로를 현재경로로
          projectDir = dirname(prevProjectDir) //현재 경로 폴더의 경로를 얻으므로 한단계 상위로 올라가게 된다.
        }
      }
    },
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;resolveId정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 통해 모노레포에서 어느 곳에서든 vite를 통해 특정 모듈을 요청할때마다 아래와 같이 작동한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3lVY%2FbtsF1SOrtl0%2FPl8Em7lsAZbPfdcv51IQHk%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;1193&quot; height=&quot;599&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vite 서버가 실행되면 &lt;b&gt;configResolved&lt;/b&gt;가 수행되며, 이때 아래의 그림 과정을 통해 모노레포에서 &lt;b&gt;tsconfig에 따라 모듈 상대/절대경로를 정해주는 resolversByDir 객체&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;1331&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcTCZz/btsF10S3YYJ/aNuZkgxzokEJqlmuqQhSK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcTCZz%2FbtsF10S3YYJ%2FaNuZkgxzokEJqlmuqQhSK0%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;1331&quot; height=&quot;680&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;680&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;2. 모듈이 요청되면 resolveId가 수행되며 &lt;b&gt;해당 모듈이 있는 디렉토리부터 상위 디렉토리로 이동하며&lt;/b&gt;&amp;nbsp;tsconfig이 있는 디렉토리까지 이동한다. 그러면 resolversByDir&lt;span&gt; 에 resolver함수가 존재하므로 resolve가 되고 모듈이 가져와진다.&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;1193&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3lVY/btsF1SOrtl0/Pl8Em7lsAZbPfdcv51IQHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3lVY%2FbtsF1SOrtl0%2FPl8Em7lsAZbPfdcv51IQHk%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;1193&quot; height=&quot;599&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>storybook</category>
      <category>tsconfigPath</category>
      <category>VITE</category>
      <category>vite-tsconfig-paths</category>
      <category>모노레포</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/358</guid>
      <comments>https://0422.tistory.com/358#entry358comment</comments>
      <pubDate>Sun, 24 Mar 2024 00:04:38 +0900</pubDate>
    </item>
    <item>
      <title>모노레포에서 Storybook 통합으로 UI배포 시간 단축시키기 (6min -&amp;gt; 2min)</title>
      <link>https://0422.tistory.com/357</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HS11Q/btsF2NMkppC/Rj6nXazXQuXt7hWs9cd2NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HS11Q/btsF2NMkppC/Rj6nXazXQuXt7hWs9cd2NK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HS11Q/btsF2NMkppC/Rj6nXazXQuXt7hWs9cd2NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHS11Q%2FbtsF2NMkppC%2FRj6nXazXQuXt7hWs9cd2NK%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;1184&quot; height=&quot;480&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;480&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;a href=&quot;https://0422.tistory.com/356&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 게시글&lt;/a&gt;에서 UI테스트를 하기 위해 Storybook을 도입했다.&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;배포가 너무 많다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3개의 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 레포에 대해서 chromatic을 통해 storybook을 각각 배포하다보니 두가지 문제가 발생했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트하기 힘들다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (3).png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dj4KhH/btsF4z2eWQp/LdhLqU9zqCNr0AlrC39LDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dj4KhH/btsF4z2eWQp/LdhLqU9zqCNr0AlrC39LDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dj4KhH/btsF4z2eWQp/LdhLqU9zqCNr0AlrC39LDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdj4KhH%2FbtsF4z2eWQp%2FLdhLqU9zqCNr0AlrC39LDK%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;378&quot; data-filename=&quot;다운로드 (3).png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 완료된 링크가 3개라 일일히 들어가서 테스트해봐야한다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;속도가 느리다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D5ykO/btsF2O5xFBI/FGVO9XDfxCId7TFLozKh9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D5ykO/btsF2O5xFBI/FGVO9XDfxCId7TFLozKh9k/img.png&quot; data-alt=&quot;프로덕션 배포보다 느린 Storybook배포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D5ykO/btsF2O5xFBI/FGVO9XDfxCId7TFLozKh9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD5ykO%2FbtsF2O5xFBI%2FFGVO9XDfxCId7TFLozKh9k%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;1354&quot; height=&quot;288&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로덕션 배포보다 느린 Storybook배포&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 순차적인 형태로 각각의 레포 storybook을 배포하다보니 2분씩, 총 7&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;심지어 이 CI는 pull_request마다 돌아야하므로 더 자주, 더 많이 호출되어야한다. 또 우리 레포는 현재 4개의 프로젝트로 구성되어있고, 추후에 더 추가될 수도 있었기 때문에 이런 문제를 &lt;b&gt;반드시&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;Storybook 통합하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 생성&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apps내부에 storybook을 위한 프로젝트를&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; vite를 기반&lt;/b&gt;&lt;/span&gt;으로해서 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고로 우리는 turborepo를 통해 프로젝트를 생성했다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (1).png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dM9ze3/btsF11FCsuB/fFl6PSwv88Yek1Y3r3KvW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dM9ze3/btsF11FCsuB/fFl6PSwv88Yek1Y3r3KvW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dM9ze3/btsF11FCsuB/fFl6PSwv88Yek1Y3r3KvW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdM9ze3%2FbtsF11FCsuB%2FfFl6PSwv88Yek1Y3r3KvW1%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;426&quot; height=&quot;529&quot; data-filename=&quot;다운로드 (1).png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stories이동시키기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;project.png&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIZla/btsF5tOgcnb/kW0DUHuLyg3Pj7c0Xv1maK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIZla/btsF5tOgcnb/kW0DUHuLyg3Pj7c0Xv1maK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIZla/btsF5tOgcnb/kW0DUHuLyg3Pj7c0Xv1maK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIZla%2FbtsF5tOgcnb%2FkW0DUHuLyg3Pj7c0Xv1maK%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;441&quot; height=&quot;394&quot; data-filename=&quot;project.png&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 레포에 있던 모든 stories파일을 apps/storybook/src/프로젝트 명으로 옮겨주었다.&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;이제 tsconfig.json이 달라졌기때문에 해당 경로를 찾을 수 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (2).png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cewWpo/btsF3dL7Tnz/iN05bM4e4DmfNL87Bnfrv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cewWpo/btsF3dL7Tnz/iN05bM4e4DmfNL87Bnfrv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cewWpo/btsF3dL7Tnz/iN05bM4e4DmfNL87Bnfrv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcewWpo%2FbtsF3dL7Tnz%2FiN05bM4e4DmfNL87Bnfrv0%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;848&quot; height=&quot;69&quot; data-filename=&quot;다운로드 (2).png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;69&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-filename=&quot;다운로드 (3).png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4SaDT/btsF3VYEqJX/B6e31iyCjkf4lnIe15U4fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4SaDT/btsF3VYEqJX/B6e31iyCjkf4lnIe15U4fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4SaDT/btsF3VYEqJX/B6e31iyCjkf4lnIe15U4fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4SaDT%2FbtsF3VYEqJX%2FB6e31iyCjkf4lnIe15U4fK%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;984&quot; height=&quot;72&quot; data-filename=&quot;다운로드 (3).png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러고 Storybook 서버를 실행시켜보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cS2rdJ/btsF55zkrn0/pgEqoehkcVonbvMEJARA2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cS2rdJ/btsF55zkrn0/pgEqoehkcVonbvMEJARA2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cS2rdJ/btsF55zkrn0/pgEqoehkcVonbvMEJARA2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcS2rdJ%2FbtsF55zkrn0%2FpgEqoehkcVonbvMEJARA2k%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;1029&quot; height=&quot;572&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 마치 stories.tsx파일을 못가져오는 것 처럼 보이지만, 실제로 서버가 돌고있는 터미널을 보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcGD4Q/btsF3DqgDxu/YjBzvviXWc6vgjrqtuMLb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcGD4Q/btsF3DqgDxu/YjBzvviXWc6vgjrqtuMLb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcGD4Q/btsF3DqgDxu/YjBzvviXWc6vgjrqtuMLb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcGD4Q%2FbtsF3DqgDxu%2FYjBzvviXWc6vgjrqtuMLb0%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;192&quot; data-filename=&quot;다운로드.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Stories.tsx에서 import해온 절대경로 모듈때문에 해당 모듈을 가져오지 못하여 발생한 문제라는 것을 알 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;절대 경로 문제 해결하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 도대체 어떻게 해결해야할까 고민했다. 3개의 절대경로를 각각 다르게 매칭시켜주어야했는데, 이걸 런타임에 해주려니 정말 쉬운일이 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 이 질문글과 답변글을 보게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/storybookjs/builder-vite/discussions/411&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/storybookjs/builder-vite/discussions/411&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711174193985&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Absolute Imports in Storybook Vite &amp;middot; storybookjs builder-vite &amp;middot; Discussion #411&quot; data-og-description=&quot;I was able to setup Vite TS React project, and I was able to enable absolute imports by adding baseUrl/rootDir in tsconfig.json, and using vite-tsconfig-paths vite plugin, in React app. Sadly this ...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/storybookjs/builder-vite/discussions/411&quot; data-og-url=&quot;https://github.com/storybookjs/builder-vite/discussions/411&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/J2P33/hyVDA5bKbp/nfWswolhSzFiwxv5rrFSw1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/storybookjs/builder-vite/discussions/411&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/storybookjs/builder-vite/discussions/411&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/J2P33/hyVDA5bKbp/nfWswolhSzFiwxv5rrFSw1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Absolute Imports in Storybook Vite &amp;middot; storybookjs builder-vite &amp;middot; Discussion #411&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I was able to setup Vite TS React project, and I was able to enable absolute imports by adding baseUrl/rootDir in tsconfig.json, and using vite-tsconfig-paths vite plugin, in React app. Sadly this ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 주요 내용은 아래 코드를 .storybook/main.ts에 집어넣으면 된다는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1711174594333&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const config: StorybookConfig = {
  ...
  viteFinal: async (config) =&amp;gt; {
    return mergeConfig(config, {
      plugins: [tsconfigPath()],
    });
  },
};
export default config;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해당 파일이 있는 프로젝트의 tsconfig.json의 내용을 통해 경로를 설정해주는 것으로 보인다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이러면 잘 작동하는 것을 확인할 수 있다!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;yml수정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 yml을 통해 단일배포로 변경해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1711176013989&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: storybook-deployment

on:
  pull_request:
    branches: ['dev']

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: ✅ 코드 체크아웃
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: ⬇️ 의존성 설치
        run: npm install

      - name:   storybook 배포
        uses: chromaui/action@latest
        with:
          workingDir: apps/storybook
          projectToken: {{   }}
          token: ${{ secrets.GITHUB_TOKEN }}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb40Nt/btsF3m8LbKH/6WR1HX3NO8oKzpIsRhkzqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb40Nt/btsF3m8LbKH/6WR1HX3NO8oKzpIsRhkzqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb40Nt/btsF3m8LbKH/6WR1HX3NO8oKzpIsRhkzqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb40Nt%2FbtsF3m8LbKH%2F6WR1HX3NO8oKzpIsRhkzqK%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;1440&quot; height=&quot;104&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7분가량에서 2분초반대로 약 &lt;b&gt;70%가량&lt;/b&gt;의 속도개선이 생겼다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Next&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이게 어떻게 동작하는지는 아직 정확하게는 알아보지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 게시글에서 tsconfigPath라이브러리가 어떻게 동작하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음게시글 :&amp;nbsp; &lt;a href=&quot;https://0422.tistory.com/358&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/358&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711206316793&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;모노레포 Vite 경로의 마법사를 해부해보자 - vite-tsconfig-paths&quot; data-og-description=&quot;어떻게? 왜 작동하는가? 자 이전글에서 모노레포에서 tsconfigPath의 마법 같은 경로문제 해결 기능을 보았다. 하지만, 개발자라면 마법과 같은 일은 없으리라는 것을 알고 있을 것이다. 결국 왜? 를&quot; data-og-host=&quot;0422.tistory.com&quot; data-og-source-url=&quot;https://0422.tistory.com/358&quot; data-og-url=&quot;https://0422.tistory.com/358&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/csr2L8/hyVDwBTCRj/6t55oURSPTZaBepduIJiXk/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bbtloP/hyVDAK5lYs/ysMXmjR81WSDf3Ko8I3TmK/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bfXoBW/hyVDD16EeU/Zdbg1ic63YDFqwRpbMQm71/img.png?width=1344&amp;amp;height=742&amp;amp;face=0_0_1344_742&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/358&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0422.tistory.com/358&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/csr2L8/hyVDwBTCRj/6t55oURSPTZaBepduIJiXk/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bbtloP/hyVDAK5lYs/ysMXmjR81WSDf3Ko8I3TmK/img.png?width=800&amp;amp;height=416&amp;amp;face=0_0_800_416,https://scrap.kakaocdn.net/dn/bfXoBW/hyVDD16EeU/Zdbg1ic63YDFqwRpbMQm71/img.png?width=1344&amp;amp;height=742&amp;amp;face=0_0_1344_742');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;모노레포 Vite 경로의 마법사를 해부해보자 - vite-tsconfig-paths&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어떻게? 왜 작동하는가? 자 이전글에서 모노레포에서 tsconfigPath의 마법 같은 경로문제 해결 기능을 보았다. 하지만, 개발자라면 마법과 같은 일은 없으리라는 것을 알고 있을 것이다. 결국 왜? 를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0422.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>Chromatic</category>
      <category>storybook</category>
      <category>개선</category>
      <category>경로문제</category>
      <category>모노레포</category>
      <category>빌드</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/357</guid>
      <comments>https://0422.tistory.com/357#entry357comment</comments>
      <pubDate>Sat, 23 Mar 2024 21:49:49 +0900</pubDate>
    </item>
    <item>
      <title>UI테스트를 위해 Storybook을 도입하게 된 이야기</title>
      <link>https://0422.tistory.com/356</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제 프로젝트는 앱 런칭을 앞두고 QA를 진행하고있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbxyrP/btsFPGnCsb7/8KHdjxAls2X47OWqSd2be0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbxyrP/btsFPGnCsb7/8KHdjxAls2X47OWqSd2be0/img.png&quot; data-alt=&quot;139개의 QA를 진행했다...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbxyrP/btsFPGnCsb7/8KHdjxAls2X47OWqSd2be0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbxyrP%2FbtsFPGnCsb7%2F8KHdjxAls2X47OWqSd2be0%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;762&quot; height=&quot;134&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;139개의 QA를 진행했다...&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (2).png&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tNSs9/btsF2OZ9mwX/VpcM13zB4gMeVNT4eeCsm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tNSs9/btsF2OZ9mwX/VpcM13zB4gMeVNT4eeCsm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tNSs9/btsF2OZ9mwX/VpcM13zB4gMeVNT4eeCsm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtNSs9%2FbtsF2OZ9mwX%2FVpcM13zB4gMeVNT4eeCsm0%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;860&quot; height=&quot;421&quot; data-filename=&quot;다운로드 (2).png&quot; data-origin-width=&quot;860&quot; data-origin-height=&quot;421&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;이렇게 QA를 진행하다보니 알게된 것은 생각보다 다양한 기기에서 테스트하다보니 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;그러나 의외로 UI 깨지는 것을 쉽게 해결하기가 쉽지않았다.&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;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;아래와 같이 화면이 좁아지면&amp;nbsp; UI가 깨진 다던가... 문제가 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnU24v/btsF2MgRxKs/zuUMWpDFQOdiEyWsFimjk0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnU24v/btsF2MgRxKs/zuUMWpDFQOdiEyWsFimjk0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnU24v/btsF2MgRxKs/zuUMWpDFQOdiEyWsFimjk0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnU24v%2FbtsF2MgRxKs%2FzuUMWpDFQOdiEyWsFimjk0%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;612&quot; height=&quot;302&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;서버 API호출을 줄여 운영 비용을 줄이기 위해 네이티브 DB를 사용하기로 결정했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 쓴 글, 좋아요 누른 글 등을 앱 내부 DB에 저장하게 하고, 로그인시 서버와 1회 통신을 통해 동기화하는 형태로 서비스를 구성했다.&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;이렇게 앱 내부 DB를 사용하는 곳이 늘어나다보니 이제는 브라우저로 배포된 서비스 URL을 열었을때는 제대로 앱이 기능하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 애초에 로그인시 들어오는 토큰을 webview로 전송해주기때문에 배포 URL로 접근하면 로딩화면만 보이게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 아예 디버그가 안된다는 문제가 있다. 이는 chrome://inspect를 통해 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USB디버깅을 통해 안드로이드에서 열린 chrome 화면을 분석할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccpSqc/btsFQyoMWer/ua3soh3GQAIWzp5WiN4sh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccpSqc/btsFQyoMWer/ua3soh3GQAIWzp5WiN4sh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccpSqc/btsFQyoMWer/ua3soh3GQAIWzp5WiN4sh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccpSqc%2FbtsFQyoMWer%2Fua3soh3GQAIWzp5WiN4sh0%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;450&quot; height=&quot;309&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;505&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;따라서 반응형 UI 테스트를 해볼 방법이 없었다.&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;/b&gt;&lt;/h4&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;모노레포에 접근해서 npm run dev를 통해 4가지 프로젝트에 대한 로컬 서버를 띄운다.&lt;/li&gt;
&lt;li&gt;reactNative 프로젝트에 접근해서 WebView uri를 열린 로컬 서버 주소로 변경한다.&lt;/li&gt;
&lt;li&gt;usb를 안드로이드 기기에 연결한다.&lt;/li&gt;
&lt;li&gt;npm run start를 통해 reactNative를 실행시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 일련의 과정을 수행하는데에 매우 많은 시간이 소모되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 4번은 첫 빌드하는데 10분가량의 시간이 걸린다. 위 과정을 매 디버그마다 겪으며 우리 팀 프론트엔드 개발자는 피로감을 느끼게 되었다.&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;StoryBook&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beBN0T/btsFQhnlDJ3/qYSYTHu25ETBQBTu3D1EFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beBN0T/btsFQhnlDJ3/qYSYTHu25ETBQBTu3D1EFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beBN0T/btsFQhnlDJ3/qYSYTHu25ETBQBTu3D1EFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeBN0T%2FbtsFQhnlDJ3%2FqYSYTHu25ETBQBTu3D1EFk%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;800&quot; height=&quot;800&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 위의 과정을 거치지 않고 컴포넌트를 시각화하고 테스트해볼까 고민하다가 이쪽으로 가장 유명하다는 &lt;s&gt;홍박사님을&lt;/s&gt;&amp;nbsp;storybook의 도입을 고려하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전까지는 항상 &quot; 뭐 이렇게 까지 해야해? &quot; 라는 생각이 있었는데, 이렇게 직접 UI문제를 마주하고, 일관된 사용자 경험을 제공할 수 없겠다는 문제가 예상되자 스토리북이 절실해졌다.&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 경로에 가서 아래 명령어를 실행시키면 알아서 기본 세팅을 다해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1710566335563&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx sb init&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storybook을 도입하자 생긴 가장 큰 문제점이 우리 프로젝트는 디자인시스템이 따로 없고, 아토믹 디자인도 사용하지 않았다. 그래서 각 컴포넌트에서 외부 라이브러리의 훅을 사용하는 경우가 빈번했고, 이에 따라 추가 설정이 필요해졌다.&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;Context Provider 세팅하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Context Provider로 감싸진 상태에서 렌더링 되는 경우에 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로 우리 프로젝트는 react프로젝트라 react-router-dom을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라이브러리는 Provider를 통해 구현되었기때문에 useNavigate, useLocation등 이 라이브러리에서 제공되는 훅들을 사용하려면 RouterProvider로 감싸주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 decorators라는 옵션을 통해 설정할 수 있다.&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;예를들어 ToastProvider를 사용하고 싶은 경우 SomeComponent.storeis.ts에 아래와 같이 작성하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1710566836885&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const meta = {
  ...
  decorators: [ 
    (Story) =&amp;gt; (
      &amp;lt;ToastProvider&amp;gt;
        &amp;lt;Story /&amp;gt;
      &amp;lt;/ToastProvider&amp;gt;
    ),
  ],
  ...
  } satisfies Meta&amp;lt;typeof Person&amp;gt;;
  
export default meta;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 react-router-dom 의 Provider는 라우팅 설정이 된 객체를 router value로 주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 매번 설정하기 귀찮고, 컴포넌트 하나를 테스트하자고 이걸 다 설정할 필요도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 필요한건 그냥 문제없이 라이브러리 내부의 훅이 수행되는 것이다.&lt;/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;storybook-addon-remix-react-router&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 훅이 잘 작동하게 해주는 addon이 있다. &lt;a href=&quot;https://storybook.js.org/addons/storybook-addon-remix-react-router&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;storybook-addon-remix-react-router&lt;/a&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 사용하면 복잡하게 설정해줄 필요없이 해당 라이브러리의 withRouter만 적어주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710567280665&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { withRouter } from 'storybook-addon-remix-react-router';

const meta = {
  ...
  decorators: [withRouter],
  ...
}&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 turborepo를 활용하여 4개의 react 프로젝트를 구성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 내부 package를 통해 공통 컴포넌트를 구성하긴했지만, 스토리북을 도입한 이유는 네이티브 기기없이 UI테스팅을 하기 위함이었으므로 각 프로젝트마다 storybook을 구성해주었다.&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;이제는 이 storybook을 배포해서 디자이너/기획자분들도 어렵지 않게 컴포넌트를 직접 보고, UI 엣지 케이스를 테스팅할 수 있게&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;배포 자체는 아래 url을 참고하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://storybook.js.org/tutorials/intro-to-storybook/react/ko/deploy/&lt;/a&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;CI구성에서 생긴 문제점 (secrets 변수를 가져오지 못하는 문제점)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pull request시에 storybook을 빌드해서 배포하고, chromui를 통해 이전 빌드와 시각적으로 달라진 부분을 확인하는 형태로 세팅을 하려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (5).png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYerGf/btsF3F2Gns1/nMCGIQBefUWMKf9rRKBQS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYerGf/btsF3F2Gns1/nMCGIQBefUWMKf9rRKBQS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYerGf/btsF3F2Gns1/nMCGIQBefUWMKf9rRKBQS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYerGf%2FbtsF3F2Gns1%2FnMCGIQBefUWMKf9rRKBQS0%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;473&quot; height=&quot;594&quot; data-filename=&quot;다운로드 (5).png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이게 죽었다 깨어나도 secrets의 변수를 가져오지 못하는것이었다.&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/cFjt5Y/btsF4y949Fc/86RAfr2k3pky23VK3wF9NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFjt5Y/btsF4y949Fc/86RAfr2k3pky23VK3wF9NK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;266&quot; data-filename=&quot;다운로드 (4).png&quot; style=&quot;width: 57.3238%; margin-right: 10px;&quot; data-widthpercent=&quot;58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFjt5Y/btsF4y949Fc/86RAfr2k3pky23VK3wF9NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFjt5Y%2FbtsF4y949Fc%2F86RAfr2k3pky23VK3wF9NK%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;605&quot; height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCBK6/btsFRZMySAk/PZYbexEZBCpkQthw6ELIwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCBK6/btsFRZMySAk/PZYbexEZBCpkQthw6ELIwk/img.png&quot; data-origin-width=&quot;1489&quot; data-origin-height=&quot;904&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;42&quot; style=&quot;width: 41.5134%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCBK6/btsFRZMySAk/PZYbexEZBCpkQthw6ELIwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCBK6%2FbtsFRZMySAk%2FPZYbexEZBCpkQthw6ELIwk%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;1489&quot; height=&quot;904&quot;/&gt;&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;그러다가 엄청난걸 찾았다.&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;691&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oeUUv/btsFQrJ1yt3/Zn3EzDcaaekJhWpE3wWs60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oeUUv/btsFQrJ1yt3/Zn3EzDcaaekJhWpE3wWs60/img.png&quot; data-alt=&quot;https://github.com/pypa/gh-action-pypi-publish/discussions/49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oeUUv/btsFQrJ1yt3/Zn3EzDcaaekJhWpE3wWs60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoeUUv%2FbtsFQrJ1yt3%2FZn3EzDcaaekJhWpE3wWs60%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;691&quot; height=&quot;616&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/pypa/gh-action-pypi-publish/discussions/49&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;upstream 레포지토리에 pull_request를 fork된 레포지토리에서 쓴 경우 github actions은 untrusted mode로 돌아가서 secrets를 못 가져온다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나의 4시간은 날아갔다.&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;push이벤트로 변경했고, merge시에 비교할 수 있도록 변경했다.&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;1916&quot; data-origin-height=&quot;938&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5MplP/btsF5UYX4pJ/aSWhHcPaKaIFX3yyFYi4rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5MplP/btsF5UYX4pJ/aSWhHcPaKaIFX3yyFYi4rK/img.png&quot; data-alt=&quot;완료된 예시 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5MplP/btsF5UYX4pJ/aSWhHcPaKaIFX3yyFYi4rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5MplP%2FbtsF5UYX4pJ%2FaSWhHcPaKaIFX3yyFYi4rK%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;1916&quot; height=&quot;938&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;938&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;이제 네이티브 기기 없이도 시각적 테스트를 할 수 있고, 더불어 dev에 머지될때마다 자동으로 배포되어 팀과 함께 UI를 확인할 수있다!&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>github actions</category>
      <category>secrets 못가져옴</category>
      <category>storybook</category>
      <category>WebView</category>
      <category>웹뷰</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/356</guid>
      <comments>https://0422.tistory.com/356#entry356comment</comments>
      <pubDate>Sat, 16 Mar 2024 14:58:38 +0900</pubDate>
    </item>
    <item>
      <title>노르웨이의 숲</title>
      <link>https://0422.tistory.com/355</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqXrni/btsFsmpV4Fl/MRTkh8546rd8hYZTlguBF1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqXrni/btsFsmpV4Fl/MRTkh8546rd8hYZTlguBF1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqXrni/btsFsmpV4Fl/MRTkh8546rd8hYZTlguBF1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqXrni%2FbtsFsmpV4Fl%2FMRTkh8546rd8hYZTlguBF1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;293&quot; height=&quot;500&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;852&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;h2 data-ke-size=&quot;size26&quot;&gt;하루키의 문장들&lt;/h2&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루키의 문체는 정말... 뭐랄까... 모네의 그림같은 느낌이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fk0t4/btsFAu7xAtM/1WktkPaDkVvfAcVmfgS3LK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fk0t4/btsFAu7xAtM/1WktkPaDkVvfAcVmfgS3LK/img.jpg&quot; data-alt=&quot;푸르빌 절벽 위 산책은 2년째 내 노트북 배경화면이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fk0t4/btsFAu7xAtM/1WktkPaDkVvfAcVmfgS3LK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFk0t4%2FbtsFAu7xAtM%2F1WktkPaDkVvfAcVmfgS3LK%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;435&quot; height=&quot;312&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;푸르빌 절벽 위 산책은 2년째 내 노트북 배경화면이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 모네의 그림을 좋아한다. 분명 현실을 담았음에도 그 순간의 기억, 기분, 냄새까지 담겨 있는, 한순간의 추억을 담아내기때문이다.&lt;/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;blockquote data-ke-style=&quot;style3&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;아래 문장들에서는 뭔가 수련같은 느낌을 받았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UG14A/btsFxXbL7W6/PVjSDUxe6qjSknvIzsQclk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UG14A/btsFxXbL7W6/PVjSDUxe6qjSknvIzsQclk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UG14A/btsFxXbL7W6/PVjSDUxe6qjSknvIzsQclk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUG14A%2FbtsFxXbL7W6%2FPVjSDUxe6qjSknvIzsQclk%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;352&quot; height=&quot;352&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;봄의 어둠 속 벚꽃은 마치 피부를 찢고 튀어나온 짓무른 살처럼 보였다. 정원은 그렇게 많은 살들의 달콤하고 무거운 부패로 가득했다.&amp;nbsp;&lt;br /&gt;...&lt;br /&gt;나는 방으로 들어와 창의 커튼을 닫았지만 방 안에도 봄의 향기가 가득했다. 봄의 향기는 모든 지표면에 가득 차있었다. 그러나 지금 그것은 나에게 부패를 연상시킬 따름이었다.&amp;nbsp;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;백사장에는 폭죽을 터뜨린 뒤 남은 종잇조각들이 흩어졌고 파도는 미친듯 굉음을 울리며 모래 끝자락에서 부서졌다. 비쩍 마른 개가 꼬리를 흔들며 다가와 뭐 먹을거 없나 하고 내가 피운 조그만 모닥불 주위를 어슬렁거리다가 아무것도 없다는 걸 알고는 체념한듯 발길을 돌렸다.&lt;/blockquote&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;blockquote data-ke-style=&quot;style3&quot;&gt;어떤 진리로도 사랑하는 것을 잃은 슬픔을 치유할 수는 없다.&amp;nbsp;&lt;br /&gt;어떤 진리도,&amp;nbsp;&lt;br /&gt;어떤 성실함도,&amp;nbsp;&lt;br /&gt;어떤 강인함도,&amp;nbsp;&lt;br /&gt;어떤 상냥함도,&amp;nbsp;&lt;br /&gt;&lt;br /&gt;그 슬픔을 치유할 수 없다.&amp;nbsp;&lt;br /&gt;우리는 그 슬픔을 다 슬퍼한 다음 거기에서 뭔가를 배우는 것뿐이고,&amp;nbsp;&lt;br /&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;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;+) 별로였던점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;책에 스토리와 별개로 외설적인 장면이 매우 몹시 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 필요한 장면이면 괜찮은데 뜬금없이 그런 장면이 많아서 좀 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별개로 하루키의 문체와 문장의 흡입력은 엄청나서 하루키의 다른 작품을 읽고 싶으면서도, 이 이유 때문에 다음 책을 고르기가 꺼려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>독서</category>
      <category>노르웨이의숲</category>
      <category>무라카미하루키</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/355</guid>
      <comments>https://0422.tistory.com/355#entry355comment</comments>
      <pubDate>Sat, 9 Mar 2024 17:30:41 +0900</pubDate>
    </item>
    <item>
      <title>React-ReactNative Webview통신을 Event-Driven에서 Request-Response형태로 확장하기</title>
      <link>https://0422.tistory.com/354</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqjLWC/btsFh68GwWa/Yxrm9ZB2FELQan8EhlSuT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqjLWC/btsFh68GwWa/Yxrm9ZB2FELQan8EhlSuT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqjLWC/btsFh68GwWa/Yxrm9ZB2FELQan8EhlSuT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqjLWC%2FbtsFh68GwWa%2FYxrm9ZB2FELQan8EhlSuT1%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;2416&quot; height=&quot;800&quot; data-origin-width=&quot;2416&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글들에서 &lt;a href=&quot;https://0422.tistory.com/347&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;React Webview와 ReactNative의 통신&lt;/a&gt;을 시켜보았고, &lt;a href=&quot;https://0422.tistory.com/353&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;&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;ReactNative-&amp;gt;React&lt;/b&gt; 통신의 경우 onLoad시 React Webview에서 필요한 데이터를 &lt;b&gt;한번에 주입시켜주는 형태&lt;/b&gt; 하나였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React-&amp;gt;ReactNative&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호출을 줄이고자 내활동을 저장하는 부분에 대해 네이티브 기기의 DB를 사용하는 부분을 적용시켰고, 이에 따라 Webview가 열린 후에 데이터를 요청해야하는 부분이 점점 커져갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;기존의 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 native DB에 있는 하나의 데이터만이 필요하여 ReactNative의 WebView컴포넌트가 열린 후, onLoad함수로 모든 데이터를 &lt;b&gt;한번에 집어넣는 형태&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://0422.tistory.com/353&quot;&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;h2 data-ke-size=&quot;size26&quot;&gt;문제상황&lt;/h2&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;신고한 게시글을 또 신고하게 해서는 안됐다. 해당 부분을 네이티브 DB에 저장해 뒀다가 신고 유무를 확인해주어야했다.&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;/li&gt;
&lt;li&gt;네이티브 DB에서 해당 글 신고했는지 확인&lt;/li&gt;
&lt;li&gt;신고했다면 신고하지 못하게 처리 / 신고하지 않았다면 신고&lt;/li&gt;
&lt;li&gt;신고했다면, 네이티브 DB에 해당 데이터 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 문제는&lt;b&gt; 내가 이전에 설계했던 CustomWebView에서는 신고 DB데이터를 딱한번, 웹뷰를 열때 받아오게 만들었다는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면, 새롭게 신고를 한 경우를 확인할 수가 없다.(&lt;b&gt;연속적으로 동일한 게시글에 신고를 하는 경우&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분이 매우 많아지기 시작했다. (내가 작성한 글/내가 좋아요누른 글/댓글을 작성한 글, 알림 피드 등등...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결방안 두가지&lt;/h3&gt;
&lt;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;onLoad로 받아온 DB데이터를 전역상태로 관리한다.&lt;/li&gt;
&lt;li&gt;WebView-Native통신을 WebView컴포넌트가 열린 후에도 가능하게 만든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 전역상태로 관리해볼까? 했으나, 상태가 점점 많아지고, 관리하기 힘들어지는 부분이 있어서 후자를 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 통신의 문제점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 통신의 문제점은 바로 WebView와 Native가 Event Driven이라는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국은 EventListener를 통해 동작하기에 Request-Response형태로 확장시켜 주어야했다.&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요청하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebView에서 Request하기 위한 방법을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 요청에 하나의 eventListener를 등록하는 형태로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, &lt;b&gt;요청에 대한 응답이 온 후에도 이게 남아있으면 callback함수가 여러번 수행되게 되므로, 응답을 받게되면 eventListener를 제거해주었다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709087979457&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function webViewRequest&amp;lt;T&amp;gt;(callback: () =&amp;gt; void) {
  const promise = new Promise&amp;lt;T&amp;gt;((resolve, reject) =&amp;gt; {
    const timer = setTimeout(() =&amp;gt; reject(new Error('Timeout')), 3000); //3초가 지나면 reject된다.

    const handler = (event: MessageEvent) =&amp;gt; {
      clearTimeout(timer);
      const data = JSON.parse(event.data);
      resolve(data);
      document.removeEventListener('message', handler as EventListener);
      window.removeEventListener('message', handler);
    };

    document.addEventListener('message', handler as EventListener);
    window.addEventListener('message', handler);
    callback();
  });

  return promise;
}&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RN에서는 onMessage 함수를 통해 React로 부터 받은 메시지를 확인할 수 있다.&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;현재 RN이 백엔드 처럼 작동하기에 express코드와 유사하게 코드를 작성해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QiJjr/btsF2DK9VuL/zRdnnkkSnajbJY461aC9kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QiJjr/btsF2DK9VuL/zRdnnkkSnajbJY461aC9kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QiJjr/btsF2DK9VuL/zRdnnkkSnajbJY461aC9kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQiJjr%2FbtsF2DK9VuL%2FzRdnnkkSnajbJY461aC9kk%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;268&quot; height=&quot;400&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 onMessage에서는 router로 넘겨주기만 하면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709088246769&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onMessage = async (event: WebViewMessageEvent) =&amp;gt; {
    const {nativeEvent} = event;
    const customWebViewEvent: CustomWebViewEventType = JSON.parse(
      nativeEvent.data,
    ); //타입에 대해서는 아래에서 더 설명하겠다.
    if (webViewRef.current) {
      router(
        customWebViewEvent,
        webViewRef.current,
        navigation,
        deviceToken,
        dispatch,
      );
    }
  };&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;h3 data-ke-size=&quot;size23&quot;&gt;동시에 다수의 요청을 하는 경우&lt;/h3&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;1118&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYWamd/btsFkXJWlFL/GovVm0lg8hYAcZtjAuqtT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYWamd/btsFkXJWlFL/GovVm0lg8hYAcZtjAuqtT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYWamd/btsFkXJWlFL/GovVm0lg8hYAcZtjAuqtT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYWamd%2FbtsFkXJWlFL%2FGovVm0lg8hYAcZtjAuqtT0%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;1118&quot; height=&quot;575&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 연속적으로 3개의 다른 요청을 하게되면, native의 postMessage가 한번만 수행되도 모든 이벤트 리스너가 수행되고, 해당 이벤트 리스너가 제거된다.&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;이러한 문제를 막기위해서 통신에 type이라는 구분자를 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 A이벤트를 보낼때 type을 담아서 보내고, RN에서 응답시에 동일한 type을 보내주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그럼 이제 type을 설계하면된다.&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;Request-Response type설계하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RN이 응답을 해주기때문에 RN에서 먼저 타입을 설계해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청시에 data body가 있는지에 따라 WebViewCommonEventType, WebViewDBEventType으로 나누어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709088467098&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface WebViewCommonEventType {
  type: WebViewEventTypeCategory | WebViewDBGetEvent;
}

export interface WebViewDBEventType {
  type:
    | WebViewDBSaveEvent
    | WebViewDBDeleteEvent
    | WebViewDBModifyEvent
  data: {[key: string]: unknown};
}&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_1709088485125&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type WebViewEventTypeCategory =
  | 'BACK'
  | 'LOGOUT';

type WebViewDBGetEvent ='GET_SOME_DATA';

export type WebViewDBSaveEvent ='SAVE_DATA'


type WebViewDBDeleteEvent = 'DELETE_DATA'

type WebViewDBModifyEvent = 'MODIFY_DATA';&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;h3 data-ke-size=&quot;size23&quot;&gt;RN에서 타입기반으로 작동하게 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;router-&amp;gt;controller-&amp;gt;service를 통해 메시지가 전달되고, 반대로 응답이 오는 구조다.&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;postMessage를 단순화하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제는 postMessage가 WebView.current에 붙어있는 함수라는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 분리해줄 필요가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 WebView를 받는 postMessage함수를 만들고, 이후에 이 함수에 WebView객체를 바인딩시켜줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1709088660086&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import WebView from 'react-native-webview';
import {CustomWebViewEventType} from './type';

export default function postMessage(
  webView: WebView&amp;lt;{}&amp;gt;,
  type: CustomWebViewEventType['type'],
  data?: unknown,
) {
  webView.postMessage(
    JSON.stringify({
      type,
      data,
    }),
  );
}&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;이 함수를 bind를 통해 router에서 인자로 받는 WebView컴포넌트를 고정시켜주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709088704429&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function router(
  ...
  WebView: WebView&amp;lt;{}&amp;gt;,
  ...
) {
  const send = postMessage.bind(null, WebView);
  }&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;이걸 Controller로 전달해주자.&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;Controller 설계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 DB만 조회하는 것이아니라 native의 특정 함수를 수행시키는 경우도 있어 commonController와 DBPostController로 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commonController는 다양한 인자를 받아야 하기에 switch를 사용하는 함수형태로, DBPostController는 객체로 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709089127080&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function router(
  customWebViewEvent: CustomWebViewEventType,
  WebView: WebView&amp;lt;{}&amp;gt;,
  navigation: NativeStackNavigationProp&amp;lt;StackNavigationType&amp;gt;,
  deviceToken: string,
  dispatch: any,
) {
  const send = postMessage.bind(null, WebView);
  const IS_DB_POST_EVENT = 'data' in customWebViewEvent;

  if (IS_DB_POST_EVENT) {
    const {type, data} = customWebViewEvent;
    return DBPostController[type](data, send);
  }

  return commonController(
    customWebViewEvent,
    send,
    navigation,
    deviceToken,
    dispatch,
  );
}&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;DBPostController의 타입설계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 위에서 설계한 CustomWebVIewEventType을 key로 갖고, value로 data와 send를 인자로 받는 함수를 갖는 객체형태로 Controller를 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;send는 위에서 바인딩한 send다.&lt;/p&gt;
&lt;pre id=&quot;code_1709088804570&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export type Controller&amp;lt;T extends string&amp;gt; = Record&amp;lt;
  T,
  (
    data: unknown,
    send: (type: CustomWebViewEventType['type'], data?: unknown) =&amp;gt; void, //postMessage를 바인딩한 send
  ) =&amp;gt; Promise&amp;lt;void&amp;gt;
&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; (타입을 지정했기에 자동완성이 되어 편하다.)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1709088911171&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const DBPostController: Controller&amp;lt;WebViewDBEventType['type']&amp;gt; = {
  SAVE_DATA: async (data, send) =&amp;gt; {
    await InsertService.data1(data);
    send('SAVE_DATA');
  },

  SAVE_DATA2: async (data, send) =&amp;gt; {
    await InsertService.data2(data);
    send('SAVE_DATA2');
  },
}&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;React에서 요청시 type적용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RN과 타입을 맞춰주기 위해 객체를 하나 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709094263142&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default {
  TOKEN: 'TOKEN',
  BACK: 'BACK',
  LOGOUT: 'LOGOUT',
  IDENTITY_VERIFY: 'IDENTITY_VERIFY',
  SAVE_DATA: 'SAVE_DATA',
  DELETE_DATA: 'DELETE_DATA',
  MODIFY_DATA: 'MODIFY_DATA',
  NOTIFICATION: 'NOTIFICATION',
};&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;기존의 WebViewRequest에서 type이 같을때만 eventListener를 제거하도록 처리해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1709094557491&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { WEBVIEW_EVENTS } from '@repo/constants';

interface WebViewResponse&amp;lt;T&amp;gt; {
  type: keyof typeof WEBVIEW_EVENTS;
  data: T;
}

export default function webViewRequest&amp;lt;T&amp;gt;(callback: () =&amp;gt; void, type: keyof typeof WEBVIEW_EVENTS) {
  const promise = new Promise&amp;lt;T&amp;gt;((resolve, reject) =&amp;gt; {
    const timer = setTimeout(() =&amp;gt; reject(new Error('Timeout')), 3000);

    const handler = (event: MessageEvent) =&amp;gt; {
      clearTimeout(timer);
      const { data, type: responseType }: WebViewResponse&amp;lt;T&amp;gt; = JSON.parse(event.data);
      if (responseType === type) { //responseType과 기존 요청의 type이 같아야만 제대로 동작
        resolve(data);
        document.removeEventListener('message', handler as EventListener);
        window.removeEventListener('message', handler);
      }
    };

    document.addEventListener('message', handler as EventListener);
    window.addEventListener('message', handler);
    callback();
  });

  return promise;
}&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;이렇게 하면 이제 webViewRequest가 아래와 같이 동작하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzgAm0/btsFlRvXaUO/p3RIEcuVPYfAuXNj0v9dNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzgAm0/btsFlRvXaUO/p3RIEcuVPYfAuXNj0v9dNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzgAm0/btsFlRvXaUO/p3RIEcuVPYfAuXNj0v9dNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzgAm0%2FbtsFlRvXaUO%2Fp3RIEcuVPYfAuXNj0v9dNK%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;1042&quot; height=&quot;568&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;568&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;이제 RN에 특정 데이터를 받고싶다면 이렇게 보내면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709093833299&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const data = await webViewRequest(() =&amp;gt; window.ReactNativeWebView.postMessage(JSON.stringify({ type:&quot;GET_DATA&quot; })), &quot;GET_DATA&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;그리고, RN에 특정 데이터를 추가하고싶다면 이렇게 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1709093955477&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; webViewRequest(() =&amp;gt; window.ReactNativeWebView.postMessage(JSON.stringify({ type:&quot;INSERT_DATA&quot;,data:&quot;some data...&quot; })), &quot;INSERT_DATA&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;또한, RN에서 응답데이터를 주는 경우 어떤 데이터인지 타입으로 확인하기도 어렵다.&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;따라서 이 함수를 래핑해주자. 동시에 제네릭을 통해 반환타입을 지정할 수 있게 만들었다. (axios처럼)&lt;/p&gt;
&lt;pre id=&quot;code_1709094009870&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const fireEvent = &amp;lt;T&amp;gt;(type: string) =&amp;gt;
  webViewRequest&amp;lt;T&amp;gt;(() =&amp;gt; window.ReactNativeWebView.postMessage(JSON.stringify({ type })), type as keyof typeof WEBVIEW_EVENTS);
const fireEventWithData = &amp;lt;T&amp;gt;(type: string, data: unknown) =&amp;gt;
  webViewRequest&amp;lt;T&amp;gt;(() =&amp;gt; window.ReactNativeWebView.postMessage(JSON.stringify({ type, data })), type as keyof typeof WEBVIEW_EVENTS);&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;객체를 통해 특정 WEBVIEW_EVENTS에 대한 함수를 미리 정의해두자.&lt;/p&gt;
&lt;pre id=&quot;code_1709094346896&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const nativeEvent = {
  TOKEN: () =&amp;gt; fireEvent&amp;lt;{ accessToken: string; refreshToken: string }&amp;gt;(WEBVIEW_EVENTS.TOKEN),
  GET_DATA: &amp;lt;T&amp;gt;() =&amp;gt; fireEvent&amp;lt;T&amp;gt;(WEBVIEW_EVENTS.GET_DATA),
  BACK: () =&amp;gt; fireEvent(WEBVIEW_EVENTS.BACK),
  LOGOUT: () =&amp;gt; fireEvent(WEBVIEW_EVENTS.LOGOUT),
  SAVE_DATA: (data: unknown) =&amp;gt; fireEventWithData(WEBVIEW_EVENTS.SAVE_DATA, data),
  DELETE_DATA: (data: unknown) =&amp;gt; fireEventWithData(WEBVIEW_EVENTS.DELETE_DATA, data),
  MODIFY_DATA: (data: unknown) =&amp;gt; fireEventWithData(WEBVIEW_EVENTS.MODIFY_DATA, data),
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 native.특정이벤트()형태로 바로 데이터를 얻어올 수 있다!&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;pre id=&quot;code_1709094470842&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useSomeQuery() {
  const getSomeData = async () =&amp;gt; {
    const result = await nativeEvent.GET_DATA&amp;lt;UserActionResponse[]&amp;gt;();
    const likes = await nativeEvent.GET_LIKE_DATA&amp;lt;UserActionResponse[]&amp;gt;();

    const feed: Feed[] = result.map((article) =&amp;gt; ({ ...article, liked: likes.some((like) =&amp;gt; like.id === article.id) }));
    return feed;
  };

  return useQuery({ queryKey: [REACT_QUERY_KEYS.SOME_DATA], queryFn: getSomeData, staleTime: 0 });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다하고나니 새삼 http요청/axios/express가 얼마나 잘 만든 라이브러리인지 알 수 있었다.&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;</description>
      <category>Frontend/문제 해결기</category>
      <category>eventDriven</category>
      <category>react</category>
      <category>REQUEST</category>
      <category>response</category>
      <category>RN</category>
      <category>통신</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/354</guid>
      <comments>https://0422.tistory.com/354#entry354comment</comments>
      <pubDate>Wed, 28 Feb 2024 13:40:55 +0900</pubDate>
    </item>
    <item>
      <title>Promise 지연평가로 비동기 문제 해결하기</title>
      <link>https://0422.tistory.com/353</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://0422.tistory.com/347&quot;&gt;이전 게시글&lt;/a&gt;에서 React Webview와 ReactNative의 통신을 시켜보았다.&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;이때의 방식은 WebView가 onLoad되었을때, React Webview에서 필요한 토큰을 주입하게 하는 형태였다.&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;하지만, 프로젝트가 커지고, 다양한 API요청이 많아짐에 따라 우리팀은 자연스럽게 비용문제가 발생할 수 있을거라는 걱정을 하게되었고, 이에 서버 API호출을 줄이고자 데이터를 받아오는 부분에서 네이티브 기기의 DB를 사용하기로 하였다.&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;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기 DB에 저장된 데이터를 받아와서, 웹뷰에서 보여줘야했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;다운로드 (1).png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EsUnF/btsF3lQJ1Vn/NWC6KIo2gbOiGfiKNVrKk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EsUnF/btsF3lQJ1Vn/NWC6KIo2gbOiGfiKNVrKk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EsUnF/btsF3lQJ1Vn/NWC6KIo2gbOiGfiKNVrKk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEsUnF%2FbtsF3lQJ1Vn%2FNWC6KIo2gbOiGfiKNVrKk0%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;648&quot; height=&quot;606&quot; data-filename=&quot;다운로드 (1).png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때의 DB테이블은 우리팀원이 이미 만들어 둔 상태였기에, 나는 이것을 조회해서 웹뷰에 넣어만 주면 됐다.&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;DB조회는 비동기적으로 일어난다. &lt;/b&gt;Promise를 resolve시켜야 실제 데이터를 얻어올 수 있다.&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;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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;CustomWebView&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 서비스는 여러개의 웹뷰 화면을 보여주기때문에 CustomWebView를 구성해주었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 여기에 data를 주입시킬 수 있어야했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086014326&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function CustomWebView({uri}: {uri: string}) {
  const webViewRef = useRef&amp;lt;WebView&amp;gt;(null);
  const onLoad = async () =&amp;gt; {
    const accessToken = await storageGetValue('accessToken'));
    const refreshToken = await storageGetValue('refreshToken');
    if (webViewRef.current) {
      webViewRef.current.postMessage(
        JSON.stringify({accessToken, refreshToken}),
      );
    }
  };
  
  ...
  return &amp;lt;WebView
        uri={{uri}}
        ref={webViewRef as any}
        onLoad={onLoad}
      /&amp;gt;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;현상 파악하기&lt;/h2&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;DB데이터를 비동기적으로 조회한다.&lt;/li&gt;
&lt;li&gt;웹뷰 컴포넌트가 동기적으로 실행된다.&lt;/li&gt;
&lt;li&gt;onLoad될때 데이터를 주입시킨다. 이때, 데이터는 Promise면 안된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 WebView로 전달할때가 아니라 onLoad시에 Promise를 resolve시켜주면된다!&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;CustomWebView props추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 주입받을 데이터를 props로 받아주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때의 데이터는 그냥 객체가 아니라 Promise형태의 객체를 받게하여, onLoad시에 Promise를 평가하도록 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086124959&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface CustomWebViewProps {
  uri: string;
  additionalData?: AdditionalData;
}

export default function CustomWebView({
  uri,
  additionalData,
}: CustomWebViewProps) {
...
}&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;따라서 additionalData의 데이터 타입을 이렇게 설계해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086157900&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface AdditionalData {
  [key: string]: Promise&amp;lt;unknown&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;그리고 onLoad에서 해당 Promise를 resolve시킨 후에, webViewRef.current.postMessage로 WebView에 주입시켜주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086262742&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onLoad = async () =&amp;gt; {
    setLoading(false);
    const accessToken = (await storageGetValue('accessToken')) || '';
    const refreshToken = await storageGetValue('refreshToken');

    if (additionalData)
      await Promise.all(
        Object.entries(additionalData).map( //객체 value를 resolve시키고, value를 resolve된 값으로 변경한다.
          async ([key, value]: [any, Promise&amp;lt;any&amp;gt;]) =&amp;gt; {
            additionalData[key] = await value;
          },
        ),
      );

    if (webViewRef.current) {
      webViewRef.current.postMessage(
        JSON.stringify({accessToken, refreshToken, ...additionalData}),
      );
    }
  };&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;이제 화면단에서 웹뷰를 열때 아래와 같이 다양한 데이터를 객체로 묶어서 주입해줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086364895&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const Page = () =&amp;gt; {
  const data = {
    schools: getSchools(), //Promise를 반환하는 비동기 DB조회 함수 호출
  };

  return (
    &amp;lt;CustomWebView
      uri=&quot;http://uri...&quot;
      additionalData={data}
    /&amp;gt;
  );
};&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;React에서 받기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Provider를 통해 데이터를 받도록 처리했다.&lt;/p&gt;
&lt;pre id=&quot;code_1709086656391&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ReactNode, useEffect, useState } from 'react';
import { School, SchoolInfo } from '@/hooks/useSchoolData';

export default function SchoolProvider({ children }: { children: ReactNode }) {
  const [schoolData, setSchoolData] = useState&amp;lt;SchoolInfo[]&amp;gt;([]);

  const schoolDataHandler = (event: MessageEvent) =&amp;gt; {
    const { schools } = JSON.parse(event.data);
    setSchoolData(schools);
  };

  useEffect(() =&amp;gt; {
    document.addEventListener('message', schoolDataHandler as EventListener);
    window.addEventListener('message', schoolDataHandler);
  }, []);

  return &amp;lt;School.Provider value={schoolData}&amp;gt;{children}&amp;lt;/School.Provider&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>PROMISE</category>
      <category>reactnative</category>
      <category>WebView</category>
      <category>비동기</category>
      <category>웹뷰</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/353</guid>
      <comments>https://0422.tistory.com/353#entry353comment</comments>
      <pubDate>Wed, 28 Feb 2024 11:18:16 +0900</pubDate>
    </item>
    <item>
      <title>무기여 잘 있거라</title>
      <link>https://0422.tistory.com/352</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ItPm/btsEFOtlnvQ/eO9F5wCfbGeYnBGqDKAgHk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ItPm/btsEFOtlnvQ/eO9F5wCfbGeYnBGqDKAgHk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ItPm/btsEFOtlnvQ/eO9F5wCfbGeYnBGqDKAgHk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ItPm%2FbtsEFOtlnvQ%2FeO9F5wCfbGeYnBGqDKAgHk%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;302&quot; height=&quot;515&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 년도에는 정보성 글, 자기계발서들에서 의도적으로 벗어나 소설을 읽어 보려 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 서점에서 민음사코너를 보게 됐다. 쭉 훑다가 눈에 들어온 것이 무기여 잘있거라였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어니스트 헤밍웨이가 그렇게 대가로 유명하다는데 나는 그의 글을 한 번도 읽어본 적이 없었다는걸 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과연 이 사람은 어떤 내용의 글을 어떻게 적었을지 궁금하여 선택하게 된 책이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&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;내가 지금까지 읽었던 고전 소설들은 대부분 어떤 메시지를 담으려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/149&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인생은 자신만의 양탄자 무늬를 짜는 것 이라던가&lt;/a&gt;...&amp;nbsp; &lt;a href=&quot;https://0422.tistory.com/54&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 무기여 잘있거라는 정말 사실만을 담았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사람은 죽는다. 어찌됐던, 다양한 이유로.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 이상의 수식도, 미사여구도, 교훈도 없이 이게 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘이 함께 오래오래 행복하게 살았답니다는 동화속의 이야기다. 인간으로 태어난 이상, 인간이 맞을 수 있는 결말은 단 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 주제를 작가 특유의 하드보일드 문체로 풀어내니 여운이 정말 엄청나다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 주인공, 캐릭터들의 감정을 묘사한 작품들은 캐릭터의 감정에 집중하게 되어 오히려 제 3자의 입장에서 주인공의 감정에 공감했었다면, 이 작품은 사실만을 전달하여 오히려 내가 그 현장에 있는 것과 같은 경험을 할 수 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&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;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;전쟁의(Arms) 전우들, 캐서린의 품(Arms)에 작별을 고하며, 잘 있으라 말해주는게 전부였다.&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>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/352</guid>
      <comments>https://0422.tistory.com/352#entry352comment</comments>
      <pubDate>Tue, 13 Feb 2024 16:03:11 +0900</pubDate>
    </item>
    <item>
      <title>무한 스크롤 (3) - 피드 데이터 최신화하기</title>
      <link>https://0422.tistory.com/351</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6VEEV/btsD3o2KP8t/SBySy9dllvvlAohch6nOak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6VEEV/btsD3o2KP8t/SBySy9dllvvlAohch6nOak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6VEEV/btsD3o2KP8t/SBySy9dllvvlAohch6nOak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6VEEV%2FbtsD3o2KP8t%2FSBySy9dllvvlAohch6nOak%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;1029&quot; height=&quot;516&quot; data-origin-width=&quot;1029&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 useInfinityQuery를 사용하여 무한 스크롤 피드를 구현해보았다.&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;새롭게 글을 작성/수정/삭제 하는경우 피드데이터는 어떻게 최신화 되어야 하는가?&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세부 글보기에서 좋아요/댓글을 추가/삭제하는 경우 피드데이터는 어떻게 최신화 되어야 하는가?&lt;/b&gt;&lt;/li&gt;
&lt;/ol&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 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;이걸 직접 해결해 볼 수 있는 기회가 이렇게나 빨리 오게 될 줄이야....&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://0422.tistory.com/331&quot;&gt;https://0422.tistory.com/331&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706438900832&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;당근마켓 윈터테크 인턴십 그룹플랫폼 직무면접 불합격 회고&quot; data-og-description=&quot;그룹 플랫폼 팀에 지원 이번 윈터테크 인턴십은 프론트엔드가 코어, 광고, 그룹플랫폼 3가지 형태로 구성되어있었다. 나는 이 중 광고와 그룹플랫폼에 지원했다. 그 중, 그룹플랫폼에 조금 더 신&quot; data-og-host=&quot;0422.tistory.com&quot; data-og-source-url=&quot;https://0422.tistory.com/331&quot; data-og-url=&quot;https://0422.tistory.com/331&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cgnxTj/hyVb0iLL2T/Kxn69O0E8NUSp1ZGsfo1j1/img.png?width=696&amp;amp;height=645&amp;amp;face=0_0_696_645,https://scrap.kakaocdn.net/dn/caXsBO/hyVb792xDk/TqjWnYyIXxodxwDTu8ojZ1/img.png?width=696&amp;amp;height=645&amp;amp;face=0_0_696_645,https://scrap.kakaocdn.net/dn/cl4zht/hyVb46xAZJ/z7YJMRvm0mO2VyQy6CXsk1/img.png?width=729&amp;amp;height=665&amp;amp;face=0_0_729_665&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/331&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0422.tistory.com/331&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cgnxTj/hyVb0iLL2T/Kxn69O0E8NUSp1ZGsfo1j1/img.png?width=696&amp;amp;height=645&amp;amp;face=0_0_696_645,https://scrap.kakaocdn.net/dn/caXsBO/hyVb792xDk/TqjWnYyIXxodxwDTu8ojZ1/img.png?width=696&amp;amp;height=645&amp;amp;face=0_0_696_645,https://scrap.kakaocdn.net/dn/cl4zht/hyVb46xAZJ/z7YJMRvm0mO2VyQy6CXsk1/img.png?width=729&amp;amp;height=665&amp;amp;face=0_0_729_665');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;당근마켓 윈터테크 인턴십 그룹플랫폼 직무면접 불합격 회고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;그룹 플랫폼 팀에 지원 이번 윈터테크 인턴십은 프론트엔드가 코어, 광고, 그룹플랫폼 3가지 형태로 구성되어있었다. 나는 이 중 광고와 그룹플랫폼에 지원했다. 그 중, 그룹플랫폼에 조금 더 신&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0422.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;글을 새롭게 작성하거나 수정/삭제하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 글을 새롭게 작성하거나/수정/삭제하는 경우다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자입장에서 글을 쓰고,수정하고, 삭제를 한다면 그 사용자의 목적은&lt;b&gt;&amp;nbsp;내 글이 어떻게 되었는가, 잘 업데이트 되었는가&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 피드 데이터를 처음부터 받아오고, 최신 피드데이터를보아야 하므로 최상단으로 이동시켜야 한다고 생각했다.&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;그렇다면 queryKey의 데이터를 무시하고 새롭게 피드데이터를 받아와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 invalidateQueries 메서드로 가능하다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;invalidateQueries&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React-query에서는 queryClient의 invalidateQueries 메서드를 통해 캐시데이터를 무효화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 해당 쿼리가 마운트된 상황일때 queryFn을 통해 데이터를 다시 받아오게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1706432475757&quot; class=&quot;css&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  queryClient.invalidateQueries({ queryKey: [무효화할 query Key] });&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;&lt;span&gt;&amp;nbsp;&lt;/span&gt;QueryKey에 저장된 데이터를 다시 받아오게 만드는 것이다.&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;예시로 글을 삭제하는 경우, mutate가 성공적으로 수행되면 무한 스크롤 데이터의 캐시 데이터를 무효화시켜주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706432475757&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useToast } from '@repo/toast';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import REACT_QUERY_KEYS from '@/constants/REACT_QUERY_KEYS';
import useAuthAxios from '@/hooks/useAuthAxios';

export default function useDeleteArticleMutation() {
  const openToast = useToast();
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const authAxios = useAuthAxios();

  const deleteThread = (id: number) =&amp;gt; {
    return authAxios.delete(`/apiURL/${id}`);
  };

  return useMutation({
    mutationFn: deleteThread,
    onSuccess: () =&amp;gt; {
      openToast({ type: 'information', content: '삭제 완료' });
      navigate(-1);
      queryClient.invalidateQueries({ queryKey: [REACT_QUERY_KEYS.ENTIRE_THREADS] });
    },
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 글 작성/수정/삭제시, 진행중이던 무한스크롤에서 최상단으로 이동하게 되며 최신 데이터를 받아올 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋아요/댓글/답글을 추가/삭제 하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, &lt;b&gt;단일 글&lt;/b&gt;에서 좋아요, 댓글, 답글을 다는 이벤트가 일어났을때는 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때는 글작성/수정/삭제와 달리 사용자의 의도가 &lt;b&gt;피드 탐색&lt;/b&gt;에 더욱 초점이 맞춰진 상태라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 캐시 데이터를 무효화하면 탐색하던 페이지로 돌아오는게 아닌, 최상단으로 올라가게 될 것이므로 &lt;b&gt;사용자가 불편함을 느끼게 될 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다른 방법이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;setQueryData&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Query에서는 queryKey내부 데이터를 변경할 수 있는 메서드도 제공한다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 이전의 쿼리데이터도 가져올 수 있고, 새롭게 쿼리 데이터를 지정할 수 도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706435329458&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;queryClient.setQueryData([querKey], (이전 쿼리 데이터) =&amp;gt; (새롭게 지정할 쿼리 데이터));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 그럼 새로운 데이터만 제대로 가져올 수 있다면 피드데이터를 수정해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 글 데이터를 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트는 피드에서 단일 글을 눌러 해당 글로 이동할 때, 단일 글 요소의 id를 통해 서버에서 데이터를 받아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 댓글, 좋아요 등 사용자의 actions이 있을 시 해당 글의 캐시를 무효화 하는 형태로 단일 글의 데이터를 최신상태로 유지했다.&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;단일글 받아오기(useThreadQuery)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706436602179&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useThreadQuery(threadId: number) {
  const authAxios = useAuthAxios();

  const getThread = async () =&amp;gt; {
    const result = await authAxios.get&amp;lt;ArticleRawData&amp;gt;(`/apiURL/${threadId}`);
    return result.data;
  };

  return useQuery({ queryKey: [REACT_QUERY_KEYS.THREAD, threadId], queryFn: getThread });
}&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;좋아요 클릭시 캐시 무효화 (useLikeArticleMutation)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1706436712193&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useLikeArticleMutation() {
  const queryClient = useQueryClient();
  const authAxios = useAuthAxios();

  const likeThread = async (threadId: number) =&amp;gt; {
    await authAxios.post(`/likeApiURL/?id=${threadId}`);
    return threadId;
  };

  return useMutation({
    mutationFn: likeThread,
    onSuccess: (threadId) =&amp;gt; {
    //mustate가 제대로 일어나면 해당 id 글의 캐시를 무효화한다.
      queryClient.invalidateQueries({ queryKey: [REACT_QUERY_KEYS.THREAD, threadId] });
    },
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 최신데이터를 받아왔을 때, 피드데이터를 동기화 시켜주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떻게 동기화 할 것인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 모든 페이지를 순회하면서 해당 글 id를 통해 요소를 찾을 수도 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그것보다는 요소 클릭시 해당 요소의 무한 스크롤 페이지 index를 기록하고, 해당 페이지 내에서 탐색하는 것이 효과적이라고 판단했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;714&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6dKO3/btsD4H1NSil/X3OoMJBMHgVS33LTbKrUK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6dKO3/btsD4H1NSil/X3OoMJBMHgVS33LTbKrUK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6dKO3/btsD4H1NSil/X3OoMJBMHgVS33LTbKrUK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6dKO3%2FbtsD4H1NSil%2FX3OoMJBMHgVS33LTbKrUK1%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;740&quot; height=&quot;443&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;714&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1706437413649&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function Threads() {
  const { data, isFetchingNextPage, fetchNextPage } = useThreadsQuery();
  // 특정 글 클릭시 전역상태 변경하는 함수
  const setPageIndex = usePageIndex((state) =&amp;gt; state.setPageIndex);
  ...
  return (
  	...
        {data.pages.map((page, pageIndex) =&amp;gt;
          ... // 클릭시 전역상태 변경
              &amp;lt;FeedArticle data={feedData} onLinkMove={() =&amp;gt; setPageIndex(pageIndex)} /&amp;gt;
          ...
          )),
        )}
    ...
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 단일글을 불러올때, 전역 pageIndex를 기반으로 해당 글을 탐색한 후, 최신 값으로 변경시켜주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706437974729&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useThreadQuery(threadId: number) {
  const authAxios = useAuthAxios();
  const queryClient = useQueryClient();
  const pageIndex = usePageIndex((state) =&amp;gt; state.pageIndex);
  
  //최신화한 피드데이터 얻기
  const changeFeedArticleData = (feedData: InfiniteData&amp;lt;FeedData&amp;gt;, newThreadData: ArticleRawData) =&amp;gt; {
    const newPage = [...feedData.pages];
    const articleData = newPage[pageIndex].posts.find((post) =&amp;gt; post.id === threadId); //해당 페이지에서 현재 글 찾기
    if (articleData) { // 데이터 갱신
      articleData.liked = newThreadData.liked; //좋아요 여부
      articleData.likeCount = newThreadData.likeCount; //좋아요 수 갱신
      articleData.commentCount = newThreadData.commentCount; //댓글 개수 갱신
      ...
    }
    return newPage;
  };

  const getThread = async () =&amp;gt; {
    const result = await authAxios.get&amp;lt;ArticleRawData&amp;gt;(`/apiURL/${threadId}`);

    if (queryClient.getQueryState([REACT_QUERY_KEYS.THREADS]))
      queryClient.setQueryData([REACT_QUERY_KEYS.THREADS], (feedData: InfiniteData&amp;lt;FeedData&amp;gt;) =&amp;gt; ({
        pages: changeFeedArticleData(feedData, result.data), //기존 피드데이터에서 새로운 피드데이터로 갱신
        pageParams: feedData.pageParams,
      }));
    return result.data;
  };

  return useQuery({ queryKey: [REACT_QUERY_KEYS.THREAD, threadId], queryFn: getThread });
}&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;</description>
      <category>Frontend/문제 해결기</category>
      <category>infinityQuery</category>
      <category>react-query</category>
      <category>setQueryData</category>
      <category>무한스크롤</category>
      <category>최신화</category>
      <category>캐시</category>
      <category>피드</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/351</guid>
      <comments>https://0422.tistory.com/351#entry351comment</comments>
      <pubDate>Sun, 28 Jan 2024 19:48:23 +0900</pubDate>
    </item>
    <item>
      <title>무한스크롤 (2) - 데이터 요청과 캐시 처리 (react-query)</title>
      <link>https://0422.tistory.com/350</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전글에서 무한스크롤에 필요한 두가지 조건을 정리했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;사용자가 페이지 하단에 도달했을때&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;콘텐츠가 계속 로드&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 조건은 이전글에서 완료했으니, 2번째 미션을 해결할 차례다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 콘텐츠가 계속 로드되게 만들 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 페이지네이션 형태로 제공하는 방법은 크게&amp;nbsp; cursor 기반과 offset 기반, 두가지 방법이 존재한다.&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;offset 기반&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;페이지단위로 구분해서 구현이 간단하나 데이터 중복문제(중간에 글이 써지는경우)가 발생한다.&lt;/li&gt;
&lt;li&gt;offset값이 커지면 앞의 데이터를 모두 읽어야해서 DB상 성능문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cursor 기반
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;cursor라는 사용자에게 마지막으로 응답해준 마지막 데이터 식별자 값을 기억하여 페이지네이션을 구현한다.&lt;/li&gt;
&lt;li&gt;offset에서 1억 +10번달라고 하면 1억 10개의 데이터를 읽지만, 커서기반에서는 1억번부터(cursor 데이터에 기록) 10개의 데이터만 읽게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능상의 이점때문에 우리 프로젝트는 cursor기반을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useState + useEffect로 간단하게 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 데이터 패칭을 하는 형태로 간단하게 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next가 true면 다음 cursor를 set하고 요소가 보일때 리패칭 해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1706336248530&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface FetchData {
  posts: { id: number; title: string; content: string }[];
  cursor: string;
  next: boolean;
}&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;받아온 데이터, 커서, refetch유무, next정보를 state로 구성해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 다른 state로 구성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1706336355401&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function FetchingComponent() {
  const [data, setData] = useState&amp;lt;FetchData['posts']&amp;gt;();
  const [cursor, setCursor] = useState('');
  const [refetch, setRefetch] = useState(true);
  const [next, setNext] = useState(true);
  ...
}&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;그리고 내부적으로 fetch할 함수를 만들어주자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useCallback으로 cursor정보가 바뀔때만 함수가 재선언되도록 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706336401872&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const getFeed = useCallback(async () =&amp;gt; {
    const result = await axios.get&amp;lt;FetchData&amp;gt;(`/post${cursor &amp;amp;&amp;amp; `?cursor=${cursor}`}`);
    return result.data;
  }, [cursor]);&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;그리고 useEffect를 통해 refetch가 true고 next가 있을때 데이터를 패칭되도록 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1706336503008&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    if (refetch &amp;amp;&amp;amp; next)
      getFeed().then((res: FetchData) =&amp;gt; {
        if (data) setData([...data, ...res.posts]); //데이터 이어붙이기 
        else setData(res.posts); //첫 데이터 받아오기
        if (!res.next) setNext(false); //받아온 데이터가 next가 없으면 setNext를 false로
        setCursor(res.cursor); //cursor정보 업데이트
        setRefetch(false); //모든 작업이 끝나면 refetch를 false로 변경
      });
  }, [refetch]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 요소가 화면에 들어오면 refetch를 true로 만들어 주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시글에서 만들어둔 useRefFocusEffect를 사용하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706337080652&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function FetchingComponent() {
  ...
  const [refetch, setRefetch] = useState(true);
  const { elementRef } = useRefFocusEffect&amp;lt;HTMLDivElement&amp;gt;(() =&amp;gt; setRefetch(true), []);
  ...

  if (data)
    return (
      &amp;lt;div&amp;gt;
        {data.map((article) =&amp;gt; (
          &amp;lt;div className=&quot;mt-10&quot;&amp;gt;
            &amp;lt;div&amp;gt;제목: {article.title}&amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;내용: {article.content}&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        ))}
        &amp;lt;div ref={elementRef}&amp;gt;이게 보이면 리패칭&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&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;/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;돌아오게되면 첫 cursor 요청부터 다시 시작해야한다는 점이다.&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요청수도 증가하게된다.&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;전역상태가 있다면, fetch 하지 말것&lt;/li&gt;
&lt;li&gt;특정 상황에 데이터를 다시 fetch해올 것&lt;/li&gt;
&lt;li&gt;이 데이터를 cursor별로 관리할 수 있어야할 것&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 전역상태로 서버데이터를 저장해주는 라이브러리가 이미 존재한다. 바로 리액트 쿼리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 나는 리액트 쿼리를 도입하여 피드데이터를 관리하고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;React-Query&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 쿼리는 서버로 부터 받아온 데이터를 전역 상태 형태로 관리할 수 있게 도와주는 라이브러리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Query Key별로 데이터를 저장해둘 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 언마운트 될때도 상태정보가 유지가 되므로, api호출을 줄일 수 있게 된다.&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;a href=&quot;https://www.timegambit.com/blog/digging/react-query/01&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.timegambit.com/blog/digging/react-query/01&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706337975099&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[React Query] useQuery 동작원리(1)&quot; data-og-description=&quot;Tanstack Query(React Query)의 useQuery의 동작원리를 분석했습니다. 이 포스트에서는 중요한 객체들간의 관계와 QueryObserver의 생성에 대해 다룹니다.&quot; data-og-host=&quot;www.timegambit.com&quot; data-og-source-url=&quot;https://www.timegambit.com/blog/digging/react-query/01&quot; data-og-url=&quot;https://www.timegambit.com/blog/digging/react-query/01&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baxSex/hyVcarWak5/CfdHe7VpZihkwlg5dqnM7k/img.png?width=716&amp;amp;height=374&amp;amp;face=0_0_716_374,https://scrap.kakaocdn.net/dn/K9RxZ/hyVb1Piy23/rINxK6TQkXsVQwzzkzRoa1/img.png?width=716&amp;amp;height=374&amp;amp;face=0_0_716_374&quot;&gt;&lt;a href=&quot;https://www.timegambit.com/blog/digging/react-query/01&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.timegambit.com/blog/digging/react-query/01&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baxSex/hyVcarWak5/CfdHe7VpZihkwlg5dqnM7k/img.png?width=716&amp;amp;height=374&amp;amp;face=0_0_716_374,https://scrap.kakaocdn.net/dn/K9RxZ/hyVb1Piy23/rINxK6TQkXsVQwzzkzRoa1/img.png?width=716&amp;amp;height=374&amp;amp;face=0_0_716_374');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[React Query] useQuery 동작원리(1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Tanstack Query(React Query)의 useQuery의 동작원리를 분석했습니다. 이 포스트에서는 중요한 객체들간의 관계와 QueryObserver의 생성에 대해 다룹니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.timegambit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;useQuery사용해서 구성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useQuery를 간단하게 설명하자면, 아래와 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1706339797483&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const result=useQuery({queryKey:쿼리키, queryFn:패칭함수});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 queryKey에 패칭해온 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 cursor 정보마다 queryKey를 구성해주면 된다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 cursor정보는 런타임에 결정되므로 useQuery 훅을 동적으로 호출해야한다는 문제가 생긴다.&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 Fiber객체의 훅 연결리스트 순서보장 문제), 쿼리키에는 피드 전체 데이터를 저장하되, cursor별로 페이지를 분리하여 저장하면 될 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1706428929864&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface FeedPageData{ 
	id: number; 
    title: string; 
    content: string 
};

type Cursor=string;

interface FeedData{
	pages:&amp;lt;Cursor,FeedPageData[]&amp;gt;[],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 구성하고, useQuery에서 데이터를 패치하는 형태로 구성할 수 있을것이다.&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-Query에서는 이미 무한스크롤을 위한 Query를 제공한다. 바로&lt;b&gt; InfinityQuery&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;useInfinityQuery&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useInfiityQuery는 지금까지 우리가 고민했던 모든것을 담아놓은 훅이다.&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;queryKey에 데이터 캐싱&lt;/li&gt;
&lt;li&gt;데이터 페이지별로 관리&lt;/li&gt;
&lt;li&gt;초기 cursor param&lt;/li&gt;
&lt;li&gt;다음 데이터를 불러올 fetch함수를 만들기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 설정하여 호출하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1706429477526&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useInfiniteQuery({
    queryKey: //쿼리키,
    initialPageParam: //초기 param,
    queryFn: ({ pageParam}) =&amp;gt; getParamThread(pageParam), //param을 받아서 queryFn에게 넘겨주는 형태
    getNextPageParam: (queryFn으로 받아온 데이터)=&amp;gt;다음 param을 return하도록 구성
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return 값은 data, isFetchingNextPage, fetchNextPage등을 제공한다.&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;data는 page와 pageParams로 구성되며 실제 데이터는 page내부에 배열형태로 들어가있다.&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;fetchNextPage를 통해 다음 fetch함수를 실행시키고, page를 추가할 수 있다.&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;나같은 경우 전체 피드를 받아오는 useInfinityQuery를 훅으로 래핑해서 사용했다.&lt;/p&gt;
&lt;pre id=&quot;code_1706429818717&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useInfiniteQuery } from '@tanstack/react-query';
import REACT_QUERY_KEYS from '@/constants/REACT_QUERY_KEYS';
import useAuthAxios from '@/hooks/useAuthAxios';
import { FeedData } from './type';

export default function useThreadsQuery() {
  const authAxios = useAuthAxios();

  const getParamThread = async (param?: string) =&amp;gt; {
    const result = await authAxios.get&amp;lt;FeedData&amp;gt;(`/apiURL${param &amp;amp;&amp;amp; `?cursor=${param}`}`);
    return result.data;
  };

  return useInfiniteQuery({
    queryKey: [REACT_QUERY_KEYS.ENTIRE_THREADS],
    initialPageParam: '',
    queryFn: ({ pageParam = '' }) =&amp;gt; getParamThread(pageParam),
    getNextPageParam: (lastPage) =&amp;gt; (lastPage.next ? lastPage.cursor : undefined),
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 받아온 데이터를 아래와 같이 컴포넌트를 구성해 렌더링해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706429938836&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { FeedArticle, Loading } from '@repo/components';
import useEntireThreadsQuery from '@/apis/useEntireThreadsQuery';
import useRefFocusEffect from '@/hooks/useRefFocusEffect';
import InfiniteLoading from './InfiniteLoading';

export default function EntireFeed() {
  const { data, isFetchingNextPage, fetchNextPage } = useEntireThreadsQuery();
  const { elementRef } = useRefFocusEffect&amp;lt;HTMLDivElement&amp;gt;(fetchNextPage, [data]);

  if (!data) return &amp;lt;Loading /&amp;gt;;
  return (
    &amp;lt;&amp;gt;
      {data.pages.map((page) =&amp;gt;
        page.posts.map((feedData, feedIndex) =&amp;gt; (
          &amp;lt;div className=&quot;border-b border-[#232636] py-2&quot; key={feedIndex}&amp;gt;
            &amp;lt;FeedArticle data={feedData} /&amp;gt;
          &amp;lt;/div&amp;gt;
        )),
      )}
      &amp;lt;div className=&quot;w-full justify-center flex items-center py-2&quot;&amp;gt;
      {// 여기가 보이면 다음 cursor로 요청한다}
        &amp;lt;InfiniteLoading ref={elementRef} isLoading={isFetchingNextPage} /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 잘 작동되는 것을 확인할 수 있다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새로운 문제 - 피드 데이터 최신화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이게 끝이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫번째&lt;/b&gt;는 새롭게 &lt;b&gt;글을 작성/수정/삭제&lt;/b&gt;하는경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두번째&lt;/b&gt;는 세부글에서 &lt;b&gt;좋아요/댓글을 추가/삭제&lt;/b&gt;하는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피드에서 세부 게시글을 눌러서 세부 글을 볼때, 댓글이나 좋아요를 남긴후, 피드로 돌아가게되면 피드에는 전혀 반영이 되지않은 것을 볼 수 있다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6QWmZ/btsD5xkiPrg/S7GBq8C9TL4DkmoPQOk0O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6QWmZ/btsD5xkiPrg/S7GBq8C9TL4DkmoPQOk0O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6QWmZ/btsD5xkiPrg/S7GBq8C9TL4DkmoPQOk0O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6QWmZ%2FbtsD5xkiPrg%2FS7GBq8C9TL4DkmoPQOk0O0%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;678&quot; height=&quot;435&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;735&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;다음 게시글에서 이 두가지 문제를 해결해 볼 것이다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>react</category>
      <category>react-query</category>
      <category>useInfinityQuery</category>
      <category>useQuery</category>
      <category>무한스크롤</category>
      <category>피드</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/350</guid>
      <comments>https://0422.tistory.com/350#entry350comment</comments>
      <pubDate>Sun, 28 Jan 2024 17:44:17 +0900</pubDate>
    </item>
    <item>
      <title>무한 스크롤 (1) - 하단 감지하기 (Scroll Event vs Intersection Observer)</title>
      <link>https://0422.tistory.com/349</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티 서비스를 만들고 있는데, 피드를 만들어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 &lt;b&gt;당근마켓 인턴십의 사전 질문&lt;/b&gt;이기도 했던&lt;b&gt; 세부 글보기 에서 좋아요/댓글이 추가/삭제된 경우 어떻게 피드에 반영할 것인가?&lt;/b&gt;라는 질문에 직접 구현해보며 답할 수 있는 좋은 기회라고 생각해서 구현하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한스크롤에 필요한 것들을 하나씩 확인해보면서 구현할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;필요한 것 알아내기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한 스크롤은 사용자가 페이지 하단에 도달했을때 콘텐츠가 계속 로드되는 사용자 경험 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 구현에 필요한 두가지 필수 조건을 알 수 있다.&lt;/p&gt;
&lt;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;/ol&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;이 게시글에서는 1번 항목에 대해 어떻게 구현할 것인지를 고민해보았다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자가 페이지 하단에 도달했음을 감지하기&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Scroll Event (onScroll)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 생각했을때, scroll Event가 일어났을때마다 최하단에 도달했는지를 확인한다면 해결되는 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scrollHeight, scrollTop, clientHeight 세가지 속성을 사용해서 페이지 하단에 도달했음을 확인할 수 있다.&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;scrollHeight, scrollTop, clientHeight&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 쉽게 그림을 그려왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clientHeight은 요소의 보이는 높이값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 scrollTop은 스크롤바의 위치를, scrollHeight는 scrollBar가 없을때(요소를 쫙 폈을때) 요소의 높이값이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9dv9P/btsD3LpEezj/y9tDpNpR9ZNOLpg5R2Uuzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9dv9P/btsD3LpEezj/y9tDpNpR9ZNOLpg5R2Uuzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9dv9P/btsD3LpEezj/y9tDpNpR9ZNOLpg5R2Uuzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9dv9P%2FbtsD3LpEezj%2Fy9tDpNpR9ZNOLpg5R2Uuzk%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;1298&quot; height=&quot;733&quot; data-origin-width=&quot;1298&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 세가지 요소를 조합해서 맨 아래에 도착했는지 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대스크롤 가능요소까지 스크롤하게되면 scrollHeight-scrollTop이 clientHeight와 같아지는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccCj3r/btsD4ITz3I5/ER6xcpsQmqbwUoCQNQjLJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccCj3r/btsD4ITz3I5/ER6xcpsQmqbwUoCQNQjLJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccCj3r/btsD4ITz3I5/ER6xcpsQmqbwUoCQNQjLJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccCj3r%2FbtsD4ITz3I5%2FER6xcpsQmqbwUoCQNQjLJk%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;745&quot; height=&quot;419&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이걸 활용하면 이렇게 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706319881099&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const scrollTop=document.documentElement.scrollTop;
const scrollHeight=document.documentElement.scrollHeight;
const clientHeight=document.documentElement.clientHeight;

if(clientHeight===scrollHeight-scrollTop){
    fetch();
}&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;1280&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yL1Dj/btsD3qeYCp0/LjDOePXX7lOx1NFXfJDD9k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yL1Dj/btsD3qeYCp0/LjDOePXX7lOx1NFXfJDD9k/img.gif&quot; data-alt=&quot;https://www.smashingmagazine.com/2018/08/scroll-bouncing-websites/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yL1Dj/btsD3qeYCp0/LjDOePXX7lOx1NFXfJDD9k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/yL1Dj/btsD3qeYCp0/LjDOePXX7lOx1NFXfJDD9k/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;632&quot; height=&quot;350&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.smashingmagazine.com/2018/08/scroll-bouncing-websites/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 clientHeight보다 scrollHeight-scrollTop한 크기가 더 작아진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부등호를 넣어주자.&lt;/p&gt;
&lt;pre id=&quot;code_1706320206532&quot; class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const scrollTop=document.documentElement.scrollTop;
const scrollHeight=document.documentElement.scrollHeight;
const clientHeight=document.documentElement.clientHeight;

if(clientHeight&amp;gt;=scrollHeight-scrollTop){
    fetch();
}&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;그럼 이걸 window.onScroll에 달아주면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1706320479702&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  useEffect(() =&amp;gt; {
    window.addEventListener(() =&amp;gt; {
      const scrollTop = document.documentElement.scrollTop;
      const scrollHeight = document.documentElement.scrollHeight;
      const clientHeight = document.documentElement.clientHeight;

      if (clientHeight &amp;gt;= scrollHeight - scrollTop) {
        fetch();
      }
    });
  }, []);&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onScroll의 콜백함수는 스크롤이 일어날때마다 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알다시피 JS는 싱글스레드 언어이다.&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;380&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WEhfy/btsD080xfNO/3e0SdwFQGfbqZM0AqP9QI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WEhfy/btsD080xfNO/3e0SdwFQGfbqZM0AqP9QI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WEhfy/btsD080xfNO/3e0SdwFQGfbqZM0AqP9QI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWEhfy%2FbtsD080xfNO%2F3e0SdwFQGfbqZM0AqP9QI1%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;181&quot; height=&quot;320&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 무의미한 함수호출을 계속하는 것 뿐 아니라 이론적으로는 CallStack이 쌓이면서 다른 함수 호출, 비동기 처리에도 문제를 일으킬 수 있다.&amp;nbsp; (실제 &lt;a href=&quot;https://medium.com/@i_am_root/scroll-event-%EC%99%80-intersection-observer-%EB%B9%84%EA%B5%90-200048e750a9&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;&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;이런 문제를 해결하기 위해서 쓰로틀링, 디바운싱을 걸 수 있다...만 우리가 원하는건 마지막 부분에 왔을 때만, fetch시키는 것이다.&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;b&gt;무의미한 호출&lt;/b&gt;을 하지 않는 것이 BEST라고 할 수 있다.&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;바로 Intersection Observer API이다.&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;Intersection Observer API&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Intersectin Oberserver API는 특정 요소가 다른 요소의 교차에 관한 API다.&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;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mdn을 참고&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;IntersectionObserver는 다음과 같이 새로운 인스턴스를 만들고, 요소를 관찰할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706323383786&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const observer = new IntersectionObserver(콜백함수, 옵저버옵션);
const element=관찰할요소;
observer.observe(element); // 해당 요소 관찰 시작&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 옵저버 옵션으로 다양한 값을 줄 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한게 root와 threshold다.&lt;/p&gt;
&lt;pre id=&quot;code_1706323414495&quot; class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const observer = new IntersectionObserver(콜백함수, {
	root: &quot;관찰할 요소의 상위요소&quot;,
    threshold: &quot;관찰 요소가 얼마나 보여야하는가?&quot; //1 -&amp;gt; 100%다.
});&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;이때 root 값이 없으면 브라우저 뷰포트가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;콜백함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백함수는 지정된 형태를 갖는데 두개의 인자를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;entries는 해당 옵저버가 관찰하고 있는 대상 전체를 말한다. (여러개의 대상을 관찰하고 있을 수 있으므로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;observer는 옵저버 객체다.&lt;/p&gt;
&lt;pre id=&quot;code_1706323843722&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let callback = (entries, observer) =&amp;gt; {
  entries.forEach((entry) =&amp;gt; {
    // 각 엔트리는 observe하고있는 요소 하나의 교차 변화를 설명한다.
  });
};&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;이 entry에서 다양한 정보를 얻어올 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;boundingClientRect: 관찰 대상의 사각형 정보(DOMRectReadOnly)&lt;/li&gt;
&lt;li&gt;intersectionRect: 관찰 대상의 교차한 영역 정보(DOMRectReadOnly)&lt;/li&gt;
&lt;li&gt;intersectionRatio: 관찰 대상의 교차한 영역 백분율(intersectionRect 영역에서 boundingClientRect 영역까지 비율, Number)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;isIntersecting: 관찰 대상의 교차 상태(Boolean)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;rootBounds: 지정한 루트 요소의 사각형 정보(DOMRectReadOnly)&lt;/li&gt;
&lt;li&gt;target: 관찰 대상 요소(Element)&lt;/li&gt;
&lt;li&gt;time:&amp;nbsp;변경이&amp;nbsp;발생한&amp;nbsp;시간&amp;nbsp;정보(DOMHighResTimeStamp)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 isIntersecting을 사용하면 무한스크롤을 구현할 수 있다.&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;useRefFocusEffect&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 활용해서 ref요소가 화면에 들어왔을때(focus) 콜백함수가 수행되는 훅을 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706325435491&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useRef } from 'react';

export default function useRefFocusEffect&amp;lt;T extends HTMLElement&amp;gt;(onFocusCallback: () =&amp;gt; void, deps?: React.DependencyList) {
  const ref = useRef&amp;lt;T&amp;gt;(null);

  useEffect(() =&amp;gt; {
    if (ref.current) {
      const observer = new IntersectionObserver((entries) =&amp;gt; entries.forEach((entry) =&amp;gt; entry.isIntersecting &amp;amp;&amp;amp; onFocusCallback()), {
        threshold: 1,
      });
      observer.observe(ref.current);
      return () =&amp;gt; {
        if (ref.current) observer.unobserve(ref.current);
      };
    }
  }, [deps]);

  return { elementRef: ref };
}&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;그럼 요소에 ref만 달아주면 ref가 화면에 보일때 콜백이 수행된다.&lt;/p&gt;
&lt;pre id=&quot;code_1706325585764&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;...
const { elementRef } = useRefFocusEffect&amp;lt;HTMLDivElement&amp;gt;(fetch, [data]);

return &amp;lt;div ref={elementRef}&amp;gt;이 요소가 보이면 fetch&amp;lt;/div&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;이렇게 하면 해당 요소가 화면에 100%보일때 fetch가 일어나게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이전 onScroll처럼 무의미하게 호출을 하지 않고 &lt;b&gt;필요할때만 콜백을 호출할 수 있는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 게시글에서는 피드 데이터를 어떻게 받아오고, 관리할 것인지를 알아보자.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>IntersectionObserver</category>
      <category>onScroll</category>
      <category>react</category>
      <category>react-query</category>
      <category>scroll event</category>
      <category>무한스크롤</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/349</guid>
      <comments>https://0422.tistory.com/349#entry349comment</comments>
      <pubDate>Sat, 27 Jan 2024 12:58:35 +0900</pubDate>
    </item>
    <item>
      <title>웹뷰 + 모노레포 환경에서 자동 토큰 갱신 훅으로 개발자 경험 개선하기 (feat. axios, zustand)</title>
      <link>https://0422.tistory.com/348</link>
      <description>&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/347&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://0422.tistory.com/347&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706257369662&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;React, RN 환경에서 WebView 통신하기 (react-native-webview)&quot; data-og-description=&quot;배경 웹뷰를 사용하여 앱을 구성하고 있다. 웹뷰는 마치 HTML의 iframe 태그처럼, 내부적으로 하나의 화면을 띄우는 형태이다. 그런데 우리 서비스의 로그인 로직은 앱에 있어서, 웹에서 따로 로그&quot; data-og-host=&quot;0422.tistory.com&quot; data-og-source-url=&quot;https://0422.tistory.com/347&quot; data-og-url=&quot;https://0422.tistory.com/347&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Hgd6q/hyU82Ccdke/6OO9cL5XD9YHS60KQSU4i0/img.png?width=800&amp;amp;height=471&amp;amp;face=0_0_800_471,https://scrap.kakaocdn.net/dn/xO6oi/hyU8ZlcJlU/LzLCkPygo2IUak4Kap0CSk/img.png?width=800&amp;amp;height=471&amp;amp;face=0_0_800_471,https://scrap.kakaocdn.net/dn/eiLVqC/hyVb6wconu/kq2mMzF1SzeFngC0vKNZKk/img.png?width=1183&amp;amp;height=697&amp;amp;face=0_0_1183_697&quot;&gt;&lt;a href=&quot;https://0422.tistory.com/347&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://0422.tistory.com/347&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Hgd6q/hyU82Ccdke/6OO9cL5XD9YHS60KQSU4i0/img.png?width=800&amp;amp;height=471&amp;amp;face=0_0_800_471,https://scrap.kakaocdn.net/dn/xO6oi/hyU8ZlcJlU/LzLCkPygo2IUak4Kap0CSk/img.png?width=800&amp;amp;height=471&amp;amp;face=0_0_800_471,https://scrap.kakaocdn.net/dn/eiLVqC/hyVb6wconu/kq2mMzF1SzeFngC0vKNZKk/img.png?width=1183&amp;amp;height=697&amp;amp;face=0_0_1183_697');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React, RN 환경에서 WebView 통신하기 (react-native-webview)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배경 웹뷰를 사용하여 앱을 구성하고 있다. 웹뷰는 마치 HTML의 iframe 태그처럼, 내부적으로 하나의 화면을 띄우는 형태이다. 그런데 우리 서비스의 로그인 로직은 앱에 있어서, 웹에서 따로 로그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;0422.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 글에서 이어진다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 게시글을 통해 앱-웹간 통신으로 토큰을 받아오고 이벤트를 발생시키는 것까지는 완료했다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이제부터 시작이다. 받아오기전에는 어떻게 하고, 받은 토큰을 어떻게 관리할 것인가?&lt;/p&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 앱의 onLoad를 통해 주입되지만, 이 이벤트가 동기적으로 작동하지는 않는다.&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;이때 api호출이 되면 무조건 401에러가 발생하므로 이러한 일이 일어나지 않도록 방지해주어야 했다.&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;최상단 App.tsx에서 access나 refresh 토큰이 없는경우 Loading컴포넌트를 보여주는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1706257570422&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  const { setAccess, setRefresh } = useSetToken();
  const { access, refresh } = useToken();

  const tokenHandler = (event: MessageEvent) =&amp;gt; {
    const { accessToken, refreshToken } = JSON.parse(event.data);
    setAccess(accessToken);
    setRefresh(refreshToken);
  };

  useEffect(() =&amp;gt; {
    document.addEventListener('message', tokenHandler as EventListener);
    window.addEventListener('message', tokenHandler);
  }, []);

  if (!access || !refresh) return &amp;lt;Loading /&amp;gt;;
  ...
}&lt;/code&gt;&lt;/pre&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;/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;크게 두 가지 고민이 생긴다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;웹 사용중에 access token이 만료되는 경우 access token 갱신을 어떻게 해줄 것인가?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹에서만 access token 토큰을 갱신한다&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;vs&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;앱에서도 access token을 갱신한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 사용중에 refresh token이 만료되면 어떻게 처리해줄 것인가?&lt;/li&gt;
&lt;li&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;localStorage&lt;/li&gt;
&lt;li&gt;전역상태&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;웹 사용 중에 access token이 만료되는 경우 access token 갱신을 어떻게 해줄 것인가?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱에서 JWT토큰을 웹으로 넣어주면, 웹에서는 내부적으로 토큰을 갱신하고, 관리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;굳이 앱과 웹의 토큰 수명 주기를 맞춰줄 필요성을 느끼지 못했기 때문이다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;웹 사용중에 refresh token이 만료되면 어떻게 처리해줄 것인가?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 사용중에 refresh token이 만료되는 경우 앱에서 로그아웃 처리하고, 재로그인 시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이건 웹 내부에서 다시 재로그인을 시킬 수는 없기때문에 이 방법밖에 없다고 판단했다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 방법을 사용하여 토큰을 관리할 것인가?
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;쿠키 : 로그인 자체를 웹환경에서 하지 않기때문에&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Set-Cookie를 통해 httpOnly쿠키를 받아올 수 없다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;즉, 쿠키로 관리하는 장점이 없다.&lt;/li&gt;
&lt;li&gt;localStorage: 매번 웹뷰가 열릴때 토큰을 주입받으므로&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;localStorage를 사용할 이유가 없다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&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;전역상태 라이브러리는 번들 사이즈가 작은 Zustand를 택했다.&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;또한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;토큰 에러 핸들링을 직접 api마다 해주면 매우 반복되는 작업이 많아지고 힘들어지기에 자동 갱신이 되도록 axios interceptor를 활용해줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더불어 우리는 모노레포로 Turborepo를 사용하고 있다. 여러 apps에서 &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;&lt;b&gt;여러 레포에서 사용할 수 있는 자동 토큰 관리 시스템을 만들어줄 것이다.&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;package/hooks 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통 훅들을 여기에 모아줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios와 zustand를 설치해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706258821426&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//package.json
{
  &quot;name&quot;: &quot;@repo/hooks&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;common hooks&quot;,
  &quot;main&quot;: &quot;./index.ts&quot;,
  &quot;types&quot;: &quot;./index.ts&quot;,
  &quot;private&quot;: true,
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;scripts&quot;: {
    &quot;lint&quot;: &quot;npx prettier --write src &amp;amp; eslint src&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@repo/eslint-config&quot;: &quot;*&quot;, //lint공통 설정
    &quot;@repo/typescript-config&quot;: &quot;*&quot;, //ts공통설정
    &quot;@types/axios&quot;: &quot;^0.14.0&quot;,
    &quot;typescript&quot;: &quot;^5.3.3&quot;
  },
  &quot;dependencies&quot;: {
    &quot;axios&quot;: &quot;^1.6.5&quot;,
    &quot;zustand&quot;: &quot;^4.4.7&quot;
  }
}&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AuthStore를 만들어주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706258040960&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { create } from 'zustand';

interface AuthState {
  access: string;
  refresh: string;
  setAccess: (value: string) =&amp;gt; void;
  setRefresh: (value: string) =&amp;gt; void;
}

const useAuthStore = create&amp;lt;AuthState&amp;gt;((set) =&amp;gt; ({
  access: '',
  refresh: '',
  setAccess: (value: string) =&amp;gt; set((state) =&amp;gt; ({ ...state, access: value })),
  setRefresh: (value: string) =&amp;gt; set((state) =&amp;gt; ({ ...state, refresh: value })),
}));

export default useAuthStore;&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;토큰을 쓰는 곳과 set하는 곳이 다를 수 있을거라 생각해서... 두개의 훅으로 분리시켜주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706258100249&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// useToken.ts
import useAuthStore from '../stores/useAuthStore';

export default function useToken() {
  const access = useAuthStore((state) =&amp;gt; state.access);
  const refresh = useAuthStore((state) =&amp;gt; state.refresh);
  return { access, refresh };
}


// useSetToken.ts
import useAuthStore from '../stores/useAuthStore';

export default function useSetToken() {
  const setAccess = useAuthStore((state) =&amp;gt; state.setAccess);
  const setRefresh = useAuthStore((state) =&amp;gt; state.setRefresh);
  return { setAccess, setRefresh };
}&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;h3 data-ke-size=&quot;size23&quot;&gt;axios interceptor 를 활용한 useAuthAxiosInstance&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;access token이 만료되면, refresh한후에 재요청을 보내는&amp;nbsp;axios인스턴스를 만들어서 내보내는 훅을 만들어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 아닌 &lt;b&gt;훅을 택한 이유&lt;/b&gt;는 여기서&lt;b&gt; 전역상태 토큰을 알아서 관리해줄 것&lt;/b&gt;이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;baseURL과 onError 함수를 받아 각 프로젝트별로 다른 URL를 base로 사용할 수 있게 했고, refresh가 만료된 경우에도 각각 다른 Action을 일으킬 수 있게 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1706259103472&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function useAuthAxiosInstance(baseURL: string, onError: () =&amp;gt; void) {
  const { access, refresh } = useToken();

  const authAxios = axios.create({
    baseURL, 
    headers: {
      Authorization: `Bearer ${access}`,
    },
  });
  
  ...
 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;baseURL과 access token을 알아서 넣어서 요청하는 인스턴스를 만들었다.&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;이제 401인경우 refresh를 통해 토큰을 갱신시켜주자.&lt;/p&gt;
&lt;pre id=&quot;code_1706259317981&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;authAxios.interceptors.response.use(
    (res) =&amp;gt; res, //응답이 okay면 그냥 결과를 내보낸다.
    async (error) =&amp;gt; {
      const {
        config,
        response: { status },
      } = error;
      if (status !== 401) return Promise.reject(error); //401이 아니면 Promise를 reject시킨다.
      try { //아니라면(401이면) 토큰을 재발급하여 재요청보낸다.
        const { accessToken: newAccessToken, refreshToken: newRefreshToken } = await getNewToken(baseURL, refresh); //새로운 토큰을 발급받는다.
        setAccess(newAccessToken); 
        setRefresh(newRefreshToken); 
        
        config.headers.Authorization = `Bearer ${newAccessToken}`; //기존 요청의 헤더를 재설정한다.
        const response = await axios.get(config.url, config); //기존 요청을 한번 더 보낸다.
        return await Promise.resolve(response); //결과를 내보낸다.
      } catch (err) {
        onError(); //재발급에서 오류가 나면 주입받은 onError 함수를 수행한다.
        return Promise.reject(err);
      }
    },
  );&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;1264&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjL9UP/btsDZGpN6oL/dEKgwpKKcWc6xF8kWIqCDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjL9UP/btsDZGpN6oL/dEKgwpKKcWc6xF8kWIqCDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjL9UP/btsDZGpN6oL/dEKgwpKKcWc6xF8kWIqCDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjL9UP%2FbtsDZGpN6oL%2FdEKgwpKKcWc6xF8kWIqCDk%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;1264&quot; height=&quot;518&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;518&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;더불어 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;개발자는 여기서 만든 authAxios를 통해 요청하면 토큰에 대해서 전혀 신경쓰지 않아도 된다.&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;pre id=&quot;code_1706260050922&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import axios from 'axios';
import useToken from './useToken';
import useSetToken from './useSetToken';

const getNewToken = async (baseURL: string, refresh: string) =&amp;gt; {
  const { data } = await axios.get(`${baseURL}/token`, { headers: { Authorization: `Bearer ${refresh}` } });
  return data as { accessToken: string; refreshToken: string };
};

export default function useAuthAxiosInstance(baseURL: string, onError: () =&amp;gt; void) {
  const { access, refresh } = useToken();
  const { setAccess, setRefresh } = useSetToken();

  const authAxios = axios.create({
    baseURL,
    headers: {
      Authorization: `Bearer ${access}`,
    },
  });

  authAxios.interceptors.response.use(
    (res) =&amp;gt; res,
    async (error) =&amp;gt; {
      const {
        config,
        response: { status },
      } = error;
      if (status !== 401) return Promise.reject(error);
      try {
        const { accessToken: newAccessToken, refreshToken: newRefreshToken } = await getNewToken(baseURL, refresh);
        setAccess(newAccessToken);
        setRefresh(newRefreshToken);
        config.headers.Authorization = `Bearer ${newAccessToken}`;
        const response = await axios.get(config.url, config);
        return await Promise.resolve(response);
      } catch (err) {
        onError();
        return Promise.reject(err);
      }
    },
  );
  return authAxios;
}&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;사용하기&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최초 토큰 초기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰이 app에서 넘어왔을때, 딱 한번 set해줄 필요가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1706260240496&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  const { setAccess, setRefresh } = useSetToken();
  const { access, refresh } = useToken();

  const tokenHandler = (event: MessageEvent) =&amp;gt; {
    const { accessToken, refreshToken } = JSON.parse(event.data);
    setAccess(accessToken);
    setRefresh(refreshToken);
  };

  useEffect(() =&amp;gt; {
    document.addEventListener('message', tokenHandler as EventListener);
    window.addEventListener('message', tokenHandler);
  }, []);

  if (!access || !refresh) return &amp;lt;Loading /&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;h3 data-ke-size=&quot;size23&quot;&gt;useAuthAxios&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레포에서 useAuthAxiosInstance를 import해서 useAuthAxios로 한번 래핑해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1706260099194&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useAuthAxiosInstance } from '@repo/hooks';

export default function useAuthAxios() {
  const onError = () =&amp;gt; {
    window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'LOGOUT' })); //refresh가 만료되면 로그아웃 처리
  };
  return useAuthAxiosInstance(import.meta.env.VITE_API_URL, onError);
}&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;이후에 사용해주면 토큰 신경쓸 필요없이 api요청이 가능해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1706260173385&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useQuery } from '@tanstack/react-query';
import REACT_QUERY_KEYS from '@/constants/REACT_QUERY_KEYS';
import useAuthAxios from '@/hooks/useAuthAxios';

export default function useSomeQuery() {
  const authAxios = useAuthAxios(); //요 훅만 사용하면 알아서 토큰이 담긴다!

  const getMyPosts = async () =&amp;gt; {
    const result = await authAxios.get('/apiUrl');

    return result.data;
  };

  return useQuery({ queryKey: [REACT_QUERY_KEYS.MY_POSTS], queryFn: useSomeQuery });
}&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4Y0VT/btsDZH3o3K7/UqGhBViR7882UNfXfjhpYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4Y0VT/btsDZH3o3K7/UqGhBViR7882UNfXfjhpYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4Y0VT/btsDZH3o3K7/UqGhBViR7882UNfXfjhpYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4Y0VT%2FbtsDZH3o3K7%2FUqGhBViR7882UNfXfjhpYk%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;1398&quot; height=&quot;754&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 위의 이미지 처럼 작동하게 되고, 각 프로젝트에서는 token에 대해 신경쓸 필요없이 api요청을 할 수 있게 된다.&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>axios</category>
      <category>DX개선</category>
      <category>interceptors</category>
      <category>jwt</category>
      <category>turborepo</category>
      <category>zustand</category>
      <category>모노레포</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/348</guid>
      <comments>https://0422.tistory.com/348#entry348comment</comments>
      <pubDate>Fri, 26 Jan 2024 18:25:38 +0900</pubDate>
    </item>
    <item>
      <title>React, RN 환경에서 WebView 통신하기 (react-native-webview)</title>
      <link>https://0422.tistory.com/347</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰를 사용하여 앱을 구성하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰는 마치 HTML의 iframe 태그처럼, 내부적으로 하나의 화면을 띄우는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 우리 서비스의 로그인 로직은 앱에 있어서, 웹에서 따로 로그인/로그아웃을 구현하지 않고, 앱 내부의 JWT토큰을 웹으로 전달할 수 있어야 했다. 또한 웹내에서 JWT토큰이 만료되는 경우 로그인 페이지로 이동시켜줄 필요가 있었다.&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;우선 Web과 App환경의 통신 방법에 대해 알아보고, 이후에 토큰을 내부적으로 관리해볼 것이다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 react-native-webview 라이브러리를 사용해서 WebView 화면을 띄워주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-webview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.npmjs.com/package/react-native-webview&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1706251977553&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;react-native-webview&quot; data-og-description=&quot;React Native WebView component for iOS, Android, macOS, and Windows. Latest version: 13.6.4, last published: 24 days ago. Start using react-native-webview in your project by running &amp;#96;npm i react-native-webview&amp;#96;. There are 950 other projects in the npm regi&quot; data-og-host=&quot;www.npmjs.com&quot; data-og-source-url=&quot;https://www.npmjs.com/package/react-native-webview&quot; data-og-url=&quot;https://www.npmjs.com/package/react-native-webview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Y3OG3/hyU8Teb2Vn/gv1e39RRQtaSESB6wC7VNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-native-webview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.npmjs.com/package/react-native-webview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Y3OG3/hyU8Teb2Vn/gv1e39RRQtaSESB6wC7VNK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;react-native-webview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;React Native WebView component for iOS, Android, macOS, and Windows. Latest version: 13.6.4, last published: 24 days ago. Start using react-native-webview in your project by running `npm i react-native-webview`. There are 950 other projects in the npm regi&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.npmjs.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;요런식으로 uri만 집어넣으면 해당 웹 사이트를 앱 내부에서 브라우저로 띄워주는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1706252052122&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { WebView } from 'react-native-webview';

// ...
const MyWebComponent = () =&amp;gt; {
  return &amp;lt;WebView source={{ uri: 'https://reactnative.dev/' }} style={{ flex: 1 }} /&amp;gt;;
}&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;react-native-webview에서 제공하는 WebView컴포넌트는 다양한 이벤트 핸들러를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 onLoad와 onMessage를 사용해서 웹 화면과 앱이 통신하게 만들 것이다.&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;onLoad는 웹뷰 화면이 불러와졌을때를, onMessage는 웹으로 부터 메시지를 받은 경우, 콜백함수를 수행시킨다.&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;postMessage&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰와 웹 모두 postMessage라는 메서드를 통해 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹의 경우에는 window.ReactNativeWebView.postMessage 를 통해, 앱에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebView컴포넌트.postMessage형태로 사용한다. (ref를 활용해야한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이 두가지 상에서 오고갈 수 있는 것은 string데이터여서 객체를 보내기 위해서는 JSON.stringify를 통해 string으로 변환하여 넘겨야하고, 받을때는 JSON.parse를 통해 객체로 변환해주어야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XFiPs/btsDY6IPYUG/CpQosiWELsEcSkIxFCql1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XFiPs/btsDY6IPYUG/CpQosiWELsEcSkIxFCql1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XFiPs/btsDY6IPYUG/CpQosiWELsEcSkIxFCql1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXFiPs%2FbtsDY6IPYUG%2FCpQosiWELsEcSkIxFCql1K%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;1183&quot; height=&quot;697&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;양방향 통신 간단하게 살펴보기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앱 (RN)&amp;nbsp; -&amp;gt; 웹 (React)&lt;/h3&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;그럼 이제 onLoad됐을때 WebViewRef.current.postMessage를 통해 웹화면으로 token을 넘겨주면된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwHq6/btsDZ3EQUPF/9L91wt4ZCiudFLSXEwkpz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwHq6/btsDZ3EQUPF/9L91wt4ZCiudFLSXEwkpz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwHq6/btsDZ3EQUPF/9L91wt4ZCiudFLSXEwkpz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnwHq6%2FbtsDZ3EQUPF%2F9L91wt4ZCiudFLSXEwkpz0%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;591&quot; height=&quot;354&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1706254433499&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function CustomWebView({uri}: {uri: string}) {
  const webViewRef = useRef&amp;lt;WebView&amp;gt;(null);
  const onLoad = async () =&amp;gt; {
    const accessToken = await storageGetValue('accessToken'));
    const refreshToken = await storageGetValue('refreshToken');
    if (webViewRef.current) {
      webViewRef.current.postMessage(
        JSON.stringify({accessToken, refreshToken}),
      );
    }
  };
  
  ...
  return &amp;lt;WebView
        uri={{uri}}
        ref={webViewRef as any}
        onLoad={onLoad}
      /&amp;gt;
  }&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;웹에서 토큰 받기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App.tsx 최상단에서 window의 onmessage 이벤트에 대한 콜백함수를 등록해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onmessage이벤트는 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/message_event&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 참고하면 좋다. (이렇게 등록하는 것으로 보아 WebViewRef.currentMessage.postMessage는 Window.postMessage를 활용한다는 것을 유추해 볼 수 있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P4n2C/btsD3mDigXF/e2kqZFeujHb0QjV6ZtzTS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P4n2C/btsD3mDigXF/e2kqZFeujHb0QjV6ZtzTS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P4n2C/btsD3mDigXF/e2kqZFeujHb0QjV6ZtzTS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP4n2C%2FbtsD3mDigXF%2Fe2kqZFeujHb0QjV6ZtzTS1%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;617&quot; height=&quot;356&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1706255499422&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//App.tsx 
  const tokenHandler = (event: MessageEvent) =&amp;gt; {
    const { accessToken, refreshToken } = JSON.parse(event.data);
    console.log(accessToken, refreshToken);
  };

  useEffect(() =&amp;gt; {
    //안드로이드와 iOS의 차이때문에 두개를 달아준다.
    document.addEventListener('message', tokenHandler as EventListener);
    window.addEventListener('message', tokenHandler);
  }, []);&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;웹(React) -&amp;gt; 앱(RN)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서 앱으로 일으키는 이벤트는 window.ReactNativeWebView.postMessage메서드를 통해서 발생시킬 수 있고, 앱에서는 WebView컴포넌트의 onMessage props를 통해 콜백을 등록할 수 있다.&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;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window.ReactNativeWebView.postMessage를 통해 일으킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ts를 사용중인 경우 global declare를 통해 타입을 추가해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나같은 경우에는 vite를 사용하기에 vite-env.d.ts에 Window interface를 추가하여 타입문제를 해결했다.&lt;/p&gt;
&lt;pre id=&quot;code_1706256766288&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// &amp;lt;reference types=&quot;vite/client&quot; /&amp;gt;
interface Window {
  ReactNativeWebView: {
    postMessage: (value: string) =&amp;gt; void;
  };
}&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;1168&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3QJav/btsDZXEIqu3/PL25zyrNhjEo94Ow26v7ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3QJav/btsDZXEIqu3/PL25zyrNhjEo94Ow26v7ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3QJav/btsDZXEIqu3/PL25zyrNhjEo94Ow26v7ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3QJav%2FbtsDZXEIqu3%2FPL25zyrNhjEo94Ow26v7ZK%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;540&quot; height=&quot;325&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 type이 Back인 객체를 전송해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1706256846052&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function MyPageHeader({ children }: { children: string }) {
  const back = () =&amp;gt; window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'BACK' }));
  return &amp;lt;Header onClick={back}&amp;gt;{children}&amp;lt;/Header&amp;gt;;
}&lt;/code&gt;&lt;/pre&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;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;앱에서는 onMessage 핸들러를 추가해서 이벤트 data를 받아올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BYSYc/btsD3pNsuks/sT5VLNZqgEIOosS4k0UKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BYSYc/btsD3pNsuks/sT5VLNZqgEIOosS4k0UKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BYSYc/btsD3pNsuks/sT5VLNZqgEIOosS4k0UKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBYSYc%2FbtsD3pNsuks%2FsT5VLNZqgEIOosS4k0UKkk%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;538&quot; height=&quot;318&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1706256971958&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const onMessage = async (event: WebViewMessageEvent) =&amp;gt; {
    const {nativeEvent} = event;
    const {type} = JSON.parse(nativeEvent.data); //받은 string을 객체로 변환하고, type을 가져옴

    if (type === 'BACK') navigation.goBack(); 
    if (type === 'LOGOUT') logoutApi();
  };&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;다음 게시글에서는 여기서 받은 JWT를 통해 어떻게 웹에서 토큰을 관리했는지 작성해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>jwt</category>
      <category>react</category>
      <category>reactnative</category>
      <category>WebView</category>
      <category>네이티브</category>
      <category>리액트</category>
      <category>웹뷰</category>
      <category>인증인가</category>
      <category>토큰</category>
      <category>통신</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/347</guid>
      <comments>https://0422.tistory.com/347#entry347comment</comments>
      <pubDate>Fri, 26 Jan 2024 17:20:27 +0900</pubDate>
    </item>
    <item>
      <title>React로 라이브러리 없이 Carousel 만들기 (2) - Indexed Carousel</title>
      <link>https://0422.tistory.com/346</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목을-입력해주세요_ (5).gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxUeRu/btsDJZBAWLk/UfSODU6hYStwZAa6Gwm8e1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxUeRu/btsDJZBAWLk/UfSODU6hYStwZAa6Gwm8e1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxUeRu/btsDJZBAWLk/UfSODU6hYStwZAa6Gwm8e1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bxUeRu/btsDJZBAWLk/UfSODU6hYStwZAa6Gwm8e1/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;1080&quot; height=&quot;540&quot; data-filename=&quot;제목을-입력해주세요_ (5).gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;Indexed Carousel&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Index 기반의 Carousel을 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 로직은 &lt;a href=&quot;https://0422.tistory.com/345&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;과 거의 동일하다. 시작 터치 위치를 기억하고, 움직일때 transX를 변경하고, transX를 기반으로 translateX를 통해 스크롤을 구현한다.&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;여기서 Index를 추가하고, 특정 scroll값을 넘으면 인덱스를 변경시켜 주고, translateX값을 Index기반으로 작동하도록 만들어주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 handleTouchStart와 handleTouchMove콜백함수는 Non-Indexed와 동일하다.&lt;/p&gt;
&lt;pre id=&quot;code_1705719603491&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// Non-Indexed와 동일하다.
const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    setTouchStartX(e.touches[0].clientX);
  };

  const handleTouchMove = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    const moveWidth = e.touches[0].clientX - touchStartX;
    setTransX(moveWidth);
  };&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;Index State를 추가해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1705719603493&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  const [index, setIndex] = useState(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 끝났을때와 translateX가 문제다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 이걸 인덱스 기반으로 작동시킬 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 어떻게 작동하기 원하는지, 기대하는 것을 작성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;임계값보다 많이 scroll했다면, 오른쪽으로 scroll했다면 index를 하나 늘리고, 왼쪽으로 scroll했다면 index를 하나 줄인다.&lt;/li&gt;
&lt;li&gt;index*각 요소 너비만큼 스크롤을 이동시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 1번은 handleTouchEnd시에, 2번은 translateX에서 일어나야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, handleTouchEnd에서 조정해준 index를 기반으로 translateX값을 변경하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;인덱스마다 얼마나 이동시켜 줄 것인가?&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 기반으로 이동시키다보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;인덱스 한칸의 너비를 어떻게 주어야 요소가 중앙에 올것인가?&lt;/b&gt;를 고민하게됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 요소 배치를 어떻게 하냐에 따라 어떻게 중앙에 배치시킬 수 있을지가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 정확한 자식 요소들의 width값을 알아야 중앙에 위치시킬 수 있다.&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;하지만 내가 원하는 IndexCarousel은 children을 받아서 어떤 요소든 Carousel로 배치시킬 수 있도록 만들 것이기 때문에 정확한 값에 의존하게 만들고 싶지는 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Thqys/btsDKds3tPt/aXyGkM6R5Tu8mxVwJsX2u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Thqys/btsDKds3tPt/aXyGkM6R5Tu8mxVwJsX2u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Thqys/btsDKds3tPt/aXyGkM6R5Tu8mxVwJsX2u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FThqys%2FbtsDKds3tPt%2FaXyGkM6R5Tu8mxVwJsX2u1%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;692&quot; height=&quot;421&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;658&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;h4 style=&quot;color: #000000;&quot; 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;요소를 기본적으로 스크롤 요소가 화면에 보이는 너비와 동일하게 맞춰주고, 내부에서 padding을 줘서 중앙에 맞추면 스크롤은 스크롤 요소가 화면에 보이는 만큼만 줘도 된다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSkve2/btsDIzcDirS/L5bcuhaMyjKn7OjMkJyJNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSkve2/btsDIzcDirS/L5bcuhaMyjKn7OjMkJyJNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSkve2/btsDIzcDirS/L5bcuhaMyjKn7OjMkJyJNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSkve2%2FbtsDIzcDirS%2FL5bcuhaMyjKn7OjMkJyJNk%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;556&quot; height=&quot;293&quot; data-origin-width=&quot;1162&quot; data-origin-height=&quot;612&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&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;index*스크롤wrapper요소의 너비&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 index기반의 슬라이드를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래와 같이 style을 주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1705719603495&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt; export default function IndexCarousel({ children }: { children: ReactNode }) {
   const getSliderWidth = () =&amp;gt; {
    if (ref.current) {
      return ref.current.clientWidth; //스크롤 wrapper의 화면에 보이는 너비를 가져온다.
    }
    return 0;
  };
 ...
  return (
    &amp;lt;div className=&quot;overflow-hidden&quot;&amp;gt;
      &amp;lt;div
        ref={ref}
        className=&quot;flex&quot;
        style={{
          transform: `translateX(${-index * getSliderWidth() + transX}px)`, //transX를 주지 않으면 중간 스크롤 중에 이동하지 않는다.
          transitionDuration: '300ms',
          transitionTimingFunction: 'ease-out',
        }}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
      &amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&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;그러면 터치가 끝났을때 index와 transX값을 조정해주기만 하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705719603497&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  const handleTouchEnd = () =&amp;gt; {
    const limitPage = Children.count(children) - 1; //children의 갯수-1만큼 스크롤 가능하게 만든다.
    if (transX &amp;gt; MIN_MOVE) { //지정한 최소값보다 큰 경우만 index를 조정한다.
      setIndex((prev) =&amp;gt; (prev &amp;gt; 0 ? prev - 1 : prev));
    } else if (transX &amp;lt; -MIN_MOVE) {
      setIndex((prev) =&amp;gt; (prev &amp;lt; limitPage ? prev + 1 : prev));
    }
    setTransX(0); //translateX에서 준 +transX때문에 0으로 지정해주어야 인덱스에 딱 맞게 지정이 된다.
    setTouchStartX(0); //터치 위치를 초기화한다.
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 인덱스 기반의 Carousel이 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-01-1716-11-39-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGFGON/btsDGs6CMrj/TctmI7BdluRVmz6JGJnfj1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGFGON/btsDGs6CMrj/TctmI7BdluRVmz6JGJnfj1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGFGON/btsDGs6CMrj/TctmI7BdluRVmz6JGJnfj1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cGFGON/btsDGs6CMrj/TctmI7BdluRVmz6JGJnfj1/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;302&quot; height=&quot;391&quot; data-filename=&quot;2024-01-1716-11-39-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;777&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;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;전체 코드&lt;/h4&gt;
&lt;pre id=&quot;code_1705719603499&quot; class=&quot;javascript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { Children, ReactNode, useRef, useState } from 'react';

const MIN_MOVE = 60;

export default function IndexCarousel({ children }: { children: ReactNode }) {
  const [touchStartX, setTouchStartX] = useState(0);
  const [transX, setTransX] = useState(0);
  const [index, setIndex] = useState(0);
  const ref = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    setTouchStartX(e.touches[0].clientX);
  };

  const handleTouchMove = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    const moveWidth = e.touches[0].clientX - touchStartX;
    setTransX(moveWidth);
  };

  const getSliderWidth = () =&amp;gt; {
    if (ref.current) {
      return ref.current.clientWidth;
    }
    return 0;
  };

  const handleTouchEnd = () =&amp;gt; {
    const limitPage = Children.count(children) - 1;
    if (transX &amp;gt; MIN_MOVE) {
      setIndex((prev) =&amp;gt; (prev &amp;gt; 0 ? prev - 1 : prev));
    } else if (transX &amp;lt; -MIN_MOVE) {
      setIndex((prev) =&amp;gt; (prev &amp;lt; limitPage ? prev + 1 : prev));
    }
    setTransX(0);
    setTouchStartX(0);
  };

  return (
    &amp;lt;div className=&quot;overflow-hidden&quot;&amp;gt;
      &amp;lt;div
        ref={ref}
        className=&quot;flex&quot;
        style={{
          transform: `translateX(${-index * getSliderWidth() + transX}px)`,
          transitionDuration: '300ms',
          transitionTimingFunction: 'ease-out',
        }}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
      &amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&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;문제는 우리 디자인은 양 옆의 요소가 약간 보여야 하고, 중앙에 온 요소가 다른 요소보다 커야한다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-01-1716-11-39-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce28cv/btsDGoiBwu5/rg08b3GU1qSmFb3kKaKWXK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce28cv/btsDGoiBwu5/rg08b3GU1qSmFb3kKaKWXK/img.gif&quot; data-alt=&quot;효과보다 index에 집중해서 보면 어디까지 요소가 있는지 전혀 알수가 없다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce28cv/btsDGoiBwu5/rg08b3GU1qSmFb3kKaKWXK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ce28cv/btsDGoiBwu5/rg08b3GU1qSmFb3kKaKWXK/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;338&quot; height=&quot;438&quot; data-filename=&quot;2024-01-1716-11-39-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;효과보다 index에 집중해서 보면 어디까지 요소가 있는지 전혀 알수가 없다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Index Carousel을 훅으로 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 IndexCarousel로직을 그대로 사용하되, 약간만 바뀌기 때문에 IndexCarousel 컴포넌트를 useIndexCarousel로 변경해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1705721256124&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useRef, useState } from 'react';

export default function useIndexCarousel(pageLimit: number, minMove = 60) {
  const [touchStartX, setTouchStartX] = useState(0);
  const [transX, setTransX] = useState(0);
  const [index, setIndex] = useState(0);
  const ref = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    setTouchStartX(e.touches[0].clientX);
  };

  const handleTouchMove = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    const moveWidth = e.touches[0].clientX - touchStartX;
    setTransX(moveWidth);
  };

  const getSliderWidth = () =&amp;gt; {
    if (ref.current) {
      return ref.current.clientWidth;
    }
    return 0;
  };

  const handleTouchEnd = () =&amp;gt; {
    const limitPage = pageLimit;
    if (transX &amp;gt; minMove) {
      setIndex((prev) =&amp;gt; (prev &amp;gt; 0 ? prev - 1 : prev));
    } else if (transX &amp;lt; -minMove) {
      setIndex((prev) =&amp;gt; (prev &amp;lt; limitPage ? prev + 1 : prev));
    }
    setTransX(0);
    setTouchStartX(0);
  };

  const style = {
    transform: `translateX(${-index * getSliderWidth() + transX}px)`,
    transitionDuration: '300ms',
    transitionTimingFunction: 'ease-out',
  };

  return { style, ref, handleTouchStart, handleTouchMove, handleTouchEnd, index };
}&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;이걸 사용해서 중앙에 온 요소가 커지고, 양 옆요소가 살짝 보이는 ScaleCarousel을 만들어 줄것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 아이디어&lt;/h3&gt;
&lt;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;1158&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uLn1o/btsDJlrC5u6/r3pFLEOE2vL9Hu1NTvfplK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uLn1o/btsDJlrC5u6/r3pFLEOE2vL9Hu1NTvfplK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uLn1o/btsDJlrC5u6/r3pFLEOE2vL9Hu1NTvfplK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuLn1o%2FbtsDJlrC5u6%2Fr3pFLEOE2vL9Hu1NTvfplK%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;595&quot; height=&quot;332&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙요소가&amp;nbsp; 다른 요소보다 더 크게 보여야하므로 요런식으로 처리하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Lmgq/btsDGmrxglw/cQdKwlXjutcF61uODTnInk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Lmgq/btsDGmrxglw/cQdKwlXjutcF61uODTnInk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Lmgq/btsDGmrxglw/cQdKwlXjutcF61uODTnInk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Lmgq%2FbtsDGmrxglw%2FcQdKwlXjutcF61uODTnInk%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;680&quot; height=&quot;403&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 하려면 자식요소에서 index값을 받아서 transform 처리를 해주어야한다.&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;그런데 내가 만든 Carousel은 children props을 통해서 자식요소를 렌더링하기때문에 그냥 렌더링해서는&lt;b&gt; 자식요소에서 index를 받을 수 없다는 문제가 있다&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;이를 해결하기 위해서 ScaleCarouselProvider를 통해서 자식요소에 index를 내려주었다. 그리고 &amp;nbsp;Wrapper.tsx를 작성해주었다.&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;context.ts&lt;/p&gt;
&lt;pre id=&quot;code_1705722461371&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createContext, useContext } from 'react';

export const ScaleCarouselIndex = createContext&amp;lt;number&amp;gt;(0);

export const useScaleCarouselIndex = () =&amp;gt; {
  return useContext(ScaleCarouselIndex);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wrapper.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1705722434606&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Children, ReactNode } from 'react';
import { useIndexCarousel } from '@repo/hooks';
import { ScaleCarouselIndex } from './context';

const MIN_MOVE = 60;

export default function Wrapper({ children }: { children: ReactNode }) {
  const pageLimit = Children.count(children) - 1;
  const { index, ref, style, handleTouchStart, handleTouchMove, handleTouchEnd } = useIndexCarousel(pageLimit, MIN_MOVE);

  return (
    &amp;lt;div className=&quot;overflow-hidden&quot;&amp;gt;
      &amp;lt;div
        ref={ref}
        className=&quot;flex&quot;
        style={style}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
      &amp;gt;
        &amp;lt;ScaleCarouselIndex.Provider value={index}&amp;gt;{children}&amp;lt;/ScaleCarouselIndex.Provider&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&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;이제 자식요소에서 useScaleCarouselIndex를 통해 자신의 map인덱스와 중앙에 위치한 인덱스를 비교하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙 인덱스 양옆에 있는 요소의 경우 scale과 translate를 통해 중앙으로 당겨와주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705726663772&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ReactNode } from 'react';
import { useScaleCarouselIndex } from './context';

const getTransform = (mapIndex: number, scaleIndex: number) =&amp;gt; {
  const MOVE_VALUE = 8.5;
  const SCALE_POINT = 0.8;
  if (mapIndex === scaleIndex) return '';
  if (mapIndex === scaleIndex + 1) return `scale(${SCALE_POINT}) translate(-${MOVE_VALUE}rem)`;
  if (mapIndex === scaleIndex - 1) return `scale(${SCALE_POINT}) translate(${MOVE_VALUE}rem)`;
};

export default function Card({ index, children }: { index: number; children: ReactNode }) {
  const scaleIndex = useScaleCarouselIndex();
  return (
    &amp;lt;div
      className=&quot;flex-shrink-0 w-full&quot;
      style={{
        transform: getTransform(index, scaleIndex),
        transitionDuration: '300ms',
      }}
    &amp;gt;
      &amp;lt;div className=&quot;px-10 w-full&quot;&amp;gt;{children}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 이제 옆요소가 잘 보이게된다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tU8fC/btsDGpaL63i/ZRIRZUrz1OUimKqThrkkJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tU8fC/btsDGpaL63i/ZRIRZUrz1OUimKqThrkkJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tU8fC/btsDGpaL63i/ZRIRZUrz1OUimKqThrkkJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtU8fC%2FbtsDGpaL63i%2FZRIRZUrz1OUimKqThrkkJ1%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;310&quot; height=&quot;438&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만.. Ipad 같이 화면 너비가 넓어지면 양옆 요소가 제대로 보이지 않게된다는 문제가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k6PR3/btsDGsegevx/LiPK1gNK9ENQkS1KyK4cO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k6PR3/btsDGsegevx/LiPK1gNK9ENQkS1KyK4cO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k6PR3/btsDGsegevx/LiPK1gNK9ENQkS1KyK4cO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk6PR3%2FbtsDGsegevx%2FLiPK1gNK9ENQkS1KyK4cO1%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;645&quot; height=&quot;402&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;846&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;b&gt;화면 너비가 넓어짐에 따라 스크롤 Wrapper의 너비가 커지게 되고, 이에따라 scale하는 정도도 같이 커져서 tranlate고정값으로는 요소를 제대로 가져올 수 없기 때문이었다.&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;1246&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkMXsQ/btsDLq6AIs1/cxhGngZhoiO3yLKkd8LXBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkMXsQ/btsDLq6AIs1/cxhGngZhoiO3yLKkd8LXBK/img.png&quot; data-alt=&quot;translate하는 정도가 같아서 제대로 가져오지 못한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkMXsQ/btsDLq6AIs1/cxhGngZhoiO3yLKkd8LXBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkMXsQ%2FbtsDLq6AIs1%2FcxhGngZhoiO3yLKkd8LXBK%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;1246&quot; height=&quot;543&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;translate하는 정도가 같아서 제대로 가져오지 못한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;translate %로 주기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;translate를 %로 주면 해결될 일이라고 생각했다...만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;translate의 %비율이 scale처리가 완료된 요소의 %로 들어가면서...&amp;nbsp; 예상한대로 translate되지 않았다.&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;Scale Value에반응형 적용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 너비 500을 기점으로 보이지 않아서 500을 기준으로 scale을 화면너비에 따른 반응형으로 처리해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1705727958470&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getTransform = (mapIndex: number, scaleIndex: number) =&amp;gt; {
  const MOVE_VALUE = 8.5;
  const SCALE_POINT = window.screen.width &amp;gt; 500 ? 0.9 : 0.8;
  if (mapIndex === scaleIndex) return '';
  if (mapIndex === scaleIndex + 1) return `scale(${SCALE_POINT}) translate(-${MOVE_VALUE}rem)`;
  if (mapIndex === scaleIndex - 1) return `scale(${SCALE_POINT}) translate(${MOVE_VALUE}rem)`;
};&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;이제 화면너비 1200까지 대응이 된다! 1200까지 대응한 이유는 가장 큰 테블릿의 화면너비가 1200이기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcS9wX/btsDKcAX5hH/dm8YwESXkYKKx4W6JEEF2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcS9wX/btsDKcAX5hH/dm8YwESXkYKKx4W6JEEF2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcS9wX/btsDKcAX5hH/dm8YwESXkYKKx4W6JEEF2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcS9wX%2FbtsDKcAX5hH%2Fdm8YwESXkYKKx4W6JEEF2K%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;475&quot; height=&quot;306&quot; data-origin-width=&quot;1343&quot; data-origin-height=&quot;865&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;h4 data-ke-size=&quot;size20&quot;&gt;합성 컴포넌트로 마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이걸 index.ts로 묶어서 합성 컴포넌트로 만들어주자.&lt;/p&gt;
&lt;pre id=&quot;code_1705728263690&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Card from './Card';
import Wrapper from './Wrapper';

export default { Card, Wrapper };&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_1705728289854&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Loading } from '@repo/components';
import useVoteListQuery from '@/api/useVoteListQuery';
import ScaleCarousel from './ScaleCarousel';
import Vote from './Vote/Vote';

export default function TodayVote() {
  const { data } = useVoteListQuery();

  if (!data) return &amp;lt;Loading /&amp;gt;;

  return (
    &amp;lt;ScaleCarousel.Wrapper&amp;gt;
      {data.todayVoteList.map((vote, index) =&amp;gt; (
        &amp;lt;ScaleCarousel.Card key={vote.voteListId} index={index}&amp;gt;
          &amp;lt;Vote data={vote} /&amp;gt;
        &amp;lt;/ScaleCarousel.Card&amp;gt;
      ))}
    &amp;lt;/ScaleCarousel.Wrapper&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>Carousel</category>
      <category>indexed</category>
      <category>react</category>
      <category>리액트</category>
      <category>모바일</category>
      <category>스크롤</category>
      <category>슬라이드</category>
      <category>터치</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/346</guid>
      <comments>https://0422.tistory.com/346#entry346comment</comments>
      <pubDate>Sat, 20 Jan 2024 14:23:01 +0900</pubDate>
    </item>
    <item>
      <title>React로 라이브러리 없이 Carousel 만들기 (1) - Non-Indexed Carousel</title>
      <link>https://0422.tistory.com/345</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RtH1z/btsDKJljFai/k0M8YoZF7foBHqIl8HaKv0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RtH1z/btsDKJljFai/k0M8YoZF7foBHqIl8HaKv0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RtH1z/btsDKJljFai/k0M8YoZF7foBHqIl8HaKv0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/RtH1z/btsDKJljFai/k0M8YoZF7foBHqIl8HaKv0/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;1080&quot; height=&quot;540&quot; data-filename=&quot;img.gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Teengle 프로젝트는 웹뷰프로젝트로, 모바일 기기에서 실행되어야하다보니, 여러 정보를 작은 화면에서 조회하기위해서 상당히 많은... Carousel이 필요했다.&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;Non-Indexed Carousel&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 Carousel은 커뮤니티 피드에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;마치 스레드처럼&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;여러개의 사진을 볼 수 있는 기능에 필요했다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 Carousel은 아래 사진 처럼 스크롤한 만큼 자유롭게 이동이가능해야했다.&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;2024-01-1600-31-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2KpE8/btsDD0BbVd7/2TqdfVaAPDW2LUkDXleV8k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2KpE8/btsDD0BbVd7/2TqdfVaAPDW2LUkDXleV8k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2KpE8/btsDD0BbVd7/2TqdfVaAPDW2LUkDXleV8k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b2KpE8/btsDD0BbVd7/2TqdfVaAPDW2LUkDXleV8k/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;256&quot; height=&quot;348&quot; data-filename=&quot;2024-01-1600-31-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그냥 overflow scroll로 구성해서는 스크롤 속도가 너무 빨라서 사용할 수 없었다.&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;2024-01-2201-13-34-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IhAB0/btsDGKzfpci/18NKVOgGp1v5YhcxMc1Jqk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IhAB0/btsDGKzfpci/18NKVOgGp1v5YhcxMc1Jqk/img.gif&quot; data-alt=&quot;overflow-scroll만 적용한 모습(너무빠르다)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IhAB0/btsDGKzfpci/18NKVOgGp1v5YhcxMc1Jqk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/IhAB0/btsDGKzfpci/18NKVOgGp1v5YhcxMc1Jqk/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;258&quot; height=&quot;305&quot; data-filename=&quot;2024-01-2201-13-34-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;overflow-scroll만 적용한 모습(너무빠르다)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Indexed Carousel&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두번째 Carousel은 투표기능에서 여러 투표를 슬라이드를 통해 확인하기 위해 필요했다.&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 Carousel은 스크롤시에 요소를 중앙으로 배치시켜주어야 했기에 Non-Indexed와 로직은 비슷하지만, 약간은 다른 형태가 필요했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-01-1821-18-55-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QLkAe/btsDJVTJFoK/77x91Hi9Qdezfpg1yYmKak/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QLkAe/btsDJVTJFoK/77x91Hi9Qdezfpg1yYmKak/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QLkAe/btsDJVTJFoK/77x91Hi9Qdezfpg1yYmKak/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/QLkAe/btsDJVTJFoK/77x91Hi9Qdezfpg1yYmKak/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;287&quot; height=&quot;392&quot; data-filename=&quot;2024-01-1821-18-55-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 Indexed와 Non-Indexed 각각 두개의 Carousel이 필요한 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이브러리 도입을 하지 않은 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리를 사용할까 고민했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이전까지 애용하던 js 애니메이션 라이브러리는 framer-motion이라는 라이브러리였는데, 번들사이즈를 확인해보니 놀라지 않을 수 없었다. (&lt;b&gt;물론 이건 트리쉐이킹은 적용되지 않은 용량이긴하고 &lt;a href=&quot;https://www.framer.com/motion/guide-reduce-bundle-size/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;framer-motion의 &lt;/a&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://www.framer.com/motion/guide-reduce-bundle-size/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;번들사이즈를 줄일 수도 있긴하다&lt;/a&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;1563&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PKD1B/btsDzvPArU3/qWtOGu2EENu922OsicH5aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PKD1B/btsDzvPArU3/qWtOGu2EENu922OsicH5aK/img.png&quot; data-alt=&quot;framer-motion의 번들사이즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PKD1B/btsDzvPArU3/qWtOGu2EENu922OsicH5aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPKD1B%2FbtsDzvPArU3%2FqWtOGu2EENu922OsicH5aK%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;1563&quot; height=&quot;686&quot; data-origin-width=&quot;1563&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;framer-motion의 번들사이즈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 우리 서비스는 모바일 환경이다보니 &lt;b&gt;느린 3G환경에서 사용하는 사용자도 충분히 많을 것이다. (&lt;s&gt;나도 데이터를 다써서... 느린 3G를 쓰고있다...&lt;/s&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 렌더링시에 라이브러리를 다운받다보면 거의 1초가 걸리게된다는 점에서 라이브러리 없이 직접 구현하는게 좋겠다는 생각을 하게됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rs03F/btsDAwtCW3J/IxQJ61lKdg8gOx4nkkMVzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rs03F/btsDAwtCW3J/IxQJ61lKdg8gOx4nkkMVzk/img.png&quot; data-alt=&quot;전역 상태 관리 도구인 zustand와 번들사이즈가 거의 40배정도 차이가 난다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rs03F/btsDAwtCW3J/IxQJ61lKdg8gOx4nkkMVzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frs03F%2FbtsDAwtCW3J%2FIxQJ61lKdg8gOx4nkkMVzk%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;577&quot; height=&quot;265&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;전역 상태 관리 도구인 zustand와 번들사이즈가 거의 40배정도 차이가 난다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지 Carousel을 라이브러리 없이 리액트 환경에서 하나씩 구현해보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TouchEvent vs MouseEvent&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwRXen/btsDEiV9EKj/ney3rVi0OYtomYMqEPOIYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwRXen/btsDEiV9EKj/ney3rVi0OYtomYMqEPOIYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwRXen/btsDEiV9EKj/ney3rVi0OYtomYMqEPOIYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwRXen%2FbtsDEiV9EKj%2Fney3rVi0OYtomYMqEPOIYk%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;418&quot; height=&quot;365&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;603&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;p data-ke-size=&quot;size16&quot;&gt;따라서 이 게시글에서는 TouchEvent만을 다룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 개념 생각하기&lt;/h3&gt;
&lt;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;/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;터치가 처음 시작해서 끝날때까지 끊기지 않고 이어지는 것이다. 그리고 터치가 끝나면, 스크롤이 끝난다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그말은 스크롤을 구현하려면 처음 터치가 시작된 위치를 기록하고, 이동하는 만큼 요소를 이동시켜주면 된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트에 대한 정보이므로 TouchEvent 핸들러에 제공되는 콜백에 event객체로 얻을 수 있으면 베스트다.&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;그래서 TouchEvent MDN을 살펴봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TouchEvent는 event객체에 touches를 담고있다. 이는 Touch Object의 집합이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UegR2/btsDGYikpHg/eiINW5yAbUPiiKOHcZkOu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UegR2/btsDGYikpHg/eiINW5yAbUPiiKOHcZkOu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UegR2/btsDGYikpHg/eiINW5yAbUPiiKOHcZkOu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUegR2%2FbtsDGYikpHg%2FeiINW5yAbUPiiKOHcZkOu0%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;760&quot; height=&quot;128&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Touch Object는 clientX라는 프로퍼티를 갖는데 이 프로퍼티를 통해서 브라우저 기준으로 터치가된 위치 정보를 가져올 수 있게된다. (스크롤은 포함하지 않는다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs5wwj/btsDBBoDmXp/06sKuY3VX1aidkXIQ8oeV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs5wwj/btsDBBoDmXp/06sKuY3VX1aidkXIQ8oeV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs5wwj/btsDBBoDmXp/06sKuY3VX1aidkXIQ8oeV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs5wwj%2FbtsDBBoDmXp%2F06sKuY3VX1aidkXIQ8oeV1%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;799&quot; height=&quot;129&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;129&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;그리고 터치 이벤트로 touchStart, touchMove, touchEnd이벤트가 있다고 한다. 이거 세개면 충분히 구현할 수 가 있다.&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;Non-Index Carousel 구현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위의 정보들을 써서 인덱스 없는 스크롤 한 그대로 유지되는 Carousel을 만들어보자.&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;Touch가 시작되면, 시작 지점을 기록한다.&lt;/li&gt;
&lt;li&gt;Touch가 이어지는동안 시작지점에서 현재 터치되는 요소까지의 거리를 스크롤할 요소의 스크롤 값에 더한 뒤, 스크롤 값으로 넣어준다. 그리고 시작값을 현재 터치가 일어나는 값으로 바꿔준다.&lt;b&gt;(중첩해서 적용되지않도록)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 신경써야 할 것은, &lt;b&gt;스크롤 할 요소의 총 너비를 벗어나지 않게 할 것&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 &lt;b&gt;스크롤 요소의 총너비- 화면너비&lt;/b&gt;를 통해서 구할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전체 기본 코드&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1705597733600&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ReactNode, useRef, useState } from 'react';


export default function Carousel({ children }: { children: ReactNode }) {
  const [touchStartX, setTouchStartX] = useState(0);//터치 시작위치
  const [transX, setTransX] = useState(0); //스크롤 값
  const ref = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
  
  const mediateScroll = (scrollValue: number) =&amp;gt; {
  //스크롤 값이 스크롤 요소를 넘어가지 않도록 처리
    if (scrollValue &amp;gt; 0) return 0;

    if (ref.current) {
      const maxScroll = ref.current.scrollWidth - ref.current.clientWidth; //최대 스크롤 너비는 요소의 스크롤 너비 - 화면너비다

      if (maxScroll &amp;gt; -scrollValue) return scrollValue;

      return -maxScroll;
    }

    return 0;
  };
  
  const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    // 실제 이동에 사용할 시작점을 기록
    setTouchStartX(e.touches[0].clientX);
  };

  const handleTouchMove = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
  //터치 중에 시작값과 스크롤 값을 갱신
    const moveWidth = e.touches[0].clientX - touchStartX;
    setTransX((prev) =&amp;gt; mediateScroll(prev + moveWidth));
    setTouchStartX(e.touches[0].clientX);
  };

  return (
    &amp;lt;div className=&quot;overflow-hidden&quot;&amp;gt; //tailwind를 사용했다.
      &amp;lt;div
        ref={ref}
        className=&quot;flex gap-2&quot;
        style={{
          transform: `translateX(${transX}px)`, //여기서 스크롤을 이동시킨다.
          transitionDuration: '300ms',
          transitionTimingFunction: 'ease-out',
        }}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
      &amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 잘 동작한다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2024-01-1600-31-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/do7mpf/btsDJiBE5Ml/V13DshI3IxZ8PuvweKB0v0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/do7mpf/btsDJiBE5Ml/V13DshI3IxZ8PuvweKB0v0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/do7mpf/btsDJiBE5Ml/V13DshI3IxZ8PuvweKB0v0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/do7mpf/btsDJiBE5Ml/V13DshI3IxZ8PuvweKB0v0/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;320&quot; height=&quot;435&quot; data-filename=&quot;2024-01-1600-31-53-ezgif.com-video-to-gif-converter.gif&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;720&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;스크롤 속도 적용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나... 스마트폰에서 &lt;b&gt;빠르게 스크롤하면, 더 많이 스크롤된다는 것&lt;/b&gt;을 우리는 본능적으로 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이게 처리가 안되어있으니 스크롤 모션이 너무 뻑뻑해 보이기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 로직만으로는 이게 불가능하다. 그래서 나는 조금이나마 이런 사용자 경험을 높혀보고자 스크롤 속도에 따른 추가 스크롤을 구현해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 최초로 터치가 시작된 때부터 터치가 끝난시점까지 적은 시간동안 얼마나 많이 이동했느냐이다.&lt;/p&gt;
&lt;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_1705598113897&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; const [startInformation, setStartInformation] = useState({ value: 0, time: new Date(Date.now()) });
 
   const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    // 실제 이동에 사용할 시작점을 기록
    setTouchStartX(e.touches[0].clientX);
    // 가속 구현을 위해 touch 최초의 스크롤 정도와, 시간을 기록한다.
    setStartInformation({
      value: transX, 
      time: new Date(Date.now()),
    });
  };&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;끝난 스크롤 위치-시작한 스크롤 위치 / 터치 과정에서 소요된 시간&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;그리고 가속 값을 곱해서 최종 스크롤 값을 변경시켜주면 터치가 끝났을때 추가 이동거리를 적용하고 이것이 transition이 적용되어 부드럽게 이동하게된다!&lt;/p&gt;
&lt;pre id=&quot;code_1705711430259&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleTouchEnd=() =&amp;gt; {
      const moveDistance = (transX - startInformation.value) / (new Date().getTime() - startInformation.time.getTime());
      setTransX((prev) =&amp;gt; mediateScroll(prev + moveDistance * ACCELERATION_VALUE));
}&lt;/code&gt;&lt;/pre&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/zXq3a/btsDKd7DzmM/hYnLE7vvP0Ob06xGVvhMk0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zXq3a/btsDKd7DzmM/hYnLE7vvP0Ob06xGVvhMk0/img.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;645&quot; data-is-animation=&quot;true&quot; data-filename=&quot;2024-01-2009-57-10-ezgif.com-video-to-gif-converter.gif&quot; width=&quot;384&quot; height=&quot;413&quot; data-widthpercent=&quot;50.5&quot; style=&quot;width: 49.9117%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zXq3a/btsDKd7DzmM/hYnLE7vvP0Ob06xGVvhMk0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzXq3a%2FbtsDKd7DzmM%2FhYnLE7vvP0Ob06xGVvhMk0%2Fimg.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;645&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0NfAZ/btsDGmrve9L/aKDQcKI6z9k7KkD4aIROj1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0NfAZ/btsDGmrve9L/aKDQcKI6z9k7KkD4aIROj1/img.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;658&quot; data-is-animation=&quot;true&quot; data-filename=&quot;2024-01-2010-03-25-ezgif.com-video-to-gif-converter.gif&quot; style=&quot;width: 48.9256%;&quot; data-widthpercent=&quot;49.5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0NfAZ/btsDGmrve9L/aKDQcKI6z9k7KkD4aIROj1/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0NfAZ%2FbtsDGmrve9L%2FaKDQcKI6z9k7KkD4aIROj1%2Fimg.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;658&quot;/&gt;&lt;/span&gt;&lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;이렇게 Non-Indexed Carousel을 구현할 수 있었다.&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;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1705719411273&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ReactNode, useRef, useState } from 'react';

const ACCELERATION_VALUE = 30;

export default function Carousel({ children }: { children: ReactNode }) {
  const [startInformation, setStartInformation] = useState({ value: 0, time: new Date(Date.now()) });
  const [touchStartX, setTouchStartX] = useState(0);
  const [transX, setTransX] = useState(0);
  const ref = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  const mediateScroll = (scrollValue: number) =&amp;gt; {
    if (scrollValue &amp;gt; 0) return 0;

    if (ref.current) {
      const maxScroll = ref.current.scrollWidth - ref.current.clientWidth;

      if (maxScroll &amp;gt; -scrollValue) return scrollValue;

      return -maxScroll;
    }

    return 0;
  };

  const handleTouchStart = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    // 실제 이동에 사용할 시작점으로,
    setTouchStartX(e.touches[0].clientX);
    // touch 최초의 시작점, 시간을 기록한다.
    setStartInformation({
      value: transX,
      time: new Date(Date.now()),
    });
  };

  const handleTouchMove = (e: React.TouchEvent&amp;lt;HTMLDivElement&amp;gt;) =&amp;gt; {
    const moveWidth = e.touches[0].clientX - touchStartX;
    setTransX((prev) =&amp;gt; mediateScroll(prev + moveWidth));
    setTouchStartX(e.touches[0].clientX);
  };

  return (
    &amp;lt;div className=&quot;overflow-hidden&quot;&amp;gt;
      &amp;lt;div
        ref={ref}
        className=&quot;flex gap-2&quot;
        style={{
          transform: `translateX(${transX}px)`,
          transitionDuration: '300ms',
          transitionTimingFunction: 'ease-out',
        }}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={() =&amp;gt; {
          const moveDistance = (transX - startInformation.value) / (new Date().getTime() - startInformation.time.getTime());
          setTransX((prev) =&amp;gt; mediateScroll(prev + moveDistance * ACCELERATION_VALUE));
        }}
      &amp;gt;
        {children}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>Carousel</category>
      <category>react</category>
      <category>리액트</category>
      <category>모바일</category>
      <category>스크롤</category>
      <category>터치</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/345</guid>
      <comments>https://0422.tistory.com/345#entry345comment</comments>
      <pubDate>Sat, 20 Jan 2024 12:12:47 +0900</pubDate>
    </item>
    <item>
      <title>Turborepo를 도입하게 된 이야기</title>
      <link>https://0422.tistory.com/344</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b75MJS/btsDqbpA0CQ/J4bM99zd0nPL0fTCWSXeeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b75MJS/btsDqbpA0CQ/J4bM99zd0nPL0fTCWSXeeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b75MJS/btsDqbpA0CQ/J4bM99zd0nPL0fTCWSXeeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb75MJS%2FbtsDqbpA0CQ%2FJ4bM99zd0nPL0fTCWSXeeK%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;601&quot; height=&quot;351&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 프로젝트는 많은 사용자들이 모바일 환경에서 사용할 수 있도록 &lt;span style=&quot;color: #333333;&quot;&gt;앱 형태를 택했다.&lt;/span&gt;&lt;br /&gt;또, 플레이스토어/앱스토어에서의 업데이트 없이, 실험을 통해 앱을 지속적으로 빠르게 업데이트해보고자 그 중에서도 웹뷰를 선택하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 웹뷰는 토스/당근에서 다양한 실험/빠른 업데이트 때문에 사용한다는 이야기를 들은 뒤부터&lt;b&gt; 사용자에게 정말 유효한 가치를 전달하려면 꼭 필요한 기술&lt;/b&gt;이라고 생각해서 꼭 한번 도전해보고 싶었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;웹뷰 화면 설계를 이야기 하지 않을 수가 없다.&lt;br /&gt;웹뷰, 아니 앱의 문제점중 하나라고 한다면, 바로 &lt;b&gt;스토어 리젝&lt;/b&gt;이다.&lt;br /&gt;&lt;a href=&quot;https://orangebrother.dev/blog/app_why_reject&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://orangebrother.dev/blog/app_why_reject&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;iOS / Android 앱 심사 시에 발생하는 Reject 사유들&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;앱을 배포하기 위해서는 각 플랫폼에서 심사를 거쳐야 합니다. 그리고 이 심사는 길고 까다로운 일이 될 수 있습니다. 여러가지 사유들로 출시, 혹은 업데이트가 반려되기 때문입니다.&quot; data-og-host=&quot;orangebrother.dev&quot; data-og-source-url=&quot;https://orangebrother.dev/blog/app_why_reject&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kqNZM/hyU2ggdDHC/0PQSNK5U5XXbFMo6dskkb1/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260,https://scrap.kakaocdn.net/dn/9DuRZ/hyU2q39uvb/L6vZBkJOko1MGFa5iwGbb0/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260&quot; data-og-url=&quot;https://orangebrother.dev/blog/app_why_reject&quot;&gt;&lt;a href=&quot;https://orangebrother.dev/blog/app_why_reject&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://orangebrother.dev/blog/app_why_reject&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kqNZM/hyU2ggdDHC/0PQSNK5U5XXbFMo6dskkb1/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260,https://scrap.kakaocdn.net/dn/9DuRZ/hyU2q39uvb/L6vZBkJOko1MGFa5iwGbb0/img.png?width=260&amp;amp;height=260&amp;amp;face=0_0_260_260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;iOS / Android 앱 심사 시에 발생하는 Reject 사유들&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;앱을 배포하기 위해서는 각 플랫폼에서 심사를 거쳐야 합니다. 그리고 이 심사는 길고 까다로운 일이 될 수 있습니다. 여러가지 사유들로 출시, 혹은 업데이트가 반려되기 때문입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;orangebrother.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;여러가지 리젝 사유가 있지만, 가장 신경써야할 부분은 우리가 웹뷰 서비스라는 것이다.&lt;br /&gt;웹뷰인 경우 리젝 확률이 기하급수적으로 상승한다.&lt;br /&gt;왜 웹이 아니라 앱을 사용하냐에 대한 대답이 명쾌해야한다고 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;특히 푸시알림, 결제, 카메라 연동 등 네이티브 기능이 없이, 정말 웹화면과 동일한 앱이라면 거의 100% 리젝이 된다.&lt;br /&gt;따라서 어떻게 하면 이러한 기능을 넣을지 고민했다.&lt;br /&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;720&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgar9C/btsDni4bNRQ/rH8OUlvzPC7RqmAtkrFNs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgar9C/btsDni4bNRQ/rH8OUlvzPC7RqmAtkrFNs0/img.png&quot; data-alt=&quot;예시 사진이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgar9C/btsDni4bNRQ/rH8OUlvzPC7RqmAtkrFNs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgar9C%2FbtsDni4bNRQ%2FrH8OUlvzPC7RqmAtkrFNs0%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;623&quot; height=&quot;200&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 사진이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로 네비게이팅, 푸시알림, 소셜 로그인 기능을 앱단에서 처리하고, 위 사진처럼 네비게이팅시 결과 화면을 웹뷰로 보여주는 형태로 네이티브 기술을 사용하면서, 동시에 웹뷰도 사용할 수 있도록 구현하기로 했다.&lt;br /&gt;즉 둘을 적절하게 섞는 것이다.&lt;br /&gt;&amp;nbsp;&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;750&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m5ups/btsDnCnUmij/ixqzvGwXUvkKhrtqmgJrxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m5ups/btsDnCnUmij/ixqzvGwXUvkKhrtqmgJrxk/img.png&quot; data-alt=&quot;역시 예시사진이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m5ups/btsDnCnUmij/ixqzvGwXUvkKhrtqmgJrxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm5ups%2FbtsDnCnUmij%2FixqzvGwXUvkKhrtqmgJrxk%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;535&quot; height=&quot;190&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;역시 예시사진이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 우리 프로젝트는 위 예시 사진처럼 5개의 배포가 필요했다.&lt;br /&gt;이렇게 배포 단위가 쪼개지면서, 자연스럽게 여러가지 고민거리들이 생기기 시작했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 환경(ts, vite, jest, tailwind, eslint)을 &lt;span style=&quot;color: #333333;&quot;&gt;5번 각각&amp;nbsp;&lt;/span&gt;구성해줘야함&lt;/li&gt;
&lt;li&gt;tailwind config에서 5번 스타일을 지정해줘야함&lt;/li&gt;
&lt;li&gt;공통된 타입/유틸/훅/컴포넌트가 동일해도 두 번 만들어 써야함&lt;/li&gt;
&lt;li&gt;배포 CICD도 각각 따로, 5회 구성해야함&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;해결책&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 고민들을 바로 해결해줄 수 있는게 모노레포였다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;모노레포&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;모노레포란, 다수의 프로젝트를 한 개의 레포지토리 내에서 관리하는 소프트웨어 개발전략이다.&lt;br /&gt;공통으로 사용하는 것들을 묶어서 하나의 패키지로 만들고, 이를 다른 프로젝트에서 사용할 수 있게 된다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이를 통해 위에서 생겼던 5가지 고민을 모두 해결할 수 있다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 :&lt;/b&gt; 프로젝트 환경(ts, vite, jest, tailwind, eslint)을&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;5번&amp;nbsp;각각&amp;nbsp;&lt;/span&gt;구성해줘야함 / tailwind config에서 5번 스타일을 지정해줘야함&lt;br /&gt;&lt;b&gt;해결 :&lt;/b&gt; tsconfig/jest/tailwind/eslint/ 공통 패키지 파일을 하나만 만들어서 다른 프로젝트에서 참조한뒤, 상속시키면 단 한번의 구성으로 모든 내부 프로젝트가 동일한 구성을 갖게할 수 있음&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;문제 :&lt;/b&gt; 공통된 타입/유틸/훅/컴포넌트가 동일해도 두 번 만들어 써야함&lt;br /&gt;&lt;b&gt;해결 :&lt;/b&gt; 공통된 타입/유틸/훅/컴포넌트를 따로 패키지로 분리하여 각 프로젝트에서 참조하여 사용가능&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;문제:&lt;/b&gt; 배포 CICD도 각각 따로, 5회 구성해야함&lt;br /&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;CI는 전체 레포에서 1회 테스트/빌드시키는 형태로 구성이 가능&lt;/li&gt;
&lt;li&gt;CD는 각 프로젝트 폴더별로 구성하여 배포브랜치를 통해 독립적인 자동화 배포를 구성할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 바로 우리가 겪었던 문제를 해결할 수 있기에 모노레포를 도입하기로 했다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;어떤 도구를 사용할 것인가?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 모노레포 구성을 위한 도구로는&amp;nbsp;&lt;br /&gt;Yarn, Lerna, Nx, Turborepo 4가지가 존재했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이중에 하나를 골라야 했는데 우리 팀의 결정 기준은 다음과 같았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 마감/런칭이 3월까지는 완료되어야함 -&amp;gt; 학습 비용이 적어야함&lt;/li&gt;
&lt;li&gt;Private repo를 사용하기에 github actions 제한시간이 2000분임 -&amp;gt; CI/CD 스크립트를 실행시키는데에 드는 시간을 감소시켜야만함&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;우선적으로 Yarn은 직접적으로 모노레포를 지원하지는 않는다. 직접 구성을 해야하는데, 학습 비용이 높으며, 따로 CI/CD과정에서 최적화도 직접 진행해야했다. 첫번째, 두번째 조건 모두 만족하지 않는다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;Lerna는 직접적으로 모노레포를 지원하지만, 캐시나 스크립트 병렬 실행/관리에 최적화되어있지는 않다. 즉 두번째 조건에서 탈락이다. (&lt;a href=&quot;https://fromundefined.com/posts/2022-08-ultimate-monorepo/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://fromundefined.com/posts/2022-08-ultimate-monorepo/&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Nx vs Turborepo&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 남은게 Nx와 Turborepo인데, 이 둘 중에서 어떤것을 사용할지를 고민했다.&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/GwBL6/btsDp5QvDWy/3CjyvU0IyqDbP4mQMSRRcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GwBL6/btsDp5QvDWy/3CjyvU0IyqDbP4mQMSRRcK/img.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;348&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.0726%; margin-right: 10px;&quot; data-widthpercent=&quot;54.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GwBL6/btsDp5QvDWy/3CjyvU0IyqDbP4mQMSRRcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGwBL6%2FbtsDp5QvDWy%2F3CjyvU0IyqDbP4mQMSRRcK%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;730&quot; height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vCwUm/btsDqksW62o/RFNpCeT4Wp6eJXuKTecKt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vCwUm/btsDqksW62o/RFNpCeT4Wp6eJXuKTecKt0/img.png&quot; width=&quot;521&quot; height=&quot;300&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;896&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.7646%;&quot; data-widthpercent=&quot;45.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vCwUm/btsDqksW62o/RFNpCeT4Wp6eJXuKTecKt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvCwUm%2FbtsDqksW62o%2FRFNpCeT4Wp6eJXuKTecKt0%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;1556&quot; height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌 - 병렬실행이 되지않는경우, 우 - 병렬실행이 되는경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;두 도구 모두 스크립트 병렬 실행을 지원해서, 병렬적으로 린트-테스트-빌드를 진행할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;병렬 실행이 되지않으면 상단 좌측 이미지처럼 실행전 대기시작때문에 CI/CD에 소요되는 시간이 늘어나게 된다.&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;&lt;br /&gt;더불어 Turborepo와 Nx 둘다 클라우드 캐시를 지원하여, CI/CD환경에서 이미 빌드/테스트한 파일을 다시 빌드/테스트하지 않을 수 있어 CI/CD에 드는 시간을 더욱더 감소시킬 수 있었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;실제로 사용하는 방법(&lt;a href=&quot;https://d2.naver.com/helloworld/7553804#ch4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://d2.naver.com/helloworld/7553804#ch4&lt;/span&gt;&lt;/a&gt;)을 찾아보니 Turborepo가 설정하기에 더 간단하고, &lt;s&gt;Vercel에 인수되었으며&lt;/s&gt; 간편하게 쓸수 있다고 판단하여 Turborepo를 선택하게 되었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;구성하기&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;터보레포 설정은 공식문서(&lt;a href=&quot;https://turbo.build/repo/docs/getting-started/create-new&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://turbo.build/repo/docs/getting-started/create-new&lt;/span&gt;&lt;/a&gt;) 를 참고하기바란다.&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;469&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ2iye/btsDrDFibGZ/ANysuA4Mz9fWk8WfdKlvFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ2iye/btsDrDFibGZ/ANysuA4Mz9fWk8WfdKlvFk/img.png&quot; data-alt=&quot;우리 프로젝트의 패키지 구성이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ2iye/btsDrDFibGZ/ANysuA4Mz9fWk8WfdKlvFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ2iye%2FbtsDrDFibGZ%2FANysuA4Mz9fWk8WfdKlvFk%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;293&quot; height=&quot;267&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우리 프로젝트의 패키지 구성이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;작업하게 되는 부분은 apps와 packages로 나뉜다.&lt;br /&gt;apps는 프로젝트, packages는 공통으로 사용할 요소들로 구성해주었다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공통 환경 구성하기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;tailwind.config를 예시로 들겠다.&lt;br /&gt;packages/tailwind-config/package.json에 이렇게 작성해주었다&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;name&quot;: &quot;@repo/tailwind-config&quot;,
&amp;nbsp;&amp;nbsp;&quot;version&quot;: &quot;0.0.0&quot;,
&amp;nbsp;&amp;nbsp;&quot;private&quot;: &quot;true&quot;,
&amp;nbsp;&amp;nbsp;&quot;exports&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;.&quot;: &quot;./tailwind.config.ts&quot;
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&quot;devDependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;tailwindcss&quot;: &quot;^3.4.1&quot;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 tailwind.config.ts는 아래와 같이 작성한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import type { Config } from 'tailwindcss';

//공통 tailwind.config.ts설정
const config: Omit&amp;lt;Config, 'content'&amp;gt; = {
&amp;nbsp;&amp;nbsp;theme: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;extend: {
	...
&amp;nbsp;&amp;nbsp;},
};

export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 패키지 구성은 끝이다!&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이걸 사용하는 apps에서 사용해보자.&lt;br /&gt;apps/home/package.json이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;name&quot;: &quot;home&quot;,
&amp;nbsp;&amp;nbsp;&quot;private&quot;: true,
&amp;nbsp;&amp;nbsp;&quot;version&quot;: &quot;0.0.0&quot;,
&amp;nbsp;&amp;nbsp;&quot;type&quot;: &quot;module&quot;,
&amp;nbsp;&amp;nbsp;&quot;scripts&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&quot;dependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&quot;devDependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@repo/tailwind-config&quot;: &quot;*&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@repo/typescript-config&quot;: &quot;*&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;autoprefixer&quot;: &quot;^10.4.16&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;postcss&quot;: &quot;^8.4.32&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;tailwindcss&quot;: &quot;^3.4.0&quot;,
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그리고 이제 apps/home/tailwind.config.ts에서 공통파일을 import해서 상속해주면된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import type { Config } from 'tailwindcss';
import sharedConfig from '@repo/tailwind-config';

const config: Pick&amp;lt;Config, 'content' | 'presets'&amp;gt; = {
&amp;nbsp;&amp;nbsp;content: ['./src/**/*.tsx'],
&amp;nbsp;&amp;nbsp;presets: [sharedConfig],
};

export default config;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;이러면 공통 tailwind.config만 바꿔도 home에서 변경된 custom 스타일을 사용할 수 있게 된다!&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;패키지 구성하기&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 공통 패키지이다.&lt;br /&gt;예시로 toast패키지를 들자면, packages/toast/package.json을 이렇게 구성해주었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;name&quot;: &quot;@repo/toast&quot;, //import할때 이 이름으로 import하면 된다.
&amp;nbsp;&amp;nbsp;&quot;version&quot;: &quot;1.0.0&quot;,
&amp;nbsp;&amp;nbsp;&quot;description&quot;: &quot;toast package&quot;,
&amp;nbsp;&amp;nbsp;&quot;main&quot;: &quot;./index.tsx&quot;, //export 할 파일
&amp;nbsp;&amp;nbsp;&quot;types&quot;: &quot;./index.tsx&quot;,
&amp;nbsp;&amp;nbsp;&quot;private&quot;: true,
&amp;nbsp;&amp;nbsp;&quot;license&quot;: &quot;ISC&quot;,
&amp;nbsp;&amp;nbsp;&quot;dependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;react&quot;: &quot;^18.2.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;react-dom&quot;: &quot;^18.2.0&quot;
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&quot;scripts&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;lint&quot;: &quot;npx prettier --write src &amp;amp; eslint src&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;test&quot;: &quot;jest&quot;
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&quot;devDependencies&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@repo/eslint-config&quot;: &quot;*&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@repo/jest-config&quot;: &quot;*&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@repo/typescript-config&quot;: &quot;*&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/jest-dom&quot;: &quot;^6.2.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@testing-library/react&quot;: &quot;^14.1.2&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;@types/jest&quot;: &quot;^29.5.11&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;jest&quot;: &quot;^29.7.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;jest-environment-jsdom&quot;: &quot;^29.7.0&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;ts-jest&quot;: &quot;^29.1.1&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;ts-node&quot;: &quot;^10.9.2&quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;typescript&quot;: &quot;^5.3.3&quot;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그리고 index.tsx를 아래와 같이 구성했다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import ToastProvider from './src/ToastProvider';
import useToast from './src/hooks/useToast';

export { ToastProvider, useToast };&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;사용할 apps의 package.json에서 아래와 같이 등록해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Eu9LO/btsDrDywNov/gvjlJztST3gDAvhQFpVn11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Eu9LO/btsDrDywNov/gvjlJztST3gDAvhQFpVn11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Eu9LO/btsDrDywNov/gvjlJztST3gDAvhQFpVn11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEu9LO%2FbtsDrDywNov%2FgvjlJztST3gDAvhQFpVn11%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;424&quot; height=&quot;261&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 이제 @repo/toast를 사용할 수 있게된다. ToastProvider로 App을 감싸주었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ToastProvider } from '@repo/toast';
import App from './App';
import './index.css';

const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity } } });

ReactDOM.createRoot(document.getElementById('root')!).render(
&amp;nbsp;&amp;nbsp;&amp;lt;QueryClientProvider client={queryClient}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ToastProvider&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;App /&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ToastProvider&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;lt;/QueryClientProvider&amp;gt;,
);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;빌드/테스트&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 등록한 패키지, 앱들을 실행시키거나, 빌드하거나, 테스트하려면 해당 패키지의 package.json에 script를 작성해주고, 그 커맨드를 turbo.json에 등록해주면된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;&quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
&amp;nbsp;&amp;nbsp;&quot;globalDependencies&quot;: [&quot;**/.env.*local&quot;],
&amp;nbsp;&amp;nbsp;&quot;pipeline&quot;: {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;build&quot;: { //전체 빌드
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;outputs&quot;: [&quot;dist/**&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;lint&quot;: { //전체 lint
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;dependsOn&quot;: [&quot;^lint&quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;test&quot;: {}, //전체 테스트
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;dev&quot;: { //전체 개발 모드로 실행
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;cache&quot;: false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;persistent&quot;: true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;type-check&quot;: {} //tsc
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;캐시 처리&lt;/h4&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/d0GofA/btsDrJMkvz7/TXev99PJ18IgpwZx2wIVy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0GofA/btsDrJMkvz7/TXev99PJ18IgpwZx2wIVy1/img.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;415&quot; style=&quot;width: 55.5606%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;56.21&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0GofA/btsDrJMkvz7/TXev99PJ18IgpwZx2wIVy1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0GofA%2FbtsDrJMkvz7%2FTXev99PJ18IgpwZx2wIVy1%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;859&quot; height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6nBnT/btsDs0mKL5n/bBxUmDaqu4taSnqWQaZsi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6nBnT/btsDs0mKL5n/bBxUmDaqu4taSnqWQaZsi1/img.png&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;441&quot; style=&quot;width: 43.2766%;&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;43.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6nBnT/btsDs0mKL5n/bBxUmDaqu4taSnqWQaZsi1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6nBnT%2FbtsDs0mKL5n%2FbBxUmDaqu4taSnqWQaZsi1%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;711&quot; height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파일이 변경되지 않으면 캐시가 사용되어 606ms만에 빌드가, 542ms만에 테스트가 끝난 모습이다.&lt;br /&gt;만약 파일이 변경되면, 해당 부분에 대해서만 재빌드/테스트를 하기때문에 굉장히 효율적이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;클라우드 캐싱&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 위의 캐시처리는 로컬 환경에서 파일을 통해 캐시하기때문에 github actions에서 빌드/테스트를 하는 경우에는 이를 쓸 수 가 없다.&lt;br /&gt;그래서 vercel에서는 클라우드 캐싱과 github actions에서의 캐싱을 지원한다.&lt;br /&gt;우리의 경우 github actions에서 사용하므로 공식문서의 두번째를 따라 진행했다. 굉장히 간단하게 설정할 수 있었다.&lt;br /&gt;&lt;a href=&quot;https://turbo.build/repo/docs/ci/github-actions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://turbo.build/repo/docs/ci/github-actions&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Using Turborepo with GitHub Actions &amp;ndash; Turborepo&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;How to use GitHub Actions with Turborepo to optimize your CI workflow&quot; data-og-host=&quot;turbo.build&quot; data-og-source-url=&quot;https://turbo.build/repo/docs/ci/github-actions&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Zi2aI/hyU2f2GtaO/xOKqkC6ohB903loLgjfcx1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/btQ4Do/hyU5Q70VSg/OVYkADCZs8vgBtRUuKqK00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot; data-og-url=&quot;https://turbo.build/repo/docs/ci/github-actions&quot;&gt;&lt;a href=&quot;https://turbo.build/repo/docs/ci/github-actions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://turbo.build/repo/docs/ci/github-actions&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Zi2aI/hyU2f2GtaO/xOKqkC6ohB903loLgjfcx1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/btQ4Do/hyU5Q70VSg/OVYkADCZs8vgBtRUuKqK00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Using Turborepo with GitHub Actions &amp;ndash; Turborepo&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How to use GitHub Actions with Turborepo to optimize your CI workflow&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;turbo.build&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frontend/문제 해결기</category>
      <category>TAILWIND</category>
      <category>turborepo</category>
      <category>vercel</category>
      <category>WebView</category>
      <category>모노레포</category>
      <category>웹뷰</category>
      <author>_0422</author>
      <guid isPermaLink="true">https://0422.tistory.com/344</guid>
      <comments>https://0422.tistory.com/344#entry344comment</comments>
      <pubDate>Sat, 13 Jan 2024 14:24:11 +0900</pubDate>
    </item>
  </channel>
</rss>