<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>도리</title>
    <link>https://blue-tang.tistory.com/</link>
    <description>5년차 / FE / 웹 서비스의 전문가가 되기 위해 공부하고 기록합니다.  
#Typescript #React
</description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 02:48:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>도리[Dori]</managingEditor>
    <image>
      <title>도리</title>
      <url>https://tistory1.daumcdn.net/tistory/4667100/attach/eb825a961f8c478fb111fa236659353b</url>
      <link>https://blue-tang.tistory.com</link>
    </image>
    <item>
      <title>FE Conf 2025 후기</title>
      <link>https://blue-tang.tistory.com/113</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmRiqi/btsP4TQuKrW/Tku6J5iVtbgnttRRVlrbm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmRiqi/btsP4TQuKrW/Tku6J5iVtbgnttRRVlrbm0/img.jpg&quot; data-alt=&quot;FE CONF 2025&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmRiqi/btsP4TQuKrW/Tku6J5iVtbgnttRRVlrbm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmRiqi%2FbtsP4TQuKrW%2FTku6J5iVtbgnttRRVlrbm0%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;2048&quot; height=&quot;1536&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FE CONF 2025&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일시&lt;/b&gt;:8월 23일 (토)&lt;br /&gt;&lt;b&gt;장소&lt;/b&gt;: 세종대학교 광개토관 B2&lt;br /&gt;&lt;b&gt;홈페이지&lt;/b&gt;: &lt;a href=&quot;https://2025.feconf.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://2025.feconf.kr/&lt;/a&gt;&lt;br /&gt;&lt;b&gt;트위터&lt;/b&gt;: &lt;a href=&quot;https://x.com/feconf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://x.com/feconf&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FE Conf 2025에 다녀왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NHN Forward 2022 이후 3년 만에 참가한 오프라인 개발자 컨퍼런스였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 프론트엔드 개발자위주의 행사라는 점, 유튜브로 꾸준히 챙겨보던 행사라는 점 때문에 더 기대가 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 매우 즐거웠고 개발에 열정 가득한 많은 사람을 만나는 게 건강한 자극이 되었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메인 프로그램&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPgXnx/btsP58e60lx/uV41HF05oE6W9m8DkKhZV0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPgXnx/btsP58e60lx/uV41HF05oE6W9m8DkKhZV0/img.jpg&quot; data-alt=&quot;B홀 광경 (시작 전 광고)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPgXnx/btsP58e60lx/uV41HF05oE6W9m8DkKhZV0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPgXnx%2FbtsP58e60lx%2FuV41HF05oE6W9m8DkKhZV0%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;563&quot; height=&quot;768&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;B홀 광경 (시작 전 광고)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 프로그램은 A, B 홀에서 병렬로 진행되었다. A는 모바일 관련 내용이 많아 보여서 나는 주로 B 홀에서 발표를 시청했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세부 내용은 유튜브에 공개될 예정이라, 여기서는 기억에 남은 발표만 간단히 정리했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스벨트를 통해 리액트 이해하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;i&gt;`다른 프레임워크를 통해 리액트를 더 깊이 이해할 수 있다`&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스벨트의 장점을 소개하고 리액트에도 적용시키는 예시들을 보여주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표 구성이 매끄럽고 실무 적용까지 고민해볼 만한 주제라 인상 깊었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;메모를 지울 결심&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 리액트 컴파일러를 소개하는 발표일 거라 예상했지만 리액트 컴파일러의 작동 원리와 실제 캐싱의 차이까지 보여주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;새로운 기술적 측면을 알 수 있었고,&amp;nbsp;&lt;/span&gt;우리 코드에도 적용할 수 있게 된다면 캐시를 잘 쓰기 위해 어떤 고민을 해야 할지 배울 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라이트닝 토크&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk5nsD/btsP5Kr0e1I/0kiv22WHMcRgJGkEbbcue0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk5nsD/btsP5Kr0e1I/0kiv22WHMcRgJGkEbbcue0/img.jpg&quot; data-alt=&quot;라이트닝 토크 일부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk5nsD/btsP5Kr0e1I/0kiv22WHMcRgJGkEbbcue0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk5nsD%2FbtsP5Kr0e1I%2F0kiv22WHMcRgJGkEbbcue0%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;535&quot; height=&quot;768&quot; data-origin-width=&quot;1024&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;C 홀은 초반엔 골드 티켓을 위한 리더토크가 있었고 14:30부터 라이트닝 토크가 진행되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 중간쯤부터 라이트닝 토크를 시청했고, 부스에도 왔다 갔다 하며 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;가볍게,&amp;nbsp;부담&amp;nbsp;없이&amp;nbsp;즐길&amp;nbsp;수&amp;nbsp;있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;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;AI 시대에 FE 개발자로 살아남기&lt;/li&gt;
&lt;li&gt;오픈 소스에 기여하기&lt;/li&gt;
&lt;li&gt;최신 CSS 동향&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 집, 강남언니, AWS, Moyo 네 곳의 부스가 C홀 입구 쪽에서 진행됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부스별로 이벤트도 하는데 오픈 초반에 사람이 많았고 이후에는 사은품이 많이 소진되어 사람이 줄었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부스별 커피챗도 진행해서 관심 기업이 있거나 다른 회사의 개발팀은 어떤 걸 하고 있는지 궁금하면 가서 편하게 얘기해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 이직을 준비하고 있진 않지만, 어떤 개발 문화를 가진 팀들인지 궁금해 방문해 보았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자기 기술에 철학이 있는 사람&lt;/li&gt;
&lt;li&gt;기능을 만드는 것뿐만 아니라 제품을 성장시키고, 안정적으로 운영할 수 있는 사람&lt;/li&gt;
&lt;li&gt;좋은 제품을 만들기 위해 적극적으로 고민하고 눈치 보지 않고 의견을 낼 수 있는 사람&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 역량은 당연하고 그 너머로 제품을 성장시킬 수 있는 개발자를 원하는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 그런 부분에 대해 더 고민하고 준비해야겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워킹&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당근 모임으로 네트워킹을 신청하여 다른 개발자분들과 네트워킹을 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 4명이 모였고 약 30분 정도 대화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내향형인 나에게 이런 자리가 어색하긴 하지만 요즘 다른 분들의 개발 이야기나 이직 고민에 대해 이야기 나눌 수 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한&amp;nbsp;연차의&amp;nbsp;개발자들이&amp;nbsp;있었다면&amp;nbsp;더&amp;nbsp;공감할&amp;nbsp;수&amp;nbsp;있었을&amp;nbsp;텐데,&amp;nbsp;그래도&amp;nbsp;충분히&amp;nbsp;유익했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애프터파티&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X3ICR/btsP3zyAzaN/PxcbAofGn08zcqH1U2VSWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X3ICR/btsP3zyAzaN/PxcbAofGn08zcqH1U2VSWk/img.png&quot; data-alt=&quot;토스 프라이빗 애프터파티&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X3ICR/btsP3zyAzaN/PxcbAofGn08zcqH1U2VSWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX3ICR%2FbtsP3zyAzaN%2FPxcbAofGn08zcqH1U2VSWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;377&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;573&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;토스 프라이빗 애프터파티&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행사 끝나고 여운이 남았던 건지 저녁을 같이 먹으려는 사람들도 보이고 활기찼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 운 좋게도 토스 애프터파티에 당첨되어서 거기에 참여하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기서 토스 개발자 분들과 스피커분들, 그리고 애프터파티 당첨자분들과 함께 즐거운 시간을 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조별로 앉아 개발 문화 이야기, 업계 분위기 이야기 등등 다양한 이야기가 오갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이후엔 맛있는 식사와 함께 토스의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;FE 모닥불&lt;/b&gt;을 눈앞에서 볼 수 있었고, 조원들과도 랜덤주제 토론을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 조는 스타트업과 대기업, 매니저와 IC가 적절히 섞여있어서 양측의 관점을 모두 들을 수 있어서 정말 흥미로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티 중에&lt;i&gt;&lt;/i&gt;&lt;i&gt; &lt;/i&gt;&lt;span style=&quot;background-color: #f0f0f0;&quot;&gt;&lt;i&gt;토스에서는 이런 토론을 언제든지 할 수 있다 &lt;/i&gt;&lt;/span&gt;라고 하신 말이 기억에 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토스라는 회사에 대해 자부심과 함께, FE에 대한 관심과 열정이 높은 사람들이 모여 일하는 곳이라는 인상을 받았다.&lt;/p&gt;
&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;010&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/010.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/010.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멋있게 재밌게 일하는 회사들과 개발자들이 많은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 끊임없이 고민하고 소통하면서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;좋은 제품&quot;을 만들기 위해&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;노력하고 있었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;웹서비스를 개발하는 사람으로서 나에게도 굉장히 자극이 되고 에너지를 얻어가는 그런 행사였다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 날 얻은 에너지로 하반기도 열심히, 잘해야겠다. 내년에 또 가야지~&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>2025 Frontend Conf</category>
      <category>FE Conf 2025</category>
      <category>feconf 후기</category>
      <category>frontend</category>
      <category>Standard</category>
      <category>개발자 컨퍼런스</category>
      <category>네트워킹</category>
      <category>부스 후기</category>
      <category>커리어</category>
      <category>토스 애프터파티</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/113</guid>
      <comments>https://blue-tang.tistory.com/113#entry113comment</comments>
      <pubDate>Sun, 24 Aug 2025 18:23:30 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;프로그래머의 뇌&amp;gt; 정리</title>
      <link>https://blue-tang.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.yes24.com/product/goods/105911017&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.yes24.com/product/goods/105911017&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755338198004&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;프로그래머의 뇌 - 예스24&quot; data-og-description=&quot;인지과학을 활용한 개발자의 일머리 개선법 이 책은 인지과학에 기반을 둔 각종 방법론으로 개발자가 새로운 언어나 프레임워크를 빠르게 배워 생산성을 향상하도록 돕는다. 코드를 더 잘 이해&quot; data-og-host=&quot;www.yes24.com&quot; data-og-source-url=&quot;https://www.yes24.com/product/goods/105911017&quot; data-og-url=&quot;https://www.yes24.com/product/goods/105911017&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JSK6R/hyZvoA94Z0/Udo6mqD3CsU3NPRSyCGuC1/img.jpg?width=914&amp;amp;height=1200&amp;amp;face=620_204_695_284,https://scrap.kakaocdn.net/dn/AQfGz/hyZymPG1sD/VSXqwlY5Je9voooMKhYR1k/img.jpg?width=914&amp;amp;height=1200&amp;amp;face=620_204_695_284,https://scrap.kakaocdn.net/dn/oiuNI/hyZzHeKhl2/g68LrZp9skKb2NPC8UyG70/img.jpg?width=915&amp;amp;height=1200&amp;amp;face=0_0_915_1200&quot;&gt;&lt;a href=&quot;https://www.yes24.com/product/goods/105911017&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.yes24.com/product/goods/105911017&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JSK6R/hyZvoA94Z0/Udo6mqD3CsU3NPRSyCGuC1/img.jpg?width=914&amp;amp;height=1200&amp;amp;face=620_204_695_284,https://scrap.kakaocdn.net/dn/AQfGz/hyZymPG1sD/VSXqwlY5Je9voooMKhYR1k/img.jpg?width=914&amp;amp;height=1200&amp;amp;face=620_204_695_284,https://scrap.kakaocdn.net/dn/oiuNI/hyZzHeKhl2/g68LrZp9skKb2NPC8UyG70/img.jpg?width=915&amp;amp;height=1200&amp;amp;face=0_0_915_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;프로그래머의 뇌 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;인지과학을 활용한 개발자의 일머리 개선법 이 책은 인지과학에 기반을 둔 각종 방법론으로 개발자가 새로운 언어나 프레임워크를 빠르게 배워 생산성을 향상하도록 돕는다. 코드를 더 잘 이해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.yes24.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ydJWi/btsPTn6FK96/VFRF1vlw2bSxVK3uzWJkUk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ydJWi/btsPTn6FK96/VFRF1vlw2bSxVK3uzWJkUk/img.jpg&quot; data-alt=&quot;프로그래머의 뇌 - 훌륭한 프로그래머가 알아야할 인지과학의 모든 것&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ydJWi/btsPTn6FK96/VFRF1vlw2bSxVK3uzWJkUk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FydJWi%2FbtsPTn6FK96%2FVFRF1vlw2bSxVK3uzWJkUk%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;914&quot; height=&quot;1200&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프로그래머의 뇌 - 훌륭한 프로그래머가 알아야할 인지과학의 모든 것&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 네 파트로 구성되어, 개발하면서 겪는 어려움을 인지 과학의 관점에서 분류한다.&lt;br /&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;내가 짠 코드를 상대방에게 설명하거나, PR을 작성할 때&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;내가 이해한 흐름으로 큰 내용을 재구성하여 짧게 정리해보았다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인지과학적 관점의 프로그래밍&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인지과학에서 우리 뇌는 크게 세 부분으로 구분된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LTM(Long term memory) - 장기 기억 공간 (지식) - 하드 디스크&lt;/li&gt;
