<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Antifreeze!</title>
    <link>https://9yujin.tistory.com/</link>
    <description>https://github.com/9yujin</description>
    <language>ko</language>
    <pubDate>Wed, 20 May 2026 22:23:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>한규진</managingEditor>
    <image>
      <title>Antifreeze!</title>
      <url>https://tistory1.daumcdn.net/tistory/4973336/attach/2d4671cbedc248258738384c52f95acf</url>
      <link>https://9yujin.tistory.com</link>
    </image>
    <item>
      <title>3분기 후일담</title>
      <link>https://9yujin.tistory.com/117</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(지난 줄거리...) 현재 돌아가고 있는 앱 서비스의 어드민 웹을 리뉴얼하는 외주 프로젝트를 시작했다. 타입스크립트 스터디를 같이 하면서 탬플릿 컴포넌트를 작성하는게 적지 않은 도움이 되었다. 그리고 야심차게 회사에 첫 지원서를 냈다. 최종 면접에서 떨어졌고, 그게 2분기 회고의 마지막이었다. (푸슝.. 하고 나오는 타이틀)&lt;/i&gt;.&lt;br&gt;&amp;nbsp;&lt;br&gt;주 2회 방영하는 드라마들을 보면, 한 주가 지난 뒤에 방송하는 회차에서 '지난 줄거리'로 시작하는 경우가 많다(무빙보다 이런 생각했음). 뭐 이런 느낌이랄까. 겨우 4-5일 지난 드라마도 다 까먹어서 줄거리 요약해주는데, 하물며 3개월마다 내는 회고글을 막 쓴다는건 참 불친절하다. 여러분은 지금 세상 친절하지만 수요없는 기술 블로그를 보고 계십니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리쿠르팅 1&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;토스인슈어런스 개발 어시스턴트 공고가 올라왔다. 그로부터 일주일 후에는 토스 Next 대규모 채용이 열렸고, 삼주 뒤에는 토스뱅크 채용이 열렸다. 당근 인턴에 떨어진지 채 일주일도 되지 않았을 때였다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHbwX/btswwRjH2JE/eCv83oi5RdmBXsB7OqlTD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHbwX/btswwRjH2JE/eCv83oi5RdmBXsB7OqlTD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHbwX/btswwRjH2JE/eCv83oi5RdmBXsB7OqlTD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHbwX%2FbtswwRjH2JE%2FeCv83oi5RdmBXsB7OqlTD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;155&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;2년간 개발을 공부하면서 꽤나 친숙해졌고 영향을 많이 받은 회사였다. 토스 슬래시나 FEConf에서 토스 개발자들의 발표에서 들은 내용을 프로젝트에서 비슷하게 써먹어보기도 했고, 친구가 토스 개발자와 같이 하는 프로젝트의 코드를 자주 읽어보기도 했다. 직접적인 개발 관련한 지식 외에도 많은 인사이트를 얻었다. Toss Sans나 토스 이모지를 자체적으로 디자인해 사용하는 것, 개발자의 생산성을 올리기 위해 다양한 시도를 하는 회사라는 사실이 정말 맘에 들었다.&lt;br&gt;&amp;nbsp;&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;1335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NGxOS/btsuZRfsjQc/kUVyni9sqDHAuKjhfWRO21/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NGxOS/btsuZRfsjQc/kUVyni9sqDHAuKjhfWRO21/img.jpg&quot; data-alt=&quot;토스에 고스락 카르텔을 만드려는 목적이 있어보이는 듀안.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NGxOS/btsuZRfsjQc/kUVyni9sqDHAuKjhfWRO21/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNGxOS%2FbtsuZRfsjQc%2FkUVyni9sqDHAuKjhfWRO21%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;411&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1335&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;결과적으론 세개 다 썼다. 처음 대학교 수시 원서를 쓸 때도 '여기는 가야한다'하는 학교에 교과, 종합, 논술 세 개를 다 박았다. 그 위로 적은 학교 세 군데를 전부 떨어지고 홍대에 입학했다. 지금까지 했던 선택 중에서 기억에 남는 선택 중 하나였다. 그 때 생각이 났다. 그만큼 가고 싶은 곳이랄까. 별도의 지원서 문항을 채울 필요는 없었다. 이력서와 나를 나타낼 수 있는 페이지만 제출하면 됐다. 기술 블로그 링크를 함께 넣었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;이력서&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;&lt;b&gt;어드민 탬플릿 개발 외주&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;전혀 기대하지 않았지만 생각보다 많은 도움이 되었던 프로젝트였다. 저번 분기에 솝퀘에서 어드민 웹을 새로 개발하는 외주를 하며 고민이 담긴 여러 컴포넌트들을 뽑아냈었다. 반복되는 뷰와 로직이 많은 어드민 프로젝트였기 때문이다. 테이블 컴포넌트를 다양한 상황에서 사용할 수 있도록 많은 기능을 넣어 추상화해 사용했다. 리액트쿼리와 함께 사용할때 효율적으로 상태를 관리할 수 있도록 작성했다. 타입스크립트를 적극적으로 사용해보려고 했다. 그 결과 (물론 지금보기엔 구리지만) 쓸만한 컴포넌트들이 많이 나왔고, 빠르게 피쳐들을 쳐낼 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;외주가 끝나고 곧바로 새로운 오퍼가 왔다. 내가 작성했던 코드들을 소프트 스퀘어드의 다음 프로젝트에서 사용할 수 있도록 탬플릿화하는 작업이었다. 내가 아닌 남이 읽는게 우선인 코드를 작성하는건 처음이라 정말 재밌었다. Jsdoc 작업이나 변수명은 물론, 내부 구현까지 신경쓰게 되더라. 패키징된 라이브러리가 아니라 깃허브 탬플릿이기 때문에 어떤 함수가 어떤 일을 하는지 더욱 명확하게 분리하고 가독성을 높이는 것이 중요했다. 또한, &lt;a href=&quot;https://toss.im/slash-21/sessions/3-3&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;이전 토스 슬래시 영상&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;을 보고 컴포넌트의 구조를 개선해보기도 했다. 모달 컴포넌트와 useModal 류의 훅, 그리고 트리거 되는 버튼으로 나누어져있던 로직을 &amp;lt;ModalButton/&amp;gt;과 같이 선언적인 방식으로 사용할 수 있도록 구현했다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&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;b&gt;리쿠르팅 2&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;6월 20일 토스인슈어런스 어시스턴트 지원서를 낸 후 2주동안 아무런 연락이 없었다. 심지어 그 사이에 올라와있던 공고도 내려가있었다. 다른 사람 뽑아놓고 나한테 말도 안해준 줄 알고 약간 서운했음. 그 사이에 토스 Next 챌린지를 지원했다. 며칠 뒤, 기다리던 토스인슈어런스 서류 합격 메일이 왔다. 과제 테스트 날짜가 잡혔다. 목요일이 토스인슈언스 과제였고 토요일이 Next 챌린지 과제였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;토스인슈어런스의 과제&lt;/b&gt;는 정말 신선했다. 단순히 무언가 요구사항대로 구현하는 것이 아니라, 실제 토인슈에 입사 후 해결해야하는 문제들과 관련이 있는 과제들이었다. 내 강점이 나름 잘 들어나도록 제출했던 과제라고 생각했다. 보안서약서를 작성해서 자세히는 기록하지 못한다. 그리고 1주일동안 또 연락이 없다가 다음 전형인 인터뷰 안내 메일이 왔다. 과제 합격이란다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;인터뷰&lt;/b&gt;는 7월 17일 월요일 오전이었다. 상수역에 있는 조그만 스터디룸을 빌렸다. 당근 면접 탈락한 바로 그 방이었음. 한시간씩 두번의 인터뷰를 연속으로 봤다. 첫 타임은 직무 인터뷰였다. 틀에 박힌 말이지만 말 그대로 시간 가는 줄 모르고 떠들었던 것 같다.&lt;br&gt;&amp;nbsp;&lt;br&gt;정말 좋은 시간이었다. 면접에서 이런 기분을 느낄 줄은 몰랐다. 토스코어에서 프론트엔드 개발하고 계시는 조유성님이 직무인터뷰를 진행해주셨다. 면접이 끝나고 나서야 생각난 건데, 올해 슬래시를 보며 가장 재밌게 보았던 영상의 주인공이었다. 반복되는 어드민 리소스를 줄이기 위해서 yaml과 비슷한 형식의 쉬운 코드를 자동으로 웹 코드로 변환해주는 아이디어였다. 백오피스를 중점적으로 다루어야 하는 직무 면접에 딱 맞는 인터뷰어였다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=3wxG1WLDONI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bocyYl/hyT54tznJT/RNqf8l9w43EnhlMUf1pFkK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=966_366_1046_454&quot; data-video-width=&quot;500&quot; data-video-height=&quot;281&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/3wxG1WLDONI&quot; width=&quot;500&quot; height=&quot;281&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;figcaption&gt;slash23 조유성님의 발표&lt;/figcaption&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;직무 인터뷰의 대부분은 내가 제출했던 과제 기반이었다. 내가 제출했던 답변에서 놓친 부분들은 힌트를 넌지시 알려주면서 다시 물어봐주시기도 했다. 그때 떠오른 생각을 말씀드렸더니, &quot;방금 말씀하신 내용을 코드로 한번 작성해보시겠어요?&quot;로 돌아왔다. 당연한거지만 라이브 코딩은 처음이어서 적잖이 당황했다. 면접 직전까지 교내근로에서 파이썬하다 왔더니 기본적인 자바스크립트 메소드들도 헷갈리더라. 웃으시면서 검색해도 된다고 하셨지만 그래서 더 긴장됐다. 라이브 코딩을 조진것 외에는 기분 좋은 인터뷰였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;왜 이런 코드를 작성하셨나요?&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; ~~한 부분이 문제라고 생각해 @@한 방법으로 개선해보았습니다!&lt;/i&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;어 제 생각엔 그렇게 하면 ##한 문제가 생길것 같은데, 이 점은 어떻게 해결해볼 수 있을까요??&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; 그렇다면 $$$를 사용해서 코드를 작성해볼 수 있을것 같아요. 더 좋은 방법이 있을까요..??&lt;/i&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;규진님이 했던 고민을 저희 토스팀에서도 이전에 똑같이 한 적이 있어요. 해당 고민의 결과가 저희 슬래시 라이브러리에 오픈소스로 올라와있으니, 규진님께서 보시면 도움이 될 수 있을것 같아요!&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;&amp;nbsp; &amp;nbsp; 헉.. 감사합니다! (라곤 안했던것같은데 뭐였더라)&lt;/i&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;면접에서 이런 대화를 나눈다는게 신기했다. 단순히 인터뷰이를 평가하고 &lt;b&gt;준비한 질문을 하는것이 아니라, 개발자와 재밌는 개발 이야기를 나누는 느낌&lt;/b&gt;이었다. 덕분에 점점 긴장이 풀려 마지막 즈음에는 친구와 이야기하듯이 편하게 떠들다 끝났다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;두번째 시간은 토인슈에서 같이 일하게 될 백엔드 개발자분이 인터뷰어로 들어오셨다. 같이 일하게 될 동료로서 궁금한 것들을 여쭈어보셨다. 세오스와 고스락에서 여러 서비스를 만들었던 과정을 많이 궁금해하셨다. 여기 블로그에 있는 내용들을 비슷하게 말씀드렸다. 어떤 방식으로 일하는걸 좋아하는지, 프론트 개발은 어떻게 하게 되었는지. 같이 일하고 싶게 만드는 인터뷰였다. 앞서 1차 인터뷰를 하면서 긴장이 풀린 상태라 그런지, 편안하게 이야기했던 것 같다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇게 (20분같은) 두 시간의 면접이 끝났다. 완전히 기진맥진한 상태였다. 분위기는 나쁘지 않은 것 같았다. 내 이야기를 듣고 더 궁금한게 생겨서 또 질문을 하는 느낌을 받았음. 사실은 운이 좋았다. 처음 자기소개를 하면서 백오피스나 어드민 페이지 경험이 있다는 부분을 강조했었다. 바로 직전에 했던 에그픽 어드민 외주는 기존에 돌아가는 레거시를 새 기술과 더 나은 사용성으로 개선하는 프로젝트였다. 고스락 프로젝트는 처음부터 어드민이 있었고, 심지어 두둥에서는 어드민 기능을 한층 강화했던 프로젝트였다. 현재 회사의 상황과 비슷하게 엮인 포트폴리오였다. 기획 요건 정의와 UI 디자인까지 내가 도맡아 했다는 점도 퍼즐처럼 잘 맞아 떨어졌다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;토스인슈어런스&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그 사이에 토스뱅크를 지원했고 삼일만에 서류에서 잘렸다. Next챌린지는 과제에서 떨어졌다.&amp;nbsp;마지막&amp;nbsp;토스인슈어런스에서&amp;nbsp;면접을 본지 정확히 일주일 뒤에 합격 전화가 왔다. 합격 소식은 전화로 직접 준다는 이야기를 들어서 며칠동안 계속 신경을 쓰고 있었더니 피곤했다. 방에 누워있는데 갑자기 010으로 시작하는 전화가 왔다. 합격이다! 싶었다. 좋은 소식을 전해드릴수 있게 되었고, 입사일은 삼주 뒤쯤으로 생각하고 있다 -라고 하심. 딱 그때 나트랑 가족여행이 예정되어 있었다. 눈물을 머금고 비행기를 취소했다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blCuWB/btsxqxrNzUf/YnUG9qe0CgBYlADqNCxQPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blCuWB/btsxqxrNzUf/YnUG9qe0CgBYlADqNCxQPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blCuWB/btsxqxrNzUf/YnUG9qe0CgBYlADqNCxQPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblCuWB%2FbtsxqxrNzUf%2FYnUG9qe0CgBYlADqNCxQPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;182&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;8월 7일에 처음 출근했다. 제작년 토인슈 개발팀이 코어로 이동한 이후 처음으로 다시 생긴 프론트 개발자였다. 때문에 토스코어의 프론트 챕터와 (물리적으로도 업무적으로도) 동떨어져 있는 상황이었다. 그래도 코어에서 많은 신경을 써주셨다. 매주 화,목요일마다 코어로 이동해 옆에서 많이 배웠고, 프론트 챕터 소모임 위클리에도 참여할 수 있었다. 코드리뷰도 코어의 두분이 전담해 많은 도움을 주셨다.&lt;br&gt;&amp;nbsp;&lt;br&gt;토인슈에서 Frontend Developer Assistant 라는 이름으로 일하면서 느낀 것들을 정리해본다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;일하기에 정말 좋은 환경이다. 어시스턴트라는 제한된 권한을 가진 형태로 들어와 정보 열람이나 온보딩에 약간의 어려움이 있었지만, 주변 동료들이 본인 일처럼 도와주었다. 덕분에 빠르게 해결해 퍼포먼스를 낼 수 있도록 적응할 수 있었던 것 같다. 개발적으로도 환경이 정말 잘 되어있다. TDS 덕분에 디자이너가 없는 상황에도 어느정도의 퀄리티를 보장할 수 있었다. 사내 프론트 라이브러리도 쓸만하다. 러프한 문서화가 약간은 아쉬운 포인트.&lt;/li&gt;&lt;li&gt;그치만 어시스턴트라고 크게 다를건 없었다. 일반 임직원과 다른점은, 회사의 모든 정보를 볼 수 있는 권한이 어시스턴트한테는 없다는 것. 그리고 아직 삼개월 아르바이트일 뿐이라는 심리적인 보호막을 칠 수 있었다는 것. 장점일 수도 단점일 수도 있지만, 토스에 계속 있고 싶다면 단점이 되겠다. 주인의식과 책임을 갖고 팀의 목표를 추구하는게 우선이라나.&amp;nbsp;&lt;/li&gt;&lt;li&gt;이런 코드리뷰는 처음 받아본다. 더 나은 설계에 대해 토론을 하고 추상화와 선언적인 코드에 대한 피드백을 받는다. 개발자마다 각자의 스타일이 있고 그걸 존중해주지만, 토스에서 전반적으로 중요하게 여기는 가치는 동일한 것 같다. 코드리뷰를 통해서 한달 사이에 많이 성장했다고 느낀다.&lt;/li&gt;&lt;li&gt;회사로 출근하면 가장 처음 마주치는 커뮤니티 팀과 커싸 바리스타분들이 항상 밝게 인사해주시는데, 매일 피곤에 쩔어서 인사하는 내가 죄송스러울 정도. 커피사일로와 사내 편의점도 진짜 최고다. 매일 다른 유부초밥과 오늘의 커피 먹으러 출근한다. 한타 커싸에는 카카오라떼와 홍시 스무디가 맛있고, 아크 커싸에는 그릭요거트와 복쿠르트가 맛있다. 아, 또 먹고 싶다.&lt;/li&gt;&lt;/ul&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm89iP/btsxvHmDcwG/YxZ9iYD5nRopavagzvbkk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm89iP/btsxvHmDcwG/YxZ9iYD5nRopavagzvbkk1/img.png&quot; data-alt=&quot;복쿠르트 첫 대면날. 요거트에 복숭아 샤베트임.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm89iP/btsxvHmDcwG/YxZ9iYD5nRopavagzvbkk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm89iP%2FbtsxvHmDcwG%2FYxZ9iYD5nRopavagzvbkk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;347&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;복쿠르트 첫 대면날. 요거트에 복숭아 샤베트임.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;명확한 비전을 갖고 있는 회사와, 확신을 갖고 일하는 동료들이 멋있었다! 사실 토스에 기대하던 개발 커뮤니티와는 약간 동떨어져있는 계열사에서 일하는게 약간 아쉬웠다. 하지만 한달간 일해보니 그 아쉬움을 상쇄할 수 있는 다른 것들이 많이 보였다. (물론 코어도 명확한 비전이 있고 멋있는 동료들이겠지만 눈으로 직접 본다는건 다르다.) 회사의 히스토리와 현재 어떤일을 하려고 하는지 대충 들었다. 아직 보험에 대한 지식이 별로 없어도 지금의 문제가 무엇이고 어떻게 해결하려고 하는지 공감할 수 있었다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;한규진B&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;처음 어시스턴트로 공고를 올릴 때부터 전환을 염두에 둔 자리라 듣기는 했다. 그래도 진짜 시켜주기 전까지는 모르는거니까 신경을 안쓰고 있었다.&amp;nbsp;&lt;s&gt;그러면서 내심 기대함.&lt;/s&gt;&amp;nbsp;삼개월 계약이 절반 정도 지났을때 계약직 전환에 대한 이야기를 들을 수 있었다. 이 회사에서 계속 일하고 싶다는 생각이 들 때 즈음이었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;계약 전환을 위해 인터뷰 프로세스를 또 거쳐야 한다. 원래 정규직 채용을 할때 직무인터뷰 이후에 컬쳐핏 인터뷰를 따로 진행하는데, 그때 안했던 컬쳐핏 인터뷰를 지금 하는 느낌. 팀 안에서 직접 일을 해본 경험이 있다보니 보통 채용 때 하는 컬쳐핏과는 약간 다르게 진행되지 않았을까. 코어의 개발자 리드가 아니라 인슈 리더가 직접 인터뷰를 진행하기로 되어 있어서, 조금 더 편한 마음으로 대화하고 나올 수 있었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vDMzY/btsxq3YtGf0/g7GUfi6JklKXJx2EAJJ2uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vDMzY/btsxq3YtGf0/g7GUfi6JklKXJx2EAJJ2uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vDMzY/btsxq3YtGf0/g7GUfi6JklKXJx2EAJJ2uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvDMzY%2Fbtsxq3YtGf0%2Fg7GUfi6JklKXJx2EAJJ2uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;80&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그렇게 한규진B가 되었답니다. Assistant라는 꼬리가 빠졌다는건 생각보다 기분이 좋다. 그렇다고 변한건 거의 없다. 어차피 다음날 그대로 출근해야 함. 월급이 조금 올랐고, 한달에 하루씩 생기던 연차 제도가 바뀌고. 임시 출입증으로 들락하던 회사를 이젠 법카로 찍고 들어갈 수 있게 되었고. 토스의 슬랙과 노션 채널에 권한이 생겼다.&lt;br&gt;&amp;nbsp;&lt;br&gt;후드티와 스티커세트 등이 담겨 있는 온보딩 키트와 명함을 받았고, 추석 연휴 이후 화요일에는 다른 정규 입사자들과 함께 온보딩을 했다. toss.im 이메일이 나올거고 새 맥북도 받을 예정. 두달 전에 16인치로 골랐다가 약간 후회했던지라 14인치로 다시 신청했다. 실물로 손에 들어오는게 있어야 어느정도 실감이 난다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JAgwu/btsxsoVrPWm/cK0FYlQsE8E1oTPm9uX8PK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JAgwu/btsxsoVrPWm/cK0FYlQsE8E1oTPm9uX8PK/img.jpg&quot; data-alt=&quot;자리 이름표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JAgwu/btsxsoVrPWm/cK0FYlQsE8E1oTPm9uX8PK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJAgwu%2FbtsxsoVrPWm%2FcK0FYlQsE8E1oTPm9uX8PK%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;400&quot; height=&quot;300&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자리 이름표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br&gt;처음 어시스턴트에 합격했을땐 이게 취업이라는 생각을 일절 안했다. 인턴이나 아르바이트 정도로 생각했음. 그 때문인지, 보통 취뽀했을때 느끼는 해방감이나 기쁨을 별로 누리지 못했던 것 같아 아쉽다. 처음 입사해서 팀원들과 밥먹고 커피마시면서 &quot;합격하고 뭐했어요? 해외여행 이런거 다녀왔어요?&quot; 하는 질문에, 어.. 음.. 그러게요.. 락페다녀왔어요. 라고 했던것 같은데.&lt;br&gt;&lt;br&gt;조금 더 많은 얘기를 쓰고 싶었는데, 쓰다보니까 너무 자랑처럼 느껴져서 말을 줄이게 된다. 지금까지 자랑 길~게 다 해놨으면서 무슨. 생각나는 것들 몇개만 적고 마무리.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;아침에 기숙사에서 나와 자유인들, 홍문관, 푸르지오 상가를 지나서 홍입으로 가는 길이 생각보다 나쁘지 않다. 씻으면서 그날 출근길에 들을 노래를 고르는데, 그 날 기분에 꽤 많은 영향을 끼친다. 어제는 이리카페에서 새로운 노래를 들었다. 오늘 출근길에는 그 밴드의 노래를 들으면서 갔다. 오월오일과 불고기디스코. 그리고 막 나온 로꼬의 새 앨범. 개발자를 그만하게 되는 날에는 카페하고 싶어졌다.&lt;/li&gt;&lt;li&gt;취준에 대한 스트레스를 남들에 비해서 덜 느꼈다는거도 감사하다. 지난 1-2년간 노력했던 것들이 취업을 위해서가 아니라, 그 당시의 목표를 위해서 한것이라는게 토스의 핏과 잘 맞아 떨어졌던 것 같다. 딱 내가 취준을 시작하려고 할때에 토인슈가 특정한 장점을 가진 개발자를 원하고 있었고, 서류와 인터뷰를 통해 본 지원자 중에 내가 가장 적합한 사람이 될 수 있었다는 것. 그리고&amp;nbsp;그 자리가 단기적인 자리가 아니라 장기적인 플랜을 갖고 만든 자리였다는 것. 만약에 당근을 붙었더라면 이런 기회는 없었을 것이라는 생각을 하면서.&amp;nbsp;감사하다!&lt;/li&gt;&lt;li&gt;묵혀뒀던 글을 올리기 직전 덧붙이는 한 문단. 저번주에 입사 후 일주일 정도 온보딩 세션을 여럿 들었다. 스스로가 만든 문화와 역사에 굉장한 자부심을 느끼는 회사라는 생각이 든다. 그리고 거기에 멋을 느끼고 공감하는 사람들만 채용되고 일을 하고 있다는 것도 확실히 느껴진다. 그 예로, 정부재난지원금 이슈가 처음 생겼을때 금요일에 누군가가 갑자기 아이디어를 내고, 곧바로 길드를 만들고, 여러사람이 달라붙어 일요일에 런칭을 했다는 이야기를 일주일동안 세번정도 들은 듯.&lt;br&gt;근데 이런 느낌을 단지 온보딩 세션에서만 전달 받을 수 있었던 건 아니다. 심심할때 팀 슬랙의 여러 채널을 주룩 살펴보는데, 멋있는 부분이 분명히 있다. #product 채널에서는 자기 팀이 만들어낸 서비스를 자랑하는 글을 일주일에 몇개씩 볼 수 있고, #ideas 채널에서는 토스 앱을 사용하다가 자기와 전혀 상관없는 팀의 서비스여도 '이런게 있으면 좋겠다'는 생각을 아무렇지도 않게 내뱉는다. 확실히 나보다는 멋있는 사람들이 모여있다. 토스팀에서 이사람들과 같이 일할때 적어도 가랑이는 찢어지지 말아야겠다는 생각이다.&lt;/li&gt;&lt;/ul&gt;</description>
      <category>  글을 위한 글</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/117</guid>
      <comments>https://9yujin.tistory.com/117#entry117comment</comments>
      <pubDate>Fri, 20 Oct 2023 17:19:04 +0900</pubDate>
    </item>
    <item>
      <title>[두둥] Next.js 웹 성능 최적화</title>
      <link>https://9yujin.tistory.com/116</link>
      <description>&lt;p data-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;1280&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L3tY8/btsm5sf3JLe/jFBkkQ4P8NgEeb19W7bkB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L3tY8/btsm5sf3JLe/jFBkkQ4P8NgEeb19W7bkB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L3tY8/btsm5sf3JLe/jFBkkQ4P8NgEeb19W7bkB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL3tY8%2Fbtsm5sf3JLe%2FjFBkkQ4P8NgEeb19W7bkB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;352&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 lightHouse 점수이다. 다행히 막 크게 안좋은 점수는 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 폰트 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;* 웹폰트가 로드되는 동안 텍스트가 계속 표시되는지 확인하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹폰트를 렌더링하는 방식에는 두가지가 있다.  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대체 글꼴이 새 글꼴로 바뀜 (FOUT - 스타일이 지정되지 않은 텍스트 플래시).&lt;/li&gt;
&lt;li&gt;&quot;보이지 않는&quot; 텍스트는 새 글꼴이 렌더링될 때까지 표시됨 (FOIT - 보이지 않는 텍스트 플래시).&lt;/li&gt;
&lt;/ul&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;1280&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q1RTr/btsm8YFDej5/n1uOz41yO6uJaVAAHHubEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q1RTr/btsm8YFDej5/n1uOz41yO6uJaVAAHHubEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q1RTr/btsm8YFDej5/n1uOz41yO6uJaVAAHHubEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ1RTr%2Fbtsm8YFDej5%2Fn1uOz41yO6uJaVAAHHubEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;343&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 폰트가 불러와지기 전까지는 아무런 텍스트도 나타나지 않고 있다가 폰트가 로드된 후에 텍스트가 뙇 나타난다. 크롬 브라우저는 기본적으로 FOIT방식을 사용하고 있다 (사파리에서는 궁서체로 보이다가 바뀌더라 - FOUT 방식) .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;font-display :swap;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@font-face&lt;/code&gt;의 옵션을 통해 렌더링 방식을 바꿀 수 있다. &lt;code&gt;swap&lt;/code&gt;은 글자를 차단하는 시간을 줄이고 글자가 불러와지면 교체(swap)하는 방식이다. &lt;code&gt;optional&lt;/code&gt;과 &lt;code&gt;fallback&lt;/code&gt; 옵션을 통해, 비슷하게 동작하지만 글꼴 차단 기간 또는 swap을 대기하는 (글꼴 교체) 기간을 조절할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1689231653341&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;글꼴은 로드하는 데 시간이 걸리는 대용량 파일인 경우가 많습니다. 이를 처리하기 위해 일부 브라우저는 글꼴이 로드될 때까지 텍스트를 숨깁니다(&amp;quot;보이지 않는 텍스트의 깜박임(FOIT)&amp;quot;. 성능을 &quot; data-og-host=&quot;web.dev&quot; data-og-source-url=&quot;https://web.dev/avoid-invisible-text/&quot; data-og-url=&quot;https://web.dev/i18n/ko/avoid-invisible-text/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/n2gOO/hyTip6Qja6/VCuqUl3LZhVBHok0MnnpQK/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/kjyxx/hyTjIDIS6k/Mpt6kuPMi2qXkYxVxDzq20/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://web.dev/avoid-invisible-text/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://web.dev/avoid-invisible-text/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/n2gOO/hyTip6Qja6/VCuqUl3LZhVBHok0MnnpQK/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200,https://scrap.kakaocdn.net/dn/kjyxx/hyTjIDIS6k/Mpt6kuPMi2qXkYxVxDzq20/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&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;글꼴은 로드하는 데 시간이 걸리는 대용량 파일인 경우가 많습니다. 이를 처리하기 위해 일부 브라우저는 글꼴이 로드될 때까지 텍스트를 숨깁니다(&quot;보이지 않는 텍스트의 깜박임(FOIT)&quot;. 성능을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;web.dev&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;&lt;b&gt;* 웹폰트 미리 로드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FOUT를 방지 하기 위해 즉시 필요한 웹폰트를 미리 로드할 수 있다. html head에 이 애플리케이션에 대한 Link 요소를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
  &amp;lt;link
    rel=&quot;preload&quot;
    as=&quot;style&quot;
    crossOrigin=&quot;anonymous&quot;
    href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css&quot;
  /&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;preload&lt;/code&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;스크린샷 2023-07-10 오후 3.53.11.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X6uLz/btsm8hevXgN/M98cG5ZBk8SggSqHckKETK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X6uLz/btsm8hevXgN/M98cG5ZBk8SggSqHckKETK/img.png&quot; data-alt=&quot;크롬만 생각했었지..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X6uLz/btsm8hevXgN/M98cG5ZBk8SggSqHckKETK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX6uLz%2Fbtsm8hevXgN%2FM98cG5ZBk8SggSqHckKETK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;287&quot; data-filename=&quot;스크린샷 2023-07-10 오후 3.53.11.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;598&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;하지만 Safari (또는 모바일 브라우저)에서는 적용이 되지 않았다. 죄다 기본 글꼴로 깨져서 나타났다. 스택오버플로우에도 비슷한 질문들이 많았음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;!-- ... --&amp;gt;
  &amp;lt;link
    rel=&quot;preload&quot;
    as=&quot;style&quot;
    href=&quot;https://asset.dudoong.com/common/fonts/dudoong-fonts.css&quot;
    crossOrigin=&quot;anonymous&quot;
  /&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두둥 웹의 Origin과 현재 사용하고 있는 웹폰트 CDN의 Origin이 달라서 cross-origin 문제가 생긴다고 생각했다. &lt;code&gt;@font-face&lt;/code&gt; css파일을 두둥 인프라에서 사용하고 있는 CDN에 띄워서 직접 제공해보려고 했다. 그래도 결과는 똑같았다.&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;스크린샷 2023-07-10 오후 4.21.23.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;1480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qpE2l/btsmWlo6bov/Q7rsZWuyRhNM7XaWlR2MLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qpE2l/btsmWlo6bov/Q7rsZWuyRhNM7XaWlR2MLK/img.png&quot; data-alt=&quot;https://www.w3.org/TR/css-fonts-3/#font-fetching-requirements&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qpE2l/btsmWlo6bov/Q7rsZWuyRhNM7XaWlR2MLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqpE2l%2FbtsmWlo6bov%2FQ7rsZWuyRhNM7XaWlR2MLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;422&quot; data-filename=&quot;스크린샷 2023-07-10 오후 4.21.23.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;1480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.w3.org/TR/css-fonts-3/#font-fetching-requirements&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글꼴을 요청할때는 CORS요청으로 전송된다. 그래서 도메인이 다를땐 요청이 되지 않던것이었는데, 서브 도메인도 똑같이 cross-origin으로 분류되기 때문이었다. CDN 설정에서 Allow-Origin을 모두 열어주었음에도 불구하고 제대로 요청이 되지 않았다. &lt;a href=&quot;https://blog.banksalad.com/tech/font-preload-on-safari/&quot;&gt;이 글&lt;/a&gt;을 보면 모종의 이유로 safari에서 cdn에 대한 preload 요청이 안되는것으로 보였다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WOFF2와 같은 용량이 작은 압축된 파일을 사용하기&lt;/li&gt;
&lt;li&gt;@font-face를 통해 글꼴 모음을 정의하기&lt;/li&gt;
&lt;li&gt;서브셋, 다이나믹 서브셋 등을 통해 글꼴의 용량을 줄이기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번과 2번같은 경우엔 이미 적용이 되어 있었고, 3번을 통해 웹폰트 리소스의 용량을 줄여보았다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DeOoQ/btsninFcp4k/PtHtbfRWoeR4vLWnl4dYz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DeOoQ/btsninFcp4k/PtHtbfRWoeR4vLWnl4dYz1/img.png&quot; data-alt=&quot;https://www.44bits.io/ko/post/optimization_webfont_with_pyftsubnet&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DeOoQ/btsninFcp4k/PtHtbfRWoeR4vLWnl4dYz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDeOoQ%2FbtsninFcp4k%2FPtHtbfRWoeR4vLWnl4dYz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;675&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.44bits.io/ko/post/optimization_webfont_with_pyftsubnet&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서브셋 폰트&lt;/b&gt;는 폰트 파일에서 불필요한 글자를 제거하고 사용할 글자만 남겨둔 폰트다. 보통은 '뷁'과 같은 글자는 쓰지 않으니까. 26개의 알파벳과 달리 한글 조합은 만개 언저리나 되는데, 서브셋 폰트로 만들어 사용하면 2300개 정도의 글자만 남겨둘 수 있다. 덕분에 용량이 훨신 작아진다. 서브셋 폰트 메이커라는 도구를 통해 변환할 수 있다. 근데 웹에서 쓰이는 웬만한 폰트는 이미 있는듯.&lt;/p&gt;
&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;code&gt;unicode-range&lt;/code&gt; 속성을 통해, 해당 유니코드 영역의 문자가 사용될 때 브라우저가 폰트 파일를 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;/* [0] */
@font-face {
    font-family: 'Pretendard';
    font-style: normal;
    font-display: swap;
    font-weight: 100;
    src: url(../../../packages/pretendard/dist/web/static/woff2-dynamic-subset/Pretendard-Thin.subset.0.woff2) format('woff2'), url(../../../packages/pretendard/dist/web/static/woff-dynamic-subset/Pretendard-Thin.subset.0.woff) format('woff');
    unicode-range: U+f9ca-fa0b, U+ff03-ff05, U+ff07, U+ff0a-ff0b, U+ff0d-ff19, U+ff1b, U+ff1d, U+ff20-ff5b, U+ff5d, U+ffe0-ffe3, U+ffe5-ffe6;
}
/* [1] */
@font-face {
    font-family: 'Pretendard';
    font-style: normal;
    font-display: swap;
    font-weight: 100;
    src: url(../../../packages/pretendard/dist/web/static/woff2-dynamic-subset/Pretendard-Thin.subset.1.woff2) format('woff2'), url(../../../packages/pretendard/dist/web/static/woff-dynamic-subset/Pretendard-Thin.subset.1.woff) format('woff');
    unicode-range: U+d723-d728, U+d72a-d733, U+d735-d748, U+d74a-d74f, U+d752-d753, U+d755-d757, U+d75a-d75f, U+d762-d764, U+d766-d768, U+d76a-d76b, U+d76d-d76f, U+d771-d787, U+d789-d78b, U+d78d-d78f, U+d791-d797, U+d79a, U+d79c, U+d79e-d7a3, U+f900-f909, U+f90b-f92e;
}
/* https://github.com/orioncactus/pretendard */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프리텐다드 다이나믹 서브셋 폰트 파일의 내용이다. 한글은 매우 자주 사용하는 문자들 조금과, 비교적 적게 사용되는 문자들 다수로 이루어져 있다고 한다. 그걸 구글께서 어쩌구저쩌구 해서 최적의 unicode-range로 나누었다고 한다. &lt;a href=&quot;https://www.googblogs.com/tag/korean/&quot;&gt;https://www.googblogs.com/tag/korean/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHnR9B/btsnjb5yYa1/vOAi0qbrAKhQjkJb2SDdIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHnR9B/btsnjb5yYa1/vOAi0qbrAKhQjkJb2SDdIK/img.png&quot; data-alt=&quot;가벼운 프리텐다드와 0.6메가 지마켓산즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHnR9B/btsnjb5yYa1/vOAi0qbrAKhQjkJb2SDdIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHnR9B%2Fbtsnjb5yYa1%2FvOAi0qbrAKhQjkJb2SDdIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;276&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가벼운 프리텐다드와 0.6메가 지마켓산즈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 Header의 폰트로 사용하는 Gmarket Sans를 다이나믹 서브셋 파일로 변경만 해도 기존 1.3mb에서 408kb까지 용량을 줄일 수 있었다. 프리텐다드는 &lt;code&gt;@font-face&lt;/code&gt;의 src에서 local을 통해 로컬 폰트파일을 사용하도록 되어있었기 때문에, 기존 용량이 얼마나 되는지 따로 알아보긴 귀찮아서 패스. 대부분 아이폰에 프리텐다드가 설치되어 있을 일은 별로 없다보니 프리텐다드 역시 다이나믹 서브셋 폰트로 바꾸어 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 next/font를 적용해볼걸 하는 생각이 든다. 처음엔 구글폰트가 아닌 웹폰트를 사용했기 때문에 라이브러리 사용을 고려하지 않았다. 근데 로컬로 다운받아서 서브셋 폰트 만들고, 내 CDN에 올리고 등의 여러 시도를 하면서.. 이럴바엔 그냥 로컬폰트로 next/font를 적용하는게 맞지 않나? 하는 생각.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 이미지 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트보다 더 큰 문제는 이미지에 있었다. 두둥의 메인페이지는 호스트가 올린 포스터 이미지를 그대로 저장했다가 보여주고 있었다. 용량이 매우 많은 이미지로 업로드해놓으면 그런대로 로딩이 느려지는 상황이었음.&lt;/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;1280&quot; data-origin-height=&quot;977&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bivZMC/btsnim1Zo89/MPPkTNIdzlf9kJa2XrReCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bivZMC/btsnim1Zo89/MPPkTNIdzlf9kJa2XrReCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bivZMC/btsnim1Zo89/MPPkTNIdzlf9kJa2XrReCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbivZMC%2Fbtsnim1Zo89%2FMPPkTNIdzlf9kJa2XrReCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;382&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;977&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 이미지 최적화를 위해서 이런 것들을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 사이즈를 보여줄 크기 또는 뷰포트에 맞게 변환해서 제공한다.&lt;/li&gt;
&lt;li&gt;용량이 작게 압축된 파일 확장자(WEBP)를 이용한다.&lt;/li&gt;
&lt;li&gt;처음에 모든 이미지를 로드하지 않고, 뷰포트에 보여질 것들만 lazy loading 한다.&lt;/li&gt;
&lt;li&gt;이미지의 너비와 높이를 지정해 Cumulative Layout Shift를 방지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 &lt;code&gt;Next/Image&lt;/code&gt;를 도입했다. 위의 것들을 구현하기 위해선 &lt;code&gt;intersection-observer&lt;/code&gt; 등을 통해 직접 뷰포트에 보이는지를 캐치해 lazy loading을 적용해야 하고(크롬은 된대), 이미지를 업로드할 때 별도의 이미지 압축 라이브러리를 통해 리사이징과 압축을 한 후에 스토리지에 업로드하는 방식이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;내가 모르는거 있으면 말좀&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 &lt;code&gt;Next/Image&lt;/code&gt;는 이런 기능들을 다 제공해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;Image
  src={img}
  fill={true}
  sizes=&quot;(max-width: 768px) 50vw, 25vw&quot;
  alt={props.name}
  priority
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 메인 페이지에 적용된 &lt;code&gt;Image&lt;/code&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;* fill&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 시프트를 없애기 위해 &lt;code&gt;width&lt;/code&gt;와 &lt;code&gt;height&lt;/code&gt;를 사용한다 했는데, 대신 &lt;code&gt;fill&lt;/code&gt; 속성을 사용했다. &lt;code&gt;fill&lt;/code&gt;을 통해 이미지가 상위 요소를 채우도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;position: relative;
padding-top: 141.4%;
overflow: hidden;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Image 컴포넌트의 상위 요소에는 위와 같이 &lt;code&gt;relative&lt;/code&gt;와 &lt;code&gt;overflow: hidden&lt;/code&gt; 속성을 주고, 141.4%(A사이즈 용지)의 비율로 유지되도록 했다.&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;* sizes&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지가 반응형일때 &lt;code&gt;next-images&lt;/code&gt;는 기본적으로 이미지의 크기를 100vw로 생각하고 내려준다. &lt;code&gt;sizes&lt;/code&gt; 속성을 사용하면 실제 이미지의 사이즈가 100vw보다 작을거라고 브라우저에게 알릴 수 있다. 모바일 화면일땐 1열에 포스터가 두개, 아닐 땐 네개씩 들어가기 때문에 (max-width: 768px) 50vw, 25vw로 설정해주었다. &lt;code&gt;sizes&lt;/code&gt;속성을 사용하지 않았을때보다 16배 작은 이미지를 받아올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 이미지의 우선순위를 높이고 preload하기 위해 &lt;code&gt;priority&lt;/code&gt; 속성을 줄 수 있고, 이미지가 로딩되기까지 보여줄 placeholder 또는 blur 이미지를 설정할 수도 있다. 스크롤 없이 볼 수 있는 이미지가 지연 로드되면 페이지 수명 주기 후반에 렌더링되므로 preload 속성을 주는 것이 좋다. 이렇게 &lt;code&gt;Next/Image&lt;/code&gt; 컴포넌트로 대체하면서 두둥 메인 페이지의 초기 이미지 크기를 1800kb에서 444kb로 줄일 수 있었다. 이미지만으로도 확실히 로딩 속도가 빨라짐을 체감할 수 있었다. 제일 대문짝만하긴 해서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 번들 사이즈 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편적으로 쉽게 번들의 사이즈를 최적화하는 방법으로는 코드 스플리팅이 있다. Next는 기본적으로 pages내의 파일들을 빌드 과정에서 분할해 코드 스플리팅을 자동적으로 지원해준다. 그 외에 바로 가져올 필요가 없는 코드들은 dynamic import를 통해 필요한때에 불러올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두둥의 경우엔 모달(혹은 바텀시트)를 글로벌로 제어하기 위해, 상위 컴포넌트에 위치해놓고 전역상태를 통해 관리하고 있다. 해당 컴포넌트를 필요한 경우에만 불러올 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const GlobalOverlay = dynamic(
  () =&amp;gt; import('@components/shared/overlay/GlobalOverlay'),
  { ssr: false },
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 MdViewer, QRcode 등의 외부 라이브러리들을 &lt;code&gt;next/dynamic&lt;/code&gt;을 통해 동적으로 불러오고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용된 외부 라이브러리들이 많은 용량을 차지하는 경우가 많기 때문에 트리셰이킹을 하곤 한다. &lt;code&gt;next/bundle-analyzer&lt;/code&gt;를 통해 Next 프로젝트를 빌드할 때 어떤 모듈이 얼마나 많은 용량을 차지하는지 시각적으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/@next/bundle-analyzer&quot;&gt;https://www.npmjs.com/package/@next/bundle-analyzer&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/91b8s/btsnpE2NtvG/tGOe5bOBDRkW0z3rVSddrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/91b8s/btsnpE2NtvG/tGOe5bOBDRkW0z3rVSddrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/91b8s/btsnpE2NtvG/tGOe5bOBDRkW0z3rVSddrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F91b8s%2FbtsnpE2NtvG%2FtGOe5bOBDRkW0z3rVSddrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;319&quot; data-origin-width=&quot;1280&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;사실 크게 눈에 띄는 부분은 없다. 다만 디자이너가 랜딩페이지의 일러스트로 만들어준 이미지들을 그대로 svg로 넣었는데, 지금 생각해보니 미친 짓이었음. 어차피 랜딩페이지 리뉴얼 작업중이라 나중에 싹 바꿀 예정이기 때문에 지금 당장은 건들 생각이 없다. 페이지 맨 위 섹션 외의 아래 나오는 섹션들은 다이나믹 임포트를 통해 레이지하게 로딩되도록 수정해주었다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-13 오후 3.44.05.png&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqXNXl/btsnwocseP3/t1ersPTvIZM7NA0qIaynPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqXNXl/btsnwocseP3/t1ersPTvIZM7NA0qIaynPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqXNXl/btsnwocseP3/t1ersPTvIZM7NA0qIaynPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqXNXl%2FbtsnwocseP3%2Ft1ersPTvIZM7NA0qIaynPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1778&quot; height=&quot;572&quot; data-filename=&quot;스크린샷 2023-07-13 오후 3.44.05.png&quot; data-origin-width=&quot;1778&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;수정 전후 &lt;code&gt;pages/index.js&lt;/code&gt; 모듈의 analyzer 결과이다. 빌드 파일에 무거운 svg들이 제거된것을 시각적으로 볼 수 있다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-13 오후 3.56.38.png&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnyrL/btsnvZqwIjP/MltQj8HuzqosBY5fn9vtJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnyrL/btsnvZqwIjP/MltQj8HuzqosBY5fn9vtJK/img.png&quot; data-alt=&quot;전 / 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnyrL/btsnvZqwIjP/MltQj8HuzqosBY5fn9vtJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlnyrL%2FbtsnvZqwIjP%2FMltQj8HuzqosBY5fn9vtJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1738&quot; height=&quot;646&quot; data-filename=&quot;스크린샷 2023-07-13 오후 3.56.38.png&quot; data-origin-width=&quot;1738&quot; data-origin-height=&quot;646&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;최적화 작업 전 후로 빌드된 JS파일의 크기가 눈에 보이게 줄어들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-12 오후 5.39.07.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VT8Yj/btsnpEGyrc4/aWnYhckFVnJHkDn3KX5KZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VT8Yj/btsnpEGyrc4/aWnYhckFVnJHkDn3KX5KZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VT8Yj/btsnpEGyrc4/aWnYhckFVnJHkDn3KX5KZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVT8Yj%2FbtsnpEGyrc4%2FaWnYhckFVnJHkDn3KX5KZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;676&quot; data-filename=&quot;스크린샷 2023-07-12 오후 5.39.07.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 페이지는 89점, 공연 상세페이지는 98점까지 올릴 수 있게 되었다. 아맞다 접근성.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 32px; top: 8291.08px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>  긴호흡/고스락 티켓</category>
      <category>nextjs</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/116</guid>
      <comments>https://9yujin.tistory.com/116#entry116comment</comments>
      <pubDate>Thu, 13 Jul 2023 16:03:56 +0900</pubDate>
    </item>
    <item>
      <title>2분기 후일담</title>
      <link>https://9yujin.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;매 분기 회고를 쓰기 전엔 항상 이전에 썼던 글들을 쫙 읽는다. 그러다보니 처음에 썼던 '전역 후 1년 회고'는 정말 여러번 읽었다. &lt;span style=&quot;color: #333333;&quot;&gt;1년동안 열심히 달려왔다는 뿌듯함과 온갖 뽕에 가득차서 썼던 글이었다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; &lt;/span&gt;단순히 재미 외에도 이력서나 포트폴리오를 정리할 때, 내가 어떤 생각을 했었는지 참고를 하기위해 종종 찾아온다. 지금도 그때와는 별반 다를 건 없다. 내가 운이 좋은 케이스라는걸 여전히 자각하고 있고, 개강은 일년에 두번씩 매번 찾아온다. 계속 새로운 무언가를 시작한다. 그리고 매번 다음 분기를 기약하면서 이런거 해야지 - 하면서 글을 끝내고. 그 다음 분기 회고에선 아쉬움을 토로하면서 시작.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글도 똑같다. 알고리즘 스터디는 하나가 파토났고, 개발자 글쓰기 소모임도 지금은 돌아가지 않고 있다. 스스로 약속했던 두둥에 관한 기술적인 글은 하나 올라오고 끝났고, 내 알고리즘 실력은 저번과 크게 다른건 없는것 같음. 그래도 무언간 계속 해왔으니까 열심히 적어봐야겠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 개강했다&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기숙사에 들어왔다. 졸업 전 마지막으로 제대로 개강하는 학기였을듯. 졸업하기 전에는 그래도 한번은 기숙사에 살아봐야 하지 않나 하는 생각. 결과적으론 정말 좋은 선택이었다. 이런저런 핑계대고 방학때와 다음학기에도 기숙사에 살게 될듯. 뭔가.. '마지막 학기다, 마지막 전공 시험이다' 하면 괜히 의미부여하고 긴장되고 그럴줄 알았는데, 그런건 전혀 없었다. 오히려 '학교 왜다니지'라는 말만 하루 열번씩 하고 다녔다. 제대로 듣는 수업은 소프트웨어공학 하나밖에 없으면서 근로하느라 매일 문헌관에서 하루종일 앉아있었다. 그냥 근로하러 학교 오는 느낌.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;처음 근로 시작했을 땐, 그냥 내 공부 집중해서 하고 내 코딩 빡세게 할 수 있을 줄 알았음. 물론 할수야 있었는데, 막상 가니까 그렇게 열심히는 안하더라. 환경의 문제라기엔 조용하고 일도 별로 안시키고 에어컨도 빵빵하게 틀어주긴 했다. 그냥 내 문제였음. 에어팟을 못낀다는게 좀 컸다. MZ 아니랄까봐. 노래(특히 락)을 안들으면 코드가 잘 나오지 않는달까.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그럼 근로에 있는동안 무얼 했나. 티스토리 블로그 조회수 확인. 깃허브 피드 확인. 벨로그 트렌딩 정독. 눈물의 코딩파티라고 부르고 있는 자바스크립트 알고리즘 스터디. 오, 이건 나름 재밌었다. 파이썬으로 알고리즘을 반년 넘게 풀었는데도, 2주일 준비했던 자바스크립트 알고리즘 풀이가 훨신 익숙했다. 아무래도 '알고리즘 스터디' 보다는 '코딩파티'라는 이름이 더 기분이 좋아보인다. 그치만 눈물이 나는건 어쩔 수 없어. 어쨌든, 그렇게 시간을 보냈다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 어쩌다 외주를 시작했다&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 절대 안할거라고 생각했던 솝퀘 외주였음. 민준이에게 갑자기 걸려온 웹 어드민쪽 빵꾸났는데 할 생각있냐 라는 전화에 하겠다고 했음. 한달에 백만원이라는 (개꿀)돈과, 디테일따윈 전혀 신경 쓰지않아도 되는 &lt;span style=&quot;color: #666666;&quot;&gt;&lt;s&gt;남의 코드&lt;/s&gt;&lt;/span&gt;&amp;nbsp; 어드민이었기 때문이었다. 유지보수하라고 받아 본 코드는 생각보다 더 처참했는데, 클래스형 컴포넌트가 프로덕션에서 돌아가고 있는건 실제로 처음봤다. 취업해서 실무 코드를 보더라도 비슷한 일이 생길 수 있긴 하겠지. 다행인건, 그 코드에 반창고를 붙이는 일보단 아예 새로운 레포에서 처음부터 만드는 일이 더 많을거라는 것.&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;1648&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOC0Ai/btsjlGikKTy/SUeGssVTkwyU68KKk934lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOC0Ai/btsjlGikKTy/SUeGssVTkwyU68KKk934lk/img.png&quot; data-alt=&quot;요즘은 계약도 콤퓨타로 하나뵤&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOC0Ai/btsjlGikKTy/SUeGssVTkwyU68KKk934lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOC0Ai%2FbtsjlGikKTy%2FSUeGssVTkwyU68KKk934lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;256&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;768&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;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;컴포넌트를 제네릭 함수로 쓰기 - 테이블 컴포넌트 추상화&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/114&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;제네릭 조건부 타입 활용하기 - 컴포넌트를 커스텀훅으로 제공&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;외주를 하면서 계속 느끼는것. 이걸로 돈을 벌 수가 있구나. 손가락과 vscode만으로 처음으로 번 돈이었다. 감회가 새로움. 하지만 돈 백만원으로 일하기엔 내가 너무 아깝다! 내가 생각하는 만큼의 가치를 남들에게 인정받기 위해선 더 많은 노력이 필요하다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 타입스크립트 스터디&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;넥스터즈 활동 후기에서 타입스크립트에 대한 얘기를 했었다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #353535;&quot;&gt;타입을 쓰는데서 실력의 편차가 크게 나타난다. &lt;/span&gt;&lt;/span&gt;이번학기 중에서 가장 유의미한 활동이었다. 이펙티브 타입스크립트 책을 가지고 두달동안 정리를 해보았다. 모든 내용을 다 기억하고 내것으로 만들었다곤 할 순 없지만 '타입' 자체에 대한 개념이 다시 잡은게 중요하다. 할당 가능한 값들의 집합으로 생각하기. 이게 매우 큰 차이라고 느낀다. 위의 단락에서 링크 걸었던 기록들도 타입스크립트 스터디의 영향이 무조건 있다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/108&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;1. 타입스크립트 이해하기 : 타입과 타입 호환성&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;2. 객체 리터럴의 타입 체크 : 타입 호환성과 신선도&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/112&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;3. 타입 추론과 타입 넓히기&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;책의 뒷 내용으로 갈수록 실무적인 내용이 많아 글로 정리할 필요는 못느껴서 세개정도로만. 이 외에 위의 단락에서 링크 걸었던 기록들도 타입스크립트 스터디의 영향이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;학교에서 프론트 하는 사람을 찾기 정말 힘들다. GDSC 내에서 프론트엔드 밋업을 기획한적이 있었는데, 사람이 없어서 시작도 못했단다. 그런 와중에 같이 프론트 공부하는 사람들은 꽤 소중하다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 자기 객관화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;제일 어렵다!&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;이력서를 쓰는건 동아리 지원서를 쓰면서 느꼈던거랑은 전혀 달랐다. 차라리 '너에 대해 이런점이 궁금해!' 라고 대놓고 물어봐줬으면 좋겠다. 인터넷에서 볼 수 있는 많은 선배 개발자들의 이력서들을 많이 읽어봤다. 다들 본인의 이런 점을 내세우는구나. 이런 노력을 해왔구나. 덩달아 &lt;b&gt;나는 어떤 개발자인가. 어디에 강점이 있고, 무엇을 느꼈는가&lt;/b&gt;. 이런걸 계속 고민했던 것 같다. 최대한 많이 녹여내려고 했다. 꾸준히 블로그와 지원서를 통해 적었던 글이 많은 도움이 되었다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;고스락 공연을 준비하면서 불편함을 느꼈고, 그래서 프로젝트를 시작했어요. 지금은 두둥이라는 멋진 서비스를 만들었답니다. 그 과정에서 멋진 UX와 디자인을 고민하며 구현하는 것을 좋아했고, 그래서 이러이러한 경험이 있어요. 팀원들과 협업을 하면서 디자인시스템과 컴포넌트 설계의 중요성을 느꼈고, 그에 집중해서 공부를 해왔어요. 요즘은 특히 타입스크립트에 재미를 느껴 직접 적용을 해보고 있습니다. 그리고 그 과정을 블로그에 기록하는걸 좋아해요. 실제로 어떤 글은 많은 개발자들이 꾸준히 찾아와주고 있답니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이렇게 지난 2년을 되돌아보면서 개발자 한규진의 소개를 대표적으로 세줄로 뽑아보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주변에 있는 문제를 찾고 기술적으로 해결해 나가는 과정을 즐거워합니다.&lt;/li&gt;
&lt;li&gt;더 나은 UX와 UI에 대해 고민하며, 디테일에 욕심이 있습니다.&lt;/li&gt;
&lt;li&gt;지식과 경험을 공유하는 것을 좋아합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서의 자기소개 챕터 아래에서 프로젝트와 커뮤니티 활동을 통해 직∙간접적으로 확인할 수 있는 내용들이었다. 알아봐줬으면 하는 마음에 블로그 글이나 깃허브 내역을 통해 근거를 제시할 수 있는 내용들로 꾹꾹 눌러담았다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&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;b&gt;5. 첫 면접을 봤다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;인턴이긴 하지만 큰 회사에서 처음으로 보는 면접이었다. 그리고 난 정말 찐따같은 면접자였다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;찐따 특 : 친구와 말싸움하고 집에 와서 침대에 누우면 하지못했던말이 막 생각남. '아 그때 그렇게 말할걸!', '그렇게 받아칠걸!' 한바탕 뇌내망상 돌림.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kxi35/btsjpFJubgg/bRi2qNoDlCYmlk3wqKVTI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kxi35/btsjpFJubgg/bRi2qNoDlCYmlk3wqKVTI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kxi35/btsjpFJubgg/bRi2qNoDlCYmlk3wqKVTI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkxi35%2FbtsjpFJubgg%2FbRi2qNoDlCYmlk3wqKVTI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;307&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 조리있게 잘 대답할 수 있었는데. 아쉬움이 많이 남는다. 단순 기술 질문을 제외한 대부분의 질문이 모두 예상질문 안에서 나왔다. 준비를 해갔는데도 불구하고 정돈된 문장을 말하지 못했던 것 같다. 적어놓은 글을 보고 말하다 보면 더 어필하고 자랑하고 싶은 내용이 막 생각난다. 그래서 자꾸 말을 덧붙이다보면 문장이 구름처럼 불어나는 듯 하다. 한마디로 정리하면 욕심이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;기술 질문도 뭐 마찬가지다. 이건 내가 안일했다고 생각한다. 벨로그나 깃허브에서 돌아다니는 '프론트엔드 기술질문 리스트'에 있을 법한 질문이 거의 그대로 나왔다. 분명히 마주쳤을텐데 '음~ 다 아는 내용이네'하고 그냥 넘어갔던게 생생히 기억이 난다. 그래놓고 제대로 대답 못함. 바봉슨.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그날 받았던 질문들을 기억나는대로 메모해놓았고 계속 곱씹어보고 있다. 자바스크립트와 리액트 자체에 대한 공부가 더 필요하다. 단순히 이론 100%스러운 것들(실행컨텍스트가 뭔가요와 같은)이 중요한게 아니다. '이럴땐 어떻게 될까요' 또는 '만약 이런 상황이면 어떻게 동작할까요'와 같은 질문에 확신을 갖고 대답할 수 있어야 했다. 다음에 (어디서든) 주어진 면접 기회에는 조금 더 제대로 된 대답을 내놓을 수 있을 것 같다.&lt;br /&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;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;비교적 여유로운 한 학기였다.&lt;span style=&quot;color: #000000;&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;716&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXV36M/btsjsfcGEiI/zbd6PKRR16KRBH9USoOo50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXV36M/btsjsfcGEiI/zbd6PKRR16KRBH9USoOo50/img.png&quot; data-alt=&quot;상반기 잔디&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXV36M/btsjsfcGEiI/zbd6PKRR16KRBH9USoOo50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXV36M%2FbtsjsfcGEiI%2Fzbd6PKRR16KRBH9USoOo50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;418&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;418&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;프라이빗 외주 레포를 감안하더라도, 작년 한해동안 2000개가 훌쩍 넘는 커밋수에 비교해보면 절반 즈음의 숫자다. 단순히 새로운 프로젝트를 또 시작하고 많은 양의 피쳐를 쳐내는것보다 더 중요한게 있다는 생각을 하게 된다. 폴더 구조 하나, 컴포넌트 설계 하나에 그만큼 고민을 오래하는 것. 어떻게 하면 초기 렌더링 속도를 줄일수 있을까 생각하는것. (혹은 그 이전에 면접이라도 볼 수 있게 알고리즘을 푸는것..)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 위엔 핑계다. 그냥 많이 논거다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sZvEJ/btsjmvN82bP/VMWlKYwKfDjkm1DkkoaFm1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sZvEJ/btsjmvN82bP/VMWlKYwKfDjkm1DkkoaFm1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sZvEJ/btsjmvN82bP/VMWlKYwKfDjkm1DkkoaFm1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/sZvEJ/btsjmvN82bP/VMWlKYwKfDjkm1DkkoaFm1/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;640&quot; height=&quot;360&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;360&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;처음 두눈으로 본 넬은 너무 좋았다. 그 중에 최고는 주룩주룩 내렸던 비와 딱 맞았던 백색왜성. &amp;lsquo;초록비가 내리고&amp;rsquo; 하는 가사와 함께 딱 바뀌는 초록색 조명이 너무나 완벽했다. 다음달에 있을 조휴일의 단독공연과 그 다음달에 있을 펜타포트가 너무나도 기대된다. 돈을 많이 벌어야겠다고 생각하는 요즘. 락을 위해! 세상엔 향유할 예술이 너무나도 많다. 그리고 그걸 같이 즐길수 있는 사람이 있다는건 참 행복한거야.&lt;/p&gt;</description>
      <category>  글을 위한 글</category>
      <category>프론트엔드</category>
      <category>회고</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/115</guid>
      <comments>https://9yujin.tistory.com/115#entry115comment</comments>
      <pubDate>Tue, 13 Jun 2023 15:54:12 +0900</pubDate>
    </item>
    <item>
      <title>[React] 제네릭 조건부 타입 활용하기 - 컴포넌트를 커스텀훅으로 제공</title>
      <link>https://9yujin.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 하다보면 흔히 마주할 수 있는 상황이다. Select, Toggle 또는 DatePicker 등의 인풋 컴포넌트들을 설계할 때, 그 컴포넌트들을 핸들링할 커스텀훅도 같이 작성해 사용할 수 있다. 보통은 useToggle, useSelect 등의 이름으로 쓰고 &lt;code&gt;[value, onChange]&lt;/code&gt; 같은 배열을 return해 사용한다. 외주 개발을 하면서 초기에 이런 컴포넌트 작업을 해야할 일이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import useSelect from '~hooks/useSelect';
import Select from '~components/Select';

const App = () =&amp;gt; {
  const [value, handleSelectValue] = useSelect();

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Select 
          value={value}
        onClick={handleSelectValue} 
        placeholder={'등급 선택'}
      /&amp;gt;
    &amp;lt;/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 매번 컴포넌트 따로 훅 따로 만들고 사용할 때도 컴포넌트 따로 훅 따로 임포트해와, 훅에서 리턴한 값들을 컴포넌트에 prop으로 넘기는 패턴이 일반적이다. 조금더 개선해볼 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 알고 싶은건 input 값이 바뀌었을때 핸들링하는 onChange 함수가 아니라, 지금 input에 들어있는 value가 궁금한거니까 &lt;b&gt;한번 더 추상화를 해볼 수 있겠다&lt;/b&gt; 싶어 고민을 해보았다. 추가로 해당 input이 여러 타입의 value를 가질 수 있다면 &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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컴포넌트도 커스텀훅으로 제공하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Select 컴포넌트를 아래와 같이 사용하도록 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import useSelect from '~hooks/useSelect';

const RANK_OPTIONS: Option[] = [
  {value: 'white', label: 'white'},
  {value: 'silver', label: 'silver'},
  {value: 'gold', label: 'gold'},
  {value: 'dia', label: 'dia'},
];

const App = () =&amp;gt; {
  const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, 'silver');

  return (
    &amp;lt;&amp;gt;
      {renderSelectRank({placeholder: '등급 선택'})}
    &amp;lt;/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useSelect&lt;/code&gt; Hook의 인자로 옵션 배열과 &lt;code&gt;initialValue&lt;/code&gt;를 넘겨준다. 컴포넌트와 직접적으로 관련괸 &lt;code&gt;renderSelectRank&lt;/code&gt;에는 placeholder를 넣어주어 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const useSelectInput = (
  options: Option[],
  initialValue: selectOption = null,
) =&amp;gt; {
  const isMulti = isMultiSelect(initialValue);
  const [value, setValue] = useState&amp;lt;selectOption&amp;gt;(initialValue);

  const renderSelectInput = ({placeholder, width}: SelectInputProps) =&amp;gt; (
    &amp;lt;SelectStyles width={width}&amp;gt;
      &amp;lt;Select
        isMulti={isMulti}
        onChange={selectOption =&amp;gt; setValue(selectOption)}
        defaultValue={initialValue}
        options={options}
        placeholder={placeholder}
      /&amp;gt;
    &amp;lt;/SelectStyles&amp;gt;
  );

  return [value, renderSelectInput] as const;
};

export default useSelectInput;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 표현된 useSelect Hook의 구현이다. 훅이 render함수를 반환하는게 낯설 수도 있지만 (컴포넌트를 적절한 위치에 렌더링하고 선택된 값을 내보낸다는) 딱 필요한 정보만 App에서 볼 수 있게 되었다. render 함수에 인자로 넘겨준 값이 실제 컴포넌트의 props으로 들어간다는 점에서, 비슷하게 객체의 형태로 넘기도록 설계했다는 점도 매력있다.&lt;/p&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;컴포넌트가 여러 타입의 value를 가질 수 있다면?&lt;/b&gt;&lt;/h4&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;스크린샷 2023-06-01 오후 9.50.05.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zvKTb/btsijNiq5IB/X2Xkns2svKGabAdw6hyYWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zvKTb/btsijNiq5IB/X2Xkns2svKGabAdw6hyYWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zvKTb/btsijNiq5IB/X2Xkns2svKGabAdw6hyYWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzvKTb%2FbtsijNiq5IB%2FX2Xkns2svKGabAdw6hyYWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;87&quot; data-filename=&quot;스크린샷 2023-06-01 오후 9.50.05.png&quot; data-origin-width=&quot;1200&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;isMulti를 &lt;code&gt;true&lt;/code&gt;로 주면 위의 사진처럼 값을 여러개 선택할 수 있다. 현재 사용하고 있는 &lt;code&gt;react-select&lt;/code&gt;라이브러리는 value에 Option 또는 Option[] 타입을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type selectOption = SingleValue&amp;lt;Option&amp;gt; | MultiValue&amp;lt;Option&amp;gt;;

const isMultiSelect = (value: selectOption): value is MultiValue&amp;lt;Option&amp;gt; =&amp;gt; {
  return (value as MultiValue&amp;lt;Option&amp;gt;)?.length !== undefined;
};

export interface SelectInputProps {
  placeholder?: string;
  width?: number;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Select 컴포넌트에게 multi인지 single인지 여부를 따로 받지 않고, 인자로 받은 &lt;code&gt;initialValue&lt;/code&gt;의 타입을 보고 결정하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값으로 빈 배열을 넣어주면 MultiSelect 컴포넌트로써 동작한다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-01 오후 11.05.18.png&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgkPm4/btsijgrK7qS/ZuU4dnRGdNWvCKkFIymuQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgkPm4/btsijgrK7qS/ZuU4dnRGdNWvCKkFIymuQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgkPm4/btsijgrK7qS/ZuU4dnRGdNWvCKkFIymuQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgkPm4%2FbtsijgrK7qS%2FZuU4dnRGdNWvCKkFIymuQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;125&quot; data-filename=&quot;스크린샷 2023-06-01 오후 11.05.18.png&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 여기다. useSelect 커스텀훅에서 나온 value(여기선 rank)의 타입이 내가 생각하는대로 나오지 않는다는 것이다. &lt;code&gt;SingleValue&lt;/code&gt;와 &lt;code&gt;MultiValue&lt;/code&gt;의 유니온으로 state의 상태를 지정해주었기 때문에, 커스텀훅에서 반환될때도 그 타입(&lt;code&gt;selectOption&lt;/code&gt;)으로 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;if (isMulti){
    return [value as MultiValue&amp;lt;Option&amp;gt;, renderSelectInput] as const;
} else {
    return [value as SingleValue&amp;lt;Option&amp;gt;, renderSelectInput] as const;
}&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-06-01 오후 11.08.26.png&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AK3VY/btsikT3vGmT/e9mhpkS8WISREJjxuNNpr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AK3VY/btsikT3vGmT/e9mhpkS8WISREJjxuNNpr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AK3VY/btsikT3vGmT/e9mhpkS8WISREJjxuNNpr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAK3VY%2FbtsikT3vGmT%2Fe9mhpkS8WISREJjxuNNpr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;132&quot; data-filename=&quot;스크린샷 2023-06-01 오후 11.08.26.png&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;274&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;br /&gt;어떻게 하면 해결할 수 있을까!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;제네릭 조건부 타입 활용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useSelect 커스텀훅에서는 single과 multi select를 모두 처리하기 때문에 그에 맞게 반환 타입을 수정해야 한다. 하지만 &lt;code&gt;useState&lt;/code&gt;의 타입을 통해 타입 추론을 완벽하게 하려면 위와 같은 어려움이 있었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금의 삽질 끝에, 타입스크립트의 제네릭을 통해 커스텀훅 함수에 전달되는 &lt;code&gt;initialValue&lt;/code&gt;의 타입에 따라 반환 타입이 달라지도록 처리할 수 있었다.&lt;/p&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;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입은 가능한 값들의 집합이다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부 타입에 대한 이야기에 앞서 이 개념을 한번 더 정리하고 가면 좋다. 이펙티브 타입스크립트 스터디를 &lt;a href=&quot;https://9yujin.tistory.com/108&quot;&gt;처음 시작할때&lt;/a&gt; 가장 헷갈렸던 부분 중 하나이다. &lt;code&gt;T extends U&lt;/code&gt; 이면 &lt;code&gt;T&lt;/code&gt;는 &lt;code&gt;U&lt;/code&gt;의 부분집합이라고 말할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;T&lt;/code&gt;가 &lt;code&gt;U&lt;/code&gt;를 확장했다는 것은, &lt;code&gt;U&lt;/code&gt;에 있는 모든 속성과 메소드들이 &lt;code&gt;T&lt;/code&gt;에도 있다는 것을 보장한다. 다르게 말하면 '&lt;b&gt;T는 U에 할당 가능하다&lt;/b&gt;' 라는 의미를 가지고 있다. (&lt;code&gt;U&lt;/code&gt;로 할 수 있는 모든 것들을 &lt;code&gt;T&lt;/code&gt;로도 할 수 있다 &amp;rarr; &lt;code&gt;T&lt;/code&gt;는 &lt;code&gt;U&lt;/code&gt;에 할당 가능하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;T extends U ? X : Y&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;code&gt;T&lt;/code&gt;에 제약을 걸 수 있다. 그리고 그 개념을 이용해 조건부 타입을 나타낼 수 있다. 삼항연산자와 비슷하게 생겼다. &lt;code&gt;T&lt;/code&gt;가 &lt;code&gt;U&lt;/code&gt;를 확장하면(True면) 타입 &lt;code&gt;X&lt;/code&gt;를, False면 타입 &lt;code&gt;Y&lt;/code&gt;를 반환한다.&lt;/p&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;그렇다면 지금 useSelect 컴포넌트에선 이렇게 구현을 해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type SingleSelect = SingleValue&amp;lt;Option&amp;gt;;
type MultiSelect = MultiValue&amp;lt;Option&amp;gt;;

const useSelectInput = &amp;lt;T extends SingleSelect | MultiSelect&amp;gt;(
  options: Option[],
  initialValue: T,
) =&amp;gt; {
  const isMulti = isMultiSelect(initialValue);
  const [value, setValue] = useState&amp;lt;T&amp;gt;(initialValue);
  //...

  return [value, renderSelectInput] as SelectReturnType&amp;lt;T, typeof renderSelectInput&amp;gt;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;T&lt;/code&gt;는 &lt;code&gt;SingleSelect&lt;/code&gt; 또는 &lt;code&gt;MultiSelect&lt;/code&gt;에 할당 가능한 객체들의 집합이다. 인자로 받은 &lt;code&gt;initialValue&lt;/code&gt;와 state의 타입은 당연히 &lt;code&gt;T&lt;/code&gt;이겠다. 그리고 커스텀훅은 &lt;code&gt;SelectReturnType&amp;lt;T&amp;gt;&lt;/code&gt;라는 타입을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type SelectReturnType&amp;lt;T, RenderType&amp;gt; = T extends MultiSelect
  ? [MultiSelect, RenderType]
  : [SingleSelect, RenderType];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 조건부 타입이 등장한다. &lt;code&gt;T&lt;/code&gt;가 &lt;code&gt;MultiSelect&lt;/code&gt;에 할당 가능하면, &lt;code&gt;MultiSelect&lt;/code&gt;에 맞는 리턴 타입을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건부 타입은 컴파일 타임에 타입을 결정하는데 사용된다. 하지만 &lt;code&gt;isMultiSelect&lt;/code&gt; 같은 타입 가드는 런타임에서 실제 값에 따라 동작한다. 그렇기 때문에 아까 위에서 타입가드에서 나온 결과를 조건으로 타입을 단언해 반환했을 땐 제대로 추론이 되지 않는것이라고 결론을 내렸다.&lt;/p&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;+ initialValue를 생략하고 싶다면&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, []); //Multi
const [rank, renderSelectRank] = useSelect(RANK_OPTIONS, null); //Single&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;initialValue&lt;/code&gt;의 타입을 보고 select state의 타입을 결정하고 있다. &lt;code&gt;SingleSelect&lt;/code&gt;이고 초기값이 없는 경우에도 위와 같이 &lt;code&gt;null&lt;/code&gt;을 필수적으로 주어야 한다. 그럴 때 &lt;code&gt;initialValue&lt;/code&gt;(두번째 인자)를 생략할 수 있게 하기 위해 &lt;code&gt;initialValue&lt;/code&gt;에 디폴트 값 &lt;code&gt;null&lt;/code&gt;을 넣어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const useSelectInput = &amp;lt;T extends SingleSelect | MultiSelect = SingleSelect&amp;gt;(
  options: Option[],
  initialValue: T = null as T,
) =&amp;gt; {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게. &lt;code&gt;initialValue&lt;/code&gt;가 &lt;code&gt;null&lt;/code&gt;일때 &lt;code&gt;SingleSelect&lt;/code&gt;가 나올 수 있도록 T의 디폴트도 지정해주었다.&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;스크린샷 2023-06-02 오전 2.44.52.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oO62P/btsihenKyg3/70z4p7eIwjwIKRkycDfd8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oO62P/btsihenKyg3/70z4p7eIwjwIKRkycDfd8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oO62P/btsihenKyg3/70z4p7eIwjwIKRkycDfd8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoO62P%2FbtsihenKyg3%2F70z4p7eIwjwIKRkycDfd8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;86&quot; data-filename=&quot;스크린샷 2023-06-02 오전 2.44.52.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 내가 생각하는대로 타입 추론이 되는것을 확인해 볼 수 있다.&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <category>react</category>
      <category>TypeScript</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/114</guid>
      <comments>https://9yujin.tistory.com/114#entry114comment</comments>
      <pubDate>Fri, 2 Jun 2023 02:51:30 +0900</pubDate>
    </item>
    <item>
      <title>[React] 컴포넌트를 제네릭 함수로 쓰기 - 테이블 컴포넌트 추상화</title>
      <link>https://9yujin.tistory.com/113</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;귀찮은 일은 꽤나 자주 일어난다. 외주 작업 중 테이블 컴포넌트를 구현해야하는 일이 있었고, 기존 코드는 Core UI라는 라이브러리를 사용하고 있었다. 라이브러리가 4.0버전으로 업그레이드 되면서 PRO버전이 새로 생겼고, 그를 위한 급나누기에 테이블 컴포넌트가 걸려버렸다. 기존에 사용하던 &lt;code&gt;onRowClick&lt;/code&gt;, &lt;code&gt;scopedSlots&lt;/code&gt;등의 기능들이 사라졌고 직접 구현해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;Table
  paginationState={[current, setCurrent]}
  column={column}
  data={data}
  onRowClick={data =&amp;gt; {
    navigate(`/notices/${data.id}`);
  }}
  customCellItem={{
    info: data =&amp;gt; &amp;lt;button onClick={() =&amp;gt; alert(data.index)}&amp;gt;커스텀cell&amp;lt;/button&amp;gt;,
    date: data =&amp;gt; &amp;lt;div style={{color: 'red'}}&amp;gt;{data.date}&amp;lt;/div&amp;gt;,
  }}
/&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추상화된 &lt;code&gt;Table&lt;/code&gt;은 상위 컴포넌트는 위와 같이 사용할 수 있다. 컬럼과 data 배열을 넘겨주면 컴포넌트 내부적으로 이러쿵저러쿵해서 테이블을 멋있게 그려주는 동작이 추상화되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 내부는 간단히 이렇게 되어 있다. CoreUI에서 제공해주는 컴포넌트들에는 &lt;code&gt;C&lt;/code&gt;가 붙어있음.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const {content, size, total} = data;
const itemList = customCellItem
  ? getCustomRow(content, customCellItem)
  : content;

return (
  &amp;lt;&amp;gt;
    &amp;lt;CTable hover striped responsive&amp;gt;
      &amp;lt;CTableHead&amp;gt;
        &amp;lt;CTableRow&amp;gt;
          {column.map(c =&amp;gt; (
            &amp;lt;CTableHeaderCell scope='col' key={c.key} style={c._style}&amp;gt;
              {c.label}
            &amp;lt;/CTableHeaderCell&amp;gt;
          ))}
        &amp;lt;/CTableRow&amp;gt;
      &amp;lt;/CTableHead&amp;gt;
      &amp;lt;CTableBody&amp;gt;
        {itemList.map((item, idx) =&amp;gt; (
          &amp;lt;CTableRow
            key={`${item.id}-${idx}`}
            onDoubleClick={() =&amp;gt; onRowClick?.(item)}
          &amp;gt;
            {Object.keys(filterRowData(item, column)).map(cell =&amp;gt; (
              &amp;lt;CTableDataCell key={cell}&amp;gt;{item[cell]}&amp;lt;/CTableDataCell&amp;gt;
            ))}
          &amp;lt;/CTableRow&amp;gt;
        ))}
      &amp;lt;/CTableBody&amp;gt;
    &amp;lt;/CTable&amp;gt;
    &amp;lt;Pagination
      current={paginationState[0]}
      setCurrentPage={paginationState[1]}
      totalPage={Math.ceil(total / size)}
    /&amp;gt;
  &amp;lt;/&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;props로 받은 column과 item을 테이블 row로 렌더링시킨다. &lt;code&gt;getCustomRow&lt;/code&gt; 함수를 통해 각 cell마다 렌더링할 컴포넌트를 직접 지정해주고, &lt;code&gt;filterRowData&lt;/code&gt; 함수를 통해 서버에서 response로 받은 data 객체를 column 객체와 대조해 보여주고 싶은 키-값만 필터링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;tsx에서 함수에 제네릭 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 각 행을 클릭했을 때 실행되는 콜백 함수에, 인자로 각 행의 data가 전달되도록 했다.&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-filename=&quot;스크린샷 2023-05-25 오후 4.49.25.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKG66O/btshpYkKYsY/BqDUDVLYGkBNnros3qPR10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKG66O/btshpYkKYsY/BqDUDVLYGkBNnros3qPR10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKG66O/btshpYkKYsY/BqDUDVLYGkBNnros3qPR10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKG66O%2FbtshpYkKYsY%2FBqDUDVLYGkBNnros3qPR10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;193&quot; data-filename=&quot;스크린샷 2023-05-25 오후 4.49.25.png&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 외부에서 data가 어떤 타입인지 모르기 때문에, 멍청한 개발자가 충분히 실수할 수 있는 상황이 만들어진다. &lt;b&gt;타입스크립트와 제네릭 타입을 통해 Table 컴포넌트로 넘긴 타입의 콜백에서 추론되어 적절한 타입을 얻을 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const GenericReturnFunc = &amp;lt;T&amp;gt;(arg: T): T =&amp;gt; {
  return arg;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 ts 파일에서는 위와 같이 제네릭 함수를 쓸 수 있다. 하지만 tsx에서 위와 같이 작성하면 빨간줄이 뜨는 걸 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const GenericReturnFunc = &amp;lt;T extends {}&amp;gt;(arg: T): T =&amp;gt; {
  return arg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;extend&lt;/code&gt; 키워드를 사용하거나,&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const GenericReturnFunc = &amp;lt;T,&amp;gt;(arg: T) : T =&amp;gt; {
  return arg;
}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컴포넌트에 제네릭 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const Table = &amp;lt;T extends Item&amp;gt;({
  column,
  data,
  customCellItem,
  paginationState,
  onRowClick,
}: TableProps&amp;lt;T&amp;gt;) =&amp;gt; {
  const {content, size, total} = data;
  const itemList = customCellItem
    ? getCustomRow(content, customCellItem)
    : content;

  return (
    &amp;lt;&amp;gt;
    // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 컴포넌트 함수에 제네릭 타입을 작성해주었다.&lt;br /&gt;&lt;code&gt;,&lt;/code&gt; 가 아닌 &lt;code&gt;extends&lt;/code&gt;를 통해 Item 이라는 타입을 확장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export type Item = {
    [key: string]: number | string | any;
    _props?: CTableRowProps;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Item은 테이블 컴포넌트 라이브러리에 내장된 타입이다. &lt;br /&gt;string을 key로 갖는 (굉장히 느슨한) 객체의 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;{
  itemList.map((item, idx) =&amp;gt; (
    &amp;lt;CTableRow
      key={`${item.id}-${idx}`}
      onDoubleClick={() =&amp;gt; onRowClick?.(item)}
    &amp;gt;
      {Object.keys(filterRowData(item, column)).map(cell =&amp;gt; (
        &amp;lt;CTableDataCell key={cell}&amp;gt;{item[cell]}&amp;lt;/CTableDataCell&amp;gt;
      ))}
    &amp;lt;/CTableRow&amp;gt;
  ));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 키[값]을 사용하고 있기 때문에, Item 타입을 확장해 사용하는 것. 그렇지 않다면 item에서 id 속성을 가져오는 과정에서 &lt;code&gt;'T' 형식에 'id' 속성이 없습니다.ts(2339)&lt;/code&gt; 오류를 마주치게 된다.&lt;/p&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-25 오후 7.07.28.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OtMIU/btshp1WsDp1/dDFQZXqcXpnSl3Dawkdipk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OtMIU/btshp1WsDp1/dDFQZXqcXpnSl3Dawkdipk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OtMIU/btshp1WsDp1/dDFQZXqcXpnSl3Dawkdipk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOtMIU%2Fbtshp1WsDp1%2FdDFQZXqcXpnSl3Dawkdipk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;360&quot; data-filename=&quot;스크린샷 2023-05-25 오후 7.07.28.png&quot; data-origin-width=&quot;1746&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export interface CustomCellItem&amp;lt;T&amp;gt; {
  [key: string]: (data: T) =&amp;gt; ReactNode;
}

export interface TableProps&amp;lt;T&amp;gt; {
  column: Column[];
  data: TableResponse&amp;lt;T&amp;gt;;
  customCellItem?: CustomCellItem&amp;lt;T&amp;gt;;
  paginationState: [number, Dispatch&amp;lt;SetStateAction&amp;lt;number&amp;gt;&amp;gt;];
  onRowClick?: (data: T) =&amp;gt; void;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컴포넌트의 인터페이스에 제네릭 타입을 넘겨줄 수 있다. 단순히 키-값 형태의 객체라는 의미를 담은 Item 타입이 아닌 정확한 data의 타입을 넘겨 사용할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;멋진 테이블 컴포넌트 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;/**
 * 패칭 데이터 중 table column에 있는 값만 반환
 * @param data 원본 데이터 (패칭한 데이터)
 * @param column table에서 보여줄 column key
 */
const filterRowData = &amp;lt;T extends Item&amp;gt;(
  data: T,
  column: Column[],
): Partial&amp;lt;T&amp;gt; =&amp;gt; {
  const map = new Map();
  column.forEach(c =&amp;gt; {
    const key = c.key;
    map.set(key, data[key]);
  });
  return Object.fromEntries(map);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data의 여러 속성 중 column에 있는 속성만 남긴다. &lt;code&gt;Partial&amp;lt;T&amp;gt;&lt;/code&gt; 유틸리티 타입을 통해 반환값을 명시해주었다. 명시하지 않으면 타입이 추론되지 않아 any로 나타난다.&lt;/p&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 class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;export interface CustomCellItem&amp;lt;T&amp;gt; {
  [key: string]: (data: T) =&amp;gt; ReactNode;
}

/**
 * 버튼, input 등의 렌더링할 컴포넌트를 item으로 반환
 * @param data 렌더링할 data
 * @param customCellItem cell에 렌더링할 컴포넌트
 */
const getCustomRow = &amp;lt;T,&amp;gt;(data: T[], customCellItem: CustomCellItem&amp;lt;T&amp;gt;) =&amp;gt; {
  let customRow = data;
  Object.keys(customCellItem).forEach(v =&amp;gt; {
    customRow = customRow.map(row =&amp;gt; {
      return {...row, [v]: customCellItem[v](row)};
    });
  });
  return customRow;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;customCellItem을 위한 getCustomRow 함수이다. 인자로 받은 객체에 &lt;/span&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-05-25 오후 7.39.33.png&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qo6ZD/btshqqnYGyp/p1X6jFlKpkJTx1EGnpeRx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qo6ZD/btshqqnYGyp/p1X6jFlKpkJTx1EGnpeRx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qo6ZD/btshqqnYGyp/p1X6jFlKpkJTx1EGnpeRx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqo6ZD%2FbtshqqnYGyp%2Fp1X6jFlKpkJTx1EGnpeRx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1334&quot; height=&quot;336&quot; data-filename=&quot;스크린샷 2023-05-25 오후 7.39.33.png&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;336&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;아주 멋지게 타입이 추론되는 것을 볼 수 있다.&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <category>react</category>
      <category>TypeScript</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/113</guid>
      <comments>https://9yujin.tistory.com/113#entry113comment</comments>
      <pubDate>Thu, 25 May 2023 19:47:16 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 타입스크립트] 타입 추론과 타입 넓히기</title>
      <link>https://9yujin.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트가 결국 타입을 위한 언어이기 때문에, 변수를 선언할 때마다 타입을 명시해야 한다고 생각할 수 있다. 그러나 타입스크립트의 많은 타입 구문은 사실 불필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_스크린샷 2023-05-02 오후 11.13.41.png&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brkjig/btsd0R961Vm/5HAT4ahbjF1XncakPKCFPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brkjig/btsd0R961Vm/5HAT4ahbjF1XncakPKCFPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brkjig/btsd0R961Vm/5HAT4ahbjF1XncakPKCFPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrkjig%2Fbtsd0R961Vm%2F5HAT4ahbjF1XncakPKCFPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1626&quot; height=&quot;498&quot; data-filename=&quot;edited_edited_스크린샷 2023-05-02 오후 11.13.41.png&quot; data-origin-width=&quot;1626&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;이렇게 아무 생각없이 명시했던 타입들이 있다. 하수의 코드. 타입 추론이 된다면 명시적 타입 구문은 필요하지 않다. 오히려 코드가 길어지고 가독성이 안좋아질 수 있기 때문에.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 타입이 추론될 수 있음에도 타입을 명시하는 것이 더 좋은 몇가지 상황들이 있다. 더 정확한 위치에 오류를 표시하기 위함이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 리터럴을 정의할 때&lt;/li&gt;
&lt;li&gt;함수의 반환값을 명시할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입 넓히기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let 으로 선언하는 경우. 값이 재할당될 가능성이 있다.&lt;br /&gt;따라서 처음 초기화한 값을 가지고 할당 가능한 값들의 집합을 유추해야 함.&lt;br /&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적 타입 구문&lt;/li&gt;
&lt;li&gt;추가적인 문맥 제공 (함수의 매개변수로 전달. 매개변수의 타입이 지정되어 있다면 그걸로 되니까) -&amp;gt;26&lt;/li&gt;
&lt;li&gt;const 단언문 사용 (as const) -&amp;gt; 최대한 좁은 타입으로 추론함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타이핑을할때 리터럴 타입으로 보는거야&lt;/li&gt;
&lt;li&gt;객체의 경우에는 readonly 변수로 추론&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;type Language = 'Javascript' | 'Typescript' | 'Python';
function setLanguage(language: Language){/* ... */}

let language = 'Javascript';
setLanguage(language);     // 'string' 형식의 인수는 
                           // 'Langugae' 형식의 매개변수에 할당될 수 없습니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;let&lt;/code&gt; 변수로 분리하면, 타입스크립트는 할당 시점에 타입을 추론한다. 타입 넓히기가 적용되어서 &lt;code&gt;string&lt;/code&gt; 타입으로 추론하게 된다. &lt;code&gt;string&lt;/code&gt;타입은 &lt;code&gt;Language&lt;/code&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;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;let language: Language = 'JavaScript'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 선언에서 &lt;code&gt;language&lt;/code&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;setLanguage('Javascript')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인라인 형태에서 타입스크립트는 함수 선언을 통해 매개변수가 &lt;code&gt;Language&lt;/code&gt; 타입이어야 한다는 것을 알고 있다. 따라서 'Javascript' 문자열 리터럴은 할당 가능하다.&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;const 변수로 선언&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;const language = 'Javascript&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;타입 좁히기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null 걸러주기&lt;/li&gt;
&lt;li&gt;타입 가드&lt;/li&gt;
&lt;li&gt;타입에 명시적 태그 사용&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  짧은호흡/JS&amp;middot;TS</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/112</guid>
      <comments>https://9yujin.tistory.com/112#entry112comment</comments>
      <pubDate>Fri, 5 May 2023 16:39:43 +0900</pubDate>
    </item>
    <item>
      <title>[데브톡] 개발자로성장하기 (23.04.06)</title>
      <link>https://9yujin.tistory.com/111</link>
      <description>&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/watch?v=5qtApD2osyk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/DW9vK/hySdu2hE82/pl0okPcU5HrKgKcUksA65k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/5qtApD2osyk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;개발자로성장하기&amp;nbsp;(23.04.06)&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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; &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;p data-ke-size=&quot;size16&quot;&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;lsquo;동기부여 강사&amp;rsquo;같은 이야기가 되지 않도록 주의를 많이 기울였어요. '여러분 할수있습니다!' 같은 개쓸데없는 말을 듣고 싶어서 오신거 아니잖아요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_스크린샷 2023-04-07 오전 10.18.36.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;673&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmxOic/btr8u9hPoqs/m9wwsWDEO8BqKm5Q4EkXg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmxOic/btr8u9hPoqs/m9wwsWDEO8BqKm5Q4EkXg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmxOic/btr8u9hPoqs/m9wwsWDEO8BqKm5Q4EkXg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmxOic%2Fbtr8u9hPoqs%2Fm9wwsWDEO8BqKm5Q4EkXg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;337&quot; data-filename=&quot;edited_edited_스크린샷 2023-04-07 오전 10.18.36.png&quot; data-origin-width=&quot;1197&quot; data-origin-height=&quot;673&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;p data-ke-size=&quot;size16&quot;&gt;제 경우에는 제작년 10월쯤에 전역해서 개발 공부를 처음 시작했습니다. 1년 반정도 됐죠.&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;고스락 친구들끼리 모여서 시작한 프로젝트를 바탕으로 세오스 지원서를 썼고, 합격하게 되었습니다. 그 동아리에서 활동하면서 경험한것들, 배운것들을 바탕으로 고스락에서 두번째 프로젝트를 시작했어요. 또 그것들을 바탕으로 넥스터즈라는 동아리에 합격할 수 있었죠. 가장 최근에는 두둥이라는 공연 예매 플랫폼을 만들어서 실제 유저를 받고 있습니다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.19.29.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H4wQc/btr8wP4dtFc/o4Ld5EvwxQOCU792xz0EiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H4wQc/btr8wP4dtFc/o4Ld5EvwxQOCU792xz0EiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H4wQc/btr8wP4dtFc/o4Ld5EvwxQOCU792xz0EiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH4wQc%2Fbtr8wP4dtFc%2Fo4Ld5EvwxQOCU792xz0EiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;스크린샷 2023-04-07 오전 10.19.29.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&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;&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1 . 프로젝트를 위한 프로젝트?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 에타에서 많이 본 글들입니다. &quot;포폴용 프로젝트 하나 하실 분 구해요! 인스타그램이나 간단한 투두리스트 클론코딩 생각하고 있습니다!&quot; 그냥 뭐라도 프로젝트를 해야하니까 주제를 찾고.. 누구나 하는 게시판 만들고.. 인스타그램 클론하고.. 이런거보다 더 좋은 방법이 있을것같아요. 깃허브에 한글로 검색해봐도 몇백개가 뜨는걸요. 물론 저런걸 하지 말라는게 아닙니다. 처음엔 정말 좋은 방법이에요. 저도 맨처음 공부할때 당연히 sns 만들어봤구요, 투두리스트 당연히 해봤죠. 근데 재미없잖아요. 넥터 같이했던 현직자 형이, 제발 플젝좀 그만하래요. 그시간에 그냥 cs하라고.(뼈맞음)&lt;/p&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;lsquo;프로젝트를 위한 프로젝트&amp;rsquo;가 되겠죠.&lt;span&gt;&amp;nbsp;&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;edited_스크린샷 2023-04-07 오전 10.27.38.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBZqlY/btr8A3AHLcC/5lSDm9IWPXuFIKCNIzJftk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBZqlY/btr8A3AHLcC/5lSDm9IWPXuFIKCNIzJftk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBZqlY/btr8A3AHLcC/5lSDm9IWPXuFIKCNIzJftk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBZqlY%2Fbtr8A3AHLcC%2F5lSDm9IWPXuFIKCNIzJftk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;431&quot; data-filename=&quot;edited_스크린샷 2023-04-07 오전 10.27.38.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;786&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 군대가기전, 19년 9월에 고스락의 20번째 정기공연을 맞이하게됩니다. 선배들의 빠방한 지원으로 상상마당에서 몇백만원짜리 공연을 기획했습니다. 관객들도 갑자기 많이오구요. 그동안은 동기들에게 직접 종이티켓을 팔았는데, 회사를 다니시는 선배들에겐 그럴 수 없잖아요. 그래서 카카오톡 채널의 &amp;lsquo;쿠폰&amp;rsquo;을 이용한 티켓 판매를 구상하고 진행하게 됐습니다.&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;edited_스크린샷 2023-04-07 오전 10.27.51.png&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQkpj/btr8FhrUjlg/Chl8faAONBlfHqGHnZDAwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQkpj/btr8FhrUjlg/Chl8faAONBlfHqGHnZDAwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQkpj/btr8FhrUjlg/Chl8faAONBlfHqGHnZDAwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQkpj%2Fbtr8FhrUjlg%2FChl8faAONBlfHqGHnZDAwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;432&quot; data-filename=&quot;edited_스크린샷 2023-04-07 오전 10.27.51.png&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;866&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-filename=&quot;스크린샷 2023-04-07 오전 10.30.11.png&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rnBcy/btr8xPQsZAj/6G6UmVdFKAnWkJ6vt0HkN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rnBcy/btr8xPQsZAj/6G6UmVdFKAnWkJ6vt0HkN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rnBcy/btr8xPQsZAj/6G6UmVdFKAnWkJ6vt0HkN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrnBcy%2Fbtr8xPQsZAj%2F6G6UmVdFKAnWkJ6vt0HkN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.30.11.png&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 군대를 갔다왔고, 고스락 친구들과 고스락만을 위한 티켓 예매 서비스를 기획했어요. 사진은 피그마에서 직접 디자인한 초기 디자인과 플로우, 기능 명세서입니다. 처음으로 목표를 가지고 무언가를 만드는것이다 보니 정말 몰입해서 일했던것 같아요.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;어쨌든, 이렇게 저희 첫번째 프로젝트를 시작했습니다.&lt;span&gt;&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 공부를 위한 공부?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주제를 정했다고 바로 프로젝트를 할 수 있나요? 그땐 저희 전부다 아무런 경험이 없었거든요. 미리 이러이런거 공부해오세요!! 하고 단톡방에 공고를 올렸어요. 3개월 뒤에 모였습니다. 저는 뭐 다른가요. 저도 감자였어요.&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.32.10.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZPVWv/btr8A4zDhvk/KbnCpcggNC9jsHG81jxie0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZPVWv/btr8A4zDhvk/KbnCpcggNC9jsHG81jxie0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZPVWv/btr8A4zDhvk/KbnCpcggNC9jsHG81jxie0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZPVWv%2Fbtr8A4zDhvk%2FKbnCpcggNC9jsHG81jxie0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1822&quot; height=&quot;1026&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.32.10.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 저는 뭐 흔히 듣는 노마드코더 강의도 들어보고. &amp;lsquo;리액트를 다루는 기술&amp;rsquo;이라는 두꺼운책도 완독했어요. 나름 실력에 자신감있는 상태로 프로젝트를 시작했습니다. 그런데 막상 실제로 프로젝트에 들어가보니, 이전에 공부했던것들로는 할 수 있는게 없더라구요. 매일매일 새로운 문제에 마주쳤습니다. 그렇게 되니 플젝을 시작하기 몇달동안 책보고 강의 듣고 한것보다, 겨우 한달동안 플젝하면서 직접 부딪혀본게 훨씬 더 실력이 많이 늘은거에요.&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.32.37.png&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwUkZN/btr8vPwKVon/zy5VXL5mXxseFxB6vhmPt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwUkZN/btr8vPwKVon/zy5VXL5mXxseFxB6vhmPt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwUkZN/btr8vPwKVon/zy5VXL5mXxseFxB6vhmPt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwUkZN%2Fbtr8vPwKVon%2Fzy5VXL5mXxseFxB6vhmPt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;341&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.32.37.png&quot; data-origin-width=&quot;1558&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GDSC에서 웹기초 스터디 들으시는분 여기 많이 계신가요?? 저번주에 깃과 깃허브에 대해서 배우신걸로 압니다. 여러명이 협업을 할때 브랜치를 파고, 각자 작업을 하고, 풀리퀘를 올리고, 병합을 합니다. 저흰 심지어 이런것도 몰랐어요. 웹 기초 스터디 2주차에 배우는 것들을요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.34.29.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciJyJH/btr8vwYrBNL/RH4ETHjMZQkuBg1MlQfRZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciJyJH/btr8vwYrBNL/RH4ETHjMZQkuBg1MlQfRZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciJyJH/btr8vwYrBNL/RH4ETHjMZQkuBg1MlQfRZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciJyJH%2Fbtr8vwYrBNL%2FRH4ETHjMZQkuBg1MlQfRZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1822&quot; height=&quot;1026&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.34.29.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 짠 코드 나만 보면 그게 잘짠건지 못짠건지는 어떻게 아나요?? 그냥 돌아가면 장땡인가? 그래서 팀원들끼리 이렇게 코드리뷰를 해주면서 피드백을 주는 경험을 해봤어요.&lt;span&gt;&amp;nbsp;&lt;/span&gt;넥스터즈에서 현업자들과 개발할땐 거의 멘토링 수준으로 받았어요. 피알을 올리면 코멘트만 거의 15개였어요. 정말 감사하죠. 어디가서 내 코드 피드백 받으려면 돈주고도 못해요.&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;스크린샷 2023-04-07 오전 10.34.55.png&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnE2JH/btr8wOYzcPL/MKDjOkGwXnZ1aCD7jDIyW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnE2JH/btr8wOYzcPL/MKDjOkGwXnZ1aCD7jDIyW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnE2JH/btr8wOYzcPL/MKDjOkGwXnZ1aCD7jDIyW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnE2JH%2Fbtr8wOYzcPL%2FMKDjOkGwXnZ1aCD7jDIyW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;342&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.34.55.png&quot; data-origin-width=&quot;1460&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.36.19.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwaEjd/btr8tEpaOvA/SlMxYyt5FsbcBCjC4Ankh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwaEjd/btr8tEpaOvA/SlMxYyt5FsbcBCjC4Ankh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwaEjd/btr8tEpaOvA/SlMxYyt5FsbcBCjC4Ankh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwaEjd%2Fbtr8tEpaOvA%2FSlMxYyt5FsbcBCjC4Ankh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.36.19.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&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;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;두둥에서 등록한 공연은 구글에 검색하면 나와야 해요. 그 외 다양한 이유로 서버사이드렌더링을 이용하기로 했고, nextjs로 마이그레이션했습니다. 리액트에서 하던 방식이랑은 많이 다르더군요. 서버에서&lt;b&gt; 쿠키를 다루는 법도 몰랐고, 로그인 정보&lt;/b&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;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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 글을 위한 글?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.42.40.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaVVg/btr8zjX2PCi/An1FkDTF6U9u9WakpT8Iu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaVVg/btr8zjX2PCi/An1FkDTF6U9u9WakpT8Iu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaVVg/btr8zjX2PCi/An1FkDTF6U9u9WakpT8Iu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxaVVg%2Fbtr8zjX2PCi%2FAn1FkDTF6U9u9WakpT8Iu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;372&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.42.40.png&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;1026&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;p data-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;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.43.32.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UbWwD/btr8xUYAObB/ZkNXxbEueDsKR7LcLWFYR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UbWwD/btr8xUYAObB/ZkNXxbEueDsKR7LcLWFYR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UbWwD/btr8xUYAObB/ZkNXxbEueDsKR7LcLWFYR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbWwD%2Fbtr8xUYAObB%2FZkNXxbEueDsKR7LcLWFYR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;423&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.43.32.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아시는분은 아시겠지만, GDSC 안에서 개발자 글쓰기 소모임을 하고 있습니다. 스무명 정도 되는 사람들이 격주로 자기 글을 올리고, 다른 사람들이 쓴 글을 읽어요. 끝까지 잘 돌아갈지는 모르지만 지금 당장은 다들 열심히 글을 쓰고 있습니다. 그냥 뭐 그렇다구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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;. 그렇게 1년반을 바쁘게 살다보니까 어느새 정말 취준을 할때가 왔더라구요&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;edited_스크린샷 2023-04-07 오전 10.47.25.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;1026&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Ns9n/btr8uRPkB2s/zNW3dyuXRDurJIkPQkvsTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Ns9n/btr8uRPkB2s/zNW3dyuXRDurJIkPQkvsTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Ns9n/btr8uRPkB2s/zNW3dyuXRDurJIkPQkvsTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Ns9n%2Fbtr8uRPkB2s%2FzNW3dyuXRDurJIkPQkvsTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;491&quot; data-filename=&quot;edited_스크린샷 2023-04-07 오전 10.47.25.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;1026&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;span&gt;&amp;nbsp;&lt;/span&gt;처음부터 같이하던 18,19학번 친구들은 지금 현직자와 협업을 할 수 있는 동아리에서 경험을 쌓고있구요, 수익형 서비스를 런칭하는 동아리인 CMC를 했던 친구들은 외주하면서 돈벌고 있어요.&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;두번째, 세번째 프로젝트부터 새로 공부하며 참여했던 20,21학번 친구들은 우리와 함께 일한 경험으로 동아리에 들어갔고, 지금은 그 동아리에서 운영진을 하고 있습니다. 이제는 그 친구들이 고스락에서 후배들과 함께 하며 도움을 주는 선순환이 계속 이어지겠죠.&lt;/p&gt;
&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.48.12.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cT9816/btr8u3hWxJ2/RRr6XF6r32K3Eibis20cl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cT9816/btr8u3hWxJ2/RRr6XF6r32K3Eibis20cl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cT9816/btr8u3hWxJ2/RRr6XF6r32K3Eibis20cl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcT9816%2Fbtr8u3hWxJ2%2FRRr6XF6r32K3Eibis20cl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;109&quot; data-filename=&quot;스크린샷 2023-04-07 오전 10.48.12.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 말씀드리고 싶은건 , 남들이 1티어라고 말하는 동아리들, 넥터, 디프만.. 그런거에 그렇게까지 환상을 가질 필요는 없다는 것입니다.&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;다만 열심히 무언가를 하기 좋은 환경을 만들어주는것이지, 그 동아리들이 나에게 무언갈 주는건 아니거든요. 내가 열심히해야 그만큼 얻어가는게 있는거죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지디에스시도 마찬가지입니다. 이 커뮤니티에서 활동한다고 해서 내 머릿속에 뭐가 들어오고, 포폴이 생기는게 아닙니다. 들어와서 무언가를 해야죠. 그런 의미에서 이렇게 여러분들처럼 소중한 저녁시간을 데브톡을 들으러 오고, 매주 스터디를 수강하고 과제를 내고. 소모임이나 스터디에서 같이 개발을 공부하는 동료들을 만나고. 정말 좋다고 생각합니다. 실제로 학교 분위기가 많이 달라졌다고 느껴졌어요!! 라떼(ㅋ)는 이런게 하나도 없었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;감삼둥&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: -39px; top: -3.5px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>  긴호흡/GDSC</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/111</guid>
      <comments>https://9yujin.tistory.com/111#entry111comment</comments>
      <pubDate>Mon, 10 Apr 2023 11:44:39 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 타입스크립트] 객체 리터럴의 타입 체크 : 타입 호환성과 신선도</title>
      <link>https://9yujin.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;책을 이용한 스터디를 하면서 평소에 쓰지 않는 (경험이 아닌 책 내용을 정리하는) 글을 쓰면서 많은 고민을 하게 된다. 어떻게 해야 완벽히 이해하고, 정리하고, 기록할 수 있을까. 책의 순서 그대로 쓰지 않고 내가 이해한 흐름대로 재구성해서 글을 쓰도록 노력해보려고 한다. 효율적인 방법인지는 모르겠지만.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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 class=&quot;properties&quot;&gt;&lt;code&gt;interface Room {
    numDoors: number;
    ceilingHeightFt: number;
}

const obj = {
  numDoors: 1,
  ceilingHeightFt: 10;
  elephant: 'present',
}
const r: Room = obj;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추론된 &lt;code&gt;obj&lt;/code&gt;의 타입은 &lt;code&gt;Room&lt;/code&gt;타입의 부분 집합을 포함하므로, Room에 할당 가능하고 타입 체커도 통한다. 하지만 이러한 구조적 타입 처리는 무언가가 실제 다루는 것보다 더 많은 데이터를 받아들인다는 오해를 불러일으킬 수 있다는 약점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;interface Options {
    title: string;
    darkMode?: boolean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단적인 예로 위의 코드가 있다. &lt;code&gt;string&lt;/code&gt; 타입의 title 속성만 제대로 있다면 모든 객체는 &lt;code&gt;Options&lt;/code&gt; 타입에 속할 수 있다. 타입의 범위가 매우 넓어지는 것.&lt;/p&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;신선도(Freshness)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 TypeScipt는 구조적으로 타입 호환성이 있는 객체 리터럴의 타입 검사를 쉽게 할 수 있도록 &lt;b&gt;신선도(Freshness)&lt;/b&gt;라는 개념을 제공한다(다른 말로 &lt;i&gt;엄격한 객체 리터럴 검사&lt;/i&gt;라 하기도 한다).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;const r: Room =  {
  numDoors: 1,
  ceilingHeightFt: 10;
  elephant: 'present', //오류!!
}&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;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 object literal은 초기에 &amp;ldquo;fresh&amp;rdquo; 하다고 간주되며, 타입 단언 (type assertion) 을 하거나, 타입 추론에 의해 object literal의 타입이 확장되면 &amp;ldquo;freshness&amp;rdquo;가 사라지게 됩니다. 특정한 변수에 object literal을 할당하는 경우 이 2가지 중 한가지가 발생하게 되므로 &amp;ldquo;freshness&amp;rdquo;가 사라지게 되며, 함수에 인자로 object literal을 바로 전달하는 경우에는 &amp;ldquo;fresh&amp;rdquo;한 상태로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/article/typescript-type-compatibility&quot;&gt;https://toss.tech/article/typescript-type-compatibility&lt;/a&gt;&lt;/p&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;fresh한 객체 리터럴을 변수에 할당하지 않고 바로 사용한다면, 어차피 &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;&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;옵셔널한 속성만 가지는 'weak' 타입도 비슷하게 별도의 체크를 수행한다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;interface LineChartOptions {
    logscale?: boolean;
    invertedYAxis?: boolean;
    areaChart?: boolean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적 타이핑 관점에서 &lt;code&gt;LineChartOptions&lt;/code&gt; 타입은 모든 속성이 선택적이므로 모든 객체를 포함할 수 있다. 아무거나 넣어도 문제가 없을거라는 이야기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const opts = { logScale: true };
const o: LineChartOptions = opts; //오류!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위의 코드에선 &lt;code&gt;s&lt;/code&gt; 와 &lt;code&gt;S&lt;/code&gt; 의 오타는 체커에서 걸러주는게 효과적이다. 이러한 체크는 엄격한 객체 리터럴 체크와 다르게 모든 할당문마다 수행된다. fresh하지 않더라도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;객체 리터럴이지만 잉여 속성 체크를 하지 않는 경우&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별것도 아닌데 예외가 있다구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;pre class=&quot;groovy&quot;&gt;&lt;code&gt;interface Options {
    title: string;
    darkMode?: boolean;
}

const o = {darkmode: true, title: 'Ski Free'} as Options //정상&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 인덱스 시그니처를 사용했을 때&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;interface Options {
    darkMode?: boolean;
    [otherOptions: string]: unknown;
}

const o: Options = {darkmode: true}; //정상&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;하지만 웬만하면 쓰지 마셈.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 이렇다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeScipt는 구조적으로 타입 호환성이 있는 객체 리터럴의 타입 검사를 쉽게 할 수 있도록 &lt;b&gt;신선도(Freshness)&lt;/b&gt;라는 개념을 제공한다.&lt;/li&gt;
&lt;li&gt;객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때 잉여 속성 체크가 수행된다.&lt;/li&gt;
&lt;li&gt;임시 변수 이용, 인덱스 시그니처 등으로 잉여 속성 체크를 건너뛸 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  짧은호흡/JS&amp;middot;TS</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/110</guid>
      <comments>https://9yujin.tistory.com/110#entry110comment</comments>
      <pubDate>Wed, 5 Apr 2023 12:50:21 +0900</pubDate>
    </item>
    <item>
      <title>[두둥] 선언적인 코드 작성하기</title>
      <link>https://9yujin.tistory.com/109</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjz6Sl/btr6RYnJxHP/88blGiNCdCDI3gc3KnX0nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjz6Sl/btr6RYnJxHP/88blGiNCdCDI3gc3KnX0nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjz6Sl/btr6RYnJxHP/88blGiNCdCDI3gc3KnX0nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcjz6Sl%2Fbtr6RYnJxHP%2F88blGiNCdCDI3gc3KnX0nK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;281&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 할때 새로운 무언가를 맞닥뜨리는 경우가 줄어들어서 그런가, &lt;span&gt;코드를 이쁘게 짜는 것에 관심이 많아졌다.&lt;span&gt; &lt;/span&gt;&lt;/span&gt;두둥을 개발하면서 마주친 고민들을 공유해보려고 한다. 리액트로 개발하다보면 선언적인 코드에 대해서 고민하게 된다. 리액트 자체가 선언형이기 때문일수도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;선언적인 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령형 프로그래밍과 선언형 프로그래밍에 대한 글들에선 흔히 'How'와 'What'의 차이로 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;ul id=&amp;rdquo;list&amp;rdquo;&amp;gt;&amp;lt;/ul&amp;gt;
&amp;lt;script&amp;gt;
var arr = [1, 2, 3, 4, 5]
var elem = document.querySelector(&quot;#list&quot;);

for(var i = 0; i &amp;lt; arr.length; i ++) {
  var child = document.createElement(&quot;li&quot;);
  child.innerHTML = arr[i];
  elem.appendChild(child);
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문을 통해 배열의 원소를 순회하면서 html요소를 생성하고 보여주는 코드이다. 어떤 절차로 이루어지는지가 드러나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const arr = [1, 2, 3, 4, 5];
return (
  &amp;lt;ul&amp;gt;
    {arr.map((elem) =&amp;gt; (
      &amp;lt;li&amp;gt;{elem}&amp;lt;/li&amp;gt;
    ))}
  &amp;lt;/ul&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 jsx 문법을 사용하면 이렇게 표현할 수 있다. 핵심 데이터만 외부에서 전달 받고 세부적인 구현은 &lt;code&gt;map&lt;/code&gt;함수를 통해 숨겨져있다. 복잡한 작업을 추상화했다고 말할 수 있다.&lt;/p&gt;
&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 class=&quot;haskell&quot;&gt;&lt;code&gt;&amp;lt;NumberListItem data={arr}/&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발을 할 때는 선언적인 코드를 &quot;추상화 레벨이 높아진 코드&quot;로 볼 수 있다. 하지만 &lt;b&gt;무조건 높다고 좋은 것은 아니다&lt;/b&gt;. 여러군데에서 재활용되고 있는 컴포넌트를 사용하는 페이지에 약간의 수정이 필요할때 종종 문제가 생긴다. 기능을 추가하기 위해 조건을 걸고 prop을 더하다 보면, 오히려 책임이 많아지고 네이밍이 모호해지기도 한다. 때문에 추상화의 레벨을 적절히 선택해야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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;b&gt;무한 스크롤&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리팩토링 이전&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 고스락 티켓에서 응원톡은 무한스크롤로 보여진다. 이렇게 서버에서 데이터를 연속적으로 받아오고 적절한 방식으로 렌더링하는 동작을 추상화해서 사용하고 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/61&quot;&gt;[고스락 티켓 2.0] React-Query 무한스크롤 (with useInfiniteQuery)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;React Query&lt;/code&gt;의 &lt;code&gt;useInfiniteQuery&lt;/code&gt;를 사용하고 있다. 무한스크롤 데이터들을 각 페이지 데이터들의 배열로 받아오도록 추상화되어있다. 해당 라이브러리 함수의 사용법 자체는 위 글에 정리되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const TalkList = ({ talkList }: { talkList: ITalk[] }) =&amp;gt; {
  return (
    &amp;lt;Wrapper&amp;gt;
      {talkList.map((talk) =&amp;gt; (
        &amp;lt;TalkBubble
          nickName={talk.nickName}
          content={talk.content}
          createdAt={talk.createdAt}
          iComment={talk.iComment}
          key={talk.id}
        /&amp;gt;
      ))}
    &amp;lt;/Wrapper&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;TalkListWrapper isOpen={isOpen} ref={talkListRef}&amp;gt;
  {data?.pages.map((talkList) =&amp;gt; (
    &amp;lt;TalkList talkList={talkList.talkList} key={talkList.lastId} /&amp;gt;
  ))}
  &amp;lt;Observation /&amp;gt;
&amp;lt;/TalkListWrapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받아온 데이터들은 위와같이 두번의 &lt;code&gt;map&lt;/code&gt;을 통해 렌더링된다. 각 페이지마다 한번, 그 안에서 한번. &lt;code&gt;useInfiniteQuery&lt;/code&gt;와 &lt;code&gt;map&lt;/code&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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const { infiniteListElement } = useInfiniteQueriesList&amp;lt;EventResponse&amp;gt;(
    ['events', keyword],
    ({ pageParam = 0 }) =&amp;gt;
      EventApi.GET_EVENTS_SEARCH({ keyword, pageParam, size: 12 }),
    EventLink,
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;useInifiniteQueriesList&lt;/code&gt;라는 Hook이다. 쿼리키, api 호출 함수(QueryFn), 렌더링할 아이템(ListItem), 이렇게 3개의 핵심정보만 전달한다. 무한스크롤로 받아온 데이터와 ListItem을 이용해 &lt;code&gt;infiniteListElement&lt;/code&gt;를 만들어 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;EventList&amp;gt;{infiniteListElement}&amp;lt;/EventList&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 Hook의 코드이다. &lt;code&gt;observer&lt;/code&gt;에서 스크롤의 끝을 찾으면 새로운 데이터를 패칭해오고, &lt;code&gt;map&lt;/code&gt;으로 &lt;code&gt;ListItem&lt;/code&gt; 컴포넌트를 반복하는 흐름이 숨겨져있다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const useInfiniteQueriesList = &amp;lt;T,&amp;gt;(
  queryKey: QueryKey,
  apiFunction: (payload: any) =&amp;gt; Promise&amp;lt;InfiniteResponse&amp;lt;T&amp;gt;&amp;gt;,
  ListItem: (props: any) =&amp;gt; JSX.Element,
  options?: UseInfiniteQueryOptions&amp;lt;
    InfiniteResponse&amp;lt;T&amp;gt;,
    AxiosError,
    InfiniteResponse&amp;lt;T&amp;gt;,
    InfiniteResponse&amp;lt;T&amp;gt;,
    QueryKey
  &amp;gt;,
) =&amp;gt; {
  const { data, fetchNextPage } = useInfiniteQuery&amp;lt;
    InfiniteResponse&amp;lt;T&amp;gt;,
    AxiosError
  &amp;gt;(queryKey, apiFunction, {
    getNextPageParam: (lastPage) =&amp;gt; lastPage.page + 1,
    ...options,
  });         
  const [ref, inView] = useInView();
  /* observer 관련... */
  const observer = (
    &amp;lt;div className=&quot;observer&quot; ref={ref} style={{ height: '1px' }} /&amp;gt;
  );

  const listElement = data?.pages.map(({ content }) =&amp;gt;
    content.map((item, idx) =&amp;gt; &amp;lt;ListItem {...item} key={`item-${idx}`} /&amp;gt;),
  );

  const isEmpty = data?.pages[0].content.length === 0;

  return {
    infiniteListElement: (
      &amp;lt;&amp;gt;
        {listElement}
        {observer}
      &amp;lt;/&amp;gt;
    ),
    isEmpty,
  };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 Hook을 이용해 다양한 페이지에서 무한스크롤을 사용할 수 있도록 제네릭을 이용하고 있다. QueryFnData의 타입으로 &lt;code&gt;InfiniteResponse&amp;lt;T&amp;gt;&lt;/code&gt;를 넣어주고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export interface InfiniteResponse&amp;lt;T&amp;gt; {
  content: T[];
  page: number;
  size: number;
  hasNext: boolean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한스크롤로 받아오는 데이터의 타입은 모두 위와 같이 통일되어 있다. content로 다양한 타입의 값이 들어올 수 있다. 쿼리 옵션도 옵셔널로 받고 있기 때문에 자유롭게 넣어 사용할 수 있다. 실제로 응원톡은 2초마다 pulling하도록 하는 옵션이 설정되어 있다.&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;Overlay 컴포넌트&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 34486.png&quot; data-origin-width=&quot;1979&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4dIM1/btr6N52MeZY/9S0rkaSpgJMA30HczVzCHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4dIM1/btr6N52MeZY/9S0rkaSpgJMA30HczVzCHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4dIM1/btr6N52MeZY/9S0rkaSpgJMA30HczVzCHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4dIM1%2Fbtr6N52MeZY%2F9S0rkaSpgJMA30HczVzCHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1979&quot; height=&quot;940&quot; data-filename=&quot;Frame 34486.png&quot; data-origin-width=&quot;1979&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달창 역시 고스락 티켓에서부터 계속 써오던 UI였다. 두둥을 개발하면서 모바일일 땐 화면 아래에서 올라오는게 더 자연스럽다고 생각해서 바텀시트로, PC에선 모달로 오버레이를 띄우도록 했다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const { isOpen, openOverlay, closeOverlay } = useOverlay();

&amp;lt;OverlayBox open={isOpen} onDismiss={closeOverlay}&amp;gt;
  &amp;lt;SelectTicket items={tickets?.ticketItems} eventName={detail.name} /&amp;gt;
&amp;lt;/OverlayBox&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;b&gt;Popup 컴포넌트&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 34487.png&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;1065&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmyRIi/btr6NXjpsLc/ZjU1cGVkkAVHARAL7vY8c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmyRIi/btr6NXjpsLc/ZjU1cGVkkAVHARAL7vY8c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmyRIi/btr6NXjpsLc/ZjU1cGVkkAVHARAL7vY8c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmyRIi%2Fbtr6NXjpsLc%2FZjU1cGVkkAVHARAL7vY8c1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1978&quot; height=&quot;1065&quot; data-filename=&quot;Frame 34487.png&quot; data-origin-width=&quot;1978&quot; data-origin-height=&quot;1065&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드민페이지에서 테이블 메뉴를 클릭했을 때 뜨는 팝업 메뉴 컴포넌트이다. 이 외에도 헤더의 프로필을 클릭했을 때, 검색 옵션을 변경할 때 팝업 컴포넌트를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;Popup options={approveWaitingOptions}&amp;gt;
    &amp;lt;Icon name=&quot;threeDot&quot; /&amp;gt;
&amp;lt;/Popup&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때 &lt;code&gt;Popup&lt;/code&gt; 컴포넌트를 사용하고 있다. &lt;code&gt;children&lt;/code&gt;으로 팝업 버튼으로 사용할 컴포넌트를 넣어준다. 어떤 옵션을 보여줄지 &lt;code&gt;PopupOptions[]&lt;/code&gt; 타입의 객체를 전달하기만 하면 끝이기 때문에 선언적인 코드로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const approveWaitingOptions: PopupOptions[] = [
  {
    text: '승인하기',
    onClick: () =&amp;gt; {
      approveMutate({ eventId, order_uuid: data.orderUuid });
    },
  },
  {
    text: '자세히 보기',
    onClick: () =&amp;gt; {
      openOverlay({
        content: 'tableViewDetail',
        props: { eventId, order_uuid: data.orderUuid },
      });
    },
  },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, &lt;code&gt;approveWatingOptions&lt;/code&gt;는 &lt;code&gt;text&lt;/code&gt;와 &lt;code&gt;onClick&lt;/code&gt;을 속성으로 갖는 객체의 배열이다. 승인 대기중인 주문의 정보를 확인하거나 주문을 승인할 때 사용한다. 위에서 작성했던 &lt;code&gt;useOverlay&lt;/code&gt; Hook의 &lt;code&gt;openOverlay&lt;/code&gt; 함수를 사용해 모달을 여는 옵션임을 알 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;a href=&quot;https://sangmin802.github.io/Study/Think/abstract%20painting/&quot;&gt;https://sangmin802.github.io/Study/Think/abstract%20painting/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://toss.tech/article/frontend-declarative-code&quot;&gt;https://toss.tech/article/frontend-declarative-code&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://toss.im/slash-21/sessions/3-3&quot;&gt;https://toss.im/slash-21/sessions/3-3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  긴호흡/고스락 티켓</category>
      <category>react</category>
      <category>TypeScript</category>
      <category>두둥</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/109</guid>
      <comments>https://9yujin.tistory.com/109#entry109comment</comments>
      <pubDate>Thu, 30 Mar 2023 11:39:00 +0900</pubDate>
    </item>
    <item>
      <title>[이펙티브 타입스크립트] 타입스크립트 이해하기 : 타입과 타입 호환성</title>
      <link>https://9yujin.tistory.com/108</link>
      <description>&lt;p data-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-filename=&quot;스크린샷 2023-04-09 오후 10.55.52.png&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;826&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5XyRP/btr8MoTv0Dg/0UyGnXnULT3bIHKqOUcUrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5XyRP/btr8MoTv0Dg/0UyGnXnULT3bIHKqOUcUrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5XyRP/btr8MoTv0Dg/0UyGnXnULT3bIHKqOUcUrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5XyRP%2Fbtr8MoTv0Dg%2F0UyGnXnULT3bIHKqOUcUrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;342&quot; data-filename=&quot;스크린샷 2023-04-09 오후 10.55.52.png&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;826&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트의 모든 값은 데이터 타입을 갖는다. 값을 저장할 때 확보해야 하는 &lt;b&gt;메모리 공간의 크기&lt;/b&gt;를 결정하기 위해, 값을 참조할 때 한 번에 읽어 들여야 할 메모리 공간의 크기를 결정하기 위해, 메모리에서 읽어 들인 &lt;b&gt;2진수를 어떻게 해석&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;pre id=&quot;code_1680064908886&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var foo;
console.log(typeof foo);	// undefined

foo = 3;
console.log(typeof foo);	// number

foo = 'hello;
console.log(typeof foo);	// string&lt;/code&gt;&lt;/pre&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;동적 타입 언어는 변수에 어떤 데이터 타입의 값이더라도 자유롭게 할당할 수 있다. 바로 그 부분에서 자바스크립트의 구조적인 단점이 나타난다. 변화하는 변수 값을 추적하기 어려울 수 있다. 값에 변경에 의해 타입도 언제든지 변경될 수 있다. 따라서&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;blockquote data-ke-style=&quot;style2&quot;&gt;참고:모던 자바스크립트 딥다이브&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;타입스크립트에서의 타입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 컴파일러는 두가지 역할을 수행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최신 타입스크립트를 브라우저에서 동작하도록 구버전의 자바스크립트로 트랜스파일한다.&lt;/li&gt;
&lt;li&gt;코드의 타입 오류를 체크한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지는 서로 완벽히 독립적이다. 따라서,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입 오류가 있는 코드도 컴파일이 가능하다.&lt;/li&gt;
&lt;li&gt;런타임에는 타입 체크가 불가능하다. 런타임 타입은 선언된 타입과 다를 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;'할당한 가능한 값들의 집합'&lt;/b&gt;을 타입이라고 부를 수 있다. 이런 방향으로는 한번도 생각해본 적 없지만, 유니온(&lt;code&gt;|&lt;/code&gt;)이나 인터섹션(&lt;code&gt;&amp;amp;&lt;/code&gt;)이라는 이름의 타입이 있는걸 생각하면 쉽게 납득할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 작은 집합은 공집합이다. &lt;code&gt;never&lt;/code&gt; 타입으로 선언된다.&lt;/li&gt;
&lt;li&gt;여러개의 타입을 묶을 때는 유니온 타입을 이용한다. Union 은 합집합(A &amp;cup; B)이다.&lt;/li&gt;
&lt;li&gt;인터섹션 타입은 교집합(A &amp;cap; B)이다.&lt;code&gt;A &amp;amp; B&lt;/code&gt; 타입의 값은 &lt;code&gt;A&lt;/code&gt; 타입에도, &lt;code&gt;B&lt;/code&gt; 타입에도 할당 가능해야 한다. 만약 &lt;code&gt;A&lt;/code&gt;와 &lt;code&gt;B&lt;/code&gt; 모두 객체 타입이라면 &lt;code&gt;A &amp;amp; B&lt;/code&gt; 타입의 객체는 &lt;code&gt;A&lt;/code&gt;와 &lt;code&gt;B&lt;/code&gt; 타입 각각에 정의된 속성 모두를 가져야 한다.아무것도 겹치지 않는다면 할당할 수 있는 값이 없으므로 &lt;code&gt;never&lt;/code&gt;가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;type a = { foo: number; bar: string }; 
type b = { foo: number; baz: string }; 
type c = a &amp;amp; b; 
const example: c = { foo: 1, bar: &quot;bar&quot;, baz: &quot;baz&quot; }; //문제 없다!! 

type FavoriteSport = &quot;polo&quot; | &quot;ski&quot;; 
type BallSport = &quot;polo&quot; | &quot;baseball&quot;; 
type FavoriteBallSport = FavoriteSport &amp;amp; BallSport; // polo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;A &amp;amp; B&lt;/code&gt; 타입의 값은 &lt;code&gt;A&lt;/code&gt; 타입에도, &lt;code&gt;B&lt;/code&gt; 타입에도 할당 가능해야 한다. 만약 &lt;code&gt;A&lt;/code&gt;와 &lt;code&gt;B&lt;/code&gt; 모두 객체 타입이라면 &lt;code&gt;A &amp;amp; B&lt;/code&gt; 타입의 객체는 &lt;code&gt;A&lt;/code&gt;와 &lt;code&gt;B&lt;/code&gt; 타입 각각에 정의된 속성 모두를 가져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type FavoriteSport = &quot;swimming&quot; | &quot;ski&quot;;
type BallSport = &quot;football&quot; | &quot;baseball&quot;;
type FavoriteBallSport = FavoriteSport &amp;amp; BallSport; // never&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;b&gt;구조적 타이핑과 타입 호환성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 공식문서에서 타입 호환성에 관한 글에 이렇게 적혀있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript의 타입 호환성은 구조적 서브타이핑(structural subtyping)을 기반으로 합니다. 구조적 타이핑이란 오직 멤버만으로 타입을 관계시키는 방식입니다. 명목적 타이핑(nominal typing)과는 대조적입니다. TypeScript의 구조적 타입 시스템의 기본 규칙은 y가 최소한 x와 동일한 멤버를 가지고 있다면 x와 y는 호환된다는 것입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 본질적으로 Duck Typing 기반이다. 만약 어떤 함수의 매개변수 값이 모두 제대로 주어진다면, 그 값이 어떻게 만들어졌는지 신경 쓰지 않고 사용한다. 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;2D 벡터를 타입으로 받는 &lt;code&gt;calculateLength&lt;/code&gt; 함수가 있다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;interface Vector2D {
  x: number;
  y: number;
}

function calculateLength(v: Vector2D) {
  return Math.sqrt(v.x * v.x + v.y * v.y);
}

// 이름이 들어간 벡터 추가
interface NamedVector {
  name: string;
  x: number;
  y: number;
}

// NameVector의 구조가 Vector2D와 호환
const v: NamedVector = { x: 3, y: 4, name: &quot;Zee&quot; };
calculateLength(v);&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;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;NamedVector&lt;/code&gt;타입의 &lt;code&gt;v&lt;/code&gt;는 &lt;code&gt;calculateLength&lt;/code&gt; 함수가 필요로 하는 &lt;code&gt;Vector2D&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 코드가 있다면? 아래는 3차원 벡터를 정규화하는 &lt;code&gt;normalize&lt;/code&gt; 함수이다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface Vector3D {
  x: number;
  y: number;
  z: number;
}

function normalize(v: Vector3D) {
  const length = calculateLength(v);
  return {
    x: v.x / length,
    y: v.y / length,
    z: v.z / length,
  };
}
normalize({ x: 3, y: 4, z: 5 });
// { x: 0.6, y: 0.8, z: 1 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 length를 구하기 위해 위에서 사용했던 &lt;code&gt;calculateLength&lt;/code&gt;를 사용했다. 2D 벡터를 프로퍼티로 받도록 타이핑이 된 함수이기 때문에, 개발자의 의도대로라면 오류를 내뱉어야 한다. 하지만 구조적으로 문제가 없어 타입 호환이 허용되었고 문제없이 실행되었다. 이런 경우에도 오류가 없다고 보면 안되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조적 타이핑을 통한 유연한 타이핑은 테스트를 할때 이점이 있을 수 있다. 예를 들어 postgresDB api를 모킹한다고 했을 때, postgresDB가 사용하는 인터페이스를 모두 구현할 필요가 없는 것. 테스트 환경에서는 실제 환경의 데이터베이스에 대한 정보가 불필요하기때문에, 필요한 부분만 추상화해 사용할 수 있다. 인터페이스를 어떻게 구현하는지 명확히 선언할 필요가 없어진다.&lt;/p&gt;
&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://toss.tech/article/typescript-type-compatibility&quot;&gt;https://toss.tech/article/typescript-type-compatibility&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 이렇다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타입은 값들의 집합이다.&lt;/li&gt;
&lt;li&gt;타입스크립트는 구조적 타이핑을 통해 타입 호환성을 제공한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category> &amp;zwj;  짧은호흡/JS&amp;middot;TS</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/108</guid>
      <comments>https://9yujin.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 28 Mar 2023 19:45:29 +0900</pubDate>
    </item>
    <item>
      <title>1분기 후일담</title>
      <link>https://9yujin.tistory.com/107</link>
      <description>&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;GDSC에서 글쓰기 소모임을 시작했다. 글또를 벤치마킹한 무언가가 될 것 같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;어쩌다보니까 매주 글을 한편 이상 써야 하는 상황이 되었다. 대부분은 기술 블로그에 올리겠지만, 기술적으로 기록할 소재가 없으면 전부다 네이버 블로그행. 일단 이번주는 그럴 듯.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&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/watch?v=NdlI0LNnjJ4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/betAdr/hyR0tWQXqr/If3Pp3dBe3DOGwF2I6HN3k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/NdlI0LNnjJ4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 다시 빠진 노래. &lt;b&gt;'나 지금 돌아버릴 것 같아 / 버렸던 분실물을 찾고 싶어'&lt;/b&gt; 하는 가사에 꽂혔다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;어제는 기숙사에 누워있다가 엄마한테 전화가 왔었다. 내가 어릴때는 입꼬리가 말아 올라가면서 웃는 얼굴이 좋았단다. 그런데 요즘에는 그런 얼굴을 못본 것 같다고, 볼펜 물고 &amp;ldquo;개구리뒷다리&amp;rdquo;를 연습하고 오라고 했다. 집에 오면 웃는 얼굴을 한번 보겠다고.&lt;br /&gt;&amp;nbsp;&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;&lt;b&gt;두둥&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공연 예매 플랫폼은 이전부터 계속 하고싶어했던 프로젝트였지만 엄두를 못내고 있었다. 볼륨이 어느정도 커질지 겁부터 났기 때문. 프로젝트 리드 두명이 각각 당근 인턴과 넥터에 떨어지고 졸지에 백수가 되면서 &lt;s&gt;복수심에 불타&lt;/s&gt; 프로젝트를 시작했다. 그러다 운좋게 넥터에 추가합격했고(...) 그렇게 돌아버릴 것 같은 방학이 시작되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;모노레포를 공부하고 직접 도입했다. 프로젝트 세팅부터 런칭까지 3개월이 걸렸다. 첫 한달동안은 코드를 거의 짜지 못했다. 하루종일 모노레포와 도커 세팅에 매달렸다. 한두군데 삐걱거리지만 잘 돌아가긴 하는 프로젝트 구조가 만들어졌다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;프론트엔드 모노레포 구축 삽질기 (1) - 도입 이유, yarn workspaces, berry&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/101&quot;&gt;&lt;span&gt;프론트엔드 모노레포 구축 삽질기 (2) - 프로젝트 세팅&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/102&quot;&gt;&lt;span&gt;프론트엔드 모노레포 구축 삽질기 (3) - CICD 배포&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Next.js를 프로젝트에서 처음 사용했다. 이론적으로 알고 있던것과 실제로 프로젝트에 사용해보는건 전혀 다른 문제다. 생각지도 못한 곳에서 생각지도 못한 문제를 만났다. 특히 토큰을 다루는데서 고민을 많이 했다. 리프레쉬 로직과 api 요청이 서버에서 실행될 때 고려해야 할 점이 많았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/104&quot;&gt;&lt;span&gt;서버 사이드 렌더링과 cookie 로그인 정보 다루기&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;프로젝트가 끝나갈 즈음에는 '지금 세팅했으면 절대 이렇게 안했을텐데' 라는 말을 달고 살았다. 두둥에서 시도하는 거의 모든것이 처음이었다. 어드민과 티켓 서비스가 각각 리액트과 넥스트를 썼기 때문에 공통으로 사용할 수 있는 로직에 제약이 많았다. 모노레포 환경에서 axios 인스턴스를 사용하는 방법도 어딘가 잘못됐다. 에라이. 졸업프로젝트는 제대로 해보자는 다짐을 하면서.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;사실 개발보다 아쉬운건 기획이었다. 난 내가 생각하는 만큼 능력있는 사람은 아님을 종종 깨닫는다. 몇번의 프로젝트와 세오스 여기저기서 주워들은 경험으로 어느정도 자신이 있었는데, 많이 부족했다. 완벽하게 짜여지지 않은 기획은 개발할때 무조건 개고생으로 되돌아온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/106&quot;&gt;&lt;span&gt;모두를 위한 공연 라이프 - 기획과 디자인&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;br /&gt;&amp;nbsp;&lt;br /&gt;(누가 정한건진 모르지만) 1티어라는 동아리에 들어간다고 해서 모든 일이 알아서 척척 되는건 아니다. 결국 내가 해야되는 것. 그런 부분에서 이번 넥스터즈 활동은 약간 아쉬웠다. 두둥에서 하는 삽질이 생각보다 길어지면서 첫 기대만큼 넥스터즈에 힘을 쏟지 못하게 되었다. 볼륨이 커보이는 기획은 무서웠다. 그래도 마음에 드는 팀에 들어와 최선을 다했다. 결과적으론 좋은 사람들 만나서 잘 끝났지만 넥스터즈라는 너무나도 좋은 기회를 이리 쉽게 날릴 뻔 했다는 점이 중요하다. 이 역시, 여름방학 때 참여할 23기에는 제대로 해보자는 다짐을 하면서.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/103&quot;&gt;&lt;span&gt;IT 커뮤니티 넥스터즈 22기 - 서류, 면접, 합격 후기&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://9yujin.tistory.com/105&quot;&gt;&lt;span&gt;IT 커뮤니티 넥스터즈 22기 - 활동 후기&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;터보레포와 Nextjs를 사용했다. 서버사이드를 적극적으로 사용하진 않아서 약간은 아쉽다. 그래도 모노레포 환경에서 어떻게 구조를 가져가야 하는지, 타입은 어떻게 쓰는지 등, 양질의 많은 코드를 읽을 수 있었다. 많은 자극과 공부가 되었다. 두둥에서 미리 비슷한 프로젝트 세팅을 체험해본게 많은 도움이 되었다.&lt;br /&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;어쩌다보니 여태까지 공연중에 가장 많은 곡을 하고 있다. 그것도 제일 바쁠 때에! 공연이 다가올 때마다 느끼는 압박감이 싫어서 다음은 없다고 다짐하는데, 그게 벌써 여러번이다. 합주하면서 고스락 사람들 만나고 웃고 떠드는게 좋아서 하는 듯. 저번 9일에는 YB친구들의 공연을 봤다. 처음으로 내가 무대에 올라가지 않는 공연이었다. '나도 빨리 공연하고 싶다'는 마음이 생기는거만 빼면 정말 재밌었다. 그래서 매번 똑같은 실수를 하는구나 싶었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;다음 공연 때는 레슨을 받아볼까 싶다. 그게 어디든지 돈을 쓰면 한결 마음이 편해지잖아. 베이스를 오래 치다보니 점점 욕심이 생긴다. 점점 비싼 베이스를 갖고 싶어지고, 제대로된 페달보드를 장만하고 싶다. 기본기의 중요함을 점점 느낀다. 새내기땐 신경도 쓰지 않았던 핑거링이 제일 어렵다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;글을 쓰다가 앞으로 남은 2023년의 테마를 정했다. 기본기. 앞으로 벌여놓은 일들에도 그렇게 의미부여를 해볼 예정이다. 내 다시는 일을 벌이지 않겠다고 다짐한지 한달만에 또 일이 이만큼 벌어졌다. 요즘애들은 이런걸 스불재라고 한다더라 ㅋ.&lt;br /&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;블로그 써야하는데.. 하는데.. 하는 동기부여의 문제이다. GDSC 안에는 그런 종류의 동기부여를 줄 수 있는 사람이 많다. 격주에 한번 (또는 매주) 다같이 글을 쓰고, 서로에게 피드백을 달아준다. 항상 데드라인이 없는 글을 써왔다. 그래서 세월아 네월아 쓰곤 했다. 빠르게 쓰는 연습을 해봐야겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;글쓰기(또는 읽기) 만큼 기본기에 가까운 활동이 없다. 내가 어떤걸 모르는지 곧바로 드러날 수 밖에 없겠더라. 어제는 진호님이 웹 기초 스터디를 위해 쓰셨던 git 관련 글을 읽었는데, 나도 지금까지 git을 제대로 모르고 쓰고 있었다. 그냥 실무에서 git을 사용하는 것과, 이론적으로 알고 쓰는건 전혀 다른 문제다. 비슷한 말을 위에서 했던 것 같은데.&lt;br /&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;br /&gt;&amp;nbsp;&lt;br /&gt;그런 의미로 세오스 프론트엔드 친구들과 &lt;b&gt;자바스크립트&lt;/b&gt; 알고리즘 스터디를 또 시작했다. 여기에 추가로 GDSC에서 하는 타입스크립트 스터디까지. 프론트엔드 직군에서 자바스크립트로 코딩테스트를 보는 회사가 많아졌다. 우아한형제들, 카카오커머스 등. 꼭 코테가 아니더라도 과제전형으로 보는 회사도 많다. 이번학기에는 바닐라를 잘 챙겨봐야겠다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아니 근데 알고리즘 스터디 두탕을 뛸 수 있나. 욕심만 아득바득 생겨가지고 시작은 했는데, 생각해보니까 이제 일주일에 9문제를 풀어야 한다. 아무리 프로젝트가 끝났다고 해도 약간 무리라고 생각은 들지만.. 그렇다고 대다수 회사에서 쓸 수 있는 파이썬을 버릴 순 없으니.. 어떻게든 되겠지!&lt;br /&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;프로젝트 동아리 모집을 거들떠도 안본건 나름대로 이유가 있다. 이미 예정된 프로젝트가 세개나 더 있다. 졸업 프로젝트, GDSC 플랫폼 개발 프로젝트, 넥스터즈 23기 프로젝트. 이전 두둥 프로젝트 관련 글에서도 썼던 말이지만, 대학교를 떠나기 전에 최대한 많은 흔적을 남기고 가고 싶다. 몇년 뒤에도 내가 만들어놓은 것들을 계속 사용하는 모습을 기대하며 일하고 있다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;마치며.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 글쓰기 소모임이 끝까지 잘 유지되기 위한 고민. 인원수가 꽤 많은 상황에서 효과적으로 피드백을 나눌 수 있는 방법에 대해서. 멤버들과 나에게 효과적으로 동기를 줄 수 있는 방법에 대해서. 방금 생각해봤는데, 아무래도 격주가 나은 것 같다. 절대로 바빠서가 아니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;2. 노션 몰댄로그를 쓰는걸 보고 또다시 귀가 팔랑거리기 시작했다. 방학동안 열심히 세팅한 깃허브 블로그가 다크모드에서 굉장히 못생기게 나오는걸 보고나서 부터였다. 100편 언저리 되는 글들을 다 옮긴게 얼마 안됐기 때문에.. 일단은 참아보도록 하겠음.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;3. 아무리 생각해도 글을 상당히 못쓴다. 하고 싶은 이야기들을 쭉 적은 다음에 읽어보면, 어색한 부분이 많이 나온다. 내가 느끼는 건 다음과 같음. 첫번째, '~데, ~보면' 형식의 문장이 엄청 많다. 바로 앞문장에도 있음. 쓸데없는 쉼표가 많아지면서 어수선한 분위기를 풍기는 것 같다. 책을 많이 읽어야지 다짐. 또 다짐. 두번째, 자기자랑 아니면 자기비하밖에 없다. 1도 모르지만 건강하지 않은 심리상태인건 알겠음.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;4. 다음주부터는 두둥을 개발하며 고민했던 기술적인 글로 써보려고 한다. 하루 안에 끝내보는걸 목표. 버렸던 분실물을 찾기 위해선, 시간을 낭비하지 않는 습관부터 들여야겠다.&lt;/p&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;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  글을 위한 글</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/107</guid>
      <comments>https://9yujin.tistory.com/107#entry107comment</comments>
      <pubDate>Wed, 22 Mar 2023 14:26:42 +0900</pubDate>
    </item>
    <item>
      <title>[두둥] 모두를 위한 공연 라이프 - 기획과 디자인</title>
      <link>https://9yujin.tistory.com/106</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 배경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고스락에서 공연을 해오면서 티켓 예매방식에 불편함을 느꼈다. 컴퓨터공학과 학회답게, 작년 3월부터 매 공연마다 고스락 내부 인원들로 예매서비스를 직접 개발해 사용했다. 500명이 넘는 인원이 우리 서비스를 실사용했던 경험이었다.&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://www.9yujin.site/devlog/gosrock1/idea-220124&quot;&gt;https://www.9yujin.site/devlog/gosrock1/idea-220124&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작년 3월 첫번째 프로젝트&lt;/b&gt;에선 가장 기본적인 기능으로 서비스를 했다. 전화번호와 이름을 별개로 입력받아 문자 인증을 통해 유저를 받았다. 코로나로 인해 최대 입장 가능 인원 제한이 있어 107명의 관객이 이 서비스를 통해 고스락 공연에 와주었다. QR코드 입장 확인과 어드민 서비스에서 예매 승인 등의 대표적인 기능이 공연 당일까지 성공적으로 사용됐고, 충분히 검증되었다고 생각한다.&lt;/p&gt;
&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.9yujin.site/devlog/gosrock2/idea-220810&quot;&gt;https://www.9yujin.site/devlog/gosrock2/idea-220810&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작년 9월&lt;/b&gt;에는 첫번째 프로젝트에서 아쉬웠던 점과 공연 기획이 달라진 점을 반영해 &lt;b&gt;새로운 예매 서비스&lt;/b&gt;를 개발했다. 공연을 이틀에 나눠서 하게 되었고, 한명이 에매할 수 있는 티켓 매수의 제한도 사라졌다. 이에 맞춰서 공연 예매 플로우와 마이페이지(예매내역) 등의 기능을 추가했다. 사용자의 재방문을 유도하는 방법을 고민하다가 응원톡이라는 새로운 기능을 도입했던 프로젝트이기도 하다. 응원톡에 남긴 관객 들 중 추첨해 선물을 주는 이벤트를 열기도 했고, 총 380명이 이 서비스를 이용해 티켓을 예매했다.&lt;/p&gt;
&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;b&gt;누구나 지속적으로 사용할 수 있는 서비스&lt;/b&gt;를 기획하고 개발하기 시작했다. &lt;b&gt;두둥&lt;/b&gt;은 공연등록 및 소개, 티켓예매, QR코드를 통한 입장확인과 정산을 할 수 있는 서비스이다. 고스락 뿐만 아니라 수많은 공연 동아리들이 편하게 사용할 수 있는 서비스를 목표로 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 기획&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 인터파크 티켓이나 예스24와 같이 다양한 공연이 올라오고 다양한 티켓을 구매할 수 있다. 관리자는 다양한 공연과 티켓을 생성하고 등록하고 판매할 수 있다. 그리고 판매된 티켓들을 관리하고 통계를 볼 수 있다. 기존의 서비스를 플랫폼으로 확장했다고 생각하면 쉽다.&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;1280&quot; data-origin-height=&quot;1578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uniWR/btr2H6LmaF7/mqM8BhoIRjKknVEoz4LUi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uniWR/btr2H6LmaF7/mqM8BhoIRjKknVEoz4LUi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uniWR/btr2H6LmaF7/mqM8BhoIRjKknVEoz4LUi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuniWR%2Fbtr2H6LmaF7%2FmqM8BhoIRjKknVEoz4LUi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;592&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼륨이 배로 커진만큼 필요한 기능들을&lt;b&gt; IA와 같이 구조화&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;885&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brfRhA/btr2PTYiXTi/aKk5nOm5ELEQhElcxtdBZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brfRhA/btr2PTYiXTi/aKk5nOm5ELEQhElcxtdBZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brfRhA/btr2PTYiXTi/aKk5nOm5ELEQhElcxtdBZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrfRhA%2Fbtr2PTYiXTi%2FaKk5nOm5ELEQhElcxtdBZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;332&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;885&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;2000&quot; data-origin-height=&quot;1272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc609h/btr2PR0uSuR/cFLVvnmVAeQ1E4GMHRNcOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc609h/btr2PR0uSuR/cFLVvnmVAeQ1E4GMHRNcOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc609h/btr2PR0uSuR/cFLVvnmVAeQ1E4GMHRNcOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc609h%2Fbtr2PR0uSuR%2FcFLVvnmVAeQ1E4GMHRNcOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;305&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1272&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;디자인 작업&lt;/b&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;hellip; 그냥 보라색을 좋아해서. 처음엔 파스텔 느낌의 라일락 색을 사용했다. 디자인을 뽑으면서 보니 색들이 다 밋밋하고 채도가 낮은 듯 했다. 마켓 컬리 느낌을 생각하며 더 진한 보라색으로 팔레트를 수정했다. 결과적으로 나쁘지 않은 것 같음.&lt;/p&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;2000&quot; data-origin-height=&quot;2094&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V0nM7/btr2FepoQRL/rp0bQFNKnONVHq3cX7HNE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V0nM7/btr2FepoQRL/rp0bQFNKnONVHq3cX7HNE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V0nM7/btr2FepoQRL/rp0bQFNKnONVHq3cX7HNE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV0nM7%2Fbtr2FepoQRL%2Frp0bQFNKnONVHq3cX7HNE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;503&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2094&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;2000&quot; data-origin-height=&quot;1182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0N6NF/btr2OtZ7sRs/ou7tD7ipkJiPzilrr79wJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0N6NF/btr2OtZ7sRs/ou7tD7ipkJiPzilrr79wJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0N6NF/btr2OtZ7sRs/ou7tD7ipkJiPzilrr79wJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0N6NF%2Fbtr2OtZ7sRs%2Fou7tD7ipkJiPzilrr79wJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;284&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1182&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;로 개발하기에 최대한 재활용이 가능하고 prop으로 다루기 수월하길 원했다. 피그마에서 디자인할 때부터 component varient와 auto layout을 적극적으로 사용했다. 실제로 개발된 컴포넌트의 구조도 피그마에서 디자인한 레이어 구조와 거의 동일하다. 후반에 디자인이 많이 수정되면서 조금 의미가 퇴색된 점은 아쉽지만 정말 큰 의미가 있었다. 다음 프로젝트에서는 더 나아질거라 자신할 수 있다!! &lt;a href=&quot;https://gosrock.github.io/DuDoong-Front/&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXKgBZ/btr2H7i9IOH/UqrFM5sP5i6OhkkZPLPYRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXKgBZ/btr2H7i9IOH/UqrFM5sP5i6OhkkZPLPYRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXKgBZ/btr2H7i9IOH/UqrFM5sP5i6OhkkZPLPYRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXKgBZ%2Fbtr2H7i9IOH%2FUqrFM5sP5i6OhkkZPLPYRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;287&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1195&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 디자인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;동아리 공연&amp;rsquo; 이라는 도메인으로 지난 두번의 프로젝트를 하면서 많은 경험과 소소한 데이터들을 얻었다. 덕분에 밴드 동아리에서 불편해 하는게 무엇인지, 필요로 하는 기능은 무엇인지에 대해 잘 알게 되었다.&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;lsquo;예&amp;rsquo;를 선택했을 때 결제금액을 추가할 수 있다. 뒷풀이 희망자에게 예약비를 미리 받을 때를 상상하고 추가한 기획이다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/92Zle/btr2TChpCkO/3kDYKaHGbBPXe5G3HIkE00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/92Zle/btr2TChpCkO/3kDYKaHGbBPXe5G3HIkE00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/92Zle/btr2TChpCkO/3kDYKaHGbBPXe5G3HIkE00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F92Zle%2Fbtr2TChpCkO%2F3kDYKaHGbBPXe5G3HIkE00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1176&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공연 상세 페이지에서 예매하기를 클릭하면 티켓의 종류와 매수를 선택하는 오버레이가 나타난다. 선택해야 할 옵션이 있는 경우에는 &amp;lsquo;&lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;옵션 선택하기&lt;/span&gt;&lt;/b&gt;&amp;rsquo; 페이지로 넘어간다. 티켓을 여러장 구매할 때 모든 티켓마다 옵션을 일일히 선택해야 한다. 티켓마다 옵션들을 다 펼치면 스크롤이 너무 길어질 것 같아 아코디언으로 접을 수 있게 했다. 모든 티켓을 똑같은 옵션으로 선택하는 경우가 많을 것 같아 한번에 옵션을 적용하는 토글 버튼도 넣어주었다. 경우마다 api 요청 형식에 맞게 객체 값 바꾸는거에서 고생 좀 했다. 눈물의 스프레드 연산자 파티. 구현 다 하고 나서야 immer 의 존재가 생각났다. 에라이.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2023-03-08 오후 8.14.35.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vbPpi/btr2RhdIkUd/BwJYlUP6T87q3FZyArCZ71/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vbPpi/btr2RhdIkUd/BwJYlUP6T87q3FZyArCZ71/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vbPpi/btr2RhdIkUd/BwJYlUP6T87q3FZyArCZ71/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/vbPpi/btr2RhdIkUd/BwJYlUP6T87q3FZyArCZ71/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;800&quot; height=&quot;500&quot; data-filename=&quot;화면 기록 2023-03-08 오후 8.14.35.gif&quot; data-origin-width=&quot;800&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어드민&lt;/b&gt;에서는 드래그 앤 드롭으로 티켓에 옵션을 적용한다. 매우 직관적인 UX. 취소할 때도 빈 곳에 드롭하면 취소됨. beautiful-dnd 라이브러리를 처음 이용해봤는데, 꽤 편하다. 처음 개발을 공부할 때는 이벤트리스너로 직접 드래그앤 드롭을 구현했었다. 그래서 그런지 라이브러리를 처음 사용할 때 이해가 한결 쉬웠던 것 같다.&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;lsquo;옵션 선택하기&amp;rsquo;에서 모든 옵션에 응답을 입력하고 다음 버튼을 누르면 &lt;b&gt;주문하기&lt;/b&gt; 화면이 나온다. 모든 옵션의 응답이 똑같은 티켓은 하나의 주문정보(orderLine)으로 묶인다. 만약 3개의 티켓을 샀을 때 그 중 2개의 응답이 같고 1개의 응답이 다르다면, 하나의 주문(order)에 2개의 주문정보(orderLine)이 들어가는 것. 배달의 민족에서 여러개의 음식을 시켰을 때 옵션이 똑같으면 같은 주문에 수량이 추가되는 것과 비슷한 정책이다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boOQtA/btr2FcFlkvi/q8TCdaA0IqRpKcfk0cUOOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boOQtA/btr2FcFlkvi/q8TCdaA0IqRpKcfk0cUOOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boOQtA/btr2FcFlkvi/q8TCdaA0IqRpKcfk0cUOOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboOQtA%2Fbtr2FcFlkvi%2Fq8TCdaA0IqRpKcfk0cUOOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1233&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예매 내역에선 위와 같이 보이게 된다. 하나의 주문에 여러 주문정보(orderLine)이 있다면 좌우로 슬라이드해서 넘겨본다. 아코디언을 열어 옵션에 작성한 응답들을 다시 확인할 수 있다. 유료 옵션을 선택했다면 얼마의 가격이 붙었는지 태그를 통해 알려준다.&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;하지만 모바일 티켓은 주문정보를 모두 펼쳐 하나의 리스트로 보여준다. 위의 경우라면 2개의 주문정보이지만 3개의 티켓을 한번에 나타내는 것. QR코드를 찍고 입장하는 페이지이기 때문에, 옵션과 주문정보는 별로 중요하지 않은 정보이다. 내가 구매한 티켓이 몇장이 있고, QR코드 티켓의 상태가 무엇인지가 더 중요하다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgILUX/btr2H6dte3p/DWPSjTb4jU6QRDZjKr5C70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgILUX/btr2H6dte3p/DWPSjTb4jU6QRDZjKr5C70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgILUX/btr2H6dte3p/DWPSjTb4jU6QRDZjKr5C70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgILUX%2Fbtr2H6dte3p%2FDWPSjTb4jU6QRDZjKr5C70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1393&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1393&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;새 프로젝트를 할 때마다 느끼는건, 디자인도 하다보면 는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RFLI1/btr2O1hX0z7/kp7TYt4S3MT6Pzde9Kp3s1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RFLI1/btr2O1hX0z7/kp7TYt4S3MT6Pzde9Kp3s1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RFLI1/btr2O1hX0z7/kp7TYt4S3MT6Pzde9Kp3s1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRFLI1%2Fbtr2O1hX0z7%2Fkp7TYt4S3MT6Pzde9Kp3s1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1335&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BxxZH/btr2PSSAEq2/GPasiPJX1VsYOGD13uOKaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BxxZH/btr2PSSAEq2/GPasiPJX1VsYOGD13uOKaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BxxZH/btr2PSSAEq2/GPasiPJX1VsYOGD13uOKaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBxxZH%2Fbtr2PSSAEq2%2FGPasiPJX1VsYOGD13uOKaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;1334&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1334&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10년 뒤에도 두둥을 사용하는 고스락을 상상하면서! 다음 글로는 두둥을 개발하면서 했던 기술적인 고민들을 가져와보겠다.&lt;/p&gt;</description>
      <category>  긴호흡/고스락 티켓</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/106</guid>
      <comments>https://9yujin.tistory.com/106#entry106comment</comments>
      <pubDate>Wed, 8 Mar 2023 22:38:15 +0900</pubDate>
    </item>
    <item>
      <title>IT 커뮤니티 넥스터즈 22기 - 활동 후기</title>
      <link>https://9yujin.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;넥스터즈 22기 활동이 끝났다. ‘출출’ 팀에서 넥스터즈 운영진을 위한 어드민 서비스 위클리를 만들었다. 많이 부족했지만 팀원들의 배려 덕분에 한 기수를 즐겁게 마쳤던 것 같다. 8주 동안 넥스터즈에서 어떤 활동을 하는지, 프로젝트를 어떻게 했는지에 대한 후기이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;매주 토요일마다 정기 세션이 있다. 매주 위치가 정해져 있지는 않고, 대관이 되는 곳으로 정해지는 것 같음. 세운상가 세운홀, 역삼역 마루180 등 스타트업 창업과 관련된 장소를 주로 이용했다.&lt;br&gt;&amp;nbsp;&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;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8NkRi/btr2D4FWLFu/Q8TEB3E66mJ6wZnpRd1jbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8NkRi/btr2D4FWLFu/Q8TEB3E66mJ6wZnpRd1jbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8NkRi/btr2D4FWLFu/Q8TEB3E66mJ6wZnpRd1jbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8NkRi%2Fbtr2D4FWLFu%2FQ8TEB3E66mJ6wZnpRd1jbK%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;480&quot; height=&quot;480&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;매주 세션은 이 순서대로 진행된다. 1주차 활동이 시작하기 전에 미리 아이디어 발제와 투표를 하는 시간이 주어진다. ‘모임모임’ 이라는 서비스를 이용한다. 몇 기수 전에 넥스터즈 안에서 개발한 프로젝트를 실제로 사용하고 있는 것. 발제자가 자신의 기획을 작성해서 올린 후에 익명으로 원하는 서비스 3개의 투표를 한다. 넥스터즈 안에 다양한 파트가 있기 때문에, 내가 참가할 수 있는 (웹 프론트를 필요로 하는) 기획 위주로 투표를 했던 것 같다. 투표가 마감되면 선정된 10개의 아이디어가 발표된다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;1주차&lt;/b&gt;에 바로 &lt;b&gt;팀빌딩&lt;/b&gt;이 진행된다. 발제자(PM)들이 준비해온 발표를 모두 마치면 1지망부터 3지망까지 선택해 폼을 제출한다. 사람이 많고 시간도 없어서 회원들마다 하나씩 살펴보는건 아닌것 같고… 지망 순으로 가져가는 것 같다. 자세한건 모름. ‘모임모임’ 서비스를 여러 기수동안 실제로 사용해온 것을 보고, 넥스터즈 출석 어드민 서비스 또한 동아리 안에서 실제 유저가 사용할 수 있는 프로젝트라 생각했다. 높은 순위로 지망에 넣었다. 그리고 두달 동안 ‘출출’ 팀에서 프로젝트를 하게 되었다!&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;5주차 UT&lt;/b&gt;는 디자이너들이 만든 프로토타입을 시연해보면서 사용성을 테스트하고 피드백하는 시간이다. 피그마 프로토타입을 이용하거나 메이즈라는 서비스를 이용한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;7주차 컨퍼런스 데이&lt;/b&gt;에는 넥스터즈에서 활동하는 회원들이 직접 준비한 세션을 들을 수 있었다. ZEP이라는 메타버스 플랫폼에서 진행되었다. 게더가 이제 몇십명 이상 쓰려면 꽤 비싼 돈을 줘야 하는거로 기억... 주니어를 위한 ‘오픈소스에 기여하는 법’, Supabase 소개, 이직썰 등의 발표가 있었다. 직접 실무에서 일하는 개발자들의 경험과 지식을 공유받을 수 있다는 점이 너무 좋았다. 나도 몇년 뒤엔 실력있는 넥스터즈 OB가 되어 컨퍼런스를 준비하겠다는 다짐을 하면서.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;8주차 넥나잇&lt;/b&gt;은 원래 밤을 새며, 최종 발표 1주일전 남은 개발을 달리는 날이다. 이번엔 여러 상황이 겹쳐 나잇까진 아니고 ‘넥버닝’으로 진행되었다. 동아리에서 제공해준 피자 냠냠하면서 개발 낭낭하다가 저녁 느즈막히 집에 왔다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;프로젝트와 팀에 대한 이야기. 현재 넥스터즈의 세션 출석체크는 운영진들이 직접 수기로 하고 있다. 세션 장소에 오면 이름표를 나눠주고 엑셀에 체크한다. 출석 시간이 지났을때 남은 이름표와 체크한 엑셀을 대조해보면서 출석체크를 하는 것. 문 앞에 운영진이 항상 상주해있어야 하고, 사람이 많이 모이면 운영진 테이블 앞에 병목현상이 자주 일어나는 문제가 있었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0pndq/btr2xS0F216/pk5ETOnEVz2N5BJFWq9K0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0pndq/btr2xS0F216/pk5ETOnEVz2N5BJFWq9K0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0pndq/btr2xS0F216/pk5ETOnEVz2N5BJFWq9K0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0pndq%2Fbtr2xS0F216%2Fpk5ETOnEVz2N5BJFWq9K0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 넥스터즈 출석체크 웹으로 출발한 것. 거기서 조금씩 살을 더해 전체 회원과 활동점수 관리, 세션 관리 등의 기능으로 기획이 완성되었다. 맨 앞 스크린에 띄워진 1분마다 새로 바뀌는 QR코드를 이용해 각자 출석체크를 빠르게 하면서도 어뷰징을 방지할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;매 주차가 진행될때마다 화면에 저기 초록색 귀요미가 하나씩 늘어난다. 마지막 8주차 세션 출첵 화면에는 네잎클로버 모양의 귀요미가 두마리 보여지는 것. 디자이너들은 어떻게 저런걸 빠른 시간 안에 뽑아내는지. 볼때마다 신기하다.&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;뒷풀이도 지금까지 했던 동아리들과는 느낌이 전혀 달랐다. 대부분 현직자다 보니 회사 이야기, 이직 이야기를 많이 한다. 처음에는 적응이 잘 안됐지만 몇번 듣다보니 점점 재밌게 들렸다. 항상 마지막은 속으로 쉬는 한숨으로 끝난다. 1년만 일찍 태어날걸!&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;&lt;b&gt;코드를 짜는 시간보다 키보드에 손 떼고 구조에 대해서 고민하는 시간이 더 길다.&lt;/b&gt; 넥버닝때 PM겸 프론트 형이 머리를 감싸안고 꽤 오랫동안 미동도 안하고 있었다. 옆에서 뭐하냐고 물어보니 어떻게 하면 토큰 관리를 기가막히게 할 수 있을가 고민하고 있다더라. 물론 나도 고민을 안하는 건 아니지만. 짜면서 고민하는 것보다 어느정도 두 과정을 분리하는게 길게 개발하는데 더 좋은 방법인 것 같다. 오늘 소프트웨어 공학 1주차 수업에도 지나가면서 나왔던 내용과 어느정도 일치한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;타입을 쓰는데서 실력의 편차가 크게 나타난다.&lt;/b&gt; 컴포넌트 prop의 타입들에 특히 신경을 많이 쓰는 것 같음. 컴포넌트 내부의 타입과 외부에서 그 컴포넌트를 사용할때 보여지는 타입을 사용하는 방식이 다르기 때문에. 이 외에도 유틸리티 타입과 리액트 내장타입을 활용하는 방식을 많이 배웠다. 실제로 써보거나 양질의 다른 코드를 보지 않으면 쉽게 알 수 없는 정보였다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;아직 Next에 대한 이해가 부족하다.&lt;/b&gt; 두둥을 개발하면서 어느정도 익숙해졌다고 생각 했는데, 아직 한참이다. 프로젝트를 하며 의문이 생기는 지점이 종종 생긴다. 서버 사이드에서 리액트 쿼리의 prefetchQuery를 사용하거나, 인증정보를 관리하는 방법. 그 외에 문서에서 한번 읽고 넘어갔지만 사용해본 적 없던 유용한 기능들. 이런 것들을 새로 배울 수 있었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;프로젝트를 많이 하고 경험이 쌓이다보면 자연스레 다양한 나만의 패턴이 생기는 것 같다.&lt;/b&gt; 글을 쓸 때 자주 쓰는 말투와 어휘, 문장이 있듯이. 겉으로 보기엔 똑같이 작동하더라도 코드는 수많은 방식으로 짜여질 수 있다. 프론트엔드 공부는 그중에서 더 나은 방법을 계속 찾아 나가는 과정인 것 같다. 그러니까 다른 사람들의 코드를 보는 것이 중요하다. 활동이 끝나고 나면 다른 프로젝트의 코드들도 천천히 읽어보고, 다음 프로젝트에 적용도 해보는 시간을 가져봐야겠다. 모든 것이 경험.&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1739&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qor2x/btr2vnUoJKL/JmU9EvW4pakOofhwOnhD40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qor2x/btr2vnUoJKL/JmU9EvW4pakOofhwOnhD40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qor2x/btr2vnUoJKL/JmU9EvW4pakOofhwOnhD40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQor2x%2Fbtr2vnUoJKL%2FJmU9EvW4pakOofhwOnhD40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;292&quot; data-origin-width=&quot;1739&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;글을 마무리하며. 항상 새로운 사람들을 만나는 자리는 긴장으로 시작한다. 그래도 매번 좋은 사람들이어서 감사. 개발을 하면서 자존감이 종종 내려가려고 할때가 많은데, 그 때마다 간신히 붙잡아주는 한두마디가 툭툭 들려온다. 여기든 저기든. 개발을 하는것도 일을 하는것도 언제나 코드보다 사람이 먼저라는 생각을 하면서!!&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;개발자로서도, 사람으로서도 쪼오오금은 성장했다고 느끼는 뿌듯한 겨울방학이었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - Nexters/nexters-admin-client:  ️ Nexters 22기 출출팀 당신의 한 주의 출석을 책임지는 웹 &amp;quot;위클&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot; ️ Nexters 22기 출출팀 당신의 한 주의 출석을 책임지는 웹 &amp;quot;위클리&amp;quot;. Contribute to Nexters/nexters-admin-client development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Nexters/nexters-admin-client&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/be6JTJ/hyRRKjBrkn/IhQKeYjLK0Y5Z2u6FSUJr1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot; data-og-url=&quot;https://github.com/Nexters/nexters-admin-client&quot;&gt;
 &lt;a href=&quot;https://github.com/Nexters/nexters-admin-client&quot; target=&quot;_blank&quot; data-source-url=&quot;https://github.com/Nexters/nexters-admin-client&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/be6JTJ/hyRRKjBrkn/IhQKeYjLK0Y5Z2u6FSUJr1/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;GitHub - Nexters/nexters-admin-client:  ️ Nexters 22기 출출팀 당신의 한 주의 출석을 책임지는 웹 &quot;위클&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt; ️ Nexters 22기 출출팀 당신의 한 주의 출석을 책임지는 웹 &quot;위클리&quot;. Contribute to Nexters/nexters-admin-client development by creating an account on GitHub.&lt;/p&gt;
   &lt;p class=&quot;og-host&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>  글을 위한 글</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/105</guid>
      <comments>https://9yujin.tistory.com/105#entry105comment</comments>
      <pubDate>Tue, 7 Mar 2023 23:39:54 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 서버 사이드 렌더링(SSR)과 cookie 로그인 정보 다루기 (getInitialProps를 사용해도 되는가)</title>
      <link>https://9yujin.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Frame 34422.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dy90LO/btrWRDVAJHM/LIBOcF3OVxIZ9m2W7Acchk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dy90LO/btrWRDVAJHM/LIBOcF3OVxIZ9m2W7Acchk/img.png&quot; data-alt=&quot;둥둥즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dy90LO/btrWRDVAJHM/LIBOcF3OVxIZ9m2W7Acchk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdy90LO%2FbtrWRDVAJHM%2FLIBOcF3OVxIZ9m2W7Acchk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;281&quot; data-filename=&quot;Frame 34422.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;411&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;두둥 프로젝트를 시작하면서 Next.js를 처음 프로젝트에 사용했다. 기획상 이전 프로젝트보다 SEO를 더 신경써야했고, 이미지 최적화와 같은 것들을 자동으로 제공해준다는 점이 좋았다. 이전부터 계속 써보고 싶었던 기술이었어서 공부하면서 프로젝트에 사용할 가치가 있었다. 이번 포스팅은 서버사이드 렌더링 환경에서 카카오 소셜 로그인을 구현하면서 공부했던 내용과 마주쳤던 고민들을 기록한 글이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목차&lt;br /&gt;1. Next.js의 서버 사이드 렌더링(SSR)&lt;br /&gt;2. 서버 사이드에서 쿠키 사용하기&lt;br /&gt;3. getInitialProps를 사용해도 되는가?&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Next.js의 서버 사이드 렌더링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 준비하면서 Next.js에서 서버사이드 렌더링의 방식을 조금 더 공부해보았다. 먼저 다들 아는 얘기. Next.js는 SSR을 기반으로 하지만, 페이지가 로드된 이후에는 일반적인 리액트의 CSR을 이용한다. 페이지가 한번 렌더링된 이후에 (axios 등으로) 패치된 데이터들을 보여줄땐 클라이언트쪽에서 다시 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR에선 &lt;code&gt;useEffect&lt;/code&gt; 를 통해 컴포넌트가 렌더링될때 데이터 패칭을 할 수 있다. 하지만 서버사이드 렌더링을 하는 Next.js에서 컴포넌트는 사전에 불러와야할 데이터가 필요할 수 있다. &lt;code&gt;getInitialProps&lt;/code&gt;와 &lt;code&gt;getServerSideProps&lt;/code&gt; 등을 이용해 서버에서 데이터를 패칭하고, 반환된 데이터를 사용해 미리 렌더링한다.&lt;/p&gt;
&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Next 서버에서 Get 요청을 받으면, 요청에 맞는 Page를 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_app.js&lt;/code&gt;의 &lt;code&gt;getInitialProps&lt;/code&gt;가 있다면 실행한다.&lt;/li&gt;
&lt;li&gt;해당 Page의 &lt;code&gt;getServerSideProps&lt;/code&gt;가 있다면 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pageProps&lt;/code&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;React는 클라이언트에서 HTML, CSS, 자바스크립트 모두 &lt;code&gt;render()&lt;/code&gt; 함수를 이용해 생성한다. 반면, Next.js는 서버에서 HTML을 가져온 후에, 클라이언트에서 자바스크립트 코드와 &lt;b&gt;hydrate&lt;/b&gt; 하여 브라우저에 렌더링한다. &lt;code&gt;hydrate()&lt;/code&gt; 함수를 이용해 서버에서 받아온 HTML에 번들링된 React 코드들이 한번 더 렌더링 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 서버 사이드에서 쿠키 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두둥에선 카카오 소셜 로그인을 이용하고 있다. 웃긴건 &lt;b&gt;일년도 더 전에&lt;/b&gt; 썼던 &lt;a href=&quot;https://9yujin.tistory.com/14&quot;&gt;카카오 로그인 (실패한) 기록&lt;/a&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: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;client가 kakao server에게 인가 코드를 요청한다.&lt;/li&gt;
&lt;li&gt;이때 인가 코드는 리다이렉트 url에 쿼리로 전달된다.&lt;br /&gt;리다이렉트 페이지를 하나 만들어놓고 거기서 코드를 받아 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;인가코드를 담아 server로 보낸다. &lt;br /&gt;server는 그 코드를 kakao server로 보내 카카오의 토큰을 발급받는다.&lt;/li&gt;
&lt;li&gt;서버에서 따로 토큰을 발급해 client로 응답을 준다. 자체적으로 유저 정보를 갖고 있기 위함.&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;현재 모노레포를 사용하고 있다. 어드민 레포와 티켓 레포 둘로 나뉘어져 있지만 두 서비스간 이동이 있어도 로그인은 계속 유지되어야 했다. accessToken은 항상 recoil에서 atom으로 관리하고, refreshToken만 cookie에 저장하도록 했다. 새로고침을 하거나 처음 페이지에 들어갈때마다 리프레쉬 로직이 돌아 매번 새로운 accessToken을 받아온다. 하지만 클라이언트에서 렌더링될때 리프레쉬를 해서 보여준다면 약간의 딜레이동안 로그인이 풀린 것처럼 보일 수 있다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-22 오후 3.31.50.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c768C7/btrWVKst7Ut/ejQrxeVkxMkH0EnEAJ7ad0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c768C7/btrWVKst7Ut/ejQrxeVkxMkH0EnEAJ7ad0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c768C7/btrWVKst7Ut/ejQrxeVkxMkH0EnEAJ7ad0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc768C7%2FbtrWVKst7Ut%2FejQrxeVkxMkH0EnEAJ7ad0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;389&quot; data-filename=&quot;스크린샷 2023-01-22 오후 3.31.50.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 서버에서 렌더링된 페이지에서부터 로그인 정보가 보여지도록 하고 싶었다. 어드민 서비스로 이동하더라도 로그인이 자연스럽게 유지되는 것 처럼 보일 것 같았다. &lt;code&gt;getInitialProps&lt;/code&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;pages/_app.tsx&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;MyApp.getInitialProps = async (context: AppContext) =&amp;gt; {
  const { ctx, Component } = context;
  const refreshToken = cookies(ctx).refreshToken;
  let pageProps = {};
  let loginData: OauthLoginResponse | null;
  try {
    const response = await AuthApi.REFRESH(refreshToken!);
    loginData = response;
    ctx.res?.setHeader(
      'set-cookie',
      `refreshToken=${response.refreshToken}; path=/; max-age=${response.refreshTokenAge}`,
    );
  } catch (err: any) {
    loginData = null;
  }

  if (Component.getInitialProps) {
    // Component의 context로 ctx를 넣어주자
    pageProps = await Component.getInitialProps(ctx);
  }
  // return한 값은 해당 컴포넌트의 props로 들어가게 됩니다.
  return { pageProps, loginData };
};

export default MyApp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;refresh 요청에는 refreshToken을 담아서 보낸다. 서버에서는 &lt;code&gt;window&lt;/code&gt;와 &lt;code&gt;document&lt;/code&gt; 객체를 사용할 수 없기 때문에 로컬스토리지와 &lt;code&gt;document.cookie&lt;/code&gt;에서 토큰을 가져오는 방식은 쓸 수 없다. 대신 요청의 쿠키에 들어있는 토큰을 빼오면 서버 사이드에서도 쿠키를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getInitialProps&lt;/code&gt;는 &lt;code&gt;context&lt;/code&gt;라는 인자를 받는다. &lt;code&gt;context&lt;/code&gt; 안에는 &lt;code&gt;req&lt;/code&gt; (HTTP 요청 객체)와 &lt;code&gt;res&lt;/code&gt; (응답 객체)가 있다. 이를 이용해 서버 사이드에서 cookie를 다룰 수 있다. &lt;code&gt;next-cookies&lt;/code&gt; 라이브러리를 사용해 &lt;b&gt;ctx.req에 담긴 쿠키&lt;/b&gt;를 쉽게 문자열로 파싱해 가져왔다. refresh로 받아온 데이터들은 컴포넌트의 prop으로 같이 넘겨줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;refresh 후에 새로 발급받은 토큰들도 다시 클라이언트의 쿠키로 껴주는 작업도 필요하다. &lt;b&gt;ctx.res의 헤더에 쿠키를 세팅&lt;/b&gt;해준다. 이 부분이 없다면 쿠키가 새 토큰으로 업데이트되지 않고 옛날거가 그대로 남아있게 될것이다. refresh 요청이 클라이언트에서 보낸게 아니라 서버에서 보내서 서버로 받은 것이기 때문.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 쉽게 정리하자면, &lt;b&gt;페이지 요청에 담긴 쿠키를 빼와&lt;/b&gt;서 refresh api를 호출하는데 사용하고, 그 새로 받아온 토큰들을 &lt;b&gt;페이지 응답의 헤더에 다시 껴서 브라우저로&lt;/b&gt; 보내주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;interface MyAppProps extends AppProps {
  loginData: OauthLoginResponse | null;
}

function MyApp({ Component, pageProps, loginData }: MyAppProps) {
  const initializer = useMemo(
    () =&amp;gt;
      ({ set }: MutableSnapshot) =&amp;gt; {
        if (loginData) {
          const auth = {
            userProfile: loginData.userProfile,
            accessToken: loginData.accessToken,
            isAuthenticated: true,
            callbackUrl: (getCookie('redirectUrl') as string) || '/',
          };
          set(authState, auth);
        }
      },
    [loginData],
  );


  return (
  &amp;lt;...생략&amp;gt;
      &amp;lt;RecoilRoot initializeState={initializer}&amp;gt;
        // ...생략
      &amp;lt;/RecoilRoot&amp;gt;
    &amp;lt;/...생략&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Page로 넘겨준 loginData를 컴포넌트에서 위 코드와 같이 받을 수 있다. 받아온 정보는 리코일의 &lt;a href=&quot;https://recoiljs.org/docs/api-reference/core/RecoilRoot/&quot;&gt;initializeState&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;&lt;b&gt;개발환경일때 쿠키 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;staging 서브도메인에 띄워져 있는 서버를 가지고 로컬에서 클라이언트 개발을 진행하고 있다. 서버(staging.dudoong.com)와 로컬 개발환경(localhost)이 도메인이 다르기 때문에 쿠키가 제대로 세팅이 되지 않은 것처럼 보였다. 그냥 평소처럼 클라이언트에서 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;하지만 서버 사이드에서 refresh api를 호출할 때 문제가 생겼다. 직접 &lt;code&gt;ctx.req&lt;/code&gt;에서 쿠키를 가져와 세팅해주었기 때문이다. 이 때는 같은 도메인이 아닌 쿠키는 보여지지 않는다. 따라서 개발환경에서 로그인을 했을 때 수행할 작업을 따로 넣어주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const setCredentials = (refresh: OauthLoginResponse) =&amp;gt; {
  axiosPrivate.defaults.headers.common[
    'Authorization'
  ] = `Bearer ${refresh.accessToken}`;

  // 개발환경에서 로컬호스트로 쿠키 직접 넣어주기 위함
  if (process.env.NODE_ENV === 'development') {
    setCookie('accessToken', refresh.accessToken, {
      maxAge: refresh.accessTokenAge,
    });
    setCookie('refreshToken', refresh.refreshToken, {
      maxAge: refresh.refreshTokenAge,
    });
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. getInitialProps를 사용해도 되는가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서를 포함해 많은 글에서 &lt;code&gt;getInitialProps&lt;/code&gt; 사용을 권하지 않고 있다. 두둥에서 처음 Next를 사용하면서 공부할때 물론 문서에서 보고 지나갔던 글이었다. 그럼에도 &lt;code&gt;getInitialProps&lt;/code&gt;를 사용한건 처음 서비스에 진입할때 무조건 리프레쉬 로직을 돌아 로그인 정보를 받아와야 했기 때문이다. 서비스의 어떤 페이지에서 새로고침했을때도 항상 로그인이 유지되어야 했다.&lt;/p&gt;
&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;a. 자동 정적 최적화(Automatic Static Optimization) 불가&lt;/b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next는 빌드타임에 &lt;code&gt;getInitialProps&lt;/code&gt;를 사용하지 않는 페이지를 자동으로 pre-render 한다. 다른말로 하면, &lt;code&gt;_app.tsx&lt;/code&gt;에서 전역으로 &lt;code&gt;getInitialProps&lt;/code&gt;를 적용하게 되면, 모든 페이지가 무조건 서버 사이드 렌더링이 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;&amp;lambda;  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 시에 터미널에 나오는 말이다. pre-render 한다는 것은 정적 HTML파일로 생성되었다는 뜻이다. 실제로 &lt;code&gt;.next/server/static&lt;/code&gt; 폴더에 생성된 파일들을 확인해보면 오른쪽의 경우에 정적 HTML파일이 생성된 것을 볼 수 있다 (해당 경로에는 서버에서 사용되는 파일들이 생성된다). &lt;code&gt;getInitialProps&lt;/code&gt;가 있는 페이지는 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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 34400.png&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mD5iE/btrWQJCbvhY/iU3WwbKhU23udsjccEGuyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mD5iE/btrWQJCbvhY/iU3WwbKhU23udsjccEGuyK/img.png&quot; data-alt=&quot;(왼) _app.tsx에서 getInitialProps 사용 / (오) 사용하지 않음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mD5iE/btrWQJCbvhY/iU3WwbKhU23udsjccEGuyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmD5iE%2FbtrWQJCbvhY%2FiU3WwbKhU23udsjccEGuyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;763&quot; height=&quot;683&quot; data-filename=&quot;Group 34400.png&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(왼) _app.tsx에서 getInitialProps 사용 / (오) 사용하지 않음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 랜딩페이지와 공연 상세페이지와 같이 정적으로 보여줄 수 있는 페이지까지도 항상 SSR을 거쳐야 한다.&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;b. 페이지 이동마다 getInitialProps 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;getInitialProps&lt;/code&gt;는 클라이언트 사이드와 서버 사이드 모두에서 동작한다. 기본적으로 서버 사이드에서 패칭한 데이터를 pageProps로 넘겨 렌더링하는 역할을 한다. 하지만 &lt;code&gt;next/link&lt;/code&gt;나 &lt;code&gt;next/router&lt;/code&gt;를 이용해 페이지를 이동할 때, 클라이언트 사이드로 &lt;code&gt;getInitialProps&lt;/code&gt;가 내려와 작동하게 된다. 그렇게 되면 해당 로직이 불필요하게 앱의 번들에 포함되어 성능에 저하가 생길 수 있다. Next 9버전 이후 &lt;code&gt;getServerSideProps&lt;/code&gt;와 &lt;code&gt;getStaticProps&lt;/code&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-22 오후 7.29.17.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;238&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ulzYx/btrWVK0tuhL/7hFe2s4Y5fWyFkKydA6s00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ulzYx/btrWVK0tuhL/7hFe2s4Y5fWyFkKydA6s00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ulzYx/btrWVK0tuhL/7hFe2s4Y5fWyFkKydA6s00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FulzYx%2FbtrWVK0tuhL%2F7hFe2s4Y5fWyFkKydA6s00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1099&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2023-01-22 오후 7.29.17.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔을 찍고 페이지를 이리저리 이동해보았다. 첫 렌더링시에는 서버에서 실행되고, 그 이후론 브라우저의 콘솔에서 보여진다. 매 페이지 이동마다 &lt;code&gt;getInitialProps&lt;/code&gt;가 클라이언트 사이드에서 수행된다는 뜻. &lt;code&gt;getServerSideProps&lt;/code&gt;를 사용하는 페이지(주문,결제)가 렌더링될때는 &lt;code&gt;getInitialProps&lt;/code&gt;가 먼저 실행된 후에 &lt;code&gt;getServerSideProps&lt;/code&gt;가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 recoil에 &lt;code&gt;initializeState&lt;/code&gt;를 넣는 로직은 라이브러리 내에서 최적화가 되어있는지 최초 1회 외에는 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능은 그렇다치고, 매 페이지 이동마다 &lt;code&gt;getInitialProps&lt;/code&gt;가 실행되어 리프레쉬 요청을 보내게 되는건 내 처음 의도와 전혀 맞지 않았다. 딱 처음 한번만 되어야하는데... 자꾸 터미널 콘솔에 이상한게 찍히길래 이게 뭐지 했었는데 이게 그 이유였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1674450038042&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  try {
    if (ctx.req) {
      const response = await AuthApi.REFRESH(refreshToken!);
      console.log(response.refreshToken);
      loginData = response;
      ctx.res?.setHeader(
        'set-cookie',
        `refreshToken=${response.refreshToken}; path=/; max-age=${response.refreshTokenAge}`,
      );
    } else throw new Error('isClient');
  } catch (err: any) {
    loginData = null;
  }&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;ctx.req는 서버 사이드에 있기 때문에, 최초 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;a href=&quot;https://im-designloper.tistory.com/111&quot;&gt;https://im-designloper.tistory.com/111&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.howdy-mj.me/next/hydrate&quot;&gt;https://www.howdy-mj.me/next/hydrate&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeoulcoding.me/266&quot;&gt;https://yeoulcoding.me/266&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/104</guid>
      <comments>https://9yujin.tistory.com/104#entry104comment</comments>
      <pubDate>Sun, 22 Jan 2023 20:28:20 +0900</pubDate>
    </item>
    <item>
      <title>IT 커뮤니티 넥스터즈 22기 - 서류, 면접, 합격 후기</title>
      <link>https://9yujin.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;의도한건 아니지만 어쩌다 보니 이 카테고리에 쓰는 글들은 항상 '-고'로 끝나는 제목을 갖고 있다. 그래서 이번 글에도 비슷하게 가려다가 '회고'라는 말을 쓰기엔 아직 뭐가 전혀 없어서 지웠다. '후기'로 가겠음. CEOS 지원 후기를 거의 두 기수가 끝나갈때 즈음 작성했는데, 기억이 거의 없다시피 해서 좀 힘들었다. 그래서 이번엔 미리미리 쓴다. 넥스터즈 22기 프론트엔드 서류 지원, 면접, 합격, 그리고 두번의 세션 후기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJb4A/btrWzRmRe8w/LeUIL25IVio16hL2MMoxy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJb4A/btrWzRmRe8w/LeUIL25IVio16hL2MMoxy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJb4A/btrWzRmRe8w/LeUIL25IVio16hL2MMoxy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJb4A%2FbtrWzRmRe8w%2FLeUIL25IVio16hL2MMoxy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;325&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넥스터즈라는 동아리를 알게 된건 1년 전쯤이다. 작년 이맘때에 개발 공부를 시작하면서 여러 동아리들을 찾아보았었다. 아무런 포폴도 실력도 없었던 시기였기 때문에, 어떤 동아리들을 보아도 그림의 떡이다. 그 중에서도 넥스터즈는 완전 높아보였다. 블로그나 에타에 올라온 글들에서 '제일 들어가기 어렵다', '1티어다' 하는 말들을 언뜻 봤었다. 그런 티어는 누구 맘대로 정하는지는 모르겠지만 &lt;span style=&quot;background-color: #ffffff; color: #353535;&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;background-color: #ffffff; color: #353535;&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.00.12.png&quot; data-origin-width=&quot;2280&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9bOWD/btrWzWurabn/xY2KqtHXVpKoUa2I24lRik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9bOWD/btrWzWurabn/xY2KqtHXVpKoUa2I24lRik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9bOWD/btrWzWurabn/xY2KqtHXVpKoUa2I24lRik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9bOWD%2FbtrWzWurabn%2FxY2KqtHXVpKoUa2I24lRik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2280&quot; height=&quot;296&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.00.12.png&quot; data-origin-width=&quot;2280&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 1년동안 나름 열심히 살아왔다. 넥스터즈 22기 모집이 열렸고, 방학 때 한번 빡세게 살아보자는 마음가짐으로 지원을 했다. &lt;span style=&quot;background-color: #ffffff; color: #353535;&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;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353535;&quot;&gt;1. 서류&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353535;&quot;&gt;지원동기.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #353535;&quot;&gt;바로 위에서 말한 내용을 위주로 적었다. 넥스터즈에 지원하는 학생들이 말하는 지원동기가 백이면 백 '현직자와의 협업과 폭풍성장'일 것 같아서 조금 더 생각해보았다. 홈페이지에서 단순히 프로젝트만 끝내고 마무리되는 동아리가 아니라 그 이후에도 공모전, 스터디 등이 자율적으로 진행되는 커뮤니티로 홍보되고 있다는게 마음에 들었다. 개발을 공부하는데 있어서,&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt; 인적인 인프라와 네트워킹이 중요함&lt;/span&gt;을 느끼고 공감하고 있다는 점을 추가로 붙여 넣었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내 강점.&amp;nbsp;&lt;/b&gt;사실 가장 자신감없이 적었던 문항이다. 기가 죽어있는 상태로 지원했기 때문에.. 고민하다가 두가지로 정리해서 작성했다. 프론트엔드 개발자로서 항상 더 나은 UX에 대해 고민하며, 디테일에 욕심이 있다는 점. 그리고 블로그를 작성할 때 내가 사용했던 기술과 코드를 나열하는데 그치지 않고, 고민하고 느꼈던 과정을 남기고 공유하려고 한다는 점. 이 두가지 강점이 넥스터즈라는 큰 규모의 커뮤니티 내에서 좋은 영향을 주고 받을 수 있을거라고 생각했다.&lt;/p&gt;
&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;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;span style=&quot;color: #9d9d9d;&quot;&gt;일단 내가 보기엔 그런데, 남이 보기엔 어떨지 모르겠네. &lt;/span&gt;서류심사에 직접적인 연관은 없더라도, 평소에 했던 고민과 삽질을 기록해온게 많은 면에서 도움이 된다. 포트폴리오와 블로그가 내 지원서 내용에 어느정도 증거가 될 수 있다. 또 지원서를 작성할 때도 내 블로그를 다시 읽으면서 쓰니 한결 수월했다. 다른 잘하시는 분들의 블로그를 보면 초라해지는 내용이지만, 그런건 별로 중요하지 않은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.55.29.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfc9JG/btrWDbDQ7kf/1UCeKSsdqiwIPbfR1ORQ2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfc9JG/btrWDbDQ7kf/1UCeKSsdqiwIPbfR1ORQ2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfc9JG/btrWDbDQ7kf/1UCeKSsdqiwIPbfR1ORQ2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfc9JG%2FbtrWDbDQ7kf%2F1UCeKSsdqiwIPbfR1ORQ2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1388&quot; height=&quot;604&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.55.29.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12시가 되기 직전에 서류 합격 메일이 왔다. 얼마나 많은 지원자들이 몰렸을지.. 벌써부터 면접이 긴장되는 메일이었다. 자기 입으로 최고라고 말할 수 있는 근거 있는 자신감. 크으으.. 코오오.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 면접&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.57.08.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3vAT6/btrWzUQ6Jl6/fcBKiKEcrUBuOh8Z5JQDR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3vAT6/btrWzUQ6Jl6/fcBKiKEcrUBuOh8Z5JQDR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3vAT6/btrWzUQ6Jl6/fcBKiKEcrUBuOh8Z5JQDR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3vAT6%2FbtrWzUQ6Jl6%2FfcBKiKEcrUBuOh8Z5JQDR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1388&quot; height=&quot;348&quot; data-filename=&quot;스크린샷 2023-01-18 오후 4.57.08.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예고된대로 20분정도의 시간이 딱 정해져서 공지된다. &lt;span&gt;줌으로 비대면 인터뷰를 진행했다.&lt;span&gt; 이전에 봤던 블로그에선 강남쪽에 있는 사무실에서 대면으로 한대서 기대 반 긴장 반 갖고 있었지만..운영진분들과 이전 기수 프론트엔드, 총 세네분 정도가 면접관+서기로 들어와계셨다. 나 포함 두명이 그룹으로 함께 인터뷰를 했다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;기술면접이 꽤 깊게 들어온다고 들어서 준비를 많이 해갔다. 기술 관련 질문이 있긴 했지만 언제나 그렇듯이 준비한 내용은 물어봐주질 않는다. 기술 보다는 인성 위주의 면접이었다. 나중에 듣기로는 대학생과 현직자와 질문 수준에서 어느정도 차이를 두는 경우도 있다고. 그 덕분인가 싶다. 같이 면접 본 사람도 나와 같은 대학생이었다. 비슷한 분류로 그룹을 묶는 것 같다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커뮤니케이션에 대해서 지원서에 작성해주었는데, 나만의 노하우가 있는지.&amp;nbsp;&lt;/b&gt;첫번째 질문과 똑같은 맥락에서 답변했다. 역시 내 역할에 맞게 행동한다. 스터디원, 일반 멤버로 활동할 때와 운영진으로 활동할 때 사람들을 어떻게 대하려고 노력하는지. 답변을 하고 나니 짧은 것 같아서 MBTI를 곁들어서 이야기했는데, 지금 돌아보니 굉장히 횡설수설한 것 같다. INFJ의 가장 큰 특징인 '페르소나'를 언급했다. 뭐 나름 일리 있는 답변이었다고 생각한다ㅋㅋ&lt;/p&gt;
&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;해달라는 질문으로 다시 해주셨다.&amp;nbsp; Pick과 Partial, Omit 등을 직접 프로젝트에서 사용했던 경험을 예로 들어 말했다. 휴.&lt;/p&gt;
&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;b&gt;게시판 조회수 업데이트를 리액트 쿼리를 이용할 때 어떻게 구현할 생각인지.&amp;nbsp;&lt;/b&gt;이런 유형의 질문도 처음 받아봐서 조금 당황했다. invalidateQueries를 사용해서 게시글을 클릭할 때마다 새로 조회수 데이터를 패칭하겠다.. 라고 이야기했다. 면접이 끝나자마자 되게 별로인 방법이라고 생각이 들어서 아쉬웠다. 조회수가 엄격하게 실시간 데이터로 보여져야할 필요는 없을 것 같아서, 그냥 클라단에서만 +1 업데이트되는게 불필요한 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;&lt;b&gt;3. 최종 합격&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오후 5.58.00.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHrjeJ/btrWD4qTUAZ/2vpzbzYJCJOXgeZIfKkba1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHrjeJ/btrWD4qTUAZ/2vpzbzYJCJOXgeZIfKkba1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHrjeJ/btrWD4qTUAZ/2vpzbzYJCJOXgeZIfKkba1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHrjeJ%2FbtrWD4qTUAZ%2F2vpzbzYJCJOXgeZIfKkba1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1388&quot; height=&quot;316&quot; data-filename=&quot;스크린샷 2023-01-18 오후 5.58.00.png&quot; data-origin-width=&quot;1388&quot; data-origin-height=&quot;316&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;글을 쓰는 지금은 팀빌딩이 완료되고 두번의 세션을 진행했다. 넥스터즈 운영진을 위한 출석 web 서비스를 개발하게 되었다. 처음엔 별로 관심이 없었다가 PM님의 PR 슬라이드를 보고 마음이 바뀌었다. 개발을 하면서 얻어갈 수 있는 것이 명확해서 좋았다. 지금은 초기 세팅을 하고 있는 중이다. 우리 팀도 그렇고 다른 팀들도 그렇고 현업에서는 yarn berry가 거의 기본적으로 쓰이는 것 같다. 같이 프론트하는 팀원분들은 현업에서 몇년차 되는 분들이라 초기 세팅할때는 뭔가.. 의견을 따라가게 된다. 터보레포와 Next를 사용하기로 했다. 넥스터즈 시작 전에 모노레포를 미리 경험해봐서 다행이다.&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;</description>
      <category>  글을 위한 글</category>
      <category>개발 동아리</category>
      <category>넥스터즈</category>
      <category>면접</category>
      <category>서류</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/103</guid>
      <comments>https://9yujin.tistory.com/103#entry103comment</comments>
      <pubDate>Wed, 18 Jan 2023 18:15:36 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 모노레포 구축 삽질기 (3) - CICD 배포, Docker, Github Actions</title>
      <link>https://9yujin.tistory.com/102</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-09 오후 4.35.49.png&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcu2Dp/btrVPHpKqc8/KXS1OGMiqAKU8qb1b38bZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcu2Dp/btrVPHpKqc8/KXS1OGMiqAKU8qb1b38bZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcu2Dp/btrVPHpKqc8/KXS1OGMiqAKU8qb1b38bZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcu2Dp%2FbtrVPHpKqc8%2FKXS1OGMiqAKU8qb1b38bZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2362&quot; height=&quot;1524&quot; data-filename=&quot;스크린샷 2023-01-09 오후 4.35.49.png&quot; data-origin-width=&quot;2362&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 한번에 끝날 줄 알고 dev 브랜치에 바로 테스트를 했다. 그 결과 아직 제대로 시작도 안했는데 커밋이 100개가 넘어가는 불상사가 생겼다. 어쨌든 모노레포 시리즈 마지막은 Docker와 Github Actions를 이용한 CI/CD 구축 삽질기. 별로 어려운 내용이 아니고, 그렇기에 거창한 정보 공유 목적은 결코 아니다. 하지만 나처럼 쌩판 처음 모노레포를 시도해보는 사람들이 쉽게 참고할 수 있을만한 글이 별로 없어 힘들었기 때문에 꾸역꾸역 쓰는 글.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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://9yujin.tistory.com/47&quot;&gt;1편&lt;/a&gt;, &lt;a href=&quot;https://9yujin.tistory.com/49&quot;&gt;2편&lt;/a&gt;) 크게 다를건 없지만, 거기서부터 한줄한줄씩 바꿔가는 과정이라고 보면 될 것 같음. 따지고 보면 한줄한줄씩 고쳐나가는 과정..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;  &quot;scripts&quot;: {
    // ...
    &quot;admin:build&quot;: &quot;yarn workspace admin build&quot;,
    &quot;ticket:build&quot;: &quot;yarn workspace ticket build&quot;,
    &quot;ticket:start&quot;: &quot;yarn workspace ticket start&quot;,
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 루트의 package.json에서 위의 스크립트를 추가한다. 워크스페이스에 있는 한 app의 package.json에 있는 스크립트를 실행할 수 있다.&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;Dockerfile.admin&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;FROM node:16 AS builder
# set working directory
WORKDIR /app
# install app dependencies
COPY package.json ./
COPY yarn.lock ./
# Installs all node packages
RUN npm install yarn --global --force
RUN yarn install --immutable --immutable-cache --check-cache

# Copies everything over to Docker environment
COPY . ./
RUN yarn install --immutable
RUN yarn admin:build

#Stage 2
#######################################
#pull the official nginx:1.19.0 base image
FROM nginx:1.19.0
COPY ./nginx/admin.conf /etc/nginx/conf.d/default.conf
# Remove default nginx static resources
RUN rm -rf ./usr/share/nginx/html/*
# Copies static resources from builder stage
COPY --from=builder /app/apps/admin/dist /usr/share/nginx/html/
EXPOSE 3100
ENTRYPOINT [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어드민 레포지토리에 대한 도커파일이다. 기존 9월공연 서비스에서 쓰던걸 토대로 바꾸다 보니 &lt;code&gt;yarn install&lt;/code&gt;을 두번에 나누어서 하게 되었는데, 지금 생각하니까 그럴 필요가 없었네.&lt;/p&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을 사용하기 때문에 npm에서 먼저 yarn을 설치한 후에 의존성 패키지들을 설치해준다. CI 환경에서는 yarn.lock파일을 생성하지 않고 업데이트가 필요한 경우 실패하도록 해주는게 안전하다 한다. &lt;code&gt;--frozen-lockfile&lt;/code&gt; 옵션으로 사용할 수 있었다. 이제는 그보다 &lt;code&gt;--immutable&lt;/code&gt; 옵션을 사용하라고 권장하고 있다. &lt;code&gt;--immutable-cache&lt;/code&gt;와 &lt;code&gt;--check-cache&lt;/code&gt; 옵션은 zero-install 환경에서 사용한다고 하는데,, 지금은 zero-install을 사용하지 않고 있으니까 수정해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 설치 후에는 위에 스크립트에 작성해놓았던 &lt;code&gt;yarn admin:build&lt;/code&gt; 명령으로 어드민 프로젝트를 빌드한다. 그 뒤론 nginx 설정파일의 경로 외에는 변경사항이 없다.&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;Dockerfile.ticket&lt;/b&gt;&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;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;FROM node:16-alpine AS builder
# set working directory
WORKDIR /app
# install app dependencies
COPY package.json ./
COPY yarn.lock ./
# Installs all node packages
RUN npm install yarn --global --force
RUN yarn install --immutable --immutable-cache --check-cache


# Copies everything over to Docker environment
COPY . ./
RUN rm -rf apps/admin

RUN yarn install
RUN yarn ticket:build
RUN rm -rf apps/ticket/.next/cache

#Stage 2
EXPOSE 3000
CMD [&quot;yarn&quot;, &quot;ticket:start&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 프로젝트 배포할때와 크게 다른건 없다. 다만 Nginx를 사용하지 않고 Next 서버를 실행시켜 사용한다. 모노레포 관련 세팅 삽질을 하면서 어딘가 꼬였는지, &lt;code&gt;yarn install&lt;/code&gt; 에 &lt;code&gt;--immutable&lt;/code&gt; 옵션을 주면 yarn.lock이 변경될 수 있다고 오류내고 끝내버리더라. 옵션을 제거해주어도 일단 빌드 잘되고 배포도 잘 되는것을 보고... 일단은 그렇게 했다. 모래 위에 건물을 짓는 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 딱 도커 이미지를 허브에 올렸더니 무려 750MB의 용량을 갖고 있었다. 이게 말이 되나?? 최대한 이미지의 크기를 줄여보려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node 이미지를 가져올 때 용량이 가장 작은 alpine 버전을 사용하도록 바꿨다. 이것 하나만으로 거의 200MB 가까이 줄어들었다. 추가로 어드민 관련 디렉토리를 지웠고, 빌드 후 나온 .next의 cache 파일들도 삭제하도록 했다. 그 결과 450MB정도로 줄일 수 있었다. 하지만 여전히 크다..! 원래 이런건가.&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;GitHub Actions&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액션 워크플로우 파일은 티켓과 어드민이 거의 동일하다. 중요하게 볼 부분은 태그 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오전 1.52.15.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MOVD8/btrWyOhEwrM/Tesoem7NJpLeFcq7mBwtD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MOVD8/btrWyOhEwrM/Tesoem7NJpLeFcq7mBwtD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MOVD8/btrWyOhEwrM/Tesoem7NJpLeFcq7mBwtD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMOVD8%2FbtrWyOhEwrM%2FTesoem7NJpLeFcq7mBwtD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1854&quot; height=&quot;924&quot; data-filename=&quot;스크린샷 2023-01-18 오전 1.52.15.png&quot; data-origin-width=&quot;1854&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브에서 새로운 태그를 달고 릴리즈를 하면 액션이 트리거되고 도커 이미지가 허브로 올라가도록 하는, 그런 흐름을 갖고 있다. 티켓과 어드민 레포 각각 별개로 실행되어야 한다. 릴리즈를 할때 태그명에 &lt;code&gt;'Ticket-\*'&lt;/code&gt;, &lt;code&gt;'Admin-\*'&lt;/code&gt;과 같이 앞에 레포명을 붙여서 올린다. 그럼 그 태그 명에 맞는 액션이 실행되도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;name: Build &amp;amp; Docker Push - Ticket
on:
  push:
    # branches: [dev]
    # paths: ['apps/ticket/pages/**']
    # Publish semver tags as releases.
    tags:
      - Ticket-v*.*.*

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

      # 도커 이미지에 env 파일 포함
      - name: Create .env file
        run: |
          touch .env
          echo &quot;${{ secrets.ENV_VARS_TICKET }}&quot; &amp;gt;&amp;gt; .env
      # 도커 이미지 버전 가져오기
      - name: Get the version
        id: get_version
        run: |
          RELEASE_VERSION_WITHOUT_V=&quot;$(cut -d'v' -f2 &amp;lt;&amp;lt;&amp;lt; ${GITHUB_REF#refs/*/})&quot;
          echo ::set-output name=VERSION::$RELEASE_VERSION_WITHOUT_V

      # 도커 빌드 관련 셋업
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1
      # 내 도커허브 로그인
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      # 도커이미지 빌드하고 허브로 푸쉬
      - name: Build and push
        uses: docker/build-push-action@v2
        with:
          context: .
          file: ./Dockerfile.ticket
          platforms: linux/amd64
          push: true
          tags: david0218/dudoong-ticket:${{ steps.get_version.outputs.VERSION }}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 부분은 깃허브에서 릴리즈된 버전을 가져오고, 'v'앞의 내용을 떼서 딱 'x.x.x' 부분만 태그로 달아준다는 것. 백엔드 팀장친구의 코드이다!! (이거 안쓰면 분명 뭐라할것같음ㅎㅋ)&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-01-18 오후 2.37.33.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PaFfs/btrWCdhU5K2/w68JMR52uioPAG2nWgfGGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PaFfs/btrWCdhU5K2/w68JMR52uioPAG2nWgfGGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PaFfs/btrWCdhU5K2/w68JMR52uioPAG2nWgfGGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPaFfs%2FbtrWCdhU5K2%2Fw68JMR52uioPAG2nWgfGGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1394&quot; height=&quot;510&quot; data-filename=&quot;스크린샷 2023-01-18 오후 2.37.33.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;510&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 삽질은 더 많이 했던 것 같은데, 나중에 기억 끄집어서 쓰려니까 빠진 부분이 많은 것 같다. 하쒸.&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/102</guid>
      <comments>https://9yujin.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 18 Jan 2023 14:42:26 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 모노레포 구축 삽질기 (2) - 프로젝트 세팅 with Next.js, Vite, storybook, emotion</title>
      <link>https://9yujin.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 시작하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;yarn set version berry
yarn init
yarn add -D typescript @types/node @types/react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디렉토리 하나를 파고 yarn 버전을 berry로 설정한 후에 package.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;yarn 관련 설정은 &lt;a href=&quot;https://9yujin.tistory.com/100#--%--Yarn%--Berry&quot;&gt;이전 글&lt;/a&gt;에서 짤막하게 적어놓았다. 다시 간략하게 적자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// ./package.json
{
  &quot;name&quot;: &quot;dudoong-front&quot;,
  &quot;packageManager&quot;: &quot;yarn@3.3.0&quot;,
  &quot;private&quot;: true,
  &quot;workspaces&quot;: {
    &quot;packages&quot;: [
      &quot;apps/*&quot;,
      &quot;shared/*&quot;
    ]
  },
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트의 package.json에서 workspaces를 추가해준다. workspaces에 객체가 아니라 바로 배열을 넣어줄 수 있는데, 디폴트로 packages 설정으로 들어가게 된다. 객체로 작성해준다면 &quot;packages&quot; 외에도 &quot;nohoist&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;eslint와 tsc 설정을 추가해준다. 둘 다 --init으로 초기세팅을 시작할 수 있다.&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;.tsconfig&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noFallthroughCasesInSwitch&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;jsxImportSource&quot;: &quot;@emotion/react&quot;
  },
  &quot;references&quot;: [
    {
      &quot;path&quot;: &quot;apps/ticket&quot;
    },
    {
      &quot;path&quot;: &quot;apps/admin&quot;
    },
    {
      &quot;path&quot;: &quot;shared/ui&quot;
    },
    {
      &quot;path&quot;: &quot;shared/utils&quot;
    }
  ],
  &quot;include&quot;: [],
  &quot;exclude&quot;: [&quot;apps/**/dist/**&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tsconfig의 경우 &lt;code&gt;references&lt;/code&gt;를 추가해준다. 각 프로젝트의 tsconfig에 루트의 설정을 오버라이드할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 프로젝트 생성&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;├── apps
│   ├── admin # 어드민 서비스. react + vite
│   └── ticket # 티켓 서비스. nextjs
└── shared
    ├── ui # 공유 컴포넌트, theme. react + storybook
    └── utils # 유틸 함수, 공용 훅, 타입 등. react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 네개의 서비스를 만든다. 일반 사용자를 위한 티켓 서비스와 호스트를 위한 어드민 서비스. 티켓 서비스는 Nextjs로, 어드민 서비스는 vite로 처음 세팅해주었다. SEO와 함께 빠른 로딩 등 사용자 경험이 더 중요해졌기 때문에 Nextjs를 선택했다. 같은 팀원 중에서 Nextjs를 새로 공부하면서 프로젝트가 어려운 친구들이 있어서 어드민 서비스는 원래와 같이 리액트를 사용한다. 왜 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;shared 디렉토리에 있는 ui와 utils 서비스는 일반 리액트를 사용한다. 별도의 빌드가 필요없는 것들이고, vite에서 스토리북을 세팅하고 배포하는게 정보가 별로 없어서 일반적인 방법을 선택했다. 해당 디렉토리로 가서 각각 cra cna 등의 탬플릿으로 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;//shared/utils/package.json
{
  &quot;name&quot;: &quot;@dudoong/utils&quot;,
  &quot;main&quot;: &quot;src/index.ts&quot;,
  &quot;types&quot;: &quot;src/index.ts&quot;,
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ui와 util같은 shared 레포지토리들의 package.json에는 main과 types 필드를 설정해준다. 패키지에 대한 엔트리포인트를 루트가 아닌 &lt;code&gt;src/index.js&lt;/code&gt;로 바꿀 수 있다. 패키지 이름을 &lt;code&gt;@dudoong/&lt;/code&gt;으로 시작하게 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;  // apps/ticket/package.json
  &quot;dependencies&quot;: {
    &quot;@dudoong/ui&quot;: &quot;workspace:shared/ui&quot;,
    &quot;@dudoong/utils&quot;: &quot;workspace:shared/utils&quot;,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apps에서는 dependencies에서 똑같은 이름으로 그 패키지들을 가져다 사용할 수 있다. 절대경로를 설정할 때 @로 시작하는 것과 똑같은 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 그리고 여러가지들&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;transpile&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포 내부에서 만든 컴포넌트를 가져오려고 보니 unexpected token 문제가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Module parse failed: Unexpected token (1:21)
You may need an appropriate loader to handle this file type,
currently no loaders are configured to process this file.
See https://webpack.js.org/concepts#loaders&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;컴포넌트들을 Next 안으로 가져와서 사용하기 위해 별도의 트랜스파일링이 필요하다. yarn에서 &lt;code&gt;next-transpile-modules&lt;/code&gt; 플러그인을 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;const withTM = require('next-transpile-modules')([
  '@dudoong/ui',
  '@dudoong/utils',
]);
module.exports = withTM({
  // Any additional config for next goes in here
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next.config.js를 이렇게 수정한다. &lt;a href=&quot;https://turbo.build/repo/docs/handbook/sharing-code/internal-packages#6-configuring-your-app&quot;&gt;터보레포 문서&lt;/a&gt;에서는 &lt;code&gt;transpilePackages&lt;/code&gt;라는 것을 사용하라고 나와있다. Next13.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;처음 프로젝트를 구축할 때 이 문서에서 vite는 별도의 트랜스파일 설정이 필요없다는 것을 보았다. 그래서 admin 페이지는 vite를 사용했는데, 생각해보니까 그냥 리액트를 사용했어도 상관 없었을 것 같다. 그래도 vite를 처음 사용해봤는데, 빌드도 엄청 빠르고 별도의 eject나 craco 없이도 &lt;code&gt;vite.config.ts&lt;/code&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;emotion&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;llvm&quot;&gt;&lt;code&gt;yarn add @emotion/react @emotion/styled&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;a href=&quot;https://emotion.sh/docs/css-prop#jsx-pragma&quot;&gt;CSS Prop 관련 오류&lt;/a&gt;, 그리고 babel-plugin 등의 설정을 같이 해준다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;//emotion.d.ts

declare module '@emotion/react' {
  export interface Theme {
    palette: TypeOfPalette;
    typo: TypeOfTypo;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ThemeProvider를 사용하면서 emotion 타입 모듈을 직접 선언해서 사용해야 했다. theme과 global style 관련 파일들은 &lt;code&gt;shared/ui&lt;/code&gt;에서 작업하고 apps로 임포트하고 사용하고 있는데, ui 레포지토리 외부에서는 &lt;code&gt;emotion.d.ts&lt;/code&gt;의 경로를 가져오지 못해 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// tsconfig.json
&quot;include&quot;: [&quot;next-env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;, &quot;../../**/*.d.ts&quot;],&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 레포지토리의 tsconfig에서 직접 해당 경로의 d.ts 파일을 include하도록 설정해주어서 해결했다.&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 설정&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;yarn dlx sb init --builder webpack5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;storybook을 다운받는다. webpack5를 사용하기 위해선 별도의 설정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt; //...
&quot;resolutions&quot;: {
    &quot;webpack&quot;: &quot;5&quot;,
    &quot;@storybook/core-common/webpack&quot;: &quot;^5&quot;,
    &quot;@storybook/core-server/webpack&quot;: &quot;^5&quot;,
    &quot;@storybook/react/webpack&quot;: &quot;^5&quot;
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트의 package.json에서 resolution에 추가를 해준다. 외부 라이브러리가 의존하고 있는 다른 패키지들의 버전을 고정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;del&gt;근데 왜 webpack5를 사용하는거지... 솔직하게 말하면 우아한 블로그에서 5를 사용하길래...&lt;/del&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;code-workspace&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 루트로 들어갔을때 갖가지 설정파일들이 많아지고, 불필요한 디렉토리들 때문에 작업하기 불편하게 되었다. 현재 내가 작업하고있는 부분은 admin인데도 ticket 부분까지 탐색기에 쫙 있을 필요는 없다.&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;//set-admin.code-workspaces
{
  &quot;folders&quot;: [
    {
      &quot;path&quot;: &quot;apps/admin&quot;
    },
    {
      &quot;path&quot;: &quot;shared&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;code&gt;.code-workspaces&lt;/code&gt; 파일을 루트에 만들어주면, vscode에서 작업공간을 설정해 들어갈 수 있다.&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;스크린샷 2023-01-08 오후 10.09.52.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7WYkl/btrVzZF3ckv/XDc4BDsa1WH8gS2ajL4nDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7WYkl/btrVzZF3ckv/XDc4BDsa1WH8gS2ajL4nDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7WYkl/btrVzZF3ckv/XDc4BDsa1WH8gS2ajL4nDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7WYkl%2FbtrVzZF3ckv%2FXDc4BDsa1WH8gS2ajL4nDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;317&quot; height=&quot;337&quot; data-filename=&quot;스크린샷 2023-01-08 오후 10.09.52.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;337&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 (3편)에서는 모노레포 서비스 배포와 CICD 구축 삽질기로!&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/101</guid>
      <comments>https://9yujin.tistory.com/101#entry101comment</comments>
      <pubDate>Sun, 8 Jan 2023 21:57:29 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 모노레포 구축 삽질기 (1) - 도입 이유, yarn workspaces, berry</title>
      <link>https://9yujin.tistory.com/100</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;고스락에서 프로젝트를 세번째 이어오면서 효율적인 구조를 계속 고민하고 있다. 이번에 새로운 프로젝트, 두둥을 시작하면서 모노레포를 도입하게 되었다. 이번 포스팅은 모노레포 개발 환경과 CICD를 구축하면서 공부했던 것들과 삽질에 대한 기록이다.&lt;/p&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) 도입 이유, yarn workspaces, berry&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;a href=&quot;http://9yujin.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;모노레포 환경 세팅 - vite, nextjs, storybook, emotion&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;a href=&quot;http://9yujin.tistory.com/102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CI/CD - docker, github actions 세팅&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 도입 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 처음 프로젝트를 시작할때 프론트쪽을 3개의 레포로 구성했다. 유저 프론트 사이트, 어드민 사이트, 그리고 컴포넌트로 분리했다. 스토리북 레포지토리의 컴포넌트들은 npm 패키지로 배포해서 프론트와 어드민에서 공통으로 사용할 수 있도록 했다. 이런 &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-31 오후 5.19.54.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhaa3/btrUVaBMmrH/jqpOf51FX04KXKVZn9b1a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhaa3/btrUVaBMmrH/jqpOf51FX04KXKVZn9b1a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhaa3/btrUVaBMmrH/jqpOf51FX04KXKVZn9b1a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmhaa3%2FbtrUVaBMmrH%2FjqpOf51FX04KXKVZn9b1a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;242&quot; data-filename=&quot;스크린샷 2022-12-31 오후 5.19.54.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;423&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;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;&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;기존에 어드민은 다른 친구들에게 맡기고 프론트만 신경써서 작업했다. 하지만 새 서비스를 기획하면서 유저 페이지보다 주 기능으로 여길 정도로 어드민 페이지의 중요성이 커졌다. 대충 antd로 던졌던 디자인에서 유저 페이지와 일관된 디자인으로 다시 작업했다. 같이 협업하는 친구들이 커밋한 작업들도 같이 파악하고 리뷰할 수 있어야 했다. 모든 서비스를 하나의 레포에서 관리할 수 있다면 협업에도 큰 도움이 될 수 있다.&lt;/p&gt;
&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;b&gt;2. Yarn workspaces&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모노레포 관리를 위한 도구로 yarn workspaces를 선택했다. 우리나라에서 가장 레퍼런스가 많고 문서도 잘되어 있어서 사용하게 되었다. workspace라는 것을 내가 이해하고 사용하고 있는대로 설명하면 다음과 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;glsl&quot;&gt;&lt;code&gt;├── apps
│   ├── admin # 어드민 서비스. react + vite
│   └── ticket # 티켓 서비스. nextjs
└── shared
    ├── ui # 공유 컴포넌트, theme. react + storybook
    └── utils # 유틸 함수, 공용 훅, 타입 등. react&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 이렇게 구성되어 있을 때, shared 디렉토리에 있는 패키지들은 apps에 있는 서비스로 불러와 사용될 수 있어야 한다. 즉 같은 '작업공간'안에 있고, ui와 utils가 의존하고 있는 패키지들도 함께 불러와 사용된다. yarn berry에서는 이런 기능을 yarn workspaces라는 이름으로 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// ./package.json
{
  &quot;name&quot;: &quot;dudoong-front&quot;,
  &quot;packageManager&quot;: &quot;yarn@3.3.0&quot;,
  &quot;private&quot;: true,
  &quot;workspaces&quot;: {
    &quot;packages&quot;: [
      &quot;apps/*&quot;,
      &quot;shared/*&quot;
    ]
  },
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트의 &lt;code&gt;package.json&lt;/code&gt;에서 이렇게 workspace를 명시해준다. apps에 있는 모든 패키지와 shared에 있는 모든 패키지들이 workspace 안에 들어가있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// shared/ui/package.json
{
  &quot;name&quot;: &quot;@dudoong/ui&quot;,
  &quot;main&quot;: &quot;src/index.ts&quot;,
  &quot;types&quot;: &quot;src/index.ts&quot;,
  //...

}

// apps/admin/package.json
{
  &quot;name&quot;: &quot;admin&quot;,
  &quot;private&quot;: true,
  &quot;version&quot;: &quot;0.0.0&quot;,
  &quot;type&quot;: &quot;module&quot;,
  &quot;dependencies&quot;: {
    &quot;@dudoong/ui&quot;: &quot;workspace:shared/ui&quot;,
    &quot;@dudoong/utils&quot;: &quot;workspace:shared/utils&quot;,
    // ...
  }
  //...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 shared 레포와 apps 레포의 package.json에서 dependencies에 명시를 함으로서 workspace를 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;yarn install&lt;/code&gt;을 하면 루트의 package.json에서 하위 디렉토리의 package.json에 명시된 의존성까지 한번에 설치할 수 있다. 이 과정에서 하위 패키지들에 설치된 패키지들의 중복을 제거한다. &lt;b&gt;루트의 node_modules로 호이스팅&lt;/b&gt;된 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvSwxx/btrVeVpyqnL/S2xkIcM3evbfsD5EF8y82k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvSwxx/btrVeVpyqnL/S2xkIcM3evbfsD5EF8y82k/img.jpg&quot; data-alt=&quot;https://classic.yarnpkg.com/blog/2018/02/15/nohoist/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvSwxx/btrVeVpyqnL/S2xkIcM3evbfsD5EF8y82k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvSwxx%2FbtrVeVpyqnL%2FS2xkIcM3evbfsD5EF8y82k%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;245&quot; data-origin-width=&quot;1087&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://classic.yarnpkg.com/blog/2018/02/15/nohoist/&lt;/figcaption&gt;
&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Yarn Berry&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn workspaces를 사용하면서 yarn berry의 pnp모드를 처음 도입해보려고 했다. 결론부터 말하자면 잘 안됐다. tsconfig문제인지 뭔지 모듈들의 경로를 제대로 못찾아왔다. 일반 cra환경과 터보레포 환경에서 똑같이 yarn berry 설정을 해봤을땐 문제 없이 잘 작동한것을 보아서, 내 프로젝트의 tsconfig 같은 설정 문제인 것 같다. 거의 이틀정도를 붙잡고 있었는데도 해결하지 못해서 일단 pnp모드는 제외하기로 했다. pnp를 지원하지 않는 라이브러리들도 몇 있고 next13에서 pnp로 인한 이슈가 많이 생겼다는 이야기를 들어서 오히려 맘편히 node_module를 사용하기로.&lt;/p&gt;
&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://toss.tech/article/node-modules-and-yarn-berry&quot;&gt;node_modules로부터 우리를 구원해 줄 Yarn Berry&lt;/a&gt; 토스의 개쩌는 기술 블로그. 요약하자면 복잡하고 비효율적인 node_modules 대신 &lt;code&gt;.yarn/cache&lt;/code&gt;에 zip파일의 형태로 저장하고, &lt;code&gt;.pnp.cjs&lt;/code&gt;에 해당 파일을 찾을 수 있는 경로를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;yarn set version berry
yarn install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn의 버전을 berry로 설정하고 install하면 &lt;code&gt;yarnrc.yml&lt;/code&gt; 파일이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;yarn dlx @yarnpkg/sdks vscode&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 모듈의 경로를 node_modules가 아닌 다른 경로에서 찾아야 한다. vscode를 사용할때는 위의 명령어를 이용해 다른 타입스크립트 sdk를 사용해야 한다. pnp가 제대로 작동을 안해서 저 sdk만 수백번은 설치해봤던 것 같다.&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;KakaoTalk_Photo_2023-01-03-14-53-33.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w1fZf/btrVeVcp9kB/8mzEbphj66KaKPIVAFB1Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w1fZf/btrVeVcp9kB/8mzEbphj66KaKPIVAFB1Hk/img.png&quot; data-alt=&quot;이렇게 1도 되는게 없었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w1fZf/btrVeVcp9kB/8mzEbphj66KaKPIVAFB1Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw1fZf%2FbtrVeVcp9kB%2F8mzEbphj66KaKPIVAFB1Hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;172&quot; data-filename=&quot;KakaoTalk_Photo_2023-01-03-14-53-33.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 1도 되는게 없었다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// .yarnrc.yml
plugins:
  - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
    spec: '@yarnpkg/plugin-typescript'

yarnPath: .yarn/releases/yarn-3.3.0.cjs
nodeLinker: node-modules&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodeLinker가 없으면 기본값 pnp모드로 동작한다. 수많은 시도 끝에 node-modules로 바꿔주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&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;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 15px; top: 141px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/100</guid>
      <comments>https://9yujin.tistory.com/100#entry100comment</comments>
      <pubDate>Tue, 3 Jan 2023 15:03:29 +0900</pubDate>
    </item>
    <item>
      <title>신촌 IT창업동아리 CEOS 15기 16기 회고 - 서류, 면접, 활동 후기</title>
      <link>https://9yujin.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 써야겠다고 생각한건 처음 지원서를 작성하면서부터였다. 다른 개발 동아리들은 '어쩌구 동아리 면접 후기', '저쩌구 합격 후기' 검색하면 포스팅이 쏟아져나오는데, CEOS는 그런 글들을 거의 찾기 힘들었다. 아무래도 연세대, 이화여대, 홍익대, 서강대만이 연합해서 활동하는 동아리이기 때문에 정보가 별로 없을 수 밖에 없겠다. 그래서 내가 적겠다! 뭐 이런 마인드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CEOS는 신촌 유일 IT창업 동아리로, 신촌 소재의 네개 대학교가 함께 활동한다. 기획, 디자인, 개발(웹 프론트, 백엔드) 파트에 각각 10명씩 모집하고 있다. 학기중에는 정해진 커리큘럼에 맞게 스터디를 진행하고 방학기간에 MVP 개발을 완료한다. 나는 15기 &lt;b&gt;프론트엔드&lt;/b&gt;와 16기 &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;b&gt;1. 서류&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 지원 당시에 내 상황에 대해 설명해보자면.. 개발 공부를 시작한지 채 반년도 되지 않았을 때였다. 3-4개월 정도 혼자 책과 강의를 들으면서 공부했다. 개인 리액트 프로젝트 하나, 그리고 서버 파트로 수강했던 부트캠프 (라이징캠프) 프로젝트 하나. 그리고 친구들과 함께 고스락 프로젝트를 막 끝낸 참이었다.&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;. 이런 연합동아리의 가장 큰 장점으로는 기획과 디자인이 모두 갖춰진 상태에서 협업 프로젝트를 할 수 있다는 점이다. 이전까지 혼자 또는 주변 친구들과의 프로젝트를 하며 느꼈던 아쉬움과 고민을 풀었다. 내가 개발하는 방식에 대해 의문이 많았다. 프로젝트 구조를 이렇게 가져가는게 맞는건지, 남들도 분명히 똑같은 고민을 했을텐데 어떻게 해결했을지! 동아리에 들어오면 이런 고민들을 다같이 나누며 빠르게 성장할 수 있을 것 같았다. 혼자 프로젝트를 하다보면 아무래도 원래 써봤던 기술 위주로 진행을 하게 되는데, 이번 기회에 nextjs나 타입스크립트 같은 아직 잘 모르는 기술들을 공부해서 사용해보고 싶다는 둥의 생각을 함께 적었다.&lt;/p&gt;
&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;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;창업&lt;/b&gt;동아리이다 보니 그에 관한 질문이 있다. 생각하고 있는 IT창업 아이디어를 적는 문항이었다. 이에 관련된 내용을 면접에서 다시 물어볼 수 도 있으니 구체적으로 생각해가는게 좋다. 구체적인 질문을 공개적으로 적기는 좀 뭐해서, 이정도로만 마무리하겠다.&lt;/p&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;총 8문항 정도 된다. 하루이틀 고민하면 충분히 쓸 수 있을 정도로 부담스럽지 않은 양이다. 세오스 이후에도 많은 지원서들을 쓰면서 얻은 나름의 꿀팁이 있는데, &lt;span style=&quot;background-color: #ffffff; color: #353535;&quot;&gt;지원서를 쓰기 전에 말 그대로 왜 지원하려는지 '지원 동기'를 꽤 오래 고민해본다. 이 활동을 통해서 무엇을 얻어가고 싶고, 나는 동아리에 어떤걸 기여할 수 있을까. 그 두가지가 머릿속에서 정리되면 글은 쉽게 써진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_IMG_0196.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pOWkq/btrUAa2T5DQ/hX741PbvFPk8kBn8w2Tqqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pOWkq/btrUAa2T5DQ/hX741PbvFPk8kBn8w2Tqqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pOWkq/btrUAa2T5DQ/hX741PbvFPk8kBn8w2Tqqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpOWkq%2FbtrUAa2T5DQ%2FhX741PbvFPk8kBn8w2Tqqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;318&quot; data-filename=&quot;edited_IMG_0196.jpg&quot; data-origin-width=&quot;1170&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;이렇게 서류합격 문자가 날라온다. 이러한 개발 동아리 지원이 아예 처음이었기 때문에 경쟁률이 어느정도이고 내가 어느정도 수준인지 전혀 가늠이 가지 않았다. 불안한 와중에 다행히 서류 합격했다는 문자를 받았다.&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;IMG_0197.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J6Bwg/btrULNEBSMX/UaXrAqDJKCN7r2kNmPTCpk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J6Bwg/btrULNEBSMX/UaXrAqDJKCN7r2kNmPTCpk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J6Bwg/btrULNEBSMX/UaXrAqDJKCN7r2kNmPTCpk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ6Bwg%2FbtrULNEBSMX%2FUaXrAqDJKCN7r2kNmPTCpk%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;400&quot; height=&quot;186&quot; data-filename=&quot;IMG_0197.jpg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;543&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;b&gt;2. 면접&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접은 온라인(구글 미트)로 진행된다. 정해진 시간 5분전에 대기실에서 대기하고 있다가 면접시간이 되면 해당 파트 소회의실로 이동된다. 나를 포함해 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;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;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; 고스락 프로젝트를 하면서 깃허브를 사용했던 방식에 대해 설명했다. 프로젝트 크기가 크지 않고 모두가 깃에 익숙하지 않았기 때문에 각자 자신의 브랜치를 파서 작업하고 병합을 반복했었다. 하지만 git-flow 등의 개념을 알고 있고, CEOS에서 프로젝트 시 적용할 수 있다는 이야기도 덧붙였다.&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;디자인도 경험했다고 했는데, 만약 디자이너와 협업을 할때 디자이너가 갖고 온게 마음에 안들면 어떻게할건지.&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;b&gt;새로운&lt;span&gt; &lt;/span&gt;기술들을&lt;span&gt; &lt;/span&gt;써보고싶다고했는데&lt;span&gt;, &lt;/span&gt;만약&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; &lt;/span&gt;협업하는&lt;span&gt; &lt;/span&gt;프론트&lt;span&gt; &lt;/span&gt;멤버가&lt;span&gt; &lt;/span&gt;역량이&lt;span&gt; &lt;/span&gt;안되거나&lt;span&gt; &lt;/span&gt;해서&lt;span&gt; &lt;/span&gt;싫다고&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;할건지. &lt;/b&gt;지원서에 작성한 문장(Nextjs와 타입스크립트 같은 것들을 사용해보고 싶다)을 보시고 하신 질문이다. 이 역시 답이 정해져있는 질문 같아서 고민을 좀 했지만 마땅히 생각나는게 없어서 솔직하게 말씀드렸던 것 같다. '아쉽기는 하겠지만 두달 조금 안되는 기간동안 프로젝트를 완성하는게 중요하므로 팀원과 이야기를 많이 해보고 적절한 기술을 선택하겠다 라고 답변했다.&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;IMG_4CE49A6593CC-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szOXO/btrUGrpZxIQ/4HQsk2HPZyKrAJkUgUyXn1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szOXO/btrUGrpZxIQ/4HQsk2HPZyKrAJkUgUyXn1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szOXO/btrUGrpZxIQ/4HQsk2HPZyKrAJkUgUyXn1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FszOXO%2FbtrUGrpZxIQ%2F4HQsk2HPZyKrAJkUgUyXn1%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;400&quot; height=&quot;210&quot; data-filename=&quot;IMG_4CE49A6593CC-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;614&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-filename=&quot;edited_KakaoTalk_Photo_2023-01-04-01-36-42.jpeg&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H1RCU/btrVmWOqkwg/bOtEA9qLajO2kDXm7qKXa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H1RCU/btrVmWOqkwg/bOtEA9qLajO2kDXm7qKXa0/img.png&quot; data-alt=&quot;운영진 친구가 갖고있었다!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H1RCU/btrVmWOqkwg/bOtEA9qLajO2kDXm7qKXa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH1RCU%2FbtrVmWOqkwg%2FbOtEA9qLajO2kDXm7qKXa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;98&quot; data-filename=&quot;edited_KakaoTalk_Photo_2023-01-04-01-36-42.jpeg&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;152&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;&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;지원자로서 그리고 운영진으로서 두번 리크루팅을 경험했다. 다양한 지원자들의 서류를 검토하고 면접을 보면서 어떤 사람들이 뽑히는지 조금 알게 된 것 같다. 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;span&gt;물론 쓸데없는 미사여구 집어넣어가며 채우라는 말은 아니다.&lt;span&gt; 짧고 간단한 문장이지만 분량이 많은(어렵다!) 글을 보면 성의가 느껴진다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;흔한 사람이 되지 않기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;. 어쩌면 제일 중요한게 아닐까. 지원자가 그리 많지도 않았는데도 기억에 남는 분이 별로 없었다. 당연하게도 그런 분들은 대부분 불합격이었다. 기억에 남는 지원자들의 특징은 &lt;b&gt;디테일&lt;/b&gt;이었다. 똑같은 이야기를 하더라도 디테일한 설명이 있으면 공감하기 쉽고 기억에 남는다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 제일 중요한건 &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;꺾이지 않는 마음&lt;/s&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 활동&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세오스 활동에 관한 이야기는 이미 지난회고에서 여럿 적어놨다. 대신 그때 당시(3월)에 개인 블로그에 적어놓았던 일기를 복사해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8aAsR/btrUIZfpDO2/uAGBf2Lg4hKzQu41cT5Qq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8aAsR/btrUIZfpDO2/uAGBf2Lg4hKzQu41cT5Qq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8aAsR/btrUIZfpDO2/uAGBf2Lg4hKzQu41cT5Qq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8aAsR%2FbtrUIZfpDO2%2FuAGBf2Lg4hKzQu41cT5Qq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;386&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc;&quot;&gt;매주 과제가 올라오고, 그 과제를 풀리퀘로 제출하면 서로에게 코드리뷰를 달아주는 형식이다. 정말 좋은 기회라고 느꼈다. 혼자 공부하면서 정말 아쉬웠던 부분을 잘 채우고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcgj5l/btrUIm22S2b/Q0FQm1dh4sJq4lEryLIMAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcgj5l/btrUIm22S2b/Q0FQm1dh4sJq4lEryLIMAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcgj5l/btrUIm22S2b/Q0FQm1dh4sJq4lEryLIMAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcgj5l%2FbtrUIm22S2b%2FQ0FQm1dh4sJq4lEryLIMAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;293&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;293&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;정말 대단하다고 느낀다. 매주 열명 정도 되는 팀원들의 코드를 다 읽고 리뷰를 달아준다. 이런 열쩡과 능력... 모조리 배워가야겠다!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kSDYG/btrUNURuiFQ/4BD5JyW92yWrKQeafcKYnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kSDYG/btrUNURuiFQ/4BD5JyW92yWrKQeafcKYnk/img.png&quot; data-alt=&quot;내가 여기서 제일 새싹인거 같지만!!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kSDYG/btrUNURuiFQ/4BD5JyW92yWrKQeafcKYnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkSDYG%2FbtrUNURuiFQ%2F4BD5JyW92yWrKQeafcKYnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;295&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;295&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;span style=&quot;background-color: #ffffff;&quot;&gt;나도 다른 사람의 코드를 보면서 많은것을 배워가는데, 누군가가 나를 보고 배운다고 생각하면 정말 뿌듯하다. 열심히 해야겠다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 그렇답니다. 매주 정해진 커리큘럼에 따라서 과제를 제출한다. 동료들(그리고 심심한 운영진)은 정해진 파트너들의 코드를 보고 리뷰를 달고, 반영한다. 마지막 두 주차에는 백엔드 파트와 합동 스터디를 진행한다. 그 이전에 프로젝트 팀빌딩이 완료된다. 합동 스터디를 통해 팀원들과 미리 협업을 경험하고 레포지토리와 배포 등의 세팅을 구축해보면서 자연스럽게 프로젝트 준비로 넘어갈 수 있다. 벌써 16기까지 이어지면서 조금씩 최신화되고 업데이트된 만큼, 꽤 짜임새있고 이유있는 커리큘럼을 가지고 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기수 운영진으로 스터디를 준비하면서 커리큘럼을 새로 보완하기도 했다. 내 활동기수에서는 Next.js를 이용해서 블로그를 만드는 과제를 했었다. 데이터들을 따로 서버에서 받아오는게 아니라 프로젝트에 json으로 가지고 있다보니 서버사이드렌더링 연습에 적합하지 않은 과제였다. 이번 기수는 영화 데이터 오픈 API를 이용해서 넷플릭스 페이지를 클론해보는 과제를 해보도록 했다.&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;너무 개발 얘기만 쓴 것 같아서 팀 프로젝트에 대한 내용도 덧붙여본다. 프론트엔드 스터디는 매주 일요일마다 했고, 매주 수요일에는 전체 세션이 진행된다. 중간고사 기간 이전에는 로컬라이징과 비상관 제시어 등을 통해 기획 프로세스를 체험(?)해보는 세션을 한다. 개발자로 참여하면서 기획과 디자이너 분들의 능력을 무한 존경하게 되는 시간이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간고사 이후에는 팀빌딩이 완료된다. 매주 수요일 세션 때 팀별 진행상황을 공유한다. 기획과 디자인 파트에서는 각자 팀의 기획과 디자인을 매주 디벨롭해 발표한다. 기획 발표에서 IA와 플로우차트,와이어프레임이 만들어지는 과정을 볼 수 있다. 디자인 발표에서는 서비스의 와이어프레임에서 시작해 mid-fi, hi-fi로 디자인이 완성되어가는 과정을 볼 수 있다.&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;챌린지 만들기.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QVBjX/btrUKW3RtsG/3SA9M5d20ravPOKINIS8q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QVBjX/btrUKW3RtsG/3SA9M5d20ravPOKINIS8q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QVBjX/btrUKW3RtsG/3SA9M5d20ravPOKINIS8q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQVBjX%2FbtrUKW3RtsG%2F3SA9M5d20ravPOKINIS8q1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;챌린지 만들기.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IA.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNHAsx/btrUORfVNGN/UTEumpFLkRWQka9cRAQ2mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNHAsx/btrUORfVNGN/UTEumpFLkRWQka9cRAQ2mK/img.png&quot; data-alt=&quot;뱅키즈 초기 와이어프레임과 IA&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNHAsx/btrUORfVNGN/UTEumpFLkRWQka9cRAQ2mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNHAsx%2FbtrUORfVNGN%2FUTEumpFLkRWQka9cRAQ2mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;IA.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;뱅키즈 초기 와이어프레임과 IA&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;CEOS가 아니었다면 쉽게 경험하지 못했을 것들&lt;/b&gt;이다. 다섯 팀의 발표를 들으면서 얻어간 것이 꽤 많다. 서비스 기획을 할때는 어떠한 프로세스로 진행되는게 좋은지, UI와 디자인시스템 구축은 어떤식으로 하는지 등등.&lt;span&gt; 고스락 프로젝트 내부에서 자체적으로 기획과 디자인을 하는데 아주 많은 도움이 되었다. 거의 동일한 프로세스로 진행되고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;이렇게 기획과 디자인이 각자 팀의 작업을 하는 동안 개발 파트는 스터디를 진행하는 것이다. &lt;b&gt;스터디가 끝나면 어느정도 준비된 기획과 디자인으로 바로 개발에 들어갈 수 있는 커리큘럼&lt;/b&gt;. 정말 짜임새 있다. 주변인들에게 CEOS를 추천하는 이유! 그렇다고 수요일 세션마다 손가락이나 빨고 지켜보기만 하는건 아니고, 개발 계획과 개발 중간 상황 발표 등으로 참여한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;네트워킹&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;사실 CEOS의 진짜 장점은 여기에 있다. 모두가 신촌권 대학생이라는 것. 특히 같이 개발공부할 수 있는 친구를 얻을 수 있다는게 좋았다. 생각보다 학교 내에 개발에 열정을 가지고 같이 공부할 수 있는 사람을 찾기가 힘들다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;그런게 아쉬운 분들을 위한 GDSC 홍익 많관부.&lt;/s&gt;&lt;/span&gt; 서로 동기부여 받으면서 정보를 나눌 수 있는 친구들을 원하는 사람들에게 정말 추천해주고 싶다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JmVoB/btrUNqb42IF/waj45wSgQlK658cBtKj370/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JmVoB/btrUNqb42IF/waj45wSgQlK658cBtKj370/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JmVoB/btrUNqb42IF/waj45wSgQlK658cBtKj370/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJmVoB%2FbtrUNqb42IF%2Fwaj45wSgQlK658cBtKj370%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;214&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1426&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;2000&quot; data-origin-height=&quot;2646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpSFMK/btrUN2IMqZk/Zj2dNomguUfuCV16DlRkck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpSFMK/btrUN2IMqZk/Zj2dNomguUfuCV16DlRkck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpSFMK/btrUN2IMqZk/Zj2dNomguUfuCV16DlRkck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpSFMK%2FbtrUN2IMqZk%2FZj2dNomguUfuCV16DlRkck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;397&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;2646&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;그 외에도 CEOS 한 기수면 신촌의 맛있는 술집들은 웬만하면 갈 수 있다. 홍대... 정말 별로다. 신촌이 진짜다. 너스레. 타이완소야. 서강포차. 우리들의 공간, 포석정. 정말 좋은데가 많더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;uarr; 난 어쩌면 이 한두문장을 적기 위해서 앞에 서류며 면접이며 주저리주저리 써놨던게 아닐까.&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;어쨌든, CEOS 지원을 고민하는 누군가가 이 글을 본다면 선택에 도움이 되었으면 좋겠다.&lt;span style=&quot;color: #9d9d9d;&quot;&gt;는 말로 급 마무리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  글을 위한 글</category>
      <category>CEOS</category>
      <category>it동아리</category>
      <category>개발 동아리</category>
      <category>세오스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/99</guid>
      <comments>https://9yujin.tistory.com/99#entry99comment</comments>
      <pubDate>Tue, 27 Dec 2022 23:40:14 +0900</pubDate>
    </item>
    <item>
      <title>글, 복학, 개발, 휴일, 2022 회고</title>
      <link>https://9yujin.tistory.com/98</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 글을 위한 글&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DFT6C/btrTMRPUnY1/urtGfrQI0ZnINVzy7f0Ez1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DFT6C/btrTMRPUnY1/urtGfrQI0ZnINVzy7f0Ez1/img.png&quot; data-alt=&quot;http://news.kyobobook.co.kr/people/writerView.ink?sntn_id=11083&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DFT6C/btrTMRPUnY1/urtGfrQI0ZnINVzy7f0Ez1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDFT6C%2FbtrTMRPUnY1%2FurtGfrQI0ZnINVzy7f0Ez1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1490&quot; height=&quot;358&quot; data-origin-width=&quot;1490&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://news.kyobobook.co.kr/people/writerView.ink?sntn_id=11083&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 블로그에 이런저런 잡다한 글을 올리는 카테고리 이름을 바꿨다. 글을 위한 글. 이석원의 블로그 제목과 똑같은 이름이다. 나도 개발자로서의 기술 블로그가 아니라 그냥 글을 쓰는 사람으로서 글을 쓰고 싶다는 욕심으로 가져왔다. 네이버 일상블로그도 그렇고 티스토리도 그렇고, 처음 의도대로 관리되고 있지는 않아서 아쉬운 무언가가 있다. 그 무언가가 뭔지는 아직 모르겠음. 주변의 글을 잘 쓰는 친구들의 영향을 많이 받는다. 문창과 교양과제 출품작이라고 올렸던 단편 소설이 엄청 충격이었다. 저게 공대생 머리에서 나올 수 있는 글인가? 그때부터 나도 글을 조금씩 성의있게 쓰기 시작했던것 같음. 잘 쓰기 이전에 성의있게 쓰기!&lt;br /&gt;&lt;br /&gt;내가 자주 읽고 좋아하는 (모르는 사람들의) 블로그들을 레퍼런스 삼아 시작했었다. 근데 애초에 그 사람이랑 나랑 사는 방식이 다르니까. 아무튼. 이 블로그의 방향에 대해선 계속 고민을 해봐야겠다. 기술 블로그를 왜 하는지, 하는게 정말 나에게 도움이 되는 일인지 고민을 종종 한다. 저번 GDSC 세미나에서 초청 연사분이 &quot;개발자라면 글을 쓰세요&quot; 라며 덧붙인 이야기가, 블로그나 리드미를 열심히 쓰면 &lt;b&gt;내 능력을 100% 인정받을 수 있다&lt;/b&gt;는 것. 나름 맞는 말 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CGuZG/btrTPWjlZSi/0Fc6ACZca79ntTFiV3y90k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CGuZG/btrTPWjlZSi/0Fc6ACZca79ntTFiV3y90k/img.png&quot; data-alt=&quot;https://imksh.com/108 (넥스터즈 지원자 서류 검토 후기)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CGuZG/btrTPWjlZSi/0Fc6ACZca79ntTFiV3y90k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCGuZG%2FbtrTPWjlZSi%2F0Fc6ACZca79ntTFiV3y90k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1772&quot; height=&quot;364&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://imksh.com/108 (넥스터즈 지원자 서류 검토 후기)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이렇게 쓸거 아니면 기술블로그 하나마나다&quot; 라는 생각으로 쓰신 글 같음. 혹시 내 블로그도 찬찬히 보시고 긍정적으로 생각하셨을랑가. 많이 공감하는 내용이었기 때문에, 글 하나하나 정성스레 쓰려고 노력한다. 실제로 덕분에 작업을 할 때 내가 쓴 글이 도움이 될 때가 많다. nginx 세팅할때나 CICD 세팅할때 남의 블로그 말고 내글을 보는게 훨신 편하다. 엄청 잘 써놨거든 히히.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 일 벌이는 사람들 특&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJpAe/btrTMdMmaji/8BOKhhQrKHfkAjK75JDSaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJpAe/btrTMdMmaji/8BOKhhQrKHfkAjK75JDSaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJpAe/btrTMdMmaji/8BOKhhQrKHfkAjK75JDSaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJpAe%2FbtrTMdMmaji%2F8BOKhhQrKHfkAjK75JDSaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1438&quot; height=&quot;420&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;늘리는 건 쉬운데 다시 줄이는 건 어렵더라. 이병건더기의 말씀.&lt;br /&gt;&lt;br /&gt;정말 신기하게도 올해는 방학때가 더 바쁘다. 1-2학년때는 열심히 시험공부하다가 종강하면 갑자기 시간이 많아져서 하루종일 심심하기만 했었는데, 요즘은 밀려오는 할일들을 다 종강 후로 미루고 있다. 그 모든 짬들은 미진이(미래의 규진)가 다 맞게 된다는 것. 절대 일벌이지 말고 하나만 하겠다는 다짐으로 다가오는 분기를 맞이하지만 매번 예상 못하게 하고싶은 일이 생기더라. 저번 여름방학이 진찌 빡셌는데, 세오스 프로젝트와 고스락 프로젝트를 병행했다. 공연도 두탕 뛰겠다고 욕심부렸다가 힘들어서 돌아버릴뻔 했다. 그래놓고 2학기에는 GDSC도 시작했다. 뭐 결과적으론 모두 잘 끝나서 좋은 선택이 되긴 했다.&lt;br /&gt;&lt;br /&gt;이렇게 된 데에는 주변인들의 영향이 크다. 다들 비슷하게 개발공부를 시작해 비슷한 흐름을 걷고 있다. 학교 다니고, 동아리하고, 플젝하고. 아무도 안 쉬고 계속 무언가를 하고 있더라. 아, 절대로 그들보다 뒤쳐질까봐 걱정되고 조급해지는 마음이 들어서는 아니다. 친구가 무언갈 하고 있으면 그냥 아무 생각이 없다가도 막 부러워지고 나도 하고 싶어진다!! 휴학을 하고 다른 개발동아리를 하고 있는 친구들이 수준높은 현직자분들과 함께 프로젝트하면서 강하게 크는게 너무너무 부러웠다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 복학&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복학을 했다. 새내기때부터 3학년이 진짜 힘들다고 계속 들어왔고, 역시 말 그대로였다. 운인지 실력인지 &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;793&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfzNnQ/btrTSwKsRky/KJOhAKOqMKa1IJgQscWfI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfzNnQ/btrTSwKsRky/KJOhAKOqMKa1IJgQscWfI1/img.png&quot; data-alt=&quot;알겠다구&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfzNnQ/btrTSwKsRky/KJOhAKOqMKa1IJgQscWfI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfzNnQ%2FbtrTSwKsRky%2FKJOhAKOqMKa1IJgQscWfI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;214&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;425&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;br /&gt;친구들과 함께 정신차리고 으쌰으쌰했던 1학기였던 것 같다. 절반정도는 비대면으로, 그 이후론 쭉 대면이었다. 코로나의 수혜를 정말 하나도 받지 못한 20군번이라 조금 아쉽다. 그나마 한 학기는 학점완화를 받았다. 덕분에 한번도 받아보지 못했던 학점을 받았고 좋은 동기부여가 됐다. 애석하게도 2학기 학점은 기대가 되지 않는군요.&lt;br /&gt;&lt;br /&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8a0fg/btrTPAs4ZYH/7NaKvylxdvuwFjmFbwB4F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8a0fg/btrTPAs4ZYH/7NaKvylxdvuwFjmFbwB4F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8a0fg/btrTPAs4ZYH/7NaKvylxdvuwFjmFbwB4F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8a0fg%2FbtrTPAs4ZYH%2F7NaKvylxdvuwFjmFbwB4F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1518&quot; height=&quot;388&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4월에 3주, 6월에 3주 정도 잔디가 비어있다. 아마 시험기간이었을 거다. 열심히 달려온 한 학기에 대한 훈장 어쩌고.. 하는 마음으로 기분좋게 보고있다. 방학도 했으니 다시 매일같이 진한 잔디로 채우지 않을까 싶다.&lt;br /&gt;&lt;br /&gt;그 외의 학교를 다니면서 생긴 잡다한 이야기들.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. 홍대를 다니지만 정말 먹을게 없다.&lt;/b&gt; 상수 홍대 맛집 추천좀 해주세요. 내가 요즘 계속 학식 타령하는건, 정말로 먹을게 없어서다. 매일 돈까스만 먹고 살 수는 없으니까. 메뉴 걱정 안하고 4800원에 배불리 먹을 수 있다는게 감사해지는 요즘이다. 각박한 세상! C동 뒤쪽에 돈까스 포차라고 있다. 에타 보고 저번주에 한번 가봤는데 진짜진짜 맛있었다. 어쩌면 매일 돈까스만 먹고 살 수 있을지도 몰라.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 다음학기는 무조건 기숙사에 들어갈거다.&lt;/b&gt; 중간고사 때 한번, 기말고사 때 한번, 시험 전날에 밤새고 주안형 집에서 자고 시험을 봤다. 삶의 질이 달라짐. 4시까지 열람실에서 공부하고 들어가서 5시간이나 자고 나와도 10시면 다시 열람실로 들어갈 수 있다니! 자취는 못하니 기숙사를. 그치만 2긱은 못갈 것 같으니 3긱이라도 노려보겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 지금은 계절학기를 다니고 있다. &lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 개발 커뮤니티&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 본격적으로 프론트엔드 개발을 시작하고 다양한 활동들을 찾아서 하고 있다.&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;br /&gt;&lt;br /&gt;프로젝트도 벌써 세번째 이어나가고 있다. 반년만에 모일때마다 다들 실력이 확 올라와있는게 느껴져서 기분이 좋다. 22학번 친구들도 프로젝트를 이어나가고 싶어하는걸 알게 되어서, 이번에는 추후에 더 많은 기능이 들어갈 수 있도록 기획을 하고 있다.&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;CEOS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하면서 시야를 확 넓힐 수 있었던 활동이었다. 후배들에게도 많이 많이 추천해주고 있다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(생각보다 지원자가 많지 않으니 충분히 뽑힐 수 있다!!) &lt;/span&gt;학기중에는 스터디와 과제를 매주 진행하고 방학 한두달간 프로젝트를 한다. 시험기간에는 스터디도 휴식하니 부담없이 도전해보십쇼. 구글에 동아리 찾아보면 나오는 서류후기∙면접후기 이런거 나도 쓰고 싶다. 16기활동이 끝나면 세세하게 정리해서 올려봐야겠다. 다른 개발동아리들과는 달리 연이홍서 4개 대학만이 대상이다보니 정보가 많이 없었다. 이후에 지원하려는 사람들이 답답하게 하지 않게 잘 적어봐야겠다. &lt;a href=&quot;https://9yujin.tistory.com/99&quot;&gt;신촌 IT창업동아리 CEOS 15기∙16기 회고 - 서류, 면접, 활동 후기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;뱅키즈 팀원을 만난 것도 되게 운이 좋았다. 좋은 사람들 만나 반년동안 재밌게 프로젝트했던 것 같다. 특히 같이 프론트엔드 개발했던 형이 프로젝트 외적으로도 종종 힘이 되주었다. 하지만 2023년에는 뱅키즈 외에도 하고 싶은게 더 많아져서, 올해까지만 하고 그만하겠다고 의사를 전한 상태이다. CEOS를 통한 정말 귀한 경험이었다!!&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;GDSC Hongik&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작하면서 많은 고민을 했다. 시간을 이런 커뮤니티 활동에 쓰는게 맞는지, 아니면 개인공부에 쓰는게 맞는지. 처음엔 학교수업만 열심히 들을 생각했다가 찬진이가 다시 떡밥을 물어와서 하게 되었다. GDSC에 합류하게 된 이야기는 &lt;a href=&quot;https://9yujin.tistory.com/64&quot; target=&quot;_self&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;서 읽을 수 있다.&lt;br /&gt;&lt;br /&gt;기대만큼은 아니지만 어느정도 잘 흘러가고 있다. 내가 바빠서 리드들의 기대만큼은 못한것 같아 아쉽다. 매주 스터디와 함께 과제를 진행하고 있다. 코어멤버는 과제 안하고 스터디 진행만 하는줄 알았다가 어쩌다 같이 하고 있다. 격주로 하는 데브톡 세미나도 매우 맘에 든다. 특히 저번 데브톡에 초청으로 오셨던 선배분이 준비하신 발표가 정말 좋았다. 나도 두 개의 주제로 준비하고 발표를 했다. &lt;a href=&quot;https://9yujin.tistory.com/87&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;재미없는 군대얘기&lt;/span&gt;&lt;/a&gt;와 &lt;a href=&quot;https://9yujin.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;디자인시스템&lt;/span&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;&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;&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;600&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TgUZa/btrTQf40cak/gt3A1fuo6O9DPBnt90WEmK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TgUZa/btrTQf40cak/gt3A1fuo6O9DPBnt90WEmK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TgUZa/btrTQf40cak/gt3A1fuo6O9DPBnt90WEmK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTgUZa%2FbtrTQf40cak%2Fgt3A1fuo6O9DPBnt90WEmK%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;400&quot; height=&quot;400&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&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;정말 운이 좋았다. 역시 문닫고 들어가는게 제일 기분 좋음. 넥스터즈는 작년에 개발 동아리들이 어떤게 있는지 둘러보면서 처음 이름을 들었다. 직장인들도 많이 참여하기 때문에 붙기 많이 어렵고 수준이 높다는 글을 많이 봤다. 그래서 마냥 멀게만 느껴졌다. 개발을 시작하고 1년만에 넥스터즈에서 활동하게 되었다는게 꽤 뿌듯하고, 그만큼 기대된다!!&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 휴일&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공연을 많이 봤다.&lt;/b&gt;&lt;/h4&gt;
&lt;div style=&quot;width: 100%&quot;&gt;
&lt;p style=&quot;align : center;&quot;&gt;
	&lt;audio controls=&quot;controls&quot;&gt;
    	&lt;source src=&quot;https://blog.kakaocdn.net/dn/clNdYR/btrUxpzbOQo/G6SBeEp1bDTFMvmYBf5uY0/tfile.m4a&quot; type=&quot;audio/mp3&quot; /&gt;
    &lt;/audio&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;거의 유일한 취미라고 말할 수 있을 것 같다. 검정치마 공연을 이틀 연속 두번. 그리고 크리스마스에 한번. 이 기억을 가지고 다음 몇달을 열심히 살아가게 된다. 박소은 공연도 두번을 봤다. 브로콜리너마저와 신인류 공연도.&amp;nbsp;&lt;/p&gt;
&lt;p 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;/p&gt;
&lt;p style=&quot;text-align: left;&quot; 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;고스락에 들어와서 벌써 33곡째. 올해는 9곡을 했다!! 9월 공연에는 팀 두개를 같이 뛰었다. 고생 많이했지만 너무너무 뿌듯했음. 처음 밴드를 하면서 하고 싶었던 곡이 가을목이랑 안티프리즈였는데, 두 곡을 한 공연에 할 수 있었다니. 아 너무 좋다.. 얼어붙은 아스팔트 도시 위로! 이번 방학 기간에도 공연을 준비하기로 했다. 엄청 잘하시는 선배님들이랑 같은 팀을 하게 돼서 상당히 기대된다. 두근두근. 내년 3월도 뿌셔보겠습니다.&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;IMG_E9F6DDB295D7-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HL41D/btrUHOc7Hdm/EKoM74slofZx4SWceOyVy1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HL41D/btrUHOc7Hdm/EKoM74slofZx4SWceOyVy1/img.jpg&quot; data-alt=&quot;락스타 사이에 당당히 자리한 한규진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HL41D/btrUHOc7Hdm/EKoM74slofZx4SWceOyVy1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHL41D%2FbtrUHOc7Hdm%2FEKoM74slofZx4SWceOyVy1%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;400&quot; height=&quot;399&quot; data-filename=&quot;IMG_E9F6DDB295D7-1.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;1168&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마지막. 2023 하고싶은 것&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;코테 연습&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 말하면, 제대로 안해왔다. 4학년 때는 정말 일 벌리지 말고 PS 꾸준히 하기. 코테를 포기하면 쓸 수 있는 공고 수가 확 줄어든단다. 개발보다 진짜진짜 재미없지만 열심히 해야겠다. 어른이니까...&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;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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;00555335-CFAE-4E3E-8EFC-B01DD5355637.JPG&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqgkr/btrUxplDXZV/fFk5X55PkVDAzKiXR7lDM0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqgkr/btrUxplDXZV/fFk5X55PkVDAzKiXR7lDM0/img.jpg&quot; data-alt=&quot;행복한 휴일!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqgkr/btrUxplDXZV/fFk5X55PkVDAzKiXR7lDM0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnqgkr%2FbtrUxplDXZV%2FfFk5X55PkVDAzKiXR7lDM0%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;400&quot; height=&quot;267&quot; data-filename=&quot;00555335-CFAE-4E3E-8EFC-B01DD5355637.JPG&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;행복한 휴일!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  글을 위한 글</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/98</guid>
      <comments>https://9yujin.tistory.com/98#entry98comment</comments>
      <pubDate>Tue, 27 Dec 2022 00:04:11 +0900</pubDate>
    </item>
    <item>
      <title>[데브톡] 디자인시스템과 리액트 컴포넌트 (22.12.01)</title>
      <link>https://9yujin.tistory.com/97</link>
      <description>&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/watch?v=aagS3ZwzhsQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/YARP6/hyRjb2Tcco/K10YIeKJmgWkTPHj20FVEk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/aagS3ZwzhsQ&quot; width=&quot;860&quot; height=&quot;484&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;안녕하세요, 프론트엔드 코어멤버 한규진입니다. 벌써 데브톡이 5회차까지 오면서 양질의 많은 세미나를 들었는데, 그중에 프론트엔드 개발에 관한 이야기가 거의 없다는게 조금 아쉬웠습니다. 저번 발표를 준비하면서도 &amp;ldquo;개발에 관한 이야기는 해당 파트가 아닌 분들이 듣기엔 지루할 수 도 있겠다&amp;rdquo; 라는 생각을 했거든요.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 34398.png&quot; data-origin-width=&quot;3914&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1NPoS/btrTwAPd5MQ/VO7KZJ4P7MrVWRHVl55Vik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1NPoS/btrTwAPd5MQ/VO7KZJ4P7MrVWRHVl55Vik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1NPoS/btrTwAPd5MQ/VO7KZJ4P7MrVWRHVl55Vik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1NPoS%2FbtrTwAPd5MQ%2FVO7KZJ4P7MrVWRHVl55Vik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3914&quot; height=&quot;1080&quot; data-filename=&quot;Group 34398.png&quot; data-origin-width=&quot;3914&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 시작하기에 앞서 간단한 퀴즈같은걸 준비했습니다. 양쪽 두 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;(2) 이번엔 조금 더 어렵습니다. 오른쪽 UI에 있는 구분선이 왼쪽보다 조금 더 두껍습니다. 물론 확실하게 정답이 있는 문제는 아니지만, UI를 디자인하고 구현할땐 이렇게 사소한것들이 꽤나 중요합니다. 나는 왼쪽처럼 디자인해놓고 옆을 봤는데, 다른 디자이너가 오른쪽처럼 만들고있다면 좀 답답할 것 같습니다. 서비스의 일관성이 떨어지면서 전체적인 완성도도 구리게 보일거에요. 디자이너들이 작업하면서 따를 수 있는 일종의 &amp;lsquo;가이드'가 있으면 좋겠죠.&lt;/p&gt;
&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 21.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qNyUX/btrTA7kp8BA/kMVRRoGmkRJt6PRAH8qXo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qNyUX/btrTA7kp8BA/kMVRRoGmkRJt6PRAH8qXo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qNyUX/btrTA7kp8BA/kMVRRoGmkRJt6PRAH8qXo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqNyUX%2FbtrTA7kp8BA%2FkMVRRoGmkRJt6PRAH8qXo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 21.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 고스락 첫번째 프로젝트를 시작할때 제가 멋모르고 디자인했던 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;이럴 때 &amp;lsquo;공통으로 사용하는 하나의 컴포넌트로 만들고 재사용하자&amp;rsquo; 라는 기똥찬 생각을 하게 됩니다. 아주 좋아요. 하지만 프로젝트가 엄청나게 커지게 되면 누가 언제 어떤 컴포넌트를 만들었는지 모두 알고 있기 어렵습니다. 코드를 여기저기 찾아보다가 &amp;ldquo;이럴바엔 그냥 내가 새로 짜야지..&amp;rdquo; 하는 상황이 생깁니다.&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 22.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coMreB/btrTwBgihJP/UOLnxL52UgpfxMM3nviqt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coMreB/btrTwBgihJP/UOLnxL52UgpfxMM3nviqt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coMreB/btrTwBgihJP/UOLnxL52UgpfxMM3nviqt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoMreB%2FbtrTwBgihJP%2FUOLnxL52UgpfxMM3nviqt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 22.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 23.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhcp6/btrTCgH4z3U/VvoEKvxQ305AccL6KWVDI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhcp6/btrTCgH4z3U/VvoEKvxQ305AccL6KWVDI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhcp6/btrTCgH4z3U/VvoEKvxQ305AccL6KWVDI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnhcp6%2FbtrTCgH4z3U%2FVvoEKvxQ305AccL6KWVDI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 23.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(5) 이를 위해 &amp;lsquo;디자인 시스템' 이라는 것을 도입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Single Source Of Truth 라는 말을 들어보신적이 있으신가요? 저도 발표를 준비하면서 처음 알게 된 개념인데요, 보통 네이티브 개발이나 리덕스 상태관리 시에 나오는 개념이라고 합니다. '데이터의 사본이 존재할 가능성을 없애자'는 말을 멋있게 쓴겁니다.&lt;/p&gt;
&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Dec-14-2022 11-46-26.gif&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/covdAL/btrTAczHOP0/mPEX7nognsdKk7C9K9j0iK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/covdAL/btrTAczHOP0/mPEX7nognsdKk7C9K9j0iK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/covdAL/btrTAczHOP0/mPEX7nognsdKk7C9K9j0iK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/covdAL/btrTAczHOP0/mPEX7nognsdKk7C9K9j0iK/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;814&quot; height=&quot;454&quot; data-filename=&quot;Dec-14-2022 11-46-26.gif&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(6) 디자인시스템의 구성요소를 세가지로 정리해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 비주얼 언어란, UI를 표현할 수 있는 다양한 요소들을 말합니다. 서비스에 사용하는 모든 색상들을 이렇게 규격화합니다. 메인 색상은 Yellow100, 200, 300 등으로 라벨링을 했습니다. 가장 많은 곳에서 사용하는 모노톤 색상도 마찬가지입니다.&lt;/p&gt;
&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 28.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6gptO/btrTBiTHyC6/h6KlhFEaSclS1B2w9FXDjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6gptO/btrTBiTHyC6/h6KlhFEaSclS1B2w9FXDjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6gptO/btrTBiTHyC6/h6KlhFEaSclS1B2w9FXDjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6gptO%2FbtrTBiTHyC6%2Fh6KlhFEaSclS1B2w9FXDjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 28.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 29.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUzfuK/btrTz1dXaWt/dReZjM5x3I0iddoE6cM8Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUzfuK/btrTz1dXaWt/dReZjM5x3I0iddoE6cM8Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUzfuK/btrTz1dXaWt/dReZjM5x3I0iddoE6cM8Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUzfuK%2FbtrTz1dXaWt%2FdReZjM5x3I0iddoE6cM8Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 29.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬라이드에 있는 그림은 인풋창 컴포넌트입니다. focus되어있을때는 진한 노란색을, blur일때는 흐리게 해줍니다. 유효성 검사를 통과하지 못했을 때는 빨간색으로 경고를 띄우도록 합니다. 버튼 컴포넌트도 마찬가지입니다. 되돌릴 수 있는 액션을 실행하는 버튼은 빨간색을, 그 외 기본값으로 서비스의 메인 컬러를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 31.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JA1iW/btrTEy3hsbg/mGjeaVQbHHoAemGJKETcSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JA1iW/btrTEy3hsbg/mGjeaVQbHHoAemGJKETcSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JA1iW/btrTEy3hsbg/mGjeaVQbHHoAemGJKETcSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJA1iW%2FbtrTEy3hsbg%2FmGjeaVQbHHoAemGJKETcSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 31.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(9) 이제 이렇게 개념적으로 살펴본 디자인시스템을 직접 코드로 구현해봐야겠죠? 디자인시스템의 구성요소 중 가장 기본이라고 볼 수 있는 비주얼 언어들로 시작해볼겁니다. 비주얼 언어란 UI에 사용되는 다양한 요소들을 토큰화하는 것이라고 했습니다. 화면에 보이는건 아까 보여드렸던 색상 팔레트인데요, 피그마단에서 먼저 yellow100,200 과 같은 라벨링을 하면서 스타일 라이브러리를 세팅한 모습입니다.&lt;/p&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;lsquo;styled-components&amp;rsquo;를 사용하고 있습니다. css를 js 구문 안에서 쓸 수 있도록 해주는 npm 라이브러리입니다. 그 중에서 &amp;lsquo;Theme provider&amp;rsquo;라는 기능을 활용할겁니다. 오픈 세미나인 만큼 코드를 깊이 설명드리진 않겠지만 대충 봐도 아실겁니다. #FFF6D2와 같은 색상 코드들을 yellow100 등을 key로 하는 객체로 만들어놓고, 해당 객체를 프로젝트 컴포넌트 전체에 전역적으로 사용할 수 있도록 세팅해둡니다. 그럼 css 구문 내에서 불러와 바로 사용할 수 있습니다. 타입스크립트와 같이 사용한다면 왼쪽 사진처럼 자동완성으로 바로 입력할 수 있습니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 33.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t5C1m/btrTABNtHc6/gZbvm3eHIUknOOLuEucQi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t5C1m/btrTABNtHc6/gZbvm3eHIUknOOLuEucQi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t5C1m/btrTABNtHc6/gZbvm3eHIUknOOLuEucQi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft5C1m%2FbtrTABNtHc6%2FgZbvm3eHIUknOOLuEucQi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 33.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(10) 타이포도 마찬가지인데요, 역시 피그마단에서부터 정리된 스타일 라이브러리로 세팅이 되어있는 상태 입니다. 색상과 달리 font family, size, lign height 등 여러 스타일이 같이 적용된 타이포이기 때문에, 문자열을 통째로 토큰화해놓고 불러와 사용합니다. 이 외에도 애니메이션, 보더 레디우스, 미디어 쿼리 등 매우 다양한 요소들을 theme provider 내에서 만들어 사용하고 있습니다.&lt;/p&gt;
&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 34.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnHniN/btrTA51PZH2/as9zRqzk2WyI1NbEDFFvK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnHniN/btrTA51PZH2/as9zRqzk2WyI1NbEDFFvK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnHniN/btrTA51PZH2/as9zRqzk2WyI1NbEDFFvK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnHniN%2FbtrTA51PZH2%2Fas9zRqzk2WyI1NbEDFFvK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 34.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(11) 그 비주얼 요소들을 조합해 컴포넌트를 구성해야겠죠. 계속 예시로 들고 있는 버튼 컴포넌트입니다. property, label, state, fixed 를 props로 받고 있습니다. 리액트에서 props란 부모에서 자식 컴포넌트로 넘겨주는 인자와 비슷합니다. properties는 어떠한 때에 쓰이는 버튼인지 정보를, state는 버튼의 활성화 유무, label은 버튼에 적힌 글씨, fixed는 너비가 고정된 버튼인지 아닌지를 결정합니다. 이 네가지 인자들을 넘겨받아서 조건에 맞게 스타일링을 주면, 이 컴포넌트 하나로 서비스 내의 여러 버튼 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;이렇게 컴포넌트를 만들어만 두면 끝일까요? 개발은 혼자하는게 아니죠. 동료 프론트엔드 개발자와도 디자이너와도 계속 소통을 해야합니다. 처음에 말씀드렸던 것 처럼 코드를 여기저기 찾아보다가 &amp;ldquo;이럴바엔 그냥 내가 새로 짜야지..&amp;rdquo; 하는 일이 생기면 안된다는 겁니다.&lt;/p&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들을 테스트할 수 있는 공간이 필요합니다. 이를 위해 Storybook 이라는 또다른 라이브러리를 사용하고 있습니다. 저희 &lt;a href=&quot;https://bankidz.github.io/bankidz-client/&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Dec-14-2022 14-16-48.gif&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3xer/btrTAMVH9nd/xZdFs4SEi7THxvkrh08d61/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3xer/btrTAMVH9nd/xZdFs4SEi7THxvkrh08d61/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3xer/btrTAMVH9nd/xZdFs4SEi7THxvkrh08d61/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/s3xer/btrTAMVH9nd/xZdFs4SEi7THxvkrh08d61/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;926&quot; height=&quot;518&quot; data-filename=&quot;Dec-14-2022 14-16-48.gif&quot; data-origin-width=&quot;926&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;(12) 이렇게 리액트 프로젝트는 각각의 컴포넌트들의 조합으로 되어있습니다. 아까 보았던 프로그레스바가 쓰이는 페이지를 가져왔습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wioYE/btrTDmoA1Va/lwxGyjzwbiVKH5uoZKJUnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wioYE/btrTDmoA1Va/lwxGyjzwbiVKH5uoZKJUnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wioYE/btrTDmoA1Va/lwxGyjzwbiVKH5uoZKJUnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwioYE%2FbtrTDmoA1Va%2FlwxGyjzwbiVKH5uoZKJUnk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런식으로 코드를 짰다고 해봅시다. 현재 진행중인 스텝과, 첫번째 스텝 생략 여부를 인자로 넘겨줍니다. 총 4개의 circle을 렌더링해주는데, skip어쩌구 인자가 false일때는 circle하나를 더 보여줘서 총 5개의 동그라미를 보여주도록 하는거죠.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Dec-14-2022 14-21-04.gif&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/INITi/btrTEybfEfr/QzOMDeK5FKgSzKOpsmcwl0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/INITi/btrTEybfEfr/QzOMDeK5FKgSzKOpsmcwl0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/INITi/btrTEybfEfr/QzOMDeK5FKgSzKOpsmcwl0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/INITi/btrTEybfEfr/QzOMDeK5FKgSzKOpsmcwl0/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;926&quot; height=&quot;518&quot; data-filename=&quot;Dec-14-2022 14-21-04.gif&quot; data-origin-width=&quot;926&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;(13) 그러다 기획자가 이렇게 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아 우리 서비스의 사용방법을 보여주는 페이지를 만들어주세요! 디자인은 이렇게 나왔어요.&amp;rdquo;&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;다시 코드로 돌아와서 봅시다. 서비스 사용방법은 총 네단계니까 skipSelectParent를 true로 줘서 사용할까요? 물론 이렇게 해도 되겠지만 누가봐도 좋은 코드는 아니겠죠. 제가 작성한 코드니까 물론 저는 알아볼 수 있겠지만, 다른 누군가가 읽었을 때 절대 이해하기 쉬운 네이밍은 아닙니다.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 38.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JJlps/btrTAMO0dSm/PDpmCSImd3u3l4xLr8Axck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JJlps/btrTAMO0dSm/PDpmCSImd3u3l4xLr8Axck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JJlps/btrTAMO0dSm/PDpmCSImd3u3l4xLr8Axck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJJlps%2FbtrTAMO0dSm%2FPDpmCSImd3u3l4xLr8Axck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 38.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(14) 컴포넌트를 재사용하기 위해서는 좋은 인터페이스를 갖고 있어야합니다. 인터페이스란 컴포넌트에 인자로 넘겨주는 객체의 타입, 설계도라고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 현재 코드는 다음과 같은 문제들을 갖고 있습니다. 특정 도메인과 강하게 결합되어 있고, 확장 불가능한 구조로 되어있습니다. 전체 스탭의 가짓수를 결정하는 props는 돈길 계약하기 과정에서만 사용되는 네이밍(skipSelectParent)을 쓰고 있습니다. 전체 스탭 또한 4개 혹은 5개. 역시 돈길 계약하기에서만 쓸 수 있도록 설계되어 있죠.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Slide 16_9 - 40.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sDgiM/btrTA7eqafl/lkFuuOY4iwO3GFkdckRK3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sDgiM/btrTA7eqafl/lkFuuOY4iwO3GFkdckRK3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sDgiM/btrTA7eqafl/lkFuuOY4iwO3GFkdckRK3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsDgiM%2FbtrTA7eqafl%2FlkFuuOY4iwO3GFkdckRK3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-filename=&quot;Slide 16_9 - 40.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(15) 화면과 같이 코드를 수정해보았습니다. step과 skip어쩌구라는 이름으로 받은 인자를, now와 total이라는 (돈길계약하기)와 관련없는 보편적인 네이밍으로 바꾸었습니다. 전체 개수도 4-5개에서 제한없이 total로 받은 값 만큼 동그라미를 만들어줍니다. 이렇게 되면 스텝이 몇개가 있든지 재활용할 수 있겠죠.&lt;/p&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;a href=&quot;https://www.youtube.com/watch?v=fR8tsJ2r7Eg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(1)&lt;/a&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=LmLchZ4tCXc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(2)&lt;/a&gt; 제 이야기가 재밌으셨거나, 더 궁금하신게 있으신분은 이런것들 한번 찾아보시면 많은 도움이 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  긴호흡/GDSC</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/97</guid>
      <comments>https://9yujin.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 14 Dec 2022 14:37:59 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch5. I/O System</title>
      <link>https://9yujin.tistory.com/96</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-31.jpg&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;2182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpEkov/btrTALPmP0s/fX2H20VGaH3axLxHWVi8bk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpEkov/btrTALPmP0s/fX2H20VGaH3axLxHWVi8bk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpEkov/btrTALPmP0s/fX2H20VGaH3axLxHWVi8bk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpEkov%2FbtrTALPmP0s%2FfX2H20VGaH3axLxHWVi8bk%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;1538&quot; height=&quot;2182&quot; data-filename=&quot;기말 정리-31.jpg&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;2182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-32.jpg&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;2182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3KNTi/btrTzdeQ3T1/knvHZXYX8jEfxllxxKRJ01/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3KNTi/btrTzdeQ3T1/knvHZXYX8jEfxllxxKRJ01/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3KNTi/btrTzdeQ3T1/knvHZXYX8jEfxllxxKRJ01/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3KNTi%2FbtrTzdeQ3T1%2FknvHZXYX8jEfxllxxKRJ01%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;1538&quot; height=&quot;2182&quot; data-filename=&quot;기말 정리-32.jpg&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;2182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/96</guid>
      <comments>https://9yujin.tistory.com/96#entry96comment</comments>
      <pubDate>Wed, 14 Dec 2022 11:19:51 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch4. File System</title>
      <link>https://9yujin.tistory.com/95</link>
      <description>&lt;p&gt;&lt;iframe src=&quot;https://drive.google.com/file/d/1jlnfu_TdK8A2RjlYRqPNcrAyuuNLvwe_/preview&quot; width=&quot;100%&quot; height=&quot;530&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://drive.google.com/open?id=1jlnfu_TdK8A2RjlYRqPNcrAyuuNLvwe_&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;새 창에서 열기&lt;/a&gt;&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/95</guid>
      <comments>https://9yujin.tistory.com/95#entry95comment</comments>
      <pubDate>Tue, 13 Dec 2022 14:34:00 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch3. Memory Management</title>
      <link>https://9yujin.tistory.com/94</link>
      <description>&lt;p&gt;&lt;iframe src=&quot;https://drive.google.com/file/d/1nVSEyokSVGRBCIUOvqziJkkVfFereTbV/preview&quot; width=&quot;100%&quot; height=&quot;530&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://drive.google.com/open?id=1nVSEyokSVGRBCIUOvqziJkkVfFereTbV&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;새 창에서 열기&lt;/a&gt;&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/94</guid>
      <comments>https://9yujin.tistory.com/94#entry94comment</comments>
      <pubDate>Tue, 13 Dec 2022 14:29:13 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 정규화</title>
      <link>https://9yujin.tistory.com/93</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-8.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HRz1s/btrThmibyXk/UQloYGd4atkbsQi0CwiN0K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HRz1s/btrThmibyXk/UQloYGd4atkbsQi0CwiN0K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HRz1s/btrThmibyXk/UQloYGd4atkbsQi0CwiN0K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHRz1s%2FbtrThmibyXk%2FUQloYGd4atkbsQi0CwiN0K%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-8.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제발 노력한거보다 학점 잘 받게 해주세요&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/93</guid>
      <comments>https://9yujin.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 9 Dec 2022 22:29:58 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch21. 보안과 권한 관리</title>
      <link>https://9yujin.tistory.com/92</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-6 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uiWi0/btrThH7rP3B/CFQLIdlkv9i0p6CjTy410K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uiWi0/btrThH7rP3B/CFQLIdlkv9i0p6CjTy410K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uiWi0/btrThH7rP3B/CFQLIdlkv9i0p6CjTy410K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuiWi0%2FbtrThH7rP3B%2FCFQLIdlkv9i0p6CjTy410K%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-6 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-7.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phKAJ/btrThOL8bPr/kD7hMK4PR3D7ZTWwbKulj0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phKAJ/btrThOL8bPr/kD7hMK4PR3D7ZTWwbKulj0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phKAJ/btrThOL8bPr/kD7hMK4PR3D7ZTWwbKulj0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphKAJ%2FbtrThOL8bPr%2FkD7hMK4PR3D7ZTWwbKulj0%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-7.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/92</guid>
      <comments>https://9yujin.tistory.com/92#entry92comment</comments>
      <pubDate>Fri, 9 Dec 2022 22:27:59 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch10. 트리 구조 인덱싱 / Ch11. 해시 기반 인덱싱</title>
      <link>https://9yujin.tistory.com/91</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-4.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b80H68/btrTgHAkGYR/CkxCEbkZAZ2N0L378RgXKK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b80H68/btrTgHAkGYR/CkxCEbkZAZ2N0L378RgXKK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b80H68/btrTgHAkGYR/CkxCEbkZAZ2N0L378RgXKK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb80H68%2FbtrTgHAkGYR%2FCkxCEbkZAZ2N0L378RgXKK%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-4.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-5.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LLi8L/btrTiwRS3BX/LAGgd1SZtylLCC377YQLbK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LLi8L/btrTiwRS3BX/LAGgd1SZtylLCC377YQLbK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LLi8L/btrTiwRS3BX/LAGgd1SZtylLCC377YQLbK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLLi8L%2FbtrTiwRS3BX%2FLAGgd1SZtylLCC377YQLbK%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-5.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/91</guid>
      <comments>https://9yujin.tistory.com/91#entry91comment</comments>
      <pubDate>Fri, 9 Dec 2022 22:26:12 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch8. 저장장치와 인덱스 개론 / Ch9. 데이터 저장, 디스크</title>
      <link>https://9yujin.tistory.com/90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-2 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lFl9X/btrTiWwaav2/aHuacRzSdZNpL48529Svc0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lFl9X/btrTiWwaav2/aHuacRzSdZNpL48529Svc0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lFl9X/btrTiWwaav2/aHuacRzSdZNpL48529Svc0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlFl9X%2FbtrTiWwaav2%2FaHuacRzSdZNpL48529Svc0%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-2 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;기말 정리-3.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nmUy4/btrThyixBqV/rQd8ZSqO1hHv1nR7ztwl41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nmUy4/btrThyixBqV/rQd8ZSqO1hHv1nR7ztwl41/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nmUy4/btrThyixBqV/rQd8ZSqO1hHv1nR7ztwl41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnmUy4%2FbtrThyixBqV%2FrQd8ZSqO1hHv1nR7ztwl41%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;기말 정리-3.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/90</guid>
      <comments>https://9yujin.tistory.com/90#entry90comment</comments>
      <pubDate>Fri, 9 Dec 2022 22:25:04 +0900</pubDate>
    </item>
    <item>
      <title>[React] 다시 만드는 todo list (투두메이트 클론) (4 - atomFamily, 투두 색깔 채우기)</title>
      <link>https://9yujin.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-25 오전 12.58.42.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xS83V/btrR2F48ou5/ys4PmktDoPRsSVdRM1lUg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xS83V/btrR2F48ou5/ys4PmktDoPRsSVdRM1lUg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xS83V/btrR2F48ou5/ys4PmktDoPRsSVdRM1lUg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxS83V%2FbtrR2F48ou5%2Fys4PmktDoPRsSVdRM1lUg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1063&quot; height=&quot;808&quot; data-filename=&quot;스크린샷 2022-11-25 오전 12.58.42.png&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적인 기능 구현이 모두 끝났습니다. 하루이틀동안 급하게 마무리했음. 정리하자면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;todoItem을 유저와 날짜에 따라 별도의 상태로 관리한다.&lt;/li&gt;
&lt;li&gt;할일을 완료할 때마다 해당 날짜의 달력에 색깔과 남은 개수가 업데이트된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. TodoItem 기능 확장&lt;/b&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;span&gt;(원래는 서버에서 받아와야 하는 데이터를 프로젝트에선 목데이터처럼 사용하는 것이기 때문에).&lt;span&gt; &lt;/span&gt;&lt;/span&gt;'투두' 테이블을 하나 두고, userId와 date 컬럼을 추가해서 그걸 통해 정보를 패칭해오지 않았을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669306923579&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface ITodoItem {
  label: string;
  id: string;
  isDone: boolean;
  category: ICategory;
  userId:string;
  date:string;
}

export const todoState = atom&amp;lt;ITodoItem[]&amp;gt;({
  key: 'todo',
  default: initialState,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존과 크게 바꾸지 않고 인터페이스에 userId과 date만 추가해서 이렇게 관리를 해볼까.. 했더니 굉장히 마음에 들지 않았다. 모든 날짜와 모든 유저가 갖고있는 투두를 하나의 배열로 갖고있는다는 점이었다. 이건 엄청 큰 문제다.&lt;/p&gt;
&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;p data-ke-size=&quot;size16&quot;&gt;리액트 쿼리를 사용한 상황이었다면 어떻게 했을까? [userId, date]를 쿼리키로 사용하지 않았을까. 각각을 별도의 상태로 캐싱하고 관리하는게 더 효율적이다. 리코일의 atomFamily 함수가 제공하는 기능이다.&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;atomFamily 도입&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669308672179&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @type [selectedDate, selectedProfile]
 */
export type ITodoItemKey = [string, string];

export const todoState = atomFamily&amp;lt;ITodoItem[], ITodoItemKey&amp;gt;({
  key: 'todo',
  default: [],
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;atomFamily&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&amp;nbsp;는 동일한 형태의 atom을 생성해주는 팩토리 함수를 제공한다. 무슨 말이냐면, 매개변수로 받아온 값에 따라 각각의 아톰을 생성하는데, 그게 모두 똑같은 인터페이스를 갖는 것. 모든 날짜와 유저의 투두를 하나의 배열로 관리하는 것이 아닌 각각의 아톰으로 분리하기 위해 사용한다. &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;&quot;&gt;매&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;개변수를 리액트 쿼리의 쿼리키와 비슷한 방식으로 사용하고 싶었다. 스트링 배열의 형태로 주고 @Key라는 네이밍을 썼다. 직관적이어서 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;selectorFamily에서 atomFamily 상태 가져오기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;기존 프로젝트 구조에서는 FeedItemList.tsx에서 특정 카테고리의 투두들만 가져와서 렌더링하고 있다. 이제는 특정 날짜의 특정 유저의, 특정 카테고리의 투두들을 가져와야 한다. 원래 사용하던 selectorFamily가 atomFamily의 값을 가져와서 가공할 수 있도록 수정해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669309669517&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export type ITodoItemSelectorParams = {
  todoItemKey: ITodoItemKey;
  categoryLabel: string;
};

export const todosByCategory = selectorFamily&amp;lt;
  ITodoItem[],
  ITodoItemSelectorParams
&amp;gt;({
  key: 'todoSelector',
  get:
    ({ todoItemKey, categoryLabel }: ITodoItemSelectorParams) =&amp;gt;
    ({ get }) =&amp;gt;
      get(todoState(todoItemKey)).filter(
        (todo) =&amp;gt; todo.category.label === categoryLabel,
      ),
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;atomFamily의 파라미터와 카테고리 라벨을 받아와서 사용한다.&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_1669310046322&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface ITodoItemSelectorParams {
  todoItemKey: ITodoItemKey;
  categoryLabel: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type이 아니라 interface로 만들어 사용하면, 아래와 같은 오류가 발생한다.&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;스크린샷 2022-11-25 오전 2.13.28.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0syS/btrR1gSn2OO/TPVT1JSTKMP0kKKTauO2M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0syS/btrR1gSn2OO/TPVT1JSTKMP0kKKTauO2M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0syS/btrR1gSn2OO/TPVT1JSTKMP0kKKTauO2M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0syS%2FbtrR1gSn2OO%2FTPVT1JSTKMP0kKKTauO2M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;198&quot; data-filename=&quot;스크린샷 2022-11-25 오전 2.13.28.png&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type으로 바꾸어주기만 했는데 오류가 해결되었다. atomFamily param의 타입 중에서 {[key: string]: SerializableParam} 형태의 인터페이스로 구현하려했다. 저런걸 index signature라고 하는데, 타입별칭을 사용해야만 사용할 수 있는 듯 하다.&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_1669310876490&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const selectedDate = useRecoilValue(selectedDateState);
  const selectedProfile = useRecoilValue(selectedProfileState);
  const todos = useRecoilValue(
    todosByCategory({
      todoItemKey: [selectedDate, selectedProfile],
      categoryLabel: category.label,
    }),
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FeedItemList 컴포넌트에서 위와 같이 selectorFamily 함수를 호출해 사용한다. date와 profile 상태 또한 전역으로 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 달력 투두 아이콘 색깔 채우기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되게 재밌게 작업했다. 투두메이트만의 아이덴티티라고 할 수 있는 기능이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-25-2022 15-24-50.gif&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5h6mi/btrR79jScla/3qORU95k2QZ6PhUk9qk0I1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5h6mi/btrR79jScla/3qORU95k2QZ6PhUk9qk0I1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5h6mi/btrR79jScla/3qORU95k2QZ6PhUk9qk0I1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/5h6mi/btrR79jScla/3qORU95k2QZ6PhUk9qk0I1/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;918&quot; height=&quot;548&quot; data-filename=&quot;Nov-25-2022 15-24-50.gif&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완료한 투두의 색깔이 달력에도 표시된다. 구체적으로 어떤식으로 표시되는지 살펴봤음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;완료되지 않은 투두의 개수가 표시된다. 모든 투두가 완료되면 체크가 표시된다.&lt;/li&gt;
&lt;li&gt;달력의 아이콘은 동그라미 네개로 이루어져있는데, 각 동그라미마다 다른 색상을 넣을 수 있다.&lt;/li&gt;
&lt;li&gt;완료된 투두들의 카테고리 종류에 따라 다양하게 표시될 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;카테고리가 한가지일때는 단색으로, 두가지일때는 반반씩. 네종류일때는 동그라미 하나씩 표시된다.&lt;/li&gt;
&lt;li&gt;세가지일때는 색깔 하나가 두칸을 차지하는데, 완료된 투두 중 개수가 많은 카테고리가 두칸이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;TodoIconSvg.tsx&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669358330218&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface TodoIconSvgProps {
  colors: string[];
}

const TodoIconSvg = ({ colors }: TodoIconSvgProps) =&amp;gt; {
  let fill = [colors[0], colors[0], colors[0], colors[0]];

  switch (colors.length) {
    case 1:
      fill = [colors[0], colors[0], colors[0], colors[0]];
      break;
    case 2:
      fill = [colors[0], colors[1], colors[1], colors[0]];
      break;
    case 3:
      fill = [colors[0], colors[0], colors[1], colors[2]];
      break;
    case 4:
      fill = [colors[0], colors[1], colors[2], colors[3]];
      break;
    default:
      fill = ['#DBDDDF', '#DBDDDF', '#DBDDDF', '#DBDDDF'];
      break;
  }

  return (
    &amp;lt;svg
      width=&quot;21&quot;
      height=&quot;21&quot;
      viewBox=&quot;0 0 21 21&quot;
      fill=&quot;none&quot;
      xmlns=&quot;http://www.w3.org/2000/svg&quot;
    &amp;gt;
      &amp;lt;circle cx=&quot;6.46154&quot; cy=&quot;6.46154&quot; r=&quot;6.46154&quot; fill={fill[0]} fillOpacity={'0.9'}/&amp;gt;
      &amp;lt;circle cx=&quot;6.46154&quot; cy=&quot;14.5387&quot; r=&quot;6.46154&quot; fill={fill[1]} fillOpacity={'0.9'}/&amp;gt;
      &amp;lt;circle cx=&quot;14.5387&quot; cy=&quot;14.5387&quot; r=&quot;6.46154&quot; fill={fill[2]} fillOpacity={'0.9'}/&amp;gt;
      &amp;lt;circle cx=&quot;14.5387&quot; cy=&quot;6.46154&quot; r=&quot;6.46154&quot; fill={fill[3]} fillOpacity={'0.9'}/&amp;gt;
    &amp;lt;/svg&amp;gt;
  );
};

export default TodoIconSvg;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네개의 원으로 이루어진 svg 컴포넌트를 하나 만들었다.colors 배열을 컴포넌트 밖에서 만들어서 줬음. 위에서 언급했던 조건에 맞게 색을 채워준다. 투명도를 0.9로 했더니 실제 투두메이트랑 완전히 똑같다..! 중요한건 아니지만 괜히 기분좋은 포인트.&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;useTodoInfo.ts&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669358894341&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const useTodoInfo = (date: string, userId: string) =&amp;gt; {
  const todos = useRecoilValue(todoState([date, userId]));

  const colors = todos
    .filter((todo) =&amp;gt; todo.isDone === true)
    .map((done) =&amp;gt; done.category.color);
  const colorSet = new Set(getSortedArray(colors));
  const colorSetArr = Array.from(colorSet);

  const count = todos.filter((todo) =&amp;gt; !todo.isDone).length;
  const isDone = count === 0 &amp;amp;&amp;amp; todos.length !== 0;

  return { count, colorSetArr, isDone };
};

export default useTodoInfo;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달력 아이템 렌더링에 필요한 값들을 제공하는 로직이다. 완료한 투두들의 색깔을 가공해서 리턴한다. 빈도수로 정렬한 후에 set으로 중복을 제거하도록 했다.&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;CalenderItem.tsx&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1669358947672&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface CalenderItemProps extends ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt; {
  date: string;
  userId: string;
  isSelected: boolean;
}

const CalenderItem = ({
  date,
  userId,
  isSelected,
  ...props
}: CalenderItemProps) =&amp;gt; {
  const { count, colorSetArr, isDone } = useTodoInfo(date, userId);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button {...props}&amp;gt;
        &amp;lt;span className=&quot;count&quot;&amp;gt;{count !== 0 &amp;amp;&amp;amp; count}&amp;lt;/span&amp;gt;
        &amp;lt;TodoIconSvg colors={colorSetArr} /&amp;gt;
        {isDone &amp;amp;&amp;amp; &amp;lt;CheckIcon className=&quot;check&quot; /&amp;gt;}
      &amp;lt;/button&amp;gt;
      &amp;lt;span className=&quot;date&quot;&amp;gt;{dayjs(date).date()}&amp;lt;/span&amp;gt;
    &amp;lt;/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&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;나 살짝 천잰듯ㅋ&lt;/p&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> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/88</guid>
      <comments>https://9yujin.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 25 Nov 2022 15:59:00 +0900</pubDate>
    </item>
    <item>
      <title>[데브톡] 재미없는 군대얘기, 개발을 시작한지 1년이 되었다 (22.10.13)</title>
      <link>https://9yujin.tistory.com/87</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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/watch?v=8NBs-3vH4F0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/FLs0e/hyQzeLYpiw/rOuK7cwKxGNWoIAfl5tz8k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/8NBs-3vH4F0&quot; width=&quot;860&quot; height=&quot;484&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;2022년 10월 13일에 열린 GDSC 홍익대학교 두번째 데브톡 세션 발표 영상입니다.&lt;/p&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년정도 됐어요. 그동안 어떤 고민을 했고, 어떤 길을 걸어왔는지. 여러분들과 제가 GDSC에서 어떤 걸 기대하고 얻어가고 싶은지를 얘기해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 로 시작하는 주제로 발표를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  긴호흡/GDSC</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/87</guid>
      <comments>https://9yujin.tistory.com/87#entry87comment</comments>
      <pubDate>Sun, 13 Nov 2022 00:57:55 +0900</pubDate>
    </item>
    <item>
      <title>[React] 다시 만드는 todo list (투두메이트 클론) (3 - 달력 구현하기)</title>
      <link>https://9yujin.tistory.com/86</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;미루고 미루던 투두메이트 달력을 구현해봅시다. 프론트 공부를 하면서 한번쯤은 만들어야하는 관문 같은 느낌이 있는데, 이번 기회에 해보는거로. dayjs 라이브러리를 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-11 오후 9.45.59.png&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;1590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSOFZU/btrQ1lSDbrb/YqApmSUayiOKlKhzC8qSV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSOFZU/btrQ1lSDbrb/YqApmSUayiOKlKhzC8qSV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSOFZU/btrQ1lSDbrb/YqApmSUayiOKlKhzC8qSV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSOFZU%2FbtrQ1lSDbrb%2FYqApmSUayiOKlKhzC8qSV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1960&quot; height=&quot;1590&quot; data-filename=&quot;스크린샷 2022-11-11 오후 9.45.59.png&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;1590&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;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Calender.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const Calender = () =&amp;gt; {
  const [selectedDay, setSelectedDay] = useState&amp;lt;string&amp;gt;(
    dayjs().format('MM/DD/YY'),
  );

  const handleSelectDate = (v: string) =&amp;gt; {
    setSelectedDay(v);
  };

  const board = renderCalenderBoard(selectedDay, handleSelectDate);

  // ... 생략

  return (
    &amp;lt;Wrapper&amp;gt;
      {/* 생략 */}
      &amp;lt;Board&amp;gt;{board}&amp;lt;/Board&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
};

export default Calender;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 렌더링 시 오늘 날짜로 선택된 날짜 상태를 초기화한다. &lt;code&gt;board&lt;/code&gt;는 댤력의 내용 요소들인데, 코드가 길어져서 별도의 함수로 분리했다.&lt;/p&gt;
&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;renderCalenderBoard.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const renderCalenderBoard = (
  selectedDay: string,
  handleSelectDate: (v: string) =&amp;gt; void,
) =&amp;gt; {
  const initArr = (firstDay: number, daysInMonth: number) =&amp;gt; {
    return Array.from({ length: firstDay + daysInMonth }, (v, i) =&amp;gt;
      i &amp;lt; firstDay
        ? null
        : dayjs(selectedDay)
            .startOf('month')
            .set('date', i - firstDay + 1)
            .format('MM/DD/YY'),
    );
  };

  const [arr, setArr] = useState&amp;lt;(string | null)[]&amp;gt;([null]);

  useEffect(() =&amp;gt; {
    const firstDay = dayjs(selectedDay).startOf('month').day();
    const daysInMonth = dayjs(selectedDay).daysInMonth();
    setArr(initArr(firstDay, daysInMonth));
  }, [selectedDay]);

  const content = arr.map((v, i) =&amp;gt; (
    &amp;lt;Item key={v ? v.toString() : `${v}${i}`} isSelected={selectedDay === v}&amp;gt;
      {v &amp;amp;&amp;amp; ( //TODO
        &amp;lt;div onClick={() =&amp;gt; handleSelectDate(v)}&amp;gt;
          &amp;lt;TodoCheck fill=&quot;#DBDDDF&quot; /&amp;gt;
          &amp;lt;span&amp;gt;{dayjs(v).date()}&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/Item&amp;gt;
  ));

  return content;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 아이디어는 이렇다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 칸을 모두 담는 배열을 하나 만들고, grid를 이용해서 7개씩 가로로 나열한다.&lt;/li&gt;
&lt;li&gt;해당 월 첫째날의 요일을 받고, 그 요일값 보다 작은 인덱스일 때는 null을 담는다. 아닐 때는 &lt;code&gt;'MM/DD/YY'&lt;/code&gt; 포맷의 문자열을 담는다. &lt;code&gt;set('date', i - firistDay + 1)&lt;/code&gt; 메소드로 각 인덱스마다 맞는 날짜가 들어가도록 계산했다.&lt;/li&gt;
&lt;li&gt;null이 아닐때만 Item 컴포넌트를 렌더링하면, 달력처럼 잘 나올것!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dayjs에서 제공하는 메소드들을 적절히 사용했다. 막 복잡한건 아니라서 어떠한 라이브러리 없이도 충분히 구현할 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투두메이트에서 이전 달로 갈때는 마지막 일이, 다음 달로 가면 첫 날이 선택된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;  const handlePrevMonth = () =&amp;gt; {
    const newDate = dayjs(selectedDay)
      .subtract(1, 'month')
      .endOf('month')
      .format('MM/DD/YY');
    setSelectedDay(newDate);
  };

  const handleNextMonth = () =&amp;gt; {
    const newDate = dayjs(selectedDay)
      .add(1, 'month')
      .startOf('month')
      .format('MM/DD/YY');
    setSelectedDay(newDate);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;endOf&lt;/code&gt;와 &lt;code&gt;startOf&lt;/code&gt; 메소드들을 적절히 사용하면 간단하게 나타낼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;set(unit : dayjs.UnitType, value : number)&lt;/code&gt; : 날짜 객체의 원하는 값으로 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endOf(unit : OpUnitType)&lt;/code&gt; : 인자에 해당하는 단위의 마지막 값으로 설정. endOf('month')로 해당 월의 일수를 알 수 있다. &lt;code&gt;startOf()&lt;/code&gt;도 똑같은 방식으로 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add(value : number, unit : ManipulateType)&lt;/code&gt; : 날짜 및 시간을 더함&lt;/li&gt;
&lt;/ul&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;Nov-11-2022 22-25-36.gif&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VKgmi/btrQYPIaAMO/Mjus9Hu6w0le1B5Pkr0Va0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VKgmi/btrQYPIaAMO/Mjus9Hu6w0le1B5Pkr0Va0/img.gif&quot; data-alt=&quot;짠&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VKgmi/btrQYPIaAMO/Mjus9Hu6w0le1B5Pkr0Va0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/VKgmi/btrQYPIaAMO/Mjus9Hu6w0le1B5Pkr0Va0/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;400&quot; height=&quot;400&quot; data-filename=&quot;Nov-11-2022 22-25-36.gif&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;짠&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/86</guid>
      <comments>https://9yujin.tistory.com/86#entry86comment</comments>
      <pubDate>Fri, 11 Nov 2022 22:28:04 +0900</pubDate>
    </item>
    <item>
      <title>[뱅키즈] 8. Applike한 UX를 위한 고민</title>
      <link>https://9yujin.tistory.com/85</link>
      <description>&lt;p data-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;이런 욕심은 뱅키즈를 시작하기 전 고스락 프로젝트를 할때도 마찬가지였다. 에브리타임과 카톡 등으로 홍보되고 공유되는 서비스 특성상 모바일로 이용하는 사용자들이 많다고 예상했기 때문에, 최대한 앱과 같은 느낌을 내보기 위해 노력해왔다. 나중에 알았지만 이런 고민을 했던 사람이 꽤 많았는지 'Applike' 라는 용어도 있었다.&lt;/p&gt;
&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;b&gt;1. 레이아웃 구성 (Foreground, Background)&lt;/b&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHAb1/btrOCsH8MDE/iCTxC6gjS2qf5Ai9L263mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHAb1/btrOCsH8MDE/iCTxC6gjS2qf5Ai9L263mk/img.png&quot; data-alt=&quot;Background / Foreground&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHAb1/btrOCsH8MDE/iCTxC6gjS2qf5Ai9L263mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHAb1%2FbtrOCsH8MDE%2FiCTxC6gjS2qf5Ai9L263mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1636&quot; height=&quot;787&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;787&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Background / Foreground&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 페이지들은 두 부류로 나뉜다. 홈, 돈길 모아보기, 마이페이지 등의 메인화면들은 페이지 스택의 항상 아래에 있는 &lt;code&gt;Background&lt;/code&gt;로. 그 메인화면들에 있는 버튼들을 클릭해 이동한 다른 화면들은 &lt;code&gt;Foreground&lt;/code&gt;로 분류했다. Foreground는 Background 위에 스택처럼 쌓인다. 보통의 애플리케이션에서 작동하는 방식을 따라해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// 화면 상단에 'AppBar'를 함께 랜더링 하는 activity stack의 상위 UI template
function ForegroundTemplate({
  label,
  children,
  to,
  customEvent,
}: ForegroundTemplateProps) {
  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;AppBar label={label} to={to} customEvent={customEvent} /&amp;gt;
      &amp;lt;Screen&amp;gt;{children}&amp;lt;/Screen&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}

// 화면 하단에 'TapBar'를 함께 랜더링 하는 activity stack의 하위 UI template
function BackgroundTemplate({ children }: BackgroundTemplateProps) {
  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;TabBar /&amp;gt;
      &amp;lt;Screen&amp;gt;{children}&amp;lt;/Screen&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 전달받은 피그마 디자인을 보고 코드로 옮기며 많이 고민했다. 뭐 이런식이다. &lt;code&gt;BackgroundTemplate&lt;/code&gt;에서는 항상 하단 탭바를 렌더링하고, &lt;code&gt;ForegroundTemplate&lt;/code&gt;에서는 상단 앱바를 렌더링한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 라우팅 간 애니메이션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱에서는 화면 간 이동시, 부드러운 애니메이션과 함께 나타난다. &lt;code&gt;React-transition-group&lt;/code&gt; 라이브러리를 사용해서 구현했다.&lt;/p&gt;
&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;a href=&quot;https://9yujin.tistory.com/73&quot;&gt;[뱅키즈] 6. React transition group 라우팅 트랜지션 (1) - 도입하기&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://9yujin.tistory.com/81&quot;&gt;[뱅키즈] 7. React transition group 라우팅 애니메이션 (2) - 디테일 잡기&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 정보가 생각보다 별로 없었고, 내 경우처럼 완벽히 앱처럼 동작하는 트랜지션을 구현해본 기록을 보기 힘들었다. 그래서 더 세세히 기록을 하려고 노력했다. 위의 링크된 글에서 자세한 설명을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img (2).gif&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt8Kns/btrQqM5JvZS/V3ZKUrAGt6JLgmi57U3MYK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt8Kns/btrQqM5JvZS/V3ZKUrAGt6JLgmi57U3MYK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt8Kns/btrQqM5JvZS/V3ZKUrAGt6JLgmi57U3MYK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dt8Kns/btrQqM5JvZS/V3ZKUrAGt6JLgmi57U3MYK/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;1320&quot; height=&quot;802&quot; data-filename=&quot;img (2).gif&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 위에서 작성했던 &lt;code&gt;Foreground&lt;/code&gt; 페이지와 &lt;code&gt;Background&lt;/code&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뒤로가기 시에는 반대방향의 애니메이션이 나온다. (포개졌던 페이지가 다시 나가는 듯한)&lt;/li&gt;
&lt;li&gt;위에 포개져있는 페이지가 이동할땐 더 많은 거리를 움직인다. (Parallax한 애니메이션)&lt;/li&gt;
&lt;li&gt;페이지간 이동과 일반적인 step이 있는 UI의 애니메이션이 &lt;a href=&quot;https://9yujin.tistory.com/81?category=1048070#%EB%-D%BC%EC%-A%B-%ED%-C%--%EC%-D%B-%--%EC%--%--%EB%-B%-C%-C%--state%--%EB%B-%--%EA%B-%BD%EC%--%--%--%EB%--%B-%EB%A-%B-%--%EC%--%A-%EB%-B%--%EB%A-%--%EC%-D%B-%EC%--%--%--%EB%--%A-%EA%B-%B-&quot;&gt;다르다&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-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;b&gt;3. 바텀시트 활용&lt;/b&gt;&lt;/h2&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-10-28 오후 5.35.49.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IKl08/btrPPMK1CSC/0RcTraMBThoIaD8Lkz6kjK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IKl08/btrPPMK1CSC/0RcTraMBThoIaD8Lkz6kjK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IKl08/btrPPMK1CSC/0RcTraMBThoIaD8Lkz6kjK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/IKl08/btrPPMK1CSC/0RcTraMBThoIaD8Lkz6kjK/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;640&quot; height=&quot;466&quot; data-filename=&quot;화면 기록 2022-10-28 오후 5.35.49.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;466&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;code&gt;react-spring-bottomsheet&lt;/code&gt; 라이브러리를 사용하고 있다. 외부 라이브러리 특성상 커스텀할 수 있도록 지정된 스타일 외에는 바꾸기 힘들었지만, 직접 css 요소를 &lt;code&gt;!important&lt;/code&gt;로 강제해 바꾸어 쓰고 있다.&lt;/p&gt;
&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://9yujin.tistory.com/70&quot;&gt;돈길 계약하기 과정&lt;/a&gt;에서 사용하는 바텀시트 이외에 서비스 곳곳에서 사용하는 모든 바텀시트는 전역으로 관리되고 있다. 고스락 2차 프로젝트에서 모달 컴포넌트를 전역으로 관리하는 방식과 비슷하다. 때문에 따로 기록을 하진 않았음.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-05 오후 2.12.32.png&quot; data-origin-width=&quot;2402&quot; data-origin-height=&quot;1534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8j7Yc/btrQrrtjdOg/ukIZMOwZHt2q7McaRy0TD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8j7Yc/btrQrrtjdOg/ukIZMOwZHt2q7McaRy0TD1/img.png&quot; data-alt=&quot;figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8j7Yc/btrQrrtjdOg/ukIZMOwZHt2q7McaRy0TD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8j7Yc%2FbtrQrrtjdOg%2FukIZMOwZHt2q7McaRy0TD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2402&quot; height=&quot;1534&quot; data-filename=&quot;스크린샷 2022-11-05 오후 2.12.32.png&quot; data-origin-width=&quot;2402&quot; data-origin-height=&quot;1534&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;figma&lt;/figcaption&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;특수한 경우 3개를 제외한 모든 바텀시트를 네가지 종류의 컴포넌트로 나누었다. type과 mainAction, subAction 등의 props들을 공통적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;  // '링크가 만료되었어요' 바텀시트
  const openExpiredNoticeSheet = (handler: () =&amp;gt; void) =&amp;gt; {
    setOpenBottomSheet({
      sheetContent: 'Notice',
      contentProps: {
        type: 'expired',
        onMainActionClick: handler,
      },
    });
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바텀시트를 여는 함수는 이렇게 추상화해서 사용할 수 있다. &lt;code&gt;sheetContent&lt;/code&gt;로는 위에서 분류한 네가지 컴포넌트 중 하나를, &lt;code&gt;contentProps&lt;/code&gt;로는 그 컴포넌트에 들어갈 props를 작성한다. 간단하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 웹에서 Input 태그, 키패드 위 버튼 다루기&lt;/b&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;table data-ke-align=&quot;alignCenter&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;1446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkCGZe/btrQyWsneC6/UQi9s0RjwCBqy6Q5shNcj1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkCGZe/btrQyWsneC6/UQi9s0RjwCBqy6Q5shNcj1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkCGZe/btrQyWsneC6/UQi9s0RjwCBqy6Q5shNcj1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dkCGZe/btrQyWsneC6/UQi9s0RjwCBqy6Q5shNcj1/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;260&quot; height=&quot;528&quot; data-filename=&quot;image.gif&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;1446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/th&gt;
&lt;th&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-11-06 오후 11.30.16.gif&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBVr4x/btrQr3GGjzZ/VMppEeLpcK0IzcSA6VP80K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBVr4x/btrQr3GGjzZ/VMppEeLpcK0IzcSA6VP80K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBVr4x/btrQr3GGjzZ/VMppEeLpcK0IzcSA6VP80K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cBVr4x/btrQr3GGjzZ/VMppEeLpcK0IzcSA6VP80K/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;260&quot; height=&quot;527&quot; data-filename=&quot;화면 기록 2022-11-06 오후 11.30.16.gif&quot; data-origin-width=&quot;358&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 페이지에 진입 시 입력창에 autoFocus되고, 각 입력이 완료되면 유효성 검사 후에 자동으로 다음 input으로 focus된다. 생년월일의 양식을 똑같이 맞추지 않아도 클라이언트 단에서 후가공 후에 통일된 형태로 서버로 전송하도록 한다.&lt;/p&gt;
&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;뱅키즈에서는 해당사항이 없었지만, 모바일 웹을 구현하면서 다들 한번쯤 만났을 문제가 있다. 키패드가 올라올때 안드로이드 브라우저와 사파리 브라우저는 다르게 작동한다. 하단 버튼을 fix 등으로 고정했을때는 뷰포트의 하단에 자리잡게 된다. 하지만 안드로이드 브라우저에서는 키패드가 올라올때, 뷰포트가 화면에서 키패드를 제외한 부분으로 바뀐다. 그렇기 때문에 하단에 고정한 버튼이 키패드 위로 올라오게 되는 것. 물론 토스 같은 서비스를 보면 키패드 위에 버튼이 올라오도록 의도된 디자인이 있지만, 그렇지 않은 경우에는 상당한 멘붕 포인트가 된다.&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;1080&quot; data-origin-height=&quot;2220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QOMHk/btrQqmM4Pf6/vHKPRTzNWwh0kVh0S7Dssk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QOMHk/btrQqmM4Pf6/vHKPRTzNWwh0kVh0S7Dssk/img.jpg&quot; data-alt=&quot;고스락 2차&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QOMHk/btrQqmM4Pf6/vHKPRTzNWwh0kVh0S7Dssk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQOMHk%2FbtrQqmM4Pf6%2FvHKPRTzNWwh0kVh0S7Dssk%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;260&quot; height=&quot;534&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2220&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;고스락 2차 프로젝트에서 전화번호와 이름을 받는 페이지이다. 안드로이드는 IOS와 달리, 사진과 같이 하단 고정 버튼이 키패드 위로 올라온다. 이 때 버튼이 input Form 부분을 가리는 경우가 있다. 개발자 대부분이 아이폰을 사용했기 때문에 QA 이전에는 미처 알지 못했던 부분이었다. 뷰포트의 높이가 바뀌는걸 감지해서 일정 높이 이하일 때는 키패드가 올라왔다고 판단해 버튼을 보여주지 않는 것으로 해결했다. 버튼이 없더라도 정해진 글자수만큼 입력하면 자동으로 blur 되도록 처리했기 때문에 불편함은 전혀 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 토글 버튼 (로컬 스토리지 캐싱)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-06-2022 23-50-27.gif&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Q9GX/btrQAv86b9z/qlJIKYWtN28oUE8ummTp61/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Q9GX/btrQAv86b9z/qlJIKYWtN28oUE8ummTp61/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Q9GX/btrQAv86b9z/qlJIKYWtN28oUE8ummTp61/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/1Q9GX/btrQAv86b9z/qlJIKYWtN28oUE8ummTp61/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;1206&quot; height=&quot;788&quot; data-filename=&quot;Nov-06-2022 23-50-27.gif&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;788&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;알림 동의 여부 정보는 서버에서 가지고 있다. 알림 설정 페이지에 들어왔을 때 데이터를 패칭하기엔 사용자가 들어올때마다 매번 로딩을 기다리도록 하는게 마음에 들지 않았다. 알림 동의 여부는 자주 변하지 않는 값이므로 로컬 스토리지에 저장된 값을 우선적으로 보여주도록 했다. 그 이후 만약 사용자가 토글하면 서버로 Patch 요청을 하게 되고, 그 응답값으로 다시 서버 상태와 클라이언트 상태(로컬 스토리지)를 동기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;  const [alert, setAlert] = useState&amp;lt;IOptInDTO&amp;gt;(getLocalStorage('alert') || {});

  // 로컬 저장소에 값 없을 시 값 패칭해서 사용
  const syncAlert = (data: IOptInDTO) =&amp;gt; {
    setLocalStorage('alert', data);
    setAlert(data);
  };
  const { data } = useQuery(queryKeys.USER_OPTIN, userAPI.getUserOptIn, {
    enabled: isEmptyObject(alert),
    onSuccess: (res) =&amp;gt; syncAlert(res),
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. highlight 잔상 제거&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰에서 버튼을 터치했을 때 나오는 파란색 또는 회색의 잔상을 css 설정을 통해 제거할 수 있다. 감쪽같은 앱에 한발짝.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;noSelect {
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
noSelect:focus {
    outline: none !important;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>  긴호흡/뱅키즈</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/85</guid>
      <comments>https://9yujin.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 7 Nov 2022 00:00:52 +0900</pubDate>
    </item>
    <item>
      <title>[React] 다시 만드는 React-Messenger (selectorFamily, FaCC)</title>
      <link>https://9yujin.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;세오스 리액트 메신저 과제를 준비하면서 6개월 전에 제출했던 과제를 다시 보았다. 시간에 쫓겨가면서 하다보니, 굉장히 더러운 부분이 꽤 있다. 이런저런 기능을 추가하면서 이런저런 옵션과 props가 들어가게 되고, 그렇게 몬스터 컴포넌트가 되어간다. 스타일링을 할때도 원하는 대로 될때까지 수정에 수정을 거듭하다보니 매우 복잡해졌다. 그래서 이번 포스팅은 6개월 전 과제물 리팩토링의 기록!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Recoil selectorFamily&lt;/li&gt;
&lt;li&gt;커스텀훅을 이용해 로직 추상화&lt;/li&gt;
&lt;li&gt;Funtion as Child Component&lt;/li&gt;
&lt;li&gt;타입 가드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7w1CO/btrQmRLsfUV/f5uy4IFktvem7MmYar7vck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7w1CO/btrQmRLsfUV/f5uy4IFktvem7MmYar7vck/img.png&quot; data-alt=&quot;인제 생각해보니,&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7w1CO/btrQmRLsfUV/f5uy4IFktvem7MmYar7vck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7w1CO%2FbtrQmRLsfUV%2Ff5uy4IFktvem7MmYar7vck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1219&quot; height=&quot;814&quot; data-origin-width=&quot;1219&quot; data-origin-height=&quot;814&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 네이밍&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 시급했다. 계속 기능을 추가하면서 급급하게 변수를 만들어갔다. Room, chats, chatList 등의 이름이 별다른 기준 없이 사용되는게 스스로도 감당하기 힘들 정도였다. 개인프로젝트임에도 네이밍은 꽤 중요하다..!&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;413&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6n5HZ/btrQlq10Ap5/QKKVEOlkMvek5cBRRzIAsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6n5HZ/btrQlq10Ap5/QKKVEOlkMvek5cBRRzIAsK/img.png&quot; data-alt=&quot; &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6n5HZ/btrQlq10Ap5/QKKVEOlkMvek5cBRRzIAsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6n5HZ%2FbtrQlq10Ap5%2FQKKVEOlkMvek5cBRRzIAsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;413&quot; height=&quot;73&quot; data-origin-width=&quot;413&quot; data-origin-height=&quot;73&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;위 사진이 바로 그 단적인 예다. atom의 이름은 'chatState'인데 key는 'chats'. 심지어 타입은 'RoomType'의 배열이다. 뭐 이런 쓰레기같은 코드가 있을 수가. 최대한 명확히 기준을 잡고 타입과 상태들의 이름부터 지었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export interface IChat {
  userId: number;
  content: string;
  date: string;
  like: boolean;
  chatId: string;
}
export interface IChatting {
  chattingId: number;
  userIdList: number[];
  chatList: IChat[];
}

export interface IUser {
  userId: number;
  userName: string;
  profileImage: string;
  statusMessage: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챗 하나하나는 &lt;code&gt;IChat&lt;/code&gt;, 채팅방 관련 타입은 &lt;code&gt;IChatting&lt;/code&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chatting의 chatList에는 &lt;code&gt;IChat&lt;/code&gt;형태의 배열이 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import chattingsData from '../mocks/chattingsData.json';
import userData from '../mocks/userData.json';
import friendsData from '../mocks/friendsData.json';

export const chattingsState = atom&amp;lt;IChatting[]&amp;gt;({
  key: 'chattings',
  default: chattingsData.chattings,
});

export const userState = atom&amp;lt;IUser&amp;gt;({
  key: 'user',
  default: userData.users[0],
});

export const friendsState = atom&amp;lt;IUser[]&amp;gt;({
  key: 'friends',
  default: friendsData.users,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 atom에는 &lt;code&gt;key + State&lt;/code&gt; 형식으로 이름을 붙인다. 내 정보(user)와 친구정보(friends)를 분리해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅목록은 Chattings, 각 채팅방들은 Room이라는 네이밍. Chattings를 &lt;code&gt;map&lt;/code&gt;으로 돌려서 리스트를 렌더링해주는데, map의 첫번째 인자로 chatting이라는 이름을 사용해주었다. Room과 chatting 두 이름을 모두 쓰는 것에 대해서 고민을 꽤 했었다. Room은 컴포넌트 이름, chatting은 상태의 이름으로 생각하고 지금과 같이 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 리코일 활용하기 (selectorFamily)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 문서에서는 &lt;code&gt;Selector&lt;/code&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;p data-ke-size=&quot;size16&quot;&gt;Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하는데.. 크게 와닿지는 않다. 파생된 상태를 가져온다(get)는건, atom을 곧대로 가져오는게 아니라 원하는대로 가공해서 가져올 수 있다는 것 - 이라고 이해했다. SQL에서 &lt;code&gt;SELECT&lt;/code&gt;문을 이용해 데이터베이스에서 원하는 값을 추출하는 것과 비슷하다. 그뿐만 아니라 SQL의 aggregate함수처럼 계산된 값을 받아올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SelectorFamily&lt;/code&gt;는 &lt;code&gt;Selector&lt;/code&gt;와 비슷한 패턴이지만, &lt;code&gt;get&lt;/code&gt;과 &lt;code&gt;set&lt;/code&gt;의 콜백에 인자를 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;atoms/user.ts&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const userStateByUserId = selectorFamily&amp;lt;IUser, number&amp;gt;({
  key: 'userByUserId',
  get:
    (userId: number) =&amp;gt;
    ({ get }) =&amp;gt; {
      const user = get(userState);
      const friends = get(friendsState);
      if (userId === 0) {
        return user;
      } else {
        return friends.filter((friend) =&amp;gt; friend.userId === userId)[0];
      }
    },
});

export const usersStateByUserIdList = selectorFamily&amp;lt;IUser[], number[]&amp;gt;({
  key: 'usersByUserIdList',
  get:
    (userIdList: number[]) =&amp;gt;
    ({ get }) =&amp;gt; {
      return userIdList.map((userId) =&amp;gt; get(userStateByUserId(userId)));
    },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 이런식으로 사용할 수 있다. userId를 인자로 받아 해당 유저의 정보만 가져오는 selector와 (그 selector를 사용하는) userId의 배열을 인자로 받아 유저들의 정보를 가져오는 selector이다.&lt;br /&gt;&lt;br /&gt;get 내에서 다른 atom의 상태를 참조할 수 있다. 내 정보와 친구 정보가 다른 atom으로 분리되어 있는데,하나의 selector에서 모두 불러와 리턴하도록 했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1 selector에서 set 사용하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const chattingStateByChattingId = selectorFamily&amp;lt;IChatting, number&amp;gt;({
  key: 'chattingByChattingId',
  get:
    (chattingId: number) =&amp;gt;
    ({ get }) =&amp;gt;
      get(chattingsState).filter(
        (chatting) =&amp;gt; chatting.chattingId === chattingId,
      )[0],
  set:
    (chattingId: number) =&amp;gt;
    ({ set }, newChatting) =&amp;gt; {
      set(chattingsState, (prev) =&amp;gt;
        prev.map((chatting) =&amp;gt;
          chatting.chattingId === chattingId
            ? (newChatting as IChatting)
            : chatting,
        ),
      );
    },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;get&lt;/code&gt;만 있을 땐 readonly한 함수만 제공하지만 &lt;code&gt;set&lt;/code&gt;을 활용한다면 selector에서 상태를 변경하는 함수도 쓸 수 있다. 그냥 &lt;code&gt;useRecoilState&lt;/code&gt;를 사용하는 것 처럼. 챗을 보내는 기능도 &lt;code&gt;selectorFamily&lt;/code&gt; 안에서 구현해보았다. 해당하는 채팅방의 id와 newChatting을 인자로 보낸다. 어떤 식으로 보내야 하지??&lt;/p&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;535&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz0P4U/btrQlIIemZY/6fjtRyXe7YJ3Twm9AhRIUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz0P4U/btrQlIIemZY/6fjtRyXe7YJ3Twm9AhRIUk/img.png&quot; data-alt=&quot;https://recoiljs.org/docs/api-reference/utils/selectorFamily/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz0P4U/btrQlIIemZY/6fjtRyXe7YJ3Twm9AhRIUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz0P4U%2FbtrQlIIemZY%2F6fjtRyXe7YJ3Twm9AhRIUk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;923&quot; height=&quot;535&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;535&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://recoiljs.org/docs/api-reference/utils/selectorFamily/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set 속성에는 위의 타입의 함수가 들어갈 수 있다. 정리하면 &lt;code&gt;P =&amp;gt; ({set}, newValue) =&amp;gt; void;&lt;/code&gt; 의 형태이다. 세터와 새로운 값을 인자로 보낸다. 위에 중첩된 함수의 인자는 &lt;code&gt;selectorFamily&lt;/code&gt;의 인자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;useChat.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const useChat = (chattingId: number) =&amp;gt; {
  const currentId = useRecoilValue(currentState);
  const [chatting, setChatting] = useRecoilState(
    chattingStateByChattingId(chattingId),
  );

  const sendChat = (value: string) =&amp;gt; {
    if (value.length !== 0 &amp;amp;&amp;amp; value.replace(/ /g, '').length !== 0) {
      const newChat: IChat = {
        userId: currentId,
        content: value,
        date: dayjs().format(),
        like: false,
        chatId: uuid(),
      };

      setChatting({ ...chatting, chatList: [...chatting.chatList, newChat] });
    }
  };

  return { sendChat };
};

export default useChat;&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;&lt;code&gt;useRecoilState(chattingStateByChattingId(chattingId));&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 커스텀훅으로 로직 분리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 내에서 UI 렌더링을 담당하는 부분과 비즈니스 로직을 최대한 분리하는데에 관심이 있다. 이러한 관심사의 분리의 중요성을 다루는 아티클들이 꽤 많았다. 조금 더 선언적인 코드를 통해 컴포넌트가 어떤 일을 하는지 명확하게 알 수 있고, 변경이 잦은 UI 컴포넌트와 데이터를 다루는 로직을 분리함으로서 변경에 용이한 좋은 코드가 될 수 있다. 대체로 이런 이야기를 적어놓았다.&lt;br /&gt;&lt;br /&gt;바로 위에서 작성했던 useChat이 그 분리된 로직이다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;RoomFooter.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const RoomFooter = ({ chattingId }: { chattingId: number }) =&amp;gt; {
  const inputRef = useRef&amp;lt;HTMLTextAreaElement&amp;gt;(null);
  const { value, onChange, resetValue } = useInput('');
  const { sendChat } = useChat(chattingId);

  const focusInput = () =&amp;gt; inputRef.current?.focus();

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

  const handleSendChat = () =&amp;gt; {
    sendChat(value);
    resetValue();
    focusInput();
  };

  const onEnter = (e: KeyboardEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; {
    if (e.key === 'Enter' &amp;amp;&amp;amp; !e.shiftKey) {
      if (e.nativeEvent.isComposing === false) {
        e.preventDefault();
        handleSendChat();
      }
    }
  };

  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;MessageInput
          value={value}
          onChange={onChange}
          ref={inputRef}
          onKeyDown={onEnter}
        /&amp;gt;
        &amp;lt;SendButton
          onClick={handleSendChat}
          disabled={value.length === 0 ? true : false}
        &amp;gt;
          전송
        &amp;lt;/SendButton&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
};

export default RoomFooter;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 좋은 코드라고 말하긴 뭐하지만, UI를 다루는 로직만 남아있고 상태를 다루는 로직은 전부 커스텀훅으로 추상화되어 있도록 했다.&lt;br /&gt;이외에도 스크롤 위치를 다루는 로직 등을 커스텀훅으로 빼버리면서 컴포넌트를 더 간결하게 만들 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1 리렌더링 문제?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 커스텀훅을 이용해 각 하나의 atom을 제어하는 함수들을 모아놓고 컴포넌트에서 불러와 사용했었다. 그 컴포넌트에선 사용하지 않지만 훅에서 구독하고있는 다른 상태가 변경될때마다 불필요한 상황에서도 리렌더링되는 경우가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 chatting atom에 있는 userIdList로 user 정보를 가져오기 위해서는, chattings와 friends 모두를 구독하고 있어야 했다. user 정보를 가져오는 로직이기 때문에 &lt;code&gt;useUsers&lt;/code&gt;라는 훅에 만들고 반환했었다. 문제는 단순히 users 상태를 가져오기 위해서 &lt;code&gt;useUsers&lt;/code&gt; 훅을 사용하는 컴포넌트에서 생겼다. 해당 컴포넌트에서는 chattings 상태에 관심이 없지만, useUsers 훅에서 chattings를 구독하고 있기 때문에 그 상태가 변경될때마다 쓸데없는 리렌더링이 일어나는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해서 리코일의 &lt;code&gt;selectorFamily&lt;/code&gt;를 더 적극적으로 사용하게 되었다. 각각의 로직을 &lt;code&gt;selector&lt;/code&gt; 내에서 처리할 수 있게 되면서 불필요한 렌더링을 막을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. F&lt;/b&gt;&lt;b&gt;unction as Child Component&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅목록과 친구목록의 item을 같은 컴포넌트를 재사용하려고 했다. 저번 과제에는 ListItem 컴포넌트 하나를 사용하는 RoomListItem, FriendsListItem 두 컴포넌트를 만들어 사용했다. 상위 컴포넌트를 하나 두고 상태를 가공해서 props으로 넘겨주어야 하기 때문이었다 (동일한 ListItemProps 타입의 props를 넘겨주어야 했는데, 각 페이지에서 갖고 있는 상태는 users와 chattings로 다르다). 되려 불필요하게 코드가 분리되어서 오히려 불편해지는 감이 있었다.&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;668&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JyU1Q/btrQr6u1kze/HLVkPsUFHPfRpy0R4CkvzK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JyU1Q/btrQr6u1kze/HLVkPsUFHPfRpy0R4CkvzK/img.gif&quot; data-alt=&quot;기존 과제 코드. 번거롭게 자식 컴포넌트로 타고 타고 넘어가야 하는 느낌&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JyU1Q/btrQr6u1kze/HLVkPsUFHPfRpy0R4CkvzK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/JyU1Q/btrQr6u1kze/HLVkPsUFHPfRpy0R4CkvzK/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;365&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;406&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;이번 과제에서는 같은 파일 내에서 작성하되 ListItem 컴포넌트를 재사용할 수 있는 방법을 고민해보았다. Function as Child Component 라는 패턴을 적용했다. Headless 컴포넌트의 일종으로, 상태는 제어하지만 스타일링은 담당하지 않는 컴포넌트를 말한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Friends.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const Friends = () =&amp;gt; {
  const friends = useRecoilValue(friendsState);
  const me = useRecoilValue(userState);

  return (
    &amp;lt;Wrapper&amp;gt;
      {/* ...생략 */}
      &amp;lt;MyProfile&amp;gt;
        &amp;lt;Squircle imageUrl={me.profileImage} selected={false} size={55} /&amp;gt;
        &amp;lt;div&amp;gt;{me.userName}&amp;lt;/div&amp;gt;
      &amp;lt;/MyProfile&amp;gt;
      &amp;lt;Divider /&amp;gt;
      &amp;lt;SubHeading&amp;gt;친구 {friends.length}&amp;lt;/SubHeading&amp;gt;
      {friends.map((friend) =&amp;gt; (
        &amp;lt;FriendListHeadless friend={friend} key={friend.userId}&amp;gt;
          {({ friend, handleClickListItem }) =&amp;gt; (
            &amp;lt;ListItem data={friend} handleClickListItem={handleClickListItem} /&amp;gt;
          )}
        &amp;lt;/FriendListHeadless&amp;gt;
      ))}
    &amp;lt;/Wrapper&amp;gt;
  );
};

export default Friends;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;friends 배열을 &lt;code&gt;map&lt;/code&gt;으로 돌려 하나씩 아이템을 렌더링한다. 특이한건 반환하고 있는 컴포넌트의 모양이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;lt;FriendListHeadless /&amp;gt;&lt;/code&gt; 라는 컴포넌트의 child에는 또다른 컴포넌트(ListItem)을 반환하는 함수가 들어간다. 그 함수는 friend 객체와 클릭이벤트 시 실행될 함수를 인자로 받아 ListItem의 props으로 집어넣는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;FriendListHeadless&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface FriendListHeadlessProps {
  friend: IUser;
  children: (args: any) =&amp;gt; JSX.Element;
}

const FriendListHeadless = ({ friend, children }: FriendListHeadlessProps) =&amp;gt; {
  const navigate = useNavigate();
  const chattingId = useRecoilValue(
    chattingStateByUserId(friend.userId),
  ).chattingId;

  return children({
    friend: friend,
    handleClickListItem: () =&amp;gt; navigate(`/room/${chattingId}`),
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Headless 컴포넌트 내에서 chattingId 상태를 받아온다. 해당 채팅방으로 &lt;code&gt;navigate&lt;/code&gt; 하는 함수를 작성했다. 그리고 children 컴포넌트의 props으로 넘겨준다. 굳이 한 컴포넌트가 더 필요한 이유는 chattings가 아닌 각 chatting의 상태가 필요했기 때문. &lt;code&gt;map&lt;/code&gt;으로 돌리고 있는 그 안에서 이루어져야했다. 번거로이 자식 컴포넌트로 타고타고 내려갈 필요 없이, Headless컴포넌트가 어떤 컴포넌트를 child로 두고있는지도 한눈에 볼 수 있는 것도 장점인 것 같다.&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;327&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/55GvV/btrQpSFgEy7/xAU1qOnaFR9Z4hEIXLm79k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/55GvV/btrQpSFgEy7/xAU1qOnaFR9Z4hEIXLm79k/img.png&quot; data-alt=&quot;친구 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/55GvV/btrQpSFgEy7/xAU1qOnaFR9Z4hEIXLm79k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F55GvV%2FbtrQpSFgEy7%2FxAU1qOnaFR9Z4hEIXLm79k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;327&quot; height=&quot;116&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;친구 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjAFxt/btrQqk9aRMk/HzMn5UFoRt3cjneYkZk510/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjAFxt/btrQqk9aRMk/HzMn5UFoRt3cjneYkZk510/img.png&quot; data-alt=&quot;채팅 목록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjAFxt/btrQqk9aRMk/HzMn5UFoRt3cjneYkZk510/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjAFxt%2FbtrQqk9aRMk%2FHzMn5UFoRt3cjneYkZk510%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;327&quot; height=&quot;139&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;139&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.1 타입 가드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또다시 마주친 고민 포인트. 똑같은 ListItem 컴포넌트의 data props로 내려오는 객체가 달라질 수 있다. 채팅목록에서는 IChatting&amp;nbsp;타입, 친구목록에서는 IUser 타입의 객체이다. 프로필이미지와 사용자 이름은 공통으로 쓰지만 &lt;code&gt;IChatting&lt;/code&gt; 타입의 객체에는 userId 밖에 없기 때문에 따로 friends 상태에서 가져오는 등의 처리가 필요하다. 마지막으로 채팅목록에서는 상태메시지 대신 마지막 채팅과 그 날짜가 보여져야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;const isChattingType = (data: any): data is IChatting =&amp;gt; {
  return data.chattingId !== undefined;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;// const isChatting = isChattingType(data);

&amp;lt;InfoSub&amp;gt;
  {isChatting
    ? data.chatList[data.chatList.length - 1].content
    : data.statusMessage}
&amp;lt;/InfoSub&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 가드를 통해 조건문에서 객체의 타입을 좁혀 나갈 수 있다. 단순히 어떤 인자명은 어떠한 타입이다라는 값 (is 키워드)을 리턴하는 함수이다. 컴포넌트 내에서 타입가드 함수의 반환값을 사용할 수 있다.&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;br /&gt;&lt;br /&gt;채팅목록과 유저목록 모두에서 사용할 수 있도록 디자인되어있긴 하지만, 여전히 변경이 생기면 컴포넌트를 수정해야 한다. 애초에 컴포넌트를 도메인과 완전히 분리해서 { profileImage, mainText, subText, time? } 등의 범용적인 props를 받도록 하면 더 좋은 컴포넌트가 되려나..? &lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://jbee.io/web/components-should-be-flexible/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;변경에 유연한 컴포넌트&lt;/span&gt;&lt;/a&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;b&gt;5. 뇌 빼고 짰던 JSX, Styled-components&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;3389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xaqHa/btrQpV2662m/jo9lkEQgmPsBFOsloCfc41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xaqHa/btrQpV2662m/jo9lkEQgmPsBFOsloCfc41/img.png&quot; data-alt=&quot;(좌) 새로짠거 (우) 옛날거&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xaqHa/btrQpV2662m/jo9lkEQgmPsBFOsloCfc41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxaqHa%2FbtrQpV2662m%2Fjo9lkEQgmPsBFOsloCfc41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;638&quot; data-origin-width=&quot;1594&quot; data-origin-height=&quot;3389&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;JSX부터 Styled components 함수까지 뭐하나 제대로 된게 없는 코드였다. 나와 상대방의 말풍선이 서로 반대방향에 있어야했고, &lt;code&gt;(프사) - 내용 - 시간&lt;/code&gt; 의 순서도 나와 상대방이 반대로 되어있다. 그리고 한명이 연속해서 채팅을 보낼때, 첫번째로 보낸 말풍선에만 꼬리가 달려 있어야 했다. 그 꼬리의 스타일도 나와 상대방이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;계속해서 복잡한 조건을 하나씩 추가해가며 개발했다보니 점점 더러운 코드가 되어갔다. 이번 과제에서는 처음부터 열심히 고민하고 구성을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;code&gt;flex-end&lt;/code&gt;와 &lt;code&gt;flex-start&lt;/code&gt;를 이용해 우측정렬 좌측정렬을 간단히 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 요소의 순서는 flexbox에서 &lt;code&gt;order&lt;/code&gt; 속성을 이용해 css 내에서 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 말풍선 꼬리같은 복잡한 스타일은 따로 함수로 분리해 넣어준다.&lt;br /&gt;&lt;br /&gt;이렇게 36줄이었던 JSX 코드를 12줄로 줄일 수 있었다. Styled component 부분은 더욱 줄어들었다. 거의 두배 가까이 간결해진 코드를 볼 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/9yujin/react-messenger-16th&quot; target=&quot;_self&quot;&gt;&lt;span&gt; 이번에 리팩토링한 과제 코드&lt;/span&gt;&lt;/a&gt;와&lt;br /&gt;&lt;a href=&quot;https://github.com/9yujin/react-messenger-15th&quot; target=&quot;_self&quot;&gt;&lt;span&gt;이전에 했던 과제 코드&lt;/span&gt;&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;똑같은 기능이더라도 계속 고민해가며 리팩토링하는 경험이 중요하다.&lt;br /&gt;생각보다 많은 것을 얻어가는 과제였다.&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/84</guid>
      <comments>https://9yujin.tistory.com/84#entry84comment</comments>
      <pubDate>Fri, 4 Nov 2022 23:16:20 +0900</pubDate>
    </item>
    <item>
      <title>[React] 다시 만드는 todo list (투두메이트 클론) (2)</title>
      <link>https://9yujin.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;중간고사 기간 잘 보내셨나요.. 전 못보냈습니다. 흑흑&lt;br /&gt;다음 학기엔 무조건 기숙사에 들어가겠다는 생각을 다시 한번 해보면서, 이번주 WIL을 적고 있습니다.&lt;br /&gt;&lt;br /&gt;중간 휴식기간동안 짜잘짜잘하게 프로젝트를 건드려보았습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 이전에 있었던, 바텀시트 관련 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 TodoItem마다 있던 바텀시트를 더 상위로 올려서 관리하도록 했다. 이젠 어떤 투두에서 연 바텀시트인지 바로 알 수 없기 때문에, 선택된 투두를 전역상태로 관리하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;useBottomSheet.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useRecoilState } from 'recoil';
import { ITodoItem } from '../interfaces/ITodoItem';
import { bottomSheetState } from '../stores/bottomSheet';

function useBottomSheet(initial: boolean) {
&amp;nbsp;&amp;nbsp;const [bottomSheet, setBottomSheet] = useRecoilState(bottomSheetState);
&amp;nbsp;&amp;nbsp;const { isOpen, selectedItem } = bottomSheet;

&amp;nbsp;&amp;nbsp;function onOpen(item: ITodoItem) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setBottomSheet({ selectedItem: item, isOpen: true });
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;function onDismiss() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setBottomSheet({ selectedItem: null, isOpen: false });
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;return { isOpen, selectedItem, onOpen, onDismiss };
}

export default useBottomSheet;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열림/닫힘 상태와 선택된 투두를 리코일 atom으로 만들어 관리한다. 간단간단쓰.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Todo 관련 로직 추상화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI컴포넌트에서 최대한 비즈니스 로직을 분리하고, 커스텀훅을 이용해 넘겨받아 사용한다.&lt;br /&gt;저번주 스터디 시간에 보여드렸던 내용이죠??&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const useTodo = () =&amp;gt; {
&amp;nbsp;&amp;nbsp;const [todo, setTodo] = useRecoilState(todoState);

&amp;nbsp;&amp;nbsp;const insertTodo = (inputValue: string, category: ICategory) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (inputValue) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const newTodo = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label: inputValue,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id: uuid(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isDone: false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;category: category,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTodo((prev) =&amp;gt; [...prev, newTodo]);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;const editTodo = (inputValue: string, id: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (inputValue) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const index = todo.findIndex((v) =&amp;gt; v.id === id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const temp = [...todo];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;temp[index] = { ...temp[index], label: inputValue };
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTodo(temp);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;const toggleTodo = (id: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const index = todo.findIndex((v) =&amp;gt; v.id === id);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const temp = [...todo];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;temp[index] = { ...temp[index], isDone: !temp[index].isDone };
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTodo(temp);
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;const deleteTodo = (id: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setTodo(todo.filter((v) =&amp;gt; v.id !== id));
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;return { insertTodo, editTodo, toggleTodo, deleteTodo };
};

export default useTodo;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;투두 삭제하기 등의 함수를 커스텀훅을 통해 받아올 수 있고, 투두 상태(삭제할 투두의 id) 또한 전역으로 관리되기 때문에 프로젝트 어디에서라도 사용할 수 있다. 투두 컴포넌트와 떨어져있는 바텀시트 컴포넌트에서 받아와 사용하기에 좋다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 친구 목록 (문자열에서 이모지 다루기)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdLTbj/btrQb38n0Gt/wtiAzSk3K9GlpeKYZckEX1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdLTbj/btrQb38n0Gt/wtiAzSk3K9GlpeKYZckEX1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdLTbj/btrQb38n0Gt/wtiAzSk3K9GlpeKYZckEX1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bdLTbj/btrQb38n0Gt/wtiAzSk3K9GlpeKYZckEX1/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;442&quot; height=&quot;230&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 고민을 많이 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const initialState: IFriend[] = [
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userId: 'user1',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: ' 규진',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;profileImage: '',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;statusMessage: '어쩌다 갓생',
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userId: 'user2',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: '유진',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;profileImage: 'https://i.ibb.co/MgmDcz1/1021805078815985664.webp',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;statusMessage: '주니어 PM이 되기 위한 노력',
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userId: 'user3',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name: '김테스트',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;profileImage: '',
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;statusMessage: 'user3',
&amp;nbsp;&amp;nbsp;}
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;친구 객체는 이런 형식이다. 프로필 이미지 부분에서 시간을 많이 썼다.&lt;br /&gt;프로필이미지가 있을 때는 해당 이미지를 불러와 백그라운드로 보여주고, 없을때는 이름의 첫글자를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;FriendsIcon.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const ProfileImage = styled.div&amp;lt;{ friend: IFriend; selected: boolean }&amp;gt;`
&amp;nbsp;&amp;nbsp;position: relative;
&amp;nbsp;&amp;nbsp;width: 40px;
&amp;nbsp;&amp;nbsp;height: 40px;
&amp;nbsp;&amp;nbsp;border-radius: 50px;
&amp;nbsp;&amp;nbsp;background-color: ${({ theme }) =&amp;gt; theme.palette.mono.gray_f5};
&amp;nbsp;&amp;nbsp;${({ friend }) =&amp;gt; getImageStyle(friend)}
&amp;nbsp;&amp;nbsp;border: ${({ selected, theme }) =&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selected &amp;amp;&amp;amp; `2px solid ${theme.palette.mono.gray_44}`}
`;

const getImageStyle = (friend: IFriend) =&amp;gt; {
&amp;nbsp;&amp;nbsp;switch (friend.profileImage.length) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case 0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return css`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;::after {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content: '${sliceFirstWord(friend.name)}';
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;position: absolute;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;left: 50%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;top: 50%;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transform: translate3d(-50%, -50%, 0);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-weight: 500;
&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;default:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return css`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-image: url(${friend.profileImage});
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-size: 40px 40px;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-position: center;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;`;
&amp;nbsp;&amp;nbsp;}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로필 이미지에 관한 부분만 따로 빼서 넣어주었다. 이름의 첫글자를 보여줄 때는 after 가상 선택자를 이용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHy9UG/btrQetlcsjU/crSdPO7kF5jWkkeKrGLsBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHy9UG/btrQetlcsjU/crSdPO7kF5jWkkeKrGLsBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHy9UG/btrQetlcsjU/crSdPO7kF5jWkkeKrGLsBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHy9UG%2FbtrQetlcsjU%2FcrSdPO7kF5jWkkeKrGLsBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;108&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 과정에서 문제가 하나 있었다. 투두메이트에 이름의 첫글자로 이모지를 쓰는 경우가 많은데, 프로필 이미지에 이모지가 들어가면 꽤 이쁘다. 무작정 문자열의 첫번째 인덱스를 가져오니까 이모지가 깨졌다. 이모지는 유니코드를 쓰는데, 문자열로 처리될때 길이가 막 2,3 이렇게 나오더라. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;b&gt;sliceFirstWord&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const sliceFirstWord = (string: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;// https://avengersrhydon1121.tistory.com/268
&amp;nbsp;&amp;nbsp;if (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/^([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/.test(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;string,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return string.slice(0, 2);
&amp;nbsp;&amp;nbsp;} else {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return string[0];
&amp;nbsp;&amp;nbsp;}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받은 문자열이 이모지로 시작하면 0,1 인덱스를 리턴하고, 아니면 0번째 인덱스만 리턴하도록 했다. 정규식을 사용했는데, /^ / 를 통해 어떠한 문자로 시작하는지 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brkrrW/btrQeCvoHjo/HxDcHRn7mioB2M5T190JtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brkrrW/btrQeCvoHjo/HxDcHRn7mioB2M5T190JtK/img.png&quot; data-alt=&quot;짠&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brkrrW/btrQeCvoHjo/HxDcHRn7mioB2M5T190JtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrkrrW%2FbtrQeCvoHjo%2FHxDcHRn7mioB2M5T190JtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;108&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;짠&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 렌더링 최적화?&lt;/b&gt;&lt;/h2&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/83</guid>
      <comments>https://9yujin.tistory.com/83#entry83comment</comments>
      <pubDate>Thu, 3 Nov 2022 03:03:36 +0900</pubDate>
    </item>
    <item>
      <title>[뱅키즈] 7. React transition group 라우팅 트랜지션 (2) - 디테일 잡기</title>
      <link>https://9yujin.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 프로젝트에 페이지 전환 애니메이션을 넣으면서 마주쳤던 쓸모없는 우당탕의 기록. 옛날 코드 보는데 그냥 아무생각없이 멍청하게 생각해서 나온 문제가 대부분이었다. 그래도.. 일기같은 느낌쓰로 적어내려가겠습니다. 기본적인 개념과 방법은 이전 글에 잘 정리해 놓았다.&lt;br /&gt;&lt;a href=&quot;https://9yujin.tistory.com/73&quot;&gt;[뱅키즈] 6. React transition group 라우팅 트랜지션 (1) - 도입하기&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;b&gt;기존의 라우팅 구조 때문에 생겼던 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 트랜지션 그룹을 도입했을 때의 코드이다. 트랜지션 그룹 관련 로직을 따로 분리를 하지 않았었다. &lt;code&gt;App.tsx&lt;/code&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-11-02 오후 8.34.17.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BuW7l/btrQeB4aOZJ/BSMe7jtNDYykEG4DC0di1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BuW7l/btrQeB4aOZJ/BSMe7jtNDYykEG4DC0di1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BuW7l/btrQeB4aOZJ/BSMe7jtNDYykEG4DC0di1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBuW7l%2FbtrQeB4aOZJ%2FBSMe7jtNDYykEG4DC0di1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1084&quot; height=&quot;654&quot; data-filename=&quot;스크린샷 2022-11-02 오후 8.34.17.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;654&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-02-2022 21-02-28.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oBaIj/btrQd7WM34l/kac0ViDKYWdtLsXka7sUXk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oBaIj/btrQd7WM34l/kac0ViDKYWdtLsXka7sUXk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oBaIj/btrQd7WM34l/kac0ViDKYWdtLsXka7sUXk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/oBaIj/btrQd7WM34l/kac0ViDKYWdtLsXka7sUXk/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;1236&quot; height=&quot;772&quot; data-filename=&quot;Nov-02-2022 21-02-28.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'돈길 걷기'에서 걷고 있는 돈길이 없을 때 '돈길 계약하기'로 넘어갈 때 애니메이션이 적용되지 않았다. 되게 당연한거였다. 각각 라우터마다 트랜지션 그룹을 적용하려고 했고, '/walk'과 '/create'는 다른 라우터에 있었다.&lt;/p&gt;
&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;ServiceRouter.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const ServiceRouter = () =&amp;gt; {
  const location = useLocation();
  return (
    &amp;lt;Wrapper&amp;gt;
        &amp;lt;RouteTransition location={location}&amp;gt;
          &amp;lt;Routes location={location}&amp;gt;
            &amp;lt;Route path=&quot;/*&quot; element={&amp;lt;HomeRouter location={location} /&amp;gt;} /&amp;gt;
            &amp;lt;Route path=&quot;/walk/*&quot; element={&amp;lt;WalkRouter /&amp;gt;} /&amp;gt;
            &amp;lt;Route
              path=&quot;/mypage/*&quot;
              element={&amp;lt;MypageRouter location={location} /&amp;gt;}
            /&amp;gt;
            &amp;lt;Route path=&quot;/interest/*&quot; element={&amp;lt;InterestRouter /&amp;gt;} /&amp;gt;
          &amp;lt;/Routes&amp;gt;
        &amp;lt;/RouteTransition&amp;gt;
    &amp;lt;/Wrapper&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;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-02-2022 21-31-11.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o5EjR/btrQfJ8fyKx/MUTLRtE5fevmao1FUti4V1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o5EjR/btrQfJ8fyKx/MUTLRtE5fevmao1FUti4V1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o5EjR/btrQfJ8fyKx/MUTLRtE5fevmao1FUti4V1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/o5EjR/btrQfJ8fyKx/MUTLRtE5fevmao1FUti4V1/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;1236&quot; height=&quot;772&quot; data-filename=&quot;Nov-02-2022 21-31-11.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;페이지 순서에 따라 다른 애니메이션을 보여주기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탭바에 있는 세개의 메인 페이지를 왔다갔다 할때도 아까 적용했던 애니메이션이 나타났다. 어떤 위치에 있는 버튼을 누르든지 오른쪽에서 왼쪽으로 넘어가는 애니메이션으로 보인다. 지금 위치에서 왼쪽에 있는 버튼을 누르면 왼쪽으로 슬라이드하고, 오른쪽에 있는 버튼을 누르면 오른쪽으로 슬라이드하도록 해보자.&lt;/p&gt;
&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;RouteTransition.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const pageOrder = ['/interest', '/', '/walk', '/mypage'];

const RouteTransition = ({ location, children }: RouteTransitionProps) =&amp;gt; {
  const pathname = location.pathname;
  const state = location.state;

  return (
    &amp;lt;TransitionGroup
      className={'transition-wrapper'}
      childFactory={(child) =&amp;gt; {
        if (!state?.prev) {
          return React.cloneElement(child, {
            classNames: location.state?.direction || 'navigate-push',
          });
        } else {
          if (pageOrder.indexOf(pathname) &amp;gt; pageOrder.indexOf(state.prev)) {
            return React.cloneElement(child, {
              classNames: 'slide-next',
            });
          } else {
            return React.cloneElement(child, {
              classNames: 'slide-prev',
            });
          }
        }
      }}
    &amp;gt;
      &amp;lt;CSSTransition exact key={pathname} timeout={300}&amp;gt;
        {children}
      &amp;lt;/CSSTransition&amp;gt;
    &amp;lt;/TransitionGroup&amp;gt;
  );
};

export default RouteTransition;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 페이지의 순서를 배열로 지정해놓았다. 부모와 자녀일때 탭바의 구성이 조금 다르지만 다행히 겹치는 부분이 없어서 하나로 관리할 수 있었다. navigate시 &lt;code&gt;state&lt;/code&gt;로 받은 값과 &lt;code&gt;pathname&lt;/code&gt;의 페이지 순서를 비교해서 그에 맞는 클래스로 지정해주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ChildFactory&lt;/code&gt;에 각각의 조건마다 다른 className을 넣어준 &lt;code&gt;cloneElement&lt;/code&gt;를 반환하는 함수를 넣어주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;NavLink to=&quot;/mypage&quot; state={{ prev: pathname }}&amp;gt;
    &amp;lt;Mypage fill={pathname === '/mypage' ? active[1] : active[0]} /&amp;gt;
&amp;lt;/NavLink&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 &lt;code&gt;Link&lt;/code&gt; 컴포넌트에 state를 달아서 라우팅 시 넘겨줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Transiton.css&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.slide-next-enter {
  transform: translateX(100%);
}

.slide-next-enter-active {
  transform: translateX(0);
  transition: transform 300ms ease-in-out;
}

.slide-next-exit {
  transform: translateX(0);
}

.slide-next-exit-active {
  transform: translateX(-100%);
  transition: transform 300ms ease-in-out;
}

.slide-prev-enter {
  transform: translateX(-100%);
}

.slide-prev-enter-active {
  transform: translateX(0);
  transition: transform 300ms ease-in-out;
}

.slide-prev-exit {
  transform: translateX(0);
}

.slide-prev-exit-active {
  transform: translateX(100%);
  transition: transform 300ms ease-in-out;
}&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-02-2022 22-10-11.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vNB4Z/btrQfeOdh1p/arbqtZgdRendcIVvRu1Tt0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vNB4Z/btrQfeOdh1p/arbqtZgdRendcIVvRu1Tt0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vNB4Z/btrQfeOdh1p/arbqtZgdRendcIVvRu1Tt0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/vNB4Z/btrQfeOdh1p/arbqtZgdRendcIVvRu1Tt0/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;1236&quot; height=&quot;772&quot; data-filename=&quot;Nov-02-2022 22-10-11.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;라우팅이 아닌, state 변경에 따른 애니메이션 넣기&lt;/b&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Nov-02-2022 22-22-15.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DytoR/btrQfdIyBOl/CQRBcCC5sk6L6RJa8Db78k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DytoR/btrQfdIyBOl/CQRBcCC5sk6L6RJa8Db78k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DytoR/btrQfdIyBOl/CQRBcCC5sk6L6RJa8Db78k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/DytoR/btrQfdIyBOl/CQRBcCC5sk6L6RJa8Db78k/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;1236&quot; height=&quot;772&quot; data-filename=&quot;Nov-02-2022 22-22-15.gif&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;772&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;SlideTranstion.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const SlideTransition = ({
  keyValue,
  direction,
  children,
}: SlideTransitionProps) =&amp;gt; {
  return (
    &amp;lt;TransitionGroup
      style={{ position: 'relative' }}
      childFactory={(child) =&amp;gt; {
        return React.cloneElement(child, {
          classNames: `slide-${direction}`,
        });
      }}
    &amp;gt;
      &amp;lt;CSSTransition
        key={keyValue}
        timeout={300}
        classNames={`slide-${direction}`}
      &amp;gt;
        {children}
      &amp;lt;/CSSTransition&amp;gt;
    &amp;lt;/TransitionGroup&amp;gt;
  );
};

export default SlideTransition;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별로 다를 것 없다. direction과 key를 props로 받아서 넣어준다.&lt;/p&gt;
&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;Create.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Create() {
  const [step, setStep] = useState&amp;lt;TStep&amp;gt;(1);
  const [direction, setDirection] = useState&amp;lt;'next' | 'prev'&amp;gt;('next');

  const onPrevButtonClick = () =&amp;gt; {
    setDirection('prev');
    setStep((step - 1) as TStep);
  };

  const onNextButtonClick = () =&amp;gt; {
    setDirection('next');
    setStep((step + 1) as TStep);
  };

  return (
    &amp;lt;ForegroundTemplate
      label=&quot;돈길 계약하기&quot;
      customEvent={step !== 1 ? onPrevButtonClick : undefined}
      to=&quot;/&quot;
    &amp;gt;
      &amp;lt;Wrapper&amp;gt;
        &amp;lt;SlideTransition keyValue={step} direction={direction}&amp;gt;
          &amp;lt;ContentWrapper&amp;gt;
          // ...content
          &amp;lt;/ContentWrapper&amp;gt;
        &amp;lt;/SlideTransition&amp;gt;
      &amp;lt;/Wrapper&amp;gt;
    &amp;lt;/ForegroundTemplate&amp;gt;
  );
}

export default Create;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적당히 덜어서 가져왔다. '다음으로 가기' 또는 '이전' 버튼을 누를 때 step 상태와 함께 direction 상태를 바꿔준다. 각각 key와 direction props로 넘기면 된다. &lt;code&gt;TransitionGroup&lt;/code&gt;에서 key 값이 바뀌는걸 감지하면 트랜지션을 만들어 주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step1일 때 뒤로가기를 누르면 슬라이드 애니메이션이 아니라 라우팅 애니메이션(navigate-pop)이 나와야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  const onClickAppBar = () =&amp;gt; {
    if (customEvent) {
      customEvent();
    } else {
      to
        ? navigate(to, {
            state: { direction: 'navigate-pop' },
          })
        : navigate(-1);
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱바 뒤로가기버튼의 onClick에 들어가는 함수이다. &lt;code&gt;customEvent&lt;/code&gt; props이 있으면 그걸 수행하고, 없을땐 navigate 애니메이션이 일어나도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 443px; top: 8750.34px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>  긴호흡/뱅키즈</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/81</guid>
      <comments>https://9yujin.tistory.com/81#entry81comment</comments>
      <pubDate>Wed, 2 Nov 2022 22:46:18 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch2.4. Scheduling</title>
      <link>https://9yujin.tistory.com/80</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ready 상태에 있는 프로세스 중 누구에게 CPU를 할당할것인가에 대한 정책&lt;/li&gt;
&lt;li&gt;Batch 시스템이냐 - TimeSharing 시스템이냐에 따라 다름&lt;/li&gt;
&lt;li&gt;Context Switching Overhead를 고려해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;user mode =&amp;gt; kernel mode 모드 스위칭&lt;/li&gt;
&lt;li&gt;현재의 상태정보 저장&lt;/li&gt;
&lt;li&gt;주소공간 (memory map) 정보 저장&lt;/li&gt;
&lt;li&gt;다음에 실행할 프로세스 선택 (스케쥴링)&lt;/li&gt;
&lt;li&gt;PCB에 저장되어 있는 데이터 리로딩&lt;/li&gt;
&lt;/ul&gt;
등, 꽤 많은 일이 context switch 사이에 일어난다!!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Process Behavior&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-16 오전 1.18.23.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DtbuO/btrPkp4dQpf/7PnAIxCmExZu5juwawAin1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DtbuO/btrPkp4dQpf/7PnAIxCmExZu5juwawAin1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DtbuO/btrPkp4dQpf/7PnAIxCmExZu5juwawAin1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDtbuO%2FbtrPkp4dQpf%2F7PnAIxCmExZu5juwawAin1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;253&quot; data-filename=&quot;스크린샷 2022-10-16 오전 1.18.23.png&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU를 사용하는 패턴에 따라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;a) Compute Bound&lt;/b&gt; : CPU 연산의 비중이 높은 프로세스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;b) I/O Bound&lt;/b&gt; : I/O 연산의 비중이 높은 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CPU의 성능에 따라 상대적으로 결정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;When to Schedule&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 프로세스가 생성된 경우 : fork()로 새 프로세스가 ready queue에 들어갔을 때&lt;/li&gt;
&lt;li&gt;프로세스가 exit한 경우&lt;/li&gt;
&lt;li&gt;프로세스가 block된 경우&lt;/li&gt;
&lt;li&gt;I/O Interrupt가 발생한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Clock Interrupt&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS가 interrupt를 주기적으로 걸어서,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 관리하는 루틴을 계속 수행시킨다.&lt;/li&gt;
&lt;li&gt;한 프로세스가 CPU Quantum을 초과하는지 계속 감지한다&lt;/li&gt;
&lt;li&gt;해당 프로세스가 Quantum을 초과하면 unschedule (압수)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Two categories of scheduling&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NonPreemptive&lt;/b&gt; : 알아서 끝낼때까지 CPU를 계속 쓰게 해주는 것&lt;br /&gt;주로 Batch System에서 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Preemptive&lt;/b&gt; : 프로세스마다 CPU 점유 시간 (CPU Quantum)에 제한을 건다.&lt;br /&gt;Clock Interrupt를 통해 확인한다.&lt;br /&gt;주로 Timesharing System에서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Three Environments for Scheduling&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Batch System (일괄 처리)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;job을 operator에게 넘겨주고 일이 끝나면 결과값을 받는 것 (상호작용 x)&lt;/li&gt;
&lt;li&gt;Non-preemptive - Context Switch Overhead의 빈도를 줄인다.&lt;/li&gt;
&lt;li&gt;preemptive 알고리즘을 사용한다면, Quantum의 크기를 매우 크게 잡는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Troughput&lt;/b&gt; (단위 시간당 처리하는 job의 수)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Turnaround Time&lt;/b&gt; (operator에게 job을 주고 결과를 받기까지의 소요 시간)&lt;/li&gt;
&lt;li&gt;CPU Utilization : CPU가 항상 바쁘게 움직여야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Interactive System&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무조건 preemptive 알고리즘 사용 (Time sharing)&lt;/li&gt;
&lt;li&gt;한 프로세스가 Quantum을 초과하면 unschedule&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Response Time&lt;/b&gt; : 요청에 대한 응답 시간&lt;/li&gt;
&lt;li&gt;Proportionality : 10개에 10초면, 100개는 100초&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Real time System&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스가 처리되는 Deadline을 맞춰야 함&lt;/li&gt;
&lt;li&gt;얼마동안 써야하냐 - 같은 스케쥴이 다 정해져 있기 때문에 preemptive-nonpreemptive가 중요하지 않음. 상관없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Meeting Dealines&lt;/b&gt; : 데드라인을 맞춰야함&lt;/li&gt;
&lt;li&gt;Predictability : 일정 수준 이상의 서비스 품질이 유지되어야 함 (스트리밍 같은거)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Scheduling 스케쥴링&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Time Scale의 크기에 따라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Long Term : 몇개까지 Ready 상태로 둘 수 있게 할것인가 (너무 많이 넣어도 메모리 문제)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Short Term&lt;/b&gt; : Ready 상태에 있는 프로세스 중 CPU를 할당할 프로세스를 선정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Starvation&lt;/b&gt; : Ready queue에 있는 어떤 프로세스가 스케쥴링 알고리즘에 따라 선택을 못받고 계속 뒤로 밀려나는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. FCFS&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;First come first served&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;create된 순서대로 프로세스를 처리하는 방식 (선입선출)&lt;/li&gt;
&lt;li&gt;대체로 non-preemptive (context switch 오버헤드 적음)&lt;/li&gt;
&lt;li&gt;Starvation이 없다. 언젠가 무조건 처리되니까.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;평균 Turnaround Time이 커질 가능성이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016020904209.png&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmiPfV/btrPlH4CjGM/0L06cb0PKYtzSiQ8gZ2kRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmiPfV/btrPlH4CjGM/0L06cb0PKYtzSiQ8gZ2kRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmiPfV/btrPlH4CjGM/0L06cb0PKYtzSiQ8gZ2kRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmiPfV%2FbtrPlH4CjGM%2F0L06cb0PKYtzSiQ8gZ2kRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;383&quot; data-filename=&quot;image-20221016020904209.png&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;383&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;2. SJF&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Shortest Job First&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;CPU를 제일 조금 쓸 것 같은 애를 먼저 처리하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016021450139.png&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vPqhu/btrPkqB1gWE/Wp0d9iDWV7IV03HeLMxcI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vPqhu/btrPkqB1gWE/Wp0d9iDWV7IV03HeLMxcI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vPqhu/btrPkqB1gWE/Wp0d9iDWV7IV03HeLMxcI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvPqhu%2FbtrPkqB1gWE%2FWp0d9iDWV7IV03HeLMxcI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1375&quot; height=&quot;352&quot; data-filename=&quot;image-20221016021450139.png&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로는 CPU Burst Time을 알 수 없다.&lt;/li&gt;
&lt;li&gt;모든 Job들이 동시에 요청된 경우에만 최적의 성능을 발휘한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제 예시&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A, B, C, D, E = 2, 4, 1, 1, 1 (CPU Burst Time)&lt;/li&gt;
&lt;li&gt;A,B는 0초에 / C,D,E는 3초에 동시 요청됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016022641737.png&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y4SJO/btrPg0deh46/mj0ZqmlQXcfoIG0dNWEksk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y4SJO/btrPg0deh46/mj0ZqmlQXcfoIG0dNWEksk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y4SJO/btrPg0deh46/mj0ZqmlQXcfoIG0dNWEksk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy4SJO%2FbtrPg0deh46%2Fmj0ZqmlQXcfoIG0dNWEksk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1169&quot; height=&quot;441&quot; data-filename=&quot;image-20221016022641737.png&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. SRTN&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Shortest Remain Time Next&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;SJF의 preemptive 버전&lt;/li&gt;
&lt;li&gt;매 클락마다 주기적으로 멈추고 스케쥴링을 다시 한다.&lt;/li&gt;
&lt;li&gt;현재 필요한 CPU Burst 중 가장 작은 걸 찾아서, 그 job을 먼저 처리한다.&lt;/li&gt;
&lt;li&gt;새로 들어온 짧은 job이 혜택을 많이 봄&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016173020740.png&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b00dbV/btrPjkWmuDj/LuzkvHREDCgkKssQh5nek0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b00dbV/btrPjkWmuDj/LuzkvHREDCgkKssQh5nek0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b00dbV/btrPjkWmuDj/LuzkvHREDCgkKssQh5nek0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb00dbV%2FbtrPjkWmuDj%2FLuzkvHREDCgkKssQh5nek0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;271&quot; data-filename=&quot;image-20221016173020740.png&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;415&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;4. Round Robin&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ready Queue 맨 앞에 있는 애부터 수행, 정해진 Quantum을 넘어가면 맨 뒤로 보내버린다.&lt;/li&gt;
&lt;li&gt;Preemptive (퀀텀 있으면 preemptive인것)&lt;/li&gt;
&lt;li&gt;Starvation이 없다 (기본적으로 FCFS이기 때문에)&lt;/li&gt;
&lt;li&gt;보통 원형 큐로 구현 (포인터만 옮기면 바로 큐 맨뒤로 옮기는게 되니깐)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt; : 적당한 Quantum 값을 찾기 힘들다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Quantum의 값이 작으면 : Context Switch가 자주 일어나 overhead 커짐&lt;/li&gt;
&lt;li&gt;Quantum의 값이 크면 : Response Time이 커진다 (결국 그냥 FCFS와 비슷해지니까)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Priority Scheduling&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;job들에게 우선순위를 부여함 (같은 애들끼리는 FCFS나 RR 같은 다른 알고리즘 사용)&lt;/li&gt;
&lt;li&gt;Static Assignment : 정적으로 우선순위 부여&lt;/li&gt;
&lt;li&gt;Dynamic Assignment : 동적으로
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O Bound Process에게 더 높은 우선순위를 준다 (보통은 Quantum을 다 쓰기 전에 끝내니까)&lt;/li&gt;
&lt;li&gt;우선순위 : 1/  (  : 해당 프로세스가 quantum 중 사용한 시간의 비율)&lt;br /&gt;Ex. q:10 중에 2만큼만 사용하고 I/O 요청으로 인해서 block 되였다면 : 1/ (2/10) = 5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제점&lt;/b&gt; : 역시 Starvation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오래 기다릴 수록 우선순위를 증가시켜준다&lt;/li&gt;
&lt;li&gt;같은 우선순위 애들끼리는 RR (아래 예시)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016181136025.png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwwycw/btrPjF0hlSA/eMWMGg6UENaOg9gvEIkkKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwwycw/btrPjF0hlSA/eMWMGg6UENaOg9gvEIkkKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwwycw/btrPjF0hlSA/eMWMGg6UENaOg9gvEIkkKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwwycw%2FbtrPjF0hlSA%2FeMWMGg6UENaOg9gvEIkkKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;299&quot; data-filename=&quot;image-20221016181136025.png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. Multiple Queues&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 큐가 있고, 각 큐마다 다른 알고리즘을 적용&lt;/li&gt;
&lt;li&gt;프로세스들은 큐 사이를 옮겨다닐 수 있음&lt;/li&gt;
&lt;li&gt;위 Priority Scheduling도 이것 중 하나인가???&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multi Level Feedback Queues (MLFQ)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 큐마다 다른 job type을 가짐 (Batch냐.. Interactive냐.. CPUbound..)&lt;/li&gt;
&lt;li&gt;그에 맞는 Quantum을 정해두고 맞는 큐로 찾아 들어가는 구조인듯 (Feedback을 통해)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016182536561.png&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXgx3/btrPg0j3Nfm/YHNA8Bcklpa7KX8nIo6E70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXgx3/btrPg0j3Nfm/YHNA8Bcklpa7KX8nIo6E70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXgx3/btrPg0j3Nfm/YHNA8Bcklpa7KX8nIo6E70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXgx3%2FbtrPg0j3Nfm%2FYHNA8Bcklpa7KX8nIo6E70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;391&quot; data-filename=&quot;image-20221016182536561.png&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. Guaranteed Scheduling&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ratio (실제 CPU 사용한 시간 / 할당된 시간)가 작은 job 먼저&lt;/li&gt;
&lt;li&gt;다른 애들과 비율이 똑같아질때까지 계속 제일 작은거 실행하다가.. 계속 계산해가며 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. Lottery Scheduling&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Job마다 티켓을 주고, 스케쥴러가 랜덤하게 티켓을 뽑는다. 해당 번호를 갖고있는놈이 수행됨&lt;/li&gt;
&lt;li&gt;각 프로세스마다 갖고 있는 티켓의 개수가 다르겠지 (확률게임)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Highly Response&lt;/b&gt; : 새로 큐에 들어온 프로세스도 바로 CPU를 받을 가능성이 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ticket Exchanging&lt;/b&gt; : 동일한 Task를 수행하는 애들끼리 티켓을 주고 받을 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Propotional Scheduling&lt;/b&gt; : 특정 비율로 할당하는 경우에 적합 (Frame Rate이 10, 20, 25인 3개의 Job이 있는 경우, 전체 Ticket 55개를 10개, 20개, 25개로 분배하면 된다)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9. Fairshare Scheduling&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Process 단위로 CPU를 할당하는 것이 아닌, Process를 구동하는 User들에게 공평하게 CPU를 할당하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016190826973.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/svrKv/btrPgfhx8BU/iRGP9HLHj3iu7lS1Rti35k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/svrKv/btrPgfhx8BU/iRGP9HLHj3iu7lS1Rti35k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/svrKv/btrPgfhx8BU/iRGP9HLHj3iu7lS1Rti35k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsvrKv%2FbtrPgfhx8BU%2FiRGP9HLHj3iu7lS1Rti35k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;378&quot; data-filename=&quot;image-20221016190826973.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;629&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;b&gt;Real-Time Systems&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Real time 시스템에서는 Response, Turnaround Time보다는 데드라인이 중요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Periodic&lt;/b&gt; : Event가 Regular하게 발생되는 경우를 의미한다. (Event란, CPU를 할당하여 처리해야 하는 일을 의미한다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Aperiodic&lt;/b&gt; : Event가 불규칙적으로 발생되는 경우를 의미한다. Dead-Line을 충족시키기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221016192455073.png&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqbhV/btrPpWNtTUo/lGw8cwvUb05EvthBeiukzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqbhV/btrPpWNtTUo/lGw8cwvUb05EvthBeiukzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqbhV/btrPpWNtTUo/lGw8cwvUb05EvthBeiukzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqbhV%2FbtrPpWNtTUo%2FlGw8cwvUb05EvthBeiukzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1292&quot; height=&quot;615&quot; data-filename=&quot;image-20221016192455073.png&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/80</guid>
      <comments>https://9yujin.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 23 Oct 2022 19:37:41 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch2.3. Synchronization</title>
      <link>https://9yujin.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러 쓰레드가 공유된 데이터들에 동시에 접근하려 할 때, 그 실행들을 제어해주어야 한다. 실행되는 여러 작업들이 문제가 없게끔 맞춰주는 걸 &lt;b&gt;Synchronization&lt;/b&gt; 이라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Race Condition&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;똑같은 수행을 하는데 매번 결과가 다르게 나오는 것 (타이밍에 따라서)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221017211815610.png&quot; data-origin-width=&quot;1875&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qfDLP/btrPjluc4VD/yOdpUnpnQF15apXpGr1Vp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qfDLP/btrPjluc4VD/yOdpUnpnQF15apXpGr1Vp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qfDLP/btrPjluc4VD/yOdpUnpnQF15apXpGr1Vp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqfDLP%2FbtrPjluc4VD%2FyOdpUnpnQF15apXpGr1Vp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1875&quot; height=&quot;782&quot; data-filename=&quot;image-20221017211815610.png&quot; data-origin-width=&quot;1875&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초록색 리턴된 다음에 빨간색 실행된 거&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221017211924349.png&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/20k4I/btrPg05oFkg/YaUlBVPF6ntJbVufHLNYU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/20k4I/btrPg05oFkg/YaUlBVPF6ntJbVufHLNYU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/20k4I/btrPg05oFkg/YaUlBVPF6ntJbVufHLNYU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F20k4I%2FbtrPg05oFkg%2FYaUlBVPF6ntJbVufHLNYU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1844&quot; height=&quot;482&quot; data-filename=&quot;image-20221017211924349.png&quot; data-origin-width=&quot;1844&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;put_balance에 값 넣기 전에 Context Switch가 발생한 경우&lt;/li&gt;
&lt;li&gt;초록색은 9000원, 빨간색은 8000원 리턴.&lt;/li&gt;
&lt;li&gt;빨간색 리턴(8000)한 다음에 다시 초록색 풋 밸런스로 돌아가서 초록색 리턴(9000).&lt;/li&gt;
&lt;li&gt;총 삼천원 빼간건데 결과적으론 1000원만 없어짐. 개꿀? -&amp;gt; 이게 race condition&lt;/li&gt;
&lt;/ul&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;Critical Regions&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 코드 중 공유된 자원에 접근하는 부분&lt;/li&gt;
&lt;li&gt;해당 자원에 동시에 access할때는 synchronization이 필요하다&lt;/li&gt;
&lt;li&gt;Critical Region에 진입할때는 mutual exclustion하게 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Critical Section Problem (CSP)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;critical section 문제를 해결하기 위해서는 아래의 조건을 만족해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mutual exclusion&lt;/li&gt;
&lt;li&gt;No Starvation&lt;/li&gt;
&lt;li&gt;Progress : T가 CR 바깥에 있을 때, S가 T때문에 CR에 못들어오면 안된다.&lt;/li&gt;
&lt;li&gt;CPU의 속도에 대해선 어떠한 가정도 해선 안된다&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;Mechanisms for Solving the CSP&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;with Busy Waiting&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Disabling Interrupt&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lock&lt;/b&gt; Variables -&amp;gt; 중간에 꼬이면 CR에서 충돌&lt;/li&gt;
&lt;li&gt;Strict Alteration -&amp;gt; 중간에 꼬이면 아무도 CR 못들어감&lt;/li&gt;
&lt;li&gt;Peterson's Solution -&amp;gt; 되긴 하는데 복잡하고 성능 안좋음&lt;/li&gt;
&lt;li&gt;TSL =&amp;gt; 아토믹하게, 하드웨어 설계로&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sleep and Wakeup =&amp;gt; busy waiting 단점 줄임 (깨워줘), 얘도 지연생기면 꼬일 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Semaphores&lt;/b&gt; =&amp;gt; 해결 가능 but 코딩 어려움&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Montiors&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 두꺼운 글씨로 되어있는게 shared memory라고 강의록에 써있음.&lt;br /&gt;sleep and wakeup도 공유메모리 맞는거같음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Message Passing&lt;/b&gt; : 얘는 non-shared memory&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Busy Waiting&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;a) Disabling Interrupt&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Critigcal region을 지나는 동안 interrupt가 발생하면 무시한다. 방해받지 않고 끝까지 수행한 뒤에 enable시킴. OS로 안넘어가도록. 즉, &lt;b&gt;Context Switch를 일어나지 않게&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;애초에 context switch가 일어나지 않으니까 다른 프로세스가 CR을 수행하지 못한다.&lt;/li&gt;
&lt;li&gt;위의 문장은.. CPU가 하나일때만 해당되는 말!! 멀티 프로세서 환경에서는 다른 CPU에 의해 다른 프로세스가 CR을 수행할 수 있기 때문에.&lt;/li&gt;
&lt;li&gt;User-process에서는 불가능한 방법 : 이런건 권한이 필요한 작업 (privilege instruction).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;b) Lock Variables&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;while ( lock == 1 );  // Waiting

// 하지만 여기서!! Context Switch가 된다면?
// A는 while 루프를 통과했는데 lock을 미쳐 1로 바꾸지 못한 경우
// : B도 수행 가능해진다. CR에 둘이 들어갈 수 있게 됨.
lock = 1;
Critical_Region();
lock = 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로세스가 접근 가능한 global 변수를 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 : 접근 가능&lt;/li&gt;
&lt;li&gt;1 : 누가 쓰는 중 -&amp;gt; 기다린다&lt;/li&gt;
&lt;li&gt;lock값이 0이 될때 여러 프로세스가 Critical Region에 동시에 진입하는 문제가 생길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;c) Strict Alteration&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 문제를 해결하기 위해 turn 이라는 변수를 도입함.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;1463&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/667Do/btrPneujlhM/EVzGwzb7UxxcYFKKRgnkoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/667Do/btrPneujlhM/EVzGwzb7UxxcYFKKRgnkoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/667Do/btrPneujlhM/EVzGwzb7UxxcYFKKRgnkoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F667Do%2FbtrPneujlhM%2FEVzGwzb7UxxcYFKKRgnkoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;178&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;1463&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한쪽은 turn이 1일때 수행, 다른쪽은 0일때 수행.&lt;/li&gt;
&lt;li&gt;turn이 동시에 0 또는 1일 수 없으므로, 무조건 CR에는 하나의 프로세스만 진입할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 무조건 ababab 이렇게 번갈아가며 수행해야 한다. CR이 비어있다 하더라도, 두번 이상 연속적으로 접근하지 못함. (CSP 3번 조건을 만족하지 않음 : 밖에 있는 나때문에 다른애가 못들어가면 안됨)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;critical_region()&lt;/code&gt; 하고 turn 1로 바꾸기 직전에 멈췄을때!! -&amp;gt; CR은 비어있는데 turn = 0이라 아무도 못들어가는 상황이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Busy Waiting&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;이렇게, while loop 돌면서 계속 검사하면서 기다리는걸 Busy Waiting이라고 함.&lt;/li&gt;
&lt;li&gt;spin lock : lock variable에 대한 busy waiting. CPU 낭비!!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;d) Peterson Solution&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 나온 여러 방법들의 software적인 해결 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;되긴 하는데 오버헤드가 만만치 않은 해결책이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;e) TSL instructionv (Test and Set Lock)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 제공하는 하드웨어적인 방법이다. Atomic하게 수행된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Atomic instrunction : instruction이 시작하면 끝날때까지 interrupt를 받지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;bool TSL(bool *flag)
{
    bool old = *flag;
    *flag = true;
    return old;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수로 표현되어 있지만 하드웨어로 구현되어 있다.&lt;/li&gt;
&lt;li&gt;인자로 들어온 flag의 값을 리턴하고, 1로 세팅한다. (세팅 전의 값을 리턴해주는 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221022024148587.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eMRn1F/btrPhpRpIXT/BATT9Kzl4Y4dxD9XL42oBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eMRn1F/btrPhpRpIXT/BATT9Kzl4Y4dxD9XL42oBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eMRn1F/btrPhpRpIXT/BATT9Kzl4Y4dxD9XL42oBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeMRn1F%2FbtrPhpRpIXT%2FBATT9Kzl4Y4dxD9XL42oBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;224&quot; data-filename=&quot;image-20221022024148587.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TSL 명령 사용하는 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TSL 구현&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;image-20221022025447283.png&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E7fVT/btrPlGLoiK1/Yqu02eFKIAGBA3dmn42Gck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E7fVT/btrPlGLoiK1/Yqu02eFKIAGBA3dmn42Gck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E7fVT/btrPlGLoiK1/Yqu02eFKIAGBA3dmn42Gck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE7fVT%2FbtrPlGLoiK1%2FYqu02eFKIAGBA3dmn42Gck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;433&quot; data-filename=&quot;image-20221022025447283.png&quot; data-origin-width=&quot;1159&quot; data-origin-height=&quot;836&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lock 변수는 메모리에 저장되어 있고, CPU와는 메모리 버스로 연결되어 있다.&lt;/li&gt;
&lt;li&gt;CPU에서 lock에 접근하기 위해서는 - 메모리 버스의 사용권을 얻어내야 한다.&lt;br /&gt;즉, 한번에 한 CPU만 메모리에 접근할 수 있다. HW적으로 처리.&lt;/li&gt;
&lt;li&gt;CRITICAL_REGION이 끝나면 다음에 leave_region 이 나오는 형태.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;spinlock의 문제점&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;CPU 낭비&lt;/li&gt;
&lt;li&gt;A가 lock 0이 될때까지 계속 CPU를 사용하며 기다리는데, 막상 B는 우선순위가 낮아서 CPU 할당을 못받아 leave_region을 수행할 수 없는 경우.. 결국 아무도 못쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Sleep and Wakeup&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;busy waiting을 대체하기 위해 &lt;code&gt;sleep()&lt;/code&gt;과 &lt;code&gt;wakeup&lt;/code&gt;을 사용한다. 프로세스 자기 자신을 Block시키거나, 자고 있는 (인자로 넣은) 프로세스를 wakeup으로 호출해서 깨울 수 있다.&lt;/li&gt;
&lt;li&gt;sleep과 wakeup도 시스템 콜.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Producer - Consumer Problem&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Producer는 Item을 만들어서 Buffer로 넣고, Consumer는 Buffer에서 Item을 꺼내서 사용&lt;/li&gt;
&lt;li&gt;Producer는 버퍼가 꽉차면, Consumer는 버퍼가 비면 sleep&lt;/li&gt;
&lt;li&gt;Producer는 빈 버퍼에 item을 하나 추가하면 &lt;code&gt;wakeup(Consumer)&lt;/code&gt;,&lt;br /&gt;Consumer는 꽉 찬 버퍼의 item을 하나 사용하면 &lt;code&gt;wakeup(Producer)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221023005513360.png&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnAsZe/btrPh8IBp6O/Nf95kbhhVYGW60ptnnlcG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnAsZe/btrPh8IBp6O/Nf95kbhhVYGW60ptnnlcG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnAsZe/btrPh8IBp6O/Nf95kbhhVYGW60ptnnlcG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnAsZe%2FbtrPh8IBp6O%2FNf95kbhhVYGW60ptnnlcG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1770&quot; height=&quot;1176&quot; data-filename=&quot;image-20221023005513360.png&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간색은 Producer-Consumer의 실행과정, 보라색은 &lt;b&gt;문제점&lt;/b&gt;.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;count = count + 1&lt;/code&gt; 또는 &lt;code&gt;- 1&lt;/code&gt; 하는 부분이 Atomic하지 않기 때문에, Mutual Exclusive 하지 않음. 이 과정에서 어셈블리 명령어들이 순서가 꼬여 문제가 생길 수 있다.&lt;/li&gt;
&lt;li&gt;P에서 &lt;code&gt;count == N&lt;/code&gt; 조건을 통과해서 sleep 하려는 사이에 Context switch가 발생하고 C가 수행된다면 : C에서는 버퍼에 아이템이 있으므로 계속 count를 줄여나감.&lt;br /&gt;그러다가 count = 0 이 돼서 C도 자버린다. 둘다 Sleep하게 됨. 영원히!
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;근데 여기서 wakeup()을 저장했다가 나중에 다 sleep했을때 꺼내서 쓸수도..?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Semaphores&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sleep and Wait의 데드락 문제를 해결하기 위해!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세마포어는 변수야.&lt;/li&gt;
&lt;li&gt;Atomic하게 동작하는 다운과 업 연산으로 조작함 (아토믹하다!)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Down(semaphore)&lt;/b&gt; : 0보다 크면 sem--; / 0이면 wait(블락 처리)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Up(semaphore)&lt;/b&gt; : sem++; / wait중인 스레드가 있으면 하나를 wakeup&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세마포어 두 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Binary semaphore (mutex semaphore) : 0과 1 (버퍼가 1인거?)&lt;/li&gt;
&lt;li&gt;Counting Semaphore (N까지). synchronization을 구현할때 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두가지 세마포어를 이용해서 Producer-Consumer 문제를 해결해볼거야.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;var mutex: semaphore = 1    // mutex (lock과 비슷하게 사용)
    empty: semaphore = n    // 버퍼의 빈 공간 &quot;empty&quot;
    full : semaphore = 0    // 버퍼가 찬 공간 &quot;full&quot;
                            // empty + full == N

producer:
        &amp;lt;produce item&amp;gt;
    down(empty)
    down(mutex)
        &amp;lt;add item to buffer&amp;gt;   
    up(mutex)
    up(full)

consumer:
    down(full)
    down(mutex)
        &amp;lt;remove item from buffer&amp;gt;
    up(mutex)
    up(empty)    
        &amp;lt;use item&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;Mutex&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;add&lt;/code&gt;나 &lt;code&gt;remove&lt;/code&gt;같이 버퍼(공유데이터)에 접근하는 부분에&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;들어갈때 : &lt;code&gt;down(mutex)&lt;/code&gt; 1 -&amp;gt; 0&lt;/li&gt;
&lt;li&gt;나갈때 : &lt;code&gt;up(mutex)&lt;/code&gt; 0 -&amp;gt; 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Synchronization&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;Producer
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Item 하나 만들고 &lt;code&gt;down(empty)&lt;/code&gt; : 비어있는 버퍼수--; &lt;br /&gt;(CR작업 끝나면); &lt;br /&gt;&lt;code&gt;up(full)&lt;/code&gt; : 차있는 버퍼수++;&lt;/li&gt;
&lt;li&gt;컨슈머에서는 처음에 차있는 버퍼수를 보고 CR진입하기 때문에, 버퍼에 입력하는 작업이 끝난 다음에 full 세마포어를 올려주는 듯&lt;/li&gt;
&lt;li&gt;처음에 empty = 0 이면 &lt;code&gt;down(empty)&lt;/code&gt; 할 때 블락됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Consumer
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바로 &lt;code&gt;down(full)&lt;/code&gt; : 차있는 버퍼수--; (CR작업 끝나면); &lt;code&gt;up(empty)&lt;/code&gt; : 비어있는 버퍼수++;&lt;/li&gt;
&lt;li&gt;empty 올려준 다음에, 가져온 item을 사용한다.&lt;/li&gt;
&lt;li&gt;처음에 full = 0 이면 &lt;code&gt;down(full)&lt;/code&gt;할 때 블락됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;Busy Waiting을 하지 않는다.&lt;/li&gt;
&lt;li&gt;코딩하기 어려워서 버그가 있을 가능성이 높다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;down(mutex)&lt;/code&gt;와 &lt;code&gt;down(empty)&lt;/code&gt;의 순서를 바꾸면 데드락 일어날 가능성.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Monitors&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;shared data mutex 지원&lt;/li&gt;
&lt;li&gt;Critical Region에 비정상적으로 접근하는 것을 원천적으로 막는다!&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;monitor example
    integer i;
    condition c;

    procedure producer();   // CR
        ...
    end;

    procedure consumer();   // CR
        ...
    end;
end monitor;    &lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한번에 하나의 process만 해당 모니터의 procedure를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&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;Condition Variables&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모니터 안에서만 접근 가능한 condition variables을 이용해서 시그널을 주고받을 수 있다. 이를 이용해 sleep and wakeup을 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 변수는 monitor안에 있음. 그래서 그 count 변수에 접근하려면 monitor 안에 있는 procedure로 밖에 접근할 수 없다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨디션이라는건 그 변수마다 큐가 있는건데.. 여기선 괜히 이름을 full empty라고 해가지고 헷갈리는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;wait(c)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;wait 걸리면 모니터 안에서 빠져나오고, 다른 애가 들어와서 쓸 수 있도록 열어준다&lt;br /&gt;: release monitor lock&lt;/li&gt;
&lt;li&gt;누가 모니터 안의 프로시저를 사용하면 다른 사람들이 못들어오게 모니터를 잠가준다&lt;br /&gt;: monitor lock&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if count = N then wait(full)&lt;/code&gt;&lt;br /&gt;만약에 프로듀서가 여러개면, p1 - p2 - p3 이렇게 큐에서 대기한다.&lt;br /&gt;그리고 그 큐의 이름이 full이라고 생각하면 됨. 프로듀서는 항상 full 큐에서 대기하고 있고, 컨슈머는 항상 empty 큐에서 대기하고 있다. 프로듀서가 count 0 에서 1로 만들어주면 signal(empty)를 해주는 이유도, empty 큐에 대기하고 있는 컨슈머를 깨우기 위해서!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;signal(c)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 컨디션 변수에 wait중인 프로세스나 쓰레드를 깨운다.&lt;/li&gt;
&lt;li&gt;좀더 쉽게 쓰자면.. (c) 상태에서 대기중인 놈을 깨운다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모니터로 구현한 sleep &amp;amp; wake&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221023181318502.png&quot; data-origin-width=&quot;1753&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VMN7M/btrPmD8HJ0B/UEjhvFl7DtdwakC3lHpkgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VMN7M/btrPmD8HJ0B/UEjhvFl7DtdwakC3lHpkgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VMN7M/btrPmD8HJ0B/UEjhvFl7DtdwakC3lHpkgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVMN7M%2FbtrPmD8HJ0B%2FUEjhvFl7DtdwakC3lHpkgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1753&quot; height=&quot;927&quot; data-filename=&quot;image-20221023181318502.png&quot; data-origin-width=&quot;1753&quot; data-origin-height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모니터에선 한번에 오직 하나의 프로시저만 돌아간다. 만약 어떤 프로듀서가 item을 만들고 있으면, 다른 컨슈머가 remove를 하고 싶어도 못한다.&lt;/li&gt;
&lt;li&gt;mutex하기때문에.. Race Condition은 발생하지 않아!&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보라색 글씨&lt;/b&gt; : signal()을 호출한 뒤에 어디서 다시 수행을 하나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hoare monitors&lt;/b&gt; : 새로 wakeup된 프로세스가 바로 모니터에 들어가서 수행된다. 그 다음에 signaler가 블락됨. 바로 버퍼 바뀌는 일 없이 들어가므로, Condition은 바뀌지 않음이 보장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Hansen monitors&lt;/b&gt; : signal이 발생하더라도 수행중인 프로세스를 계속 수행하고, 작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mesa monitors&lt;/b&gt; : signaler가 다 수행되고 빠져나가고 나서 waiter가 수행된다. waiter가 수행될때 condition이 달라져있을수도 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;monitor 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모니터는 OS 아니라 프로그래밍 언어단에서 지원하는 것 c/c++에선 안된대&lt;/li&gt;
&lt;li&gt;분산 시스템(메모리 공유를 안함. 네트워크를 통해 받음)에서는 사용할 수 없다.&lt;/li&gt;
&lt;li&gt;분산시스템에서는 message passing으로 구현!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Message Passing&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리를 직접적으로 공유할 수 없는 환경에선 send, receive system call 사용해서 받는다&lt;/li&gt;
&lt;li&gt;P와 C가 각각 독립적인 머신에서 각자의 메모리를 이용하기 때문에 공유 리소스 없음. CR 없다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;send(dest, &amp;amp;message)&lt;/b&gt; : 메시지를 전송한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Receive(src, &amp;amp;message)&lt;/b&gt; : 메시지를 받는다. 리시버는 메세지를 수신할때까지 블락됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221023192432930.png&quot; data-origin-width=&quot;1883&quot; data-origin-height=&quot;1163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AUNxz/btrPh9Oll03/eytkoGsyPT5YQKtp4IQNu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AUNxz/btrPh9Oll03/eytkoGsyPT5YQKtp4IQNu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AUNxz/btrPh9Oll03/eytkoGsyPT5YQKtp4IQNu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAUNxz%2FbtrPh9Oll03%2FeytkoGsyPT5YQKtp4IQNu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1883&quot; height=&quot;1163&quot; data-filename=&quot;image-20221023192432930.png&quot; data-origin-width=&quot;1883&quot; data-origin-height=&quot;1163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/79</guid>
      <comments>https://9yujin.tistory.com/79#entry79comment</comments>
      <pubDate>Sun, 23 Oct 2022 19:31:31 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch5. SQL : 질의</title>
      <link>https://9yujin.tistory.com/78</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1)-8.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCBKUL/btrPfOvLc43/1aBTyBOn30KlUVcKNJJ2Dk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCBKUL/btrPfOvLc43/1aBTyBOn30KlUVcKNJJ2Dk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCBKUL/btrPfOvLc43/1aBTyBOn30KlUVcKNJJ2Dk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCBKUL%2FbtrPfOvLc43%2F1aBTyBOn30KlUVcKNJJ2Dk%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (1)-8.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_Untitled (1)-9.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btJEYo/btrPfPuF1oq/jOtS8k3yO9mSNpaud3OOB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btJEYo/btrPfPuF1oq/jOtS8k3yO9mSNpaud3OOB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btJEYo/btrPfPuF1oq/jOtS8k3yO9mSNpaud3OOB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtJEYo%2FbtrPfPuF1oq%2FjOtS8k3yO9mSNpaud3OOB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2182&quot; height=&quot;3086&quot; data-filename=&quot;edited_Untitled (1)-9.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/78</guid>
      <comments>https://9yujin.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 21 Oct 2022 17:17:33 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch4. 관계대수</title>
      <link>https://9yujin.tistory.com/77</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mcdf0/btrPbBXjLIJ/cgALRYU7FNVsr5u93WR2u1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mcdf0/btrPbBXjLIJ/cgALRYU7FNVsr5u93WR2u1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mcdf0/btrPbBXjLIJ/cgALRYU7FNVsr5u93WR2u1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmcdf0%2FbtrPbBXjLIJ%2FcgALRYU7FNVsr5u93WR2u1%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;2182&quot; height=&quot;3086&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1)-7 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJfDFi/btrPaHw8cuU/ok0v7IzVyfJQmFpcHXcu11/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJfDFi/btrPaHw8cuU/ok0v7IzVyfJQmFpcHXcu11/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJfDFi/btrPaHw8cuU/ok0v7IzVyfJQmFpcHXcu11/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJfDFi%2FbtrPaHw8cuU%2Fok0v7IzVyfJQmFpcHXcu11%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (1)-7 2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/77</guid>
      <comments>https://9yujin.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 21 Oct 2022 01:16:58 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch3. 관계모델</title>
      <link>https://9yujin.tistory.com/76</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1)-3.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0LpTh/btrO4pqObdv/OXYHtDDcw9BYoTGtmKNmy0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0LpTh/btrO4pqObdv/OXYHtDDcw9BYoTGtmKNmy0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0LpTh/btrO4pqObdv/OXYHtDDcw9BYoTGtmKNmy0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0LpTh%2FbtrO4pqObdv%2FOXYHtDDcw9BYoTGtmKNmy0%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (1)-3.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1)-4.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8Qj2e/btrO4pRT3rP/bt6ytzpJ7eawD3dO7MwaHK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8Qj2e/btrO4pRT3rP/bt6ytzpJ7eawD3dO7MwaHK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8Qj2e/btrO4pRT3rP/bt6ytzpJ7eawD3dO7MwaHK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8Qj2e%2FbtrO4pRT3rP%2Fbt6ytzpJ7eawD3dO7MwaHK%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (1)-4.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_Untitled (1)-5.jpg&quot; data-origin-width=&quot;2121&quot; data-origin-height=&quot;1500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzSRyP/btrO31DGHek/h5i1wCJdKvEUTuDMPYLfgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzSRyP/btrO31DGHek/h5i1wCJdKvEUTuDMPYLfgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzSRyP/btrO31DGHek/h5i1wCJdKvEUTuDMPYLfgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzSRyP%2FbtrO31DGHek%2Fh5i1wCJdKvEUTuDMPYLfgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2121&quot; height=&quot;1500&quot; data-filename=&quot;edited_Untitled (1)-5.jpg&quot; data-origin-width=&quot;2121&quot; data-origin-height=&quot;1500&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>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/76</guid>
      <comments>https://9yujin.tistory.com/76#entry76comment</comments>
      <pubDate>Fri, 21 Oct 2022 01:15:35 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Ch2. 데이터베이스 설계의 개요</title>
      <link>https://9yujin.tistory.com/75</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (Draft)-1.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SOs86/btrPaIJkXWK/i8AnDerR3ggF4kdiDOc1h1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SOs86/btrPaIJkXWK/i8AnDerR3ggF4kdiDOc1h1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SOs86/btrPaIJkXWK/i8AnDerR3ggF4kdiDOc1h1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSOs86%2FbtrPaIJkXWK%2Fi8AnDerR3ggF4kdiDOc1h1%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (Draft)-1.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (Draft)-2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UFbHz/btrOQFnogIB/QWaIhYGFqW8F1RRue68Al0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UFbHz/btrOQFnogIB/QWaIhYGFqW8F1RRue68Al0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UFbHz/btrOQFnogIB/QWaIhYGFqW8F1RRue68Al0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUFbHz%2FbtrOQFnogIB%2FQWaIhYGFqW8F1RRue68Al0%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;2182&quot; height=&quot;3086&quot; data-filename=&quot;Untitled (Draft)-2.jpg&quot; data-origin-width=&quot;2182&quot; data-origin-height=&quot;3086&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/데이터베이스</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/75</guid>
      <comments>https://9yujin.tistory.com/75#entry75comment</comments>
      <pubDate>Fri, 21 Oct 2022 01:13:20 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch2.2. Threads</title>
      <link>https://9yujin.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Parallal Program&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;하나의 Job을 여러 CPU에서 동시에 처리하는 방식&lt;/li&gt;
&lt;li&gt;다른 프로세스로 작업을 포크해가서 처리해서 주는 것&lt;/li&gt;
&lt;li&gt;PCB, PT들을 생성해야되고, OS 구조도 만들어야하고 해서 비효율적이다 (공간, 시간)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Address Space (Code, data)&lt;/li&gt;
&lt;li&gt;Privilege (특권?)&lt;/li&gt;
&lt;li&gt;리소스 (파일, 소켓 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 것 - 이렇게 네개!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Registers, PC, SP, Process State&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스레드 : 같은 것은 공유하고, 다른 것은 개별적으로 가져가자&lt;/b&gt;&lt;/p&gt;
&lt;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;Thread&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많은 프로그램이 동시에 돌아간다&lt;/li&gt;
&lt;li&gt;쓰레드들은 같은 주소공간을 공유하기 때문에 쓰레드간 정보 교환이 간단하다&lt;/li&gt;
&lt;li&gt;프로세스를 만드는것 보다 가볍다. 시간. 메모리&lt;/li&gt;
&lt;li&gt;한 프로세스 내에서 각 쓰레드마다 cpu를 할당할 수 있음&lt;/li&gt;
&lt;li&gt;오버헤드가 작다&lt;/li&gt;
&lt;li&gt;단점 : 한 쓰레드가 오류나면 전체 프로세스가 다 죽는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Threads and Processes&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Process : 주소공간을 갖고 바깥의 리소스들을 받아온다&lt;/li&gt;
&lt;li&gt;Thread : 순차적으로 명령어를 수행한다.&lt;/li&gt;
&lt;li&gt;한 프로세스 안에 쓰레드가 여러개 있을 수 있다.&lt;br /&gt;한 스레드가 os를 통해 어떤 리소스를 얻어낸다는건 -&amp;gt; 프로세스 단에서 이뤄짐&lt;/li&gt;
&lt;li&gt;모든 스레드가 그 리소스를 공유한다.&lt;/li&gt;
&lt;li&gt;스레드는 CPU를 사용하는 주체 즉, CPU를 할당하는 단위가 된다.&lt;/li&gt;
&lt;/ul&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-20221017195220771.png&quot; data-origin-width=&quot;1717&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DC7Vx/btrO92U5TY7/xuSUpHv3d4lmqAwuRKb8OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DC7Vx/btrO92U5TY7/xuSUpHv3d4lmqAwuRKb8OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DC7Vx/btrO92U5TY7/xuSUpHv3d4lmqAwuRKb8OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDC7Vx%2FbtrO92U5TY7%2FxuSUpHv3d4lmqAwuRKb8OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;173&quot; data-filename=&quot;image-20221017195220771.png&quot; data-origin-width=&quot;1717&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쓰레드 안에서도 Context Switch가 일어날 수 있다.&lt;/li&gt;
&lt;li&gt;프로세스가 하나일 때, 그 안에 여러개의 TCB가 존재 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Address space with threads&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221017195731429.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIONyA/btrOQGfsGlf/l0D0xoBRd63Ct3SwUN5pk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIONyA/btrOQGfsGlf/l0D0xoBRd63Ct3SwUN5pk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIONyA/btrOQGfsGlf/l0D0xoBRd63Ct3SwUN5pk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIONyA%2FbtrOQGfsGlf%2Fl0D0xoBRd63Ct3SwUN5pk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;423&quot; data-filename=&quot;image-20221017195731429.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;/figure&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할때는 synchronize가 필요하다. (쓰레드끼리 공유하는 공간)&lt;/li&gt;
&lt;li&gt;각 쓰레드마다 스택포인터가 따로 따로 할당된다.&lt;br /&gt;function call이 각 쓰레드 내에서 독자적으로 일어나기 때문에 각각 필요함.&lt;br /&gt;-&amp;gt; 지역변수들은 각 스택 내에 저장되기 때문에 공유되기 어려움&lt;/li&gt;
&lt;li&gt;각 쓰레드가 수행하고 있는 코드 위치가 이렇게 각각 다른 상황.&lt;br /&gt;하지만 같은 레지스터 공간을 이용하기 때문에 변수를 공유하기 쉽다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 스택포인터가 가깝거나 붙어있다면, 위에 있는 쓰레드에서 다른 함수를 호출할때 다른 쓰레드에 있는 함수를 오버라이트하겠지. -&amp;gt; 시작점을 적당히 떨어뜨린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kernel threads &amp;amp; User-level threads&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221017201712043.png&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8A3Lf/btrO78WM1CD/Kwg5DACMN8ElKFpc9mV7yK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8A3Lf/btrO78WM1CD/Kwg5DACMN8ElKFpc9mV7yK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8A3Lf/btrO78WM1CD/Kwg5DACMN8ElKFpc9mV7yK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8A3Lf%2FbtrO78WM1CD%2FKwg5DACMN8ElKFpc9mV7yK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1506&quot; height=&quot;872&quot; data-filename=&quot;image-20221017201712043.png&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User Level Thread (장)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싸고 빠르다. 쓰레드 관리가 모두 라이브러리 함수 콜로 이루어지기 때문에.&lt;/li&gt;
&lt;li&gt;프로세스 간의 Context switch overhead가 없어 (시스템 콜이 아님 -&amp;gt; 모드 스위치 안함)&lt;/li&gt;
&lt;li&gt;OS의 관여가 필요없으므로 스케쥴링 알고리즘을 커스텀하기 쉬움.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;User Level Thread (단)&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;OS는 모른다!!&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Blocking System Call&lt;/b&gt; : 하나의 스레드가 block되면 프로세스 자체에서 CPU 뺏어서 다른 프로세스 주는 경우가 있음 (다른 스레드에서 쓸 수 있음에도 불구하고)&lt;br /&gt;라이브러리에서 unblocking하는 call로 직접 호출해주어야 한다 (Asynchronous I/O)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Page Fault&lt;/b&gt; : 메모리 접근 시에 fault 발생하면, 해당 프로세스 전체가 block 당함.&lt;/li&gt;
&lt;li&gt;Infinite Loop : 만약 쓰레드가 무한루프 돌면 꺼낼 방법이 되게 복잡함&lt;/li&gt;
&lt;li&gt;한 프로세스에 여러 CPU를 할당할 수 없다 (OS는 있는지도 모르니까!) - 요즘처럼 멀티코어에선 별로&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Kernel Thread&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 쓰레드에 대한 정보는 커널에서 관리된다.&lt;/li&gt;
&lt;li&gt;프로세스 내에서 특정 쓰레드가 block 되어도 다른애들은 OS에 의해 다시 스케쥴링된다&lt;/li&gt;
&lt;li&gt;하지만 context switch overhead가 큰 편. OS 시스템 콜을 통해 생성되고 관리되는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hybrid Implementations&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;그래서 두개를 합쳐. User 레벨에선 쓰레드가 다섯 개. 커널 레벨에선 각각 3, 2개씩 묶어서 총 두개의 쓰레드로 매핑해놓은 형태.&lt;/li&gt;
&lt;li&gt;하나의 user level thread가 block 되어도 다른 커널 레벨에 연결된 user level thread들은 살아있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/74</guid>
      <comments>https://9yujin.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 20 Oct 2022 18:23:41 +0900</pubDate>
    </item>
    <item>
      <title>[뱅키즈] 6. React transition group 라우팅 트랜지션 (1) - 도입하기</title>
      <link>https://9yujin.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅 트랜지션을 적용하면서 공식문서도 찾아보고 여러 글들을 참고했는데, 무언가 실무에 쓸 수 있을 정도로 시원하게 해법을 제시한 곳이 없었다. 덕분에 고민을 많이 하게 되었던 경험이었다. 그래서 이번 포스팅을 할땐 react-transition-group을 도입하려는 사람의 궁금증을 제대로 해소해줄 수 있도록 평소보다 조금 더 친절하게 적어나갔다. 이 글을 읽은 누구라도 내 고민에 공감하고 해결방안을 찾아갈 수 있는 글이 되기를!!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;[뱅키즈] 6. React-transition-group 라우팅 트랜지션 (1) - 도입하기&lt;/b&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;react-transition-group을 이용해 라우트간 애니메이션 넣기&lt;/li&gt;
&lt;li&gt;뒤로가기 버튼 지원 (양방향 슬라이드)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결된 다른 페이지로 이동할 때에는 오른쪽에서 새로운 페이지가 들어오면서 스택이 쌓이는 듯한 느낌을 주고, 다시 메인화면을 향해 뒤로 돌아갈 때는 위에 쌓였던 페이지가 다시 오른쪽으로 나가는 애니메이션을 구현하고 싶었다. 즉, 라우팅을 할때 트랜지션을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;react-transition-group&lt;/code&gt; 이라는 공식 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. react-transition-group 도입하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactcommunity.org/react-transition-group/&quot;&gt;문서&lt;/a&gt;를 쓱 본다. 그리 친절하지는 않다. &lt;code&gt;CSSTransition&lt;/code&gt; 이라는 컴포넌트를 통해 애니메이션을 줄 수 있다. 여러 자식요소에 동시에 애니메이션을 주고 싶다면 CSSTransition 컴포넌트를 TransitionGroup 컴포넌트로 한번 감싼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;RouteTransition.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const RouteTransition = ({ location, children }: RouteTransitionProps) =&amp;gt; {
  const pathname = location.pathname;

  return (
    &amp;lt;TransitionGroup className={'transition-wrapper'}&amp;gt;
      &amp;lt;CSSTransition
        key={pathname}
        timeout={300}
        classNames={'navigate-push'}
      &amp;gt;
        {children}
      &amp;lt;/CSSTransition&amp;gt;
    &amp;lt;/TransitionGroup&amp;gt;
  );
};

export default RouteTransition;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSSTransiton 컴포넌트의 props으로 아래의 값들을 준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TransitionGroup 내에서 사용할때 map 함수에서 쓰이는 &lt;b&gt;key&lt;/b&gt;처럼 쓰인다. 구분용.&lt;/li&gt;
&lt;li&gt;애니메이션이 나타나는 시간을 &lt;b&gt;timeout&lt;/b&gt;으로 설정해준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;classNames&lt;/b&gt;을 지정해준다. 아래에서 다시 자세히 보겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQuVtu/btrOCid66BW/RhQMYgaQPvDoe4TXA9Mk4k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQuVtu/btrOCid66BW/RhQMYgaQPvDoe4TXA9Mk4k/img.gif&quot; data-alt=&quot;아주 짧은 순간에 일어나니 눈 크게 뜨고 보세요.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQuVtu/btrOCid66BW/RhQMYgaQPvDoe4TXA9Mk4k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bQuVtu/btrOCid66BW/RhQMYgaQPvDoe4TXA9Mk4k/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;363&quot; data-origin-width=&quot;1124&quot; data-origin-height=&quot;680&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;CSSTransition은 자식 요소들의 클래스를 상태에 따라 바꿔주는 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로 들어오는 페이지에는 &lt;code&gt;{classNames}-enter&lt;/code&gt;, 나가는 페이지에는 &lt;code&gt;{classNames}-exit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;트랜지션이 진행중일때는 각각 &lt;code&gt;enter-active&lt;/code&gt;, &lt;code&gt;exit-active&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;완료되면 &lt;code&gt;enter-done&lt;/code&gt;, &lt;code&gt;exit-done&lt;/code&gt;의 이름으로 붙는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Transiton.css&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.navigate-push-enter {
  transform: translateX(100%);
}

.navigate-push-enter-active {
  z-index: 1;
  transform: translateX(0);
  transition: transform 300ms ease-in-out;

  box-shadow: -5px 0px 25px rgba(0, 0, 0, 0.05);
}

.navigate-push-exit {
  transform: translateX(0);
}

.navigate-push-exit-active {
  transform: translateX(-20%);
  transition: transform 300ms ease-in-out;
}

.transition-wrapper {
  position: relative;
  width: 100vw;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스마다 css 스타일을 직접 작성했다. 새로 들어오는 페이지는 스택이 쌓이든 오른쪽에서 들어오기 때문에 z-index를 추가로 주었다. 다른 실제 애플리케이션들과 거의 비슷하게 보인다. 결국 라이브러리는 상황에 맞게 자식들의 클래스 이름을 바꿔주는게 전부이고, 그에 맞는 애니메이션은 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;TransitionGroup 컴포넌트에 &lt;code&gt;position : relative;&lt;/code&gt; 속성을 주었다. 그리고 ForegroundTemplate과 BackgroundTemplate 컴포넌트의 Wrapper에 &lt;code&gt;position : absolute;&lt;/code&gt; 속성을 준다.. 해당 탬플릿 레이아웃을 사용하지 않더라고 각 페이지의 최상위 컴포넌트에 모두 &lt;code&gt;absolute&lt;/code&gt; 속성을 주어야 한다. 그래야 애니메이션이 정상적으로 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;CSSTransiton의 자식으로는 컴포넌트 하나만 들어올 수 있다. 트랜지션 관련 로직을 따로 분리했고 라우팅 컴포넌트를 children으로 받아와 사용한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;ServiceRouter.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// ServiceRouter.tsx
const ServiceRouter = () =&amp;gt; {
  const location = useLocation();
  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;TabBar /&amp;gt;
      &amp;lt;Screen&amp;gt;
        &amp;lt;RouteTransition location={location}&amp;gt;
          &amp;lt;Routes location={location}&amp;gt;
            &amp;lt;Route path=&quot;/*&quot; element={&amp;lt;HomeRouter location={location} /&amp;gt;} /&amp;gt;
            &amp;lt;Route path=&quot;/walk/*&quot; element={&amp;lt;WalkRouter /&amp;gt;} /&amp;gt;
            &amp;lt;Route
              path=&quot;/mypage/*&quot;
              element={&amp;lt;MypageRouter location={location} /&amp;gt;}
            /&amp;gt;
            &amp;lt;Route path=&quot;/interest/*&quot; element={&amp;lt;InterestRouter /&amp;gt;} /&amp;gt;
          &amp;lt;/Routes&amp;gt;
        &amp;lt;/RouteTransition&amp;gt;
      &amp;lt;/Screen&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
};

export default ServiceRouter;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜지션을 적용할 Routes 바깥을 아까 만든 RouteTransition 컴포넌트로 감싸주었다.&lt;br /&gt;&lt;br /&gt;사실 위에서 설명하지 않고 넘어간 부분이 있다. 여기서 중요한건 &lt;b&gt;Routes에 location 객체를 넘겨준다는 것&lt;/b&gt;. 이 작업이 없으면, Routes 아래의 모든 Route들은 항상 현재 상태의 location 객체를 갖는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b62VTR/btrOCdrbi6n/qZvYYglNMQsn79rj0MQ2Q1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b62VTR/btrOCdrbi6n/qZvYYglNMQsn79rj0MQ2Q1/img.gif&quot; data-alt=&quot;location 객체를 Routes로 넘기지 않는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b62VTR/btrOCdrbi6n/qZvYYglNMQsn79rj0MQ2Q1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b62VTR/btrOCdrbi6n/qZvYYglNMQsn79rj0MQ2Q1/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;433&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;680&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;location 객체를 Routes로 넘기지 않는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 되면 이런 대참사가 일어난다. enter하는 페이지와 exit하는 페이지, 이렇게 두개랍시고 보여주긴 한다. 하지만 같은 페이지 두개가 보여진다. 아까 CSSTransition 컴포넌트의 key props로 &lt;code&gt;location.pathname&lt;/code&gt;을 넣어주었고, 같은 location 객체이기 때문에 같은 key값을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;매번 Route로 location 객체를 넘겨준다면, 모든 Route는 현재의 location 정보가 아닌 &lt;b&gt;각자 자신이 받았던 location 정보를 가질 수 있게 된다&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;생각보다 간단하다. 하지만 문제는 이게 끝이 아니다. 바로 위 움짤을 다시 보자. 알림 내역 페이지에서 뒤로가기 버튼을 눌렀을 때가 상당히 어색하다. '뒤로가기'이면 반대 방향의 애니메이션을 따로 줄 수 있어야 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 양방향으로 트랜지션 주기 (뒤로가기 버튼)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 애니메이션은 클래스명에 따라 지정해둔 스타일대로 일어난다. path가 달라질 때 그게 '뒤로가기'임을 알아내면 클래스 이름을 다르게 줌으로서 애니메이션을 다르게 넣어줄 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;AppBar.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface AppBarProps {
  // 이전 페이지명
  label?: string;
  // 이전 페이지 링크
  to?: string;
  // 커스텀 이벤트
  customEvent?: () =&amp;gt; void;
}

function AppBar({ label, to, customEvent }: AppBarProps) {
  const navigate = useNavigate();
  const onClickAppBar = () =&amp;gt; {
    if (customEvent) {
      customEvent();
    } else {
      to
        ? navigate(to, {
            state: { direction: 'navigate-pop' },
          })
        : navigate(-1);
    }
  };

  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;div onClick={onClickAppBar}&amp;gt;
        &amp;lt;Arrow /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;p&amp;gt;{label}&amp;lt;/p&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}

export default AppBar;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 추려낸 상단 앱바 컴포넌트 코드이다. customEvent는 다음편에 언급할 예정이므로 잠깐 무시해준다. &lt;b&gt;뒤로가기 버튼을 눌렀을 때 location의 state에 direction이라는 객체를 전해준다&lt;/b&gt;. navigate-pop이라는 문자열을 담았음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJXBG/btrOFenQoM8/2pz6L1Wyo2yJfRoJ6NtpOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJXBG/btrOFenQoM8/2pz6L1Wyo2yJfRoJ6NtpOK/img.png&quot; data-alt=&quot;hooks.d.ts&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJXBG/btrOFenQoM8/2pz6L1Wyo2yJfRoJ6NtpOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJXBG%2FbtrOFenQoM8%2F2pz6L1Wyo2yJfRoJ6NtpOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;125&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hooks.d.ts&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 그냥 &lt;code&gt;navigate(-1)&lt;/code&gt;로 히스토리에서 pop을 해주는 식이었지만 위 사진대로 navigate 함수에 옵션을 넣을 수 없다. 그래서 일일이 뒤로갈 주소를 적어주어야 하는 점이 아쉬웠다. 새로 바뀐 코드에서는 외부에서 돌아갈 주소(&lt;code&gt;to&lt;/code&gt;)를 인자로 받을 수 있고, 라우팅 시에 state를 담을 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;TransitionGroup className={'transition-wrapper'}&amp;gt;
  &amp;lt;CSSTransition
    key={pathname}
    timeout={300}
    classNames={location.state?.direction || 'navigate-push'}
  &amp;gt;
    {children}
  &amp;lt;/CSSTransition&amp;gt;
&amp;lt;/TransitionGroup&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSSTransiton에서 classNames를 코드와 같이 작성했다. direction state가 있으면 아까 적어준대로 'navigate-pop'을, 없다면 'navigate-push'를. Transition.css에 -pop에 해당되는 스타일도 추가해주었다. translate 방향과 그림자의 방향을 반대로 잘 조절해준다. 야심차게 실행해보자.&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;942&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqwWBV/btrOCrCwBgn/iCXSH38Pn2C2KAqwPhapRk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqwWBV/btrOCrCwBgn/iCXSH38Pn2C2KAqwPhapRk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqwWBV/btrOCrCwBgn/iCXSH38Pn2C2KAqwPhapRk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bqwWBV/btrOCrCwBgn/iCXSH38Pn2C2KAqwPhapRk/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;433&quot; data-origin-width=&quot;942&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;세상에 이게 무슨일이야. 문제는 각 라우트에 남겨놓았던 각자 자신의 location객체의 state 때문이었다. 알림내역 페이지에서 갖고 있는 &lt;code&gt;location.state&lt;/code&gt;는 &lt;code&gt;null&lt;/code&gt;이다. 반면 뒤로가기 버튼을 통해 라우팅된 메인 페이지의 &lt;code&gt;location.state&lt;/code&gt;에는 direction 객체가 있다. 그로 인해서 두 페이지의 className이 -pop과 -push로 다르게 들어가게 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;두 요소가 똑같은 className을 받아야 할 필요가 있다. 이 때, TransitionGroup에 &lt;b&gt;childFactory&lt;/b&gt; props가 등장한다. childFactory는 exiting 하는 자녀 요소를 업데이트할 때 사용할 수 있다 (라고 문서에서 그럼).&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;개선된 RouteTransiton.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const RouteTransition = ({ location, children }: RouteTransitionProps) =&amp;gt; {
  const pathname = location.pathname;
  const state = location.state;

  return (
    &amp;lt;TransitionGroup
      className={'transition-wrapper'}
      childFactory={(child) =&amp;gt; {
        return React.cloneElement(child, {
          classNames: location.state?.direction || 'navigate-push',
        });
      }}
    &amp;gt;
      &amp;lt;CSSTransition exact key={pathname} timeout={300}&amp;gt;
        {children}
      &amp;lt;/CSSTransition&amp;gt;
    &amp;lt;/TransitionGroup&amp;gt;
  );
};

export default RouteTransition;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;a href=&quot;https://ko.reactjs.org/docs/react-api.html#cloneelement&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;React.cloneElement&lt;/span&gt;&lt;/a&gt;는 인자로 받은 원래 element를 기준으로 새로운 element를 복사하고 반환한다. 반한될때는 원래 요소가 갖고있던 props가 새로운 props와 얕게 합쳐진다고 한다. 각각의 child마다 classNames을 일괄적으로 다시 정해 리턴했다. 이렇게 하면 &lt;b&gt;이동할 라우트의&lt;/b&gt;&lt;b&gt; &lt;/b&gt;&lt;b&gt;location.state를 모든 자녀가 공통으로 클래스명으로 사용&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;680&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brE9Se/btrOCX8I9X2/I1gKz7Bfw8PFG5tZBsWcTk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brE9Se/btrOCX8I9X2/I1gKz7Bfw8PFG5tZBsWcTk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brE9Se/btrOCX8I9X2/I1gKz7Bfw8PFG5tZBsWcTk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/brE9Se/btrOCX8I9X2/I1gKz7Bfw8PFG5tZBsWcTk/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;433&quot; data-origin-width=&quot;942&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;'뒤로가기'를 통해 페이지가 바뀔 때는 enter와 exit 두 요소의 클래스명이 모두 'navigate-pop'으로 적용된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c17nkP/btrOFeajYwl/Q4IkTCJCtk3uKVcGklLOn1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c17nkP/btrOFeajYwl/Q4IkTCJCtk3uKVcGklLOn1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c17nkP/btrOFeajYwl/Q4IkTCJCtk3uKVcGklLOn1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c17nkP/btrOFeajYwl/Q4IkTCJCtk3uKVcGklLOn1/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;1320&quot; height=&quot;802&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 만족스러운 결과물이다. 하지만 현실은 그만큼 녹록치 않다. 더욱 완벽한 경험을 위해선 아래의 문제를 해결해야 했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 라우팅 구조 때문에 생겼던 문제&lt;/li&gt;
&lt;li&gt;페이지 순서에 따라 다른 애니메이션을 보여주기&lt;/li&gt;
&lt;li&gt;라우팅이 아닌, state 변경에 따른 애니메이션 넣기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://9yujin.tistory.com/81?category=1048070&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;[뱅키즈] 7. React-transition-group 라우팅 애니메이션 (2) - 디테일 잡기&lt;/b&gt;&lt;/a&gt; 에서 해결해보겠습니다. 커밍쑨.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://13akstjq.github.io/react/2019/11/08/React-Transition-Group-%EC%99%84%EB%B2%BD-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;참고1&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://devnm.tistory.com/10?category=1258200&quot; target=&quot;_self&quot;&gt;&lt;span&gt;참고2&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>  긴호흡/뱅키즈</category>
      <category>react-transition-group</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/73</guid>
      <comments>https://9yujin.tistory.com/73#entry73comment</comments>
      <pubDate>Sat, 15 Oct 2022 02:52:30 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch2.1. The Process</title>
      <link>https://9yujin.tistory.com/72</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Program Execution의 기본 단위.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multiprogramming&lt;/b&gt; : 프로그램을 여러개&lt;br /&gt;&lt;b&gt;Multiprocessing&lt;/b&gt;: 프로세스가 여러개. 하나의 job을 처리하는 형태&lt;/li&gt;
&lt;/ul&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;스크린샷 2022-10-13 오후 3.54.20.png&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lwxgl/btrOBCc4qMD/3peu8itHk8BqxX8WF8RLxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lwxgl/btrOBCc4qMD/3peu8itHk8BqxX8WF8RLxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lwxgl/btrOBCc4qMD/3peu8itHk8BqxX8WF8RLxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flwxgl%2FbtrOBCc4qMD%2F3peu8itHk8BqxX8WF8RLxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;304&quot; data-filename=&quot;스크린샷 2022-10-13 오후 3.54.20.png&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Pseudoparallelism&lt;/b&gt; : 실제로 병행 프로세스인건 아니지만, 사람이 볼때는 그런 것 처럼 느껴짐. 여러 프로세스가 빠르게 돌아가면서 처리됨으로서 동시에 처리되는것 처럼.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스가 가지고 있는것&lt;/b&gt; : 주소 공간, 프로그램 실행 코드, 스택-스택포인터, PC, 레지스터(의 상태), OS에게 요청해서 얻은 리소스 - 등을 프로세스가 갖고 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Process in OS&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 프로세스는 유니크한 &lt;b&gt;PID&lt;/b&gt;로 관리된다.&lt;/li&gt;
&lt;li&gt;OS에는 프로세스들을 관리하기 위한 &lt;b&gt;Process Table (PT)&lt;/b&gt;가 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Process Control Block (PCB)&lt;/b&gt; : PT의 엔트리&lt;br /&gt;OS에 있는 구조체, 프로세스에 대한 모든 정보를 기록한다.&lt;/li&gt;
&lt;li&gt;PCB에선 execution state (Ready, Running, Blocked) , PC, SP, 프로세서 레지스터 등을 저장하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Process creation&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스는 다른 프로세스를 create할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Parent Process&lt;/b&gt;, &lt;b&gt;Child Process&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;ps 명령어를 통해 각 프로세스의 부모 프로세스 PID를 확인할 수 있다 : PPID&lt;/li&gt;
&lt;li&gt;&lt;b&gt;fork()&lt;/b&gt; 라는 시스템 콜을 호출해 child를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;FORK (UNIX에서)&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 PCB를 초기화한다.&lt;/li&gt;
&lt;li&gt;새 주소공간을 할당하고, (PT를 만든다는 얘기)&lt;/li&gt;
&lt;li&gt;parent의 주소공간에 있는 데이터를 복사한다.&lt;/li&gt;
&lt;li&gt;OS가 parent에 할당했던 리소스를 child에서 할당.&lt;/li&gt;
&lt;li&gt;새로 만들어진 PCB는 ready queue에 위치함.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fork() 시스템 콜은 두 군데서 리턴된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;parent : child의 PID 리턴&lt;/li&gt;
&lt;li&gt;child : 0 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시&lt;/b&gt; : 결과적으로 parent에서 global은 200, child에서 global은 20 이된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221014151909954.png&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bobbir/btrOC5YQ0M5/XJ1MDiQRb1jFWuqxPVNG5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bobbir/btrOC5YQ0M5/XJ1MDiQRb1jFWuqxPVNG5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bobbir/btrOC5YQ0M5/XJ1MDiQRb1jFWuqxPVNG5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbobbir%2FbtrOC5YQ0M5%2FXJ1MDiQRb1jFWuqxPVNG5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;966&quot; data-filename=&quot;image-20221014151909954.png&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;966&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;b&gt;EXEC&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;int exec(char *prog, char **argv);&lt;/code&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 프로세스를 멈추고 새로운 프로그램(prog)를 로딩함&lt;/li&gt;
&lt;li&gt;PID는 바뀌지 않음. 새로운 프로세스를 생성하는게 아니다!!&lt;/li&gt;
&lt;li&gt;PCB는 ready queue에 들어간다.&lt;/li&gt;
&lt;li&gt;exec() 호출이 성공하면 수행할 프로그램 내용이 바뀌므로, 호출 성공시의 리턴값이 의미가 없다.&lt;/li&gt;
&lt;li&gt;에러가 나면 -1 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221014153102680.png&quot; data-origin-width=&quot;1327&quot; data-origin-height=&quot;884&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/raylu/btrOCI34Nmc/bOwkdvXcKUOy1UvNaJlh00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/raylu/btrOCI34Nmc/bOwkdvXcKUOy1UvNaJlh00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/raylu/btrOCI34Nmc/bOwkdvXcKUOy1UvNaJlh00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fraylu%2FbtrOCI34Nmc%2FbOwkdvXcKUOy1UvNaJlh00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1327&quot; height=&quot;884&quot; data-filename=&quot;image-20221014153102680.png&quot; data-origin-width=&quot;1327&quot; data-origin-height=&quot;884&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;b&gt;A Simple Shell&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221014153446981.png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;839&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tFjCm/btrOCr2BdIG/wUrffta0DqOwcSBSIN0jw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tFjCm/btrOCr2BdIG/wUrffta0DqOwcSBSIN0jw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tFjCm/btrOCr2BdIG/wUrffta0DqOwcSBSIN0jw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtFjCm%2FbtrOCr2BdIG%2FwUrffta0DqOwcSBSIN0jw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1418&quot; height=&quot;839&quot; data-filename=&quot;image-20221014153446981.png&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;839&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Shell에서 명령어를 실행시키는 그런 코드&lt;/li&gt;
&lt;li&gt;Bash (born again shell) : 제일 최상위 프로세스&lt;/li&gt;
&lt;li&gt;터미널에서 명령어를 읽어들이고, fork()로 새 프로세스 생성.&lt;/li&gt;
&lt;li&gt;child에서는 읽어들인 명령어(command)로 execute하고,&lt;/li&gt;
&lt;li&gt;parent에서는 child가 terminate 될 때까지 wait 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Process Termination&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Normal exit (voluntary)&lt;/b&gt; : main()에서 &lt;code&gt;exit(0)&lt;/code&gt;를 호출해 정상적으로 종료되는 것.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Error exit (voluntary)&lt;/b&gt; : main()에서 오류가 났을 때 exit(?)을 호출해 종료. 부모 프로세스에서 arg를 받아 오류의 종류를 구분할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Fatal error (involuntary)&lt;/b&gt; : fault 같은거 발생하면 비정상적으로 종료&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Exit()&lt;/code&gt;도 시스템 콜임&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Process States&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Ready&lt;/b&gt; : ready queue에 기다리면서 CPU를 할당받기 기다림&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Running&lt;/b&gt; : CPU를 할당받아 수행 중. CPU (코어) 숫자만큼 동시에 운영 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Blocked&lt;/b&gt; : CPU가 할당되고 있어도 더 진행할 수 없음.&lt;br /&gt;ex. I/O 돌릴 때, read된 결과값이 메모리에 올때까지 기다려야 함 / page fault&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221014160202842.png&quot; data-origin-width=&quot;1587&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co45ac/btrOCxO53RR/sVhPjl0DSwCT6fT0dgzzrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co45ac/btrOCxO53RR/sVhPjl0DSwCT6fT0dgzzrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co45ac/btrOCxO53RR/sVhPjl0DSwCT6fT0dgzzrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco45ac%2FbtrOCxO53RR%2FsVhPjl0DSwCT6fT0dgzzrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1587&quot; height=&quot;754&quot; data-filename=&quot;image-20221014160202842.png&quot; data-origin-width=&quot;1587&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;preemptive&lt;/b&gt; : CPU 할당해놓고 너무 오래쓰면 다시 압수 (스케쥴러가)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Context Switch&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;하드웨어가 자동적으로 PC와 레지스터에 저장된 값들을 지정단 stack에 저장 (백업)&lt;/li&gt;
&lt;li&gt;interrupt 걸리면 Interrupt Handler Routine을 실행. (interrupt vector가 가리키는 주소에 있음)&lt;/li&gt;
&lt;li&gt;나머지 필요한 레지스터 값들을 저장한다. 이런건 C로 하기 어려워서 어셈블리어 되어있음.&lt;/li&gt;
&lt;li&gt;프로시저가 돌 수 있도록 새로운 스택을 세팅함 (현재 사용하고 있는 스택 사용하면 프로그램이 엉킬수 있음)&lt;/li&gt;
&lt;li&gt;interrupt 서비스 루틴을 실행함 (이건 C로 짜여져 있음 / 아까 세팅한 새 스택에서!)&lt;/li&gt;
&lt;li&gt;서비스 루틴이 끝나면 cpu 스케듈러가 돈다. ready queue에 있는 프로세스 중 하나가 실행됨.&lt;/li&gt;
&lt;li&gt;C procedure returns to the assembly code(?)&lt;br /&gt;처리할 프로세스의 PCB에서 CPU 상태들을 load 한다고 함.&lt;/li&gt;
&lt;li&gt;어셈블리 코드가 새로운 프로세스를 시작함. PCB에 있던 CPU 상태들을 가져와, PC가 새로운 프로그램을 가리키도록 세팅..! 모드도 다시 커널에서 유저로 바뀜.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;context switch 하는 과정도 오버헤드가 쫌 있음 (시간걸림)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image-20221014170510439.png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;1184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRVce/btrODdJgYUw/G8IfkINRymB1tlsGvVKytk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRVce/btrODdJgYUw/G8IfkINRymB1tlsGvVKytk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRVce/btrODdJgYUw/G8IfkINRymB1tlsGvVKytk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRVce%2FbtrODdJgYUw%2FG8IfkINRymB1tlsGvVKytk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1964&quot; height=&quot;1184&quot; data-filename=&quot;image-20221014170510439.png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;1184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/72</guid>
      <comments>https://9yujin.tistory.com/72#entry72comment</comments>
      <pubDate>Fri, 14 Oct 2022 17:13:09 +0900</pubDate>
    </item>
    <item>
      <title>[뱅키즈] 5. React Query 마이그레이션</title>
      <link>https://9yujin.tistory.com/71</link>
      <description>&lt;p data-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://tech.kakao.com/2022/06/13/react-query/&quot;&gt;My구독의 React Query 전환기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=MArE6Hy371c&quot;&gt;React Query와 상태관리&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;b&gt;정말 신기한건, 뱅키즈에서도 이와 똑같은 문제를 느꼈다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 리액트 쿼리로 전환한 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 &lt;b&gt;Redux Toolkit&lt;/b&gt;을 도입했을땐 정말 신선하게 느꼈다. 고스락 프로젝트에서 좋다고 사용했던 순수 리덕스와 thunk에서의 액션, 액션 타입, 리듀서 등의 이런저런 방대한 코드가 없어도 된다는 게 편리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 점점 프로젝트가 방대해지고 처음 기획과 달라지는 부분이 생기면서 조금씩 어려운 부분이 생기기 시작했다.&lt;/p&gt;
&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;createChallengeSlice.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type TcreateChallengeState = {
  status: TFetchStatus;
  error: string | undefined;
  challenge: {
    challengeCategory: string;
    isMom: boolean | null;
    itemName: string | null;
    title: string;
    interestRate: 10 | 20 | 30 | null;
    interestPrice: number;
    totalPrice: number;
    weekPrice: number;
    weeks: number;
    fileName: string;
  };
  response: TPostChallengeResponseState | null;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돈길생성 스토어에는 아래와 같은 형식으로 저장된다. 돈길 계약하기의 단계를 밟으면서 받은 정보들을 &lt;code&gt;state.challenge&lt;/code&gt;에 저장한다. 각 다른 path에서 진행하기 때문에 전역으로 관리를 해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계약서에 사인하고 다음 버튼을 누르면 스토어 정보를 가져와 api 요청의 인자로 넣어 서버로 전송한다. POST 요청의 응답이 오면 그 정보들 또한 스토어에 저장한다. 응답받은 데이터를 가져와 돈길 계약요청 완료 모달을 띄우기 때문이다. 그렇기 때문에 state 안에서 status, error, response 상태도 함께 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const postChallenge = createAsyncThunk(
  'createChallenge/postChallenge',
  async (axiosPrivate: AxiosInstance, { getState, rejectWithValue }) =&amp;gt; {
    try {
      const { createChallenge } = getState() as RootState;
      const response = await axiosPrivate.post(
        '/challenge',
        createChallenge.challenge,
      );
      return response.data;
    } catch (err) {
      if (axios.isAxiosError(err)) {
        return rejectWithValue(err);
      }
    }
  },
);

export const createChallengeSlice = createSlice({
  name: 'createChallenge',
  initialState,
  reducers: {
    setParent(state, action: PayloadAction&amp;lt;boolean&amp;gt;) {
      state.challenge.isMom = action.payload;
    },
    setItemName(state, action: PayloadAction&amp;lt;string&amp;gt;) {
      state.challenge.itemName = action.payload;
    },
    setTitle(state, action: PayloadAction&amp;lt;string&amp;gt;) {
      state.challenge.title = action.payload;
    },

    // ...생략

    resetChallengePayload(state) {
      return initialState;
    },
  },
  extraReducers: (builder) =&amp;gt; {
    builder
      .addCase(postChallenge.pending, (state) =&amp;gt; {
        state.status = 'loading';
      })
      .addCase(postChallenge.fulfilled, (state, action) =&amp;gt; {
        state.status = 'succeeded';
        state.response = action.payload;
      })
      .addCase(postChallenge.rejected, (state, action) =&amp;gt; {
        state.status = 'failed';
      });
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;createAsyncThunk&lt;/code&gt;와 &lt;code&gt;createSlice&lt;/code&gt; 함수이다. 클라이언트 상태와 서버 상태가 분리되지 않고 하나로 관리되고 있다. 그 뿐이 아니라 API 요청 상태나 에러 유무까지 (전역으로 관리할 필요가 없는 것들임에도) 스토어에서 담당하고 있다. &lt;b&gt;리덕스 사용이 비효율적이고 Boilerlate 코드가 비대해진다&amp;sup1;.&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;심지어 제안받은 돈길 (proposedDonil)의 경우는 오직 비동기 통신을 위해서만 상태 관리 라이브러리를 사용했다. 단순히 상태관리 라이브러리를 의도에 맞지 않게 사용한다는 것 외에도 다른 문제가 있었다. 부모가 '제안받은 돈길'을 수락하면 해당 돈길은 '금주의 돈길'로 보여져야 한다. '제안받은 돈길'과 '금주의 돈길'은 다른 슬라이스에서 관리되고 있기 때문에 굉장히 번거로운 작업이 필요했다. 돈길 수락 PATCH 요청을 보내면서 &lt;b&gt;서버의 상태는 변화되지만, 리덕스에 저장하고 있는 돈길의 상태는 클라이언트에서 직접 업데이트를 해주어야 한다&amp;sup2;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user 정보와 family 정보는 앱의 전반적인 부분에서 자주 쓰이는 상태들이다. 기존엔 홈('/')이 렌더링 될때 thunk 액션을 디스패치해서 정보를 받아왔다. 보통의 루트대로 홈에서 마이페이지로 가는 경우에는 받아온 정보가 있을테니 상관 없겠지만, 새로고침하거나 알림탭에서 바로 마이페이지로 이동하는 경우에는 받아온 정보가 없다. 스토어에서 값을 확인해보고 받아온 데이터가 없으면 패칭하는 로직을 구현했다가 어, 이거 이미 있는거잖아. 곧바로 리액트 쿼리를 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 두번째 써보는 리액트 쿼리&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;├── App.tsx
├── assets
├── components
├── index.tsx
├── lib
│   ├── apis # API 관련 로직 규격화 및 추상화
│   │   ├── ${controller}/${controller}API.ts
│   │   └── ${controller}/${controller}DTO.ts
│   ├── constants # queryKey 별도 파일을 통해 객체로 관리
│   ├── hooks
│   │   └── queries # query 사용 관련 공통 로직 함수화 custom hook
│   ├── styles
│   ├── types
│   └── utils
├── pages
└── store
    ├── app
    └── slices&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;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { axiosPrivate } from '../axios';
import { IFamilyGroupPayload, IFamilyDTO, IKidListDTO } from './familyDTO';

const familyAPI = {
  getFamily: async (): Promise&amp;lt;IFamilyDTO&amp;gt; =&amp;gt; {
    const response = await axiosPrivate.get('/family');
    const data = response.data;
    return data;
  },

  getKid: async (): Promise&amp;lt;IKidListDTO[]&amp;gt; =&amp;gt; {
    const response = await axiosPrivate.get('/family/kid');
    const data = response.data;
    return data;
  },

  // ...생략
};

export default familyAPI;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;API 호출 함수들은 따로 분리해 객체 형식으로 내보낸다&lt;/b&gt;&amp;sup1;&lt;b&gt;.&lt;/b&gt; 객체 리터럴을 통해 싱글톤으로 만들 수 있다. 서버의 로직과 비슷하게 controller 그대로 분류를 해주었다. &lt;code&gt;userAPI.getUser&lt;/code&gt;, &lt;code&gt;challengeAPI.postChallenge&lt;/code&gt; 따위로 가져와 사용한다. 스웨거 명세를 보고 거의 그대로 옮겨왔기 때문에 controller 이름과 메소드, uri만 보고도 편하게 자동완성으로 함수를 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export interface IMyPageDTO {
  user: IUserDTO;
  kid: IKidDTO | null;
  parent: IParentDTO | null;
}

export interface IUserDTO {
  username: string;
  isFemale: boolean;
  isKid: boolean;
  birthday: string;
  phone: string | null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 반환값의 타입으로 &lt;b&gt;백엔드에서 사용하는 DTO의 타입을 그대로 가져와 클라이언트 코드에 이식했다&amp;sup2;&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에서 사용하는 DTO를 포함하는 DTO도 있음. 이런 경우에 굉장히 편리하게 타입을 지정해줄 수 있다. 받아온 데이터의 일부만 필요할 때에는 utility 타입을 통해서 각 컴포넌트마다 필요한 형태로 가공해 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;interface SecondRowProps
  extends Pick&amp;lt;IChallengeDTO, 'totalPrice' | 'weekPrice' | 'interestRate'&amp;gt; {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영수증 모달 중 두번째 줄 컴포넌트에 들어가는 props의 타입이다. IChallengeDTO에서 필요한 값만 &lt;code&gt;Pick&lt;/code&gt;을 통해 가져온다. 기존에 RTK로 비동기 처리를 했을 때에는 서버 상태의 타입과 클라이언트 로직에서 필요한 타입이 혼재되거나 중복되는게 많았다.&lt;/p&gt;
&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;sup3;.&lt;/b&gt; 이 부분에도 특히 고민이 많았다. 모든 &lt;code&gt;useQuery&lt;/code&gt;문을 커스텀훅으로 만들어서 API 함수와 같이 분리하려고도 했다. 쿼리문을 사용하는 컴포넌트 내에서 쿼리키와 API 객체를 임포트할 필요 없이 훅 하나로 간단히 쓸 수 있다는 장점이 있었다. 하지만 그만큼 불필요한 코드가 많아지고 쓸데없는 작업이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const useLeaveFamilyMutation = (
  options?: UseMutationOptions&amp;lt;IFamilyDTO, AxiosError, any, void&amp;gt;,
) =&amp;gt; {
  return useMutation(familyApi.leaveFamily, options);
};

const { mutate: MutateLeaveFamily } = useLeaveFamilyMutation({
  onSuccess: () =&amp;gt; {
    openLeaveGroupCompletedSheet();
    queryClient.invalidateQueries(queryKeys.FAMILY);
    queryClient.invalidateQueries(queryKeys.FAMILY_KID);
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로. 인자로 넣어주는 옵션의 타입을 적으며 삽질한건 둘째 치고, 매번 옵션을 넣어주어야 한다면 굳이 커스텀훅으로 만들 필요가 없다는 의견이 있었다. &lt;code&gt;useUserQuery&lt;/code&gt;, &lt;code&gt;useFamilyQuery&lt;/code&gt;처럼 자주 호출하는 함수나, 알림내역 무한스크롤 쿼리처럼 복잡한 로직들만 따로 훅으로 작성하기로.&lt;/p&gt;
&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; 해보려고 했다. 쿼리키의 이름은 api의 url을 그대로 가져온다, 객체 리터럴 형식으로 작성한다. 이 역시 고스락 프로젝트에서는 고려하지 않고 대충 넘어갔던 부분이었음. &lt;code&gt;queryKeys.&lt;/code&gt; 로 자동완성을 이용할 수 있는점이 상당히 편했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 좋았던 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뻔하지만, 서버 상태 캐싱.&lt;/li&gt;
&lt;li&gt;코드량이 굉장히 적어졌다. 특히 slice. 사실 원래 클라이언트 상태는 그리 많지 않았다.&lt;/li&gt;
&lt;li&gt;제공하는 API 상태를 이용해서 다양한 로직을 쉽게 구현할 수 있다. enabled 옵션과 함께 사용해 쿼리문을 지정한 순서대로 수행할 수도 있고, 무한스크롤도 편히 적용했다.&lt;/li&gt;
&lt;li&gt;onSuccess와 onError를 통해 요청이 성공했을 때와 실패했을 때 실행할 로직을 넣어줄 수 있다. defaultOptions 에서 공통으로 에러핸들링을 해준다면, useQuery를 사용하는 컴포넌트에서는 오직 요청이 성공했을 때만의 상황만 볼 수 있다.&lt;/li&gt;
&lt;li&gt;서버 상태와 클라이언트 상태가 완벽하게 분리되었고, 항상 최신 상태를 유지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  긴호흡/뱅키즈</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/71</guid>
      <comments>https://9yujin.tistory.com/71#entry71comment</comments>
      <pubDate>Thu, 13 Oct 2022 02:55:31 +0900</pubDate>
    </item>
    <item>
      <title>[뱅키즈] 4. 돈길 계약하기 UI 개발과 복잡한 로직 추상화</title>
      <link>https://9yujin.tistory.com/70</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7DAgu/btrMS3cvqjP/Y82Ly1khqxVXNRBZ56VRt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7DAgu/btrMS3cvqjP/Y82Ly1khqxVXNRBZ56VRt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7DAgu/btrMS3cvqjP/Y82Ly1khqxVXNRBZ56VRt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7DAgu%2FbtrMS3cvqjP%2FY82Ly1khqxVXNRBZ56VRt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별할건 없는 단순 구현이지만 많이 고민하고 노력한게 아까워서 적는 포스팅. 앱의 가장 핵심인 돈길을 생성하는 과정을 '돈길 계약하기'라는 워딩을 통해 아이들도 재미있게 느낄 수 있도록 했다. 총 다섯개의 단계를 밟아 정보를 입력하고, 마지막에 사인을 하고 제출하면 계약 영수증이 보여지는 형식이다. 기획과 디자인팀의 노고가 느껴진다. 그리고 저걸 구현한 나도... 기능 하나하나에 많은 공을 들였어서 그런지 특히 애정이 있는 뷰들이다.&lt;br /&gt;&lt;br /&gt;팀원과 기술블로그에 대한 이야기을 나눈 적이 있다. 프로젝트 경험을 기록할 때 어렵게 공부하며 사용했던 기술을 정리할 수 도 있고, 어려운 화면을 구현하며 머리 싸맸던 고민을 기록할 수 도 있다. 난 블로그 포스팅에 시간을 꽤 많이 쓰는 편이라, 이 모든것들을 기록으로 남기는게 과연 효율적인 공부방법인지에 확신이 없었다. 팀원에게 어떻게 생각하냐고 물었더니 - 어려운 기술은 나중에 다시 와서 볼 수 있어서 좋은 반면, &lt;b&gt;단순 구현에 대한 고민은 그 과정에서 너의 피지컬이 올라갔으니 그거만으로도 이득이다 &lt;/b&gt;- 라고 생각한댔다. 그래도 그냥 넘어가기는 뭔가 아쉽잖아. 앞으로 기술 블로그 운영하는 방법을 더 많이 고민해봐야겠다. 그래도 뱅키즈하면서 개발 피지컬이 확 좋아진건 틀림없다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 바텀시트 라이브러리 - 바텀시트 바깥 부분 터치 처리 커스텀훅&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zqY2a/btrNmzIYIxN/g2pISr0XxQPXsRvKk8h5n1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zqY2a/btrNmzIYIxN/g2pISr0XxQPXsRvKk8h5n1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zqY2a/btrNmzIYIxN/g2pISr0XxQPXsRvKk8h5n1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/zqY2a/btrNmzIYIxN/g2pISr0XxQPXsRvKk8h5n1/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;956&quot; height=&quot;794&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;794&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 class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;import { useEffect, useRef } from 'react';

function useBottomSheetOutSideRef(handler: () =&amp;gt; void) {
  const sheetDivRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);
  const inputDivRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  useEffect(() =&amp;gt; {
    const handleClickOutside = (e: MouseEvent): void =&amp;gt; {
      if (
        sheetDivRef.current &amp;amp;&amp;amp;
        inputDivRef.current &amp;amp;&amp;amp;
        !inputDivRef.current.contains(e.target as Node) &amp;amp;&amp;amp;
        !sheetDivRef.current.contains(e.target as Node)
      ) {
        handler();
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () =&amp;gt; {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [inputDivRef]);

  return [sheetDivRef, inputDivRef];
}

export default useBottomSheetOutSideRef;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바텀시트와 인풋 요소에 접근하기 위한 ref를 두개 둔다. 클릭 이벤트가 발생한 곳이 해당 조건을 만족할 때에만 전달된 핸들러 함수를 실행되도록 했다. 그리고 훅에선 그 ref를 반환한다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Step3({ currentStep }: { currentStep: number }) {

  const [open, onOpen, onDismiss] = useBottomSheet(false);
  const [sheetDivRef, inputDivRef] = useBottomSheetOutSideRef(onDismiss);

  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;InputSection validate={validateAmount}&amp;gt;
        &amp;lt;div onClick={onOpen} ref={inputDivRef}&amp;gt;
          &amp;lt;InputForm
            sheetOpen={open}
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;p&amp;gt;{validateAmount.message}&amp;lt;/p&amp;gt;
      &amp;lt;/InputSection&amp;gt;

      &amp;lt;ContractSheet
        open={open}
        onDismiss={onDismiss}
        sheetRef={sheetDivRef}
      &amp;gt;
        &amp;lt;div&amp;gt;{/* 바텀시트 */}&amp;lt;/div&amp;gt;
      &amp;lt;/ContractSheet&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}

export default Step3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 컴포넌트에선 이런식으로 사용할 수 있다. 관련이 있는 부분만 남기고 지워서 가져왔더니, 알아보기 조금 힘들수도. 중요한 부분은 커스텀훅에서 반환받은 inputRef와 sheetDivRef를 각각 컴포넌트에 넘겨주는 것!! 바텀시트 닫는 함수를 인자로 넘겨서 핸들러 함수로 사용한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 계산기를 형상화한 금액 입력 커스텀 키보드&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G5xV7/btrNqMGHMJf/P7c2wsKOgyHSuxzlGT3vR1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G5xV7/btrNqMGHMJf/P7c2wsKOgyHSuxzlGT3vR1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G5xV7/btrNqMGHMJf/P7c2wsKOgyHSuxzlGT3vR1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/G5xV7/btrNqMGHMJf/P7c2wsKOgyHSuxzlGT3vR1/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;956&quot; height=&quot;794&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 금액을 입력할 수 있는 커스텀 키보드이다. 아이들이 재미있게 돈길을 계약할 수 있도록 계산기을 형상화 한 디자인이다. 기획에서 요구한 점이 꽤 특이했다. 각 금액에 해당하는 지폐 모양의 버튼을 누르면 그만큼의 돈이 추가된다. 오른쪽 아래 두 버튼 중, 왼쪽을 누르면 가장 최근에 추가한 금액만큼 지워진다. 예를 들어 500원, 5000원, 만원 순서대로 눌렀다가 취소하면 : 15500 &amp;rarr; 5500 &amp;rarr; 500 &amp;rarr; x 순으로 돌아가게 되는 것.&lt;br /&gt;&lt;br /&gt;이를 구현하기 위해 스택을 사용했다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function useStackAmount() {
  const [amountStack, setAmountStack] = useState&amp;lt;number[]&amp;gt;([]);

  const pushAmount = (amount: number) =&amp;gt; {
    setAmountStack((prev) =&amp;gt; [...prev, amount]);
  };
  const popAmount = () =&amp;gt; {
    setAmountStack((prev) =&amp;gt; prev.filter((v, i) =&amp;gt; i !== prev.length - 1));
  };
  const resetAmount = () =&amp;gt; {
    setAmountStack([]);
  };

  return [amountStack, pushAmount, popAmount, resetAmount] as const;
}

export default useStackAmount;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에 스택이 있나?? 나중에 찾아봐야지. 일단 그냥 비슷하게 구현했다. 버튼을 클릭할 때 마다 숫자 배열의 뒤에 값을 추가한다. 삭제 (돌아가기) 버튼을 누를 땐 배열의 마지막 값을 삭제해줌.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  // stack에 있는 숫자들 더해서 form state에 저장
  useEffect(() =&amp;gt; {
    const amount = amountStack.reduce((acc, cur) =&amp;gt; {
      return (acc += cur);
    }, 0);
    setForm({ ...form, contractAmount: amount });
  }, [amountStack]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환하는건 선택한 지폐들의 합산이 아닌 그냥 배열이기 때문에, 전체금액으로 다 더해주는 과정이 필요하다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 밸리데이션 검사 커스텀훅&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 금액 입력 관련 움짤에서 유효성 검사 문구가 제때제때 잘 뜨는걸 확인할 수 있다. 목표 금액은 1500원 이상 30만원 이하만 입력할 수 있다. 각각 단계에서 들어가는 값들의 유효성을 검사하는 로직을 컴포넌트 내에서 처리하려고 했지만, 너무너무 길어지는 바람에. 그 로직을 컴포넌트와 분리해보려고 했다. 커스텀훅이다. (또?)&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type TFormType = 'contractName' | 'contractAmount' | 'comment';

const validateResultContent = {
  // ...생략,
  contractAmount: {
    default: { error: false, message: '최소 1500원에서 최대 30만원까지 설정할 수 있어요!' },
    under: { error: true, message: '1,500원 이상으로 부탁해요!' },
    over: { error: true, message: '30만원 이하로 부탁해요!' },
    pass: { error: false, message: '적절한 금액이에요!' },
  },
};


function useValidation() {
  const [validateResult, setValidateResult] =
    useState&amp;lt;TValidationResult&amp;gt;(initialState);

  const validateContractName = (
    value: string,
    existChallengeNames: string[],
  ) =&amp;gt; {
    //... 생략
  };

  const validateContractAmount = (value: number) =&amp;gt; {
    if (!value) setValidateResult(validateResultContent.contractAmount.default);
    else if (value &amp;lt; 1500)
      setValidateResult(validateResultContent.contractAmount.under);
    else if (value &amp;gt; 300000)
      setValidateResult(validateResultContent.contractAmount.over);
    else setValidateResult(validateResultContent.contractAmount.pass);
  };

  const validateComment = (value: string) =&amp;gt; {
      //... 생략
  };

  const checkValidate = (
    formType: TFormType,
    value: string | number,
    existChallengeNames?: string[],
  ) =&amp;gt; {
    if (formType === 'contractName' &amp;amp;&amp;amp; typeof value === 'string') {
      validateContractName(value, existChallengeNames!);
    }

    if (formType === 'contractAmount' &amp;amp;&amp;amp; typeof value === 'number') {
      validateContractAmount(value);
    }

    if (formType === 'comment' &amp;amp;&amp;amp; typeof value === 'string') {
      validateComment(value);
    }
  };

  return [validateResult, checkValidate] as const;
}

export default useValidation;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로!! validate하는 함수와 validate 결과를 리턴해준다. form마다 다른 검사를 수행한 뒤에, error 여부와 message를 담은 객체를 결과로 보낸다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Step3({ currentStep }: { currentStep: number }) {
  const [disabledNext, setDisabledNext] = useState&amp;lt;boolean&amp;gt;(true);
  const [validateName, checkValidateName] = useValidation();
  const [validateAmount, checkValidateAmount] = useValidation();

  //form 값이 바뀔때마다 유효성검사 실행
  useEffect(() =&amp;gt; {
    checkValidateName('contractName', form.contractName, existingDongilName);
    checkValidateAmount('contractAmount', form.contractAmount);
  }, [form]);

  // 다음으로 버튼 활성화,비활성화 처리
  useEffect(() =&amp;gt; {
    validateName.message === '완전 좋은 이름인데요!' &amp;amp;&amp;amp;
    validateAmount.message === '적절한 금액이에요!'
      ? setDisabledNext(false)
      : setDisabledNext(true);
  }, [validateAmount, validateName]);

  return (
    &amp;lt;Wrapper&amp;gt;

    {/* ...생략 */}
      &amp;lt;InputSection validate={validateAmount}&amp;gt;
          &amp;lt;InputForm
            placeholder=&quot;부모님과 함께 모을 금액&quot;
            value={
              form.contractAmount === 0
                ? ''
                : getCommaThreeDigits(form.contractAmount)
            }
            error={validateAmount.error}
            onBlur={() =&amp;gt; {
              checkValidateAmount('contractAmount', form.contractAmount);
            }}
          /&amp;gt;
        &amp;lt;p&amp;gt;{validateAmount.message}&amp;lt;/p&amp;gt;
      &amp;lt;/InputSection&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 페이지에 폼이 두개 이상 들어가는 경우에도, 훅을 필요한 만큼 가져와서 쓰면 된다. 배열로 리턴하기 때문에 이름을 지정해서 받아올 수 있어 편리하다. 폼 내용이 바뀔 때, 인풋이 blur 될 때 검사를 매번 실행한다. 반환받은 결과값에서 error가 아닐 때에만 다음으로 버튼을 활성화 시킨다.&lt;br /&gt;&lt;br /&gt;객체에서 error값으로 조건을 두면 안되나?? - error 부울로 인풋 테두리의 색깔을 결정하는데, 입력한 값이 없을 때 기본값으로 error가 false이다. 검사에 통과했을 때 나오는 메시지를 조건으로 두었다. 약간 아쉽긴 한데, 뭐... 그래도 직관적이야.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 스와이프 저금액 입력&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b301QM/btrNtEv6rP0/AQUaLVMkcy2do45i2z1Qrk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b301QM/btrNtEv6rP0/AQUaLVMkcy2do45i2z1Qrk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b301QM/btrNtEv6rP0/AQUaLVMkcy2do45i2z1Qrk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b301QM/btrNtEv6rP0/AQUaLVMkcy2do45i2z1Qrk/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;1126&quot; height=&quot;762&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&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;/li&gt;
&lt;li&gt;그리고 무엇보다 저 &lt;b&gt;스와이프 바 css&lt;/b&gt;가 제일 문제. 하지만 디자인이 제일 중요하니까!!&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;첫번째.&lt;/b&gt;&lt;br /&gt;원래 기획은 돈을 모으는 기간과 매주 저금액에 따라 이자를 받도록 했었다. 매주 1000원을 모으고 5주를 모으면 5000원을 받는 형식. 하지만 기간과 매주 저금액을 정하기 전에 &lt;b&gt;상한과 하한값&lt;/b&gt;을 먼저 보여줘야 했는데, 상한과 하한값은 전체 금액에 따라 달라진다. 근데 또 전체 금액은 이자액에 따라 달라짐. 흐름이 뒤엉켜서 애초에 불가능한 방법이었다.&lt;br /&gt;&lt;br /&gt;그래서 결국 기획을 바꿔버림. 전체 저금액 * 이자율로 먼저 계산을 해두고 사용했다. 그럼 흐름이 순서대로 가서 계산된 값을 쓸 수 있음.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const getChallengeStep4Prices = (
  totalPrice: number,
  interestRate: 10 | 20 | 30 | null,
) =&amp;gt; {
  // 500원 단위로 올림
  const getRoundUpBy500 = (price: number) =&amp;gt;
    price % 500 === 0 ? price : price - (price % 500) + 500;

  const maxPrice = interestRate
    ? getRoundUpBy500(((1 - 0.01 * interestRate) * totalPrice) / 3)
    : getRoundUpBy500((0.8 * totalPrice) / 3);
  const minPrice = interestRate
    ? getRoundUpBy500(((1 - 0.01 * interestRate) * totalPrice) / 15)
    : getRoundUpBy500((0.8 * totalPrice) / 15);

  // 20퍼센트일때 가정한 중간금액
  const middlePrice =
    (minPrice + maxPrice) / 2 - (((minPrice + maxPrice) / 2) % 500);

  return { minPrice, maxPrice, middlePrice };
}

export default getChallengeStep4Prices;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뱅키즈에서 계약할 수 있는 기간은 3주부터 15주까지이다. 이에 맞추려고 (이자부스터를 제외한) 혼자 모으는 금액에 3 또는 15를 나눈 금액으로 상한 하한을 설정한다. 기본값은 이자율이 20%인 경우를 보여줌.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;두번째.&lt;/b&gt;&lt;br /&gt;필요 주수와, 끝나는 주의 주 (n월 n주차)를 계산해서 보여준다. &lt;a href=&quot;https://falsy.me/javascript-%EC%9E%85%EB%A0%A5%ED%95%9C-%EB%82%A0%EC%A7%9C%EC%9D%98-%ED%95%B4%EB%8B%B9-%EB%8B%AC-%EA%B8%B0%EC%A4%80-%EC%A3%BC%EC%B0%A8-%EA%B5%AC%ED%95%98%EA%B8%B0/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;주차 계산&lt;/span&gt;&lt;/a&gt; 로직은 여기서 가져옴. JS로 구현된 함수를 타입으로 바꾸기만 했다. 덕분에 제일 걱정했던 부분을 빠르게 해결할 수 있었다. 휴! 코드를 붙여넣으려고 했다가 그냥 단순계산인데 굳이 필요할까 싶어서.. 스킵.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;세번째.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 제일 고생했던 부분. &lt;code&gt;rc-slider&lt;/code&gt;라는 라이브러리를 사용했다. 기본 input 태그를 이용할 수 있었지만, 모바일 환경에서 터치로 조작할 때 부자연스러운 느낌이 많아서 다른 라이브러리를 도입했다. 내부적으로 touch event까지 받아서 사용하더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;&amp;lt;RangeInputForm&amp;gt;
  &amp;lt;StyledSlider
    min={min}
    max={max}
    value={value}
    onChange={(v) =&amp;gt; setValue(v as number)}
    step={step}
    railStyle={RcSliderRailStyle}
    trackStyle={RcSliderTrackStyle}
    handleStyle={RcSliderHandleStyle}
  /&amp;gt;
  &amp;lt;Selector percent={percent}&amp;gt;
    &amp;lt;WalkingBanki /&amp;gt;
  &amp;lt;/Selector&amp;gt;
  &amp;lt;ProgressBar percent={percent} /&amp;gt;
  &amp;lt;Track /&amp;gt;
&amp;lt;/RangeInputForm&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하. 삽질을 많이했다. 결국 라이브러리로 가져온 슬라이더의 스타일을 모두 가리고, 눈에 보이는 컴포넌트를 따로 만들어 같이 움직이도록 했다. Selector는 뱅키 버튼, ProgressBar는 노란색 진행 바, Track은 회색 바.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;const percent = ((value - min) * 100) / (max - min);

const Selector = styled.div&amp;lt;{ percent: number }&amp;gt;`
  position: absolute;
  top: -16px;
  height: 40px;
  width: 44px;
  z-index: 3;
  ${({ percent }) =&amp;gt; {
    return percent &amp;gt; 0
      ? css`
          left: calc(${percent}% - (0.44 * ${percent}px));
        `
      : css`
          left: 0px;
        `;
  }};
`;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;position을 absolute로 두고, left 속성을 직접 주고 이동시켰다. 그냥 퍼센트로 하면 안되는게, 저 요소의 왼쪽 끝을 기준으로 이동하기 때문에 100%일때 삐져나오는 경우가 있었음. 그래서 퍼센트와 너비에 비례해서 조금 빼줘야했다.&lt;br /&gt;&lt;br /&gt;근데 또 ProgressBar는 Selector와 똑같이 하면 뱅키 왼쪽 옆구리가 비어서 22px(뱅키 절반)만큼 더해줘야했음. 에라이.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 사인 전송하기 (Presigned Url)&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m9L1x/btrNY1XQfN5/ob8O3qdbzjgqVrMVVbR5kk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m9L1x/btrNY1XQfN5/ob8O3qdbzjgqVrMVVbR5kk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m9L1x/btrNY1XQfN5/ob8O3qdbzjgqVrMVVbR5kk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/m9L1x/btrNY1XQfN5/ob8O3qdbzjgqVrMVVbR5kk/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;772&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사인을 하고 서버에 전송한다. &lt;code&gt;react-signature-canvas&lt;/code&gt; 라이브러리를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;function Signature({ setDisabledNext, setSign }: SignatureProps) {
  const canvasRef = useRef&amp;lt;any&amp;gt;(null);

  const onEndSign = () =&amp;gt; {
    setDisabledNext(false);
    if (canvasRef.current) {
      const signImage = canvasRef.current
        .getTrimmedCanvas()
        .toDataURL('image/png');
      setSign(signImage);
    }
  };

  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;CanvasContainer&amp;gt;
        &amp;lt;SignatureCanvas
          penColor={theme.palette.greyScale.black}
          canvasProps={{ className: 'sigCanvas' }}
          ref={canvasRef}
          onEnd={onEndSign}
          minWidth={1.5}
          maxWidth={3.5}
        /&amp;gt;
      &amp;lt;/CanvasContainer&amp;gt;
      &amp;lt;p&amp;gt;이곳에 사인을 하면 계약이 진행돼요&amp;lt;/p&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
}

export default Signature;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;onEnd&lt;/code&gt; props에서 사인이 끝났을 때 (클릭/터치가 끝났을때) 실행할 함수를 지정해줄 수 있다. png 이미지를 dataUrl 형태로 바꿔 state에 저장해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;기존에 s3 업로드를 구현했을 땐 클라이언트에서 서버로 이미지를 보내고, 서버에서 s3로 업로드 한 다음에 반환받은 링크를 다시 클라이언트로 보내주는 형식이었다. 리소스 낭비가 심하고 서버에 부담이 심하다.&lt;br /&gt;&lt;br /&gt;presigned URL은 말 그대로 이미 서명된 주소를 사용하는 것이다. 서버에서 s3 버킷에 업로드할 수 있는 주소를 미리 발급받고 클라이언트로 보내면, 클라이언트에서는 그 주소로 업로드 요청을 보낸다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  // 렌더링하자마자 presignedUrl 가져오기
  useEffect(() =&amp;gt; {
    const getPresignedUrl = async () =&amp;gt; {
      try {
        const response = await axiosPrivate.get('/s3/url');
        dispatch(setFileName(response.data.imageName));
        setPreSignedUrl(response.data);
      } catch (err) {
        console.error(err);
      }
    };
    getPresignedUrl();
  }, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step5 페이지가 렌더링 되면 바로 서버로 url을 달라고 요청한다.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;  // 다음으로 버튼 클릭
  const onClickNextButton = () =&amp;gt; {
    // s3 업로드 로직
    const uploadS3 = async (sign: any) =&amp;gt; {
      const file = convertDataURLtoFile(sign, preSignedUrl.imageName);
      let formData = new FormData();
      formData.append('file', file);

      const response = await axios.put(preSignedUrl.preSignedUrl, file, {
        headers: { 'Content-Type': 'image/png' },
      });
    };
    uploadS3(sign);
    mutatePostChallenge(createChallengePayload);
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 버튼을 클릭하면 발급받은 preSignedUrl로 사인 이미지를 전송하고, 리덕스 스토어에 쌓아둔 돈길 계약 관련 입력 정보들을 서버로 보내 계약을 완료한다. &lt;a href=&quot;https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f/38935990&quot; target=&quot;_self&quot;&gt;&lt;span&gt;이미지를 파일로 변환&lt;/span&gt;&lt;/a&gt;하고 폼데이터 형식으로 또 바꿔주는 과정이 필요하다. 이 과정에서 삽질을 어마무시하게 했다.&lt;br /&gt;&lt;br /&gt;처음에 계속 4xx 에러가 났다. aws 버킷 설정에서 cors 관련 설정을 풀어줬어야 하는데, put 메소드가 설정에 등록이 되어 있지 않아서 생긴 문제였음. 추가해주니 제대로 업로드되었다.&lt;br /&gt;&lt;br /&gt;굉장히 오래걸렸던 과정인데 글로 쓰고보니 이렇게 짧다.. 현타오네.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>  긴호흡/뱅키즈</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/70</guid>
      <comments>https://9yujin.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 7 Oct 2022 02:45:16 +0900</pubDate>
    </item>
    <item>
      <title>[React] 다시 만드는 todo list (투두메이트 클론) (1)</title>
      <link>https://9yujin.tistory.com/69</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-06 오후 6.54.26.png&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wfc11/btrNXUY9Bn4/L7FOKW4E4bvo2WujQVYaFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wfc11/btrNXUY9Bn4/L7FOKW4E4bvo2WujQVYaFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wfc11/btrNXUY9Bn4/L7FOKW4E4bvo2WujQVYaFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwfc11%2FbtrNXUY9Bn4%2FL7FOKW4E4bvo2WujQVYaFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1307&quot; height=&quot;972&quot; data-filename=&quot;스크린샷 2022-10-06 오후 6.54.26.png&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;972&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;해서 GDSC 스터디 기간 동안 투두메이트 클론코딩 프로젝트를 시작해보기로.&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-06 오후 6.58.42.png&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpddIA/btrNX8ixwaF/kZkkd6oIbAilUx5rC14ik0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpddIA/btrNX8ixwaF/kZkkd6oIbAilUx5rC14ik0/img.png&quot; data-alt=&quot;좌 : 클론 / 우 : 투두메이트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpddIA/btrNX8ixwaF/kZkkd6oIbAilUx5rC14ik0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpddIA%2FbtrNX8ixwaF%2FkZkkd6oIbAilUx5rC14ik0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;832&quot; height=&quot;639&quot; data-filename=&quot;스크린샷 2022-10-06 오후 6.58.42.png&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;639&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;클론코딩인 만큼 최대한 똑같이 스타일을 넣으려 노력했다. 갖고 있는 산세리프 글꼴을 몇개 넣어봤는데, 프리텐다드였다. 좋아하는 폰트야. 그 외에 이미지나 아이콘 등은 개발자도구 네트워크탭에서 가져오거나 피그마에서 직접 만들어 svg로 내보내 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;br /&gt;&lt;br /&gt;하지만 &lt;code&gt;- 카테고리별 투두- -카테고리별 투두-&lt;/code&gt; 이렇게 분리해놓으면 나중에 드래그앤드랍을 구현할 때 다른 카테고리로 이동이 안될 것 같았다. 통으로 배열로 사용해야하나 싶었지만... 시간이 없기 때문에 일단은 떠오르는 대로 가장 직관적인 구조를 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const Feed = () =&amp;gt; {
  const categories = useRecoilValue(categoryState);

  return (
    &amp;lt;Wrapper&amp;gt;
      &amp;lt;div&amp;gt;Feed&amp;lt;/div&amp;gt;
      &amp;lt;List&amp;gt;
        {categories.map((category) =&amp;gt; (
          &amp;lt;FeedItemList category={category} key={category.label} /&amp;gt;
        ))}
      &amp;lt;/List&amp;gt;
    &amp;lt;/Wrapper&amp;gt;
  );
};

export default Feed;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리의 배열을 갖고 와서 각 카테고리마다의 투두리스트를 &lt;code&gt;map&lt;/code&gt;으로 보여주도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const FeedItemList = ({ category }: { category: ICategory }) =&amp;gt; {
  const items = useRecoilValue(todoSelector(category.label));
  const [open, setOpen] = useState&amp;lt;boolean&amp;gt;(false);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;CategoryButton category={category} setOpen={setOpen} /&amp;gt;
      {items.map((item) =&amp;gt; (
        &amp;lt;TodoItem item={item} key={item.id} /&amp;gt;
      ))}
      {open &amp;amp;&amp;amp; &amp;lt;InputForm category={category} setOpen={setOpen} /&amp;gt;}
    &amp;lt;/&amp;gt;
  );
};

export default FeedItemList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 카테고리별 피드 컴포넌트 안에서 카테고리 안에 속하는 투두 목록들을 가지고 온다. recoil의 selector를 처음 써보았다.&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;recoil selector&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;export const todoSelector = selectorFamily&amp;lt;ITodoItem[], string&amp;gt;({
  key: 'todoSelector',
  get:
    (categoryName: string) =&amp;gt;
    ({ get }) =&amp;gt;
      get(todoState).filter((todo) =&amp;gt; todo.category.label === categoryName),
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;selector는 순수함수이다. get함수에서는 &lt;code&gt;RecoilValueReadOnly&lt;/code&gt; 객체를 반환한다고 함. 함수를 하나 더 감싸서 인자를 넘겨 줄 수 있다. 카테고리 이름을 받아와서 해당 카테고리에 해당하는 투두만 &lt;code&gt;filter&lt;/code&gt;로 가져올 수 있도록 했다.&lt;/p&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로 만들었던 투두리스트를 리액트를 이용해 만들어보는 과제였다. 투두메이트에서 투두를 체크하는 부분, 그러니까 Feed 부분만 만들어보았다. 몇시간 내에 내야해서 날림으로 짠 코드라 대대적인 리팩토링이 필요하다. 특히 상태관리하는 구조를 처음부터 다시 생각해야할 것 같다.&lt;/p&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Oct-06-2022 19-09-13.gif&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVz9DC/btrNZNYKAZX/8w9AeqFjUDRDYga6fZ1hLK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVz9DC/btrNZNYKAZX/8w9AeqFjUDRDYga6fZ1hLK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVz9DC/btrNZNYKAZX/8w9AeqFjUDRDYga6fZ1hLK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cVz9DC/btrNZNYKAZX/8w9AeqFjUDRDYga6fZ1hLK/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;408&quot; height=&quot;278&quot; data-filename=&quot;Oct-06-2022 19-09-13.gif&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Oct-06-2022 19-10-02.gif&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tePqL/btrNZGZG0BV/bJYknjg5Xoy1FwuP0Tbu71/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tePqL/btrNZGZG0BV/bJYknjg5Xoy1FwuP0Tbu71/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tePqL/btrNZGZG0BV/bJYknjg5Xoy1FwuP0Tbu71/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/tePqL/btrNZGZG0BV/bJYknjg5Xoy1FwuP0Tbu71/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;408&quot; height=&quot;278&quot; data-filename=&quot;Oct-06-2022 19-10-02.gif&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;278&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;투두 삭제하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Oct-06-2022 19-12-08.gif&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/codkCv/btrNXJwPVif/M5BScJLLcGR8pFlZA9akfk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/codkCv/btrNXJwPVif/M5BScJLLcGR8pFlZA9akfk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/codkCv/btrNXJwPVif/M5BScJLLcGR8pFlZA9akfk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/codkCv/btrNXJwPVif/M5BScJLLcGR8pFlZA9akfk/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;628&quot; data-filename=&quot;Oct-06-2022 19-12-08.gif&quot; data-origin-width=&quot;798&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;뱅키즈에서 썼던 &lt;a href=&quot;https://github.com/stipsan/react-spring-bottom-sheet&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-spring-bottom-sheet&lt;/a&gt; 라이브러리를 사용했다. 바텀시트 컴포넌트가 매 투두아이템 컴포넌트에 들어가는데, 투두아이템 컴포넌트가 삭제되면서 바텀시트가 한번에 뚝 사라지는 현상이 있었음. 바텀시트를 더 상위에 전역으로 두고 사용하면 될 것 같은데, 그럼 상태관리 구조를 뜯어고쳐야할 것 같아서.. 일단 반창고만 붙여놨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;  const handleDeleteTodo = () =&amp;gt; {
    onDismiss();
    setTimeout(() =&amp;gt; onDeleteTodo(), 300);
  };&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커리큘럼마다 의도하는 내용이 있고 각 과제에서 사용하고자 하는 기술들이 있지만 구현에 급급해서 하던대로 익숙한 스택으로 코드를 쌌다. '처음엔 state와 props만을 이용해서 관리하다가 다른 라이브러리를 이용해 상태관리하는 과정'을 기록하고 멤버들과도 공유하는게 원래 목표였는데, 아쉽다.&lt;/p&gt;
&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://9yujin.github.io/todomate-clone-deploy/&quot;&gt;배포 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://9yujin.tistory.com/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Week2] 리액트 시작하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category> &amp;zwj;  짧은호흡/React</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/69</guid>
      <comments>https://9yujin.tistory.com/69#entry69comment</comments>
      <pubDate>Thu, 6 Oct 2022 22:49:54 +0900</pubDate>
    </item>
    <item>
      <title>[Week2] 리액트 시작하기</title>
      <link>https://9yujin.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, FE 코어멤버 한규진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 GDSC에서 진행하는 첫번째 대면 스터디입니다!! 리액트를 처음 시작해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;리액트 시작하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 한번도 안써보신 분은 없는걸로 알고 있으니, 굳이 자세히 적어놓지 않아도 되겠죠. 자세한건 다른 블로그나 책에 나와있으니 보시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게는 이렇습니다. Mac 기준으로 작성합니다.&lt;/p&gt;
&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. Homebrew 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;/usr/bin/ruby -e &quot;$(curl -fsSL &amp;lt;https://raw.githubusercontent.com/Homebrew/install/master/install&amp;gt;)&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;b&gt;2. node, npm 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;brew update
brew install node
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node를 설치하면 npm도 함께 설치됩니다. 나중에 다시 자세히 살펴볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;node -v
npm -v
&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;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install yarn --ignore-dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 npm보다 yarn을 주로 사용합니다. npm과 비슷한 패키지 매니저입니다. &lt;a href=&quot;https://ehddnjs8989.medium.com/npm-vs-yarn-3a611c89d291&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;&lt;b&gt;3. 프로젝트 생성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_1.52.52.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G8hKR/btrNuKQ4yOF/bEO3ky8Vt7aQVBC4UKlvKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G8hKR/btrNuKQ4yOF/bEO3ky8Vt7aQVBC4UKlvKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G8hKR/btrNuKQ4yOF/bEO3ky8Vt7aQVBC4UKlvKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG8hKR%2FbtrNuKQ4yOF%2FbEO3ky8Vt7aQVBC4UKlvKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;748&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_1.52.52.png&quot; data-origin-width=&quot;958&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;code&gt;yarn create react-app [projectName]&lt;/code&gt; 으로 리액트 프로젝트를 생성할 수 있습니다.&lt;br /&gt;개발 환경에서 실행해볼땐 &lt;code&gt;yarn start&lt;/code&gt; (또는 &lt;code&gt;npm run start&lt;/code&gt;)로 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;week2
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   └── reportWebVitals.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조의 파일들이 생성됩니다. 무언가 좀 많습니다. 원래 html css 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;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_2.04.31.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE74T6/btrNuC6BllI/rQfv8kikfdrnBeDZNbEPU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE74T6/btrNuC6BllI/rQfv8kikfdrnBeDZNbEPU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE74T6/btrNuC6BllI/rQfv8kikfdrnBeDZNbEPU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE74T6%2FbtrNuC6BllI%2FrQfv8kikfdrnBeDZNbEPU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;748&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_2.04.31.png&quot; data-origin-width=&quot;958&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;code&gt;yarn build&lt;/code&gt; 명령어로 프로젝트 빌드를 해보았습니다. Build 라는 디렉토리가 새로 생긴걸 확인할 수 있습니다. 해당 디렉토리 내에는 css, js, html 등 정적인 파일이 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹으로 배포를 할 때는 빌드된 파일들을 웹서버를 이용해 배포를 하면 되겠죠?? 간단하게 &lt;a href=&quot;https://github.com/9yujin/react-todo-15th/tree/gh-pages&quot;&gt;이렇게&lt;/a&gt;도 할 수 있습니다. 빌드된 파일로 깃허브 페이지를 통해 배포를 한 레포지토리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_2.28.01.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K46ni/btrNvi0NRAF/KRLBBwkGnGFKEahNRz55QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K46ni/btrNvi0NRAF/KRLBBwkGnGFKEahNRz55QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K46ni/btrNvi0NRAF/KRLBBwkGnGFKEahNRz55QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK46ni%2FbtrNvi0NRAF%2FKRLBBwkGnGFKEahNRz55QK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;748&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_2.28.01.png&quot; data-origin-width=&quot;958&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;만들어진 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;그런건 누가할까요?? &lt;b&gt;webpack&lt;/b&gt;과 &lt;b&gt;babel&lt;/b&gt;이라는 어디서 많이 들어본 키워드가 여기서 등장합니다.&lt;br /&gt;&lt;a href=&quot;https://ljs0705.medium.com/spa-single-page-app-%EC%97%90%EC%84%9C-webpack%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-ce7d3f82fe9&quot;&gt;SPA(single page app)에서 webpack을 사용하는 이유&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;webpack은 자바스크립트 모듈화와 번들링, 핫 로딩 등을 위해 사용하는 라이브러리입니다. 리액트에서 주로 사용하는 ES6 등 최신 문법을 모든 브라우저가 이해할 수 있는 문법으로 변환해주는 babel도 같이 사용하게 됩니다.&lt;/p&gt;
&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://webroadcast.tistory.com/47&quot;&gt;저런걸&lt;/a&gt; 본적이 없습니다. 지금 당장은 알 필요가 없습니다. 왜냐면 &lt;code&gt;create react-app&lt;/code&gt;으로 프로젝트를 생성할 때 기본적으로 세팅이 되어 있거든요. 정 궁금하시면 &lt;code&gt;yarn eject&lt;/code&gt; 명령어를 입력해보세요. (한번 꺼내면 다시 되돌릴 수 없다는 겁을 잔뜩 주며) 내장되어 있는 &lt;code&gt;webpack.config&lt;/code&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_3.05.56.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;748&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrI1g/btrNwnUnBqM/1Taa19dmmmXPTqn8Fb7Iwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrI1g/btrNwnUnBqM/1Taa19dmmmXPTqn8Fb7Iwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrI1g/btrNwnUnBqM/1Taa19dmmmXPTqn8Fb7Iwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqrI1g%2FbtrNwnUnBqM%2F1Taa19dmmmXPTqn8Fb7Iwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;958&quot; height=&quot;748&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_3.05.56.png&quot; data-origin-width=&quot;958&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;code&gt;config&lt;/code&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;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_3.09.58.png&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eIpN6m/btrNxa1kQgk/boROjMerAViuZ3wXlHVGqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eIpN6m/btrNxa1kQgk/boROjMerAViuZ3wXlHVGqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eIpN6m/btrNxa1kQgk/boROjMerAViuZ3wXlHVGqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeIpN6m%2FbtrNxa1kQgk%2FboROjMerAViuZ3wXlHVGqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;488&quot; data-filename=&quot;%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA_2022-09-22_%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE_3.09.58.png&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;package.json&lt;/code&gt; 의 내용도 꽤 많이 바뀌었습니다. 바벨과 웹팩 관련된 패키지들이 여러 설치되어 있습니다. 저 무시무시한 과정을 &lt;code&gt;create react-app&lt;/code&gt; 명령어를 통해 자동으로 세팅할 수 있습니다. 고마워!! 하고 개발 시작하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;과제&lt;/b&gt;&lt;/h2&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;첫번째 과제는 무척 간단합니다. 먼저 Figma를 통해서 여러분이 표현하고자 하는 것을 시각적으로 만들어봅니다. 그리고 이를 React를 이용해 구현해봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 하셨던 분이라면, 조금 더 효율적인 패턴에 대해서 고민을 할 수 있는 시간이 될 것이고, 리액트가 처음이라면 과제를 하며 리액트의 장점에 대해 느끼실 수 있을거에요.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. WIL&lt;/b&gt;&lt;/h4&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;blockquote data-ke-style=&quot;style3&quot;&gt;1. 리액트를 처음 시작할때 node와 npm이라는걸 설치해보았습니다. &lt;b&gt;node&lt;/b&gt;와 &lt;b&gt;npm&lt;/b&gt;은 무엇이고 왜 리액트에서 쓰이는걸까요?&lt;br /&gt;2. &lt;b&gt;컴포넌트, props, state&lt;/b&gt;는 무엇일까요?&lt;br /&gt;3. 바닐라JS와 리액트에서 &lt;b&gt;DOM&lt;/b&gt;을 다루는 방식에 대해서 어떤 점이 다를까요??&lt;br /&gt;4. 이 외에도 저희와 함께 기록하고 공유하고 싶은 내용이 있다면 얼마든지 좋아요.&lt;/blockquote&gt;</description>
      <category>  긴호흡/GDSC</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/68</guid>
      <comments>https://9yujin.tistory.com/68#entry68comment</comments>
      <pubDate>Sat, 1 Oct 2022 01:28:44 +0900</pubDate>
    </item>
    <item>
      <title>[운영체제] Ch1. Introduction</title>
      <link>https://9yujin.tistory.com/67</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;What does an operating system do?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하드웨어의 추상화된 레이어를 제공.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쌩 H/W를 신경쓰지 않고 프로그래밍할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소프트웨어적인 리소스를 관리해줌.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 프로그램이 동시에 실행될 수 있도록 &lt;br /&gt;( = CPU, 메모리를 나누어 쓸수 있도록 OS에서 관리해준다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5n2xZ/btrNoaINNk9/ZXfW7oUkrPTkgpm1IwS5k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5n2xZ/btrNoaINNk9/ZXfW7oUkrPTkgpm1IwS5k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5n2xZ/btrNoaINNk9/ZXfW7oUkrPTkgpm1IwS5k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5n2xZ%2FbtrNoaINNk9%2FZXfW7oUkrPTkgpm1IwS5k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;299&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;427&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;b&gt;CPU PipeLining&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A susperscalar CPU : 동시에 여러 instruction을 수행할 수 있는 구조&lt;/li&gt;
&lt;li&gt;Kernel mode : CPU에서 제공하는 모든 instruction 수행 가능. 모든 H/W feature (Device)에 access 가능.&lt;/li&gt;
&lt;li&gt;User mode : 모든 instruction이 아닌 제한된 명령어만 사용 가능. Device access에도 제한.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;I/O Devices&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;I/O Access 방식에는 busy waiting, interrupt, DMA 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 interrupt 방식에 대해서.&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;1640&quot; data-origin-height=&quot;1037&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcbpVn/btrPg0Lbmqu/K5B8HcRRESnh5cQYiBJwV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcbpVn/btrPg0Lbmqu/K5B8HcRRESnh5cQYiBJwV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcbpVn/btrPg0Lbmqu/K5B8HcRRESnh5cQYiBJwV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcbpVn%2FbtrPg0Lbmqu%2FK5B8HcRRESnh5cQYiBJwV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;379&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1037&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;외부에서 신호가 CPU로 들어오면 (..중략) 수행하던 명령어를 중단하고 Kernal mode로 전환한다. 특정 주소지로 뛰어서 inst 실행.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS에서 Device controller로 명령.&lt;/li&gt;
&lt;li&gt;Device가 작업을 완료하면, Interrupt controller(IC)에게 종료 시그널을 전송 (interrupt 시켜주세용).&lt;/li&gt;
&lt;li&gt;IC에서 CPU에게 Device가 작업 완료했음을 알림 (물리적 신호)&lt;/li&gt;
&lt;li&gt;IC를 통해서 여러개의 Disk controller가 연결되어 있을 수 있다. 이땐 우선순위에 의해서 처리됨.&lt;/li&gt;
&lt;li&gt;step3에서 interrupt signal을 전송함과 동시에, interrupt를 발생시킨 device의 번호를 같이 보냄.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실행중인 명령어들이 쌓여있는 메모리의 모습&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;1301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkeC6U/btrPjkWsXiq/wdpOIU9h0Hdcyvp4DY4MSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkeC6U/btrPjkWsXiq/wdpOIU9h0Hdcyvp4DY4MSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkeC6U/btrPjkWsXiq/wdpOIU9h0Hdcyvp4DY4MSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkeC6U%2FbtrPjkWsXiq%2FwdpOIU9h0Hdcyvp4DY4MSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;634&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명령어 실행 중에 interrupt가 발생되면 Kernel 모드로 전환된다. Interrupt vector를 통해서 Dispatch Handler가 있는 주소지로 점프.&lt;/li&gt;
&lt;li&gt;User 모드에서는 수행 불가능한 명령어가 있기 때문에 OS는 Kernel 모드에서만 수행된다.&lt;/li&gt;
&lt;li&gt;memory에서 interrupt를 수행하는 코드 (interrupt handler Routine)을 패칭해서 수행한다.&lt;/li&gt;
&lt;li&gt;이때 아까 4번 시그널로 넘겨줬던 번호를 가지고 특정 주소지를 찾아 불러온다 ( = interrupt vector)&lt;/li&gt;
&lt;li&gt;그 때, 기존에 사용되던 PC와 PSW? 레지스터 정보들을 따로 저장함. Handler의 작업이 완료되면 다시 돌아가서 재개할 수 있도록.&lt;/li&gt;
&lt;li&gt;return해서 next instruction이 진행된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Fault : 0으로 나누기 등, instruction이 수행 불가능한 경우일때 - 수행하던 명령어를 중단하고, Kernel 모드로 전환한다. 특정 주소지 (PC에 있는 값)으로 뛰어서 inst 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Protection&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Protected Instructions&lt;/b&gt; : 커널 모드에서만 사용 가능한 Instruction&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O Device에 직접적으로 접근하는 명령어&lt;/li&gt;
&lt;li&gt;메모리 관리 명령어&lt;/li&gt;
&lt;li&gt;Protected Processor Register(ex.PSW)에 설정되는 Mode Bit을 설정하는 명령어&lt;/li&gt;
&lt;li&gt;halt 명령어 (CPU를 멈추게 하는 것 : 이런걸 유저모드에서 쓸 수 있으면 위험)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Crossing Protection Boundaries&lt;/b&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;Interrupt (H/W Interruption)&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;Device Controller가 Interrupt Signal을 발생시키면 OS가 커널모드로 전환 (하드웨어에서 넘어오는거)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Trap (S/W Interruption)&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;유저 프로그램이 Protected Instruction을 실행시키기 위해 &lt;b&gt;System Call&lt;/b&gt;을 이용한다.&lt;/li&gt;
&lt;li&gt;Kernel Handler ventor에 저장된 주소로 이동시킴 (&lt;b&gt;커널모드로 전환&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;System Call이 실행되는 순간, caller의 상태 (레지스터, mode bit 등)은 백업됨 (fault나 interrupt 수행되는것과 비슷)&lt;/li&gt;
&lt;li&gt;시스템 콜 함수의 파라미터로 값들을 넘길 수 있음. OS는 그런 값들을 체크&lt;/li&gt;
&lt;li&gt;작업이 끝나면 다시 user mode로 넘긴다. (iret과 같은 명령어를 통해)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;System Calls&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS의 기능을 불러내는 인터페이스이다. 함수처럼 사용.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PSW (Program State Word) : 이 비트가 몇이면 커널모드, 몇이면 유저모드..&lt;/li&gt;
&lt;/ul&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;A Kernel Crossing Illustrated&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buthKc/btrNsEnI5xe/CxpKVUSILCoXaSCBI5YAOk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buthKc/btrNsEnI5xe/CxpKVUSILCoXaSCBI5YAOk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buthKc/btrNsEnI5xe/CxpKVUSILCoXaSCBI5YAOk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuthKc%2FbtrNsEnI5xe%2FCxpKVUSILCoXaSCBI5YAOk%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;718&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&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;ex&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;int read(int fd, char *buf, int size) {
  // fd : File Descriptor (포인터 같은거임)
  // buf : 주솟값
  // size : read할 데이터의 크기

    move fd, buf, size to R₁, R₂, R₃
      // 인자로 받은걸 레지스터에 저장해둔다

    move READ to R₀
      // (#define READ 3 같은거임) 특정 상수값을 저장

    int $0x80
      // Trap 명령어를 수행

    move result to R_result
      // 커널모드에서 작업 결과(result)를 레지스터에 저장한 후, read() 종료
      // result : 읽어들인 데이터의 Byte 수
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;781&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caE7Lc/btrNqM7KQZM/UrvBHKY49afJGw08Jr5bLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caE7Lc/btrNqM7KQZM/UrvBHKY49afJGw08Jr5bLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caE7Lc/btrNqM7KQZM/UrvBHKY49afJGw08Jr5bLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaE7Lc%2FbtrNqM7KQZM%2FUrvBHKY49afJGw08Jr5bLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;781&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;781&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;b&gt;OS Structure&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;os를 어떻게 구성하는게 좋은가..&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;Monolithic Kernel&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/55226431/193077650-a21c12cf-a42a-42ec-8e66-a73ca0ce687a.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS가 한 덩어리인것&lt;/li&gt;
&lt;li&gt;모든게 다 커널모드&lt;/li&gt;
&lt;li&gt;최상위에서 특정 System Call을 호출하면, 해당 System은 필요한 다른 Function을 호출해 나간다/&lt;/li&gt;
&lt;li&gt;모듈간 Call 하는 시간이 짧아, 성능이 좋음&lt;/li&gt;
&lt;li&gt;대신 프로그램의 크기가 큰 만큼, 유지보수가 안좋다&lt;/li&gt;
&lt;li&gt;일부에 문제가 생기면 전체가 셧다운 되는 가능성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MicroKernel&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/55226431/193077773-18869beb-bf2e-420a-b1f8-7b94f34e257b.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마이크로한 애들만 커널에서 돌리자 : 커널모드용 기능을 최소화한다. 나머지는 user-level process&lt;/li&gt;
&lt;li&gt;시스템 모듈이 서로 독립되어 있어서 신뢰성이 있음(?)&lt;/li&gt;
&lt;li&gt;문제가 생기면 거기만 고치고 어쩌고 할수 있음. 유지보수성이 높아진다&lt;/li&gt;
&lt;li&gt;하지만 user-kernel crossing을 해야해서 성능은 비교적 안좋음.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  CS/운영체제</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/67</guid>
      <comments>https://9yujin.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 30 Sep 2022 00:51:48 +0900</pubDate>
    </item>
    <item>
      <title>[Week1] DOM, 브라우저 렌더링</title>
      <link>https://9yujin.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;스터디 첫주차는 리액트가 아닌 바닐라 자바스크립트를 이용해 간단한 투두리스트를 만드는 과제였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;배포되어 있는 제 바닐라 투두 페이지에 들어갔을 때 개발자도구의 네트워크 탭입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-09-27 오후 4.55.37.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXm2HP/btrNcn8PYrj/l7DDIklLEMPBZDb1vqng7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXm2HP/btrNcn8PYrj/l7DDIklLEMPBZDb1vqng7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXm2HP/btrNcn8PYrj/l7DDIklLEMPBZDb1vqng7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXm2HP%2FbtrNcn8PYrj%2Fl7DDIklLEMPBZDb1vqng7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1440&quot; data-filename=&quot;스크린샷 2022-09-27 오후 4.55.37.png&quot; data-origin-width=&quot;1640&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;IP..DNS.. 어쩌구 저쩌구 해서 웹사이트를 보여주기 위한 파일을 받아옵니다. 브라우저는 이런 자료들을 요청하기 위해서 HTTP (&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;Hyper Text&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt; Transfer Protocol)라는 프로토콜을 사용합니다.&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&gt;어머어머. HTML은 &lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;Hyper Text&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt; Markup Language의 약자랍니다. 그렇다면 브라우저는 HTTP를 통해 서버에서 html과 같은 것들을 전송받아 우리에게 보여주는거군요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;브라우저 렌더링&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;html 문서는 메모장으로도 만들 수 있는, 그냥 문서에 불과합니다. 어떻게 위와같이 이쁜 모습으로 화면에 렌더링되는걸까요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Ga3J/btrNa2qOEqX/5axGRGA2z9Ms2U0OtwxlR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Ga3J/btrNa2qOEqX/5axGRGA2z9Ms2U0OtwxlR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Ga3J/btrNa2qOEqX/5axGRGA2z9Ms2U0OtwxlR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Ga3J%2FbtrNa2qOEqX%2F5axGRGA2z9Ms2U0OtwxlR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;229&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저의 렌더링은 이런 과정을 통해 이루어집니다. 렌더링엔진이 HTML파일의 각 태그 (&lt;span&gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt; 같은 태그 말하는거에요)를 노드로 만들고, 각 노드들을 트리 구조로 연결합니다. 송프언 때 파싱트리 주구장창 그렸던 기억이 나는군요. 그걸 &lt;b&gt;Dom Tree&lt;/b&gt;라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N7kPy/btrNbcAgSN8/06JI2Z4IDtmfsd23ANSYc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N7kPy/btrNbcAgSN8/06JI2Z4IDtmfsd23ANSYc1/img.png&quot; data-alt=&quot;https://web.dev/critical-rendering-path-render-tree-construction/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N7kPy/btrNbcAgSN8/06JI2Z4IDtmfsd23ANSYc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN7kPy%2FbtrNbcAgSN8%2F06JI2Z4IDtmfsd23ANSYc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1150&quot; height=&quot;537&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://web.dev/critical-rendering-path-render-tree-construction/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;CSS 파일도 비슷한 과정을 통해 CSSOM이라는 트리로 만들어지고, 두 트리를 합치면 렌더링을 위한 트리가 만들어지는거죠!! 마지막으로 렌더트리를 기반으로 모든 객체의 정확한 위치와 크기를 계산&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;(flow)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;하고, 계산한대로 실제로 화면에 그리는 과정&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;(paint)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;으로 끝나게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;만약 투두리스트에 새 투두를 추가하거나 토글을 하게 되면 (DOM에 변화를 주면) 렌더트리도 다시 만들어야 하고, 계산도 다시, 페인팅도 다시 해야합니다. 이러한 &lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;reflow&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;와 &lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;repaint&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 과정은 꽤 많은 리소스를 사용한다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;만약에 DOM에서 많은 부분이 바뀌게 되면, reflow와 repaint도 여러번 일어나겠죠?? 이러한 연산을 줄여 웹페이지의 성능을 최적화할 수 있습니다. Reflow,repaint가 발생하는 css 속성 피하기, style 호출이 아닌 클래스를 이용해 스타일 변경하기.. 뭐 이런걸로 해볼 수 있다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;virtual DOM&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 화면이 업데이트 될때마다 트리를 다시 생성하고 그리는게 아닌, 딱 변경된 부분만 찾아서 업데이트해줄 수 있다면 어떨까요?? 가상의 DOM을 하나 두고, 그걸 수정하는겁니다. 여러 동작에 대한 연산을 끝낸 후에, 실제 DOM에 적용시켜줍니다. 다만, 업데이트된 가상의 Dom과 실제 DOM을 비교해서 &lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;변화가 필요한 곳만 새로 렌더링&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span&gt;합니다. 그렇다면 flow와 paint를 하는 횟수도 줄어들겠죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJqrG8/btrNdTFI8j0/ZryAlV9cmPCyjxBv4C9iNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJqrG8/btrNdTFI8j0/ZryAlV9cmPCyjxBv4C9iNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJqrG8/btrNdTFI8j0/ZryAlV9cmPCyjxBv4C9iNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJqrG8%2FbtrNdTFI8j0%2FZryAlV9cmPCyjxBv4C9iNk%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;600&quot; height=&quot;449&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;570&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;&lt;b&gt;&lt;span&gt;리액트&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;를 이용하면 쉽고 빠르게 적용할 수 있습니다. 다음주부터는 리액트를 이용해서 간단한 프로젝트를 해봅시다. Vitual DOM을 사용한 어플리케이션 성능 최적화 이외에 어떤 장점이 있는지 알 수 있을거에요!!&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;span&gt;참고자료&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-mark=&quot;-&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;a href=&quot;https://velopert.com/3236&quot;&gt;&lt;span&gt;왜 Virtual DOM 인가?&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot;&gt;&lt;span&gt;브라우저는 어떻게 동작하는가?&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;a href=&quot;https://velog.io/@tnehd1998/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-www.google.com%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%96%88%EC%9D%84-%EB%95%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95&quot;&gt;&lt;span&gt;주소창에 www.google.com을 입력했을 때 일어나는 과정&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  긴호흡/GDSC</category>
      <author>한규진</author>
      <guid isPermaLink="true">https://9yujin.tistory.com/66</guid>
      <comments>https://9yujin.tistory.com/66#entry66comment</comments>
      <pubDate>Wed, 28 Sep 2022 00:33:01 +0900</pubDate>
    </item>
  </channel>
</rss>