&lt;li&gt;STM(Short term memory) - 단기 기억 공간 (정보) - 메모리&lt;/li&gt;
&lt;li&gt;작업 기억 공간 - 지식과 정보를 통해 처리. 생각, 아이디어, 해결책 등이 만들어짐. - 프로세서&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 읽는 것은 LTM, STM, 작업 기억 공간의 복합적인 작용이다.&lt;br /&gt;코드를 읽을 때 뇌는 LTM, STM에서 정보를 인출하여 작업 기억 공간에서 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 작업 기억 공간에 들어갈 수 있는 정보의 개수는 한정되어있고, 굉장히 적다.&lt;br /&gt;그럼 어떻게 해야 코딩처럼 많은 정보를 처리하는 일을 잘 할 수 있을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LTM 더 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &quot;청크&quot;를 활용하는 것이다.&lt;br /&gt;정보의 개수가 한정되어있다고 했지만, 어떤 사람들은 굉장히 많은 정보를 처리한다.&lt;br /&gt;이는 작업 기억 공간에 정보가 들어갈 때, 관련된 정보들이 청크 단위로 함께 들어가기 때문이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex) 트리를 활용한 코드를 읽을 때&lt;br /&gt;트리와 관련된 지식들이 청크로 작업 기억 공간에 들어감.&lt;br /&gt;트리 관련 메서드, 프로퍼티 등은 이미 알고 있기 때문에 다른 부분을 더 파악할 여지가 큼&lt;br /&gt;그런 지식이 없을 때는 트리 관련된 정보를 읽는 것부터 부하가 생김&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크를 잘 꺼내 쓰려면 &quot;표식&quot;이 중요하다.&lt;br /&gt;표식은 이 코드가 어떤 지식과 연관되어 있는지를 나타내는 어떤 표지이다.&lt;br /&gt;변수명이나 문자열, 또는 코드 구조가 될 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex) 트리 구조&lt;br /&gt;root, left, right 등을 통해 tree 구조와 관련된 지식을 떠올릴 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 내용처럼, 코드를 잘 읽으려면 LTM에 지식이 쌓여있는 것이 좋다.&lt;br /&gt;반복/인출/정교화를 통해 LTM의 지식을 강화할 수 있다.&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;인출 - 의도적으로 떠올리는 연습을 통해 인출력 강화 - LTM에서 잘 꺼낼 수 있어짐&lt;/li&gt;
&lt;li&gt;정교화 - 기존 지식과의 연결을 통해 강화 - 청크 활용을 강화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작업 기억 공간 부하 줄이기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 기억 공간에 많은 정보가 들어가면 부하가 생겨 처리에 어려움을 겪는다.&lt;br /&gt;이걸 &quot;인지 부하&quot;라고 한다.&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;모델링하여 그래프로 그려보기&lt;/li&gt;
&lt;li&gt;상태변화를 상태표로 나타내보기&lt;/li&gt;
&lt;li&gt;어려운 코드를 읽기 쉽게 리팩토링 해보기 (임시라도)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드에 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 장에서는 앞서 배운 개념들을 토대로&lt;/p&gt;
&lt;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;h3 data-ke-size=&quot;size23&quot;&gt;코드  이해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 읽고 이해하는 것은 개발자가 가장 많이 하는 일이다.&lt;br /&gt;역시 여기에도 LTM을 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;변수의 역할&quot;을 분류하여 어떤 변수가 어떻게 동작하는지에 대한 지식이 미리 있다면,&lt;br /&gt;코드를 읽을 때 미리 관련 청크를 가져와 작업 처리 공간을 더 효율적으로 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex) Stepper 변수&lt;br /&gt;i, j, k 등의 변수는 반복문 내의 stepper로 사용되므로, 반복문과 관련된 기억을 가져올 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 읽는 것은 또한 텍스트 읽는 것과 유사하다.&lt;br /&gt;긴 글을 읽을 때 처럼 스캔을 한다면 미리 필요한 지식 청크들을 가져올 수 있기 때문에 이해에 도움이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 문제는 인지부하가 커진다.&lt;br /&gt;그렇다면 LTM을 활용하고, 작업 기억 공간의 부하를 줄여 문제 해결에 도움을 줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘탈 모델을 통해 문제의 일부를 추상화하면 인지부하를 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 추상화를 통해 복잡한 디테일은 숨긴다.&lt;br /&gt;2) 추상화와 관련된 기억을 LTM에서 가져올 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;br /&gt;네트워크도 추상화 레이어가 없다면, 물리적으로 데이터를 전송하는 방법까지 생각해야한다.&lt;br /&gt;하지만 그런 복잡한 디테일은 숨겨져 있으므로 더 쉽게 네트워크를 다루고 이해할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;br /&gt;DP 문제를 자주 풀어본 사람은 문제를 읽고 DP의 풀이법을 떠올려서 쉽게 풀 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 문제를 해결하는것에 익숙해지면, 그 문제를 해결할 때에는 인지능력이 거의 사용되지 않는다.&lt;br /&gt;이런 종류의 기억을 &lt;code&gt;암시적 기억&lt;/code&gt;, 이걸 활용하는 것을 &lt;code&gt;자동화 (automization)&lt;/code&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;다른 좋은 코드를 분석 및 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 코드 작성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드를 작성하기 위해서는 이름을 잘 지어야한다. 실제로 코드 베이스의 상당 부분이 &quot;이름&quot;에 해당한다.&lt;/p&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;이름을 통해 이미 알고 있는 정보를 LTM에서 가져옴&lt;/li&gt;
&lt;li&gt;코드내에서 정보를 제공하는 &quot;문서&quot;의 역할을 할 수 있음&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;/li&gt;
&lt;li&gt;이름에서 정보를 찾지 못하면 다른 곳에서 정보를 찾게되어 인지 부하 증가.&lt;/li&gt;
&lt;li&gt;나쁜 이름과 버그는 상관관계가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;좋은 이름이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해하기 쉽도록, 너무 짧거나 길지 않은 이름&lt;br /&gt;코드베이스 내의 규칙에 따라 일관성있게 지은 이름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드베이스 내에서 일관성 있게 명명한 변수는 협업하는 사람들의 인지부하를 줄이는데에 도움이 된다.&lt;br /&gt;다만, 코드베이스의 규칙을 바꾸는 것은 쉽지 않으니 초기에 잘 정해야한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언어적 안티패턴&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 실제 수행하는 작업과 맞지 않는 이름을 가질 때 &quot;언어적 안티패턴&quot;이라 칭한다.&lt;br /&gt;이런 &quot;나쁜 이름&quot;은 LTM의 최적화와 오히려 반대로 작용하여 더 높은 인지부하를 줄 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;e.g. isValid라는 변수가 불리언 값 대신 정숫값을 가지면 기존 정보와 달라 헷갈릴 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식별자가  그 이름보다 더 많거나, 적은 역할, 또는 반대의 역할을 하지 않도록 주의해야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;업무 중단에 대응하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무가 중단되면 작업 기억 공간이 작업 중이던 중요한 정보를 잃어버린다.&lt;br /&gt;작업으로 되돌아가기 위해서는 의도적으로 노력하는 워밍업의 단계가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 업무 시간의 약 20%가 업무 중단에 사용된다.&lt;br /&gt;또한, 중단은 짜증, 불안, 실수의 정도도 높인다.&lt;br /&gt;이런 업무 중단에 잘 대응할 수 있다면 더 효율적으로 일하여 생산성을 높일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대응&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중단 시, 진행 상황에 대한 정신 모델을 작성해두기 (메모, 주석 등)&lt;/li&gt;
&lt;li&gt;큰 task를 미리 subtask로 나누고 계획해두기&lt;/li&gt;
&lt;li&gt;멀티 태스킹 피하기 (메신저, 음악 듣기 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽으며 중요하다고 느낀 부분들을 간단히 정리해보았다.&lt;br /&gt;책에는 더 디테일한 부분들과 설명들, 그리고 관련 연구 자료와 근거들이 나와있다.&lt;br /&gt;또한 이런 이론들을 연습해 볼 연습 방법들도 수록되어있다.&lt;br /&gt;그러므로 관심이 생긴다면 한번 직접 읽어보는 것 추천한다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>The Programmer's Brain</category>
      <category>개발</category>
      <category>개발 공부</category>
      <category>개발도서 추천</category>
      <category>개발자 추천도서</category>
      <category>업무중단</category>
      <category>인지 부하</category>
      <category>인지과학</category>
      <category>프로그래머의 뇌</category>
      <category>프로그래밍</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/112</guid>
      <comments>https://blue-tang.tistory.com/112#entry112comment</comments>
      <pubDate>Sat, 16 Aug 2025 19:02:11 +0900</pubDate>
    </item>
    <item>
      <title>[Github MCP] Github 작업은 AI에게 맡겨보자</title>
      <link>https://blue-tang.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발하다보면 Git은 뗄레야 뗄 수 없다. 특히, Github에서 해야할 일이 굉장히 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Repository에 걸쳐서 다양한 PR과 코멘트를 상대하다보면, 어디까지 리뷰했고 어디를 하다 말았고 헷갈리는 경우도 종종 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 파편화된 업무를 한마디면 척척 정리해주고, 나 대신 코멘트도 달아주는 친구(?)가 있으면 좋겠다는 생각을 하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침 최근에 Github 공식 MCP 서버가 나왔고, 우리 Copilot에 활용이 가능했기 때문에 바로 적용해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/github/github-mcp-server&quot;&gt;https://github.com/github/github-mcp-server&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745928589995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - github/github-mcp-server: GitHub's official MCP Server&quot; data-og-description=&quot;GitHub's official MCP Server. Contribute to github/github-mcp-server development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/github/github-mcp-server&quot; data-og-url=&quot;https://github.com/github/github-mcp-server&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bigxdl/hyYMex4Dma/d2kfiEHV2g21nff23c3cck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/POdFU/hyYH7T6CzO/PpJIzBx48wh0UkTniZCK4K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/github/github-mcp-server&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/github/github-mcp-server&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bigxdl/hyYMex4Dma/d2kfiEHV2g21nff23c3cck/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/POdFU/hyYH7T6CzO/PpJIzBx48wh0UkTniZCK4K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - github/github-mcp-server: GitHub's official MCP Server&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;GitHub's official MCP Server. Contribute to github/github-mcp-server development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 Copilot을 위주로 작성되었지만, Claude 등 다른 MCP 클라이언트와도 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;결과 미리보기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 적용 결과를 보면 다음과 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR 대시보드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 Repo의 내가 연 PR의 상태, 내가 리뷰 요청받은 PR을 한 눈에 볼 수 있는 대시보드&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;1546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EltPk/btsNFRfVMWU/fLrvccU7KHZBCxjABDWHRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EltPk/btsNFRfVMWU/fLrvccU7KHZBCxjABDWHRk/img.png&quot; data-alt=&quot;MCP를 통해 조회한 PR 대시보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EltPk/btsNFRfVMWU/fLrvccU7KHZBCxjABDWHRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEltPk%2FbtsNFRfVMWU%2FfLrvccU7KHZBCxjABDWHRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;1546&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;1546&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MCP를 통해 조회한 PR 대시보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대신 코드리뷰&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용은 가렸지만, 코드 라인에 이렇게 바로 리뷰댓글을 남기는 것도 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqLfNY/btsNF8aGHXM/vXQw55nX9bqhjryIiT2tr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqLfNY/btsNF8aGHXM/vXQw55nX9bqhjryIiT2tr0/img.png&quot; data-alt=&quot;라인별 코드 리뷰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqLfNY/btsNF8aGHXM/vXQw55nX9bqhjryIiT2tr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqLfNY%2FbtsNF8aGHXM%2FvXQw55nX9bqhjryIiT2tr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;261&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;832&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;h1&gt;연동하기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;준비물&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 docker로 실행된다.&lt;/li&gt;
&lt;li&gt;docker가 아니어도 podman 처럼 docker같은 기능을 할 수 있으면 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github Access 토큰&lt;/h3&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;li&gt;사용자 인증 및 기능 사용에 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github MCP 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치는 원클릭으로 가능하다. &lt;a href=&quot;https://github.com/github/github-mcp-server?tab=readme-ov-file#installation&quot;&gt;github-mcp-server&lt;/a&gt;에서 아래 버튼을 클릭하고 따라가면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNnZ85/btsNEQ3m5gO/IpWmBuIPEZ2WwCCHAqoyEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNnZ85/btsNEQ3m5gO/IpWmBuIPEZ2WwCCHAqoyEK/img.png&quot; data-alt=&quot;Install Server 버튼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNnZ85/btsNEQ3m5gO/IpWmBuIPEZ2WwCCHAqoyEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNnZ85%2FbtsNEQ3m5gO%2FIpWmBuIPEZ2WwCCHAqoyEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1744&quot; height=&quot;392&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Install Server 버튼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 안되거나 수동을 선호한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1)&lt;/b&gt; Command Palette (&lt;code&gt;cmd&lt;/code&gt; + &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;p&lt;/code&gt;) &amp;gt; Open Settings (JSON) 으로 이동&lt;br /&gt;&lt;b&gt;2)&lt;/b&gt; 아래 설정 추가 (&amp;lt;personal_token&amp;gt; = 위에서 발급한 토큰)&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;{
  ...
  &quot;mcp&quot;: {
    &quot;servers&quot;: {
      &quot;github&quot;: {
        &quot;command&quot;: &quot;docker&quot;,
        &quot;args&quot;: [
          &quot;run&quot;,
          &quot;-i&quot;,
          &quot;--rm&quot;,
          &quot;-e&quot;,
          &quot;GITHUB_PERSONAL_ACCESS_TOKEN&quot;,
          &quot;ghcr.io/github/github-mcp-server&quot;
        ],
        &quot;env&quot;: {
          &quot;GITHUB_PERSONAL_ACCESS_TOKEN&quot;: &quot;&amp;lt;personal_token&amp;gt;&quot;,
        }
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cf) Github Enterprise인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mcp.servers.github.args&lt;/code&gt;에 추가: &lt;code&gt;&quot;-e&quot;, &quot;GITHUB_HOST&quot;,&lt;/code&gt;&lt;br /&gt;&lt;code&gt;mcp.servers.github.env&lt;/code&gt;에 추가: &lt;code&gt;&quot;GITHUB_HOST&quot;: &quot;https://&amp;lt;enterprise-domain&amp;gt;&quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&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;cf) docker 실행이 안되는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 실행파일이 위치한 전체 경로를 넣어주면 된다.&lt;br /&gt;podman 사용 시에도 마찬가지&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;&lt;b&gt;3)&lt;/b&gt; 설치 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 서버가 떴다면 &lt;code&gt;Running&lt;/code&gt;이라는 표시가 뜬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBTjNz/btsNFYF6gyZ/AkRww3NCYyknXEr6gN6P71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBTjNz/btsNFYF6gyZ/AkRww3NCYyknXEr6gN6P71/img.png&quot; data-alt=&quot;server가 실행중인 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBTjNz/btsNFYF6gyZ/AkRww3NCYyknXEr6gN6P71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBTjNz%2FbtsNFYF6gyZ%2FAkRww3NCYyknXEr6gN6P71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;142&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;server가 실행중인 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇지 않다면 VSCode를 재실행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copilot Chat &amp;gt; Agent 모드로 변경해보면 Chat input 좌상단에 Github MCP 도구 36개가 활성화된 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YM7sg/btsNE5zcIo8/xui0yhB8lw5QG1yiDtKRIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YM7sg/btsNE5zcIo8/xui0yhB8lw5QG1yiDtKRIK/img.png&quot; data-alt=&quot;tools가 활성화된 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YM7sg/btsNE5zcIo8/xui0yhB8lw5QG1yiDtKRIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYM7sg%2FbtsNE5zcIo8%2Fxui0yhB8lw5QG1yiDtKRIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1562&quot; height=&quot;210&quot; data-origin-width=&quot;1562&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;tools가 활성화된 경우&lt;/figcaption&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;Repository 조회&lt;/li&gt;
&lt;li&gt;PR 조회, 생성 등&lt;/li&gt;
&lt;li&gt;브랜치 조회, 생성 등&lt;/li&gt;
&lt;li&gt;코멘트/리뷰 달기&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;1162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0rDau/btsNEEhPove/2FOE5HSe8fpUOpZwSkfu51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0rDau/btsNEEhPove/2FOE5HSe8fpUOpZwSkfu51/img.png&quot; data-alt=&quot;github mcp server tools&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0rDau/btsNEEhPove/2FOE5HSe8fpUOpZwSkfu51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0rDau%2FbtsNEEhPove%2F2FOE5HSe8fpUOpZwSkfu51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;1162&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;1162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;github mcp server tools&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등 기능 목록은 &lt;a href=&quot;https://github.com/github/github-mcp-server?tab=readme-ov-file#tools&quot;&gt;Tools&lt;/a&gt; 섹션에 잘 나와있으니 참고&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프롬프트 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직 단위의 Repository에서 일하는 경우, 조직명을 줘야 잘 찾는다.&lt;br /&gt;조직의 Repo에서 조회해야하는데 개인 Repo를 조회하는 경우가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회하고 싶은 Repository가 여러개인 경우, 필요한 Repository를 전달해주면 더 빠르다.&lt;br /&gt;전달해주지 않으면 현재 작업중인 디렉토리의 PR만 찾는 경우가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전달 받은 내용을 테이블 형식 등으로 출력 받으면 좋은 대시보드가 된다.&lt;br /&gt;PR 번호에 PR 링크도 걸어달라고 하면 바로 필요한 PR을 열어볼 수 있다.&lt;br /&gt;approve 여부 등도 조회할 수 있으니, 다양하게 활용해보면 좋을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으론 오전에 전날 들어오거나 놓친 리뷰등을 파악할 때 굉장히 유용하게 사용하고 있다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>ai 생산성</category>
      <category>ai 업무 관리</category>
      <category>ai 워크 플로우</category>
      <category>ai 코드 리뷰</category>
      <category>claude mcp</category>
      <category>copilot mcp</category>
      <category>github mcp</category>
      <category>github 자동화</category>
      <category>mcp 연동</category>
      <category>프롬프팅</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/111</guid>
      <comments>https://blue-tang.tistory.com/111#entry111comment</comments>
      <pubDate>Tue, 29 Apr 2025 21:35:58 +0900</pubDate>
    </item>
    <item>
      <title>뚝뚝 끊기는 이미지 UX 개선 - Progressive 이미지</title>
      <link>https://blue-tang.tistory.com/110</link>
      <description>&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Progressive 이미지를 통해 느린 이미지 로드로 인해 저하된 사용자 경험을 개선할 수 있다.&lt;/li&gt;
&lt;li&gt;JPG, PNG로 한번 만들어보기&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 용량이 크거나 네트워크가 느릴 때, 이미지가 위에서 아래로 끊기듯이 로드되어 사용자 경험을 저해할 수 있다.&lt;br /&gt;이 문제를 해결하기 위한 방법을 보다가 Progressive 방식을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 사용하다보면 이미지를 로드하는 방식에 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 앞서 말한 것처럼 이미지가 끊어지면서 로딩&lt;br /&gt;2) 흐린 이미지가 점점 선명해지면서 로딩&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자가 바로 progressive 방식의 이미지이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Progressive 방식의 이미지는 UX 상의 이점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;저사양 환경에서도 빠른 시각적 피드백&lt;/li&gt;
&lt;li&gt;사용자가 로딩 중임을 인지하게 함으로써 이탈율 감소&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 점만 있는 것은 아니다. UX 적으로는 분명 이점이 있지만, 몇가지 고려할 포인트가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;오버헤드 증가 (파일 크기, 디코드 시간)&lt;/li&gt;
&lt;li&gt;일부 브라우저 미지원 (Safari, IE8)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 체감 성능 차이에 오버헤드가 주는 영향은 다른 요소들에 비해 미미하다고 한다.&lt;br /&gt;특히, 주 고객층의 네트워크 환경이 느리거나 큰 이미지를 많이 사용하는 서비스의 경우 이미지 UX 최적화의 일환으로 사용할 수 있다.&lt;/p&gt;
&lt;h1&gt;만들어보기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;준비물&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 이미지 - 클수록 느린게 티나기 때문에 좋다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;진행 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;원하는 경로로 이동&lt;/li&gt;
&lt;li&gt;프로젝트 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sharp&lt;/code&gt; 설치&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sharp.pixelplumbing.com/&quot;&gt;https://sharp.pixelplumbing.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745742081101&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;High performance Node.js image processing&quot; data-og-description=&quot;High performance Node.js image processing. The fastest module to resize JPEG, PNG, WebP and TIFF images.&quot; data-og-host=&quot;sharp.pixelplumbing.com&quot; data-og-source-url=&quot;https://sharp.pixelplumbing.com/&quot; data-og-url=&quot;https://sharp.pixelplumbing.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://sharp.pixelplumbing.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sharp.pixelplumbing.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;High performance Node.js image processing&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;High performance Node.js image processing. The fastest module to resize JPEG, PNG, WebP and TIFF images.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sharp.pixelplumbing.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;nodejs&lt;/code&gt;의 이미지 처리 라이브러리&lt;br /&gt;&lt;code&gt;sharp&lt;/code&gt;를 통해 progressive 형식 이미지를 만들것이다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;npm install sharp  &lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;converter.js&lt;/code&gt; 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;#!/usr/bin/env node  

const path = require(&quot;path&quot;);  
const sharp = require(&quot;sharp&quot;);  
const args = process.argv.slice(2);  
let input, output;

// arg 파싱  
for (let i = 0; i &amp;lt; args.length; i++) {  
  if ((args\[i\] === &quot;-i&quot; || args\[i\] === &quot;--input&quot;) &amp;amp;&amp;amp; args\[i + 1\]) {  
    input = args\[i + 1\];  
    i++;  
  } else if ((args\[i\] === &quot;-o&quot; || args\[i\] === &quot;--output&quot;) &amp;amp;&amp;amp; args\[i + 1\]) {  
    output = args\[i + 1\];  
    i++;  
  }  
}  

if (!input || !output) {  
  console.error(&quot;Usage: node converter.js -i input.jpg/png -o output.jpg/png&quot;);  
  process.exit(1);  
}  

// Determine output format by extension  
const ext = path.extname(output).toLowerCase();  
let pipeline = sharp(input);

// progressive로 output을 만든다.  
if (ext === &quot;.jpg&quot; || ext === &quot;.jpeg&quot;) {  
  pipeline = pipeline.jpeg({ progressive: true });  
} else if (ext === &quot;.png&quot;) {  
  pipeline = pipeline.png({ progressive: true }); // interlaced PNG  
} else {  
  console.error(&quot;Unsupported output format:&quot;, ext);  
  process.exit(1);  
}  

pipeline  
  .toFile(output)  
  .then(() =&amp;gt; console.log(\`Converted and saved to ${output}\`))  
  .catch((err) =&amp;gt; {  
    console.error(&quot;Error generating output image:&quot;, err);  
    process.exit(1);  
  });  &lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;index.html&lt;/code&gt; 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!doctype html&amp;gt;  
&amp;lt;html lang=&quot;en&quot;&amp;gt;  
  &amp;lt;head&amp;gt;  
    &amp;lt;meta charset=&quot;UTF-8&quot; /&amp;gt;  
    &amp;lt;title&amp;gt;Progressive JPEG Test&amp;lt;/title&amp;gt;  
  &amp;lt;/head&amp;gt;  
  &amp;lt;body&amp;gt;  
    &amp;lt;h1&amp;gt;Progressive JPEG Test&amp;lt;/h1&amp;gt;  
    &amp;lt;img src=&quot;http://localhost:8000/image.png&quot; alt=&quot;Progressive JPEG example&quot; /&amp;gt;  
  &amp;lt;/body&amp;gt;  
&amp;lt;/html&amp;gt;  &lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;6&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 생성 및 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);  
const app = express();  
app.use(express.static(&quot;.&quot;));  
app.listen(8000, () =&amp;gt; console.log(&quot;http://localhost:8000&quot;));  &lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 이미지 변환  
node converter.js -i input.jpg -o output.jpeg  
node converter.js -i input.png -o output.png  &lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 서버 실행  
node server.js  &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬호스트에 들어가보면 이미지를 확인할 수 있다.&lt;br /&gt;네트워크 throttle을 걸어두고 느린환경에서 실행해보면, 두 방식의 차이를 확연히 알 수 있다.&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;progressive.gif&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD4rnp/btsNCqiB5Nl/rnJFXB9fCYnaZGbhbI6DRk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD4rnp/btsNCqiB5Nl/rnJFXB9fCYnaZGbhbI6DRk/img.gif&quot; data-alt=&quot;progressive 이미지 로딩&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD4rnp/btsNCqiB5Nl/rnJFXB9fCYnaZGbhbI6DRk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bD4rnp/btsNCqiB5Nl/rnJFXB9fCYnaZGbhbI6DRk/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;728&quot; height=&quot;478&quot; data-filename=&quot;progressive.gif&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;progressive 이미지 로딩&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FE 개발하면서 직접 사용할 일은 많지 않아 보이지만, 알아두면 이미지 개선 요청 등에 대응하기 좋을 것이다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>baseline mode</category>
      <category>interlaced png</category>
      <category>jpg 최적화</category>
      <category>nodejs sharp</category>
      <category>nodejs 이미지 처리</category>
      <category>progressive image loading</category>
      <category>웹 개발</category>
      <category>이미지 ux 개선</category>
      <category>이미지 최적화</category>
      <category>프론트엔드 개발</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/110</guid>
      <comments>https://blue-tang.tistory.com/110#entry110comment</comments>
      <pubDate>Sun, 27 Apr 2025 17:25:40 +0900</pubDate>
    </item>
    <item>
      <title>Neovim에서 Copilot 사용하기</title>
      <link>https://blue-tang.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 Copilot 지원이 시작되었다. 바로 신청해서 사용해보았지만 너무 큰 문제가 있었다.&lt;br /&gt;VSCode와 Neovim의 싱크가 깨지는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vscode Neovim 익스텐션은 VScode 탭과 Neovim 인스턴스 사이에서 싱크를 맞춘다.&lt;br /&gt;이 때, 코파일럿의 자동완성을 사용하면 간할적으로 Neovim의 싱크가 깨졌다.&lt;br /&gt;키들이 동작하지 않았고 매번 익스텐션을 재시작 해야했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zIYT4/btsMzshKBg8/nPaXGbMzIEVCXBfSIXKGi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zIYT4/btsMzshKBg8/nPaXGbMzIEVCXBfSIXKGi0/img.png&quot; data-alt=&quot;Sync가 깨져 발생하는 오류&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zIYT4/btsMzshKBg8/nPaXGbMzIEVCXBfSIXKGi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzIYT4%2FbtsMzshKBg8%2FnPaXGbMzIEVCXBfSIXKGi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;615&quot; height=&quot;175&quot; data-origin-width=&quot;615&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Sync가 깨져 발생하는 오류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 등록된 이슈로 확인할 수 있는데, 25.02.28 기준 아직 진행 중으로 확인된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Github 이슈 참고: &lt;a href=&quot;https://github.com/vscode-neovim/vscode-neovim/issues/2184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vscode-neovim/vscode-neovim/issues/2184&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1740728966202&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;bug: &amp;quot;Syncing layout: Syncing active editor&amp;quot; keeps running forever &amp;middot; Issue #2184 &amp;middot; vscode-neovim/vscode-neovim&quot; data-og-description=&quot;Check the following: I have checked the behavior after setting &amp;quot;vscode-neovim.neovimClean&amp;quot; in VSCode settings. I have read the vscode-neovim docs. I have searched existing issues. Neovim version (n...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/vscode-neovim/vscode-neovim/issues/2184&quot; data-og-url=&quot;https://github.com/vscode-neovim/vscode-neovim/issues/2184&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZIvib/hyYjm5gH7l/rc0WMPI33RKXLxv7Oc1Ch0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/beX4UI/hyYm0GbCsQ/hAH3RnrKDqFgc5bSqKDfC0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/vscode-neovim/vscode-neovim/issues/2184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/vscode-neovim/vscode-neovim/issues/2184&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZIvib/hyYjm5gH7l/rc0WMPI33RKXLxv7Oc1Ch0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/beX4UI/hyYm0GbCsQ/hAH3RnrKDqFgc5bSqKDfC0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;bug: &quot;Syncing layout: Syncing active editor&quot; keeps running forever &amp;middot; Issue #2184 &amp;middot; vscode-neovim/vscode-neovim&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Check the following: I have checked the behavior after setting &quot;vscode-neovim.neovimClean&quot; in VSCode settings. I have read the vscode-neovim docs. I have searched existing issues. Neovim version (n...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Copilot in Neovim&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서, 다시 Neovim으로 돌아갈 생각은 없었지만 잠시 Copilot을 사용해보기위해 Neovim으로 돌아갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히, 내가 사용하는 Lazyvim에서는 쉽게 &lt;code&gt;Copilot&lt;/code&gt;과 &lt;code&gt;Copilot-chat&lt;/code&gt;을 지원하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;:LazyExtras&lt;/code&gt;로 extra 플러그인 설치로 진입하여 &lt;code&gt;Copilot&lt;/code&gt;과 &lt;code&gt;Copilot-chat&lt;/code&gt;을 설치해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX5Pgb/btsMAIxmnKD/CPTXkO1UARcRMoBOFsBKd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX5Pgb/btsMAIxmnKD/CPTXkO1UARcRMoBOFsBKd1/img.png&quot; data-alt=&quot;LazyExtras 플러그인 설치 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX5Pgb/btsMAIxmnKD/CPTXkO1UARcRMoBOFsBKd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX5Pgb%2FbtsMAIxmnKD%2FCPTXkO1UARcRMoBOFsBKd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;467&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LazyExtras 플러그인 설치 화면&lt;/figcaption&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;a href=&quot;https://github.com/zbirenbaum/copilot.lua&quot;&gt;zbirenbaum/copilot.lua&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/CopilotC-Nvim/CopilotChat.nvim&quot;&gt;CopilotC-Nvim/CopilotChat.nvim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lazyvim이 아니라도 위 깃헙을 참고하면 쉽게 설정할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;주의!&lt;/b&gt;&lt;br /&gt;Copilot&amp;nbsp;플러그인은 Node &amp;gt;= 18에서 동작한다.&lt;br /&gt;vim.g.node\_host\_prog = &quot;/path/to/node&quot;를 통해 적당한 노드 버전을 지정해줘야한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후, &lt;code&gt;:Copilot Auth&lt;/code&gt;를 실행하면 깃헙 인증 링크가 열린다. 인증하면 성공.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Inline Suggestion&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트에서 inline suggestion이 잘 되는지 실행해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 gif를 보면, &lt;code&gt;handle&lt;/code&gt;까지 입력했을때 현재 구현이 안되어있는 &lt;code&gt;handleUpload&lt;/code&gt;에 대한 제안이 나온다.&lt;br /&gt;2개의 후보를 제시해줘서 next, prev 키를 통해 둘 중 고를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력에 즉각적으로 추천을 제시하는것을 보고, 출시 초기보다 많이 발전했음을 느꼈다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;handleupload.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRcKpK/btsMysJCTKZ/teP53YOKkKKmnAfdBMquHk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRcKpK/btsMysJCTKZ/teP53YOKkKKmnAfdBMquHk/img.gif&quot; data-alt=&quot;Handle Upload에 대한 inline suggestion 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRcKpK/btsMysJCTKZ/teP53YOKkKKmnAfdBMquHk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cRcKpK/btsMysJCTKZ/teP53YOKkKKmnAfdBMquHk/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;430&quot; data-filename=&quot;handleupload.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Handle Upload에 대한 inline suggestion 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 lazyvim에서는 &lt;code&gt;blink.cmp&lt;/code&gt; (또는 &lt;code&gt;nvim.cmp&lt;/code&gt;)를 통해 auto complete에 copilot suggestion이 통합되어있다.&lt;br /&gt;개인적으로는 VSCode에서처럼 inline suggestion을 사용하는 것이 편해서 이 부분만 따로 설정해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 값을 false로 수정해주면 inline으로 사용가능하다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;-- init.lua
vim.g.ai_cmp = false&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Github 이슈 참고: &lt;a href=&quot;https://github.com/LazyVim/LazyVim/discussions/4830&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/LazyVim/LazyVim/discussions/4830&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1740728914465&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;I want to use Copilot suggestion with &amp;lt;Tab&amp;gt; but it does not work properly &amp;middot; LazyVim LazyVim &amp;middot; Discussion #4830&quot; data-og-description=&quot;Hi, I am using LazyVim almost bare bones with plugins; however, I wanted to have copilot suggestions to be completed with &amp;lt;Tab&amp;gt; instead of going to the autocomplete panel and being mixed with varia...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/LazyVim/LazyVim/discussions/4830&quot; data-og-url=&quot;https://github.com/LazyVim/LazyVim/discussions/4830&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGgnpQ/hyYjoPBFFe/MZfoT6wI2sAVHLj8YCOHgk/img.png?width=1200&amp;amp;height=600&amp;amp;face=1003_125_1048_173,https://scrap.kakaocdn.net/dn/bA9c52/hyYjydv2Pq/zrKK1DxARPteeKMdYBPkGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1003_125_1048_173&quot;&gt;&lt;a href=&quot;https://github.com/LazyVim/LazyVim/discussions/4830&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/LazyVim/LazyVim/discussions/4830&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGgnpQ/hyYjoPBFFe/MZfoT6wI2sAVHLj8YCOHgk/img.png?width=1200&amp;amp;height=600&amp;amp;face=1003_125_1048_173,https://scrap.kakaocdn.net/dn/bA9c52/hyYjydv2Pq/zrKK1DxARPteeKMdYBPkGK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1003_125_1048_173');&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;I want to use Copilot suggestion with &amp;lt;Tab&amp;gt; but it does not work properly &amp;middot; LazyVim LazyVim &amp;middot; Discussion #4830&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hi, I am using LazyVim almost bare bones with plugins; however, I wanted to have copilot suggestions to be completed with &amp;lt;Tab&amp;gt; instead of going to the autocomplete panel and being mixed with varia...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suggestion에서는 아직 얼마전 출시한 Copilot의 GPT-4o 모델을 지원하지 않는다.&lt;br /&gt;(개발 예정 &lt;a href=&quot;https://github.com/zbirenbaum/copilot.lua/issues/365&quot;&gt;https://github.com/zbirenbaum/copilot.lua/issues/365&lt;/a&gt;)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Copilot Chat&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VSCode의 코파일럿 패널처럼 동작하는 Neovim 플러그인이다.&lt;br /&gt;여러 파일에 대한 분석 및 수정, 커밋 메시지 동작 등 생각보다 Copilot의 기능들을 충실히 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인에서 제공한 영상링크를 보면 프로젝트에서 수정할 파일들을 분석해서 적용하고, 커밋메시지까지 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/CopilotC-Nvim/CopilotChat.nvim&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;영상 링크 참고&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계별로 실행하는 과정은 다음과 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 코파일럿 챗 열기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챗은 기본적으로 추가 버퍼를 통해 열린다.&lt;br /&gt;창에서 모델 선택, 컨텍스트 입력, 명령 입력을 통해 동작한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 모델 선택 (생략 가능)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 모델을 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-28 오후 5.02.47.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpWwil/btsMyDYvRDv/03f8zHGuj063zedu0ZFYTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpWwil/btsMyDYvRDv/03f8zHGuj063zedu0ZFYTk/img.png&quot; data-alt=&quot;AI 모델 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpWwil/btsMyDYvRDv/03f8zHGuj063zedu0ZFYTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpWwil%2FbtsMyDYvRDv%2F03f8zHGuj063zedu0ZFYTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;732&quot; height=&quot;401&quot; data-filename=&quot;스크린샷 2025-02-28 오후 5.02.47.png&quot; data-origin-width=&quot;1754&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI 모델 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 컨텍스트 입력 (생략 가능)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트는 코드에 사용할 맥락을 제공한다. buffers를 통해 열린 buffer에서 맥락을 얻을 수 있고, files를 통해 workspace 내의 모든 파일에 접근하거나, file으로 특정 파일을 제공할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-28 오후 5.10.27.png&quot; data-origin-width=&quot;2332&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pEUiB/btsMykSyN1W/LlsxYlkkWFefe1UatcT3M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pEUiB/btsMykSyN1W/LlsxYlkkWFefe1UatcT3M0/img.png&quot; data-alt=&quot;컨텍스트 입력&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pEUiB/btsMykSyN1W/LlsxYlkkWFefe1UatcT3M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpEUiB%2FbtsMykSyN1W%2FLlsxYlkkWFefe1UatcT3M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;155&quot; data-filename=&quot;스크린샷 2025-02-28 오후 5.10.27.png&quot; data-origin-width=&quot;2332&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;컨텍스트 입력&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 질문&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 질문 프롬프트를 입력한다.&lt;br /&gt;전체 예시를 보기 위해 코딩을 잘하기로 유명한 Claude 3.7 Sonnet을 사용해서 간단한 코드 작성을 요청해보았다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/453343870&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/x97tz/hyYjniPGwR/ecrrOtKSKBh3CMrnGIXdfk/img.jpg?width=1486&amp;amp;height=1080&amp;amp;face=0_0_1486_1080,https://scrap.kakaocdn.net/dn/eGSSg/hyYjKE1vXM/n6A1kpeldKWmkW7L0SkhEk/img.jpg?width=1486&amp;amp;height=1080&amp;amp;face=0_0_1486_1080&quot; data-video-width=&quot;640&quot; data-video-height=&quot;465&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;625&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/453343870?service=daum_tistory&quot; width=&quot;640&quot; height=&quot;465&quot; frameborder=&quot;0&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;&lt;br /&gt;Thinking 모델을 사용하여 조금 오래걸려서 결과까지는 녹화하지 않았다.&lt;br /&gt;코파일럿이 코드를 개선해주고, 아래에 간단한 설명과 참고한 파일을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;C-y&amp;gt; 단축키를 통해 제안 사항을 바로 반영할 수 있다는 것이 굉장히 편리하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-28 오후 7.00.35.png&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;1422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5gcYy/btsMz6lhzyc/nO31VvxU3aC1NbHHlUZd8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5gcYy/btsMz6lhzyc/nO31VvxU3aC1NbHHlUZd8k/img.png&quot; data-alt=&quot;수정 내용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5gcYy/btsMz6lhzyc/nO31VvxU3aC1NbHHlUZd8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5gcYy%2FbtsMz6lhzyc%2FnO31VvxU3aC1NbHHlUZd8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;417&quot; height=&quot;1422&quot; data-filename=&quot;스크린샷 2025-02-28 오후 7.00.35.png&quot; data-origin-width=&quot;982&quot; data-origin-height=&quot;1422&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-filename=&quot;스크린샷 2025-02-28 오후 6.56.40.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLct0V/btsMyHmnMzn/FHJOrJgk6Xd76LiXKiSaK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLct0V/btsMyHmnMzn/FHJOrJgk6Xd76LiXKiSaK0/img.png&quot; data-alt=&quot;설명 및 참고 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLct0V/btsMyHmnMzn/FHJOrJgk6Xd76LiXKiSaK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLct0V%2FbtsMyHmnMzn%2FFHJOrJgk6Xd76LiXKiSaK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;409&quot; height=&quot;572&quot; data-filename=&quot;스크린샷 2025-02-28 오후 6.56.40.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설명 및 참고 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1740736743144&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // 변경사항
 
 const FileUploadButton = () =&amp;gt; {
    ...
    function formatFileSize(bytes: number) {
        if (bytes &amp;lt; 1024) return `${bytes} Bytes`;
        else if (bytes &amp;lt; 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
        else return `${(bytes / 1048576).toFixed(1)} MB`;
    }

    return ( 
      ...
      {file &amp;amp;&amp;amp; (
        &amp;lt;div className=&quot;mt-3 flex items-center p-3 rounded-md bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700&quot;&amp;gt;
          &amp;lt;FileIcon className=&quot;mr-2 h-4 w-4 text-slate-500 dark:text-slate-400&quot; /&amp;gt;
          &amp;lt;div className=&quot;flex flex-col&quot;&amp;gt;
            &amp;lt;span className=&quot;text-sm font-medium&quot;&amp;gt;{file.name}&amp;lt;/span&amp;gt;
            &amp;lt;span className=&quot;text-xs text-slate-500 dark:text-slate-400&quot;&amp;gt;
              {formatFileSize(file.size)}
            &amp;lt;/span&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
      ...
    )}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 기타&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 기능을 통해, 미리 정의된 기능들을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Fix, Commit, Tests, Explain, Docs, Optimize, Review 가 현재 정의되어 있다.&lt;br /&gt;각각의 목적에 맞는 기능을 제공하는데 개발 시 자주 사용할 수 있는 기능들이 모여있어서 유용하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-28 오후 7.05.28.png&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0i6Fo/btsMx3Q66lJ/sBzu6xuQdH7rdVlR0yVZvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0i6Fo/btsMx3Q66lJ/sBzu6xuQdH7rdVlR0yVZvK/img.png&quot; data-alt=&quot;코파일럿 프롬프트 액션 창&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0i6Fo/btsMx3Q66lJ/sBzu6xuQdH7rdVlR0yVZvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0i6Fo%2FbtsMx3Q66lJ%2FsBzu6xuQdH7rdVlR0yVZvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;528&quot; data-filename=&quot;스크린샷 2025-02-28 오후 7.05.28.png&quot; data-origin-width=&quot;2044&quot; data-origin-height=&quot;1658&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코파일럿 프롬프트 액션 창&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Neovim에서도 Copilot을 생각보다 더 자연스럽게 쓸 수 있다.&lt;br /&gt;특히, Chat 기능들도 너무 잘돼서 이정도면 실무에서 써볼만 하다는 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬움이 있다면, 아직 4o를 미지원한다는 것과, Neovim 기반이기 때문에 VSCode 만큼의 UI는 지원하지 않는다는 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로도 사용할만한 것 같아서 한 한달정도 사용하면서 장단점을 파악해볼 예정이다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>copilot.lua</category>
      <category>copilotchat.nvim</category>
      <category>inline suggestion</category>
      <category>lazyvim copilot</category>
      <category>neovim copilot</category>
      <category>neovim 코파일럿 플러그인</category>
      <category>neovim에서 코파일럿 사용</category>
      <category>vscode copilot</category>
      <category>vscode neovim 싱크</category>
      <category>코파일럿</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/109</guid>
      <comments>https://blue-tang.tistory.com/109#entry109comment</comments>
      <pubDate>Fri, 28 Feb 2025 19:08:28 +0900</pubDate>
    </item>
    <item>
      <title>[Naver D2] 고급 타입스크립트 세션 정리</title>
      <link>https://blue-tang.tistory.com/108</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-02-25 at 11.41.07 PM.png&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDi5LO/btsMx8bTH86/5k0B4KEutdKZiSKfV8V5Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDi5LO/btsMx8bTH86/5k0B4KEutdKZiSKfV8V5Nk/img.png&quot; data-alt=&quot;Typescript 홈페이지 소개글&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDi5LO/btsMx8bTH86/5k0B4KEutdKZiSKfV8V5Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDi5LO%2FbtsMx8bTH86%2F5k0B4KEutdKZiSKfV8V5Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;848&quot; data-filename=&quot;Screenshot 2025-02-25 at 11.41.07 PM.png&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Typescript 홈페이지 소개글&lt;/figcaption&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;infer, never만 보면 두려워지는 당신을 위한 고급 TypeScript를 보았다.&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;제네릭 타입 검사&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;1시간 12분으로 짧지 않지만 내용이 밀도 있고, 발표자료가 준비가 잘 되어 있기에,&lt;br /&gt;타입스크립트를 잘 사용하고 싶다면 꼭 한번 보는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://d2.naver.com/helloworld/7472830&quot;&gt;infer, never만 보면 두려워지는 당신을 위한 고급 TypeScript&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 아래에 간단히 정리해보았다. (많은 내용 생략)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입의 대소 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 A와 B의 대소는 &lt;code&gt;서브타입&lt;/code&gt; 관계에 따라 달라진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;서브타입의 정의&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;A가 B의 서브타입이다 = 모든 p에 대해, p가 B의 prop이면 p는 A의 prop이다.&lt;/blockquote&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;a는 b의 슈퍼타입&lt;/li&gt;
&lt;li&gt;a는 b의 서브타입&lt;/li&gt;
&lt;li&gt;a와 b는 서로 서브타입&lt;/li&gt;
&lt;li&gt;a와 b는 무관계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 B의 서브타입일 때, B에 A를 대입할 수 있다.&lt;br /&gt;즉, &lt;b&gt;넓은 타입&lt;/b&gt;에 &lt;b&gt;좁은 타입&lt;/b&gt;을 대입할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타입의 종류&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;원시 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boolean, number, string, symbol, null, undefined&lt;br /&gt;서로 무관계&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 타입에 속한 값 하나로 구성하는 타입. 본래 타입의 서브타입으로 간주.&lt;br /&gt;&lt;code&gt;as const&lt;/code&gt;를 통해 특정 값을 리터럴 타입으로 선언할 수 있음.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const a: number = 1; // number type
const a: 6 = 1; // '6' 그 자체가 타입 (리터럴 타입)
const a = 6 as const; // a의 타입은 '6' (리터럴 타입)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;객체 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;L의 모든 속성 P에 대하여 &lt;code&gt;L[P] &amp;gt; R[P]&lt;/code&gt; 이면 &lt;code&gt;L &amp;gt; R&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// ex)
L: { x: number; y?: string; z?: boolean; }
R: { x: number;             z: false; a: 'foo' }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서, R의 속성들을 살펴보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;x는 R의 L.x의 서브타입&lt;/li&gt;
&lt;li&gt;y(&lt;code&gt;undefined&lt;/code&gt;)는 L.y (&lt;code&gt;string | undefined&lt;/code&gt;)의 서브타입&lt;/li&gt;
&lt;li&gt;z는 R.z (&lt;code&gt;boolean | undefined&lt;/code&gt;)의 서브타입&lt;br /&gt;으로, 모두 서브타입 관계를 만족한다.&lt;br /&gt;즉, R은 L의 서브타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배열/튜플 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체와 동일하나, number를 키로 갖는다.&lt;br /&gt;튜플은 length가 상수 리터럴 타입으로 고정된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;키 타입 (keyof)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 타입의 속성의 Union으로 이루어진 타입&lt;br /&gt;가질 수 있는 가장 넓은 타입 - &lt;code&gt;number | string | symbol&lt;/code&gt;&lt;br /&gt;명시적 타입이 주어질 경우, 필드명의 union&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함수 타입&lt;/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;반환형에 대해서 공변적 (Covariant)&lt;/li&gt;
&lt;li&gt;인자형에 대해서 반변적 (Contravariant)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인자형이 같은 경우, A가 B의 서브타입이라면, A를 반환하는 함수는 B를 반환하는 함수의 서브타입이다.&lt;/li&gt;
&lt;li&gt;반환형이 같은 경우, A가 B의 서브타입이라면, B를 인자로 하는 함수는 A를 인자로 하는 함수의 서브타입이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;인자가 적은 함수&lt;/code&gt;를 &lt;code&gt;인자가 많은 함수&lt;/code&gt;에 대입할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특수 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;never, unknown, any, void&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unknown: 모든 T에 대해, never &amp;lt; T &amp;lt; unknown&lt;br /&gt;반면 any: never 제외한 모든 T에 대해 any는 T와 서로 서브타입. never는 any의 서브타입&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;i&gt;퀴즈!&lt;/i&gt;&lt;br /&gt;존재할 수 있는 함수 타입 중 가장 넓은 타입은?&lt;/blockquote&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Wide = (...args: never[]) =&amp;gt; unknown&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입스크립트 공식문서에도 종종 등장하는 타입이라고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제네릭 타입 검사&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;명시적 타입 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭에 명시적으로 타입을 전달한 경우.&lt;br /&gt;제네릭이 선언된 심볼 다음부터는 전달한 타입 그대로 간주&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;타입 인자 추론&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타입 선언 안 한 경우, 제네릭 타입 인자 생략한 경우, infer 문 사용한 경우&lt;br /&gt;컨트롤 플로우 분석으로 수집한 전제로, 해당 타입 인자를 추론. (Greedy)&lt;br /&gt;최대한 비관적, 보수적 분석&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 개발의 시선에서는 타입 시스템을 작성하는 것도 큰 이슈라고 생각했다.&lt;br /&gt;서비스 코드에서는 마지막 예시같은 극한의 상황은 아직 못겪어봤다.&lt;br /&gt;&lt;br /&gt;하지만 공용 유틸 함수 등의 개발에서는 복잡한 타입의 활용도가 높을 수 있다.&lt;br /&gt;그러므로 서비스 개발자도 다양한 복잡한 타입 케이스에 대응하는 연습을 해보면 좋다.&lt;br /&gt;이를 통해 전체 코드베이스의 안정성을 향상할 수 있을 것이다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>control flow</category>
      <category>Generic</category>
      <category>Infer</category>
      <category>javascript</category>
      <category>library</category>
      <category>subType</category>
      <category>typescript</category>
      <category>타입 대소 비교</category>
      <category>타입 추론</category>
      <category>타입스크립트</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/108</guid>
      <comments>https://blue-tang.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 25 Feb 2025 23:41:50 +0900</pubDate>
    </item>
    <item>
      <title>JS 글자 수 세기 문제</title>
      <link>https://blue-tang.tistory.com/107</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;글자 수 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS에서 글자 수를 validation을 하는 경우 보통 length를 사용한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const MAX_LENGTH = 10;
const validate = (value: string) =&amp;gt; value.length &amp;lt;= MAX_LENGTH;

// ex)
validate('1234567890') // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 아래 경우는 어떨까?&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;validate('정답을 맞춰보세요 ');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;띄어쓰기 포함 10글자이지만, &lt;code&gt;false&lt;/code&gt;를 출력하는것을 볼 수 있다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;'정답을 맞춰보세요 '.length; // 11&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emoji가 길이 2로 계산되기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결책&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;[...'정답을 맞춰보세요 '].length; // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 spread 연산자를 사용하면 결과가 10으로 정상적으로 나오는 것을 알 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 인덱싱, 길이연산 등에서 기본적으로 UTF-16 코드 유닛 기반으로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;'정답을 맞춰보세요 '[9]; // '\uD83D' (코드 유닛)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 문자열의 이터레이션은 Unicode 코드 포인트 기반으로 동작한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mdn&lt;br /&gt;Strings are iterated by Unicode code points. This means grapheme clusters will be split, but surrogate pairs will be preserved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, spread 연산자나, &lt;code&gt;for ... of&lt;/code&gt;를 통해서 반복문을 사용하면, 이모지에 대해서도 정상적으로 사용 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-02-18 at 11.10.38 PM.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKVRZp/btsMmJSpDMO/CUmUMsnDdSQHDrAmrTavy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKVRZp/btsMmJSpDMO/CUmUMsnDdSQHDrAmrTavy1/img.png&quot; data-alt=&quot;iterator로 순회하는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKVRZp/btsMmJSpDMO/CUmUMsnDdSQHDrAmrTavy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKVRZp%2FbtsMmJSpDMO%2FCUmUMsnDdSQHDrAmrTavy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;86&quot; data-filename=&quot;Screenshot 2025-02-18 at 11.10.38 PM.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;iterator로 순회하는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; &amp;zwj; &lt;/h2&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;이모지 출처: &lt;a href=&quot;https://emojipedia.org/man-cook-light-skin-tone&quot;&gt;https://emojipedia.org/man-cook-light-skin-tone&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이모지는 길이가 몇일까?&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;' &amp;zwj; '.length; // 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 이 이모지는 여러 이모지의 조합이다.&lt;/p&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;' &amp;zwj; '.substring(0, 2); //  
' &amp;zwj; '.substring(0, 5); //  &amp;zwj;
' &amp;zwj; '.substring(5, 7); //  &lt;/code&gt;&lt;/pre&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;실제로, spread 연산자로 길이 계산해보면, 다음과 같이 분해되서 (깨진채로) 나타난다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;[...' &amp;zwj; '] // [' ', ' ', '&amp;zwj;', ' ']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 애들은 console에서 입력 후 backspace로 지울 때도, 한번에 지워지지 않는다.&lt;br /&gt;(남자로 변했다가 지워짐)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;emoji.gif&quot; data-origin-width=&quot;92&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1hcgE/btsMmek2qN7/PAWWaoftSPA88g4kzE6uV1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1hcgE/btsMmek2qN7/PAWWaoftSPA88g4kzE6uV1/img.gif&quot; data-alt=&quot;한번에 지워지지 않는 이모지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1hcgE/btsMmek2qN7/PAWWaoftSPA88g4kzE6uV1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/b1hcgE/btsMmek2qN7/PAWWaoftSPA88g4kzE6uV1/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;276&quot; height=&quot;56&quot; data-filename=&quot;emoji.gif&quot; data-origin-width=&quot;92&quot; data-origin-height=&quot;56&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 유니코드 문자와, 사용자가 인식하는 문자의 단위는 다르다.&lt;br /&gt;사용자가 인식하는 문자의 단위를 &lt;code&gt;Grapheme cluster&lt;/code&gt;라고 부른다.&lt;br /&gt;이는 여러 코드 포인트의 조합이 될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결책&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot;&gt;Intl.Segmenter&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739887872429&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Intl.Segmenter - JavaScript | MDN&quot; data-og-description=&quot;The Intl.Segmenter object enables locale-sensitive text segmentation, enabling you to get meaningful items (graphemes, words or sentences) from a string.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/IE8U2/hyYfAPP1vi/TplkBHBFswGZm86df9TXxK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/IE8U2/hyYfAPP1vi/TplkBHBFswGZm86df9TXxK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&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;Intl.Segmenter - JavaScript | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Intl.Segmenter object enables locale-sensitive text segmentation, enabling you to get meaningful items (graphemes, words or sentences) from a string.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 스펙을 사용해서 이런 문제도 해결 가능하다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const segmenter = new Intl.Segmenter();
const segment = segmenter.segment(' &amp;zwj; &amp;zwj; &amp;zwj; &amp;zwj; 의 길이는?');
console.log([...segment].length); // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 &lt;a href=&quot;https://github.com/orling/grapheme-splitter&quot;&gt;https://github.com/orling/grapheme-splitter&lt;/a&gt; 같은 라이브러리를 사용할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UTF-16 코드 유닛&lt;/b&gt; - 16비트 단위로, 문자를 나타내는 Atom 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unicode Codepoint&lt;/b&gt; - UTF-16 인코딩에서는 코드 유닛 1~2개로 나타냄. 약 2&lt;sup&gt;21&lt;/sup&gt; 개&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Unicode Grapheme cluster&lt;/b&gt; - 하나 이상의 코드 포인트 조합으로, 시각적으로 하나로 인식되는 문자를 나타냄.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황마다 다르겠지만 이모지 등 Grapheme cluster 문자를 많이 쓰는 서비스에서는 글자 수를 검증하기 쉽지 않다.&lt;br /&gt;특히 길이 제한이 짧은 경우 사용자의 기대와 다른 경험을 줄 가능성이 크다.&lt;br /&gt;반대로 DB에는 일반 텍스트 기준으로 길이 제한이 걸려있는데 FE에서만 Grapheme cluster 기준으로 검증한다면 저장된 데이터가 잘리는 문제가 발생할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인식을 기준으로 글자 수 검증이 중요한 서비스라면 Grapheme Cluster 단위로 계산하는 방식을 고려해보자.&lt;br /&gt;길이의 제한은 필요하지만 꼭 글자수일 필요가 없는 경우에는 사용자에게 보여주는 제한을 &lt;code&gt;byte&lt;/code&gt;로 보여주는 방법도 있다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>emoji</category>
      <category>grapheme cluster</category>
      <category>grapheme splitter</category>
      <category>javascript</category>
      <category>segmenter</category>
      <category>Unicode</category>
      <category>utf16</category>
      <category>Validation</category>
      <category>글자 수</category>
      <category>이모지</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/107</guid>
      <comments>https://blue-tang.tistory.com/107#entry107comment</comments>
      <pubDate>Tue, 18 Feb 2025 23:16:16 +0900</pubDate>
    </item>
    <item>
      <title>더 이상 메인 에디터로 Neovim을 쓰지 않는 이유</title>
      <link>https://blue-tang.tistory.com/106</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-11-23 at 4.15.58 PM.png&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;1810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UQfx1/btsKTC1jBUB/EiHnXpKKMkRFEK6Xv9zD10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UQfx1/btsKTC1jBUB/EiHnXpKKMkRFEK6Xv9zD10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UQfx1/btsKTC1jBUB/EiHnXpKKMkRFEK6Xv9zD10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUQfx1%2FbtsKTC1jBUB%2FEiHnXpKKMkRFEK6Xv9zD10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2680&quot; height=&quot;1810&quot; data-filename=&quot;Screenshot 2024-11-23 at 4.15.58 PM.png&quot; data-origin-width=&quot;2680&quot; data-origin-height=&quot;1810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글 -&amp;nbsp;&lt;a href=&quot;https://blue-tang.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blue-tang.tistory.com/97&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732344842788&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;Neovim&quot; data-og-description=&quot;Intro 요새 코드에디터로 Neovim을 사용하려는 시도를 하고있다. Vim 자체가 익숙치 않아서 초기에 좀 어려움을 겪었다. Vim motion부터 좀 익숙해지고, 개인 프로젝트 개발할 때는 급하지 않으니 최대&quot; data-og-host=&quot;blue-tang.tistory.com&quot; data-og-source-url=&quot;https://blue-tang.tistory.com/97&quot; data-og-url=&quot;https://blue-tang.tistory.com/97&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cYfggP/hyXC9jP9GB/cfJanqviw0k5gOgoY1PtN0/img.png?width=800&amp;amp;height=494&amp;amp;face=0_0_800_494,https://scrap.kakaocdn.net/dn/bqRW1Y/hyXDmwIReL/4OHJKhBYPJ6UeAncDhyigk/img.png?width=800&amp;amp;height=494&amp;amp;face=0_0_800_494,https://scrap.kakaocdn.net/dn/cOTDMu/hyXzHo0NRM/drRCaXltKmyskwbaj2vvkK/img.png?width=1280&amp;amp;height=791&amp;amp;face=0_0_1280_791&quot;&gt;&lt;a href=&quot;https://blue-tang.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blue-tang.tistory.com/97&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cYfggP/hyXC9jP9GB/cfJanqviw0k5gOgoY1PtN0/img.png?width=800&amp;amp;height=494&amp;amp;face=0_0_800_494,https://scrap.kakaocdn.net/dn/bqRW1Y/hyXDmwIReL/4OHJKhBYPJ6UeAncDhyigk/img.png?width=800&amp;amp;height=494&amp;amp;face=0_0_800_494,https://scrap.kakaocdn.net/dn/cOTDMu/hyXzHo0NRM/drRCaXltKmyskwbaj2vvkK/img.png?width=1280&amp;amp;height=791&amp;amp;face=0_0_1280_791');&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;Neovim&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro 요새 코드에디터로 Neovim을 사용하려는 시도를 하고있다. Vim 자체가 익숙치 않아서 초기에 좀 어려움을 겪었다. Vim motion부터 좀 익숙해지고, 개인 프로젝트 개발할 때는 급하지 않으니 최대&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blue-tang.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Neovim은 약 1년이 넘는 기간동안 나의 메인 코드 에디터였다. 키보드만으로도 모든 것을 제어할 수 있는 강력한 기능과 속도는 나에게 큰 즐거움을 주었다. 그러나 시간이 지나면서, 나는 점점 Neovim을 메인 에디터로 사용하는 것에 대한 한계를 느끼게 되었다. 이번 글에서는 내가 왜 Neovim을 더 이상 메인 에디터로 사용하지 않게 되었는지에 대해 이야기해보려고 한다.&lt;/p&gt;
&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;Neovim의 장점&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;먼저, Neovim이 제공하는 뛰어난 기능들에 대해 짚고 넘어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Vim motion&lt;/b&gt;: &lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;hjkl&lt;/span&gt;&lt;/i&gt; + 기능키의 조합으로 모든 것을 제어할 수 있다는 것은 코드 작성에 있어 큰 효율성을 가져다준다. 일하면서도 게임을 하는 듯한 재미는 덤.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;다양한 플러그인&lt;/b&gt;: 대부분의 작업을, 심지어 깃 클라이언트까지도 플러그인으로 제공한다. 덕분에 이런 기능들도 키보드로 수행할 수 있어 손을 마우스로 옮길 필요가 없다. 이는 작업 흐름을 끊김없이 유지하는 데 큰 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Tmux와의 유연한 연동&lt;/b&gt;: Tmux와 결합하여 터미널 내에서 동시에 많은 세션 관리가 가능하다. 여러 프로젝트를 동시에 진행할 때 매우 유용하다. 로컬 개발 서버 등 터미널에서 작업하는 여러 기능들과의 결합이 부드럽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;빠른 시작 속도&lt;/b&gt;: 가벼운 무게 덕분에 에디터의 로딩이 매우 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&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;b&gt;Neovim의 단점&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;하지만 Neovim을 사용하면서 몇 가지 단점들도 분명히 존재했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;에디팅 외적인 부분의 부족한 UI&lt;/b&gt;: 현대적인 IDE들이 제공하는 편리한 UI가 Neovim에는 부족하다. 디버깅, 소스컨트롤 등의 기능을 이용할 때 불편함이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;그래픽적인 UI의 한계&lt;/b&gt;: 텍스트 기반의 인터페이스로 인해 이미지나 마크다운 등 그래픽적으로 표현되는 정보들을 효과적으로 전달받기 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;bull;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;마우스 부재&lt;/b&gt;: 분명 마우스로 편한 작업들이 있다. 파일을 드래그해서 한번에 옮길 때 등 종종 그 필요성이 생길 때 단점으로 다가왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;IDE와의 기능 격차&lt;/b&gt;: IDE들은 계속해서 발전하고 있는데, Neovim은 그런 발전 속도를 따라가지 못하는 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;유지보수 되지 않는 플러그인들&lt;/b&gt;: 많은 플러그인들이 시간이 지나면서 업데이트가 중단되거나 버그가 존재하는 경우가 있었고, 이는 안정성에 문제를 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;설정을 위한 Lua 학습 필요성&lt;/b&gt;: 깊이 있는 설정을 위해서는 Lua 언어를 알아야 한다. 추가적인 학습 비용이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Monorepo에서의 TS 서버 구동 어려움&lt;/b&gt;: 대규모의 Monorepo 환경에서 TypeScript 서버를 제대로 구동하기가 어려웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;큰 파일 작업 시 속도 저하&lt;/b&gt;: 큰 파일을 열 때의 최적화가 되어있지 않아 오히려 VSCode보다 느린 경우가 발생했다.&lt;/p&gt;
&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;이러한 단점들을 보완하면서도 Neovim의 장점을 유지할 수 있는 방법을 찾기 시작했다. 그리고 시도한 방식은&amp;nbsp;&lt;b&gt;VSCode의 Neovim 익스텐션&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;간편한 설치&lt;/b&gt;: VSCode 익스텐션 형태로 제공되기 때문에 설치와 설정이 간단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Neovim 플러그인 사용 가능&lt;/b&gt;: 기존에 사용하던 Neovim의 플러그인들도 일부 활용할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;남은 불편함들&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 완벽한 해결책은 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;Neovim과 VSCode의 싱크 문제&lt;/b&gt;: 두 환경 간의 싱크가 깨지는 문제가 발생했다. 이는 작업 흐름에 큰 지장을 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;버퍼 이동 오류&lt;/b&gt;: 버퍼 싱크가 깨지면서 단축키로 버퍼를 이동할 때 엉뚱한 파일로 이동하는 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제들은 사용에 꽤나 치명적인 경험이었고, 이로 인해 새로운 환경에 완전히 적응하기가 쉽지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;Neovim은 여전히 훌륭한 에디터이지만, 나의 작업 환경과 요구사항에 완벽히 부합하지는 않았다. 하지만 키보드를 통한 만족스러운 개발 경험은 Neovim 사용을 완전히 포기할 순 없게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VSCode 내에서 Neovim을 더욱 자연스럽게 사용할 수 있는 방법이 발전하기를 기원하며, 최대한 Neovim의 싱크를 깨지 않을 수 있는 방법을 찾아 볼 것이다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>extension</category>
      <category>IDE</category>
      <category>Neovim</category>
      <category>tmux</category>
      <category>vscode</category>
      <category>개발자 생산성</category>
      <category>개발자도구</category>
      <category>웹 개발</category>
      <category>코드에디터</category>
      <category>프로그래밍</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/106</guid>
      <comments>https://blue-tang.tistory.com/106#entry106comment</comments>
      <pubDate>Sat, 23 Nov 2024 16:18:09 +0900</pubDate>
    </item>
    <item>
      <title>SSE 토이 프로젝트 - 프롬프터 만들기</title>
      <link>https://blue-tang.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SSE(Server Sent Event)를 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 입력한 내용을 화면에 마크다운으로 띄워주는 프롬프터를 만들어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github: &lt;a href=&quot;https://github.com/JAAAAAEMKIM/practice/tree/main/sse-practice&quot;&gt;https://github.com/JAAAAAEMKIM/practice/tree/main/sse-practice&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ykX2R/btsIDKfABTT/rEaARpmkXjcNeelrDkqjA1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ykX2R/btsIDKfABTT/rEaARpmkXjcNeelrDkqjA1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ykX2R/btsIDKfABTT/rEaARpmkXjcNeelrDkqjA1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ykX2R/btsIDKfABTT/rEaARpmkXjcNeelrDkqjA1/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;870&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스펙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서버&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;SSE를 통해 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 클라이언트&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;Markdown을 사용해서 실시간으로 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버 개발하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 스택: Bun, Typescript&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bun 선정 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Typescript 바로 실행 가능&lt;/li&gt;
&lt;li&gt;간단한 서버 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현은 SSE 관련 내용의 bun github issue를 참고했다.&lt;br /&gt;&lt;a href=&quot;https://github.com/oven-sh/bun/issues/2443&quot;&gt;https://github.com/oven-sh/bun/issues/2443&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Bun.serve({
  port: 8080,
  fetch(req) {
    if (new URL(req.url).pathname === &quot;/sse&quot;) {
      // sse 요청 핸들링
      return sse(req);
    }
    return new Response(&quot;Not Found&quot;, { status: 404 });
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;sse&lt;/code&gt;요청 처리&lt;/h3&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;function sse(req: Request) {
  const { signal } = req;
  return new Response(

    // Stream을 응답으로 준다.
    new ReadableStream({
      start(controller) {
        eventEmitter.on(&quot;message&quot;, (data) =&amp;gt; {
          // stdin에서 입력 data를 message event에 담에 eventEmitter로 보낸다.
          // sendSseMessage 내에서 stream의 controller를 통해 data를 클라이언트로 전송한다.
          sendSseMessage(controller, data);
        });

        signal.onabort = () =&amp;gt; {
          controller.close();
        };
      },
    }),
    {
      status: 200,
      headers: {
        // SSE에 필요한 헤더 (중요)
        &quot;Content-Type&quot;: &quot;text/event-stream&quot;,
        &quot;Cache-Control&quot;: &quot;no-cache&quot;,
        Connection: &quot;keep-alive&quot;,
      },
    },
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stdin 처리&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;const prompt = &quot;Type prompt: &quot;;
process.stdout.write(prompt);

// user input을 line 단위로 읽는다.
for await (const line of console) {
  console.log(`You typed: ${line}`);
  // eventEmitter에 입력 데이터를 전달.
  eventEmitter.emit(&quot;message&quot;, line);
  process.stdout.write(prompt);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;sendSseMessage&lt;/code&gt; 구현&lt;/h3&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;function sendSseMessage(
  controller: Bun.ReadableStreamController&amp;lt;Uint8Array&amp;gt;,
  data: string,
) {
  const payload = data
    .split(&quot;\n&quot;)
      // 입력 데이터를 sse 형식에 맞게 수정 (data: [data]\n\n)
    .map((line) =&amp;gt; `data: ${line}\n\n`)
    .join(&quot;&quot;);

  controller.enqueue(Buffer.from(payload));
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라이언트 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 구현은 간단하여 짧게 넘어간다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;pnpm create vite 를 사용하여 빠른 시작&lt;/li&gt;
&lt;li&gt;Prompter 구현&lt;/li&gt;
&lt;li&gt;proxy 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { useEffect, useRef, useState } from &quot;react&quot;;

import Markdown from &quot;react-markdown&quot;;
import styles from &quot;./Prompter.module.css&quot;;

const SERVER_URL = &quot;/sse&quot;;

const Prompter = () =&amp;gt; {
  const [content, setContent] = useState(&quot;&quot;);
  const eventSourceRef = useRef&amp;lt;EventSource&amp;gt;();
  const articleRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

  useEffect(() =&amp;gt; {
    try {
      // EventSource를 통해 sse 이벤트를 받을 수 있다.
      eventSourceRef.current = new EventSource(SERVER_URL);
      eventSourceRef.current.onmessage = (ev) =&amp;gt; {

        // 기존 content와 연결해준다.
        setContent((prev) =&amp;gt; `${prev}\n${ev.data}`);
      };

      return () =&amp;gt; {
        eventSourceRef.current?.close();
      };
    } catch {
      setContent(&quot;Error&quot;);
    }
  }, []);

  useEffect(() =&amp;gt; {
    if (articleRef.current) {
      // 새로운 data가 화면에 표시될 때 보이도록 스크롤
      articleRef.current.scrollIntoView({ block: &quot;end&quot; });
    }
  }, [content]);

  return (
    &amp;lt;article className={styles.article}&amp;gt;
      &amp;lt;div ref={articleRef}&amp;gt;
        &amp;lt;Markdown&amp;gt;{content}&amp;lt;/Markdown&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/article&amp;gt;
  );
};

export default Prompter;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성하면 처음에 봤던 화면을 사용해볼 수 있다.&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>BUN</category>
      <category>nodejs</category>
      <category>react</category>
      <category>Server Sent Event</category>
      <category>SSE</category>
      <category>sse 사용법</category>
      <category>typescript</category>
      <category>웹개발</category>
      <category>웹소켓</category>
      <category>토이프로젝트</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/105</guid>
      <comments>https://blue-tang.tistory.com/105#entry105comment</comments>
      <pubDate>Thu, 18 Jul 2024 00:13:11 +0900</pubDate>
    </item>
    <item>
      <title>[React Conf 2024] Demystifying Accessibility in React Apps</title>
      <link>https://blue-tang.tistory.com/104</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/0ckOUBiuxVY&quot;&gt;https://youtu.be/0ckOUBiuxVY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=0ckOUBiuxVY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dljVox/hyWrKNWh6H/XWwOwTolbcBZjZEodJpEoK/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-video-title=&quot;React Conf 2024 Day 2&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/0ckOUBiuxVY&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React Conf 2024에서 &lt;b&gt;Demystifying Accessibility in React Apps&lt;/b&gt;라는 발표가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 발표 내용을 정리하며 React 앱에서 접근성을 어떻게 구현할 수 있는지 알아본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&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;Accessibility&lt;/b&gt;: 장애가 있는 사람들도 동일하게 접근하고 사용할 수 있도록 하는 것.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실생활 예시&lt;/b&gt;: 휠체어 경사로, 점자 블록 등.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디지털 환경&lt;/b&gt;: 스크린 리더 등이 해당됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접근성의 동작 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 문서는 HTML로 받아 DOM 트리로 파싱하고 UI로 사용자에게 보여진다.&lt;/li&gt;
&lt;li&gt;이 때, DOM 트리를 기반으로 &lt;b&gt;접근성 트리(Accessibility Tree)&lt;/b&gt;가 생성된다.&lt;/li&gt;
&lt;li&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;2000&quot; data-origin-height=&quot;1119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/benbj5/btsIm3TThrA/t4PcAJxz6PU13ucm1TlxPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/benbj5/btsIm3TThrA/t4PcAJxz6PU13ucm1TlxPK/img.png&quot; data-alt=&quot;출처: 하단 발표 자료 링크&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/benbj5/btsIm3TThrA/t4PcAJxz6PU13ucm1TlxPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbenbj5%2FbtsIm3TThrA%2Ft4PcAJxz6PU13ucm1TlxPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1119&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1119&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;2000&quot; data-origin-height=&quot;1888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ICJVz/btsImDH2cuz/0UDHZ8g4XCPqjZubAtEyt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ICJVz/btsImDH2cuz/0UDHZ8g4XCPqjZubAtEyt0/img.png&quot; data-alt=&quot;개발자 도구 접근성 트리 캡쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ICJVz/btsImDH2cuz/0UDHZ8g4XCPqjZubAtEyt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FICJVz%2FbtsImDH2cuz%2F0UDHZ8g4XCPqjZubAtEyt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;1888&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1888&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개발자 도구 접근성 트리 캡쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM 노드처럼 접근성 tree의 노드도 name, role, focusable, description 등 다양한 속성을 가진다.&lt;/li&gt;
&lt;li&gt;이 트리를 통해 보조 기술(Assistive Technology)이 동작한다.&lt;/li&gt;
&lt;li&gt;ex) 스크린 리더는 접근성 트리를 순회하며 각 노드의 정보를 읽는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Semantic HTML이 중요한 이유는 여기에 있다.&lt;/li&gt;
&lt;li&gt;모든 요소가 &amp;lt;div&amp;gt;로만 구성되어 있다면 각 노드의 의미를 알기 힘들어지고 접근성이 떨어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ARIA - Accessible Rich Internet Applications&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ARIA는 접근성 트리를 제어할 수 있는 도구이다.&lt;/li&gt;
&lt;li&gt;요소의 동작이나 외관을 변경하지 않고도 접근성을 향상시킬 수 있다.&lt;/li&gt;
&lt;li&gt;ex) aria-placeholder, aria-label 등&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의!&lt;br /&gt;&lt;br /&gt;대부분의 경우 기본 HTML만 잘 사용해도 충분하다.&lt;br /&gt;통계적으로 ARIA를 사용하는 곳에서 접근성 오류가 더 많이 발생한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시 1: 아이콘 버튼&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;icon + text&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;이 경우 icon을 aria-hidden을 통해 접근성 tree에서 숨길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;button&amp;gt;
  &amp;lt;span aria-hidden=&quot;true&quot;&amp;gt; &amp;lt;/span&amp;gt;
  Submit
&amp;lt;/button&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;icon만 있는 버튼&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;텍스트가 없는 아이콘 버튼 UI는 접근 가능한 이름이 없어서 스크린 리더가 &quot;button&quot;이라고만 읽는다.&lt;/li&gt;
&lt;li&gt;aria-label을 통해 이름을 줄 수 있지만 번역은 되지 않는다.&lt;/li&gt;
&lt;li&gt;대신 버튼 안에 span 태그로 텍스트를 넣고 CSS로 숨길 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const VisuallyHidden = ({ children }) =&amp;gt; (
  &amp;lt;span style={{
    position: 'absolute',
    height: '1px',
    width: '1px',
    padding: 0,
    margin: '-1px',
    overflow: 'hidden',
    clip: 'rect(0 0 0 0)',
    whiteSpace: 'nowrap'
    border: 0,
  }}&amp;gt;
    {children}
  &amp;lt;/span&amp;gt;
);

const IconButton = () =&amp;gt; (
  &amp;lt;button&amp;gt;
    &amp;lt;span aria-hidden=&quot;true&quot;&amp;gt; &amp;lt;/span&amp;gt;
    &amp;lt;VisuallyHidden&amp;gt;Submit&amp;lt;/VisuallyHidden&amp;gt;
  &amp;lt;/button&amp;gt;
);

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시 2: 텍스트 입력&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;input 만으로는 어떤 값을 입력하는지 알 수 없기 때문에 반드시 연관된 label이 필요하다.&lt;/li&gt;
&lt;li&gt;input과 label을 연결하기 위해 htmlFor 속성을 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// input의 name을 찾지못함.
&amp;lt;label&amp;gt;Username&amp;lt;/label&amp;gt;
&amp;lt;input type=&quot;text&quot; name=&quot;username&quot; /&amp;gt;

// input이 label과 연결되어 어떤 input인지 찾을 수 있음.
&amp;lt;label htmlFor={inputId}&amp;gt;Username&amp;lt;/label&amp;gt;
&amp;lt;input type=&quot;text&quot; name=&quot;username&quot; id={inputId} /&amp;gt;
&lt;/code&gt;&lt;/pre&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;라벨을 UI에서 숨기고 placeholder를 통해 노출하는 경우도 있다.&lt;/li&gt;
&lt;li&gt;이때, 아까 만든 VisuallyHidden 컴포넌트를 활용할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// as를 통해 태그 다형성 지원
const VisuallyHidden = ({ as: Tag = &quot;span&quot;, ...props }) =&amp;gt; {
  return &amp;lt;Tag className=&quot;visually-hidden&quot; {...props} /&amp;gt;;
}

&amp;lt;VisuallyHidden as=&quot;label&quot; htmlFor={inputId}&amp;gt;Username&amp;lt;/VisuallyHidden&amp;gt;
&amp;lt;input type=&quot;text&quot; name=&quot;username&quot; id={inputId} placeholder=&quot;Username&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&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;ARIA를 활용해 이 설명이 해당 인풋의 설명인 것을 명시해줄 수 있다.&lt;/li&gt;
&lt;li&gt;접근성 트리의 input 노드의 description 속성에 해당 설명이 들어가서 스크린 리더가 읽을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;label htmlFor={inputId}&amp;gt;Username&amp;lt;/label&amp;gt;
&amp;lt;input
  type=&quot;text&quot;
  name=&quot;username&quot;
  id={inputId}
  aria-describedby={hintId}
/&amp;gt;
&amp;lt;span id={hintId}&amp;gt;
  Must be between 3 and 20 characters long
&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&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;input의 오류 상태는 aria-invalid를 통해 나타내줄 수 있다.&lt;/li&gt;
&lt;li&gt;aria-invalid는 스타일링에도 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;오류 메시지또한 aria-describedby에 추가해줄 수 있다. 여러 id가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;input
  type=&quot;text&quot;
  name=&quot;username&quot;
  id={inputId}
  aria-describedby={[errorId, hintId].join(' ')}
/&amp;gt;
&amp;lt;span id={errorId}&amp;gt;
  Must be between 3 and 20 characters long
&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;.input[aria-invalid=&quot;true&quot;] {
  border-color: red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시 3: 알림&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;이때 aria-live 영역을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;페이지의 특정 부분을 라이브 영역으로 지정하면, 그 부분은 모니터링되며 스크린 리더에 의해 읽혀진다.&lt;/li&gt;
&lt;li&gt;aria-live=&quot;polite&quot;와 aria-atomic=&quot;true&quot;가 합쳐진role=&quot;status&quot; 을 사용해도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;aria-live = 
	| &quot;off&quot;
	| &quot;polite&quot; // user가 idle 한 경우. 가장 많이 사용
	| &quot;assertive&quot; // 변화 시 즉각적으로 읽기. 즉시 전달해야할 때 사용
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;// 이 영역에 변화가 생기면, screen reader에 전달되어 내용이 읽어진다.
&amp;lt;div aria-live=&quot;polite&quot;&amp;gt;
  {message}
&amp;lt;/div&amp;gt;

// 또는, 
&amp;lt;div role=&quot;status&quot;&amp;gt;
  {message}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;맺음말&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근성은 하나의 스펙트럼이다. 완벽하게 구현하지 않아도 조금씩 개선해 나가는 것이 중요하다. 접근성을 고려하여 React 앱을 개발하면 더 많은 사용자가 편리하게 이용할 수 있다. 모두가 접근 가능한 웹을 함께 만들어 나가자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;발표 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/Demystifying-accessibility-in-React-apps-15efe3ecdbc84cf59a22baaace09c629?pvs=21&quot;&gt;Demystifying accessibility in React apps&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Study/개발</category>
      <category>Accessibility</category>
      <category>Aria</category>
      <category>aria-describedby</category>
      <category>aria-invalid</category>
      <category>aria-live</category>
      <category>demystifying accessibility in react apps</category>
      <category>react</category>
      <category>react conf 2024</category>
      <category>react 앱에서의 접근성 이해하기</category>
      <category>접근성</category>
      <author>도리[Dori]</author>
      <guid isPermaLink="true">https://blue-tang.tistory.com/104</guid>
      <comments>https://blue-tang.tistory.com/104#entry104comment</comments>
      <pubDate>Wed, 3 Jul 2024 22:37:34 +0900</pubDate>
    </item>
  </channel>
</rss>