<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>딩가링가쓰는 개발 일기</title>
    <link>https://codingaring.tistory.com/</link>
    <description>코린이의 성장일기이자, 개념 복기를 위한 기록처</description>
    <language>ko</language>
    <pubDate>Wed, 26 Aug 2026 05:07:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코딩가링가링</managingEditor>
    <image>
      <title>딩가링가쓰는 개발 일기</title>
      <url>https://tistory1.daumcdn.net/tistory/5495686/attach/fac52a16838c4653bf512dfaef4e15fd</url>
      <link>https://codingaring.tistory.com</link>
    </image>
    <item>
      <title>서버가 죽어도 커피는 나와야 하니까: 매출 누락 0건을 향한 아키텍처 설계</title>
      <link>https://codingaring.tistory.com/entry/%EC%84%9C%EB%B2%84%EA%B0%80-%EC%A3%BD%EC%96%B4%EB%8F%84-%EC%BB%A4%ED%94%BC%EB%8A%94-%EB%82%98%EC%99%80%EC%95%BC-%ED%95%98%EB%8B%88%EA%B9%8C-%EB%A7%A4%EC%B6%9C-%EB%88%84%EB%9D%BD-0%EA%B1%B4%EC%9D%84-%ED%96%A5%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Electron 기반 키오스크 프로그램을 개발할 때 리스크가 컸던 부분 중 하나는 &lt;b&gt;&amp;ldquo;서버 의존도&amp;rdquo;&lt;/b&gt; 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이전 앱에서는 서버가 잠깐만 다운돼도 결제/판매 자체가 불가능해지는 구조였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 운영사 입장에서 단순한 서버 에러가 아닌 &lt;b&gt;&quot;영업 손실&quot;&lt;/b&gt; 이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해서 여러 고민을 했고, 대전제를 설정하는 것이 중요해 보였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;zzal.jpg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FfucC/dJMb99SH7E4/GRvI7oQarimoUIcb9BwAmk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FfucC/dJMb99SH7E4/GRvI7oQarimoUIcb9BwAmk/img.jpg&quot; data-alt=&quot;(출처: 핀과 제이크의 어드벤처 타임)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FfucC/dJMb99SH7E4/GRvI7oQarimoUIcb9BwAmk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFfucC%2FdJMb99SH7E4%2FGRvI7oQarimoUIcb9BwAmk%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;317&quot; data-filename=&quot;zzal.jpg&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(출처: 핀과 제이크의 어드벤처 타임)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 아이디어 &amp;mdash; &amp;ldquo;운영의 최소 단위는 서버가 아니라 디바이스 자체&amp;rdquo;&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;b&gt;&lt;i&gt;결제가 되었을 때, 음료를 정상적으로 제조하는 것 &lt;/i&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기본적인 대전제가 지켜지지 않으면 이후의 매출 기록 전송 및 조회, 원격 명령 등이 무의미하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대전제를 지키기 위해 필요한 조건이 어떤 것들이 있을지 차근차근 뜯어보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;결제가 되었을 때&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 결제가 성공하려면 기본적으로 구매할 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;상품 데이터&lt;/b&gt;&lt;/span&gt;가 있어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 실결제 : 카드사 Agent와의 통신이 가능하며, 정상 결제 응답을 수신해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 전액 포인트 사용 결제 : 포인트 기능을 활성화해둔 매장(&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;세부 설정 정보&lt;/b&gt;&lt;/span&gt;&lt;b&gt; &lt;/b&gt;필요)에서 결제 예정 금액만큼 포인트(포인트 번호 조회 및 잔여포인트 조회가 가능 = &lt;b&gt;서버 통신 &lt;/b&gt;필요)를 사용한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 매출 기록 전송은 그 다음 프로세스&lt;/p&gt;
&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;b&gt;음료를 정상적으로 제조하는 것&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 제조 레시피를 확인하기 위해서 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;상품 데이터&lt;/b&gt;&lt;/span&gt;가 있어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 장바구니에 담을 때 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;세부 설정 정보&lt;/b&gt;&lt;/span&gt;에 따라 옵션을 선택할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 커피 머신과의 포트 연결 및 보드 상태가 정상적이어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정상 동작을 위해서 필요한 데이터의 목록은 다 파악이 됐으니, 해당 데이터들을 어떻게 다룰 지 설계해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;저장 전략 이원화 &amp;mdash; &quot;데이터의 성격을 구분하자&quot;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 서버 &amp;rarr; 디바이스 (DownStream) : 로컬 스냅샷 기반의 Fallback&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매장을 운영하기 위한 &lt;b&gt;'기준 정보'&lt;/b&gt;들은 서버가 죽더라도 디바이스에 남아 운영에 사용되어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상품 정보 (레시피, 상품명, 가격 등)&lt;/li&gt;
&lt;li&gt;다크 모드, 언어 설정, 포인트 적립/사용 기능 설정 등 다양한 매장 세부 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 앱 부팅 시에 서버에서 최신 설정 데이터를 패칭하되, 성공할 때마다 해당 데이터를 로컬 JSON파일로 저장하는 전략을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만약 네트워크 문제로 서버 패칭에 실패한 경우, 앱은 에러를 띄우는 대신 이전에 저장된 가장 최근의 스냅샷 파일을 불러와 서비스 영속성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;이때 &lt;b&gt;Protobuf&lt;/b&gt; 같은 바이너리 포맷을 고려하기도 했지만, 현장 운영의 가시성, 텍스트 에디터를 통한 즉각적인 데이터 검증 등에 알맞은 포맷이 JSON 형식이라고 판단해서 JSON 파일 형태로 저장하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;UI로 상황 전달하기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 더 고려할 것이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점주의 입장에서 상품의 가격을 바꾸고 재시작하는 시점에서 서버 통신 장애가 발생했다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무런 안내 및 표시 없이 정상적으로 운영이 되면 점주는 의도한 만큼의 매출이 발생하지 않는 점에 의아해할 것이고, 더 나아가 손해배상을 요구할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 반드시 UI에서 해당 상황을 전달할 수 있도록 해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 연결이 유실되어 초기 데이터 패칭에 실패했을 경우 &lt;b&gt;붉은색의 깜박이는 아이콘이 활성화&lt;/b&gt;되고, 해당 아이콘을 클릭하면 &lt;b&gt;어느 시점&lt;/b&gt;에 다운로드 된 데이터를 사용하고 있으며 &lt;b&gt;현재 최신 데이터를 사용하고 있지 않으니 유의하라는 안내 문구&lt;/b&gt;가 표시되도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 디바이스 &amp;rarr; 서버 (UpStream) : SQLite과 Worker를 활용한 비동기 동기화&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;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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결제 성공/실패, 환불 내역, 재고 변경 등 키오스크에서 발생하는 모든 트랜잭션은 발생 즉시 로컬 SQLite 데이터베이스에 기록된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;sync_queue&lt;/b&gt;&lt;/i&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;3초&lt;/b&gt;마다 Polling 되며 가장 먼저 insert된 queue의 type에 맞는 api 패칭 시도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;failed_sync_queue&lt;/b&gt;&lt;/i&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;sync_queue에서 api 패칭 시도 한 후 실패한 queue가 이동하는 테이블로 &lt;b&gt;1분&lt;/b&gt;마다 Polling 되며 실패했던 api 패칭을 시도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;sales_records&lt;/b&gt;&lt;/i&gt;/&lt;i&gt;&lt;b&gt;sales_sync_logs&amp;nbsp;&lt;/b&gt;&lt;/i&gt; |&amp;nbsp; &lt;b&gt;refund_records&lt;/b&gt;/&lt;i&gt;&lt;b&gt;refund_sync_logs&lt;/b&gt;&lt;/i&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;매출 및 환불 기록이 발생하면 sales_records 테이블에 insert되고, 이때 트리거로 insert된 데이터를 sync_queue에 push한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 테이블과 트리거가 유기적으로 동작하며, API 호출 &amp;amp; DB 작업 등으로 메인 스레드의 속도 저하가 일어나지 않도록 Worker Threads에서 동작하도록 구조를 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 무한 재시도 하는 큐가 있지 않도록, cleanupOldQueues를 통해 오래된 큐를 자동으로 정리하여 무한으로 faled_queue가 쌓이는 것을 방지했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 단일 책임 원칙(SRP)이 가져온 구조적 이점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 위에서 언급했던 것들을 연결하기 위해서 아래와 같이 구분하여 구조를 설계했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DBWorkerManager
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;메인 프로세스와의 통신(IPC) 및 요청-응답 매칭에만 집중&lt;/li&gt;
&lt;li&gt;메인 프로세스는 DB 내부 구조를 몰라도 데이터를 주고 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SyncQueueProcessor
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;'언제, 무엇을, 어떻게 서버로 보낼 것인가' 라는 비즈니스 로직에만 집중&lt;/li&gt;
&lt;li&gt;DB쿼리 자체는 Worker에게 맡기고, 동기화의 흐름(Flow)만 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인별 Worker (Sales, Stock 등)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;도메인별로 담당하는 테이블의 쿼리만 책임&lt;/li&gt;
&lt;li&gt;새로운 하드웨어나 도메인, 기능이 추가되어도 다른 모듈을 건드리지 않고 해당 도메인 Worker를 추가하여 확장, 연결하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 책임을 최대한 직관적이고 단순하게 만들었을 때의 장점은 코드가 꼬이지 않는다는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 진행할 때 깔끔한 테스트 케이스를 도출할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;서버 장애 당일, 우리는 여전히 정상 운영 됩니다 :)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능을 구현해두고, 점점 잊어버리고 있었던 찰나---&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느날 갑자기 네이버 클라우드 플랫폼에서 장애가 발생하여 해당 서비스를 이용하여 서버를 운영 중이던 우리 서버도 뻗어버리는 상황이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 구조로 변경하기 이전에 서버 장애로 인한 매출 복구만 2주 했었던 기억이 스쳐지나가며 깜깜했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 아 맞다 나 이거 대비해서 구조 바꿨는데..? 잘 되나..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 서버가 정상적으로 복구되고 난 뒤, 매출 누락에 대한 문의가 들어올까 하고 긴장하고 있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무려&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무려&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매출 누락에 대한 문의는 0건이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패했었던 매출 기록이 failed_sync_queue에 쌓였다가 정상적으로 재시도된 것이다.&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;유레카.jpg&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlxvwJ/dJMcacol9VD/Op17ryJHInk3zi0ZtXusE0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlxvwJ/dJMcacol9VD/Op17ryJHInk3zi0ZtXusE0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlxvwJ/dJMcacol9VD/Op17ryJHInk3zi0ZtXusE0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlxvwJ%2FdJMcacol9VD%2FOp17ryJHInk3zi0ZtXusE0%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;446&quot; height=&quot;337&quot; data-filename=&quot;유레카.jpg&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 처음으로 내가 추가한 안정성 로직이 실제 회사의 프로덕트에 긍정적인 영향을 직접적으로 끼친다는 걸 느낄 수 있었던 정말 짜릿한 경험이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;마치며&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 DB와 Worker스레드를 이용해 멀티 스레딩 아키텍처를 구축해보면서 현재 내가 구현하고 있는 프로덕트, 서비스에 어떤 구조가 적합할 지 많이 고민해보는 좋은 시간이었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 코드, 좋은 아키텍처가 많지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좋은 것은 내 서비스에 맞는 아키텍처인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;  클라이언트가 똑똑해지면 서비스는 견고해진다&lt;/b&gt;&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>electron</category>
      <category>sqlite</category>
      <category>Worker</category>
      <category>worker 활용법</category>
      <category>아키텍처</category>
      <category>아키텍처 설계</category>
      <category>회고</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/139</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%84%9C%EB%B2%84%EA%B0%80-%EC%A3%BD%EC%96%B4%EB%8F%84-%EC%BB%A4%ED%94%BC%EB%8A%94-%EB%82%98%EC%99%80%EC%95%BC-%ED%95%98%EB%8B%88%EA%B9%8C-%EB%A7%A4%EC%B6%9C-%EB%88%84%EB%9D%BD-0%EA%B1%B4%EC%9D%84-%ED%96%A5%ED%95%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84#entry139comment</comments>
      <pubDate>Thu, 22 Jan 2026 17:01:23 +0900</pubDate>
    </item>
    <item>
      <title>일렉트론, 시리얼 통신 전부 처음이었던 개발자의 8개월차 회고</title>
      <link>https://codingaring.tistory.com/entry/%EC%9D%BC%EB%A0%89%ED%8A%B8%EB%A1%A0-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0-%EC%A0%84%EB%B6%80-%EC%B2%98%EC%9D%8C%EC%9D%B4%EC%97%88%EB%8D%98-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-8%EA%B0%9C%EC%9B%94%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-end=&quot;132&quot; data-start=&quot;99&quot; data-ke-size=&quot;size16&quot;&gt;정~말 다사다난한 8개월이 지났고, 적응이 안될 것만 같았던 일렉트론 + 시리얼 통신이 적응이 되었다&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;99&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 약 400대 넘게 설치되어 있는 데스크탑 앱을 마이그레이션하는 과정에서 느끼고 배운 점들을 회고 해보려고 한다.&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;99&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;99&quot; data-ke-size=&quot;size16&quot;&gt;회고는 &lt;b&gt;4L 회고&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 data-end=&quot;132&quot; data-start=&quot;99&quot;&gt;&lt;b&gt;Liked&lt;/b&gt;: 좋았던 것&lt;/li&gt;
&lt;li data-end=&quot;132&quot; data-start=&quot;99&quot;&gt;&lt;b&gt;Learned&lt;/b&gt;: 배운 것&lt;/li&gt;
&lt;li data-end=&quot;132&quot; data-start=&quot;99&quot;&gt;&lt;b&gt;Lacked&lt;/b&gt;: 부족햇던 것&lt;/li&gt;
&lt;li data-end=&quot;132&quot; data-start=&quot;99&quot;&gt;&lt;b&gt;Longed for&lt;/b&gt;: 바라는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 4가지의 L로 시작하는 것들에 대한 회고를 진행해보는 방식을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 회고를 진행하기에 앞서, 7개월 전의 내 모습과 그 동안 어떤 것들을 했는지 정리 한 후 회고를 진행해볼 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;132&quot; data-start=&quot;99&quot; data-ke-size=&quot;size23&quot;&gt;7개월 전의 나&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;765&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XL2mD/dJMcae0igfz/p33drZDgMe0UPiaSsWLyz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XL2mD/dJMcae0igfz/p33drZDgMe0UPiaSsWLyz1/img.png&quot; data-alt=&quot;얼레벌레 취업 한 개발자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XL2mD/dJMcae0igfz/p33drZDgMe0UPiaSsWLyz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXL2mD%2FdJMcae0igfz%2Fp33drZDgMe0UPiaSsWLyz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;765&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;765&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;개발자로서의 첫 걸음, 첫 회사에서 받은 첫 번째 프로젝트&lt;/li&gt;
&lt;li&gt;개발 경험은 웹 프론트엔드가 전부였던 초보 개발자&lt;/li&gt;
&lt;li&gt;UI/UX 공부하며 디자인&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;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;  우리 회사의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;가장 큰 비즈니스 모델&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;인 커피 머신 앱을 처음부터 다시 만드는 대규모 프로젝트.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀장님께서 renderer 쪽의 기술 스택을 react로 바꾸는 게 좋을 것 같다고 판단하시면서 회사의 유일한 react 개발 경험 보유자인 내가 담당하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 프로젝트는 홀로 담당하게 되었고, 이전 개발자가 돌연 퇴사해버리는 바람에 히스토리도 제대로 전달 받지 못한 채 작업을 시작해야 했다. 진짜 하늘이 무너지는 것 같은 막막함이 몰려왔지만, 일단 무작정 해당 코드를 뜯어보기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞게 하고 있는지도 모르면서 일단 electron + react 프로젝트 세팅부터 시작했는데 레퍼런스 많지 않아서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서부터&lt;/span&gt; 헤맸었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 와중에 작업 히스토리는 꼭 잘 정리해둬야겠다는 욕심이 생겼고(후임자는 나같은 고생하지 않기를 바라며...),&amp;nbsp; 나름 문서화는 자신있었기 때문에 문서화 + 개발 + 디자인까지 병행하면서 작업을 이어나갔었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 본격적으로 회고를 시작해보자 !&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;Liked _좋았던 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사용자 피드백을 들을 수 있다는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 직접 개발하고, 디자인 개편을 한 부분에 대해서 실제 카페를 운영 중이신 점주 분들의 피드백을 들을 수 있다는 점이 너무 소중한 경험이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 진행하고 나면, 점주 분들이 계신 오픈 채팅방이 있는데 그곳에서 반응이 제법 뜨겁다 ㅋㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부정적인 반응이 되었든 긍정적인 반응이 되었든 실제 유저 피드백을 듣고 의견을 수렴하고, 반영하면서 앱을 개선해나가는 과정에서 더더욱 내가 만든 앱에 애정을 가질 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앱 서비스 안정화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 리팩토링 과정에서는 코드적으로도 처음부터 작업했지만, 보드 업체와도 긴밀하게 연락을 주고 받으며 머신 동작의 안정성을 확보하기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷의 통신 속도는 어느 정도로 하는 것이 좋은지, 커피 제조 동작 프로토콜을 재차 확인하는 등 기존에 있던 프로그램보다 더욱 더 안정적으로 서비스를 제공할 수 있게 하기 위해 다방면으로 작업이 진행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 서버의 상태와는 무관하게 인터넷만 연결되어 있으면 정상적으로 운영될 수 있게 하는 보완 로직을 추가했는데 이는 다른 포스팅에서 더욱 더 자세하게 다뤄보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Learned_배운 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일렉트론이라는 새로운 기술 스택과 시리얼 통신&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크탑 앱을 만들기 위한 일렉트론이라는 기술 스택, 그리고 커피 머신이나 컵 디스펜서, 제빙기 같은 하드웨어와 통신하기 위한 시리얼 통신 및 프로토콜에 대해 배울 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일렉트론의 renderer로 react를 어떻게 사용하는지, 기존의 웹 기반 프론트엔드와 어떤 점이 다른지 직접 몸으로 부딪혀 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타부서와의 소통법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디자인 관련 수정 사항이나 플로우 관련 수정 사항은 피그마로 화면 단위를 그려서 공유하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;유저 페르소나를 정의해둔 뒤 해당 사용자를 빗대어 설명하는 것이 타팀에게 설명할 때는 도움이 된다.&lt;/li&gt;
&lt;li&gt;이전 버전에서 수정된 사항은 문서로 남겨둔 뒤, 공유하는 것이 좋다. &lt;br /&gt;- 고객 지원에서 응대할 때 해결된 에러의 경우라면 이번 업데이트로 개선되었다고 안내가 가능하기 때문&lt;/li&gt;
&lt;li&gt;쿠션어는 선택이 아닌 필수, 바쁠 수록 정신없을 수록 신경쓰자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;안좋은 코드도 봐야 리딩 실력이 는다&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 개발 공부를 할 때는 좋은 패턴, 좋은 아키텍처, 클린 코드에 대해서 공부하기 때문에 최악의 코드, 꼴도 보기 싫은 코드, 코드 짠 사람도 손 대기 싫은 코드를 볼 일이 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 실무의 코드는 달랐다... 이 코드를 누가 짰는지도 모르는 경우도 있었고, git에 올라가있지 않아서 압축 파일로 전달 받은 코드도 있었다....&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 마이그레이션의 대상이었던 기존 코드도 전역 변수가 너무 많이 사용되어 있어서 디버깅이 쉽지 않은 상태였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 해내야만 하는 상황(첫 임무 부여)이었기 때문에 숨 참고 러브 다이브하다보니 읽히기 시작했고, 마이그레이션이 끝나갈 무렵에는 다른 비슷한 코드를 보아도 훨씬 빠르게 파악할 수 있어서 스스로 코드 리딩 실력이 늘었다는게 느껴졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개발자 도구의 performance 탭의 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 빌드한 뒤에 개발자 도구를 켜서 performance 탭으로 성능을 측정해봤고, 메모리와 속도 면에서 언제 병목이 생기는지 분석해서 개선을 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 LCP, CLS 같은 개념만 알고 있었는데 이번 개선 경험을 통해서 좀 더 구체적이고 다양한 지표 측정 방법에 대해서 알 수 있었다.&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;Lacked_부족했던 것&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하드웨어 프로토콜 해석의 기초 부족&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리얼 통신을 처음 접했을 때 프로토콜 문서를 보면서도 패킷 구조나 체크섬 같은 개념을 제대로 이해하지 못한 채 로직을 작성했었다. 제대로 이해하지 못하고 작성했던 로직은 배포 이후 제조 관련 문제를 만드는 가장 큰 원인이 되었고, 해당 부분에 대한 로직을 제대로 이해한 후에 로직을 수정하고나서 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌이켜 봤을 때 &quot;우선 동작하니까, 커피 잘 뽑히니까&quot; 하면서 넘겼던 것이 가장 큰 패착이었다고 생각한다. 이번에 프로토콜 문서에 대한 이해를 확실하게 했기 때문에 앞으로 있을 신버전 하드웨어 개발도 매끄럽게 진행될 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점에 작성된 것들은 결국엔 모두 부족했던 것...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일렉트론이라는 새로운 기술 스택에 대한 경험 부족, 타부서와의 소통법, 코드 리딩 실력, 성능 측정 지표 활용 모두 결국엔 나의 부족함에서 얻을 수 있었던 배움이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Longed for_바라는 것&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;바라는 것과 목표를 동일하게 잡아보고 작성해봤다 !&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;신버전 커피머신 개발 매끄럽게 진행 시키기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 지금 제작되고 있는 기기의 다음 버전이 연구소에 설치되어 테스트가 가능한 상황이다. 지난 주에 보드 업체와 정리가 마무리가 되어서 1차적인 개발은 끝났는데 무사히 테스트를 마치고 다음 배포 일정까지 매끄럽게 진행이 되었으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;타입스크립트로 완전히 마이그레이션하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 main 프로세스 쪽은 그냥 JS를 쓰고 있고, renderer 쪽은 TS를 쓰고 있다. 이번에 1차적인 안정화도 끝났으니, main 프로세스 쪽도 타입스크립트로 점진적인 마이그레이션을 진행해보려고 한다.&lt;/p&gt;
&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 안정적으로 자리잡기까지 정시 퇴근 한 번 없이 열심히 달려왔고, 처음으로 배포되는 주에는 일요일날 출근해서 2시간도 못자면서 철야하다 토요일날 퇴근했다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 그렇게 한 번 고생해보니, 어떤 일이 터져도 그때보다는 큰 일이 아니겠지 하는 여유로움이 생긴 것 같다 ㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 앞으로의 서비스를 안정적으로 운영하기 위한 방법이 어떤 것이 있을 지 고민해보고, 타입스크립트로의 마이그레이션을 통해서 코드 퀄리티도 차근차근 올려보려고 한다.&lt;/p&gt;
&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;&quot;서버가 터졌지만 손해배상 금액 0원으로 만들 수 있었던 보완 로직&quot; &lt;/b&gt;에 대해서 작성해봐야겠다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 !&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>4l</category>
      <category>4L 회고</category>
      <category>개발자회고</category>
      <category>개발회고</category>
      <category>데스크탑앱</category>
      <category>시리얼통신</category>
      <category>일렉트론</category>
      <category>일렉트론개발자</category>
      <category>키오스크앱</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/138</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%9D%BC%EB%A0%89%ED%8A%B8%EB%A1%A0-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0-%EC%A0%84%EB%B6%80-%EC%B2%98%EC%9D%8C%EC%9D%B4%EC%97%88%EB%8D%98-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-8%EA%B0%9C%EC%9B%94%EC%B0%A8-%ED%9A%8C%EA%B3%A0#entry138comment</comments>
      <pubDate>Mon, 3 Nov 2025 22:46:48 +0900</pubDate>
    </item>
    <item>
      <title>시리얼 통신을 이해해보자</title>
      <link>https://codingaring.tistory.com/entry/%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사에서 키오스크 머신 앱 개발을 담당하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드를 리팩토링하고, electron의 renderer 부분을 react로 마이그레이션 하는 작업을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 업무를 맡은 이후로 2주 동안 마이그레이션할 프로젝트 환경을 세팅하고, 포트 연결 클래스 추상화 등을 진행했고, 다음주부터는 본격적으로 포트 연결을 통해 하드웨어와 시리얼 통신을 해나가면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리얼 통신을 하기 전에 앞서, 기존 코드의 로직들이 시리얼 통신에서 어떤 역할을 하고 왜 필요한 로직인지 이해하려면 기본적인 지식이 있어야할 것 같아서 학습하기로 마음 먹었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;시리얼 통신 (Serial Communication)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Serial : 일렬로 이어지는, 순차적인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리얼 통신은 영어 단어에서도 알 수 있듯이, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;데이터를 한 비트(bit)씩 순차적으로 전송하는 통신&lt;/span&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번에 한 줄(선)으로 데이터를 전송한다고 해서 &lt;b&gt;&quot;직렬 통신&quot;&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;예를 들어 10101011 이라는 8비트 데이터를 보낼 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직렬 통신은 1 -&amp;gt; 0 -&amp;gt; 1 -&amp;gt; 0 -&amp;gt; 1 -&amp;gt; 0 -&amp;gt; 1 -&amp;gt; 1 &lt;b&gt;순서대로 한 비트씩&lt;/b&gt; 보내고&lt;/li&gt;
&lt;li&gt;병렬 통신은 10101011 이라는 데이터를 &lt;b&gt;동시에&lt;/b&gt; 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 시리얼 통신은 아래와 같은 경우에 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키오스크 &amp;lt;-&amp;gt; 컵 디스펜서&lt;/li&gt;
&lt;li&gt;컴퓨터 &amp;lt;-&amp;gt; 아두이노&lt;/li&gt;
&lt;li&gt;POST 시스템 &amp;lt;-&amp;gt; 바코드 스캐너&lt;/li&gt;
&lt;li&gt;기계 &amp;lt;-&amp;gt; 센서&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우에는 키오스크와 기기 간의 통신에 해당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본 구성 요소&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;TX&lt;/b&gt; (Transmit) : 데이터를 보내는 쪽&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RX&lt;/b&gt; (Receive) : 데이터를 받는 쪽&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GND&lt;/b&gt; (Ground) : 기준 전압선 (공통 접지선)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 선택적으로 CTS/RTS 등 흐름제어용 선들도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 장비 간에 아래와 같이 연결되어 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TX &amp;lt;-&amp;gt; RX | RX &amp;lt;-&amp;gt; TX&lt;/li&gt;
&lt;li&gt;GND &amp;lt;-&amp;gt; GND&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;통신 설정 (초기 약속) _설정 항목&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리얼 통신을 하기 위해서는 두 기기간에 서로 약속된 설정 값을 동일하게 맞춰주는 과정이 꼭 있어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보통 기기마다 프로토콜 문서가 있어서 그거에 맞게 설정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;없으면 기존 코드 보고 유추하면 된다.......문서가 있으면 정말 좋겠지만....&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;설정 항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center;&quot;&gt;&lt;b&gt;예시 값&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;속도 (Baudrate)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;초당 전송 비트 수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;9600, 115200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;데이터 비트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;한 번에 보낼 데이터 비트 수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;패리티 비트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;오류 검사용 비트&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;없음(N), 짝수(E), 홀수(O)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;정지 비트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;전송 끝을 알리는 비트&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1, 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;흐름 제어&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;데이터를 너무 빠르게 보내지 않도록 제어&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;없음, RTS/CTS 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;패킷(Packet)이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷은 쉽게 말해서 데이터를 묶음으로 묶어서, 포장해서 보내는 거라고 생각하면 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신을 할 때 숫자만 띡 보내는 게 아니라, 형식을 정해서 상대방이 해석하고 이해할 수 있게 한 덩어리로 묶어서 보내는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;받는 곳에서(하드웨어)에서 이해할 수 있도록 적절한 형식을 갖추는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 컵 디스펜서에 &quot;컵 하나 내려줘&quot; 라는 명령을 보내고 싶다면 아래와 같은 형식으로 보낼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5116%; text-align: center;&quot;&gt;[시작 표시]&lt;/td&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;[명령]&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;[데이터]&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;[끝 표시]&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;[체크섬]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5116%; text-align: center;&quot;&gt;0x02&lt;/td&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;0x01&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;0x01&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;0x03&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;0x07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5116%; text-align: center;&quot;&gt;STX(Start of Text) = 시작&lt;/td&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;CMD 컵 내려줘&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;DATA 컵 개수 = 1&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;ETX(End of Text) = 끝&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center;&quot;&gt;오류 검사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-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;체크섬 (Checksum)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷을 구성하는 요소 중에 체크섬이라는 요소가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크섬이 무엇이고 어떻게 사용되길래 오류를 검사할 수 있는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이해한 바를 정리해보자면,&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;우리가 보내는 패킷 요소들을 계산해서 패킷의 마지막에 붙여서 전송한다.&lt;/li&gt;
&lt;li&gt;받는 곳에서 패킷을 계산해서(미리 약속된 방식으로) 체크섬을 만들고, 실제 받은 체크섬과 직접 계산한 체크섬이 일치하는지를 비교한다.&lt;/li&gt;
&lt;li&gt;일치하면 값이 올바르게 송신, 수신된 것이고 아니면 중간에 값이 깨진 것으로 판단한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전기 신호로 데이터를 보내다보면 중간에 1이 0으로 바뀌거나 반대로 바뀌는 경우가 생길 수도 있기 때문에 데이터가 올바르게 전달되었는지 확인하기 위해 체크섬이 필요한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체크섬을 만드는 방법은 여러 가지가 있고, 약속하기 나름이지만 대표적으로 아래와 같은 방법들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;STX, CMD + DATA + ETX 값을 전부 더해서 1바이트 (0~255)로 만들기&lt;/li&gt;
&lt;li&gt;총합 &amp;amp; 0xFF&lt;/li&gt;
&lt;li&gt;총합 &amp;amp; 256&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기에 따라서 체크섬을 계산하는 것이 아니라, XOR, CRC16, CRC32 등 다른 방식으로 데이터를 검증 하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 어떻게 데이터를 보내야하는 지도 알았으니, 우리가 패킷을 보낸 이후에 기기에서는 어떻게 답장을 해주는 지도 알아보자 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;응답값&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node.js 환경에서 시리얼 통신을 할 때는 serialport라는 패키지를 통해서 통신을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write()를 통해서 패킷을 전송하면, 기기가 응답을 하고, 이 응답을 on('data')를 통해 읽는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 전달되는 값은 크게 세 가지로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정상 수신 여부 확인 (ACK : 정상 수신 | NAK : 수신 에러)&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;0x02, 0x10, 0x00, 0x03, 0x15&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;체크섬 오류 및 다른 오류 코드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 내가 든 궁금증은 정상 수신 여부가 체크섬처럼 응답 데이터 앞이나 뒤에 붙어서 오는 건지 두 응답이 연속으로 오는 건지 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기에 따라 다르기는 하지만, 보통 2개로 나뉘어서 오는 경우가 가장 흔하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image 1.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bma6F7/btsNtgUjz0z/DRVDk5mNa64IUPKOBXogyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bma6F7/btsNtgUjz0z/DRVDk5mNa64IUPKOBXogyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bma6F7/btsNtgUjz0z/DRVDk5mNa64IUPKOBXogyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbma6F7%2FbtsNtgUjz0z%2FDRVDk5mNa64IUPKOBXogyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;431&quot; data-filename=&quot;image 1.png&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷 : &quot;어이 형씨 컵 좀 내려주쇼&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정상 수신 여부 : &quot;확인이요~ (ACK)&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 응답 데이터 : &quot;컵 잘 내려줬습니다~&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나눠서 보내면 &lt;b&gt;명령 수신 확인&lt;/b&gt;과 &lt;b&gt;작업 결과&lt;/b&gt;를 구분해서 처리할 수 있어서 안정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간혹 간단한 장비나 커스텀 프로토콜에서는 하나의 성공 패킷으로 처리하는 경우도 있다고는 하니, 사용하는 기기의 프로토콜 문서를 확인하는 것이 가장 정확하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선은 코드 리딩을 하면서 궁금했던 지점들을 중점적으로 이론을 알아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에는 포트를 찾고 연결해서 패킷을 보내고, 기기가 동작하는 과정에서 또 다른 재미를 느끼고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 실제 코드를 다루지는 않았지만, 추후에 내 머릿속에 electron과 시리얼 통신, 이벤트 핸들링, 비동기 처리까지 정리가 되면 코드를 통해서 다시 한 번 같은 내용을 더 다뤄볼까 한다.&lt;/p&gt;
&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;</description>
      <category> ️ 개발 이모저모</category>
      <category>electron</category>
      <category>Serial communication</category>
      <category>SerialPort</category>
      <category>개발 공부</category>
      <category>시리얼통신</category>
      <category>일렉트론</category>
      <category>직렬 통신</category>
      <category>체크섬</category>
      <category>하드웨어 통신</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/137</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90#entry137comment</comments>
      <pubDate>Sat, 19 Apr 2025 18:38:12 +0900</pubDate>
    </item>
    <item>
      <title>비전공자가 chatgpt랑 손잡고 5일만에 SQLD 합격하기</title>
      <link>https://codingaring.tistory.com/entry/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90%EA%B0%80-chatgpt%EB%9E%91-%EC%86%90%EC%9E%A1%EA%B3%A0-5%EC%9D%BC%EB%A7%8C%EC%97%90-SQLD-%ED%95%A9%EA%B2%A9%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;3월 8일날 SQLD 시험을 보고 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자에게&amp;nbsp; 꼭 필요한 자격증은 아니지만, 최근 회사에 들어와보니 어느 정도 데이터베이스 구조에 대한 이해를 해야 기획 및 요구사항을 파악하는데 도움이 될 것 같다는 생각이 들어서 준비하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터정의서를 보면서 어떤 데이터를 요청했을 때 조회 성능이 나빠지는지 이런 부분들을 파악하고 나면 소통할 때도 훨씬 수월할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비전공자이지만, 정보처리기사 자격증을 가지고 있고 이때 꽤 깊게 열심히 공부했어서 아직 지식이 남아있는 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5일 정도 공부했고, 다행히 합격 ! 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250330_213556378.jpg&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;3436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yFinq/btsM1a2IDju/2xynAqFNWUis728TH18ae0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yFinq/btsM1a2IDju/2xynAqFNWUis728TH18ae0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yFinq/btsM1a2IDju/2xynAqFNWUis728TH18ae0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyFinq%2FbtsM1a2IDju%2F2xynAqFNWUis728TH18ae0%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;453&quot; height=&quot;360&quot; data-filename=&quot;KakaoTalk_20250330_213556378.jpg&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;3436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 SQLD 시험을 어떻게 준비했고, 붙었는지 공유해보도록 하겠다~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SQLD가 뭔데?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.dataq.or.kr/www/sub/a_04.do&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.dataq.or.kr/www/sub/a_04.do&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741766286471&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;데이터자격검정&quot; data-og-description=&quot;데이터자격검정, 빅데이터분석기사, DAP, DAsP, SQLP, SQLD, ADP, ADsP&quot; data-og-host=&quot;www.dataq.or.kr&quot; data-og-source-url=&quot;https://www.dataq.or.kr/www/sub/a_04.do&quot; data-og-url=&quot;https://www.dataq.or.kr/www/main.do&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.dataq.or.kr/www/sub/a_04.do&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.dataq.or.kr/www/sub/a_04.do&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;데이터자격검정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터자격검정, 빅데이터분석기사, DAP, DAsP, SQLP, SQLD, ADP, ADsP&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.dataq.or.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SQL 개발자 (SQL Developer, SQLD)&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;시험 방식 : 객관식 (4지선다) _OMR로 마킹해서 제출&lt;/li&gt;
&lt;li&gt;시험 시간 : 1시간 15분&lt;/li&gt;
&lt;li&gt;합격 기준 : 100점 만점에 60점 이상인데, 각 과목별로 40% 미만이면 과락이니 주의할 것 !&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출 풀 때 그래서 내가 합격인지 궁금했어서 문제 개수별 과락 기준도 적어두겠다 (어디 적어두면 은근 편함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;과목별 과락&amp;nbsp; 기준과 탈락 기준 (문제 개수)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1과목&lt;/b&gt; 10문제 중&amp;nbsp; &lt;u&gt;7개 이상&lt;/u&gt; 틀리면 과락&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2과목&lt;/b&gt; 40문제 중 &lt;u&gt;35개 이상&lt;/u&gt; 틀리면 과락&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 &lt;b&gt;50문제 중&lt;/b&gt; &lt;u&gt;21개 이상&lt;/u&gt; 틀리면 탈락&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험에 대해서는 간단하게 알아봤으니, 그래서 어떻게 준비했는지 알아보자 :)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제집 (교재)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLD의 대표 문제집인 노랭이를 살까, 아니면 그것보다는 조금 더 쉽게 설명해놓았다는 문제집을 살까 고민을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 여러 후기에서 노랭이는 과하게 어렵다. 자격증 합격만을 위한 것이라면 굳이 이렇게까지 어려운 SQL문들을 공부할 필요는 없다. 그런 말들이 많았어서, 나는 다른 문제집을 샀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제집 이름 : 2025 SQLD 모든 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://smartstore.naver.com/dasolbooks/products/10796284485?NaPm=ct%3Dm85ev11s%7Cci%3D60c91b1119aa1f552e7cf3e8eec5a1e4e51ddffb%7Ctr%3Dboksl%7Csn%3D2403128%7Chk%3Da7648959848b361f54f2de940f7a27965837d6e3&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://smartstore.naver.com/dasolbooks/products/10796284485?NaPm=ct%3Dm85ev11s%7Cci%3D60c91b1119aa1f552e7cf3e8eec5a1e4e51ddffb%7Ctr%3Dboksl%7Csn%3D2403128%7Chk%3Da7648959848b361f54f2de940f7a27965837d6e3&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741753188761&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;2025 SQLD 모든 것 : SQLD 14일 합격 책 / 아이리포 : 재형북스&quot; data-og-description=&quot;[재형북스] 성실하게 정성을 다하여 상품을 판매하겠습니다 감사합니다&quot; data-og-host=&quot;smartstore.naver.com&quot; data-og-source-url=&quot;https://smartstore.naver.com/dasolbooks/products/10796284485?NaPm=ct%3Dm85ev11s%7Cci%3D60c91b1119aa1f552e7cf3e8eec5a1e4e51ddffb%7Ctr%3Dboksl%7Csn%3D2403128%7Chk%3Da7648959848b361f54f2de940f7a27965837d6e3&quot; data-og-url=&quot;https://smartstore.naver.com/dasolbooks/products/10796284485?&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCS1Ne/hyYqM2LN9j/iGmCL2UnIQQ5KK9YKcAcT1/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/mAWk6/hyYq0Nt8tl/t4rktv9QlwSLyv8NAGGyt1/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/cGLZB4/hyYrOy5UwG/JaWNzcVGXKbei7P7EtmIk1/img.jpg?width=510&amp;amp;height=510&amp;amp;face=0_0_510_510&quot;&gt;&lt;a href=&quot;https://smartstore.naver.com/dasolbooks/products/10796284485?NaPm=ct%3Dm85ev11s%7Cci%3D60c91b1119aa1f552e7cf3e8eec5a1e4e51ddffb%7Ctr%3Dboksl%7Csn%3D2403128%7Chk%3Da7648959848b361f54f2de940f7a27965837d6e3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://smartstore.naver.com/dasolbooks/products/10796284485?NaPm=ct%3Dm85ev11s%7Cci%3D60c91b1119aa1f552e7cf3e8eec5a1e4e51ddffb%7Ctr%3Dboksl%7Csn%3D2403128%7Chk%3Da7648959848b361f54f2de940f7a27965837d6e3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCS1Ne/hyYqM2LN9j/iGmCL2UnIQQ5KK9YKcAcT1/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/mAWk6/hyYq0Nt8tl/t4rktv9QlwSLyv8NAGGyt1/img.jpg?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000,https://scrap.kakaocdn.net/dn/cGLZB4/hyYrOy5UwG/JaWNzcVGXKbei7P7EtmIk1/img.jpg?width=510&amp;amp;height=510&amp;amp;face=0_0_510_510');&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;2025 SQLD 모든 것 : SQLD 14일 합격 책 / 아이리포 : 재형북스&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;smartstore.naver.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;1과목 공부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 문제집을 기준으로 봤을 때 개념적인 부분들은 2회독을 했다. 노션에 쭉 적어두고 계속해서 복기했는데, 이해 잘 안되고 잘 안외워지는 몇 가지는 따로 정리해서 틈날 때마다 보면서 익히려고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다른 분들이 강의를 추천해주셔서, 강의도 볼까 했었는데 솔직히 나는 큰 도움이 안됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** 아주 개인적인 견해입니다 **&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 강의랑 잘맞는다 ! 그러면 강의를 듣는 것도 괜찮지만, 나는 졸리기도 하고, 시간도 더 많이 들어간다는 느낌을 받았어서 제대로 듣지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적인 부분이다보니, 계속해서 읽고 그림과 매치시키는 연습을 많이 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2과목 공부&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2과목부터는 실질적인 SQL문에 대해서 학습하는 것인데, 내가 정보처리기사 준비를 했었어서 그런지 모르겠지만 기본적인 DDL, DCL, TCL 등에 대한 지식이 있어서 그 부분은 괜찮았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Join 연산이나 부모-자식으로 엮는 그런 명령어는 해석하기 어려웠는데 이건 정말 많이 풀어보고 해석을 보면서 체화하는 것 말고는 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출을 많이 풀다보면 빈도가 비교적 낮은 어려운 문제들이 있는데, 나는 그 경우 그냥 버리고 눈치껏 찍어보자는 마음가짐이었다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출은 교재에 있는 기출 문제만 풀었고, 풀어봤을 때 거의 합격이 나오기는 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출을 풀고, 풀면서 내가 잘 모르는 구나 싶었던 개념들을 적어두고 채점한 후에 오답노트를 쓰면서 그 개념은 두 번 틀리지 않도록 다잡아나가면서 기출을 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출을 풀기 전에 교재를 2회독 하면서 기본적으로 외워야하는 것들을 외워둔 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다보니 기출을 풀고 오답노트를 쓰는 과정에서 내가 모르는 개념이 무엇인지 확실하게 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(단순히 암기 부족으로 틀리는 문제가 없었다는 소리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;chatGPT 활용해서 공부하기&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4maOq/btsMH3bPUJR/HeoPXClmfO4KLVDIFGTap0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4maOq/btsMH3bPUJR/HeoPXClmfO4KLVDIFGTap0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4maOq/btsMH3bPUJR/HeoPXClmfO4KLVDIFGTap0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4maOq%2FbtsMH3bPUJR%2FHeoPXClmfO4KLVDIFGTap0%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;577&quot; height=&quot;325&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1과목에서 ERD를 공부할 때, 기출에서 자꾸 ERD만 나오면 잘못 해석하고 있다는 사실을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 ERD 예시를 교재에서 뽑은 후, 설명할 수 있는 모든 요소간의 관계, 기본키인지 외래키인지 등을 쭉 설명해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 chatGPT에게 이 ERD를 보여주며 설명해달라고 하고, 내가 썼던 설명과 비교해보면서 맞춰본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;+ ) IE 표기법으로 된 ERD였다면 이걸 Barker 표기법으로 바꾸면 어떻게 바뀌냐고 물어봐도 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2과목은 기출 문제에서 틀린 문제를 복기할 때 이용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 오답 노트를 통해서 이해했다고 생각했던 문제를 반복적으로 틀리는 경우가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완벽하게 이해한 것이 아니다보니, 예제와 다른 형태 또는 다른 개념과 중첩된 경우 틀리는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 관련 개념 설명과 함께 &quot;내가 풀 수 있는 문제 예제 만들어줘&quot; 하면 잘 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀고나서 다른 개념(서브 쿼리 라던가, 조인 등)을 같이 알아야 풀수 있는 문제 만들어줘 까지 해서 반복적으로 풀면 그때부터는 다음 기출에서부터의 오답률은 점점 내려가는 걸 알 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, 내가 혼자서 어떻게 공부했는지 한 번 공유해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 읽는 사람들도 다양한 생성형 AI 모델을 통해 학습에도 도움을 받아보면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;SQLD 시험을 보는 분들의 합격을 기원하며 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 !&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>ai활용해서 공부</category>
      <category>chatgpt로 학습</category>
      <category>SQLD</category>
      <category>sqld 5일 합격</category>
      <category>sqld 과락 기준 몇 문제</category>
      <category>sqld 합격컷 문제수</category>
      <category>SQLD 합격후기</category>
      <category>sqld합격 몇 문제</category>
      <category>비전공sqld</category>
      <category>비전공자 sqld 합격  후기</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/136</guid>
      <comments>https://codingaring.tistory.com/entry/%EB%B9%84%EC%A0%84%EA%B3%B5%EC%9E%90%EA%B0%80-chatgpt%EB%9E%91-%EC%86%90%EC%9E%A1%EA%B3%A0-5%EC%9D%BC%EB%A7%8C%EC%97%90-SQLD-%ED%95%A9%EA%B2%A9%ED%95%98%EA%B8%B0#entry136comment</comments>
      <pubDate>Sun, 30 Mar 2025 21:52:39 +0900</pubDate>
    </item>
    <item>
      <title>[Type Challenge] 난이도 쉬움, 진짜 쉬움?</title>
      <link>https://codingaring.tistory.com/entry/Type-Challenge-%EB%82%9C%EC%9D%B4%EB%8F%84-%EC%89%AC%EC%9B%80-%EC%A7%84%EC%A7%9C-%EC%89%AC%EC%9B%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Type Challenge를 통해서 TypeScript 실력을 기르겠노라 다짐했고, 쉬움 난이도를 다 풀어서 복기할 겸 포스팅을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글을 참조하세용 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.03.05 - [☝️ 당당한 개발자가 되기 위해] - [Type Challenge] TypeScript 실력을 길러보자 :)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741164794444&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;[Type Challenge] TypeScript 실력을 길러보자 :)&quot; data-og-description=&quot;최근에 기존 바닐라 자바스크립트, Node.js로 되어 있는 프로젝트를 TypeScript로 마이그레이션 하고 있다.그래서 지금 사용하고 있는 모듈의 Type들을 잘 정의해서 타입 안정성을 확보하고, 최대한 &quot; data-og-host=&quot;codingaring.tistory.com&quot; data-og-source-url=&quot;https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90&quot; data-og-url=&quot;https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ALL3U/hyYm8ypPco/gtqv8WM5ZPKk6IWJN8pSG0/img.png?width=800&amp;amp;height=716&amp;amp;face=0_0_800_716,https://scrap.kakaocdn.net/dn/dVfMvk/hyYmWkuY5c/7uUCZLD3RN8M0nJuKHE6B0/img.png?width=800&amp;amp;height=716&amp;amp;face=0_0_800_716,https://scrap.kakaocdn.net/dn/dcecIH/hyYmYbqNud/GDtA2HTC7lE74IWkzVcwAK/img.png?width=1061&amp;amp;height=574&amp;amp;face=0_0_1061_574&quot;&gt;&lt;a href=&quot;https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ALL3U/hyYm8ypPco/gtqv8WM5ZPKk6IWJN8pSG0/img.png?width=800&amp;amp;height=716&amp;amp;face=0_0_800_716,https://scrap.kakaocdn.net/dn/dVfMvk/hyYmWkuY5c/7uUCZLD3RN8M0nJuKHE6B0/img.png?width=800&amp;amp;height=716&amp;amp;face=0_0_800_716,https://scrap.kakaocdn.net/dn/dcecIH/hyYmYbqNud/GDtA2HTC7lE74IWkzVcwAK/img.png?width=1061&amp;amp;height=574&amp;amp;face=0_0_1061_574');&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;[Type Challenge] TypeScript 실력을 길러보자 :)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근에 기존 바닐라 자바스크립트, Node.js로 되어 있는 프로젝트를 TypeScript로 마이그레이션 하고 있다.그래서 지금 사용하고 있는 모듈의 Type들을 잘 정의해서 타입 안정성을 확보하고, 최대한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;codingaring.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;쉬움 난이도의 문제들을 풀면서 사용한 문법들을 먼저 정리하고, 문제 풀이를 통해 그 문법들을 통해서 어떻게 타입 챌린지를 풀었는지 살펴볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;쉬움 문제를 풀면서 사용한 TypeScript &amp;nbsp;문법&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;keyof 키워드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 객체 타입의 키(key) 이름만으로 구성된 유니온 타입을 생성할 때 사용&lt;/li&gt;
&lt;li&gt;주로 객체의 키를 제한하거나 특정 키의 타입을 유도할 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300159&quot; class=&quot;ada&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Person = {
  name: string;
  age: number;
  isStudent: boolean;
};

type PersonKeys = keyof Person;  // &quot;name&quot; | &quot;age&quot; | &quot;isStudent&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;363&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEhxCU/btsMCaBtpFo/GS9mrJ8iGWT6KpH0sbjyd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEhxCU/btsMCaBtpFo/GS9mrJ8iGWT6KpH0sbjyd1/img.png&quot; data-alt=&quot;keyof 사용 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEhxCU/btsMCaBtpFo/GS9mrJ8iGWT6KpH0sbjyd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEhxCU%2FbtsMCaBtpFo%2FGS9mrJ8iGWT6KpH0sbjyd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;363&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;363&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;keyof 사용 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 이렇게, 메서드나 함수들을 모아둔 객체를 정의해두고 handler 함수에 props로 메서드들만 받을 수 있게 할 때 keyof와 typeof를 같이 묶어서 사용했었다. 보시다시피, action의 type이 잘 추론되고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Mapped Types과 in 키워드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mapped Types&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 타입을 기반으로 새로운 타입을 생성할 수 있도록 하는 기능으로, 기본적으로 in 키워드를 사용해 객체 타입의 각 속성을 반복하며 변형시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;in 키워드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매핑된 타입(Mapped Types)에서 속성을 반복할 때&lt;/li&gt;
&lt;li&gt;객체에 특정 속성이 존재하는지 검사할 때 = Type Guard_타입 가드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300161&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 매핑된 타입에서의 in 키워드
type Person = {
  name: string;
  age: number;
  isStudent: boolean;
};

type ReadonlyPerson = {
  readonly [K in keyof Person]: Person[K];
};

// 타입 가드에서의 in 키워드
type Dog = {
  breed: string;
  bark: () =&amp;gt; void;
};

type Cat = {
  breed: string;
  meow: () =&amp;gt; void;
};

function makeSound(animal: Dog | Cat) {
  if (&quot;bark&quot; in animal) {
    animal.bark();  // Dog 타입으로 추론됨
  } else {
    animal.meow();  // Cat 타입으로 추론됨
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;readonly 키워드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체의 속성을 읽기 전용으로 만들 때 사용하는 키워드로, 이 키워드가 붙은 속성은 초기화 이후에 값을 변경할 수 없음&lt;/li&gt;
&lt;li&gt;readonly를 사용하는 상황
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안전성 보장 : 객체가 실수로 변경되지 않도록 해야할 때&lt;/li&gt;
&lt;li&gt;타입 안정성 : 특정 속성은 절대로 변하지 않는다는 보장을 제공해야 할&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300162&quot; class=&quot;dts&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// Readonly&amp;lt;T&amp;gt; 제네릭 유틸리티 타입으로 기존 타입을 읽기 전용으로 쉽게 변환할 수 있다.
type Person = {
  name: string;
  age: number;
};

type ReadonlyPerson = Readonly&amp;lt;Person&amp;gt;;

const person: ReadonlyPerson = {
  name: &quot;Alice&quot;,
  age: 25,
};

person.age = 26;   // ❌ 오류: 'age'는 읽기 전용입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;typeof 타입 추론&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값의 타입을 검사할 때 (JavaScript와 동일)&lt;/li&gt;
&lt;li&gt;이미 존재하는 타입을 참조할 때 (TypeScript 고유 기능)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300162&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;/** 값의 타임을 검사하는 경우
*	런타임에 변수의 타입을 문자열로 반환함 */
const num = 42;
const str = &quot;Hello&quot;;
const isActive = true;
const value = null;

console.log(typeof num);       // &quot;number&quot;
console.log(typeof str);       // &quot;string&quot;
console.log(typeof isActive);  // &quot;boolean&quot;
console.log(typeof value);     // &quot;object&quot;

/** 타입을 참조하는 경우
*	타입 재사용에 유용 */
const person = {
  name: &quot;Alice&quot;,
  age: 25,
};

type PersonType = typeof person;

const newPerson: PersonType = {
  name: &quot;Bob&quot;,
  age: 30,
};

// 매개 변수 타입 참조
function printName(name: string) {
  console.log(name);
}

type NameType = Parameters&amp;lt;typeof printName&amp;gt;[0];  // string&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;T['length'] 배열의 길이 구하기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 접근 방식을 사용해 특정 타입 T의 length 속성의 타입을 참조하는 방법&lt;/li&gt;
&lt;li&gt;객체의 특정 속성 타입을 유도하거나 사용할 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300164&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 튜플이나 배열 길이 참조 방식
type MyTuple = [string, number, boolean];
type LengthOfTuple = MyTuple['length'];  // 3

// 조건부 타입과 함께 사용하는 방식
type IsEmpty&amp;lt;T extends any[]&amp;gt; = T['length'] extends 0 ? true : false;

type Result1 = IsEmpty&amp;lt;[]&amp;gt;;          // true
type Result2 = IsEmpty&amp;lt;[number]&amp;gt;;    // false&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비슷하게 사용할 수 있는 다양한 문법들이 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;T[keyof T] : 객체 속성의 값 타입 유도&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 속성의 값 타입 유도&lt;/li&gt;
&lt;li&gt;객체의 모든 속성 값 타입을 유도할 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300165&quot; class=&quot;crmsh&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type User = {
  name: string;
  age: number;
};

type UserValues = User[keyof User];  // string | number&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;T[number] 튜플의 모든 요소 접근&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스를 하나씩 대입해 가능한 모든 타입의 유니온 타입 반환&lt;/li&gt;
&lt;li&gt;주로 배열이나 튜플의 원소 타입을 유도할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300165&quot; class=&quot;oxygene&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const tuple = ['tesla', 'model 3', 'model X'] as const;
type Elements = typeof tuple[number];
// 결과: &quot;tesla&quot; | &quot;model 3&quot; | &quot;model X&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;T[ ' prop ' ]&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 속성의 타입 유도&lt;/li&gt;
&lt;li&gt;객체의 특정 속성 타입을 직접 참조할 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300165&quot; class=&quot;rust&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Config = {
  host: string;
  port: number;
};

type HostType = Config['host'];  // string&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;T[keyof T]['&amp;nbsp; prop ' ]&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중첩된 객체에서 특정 속성의 타입을 유도할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300166&quot; class=&quot;markdown&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Data = {
  user: { name: string; age: number };
  settings: { theme: string };
};

type NameType = Data['user']['name'];  // string&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;유니온 타입 분해 (Distributive Conditional Types)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T가 유니온 타입일 때, extends는 유니온의 각 요소에 대해 개별적으로 평가됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300166&quot; class=&quot;awk&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;MyExclude&amp;lt;'a' | 'b' | 'c', 'a'&amp;gt; 
// 'a' extends 'a' ? never : 'a'  =&amp;gt; never
// 'b' extends 'a' ? never : 'b'  =&amp;gt; 'b'
// 'c' extends 'a' ? never : 'c'  =&amp;gt; 'c'
// 결과: 'b' | 'c'&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;Awaited&amp;lt;T&amp;gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise와 같은 비동기 타입의 결과값을 추출함&lt;/li&gt;
&lt;li&gt;Promise가 중첩되어 있을 경우 재귀적으로 해제&lt;/li&gt;
&lt;li&gt;then 메서드를 가진 커스텀 객체도 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300167&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyAwaited&amp;lt;T&amp;gt; = Awaited&amp;lt;T&amp;gt;

// 동작 방식
type MyAwaited&amp;lt;T&amp;gt; = T extends Promise&amp;lt;infer U&amp;gt; ? MyAwaited&amp;lt;U&amp;gt; :
                    T extends { then: (onfulfilled: (arg: infer U) =&amp;gt; any) =&amp;gt; any } ? U : 
                    T;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;infer 키워드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건부 타입 내에서 타입을 추론할 때 사용하는 키워드&lt;/li&gt;
&lt;li&gt;주로 복잡한 타입에서 부분을 추출하거나 함수의 반환 타입을 유도할 때 유용&lt;/li&gt;
&lt;li&gt;extends 절 안에서만 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1741164300167&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// 기본 문법
T extends U ? (infer R) : F

/** 함수 반환 타입 추출 */
type GetReturnType&amp;lt;T&amp;gt; = T extends (...args: any[]) =&amp;gt; infer R ? R : never;

type ExampleFunc = () =&amp;gt; string;
type Result = GetReturnType&amp;lt;ExampleFunc&amp;gt;;  // string


/** infer와 조건부 타입을 사용한 예시 */
type IsArray&amp;lt;T&amp;gt; = T extends Array&amp;lt;infer U&amp;gt; ? U : never;

type A = IsArray&amp;lt;string[]&amp;gt;;   // string
type B = IsArray&amp;lt;number[]&amp;gt;;   // number
type C = IsArray&amp;lt;string&amp;gt;;     // never


/** infer로 프로미스 타입 추출 */
type UnwrapPromise&amp;lt;T&amp;gt; = T extends Promise&amp;lt;infer U&amp;gt; ? U : T;

type A = UnwrapPromise&amp;lt;Promise&amp;lt;string&amp;gt;&amp;gt;;  // string
type B = UnwrapPromise&amp;lt;Promise&amp;lt;number&amp;gt;&amp;gt;;  // number
type C = UnwrapPromise&amp;lt;string&amp;gt;;           // string


/** infer로 배열의 첫 번째 원소 타입 추출 */
type FirstElement&amp;lt;T&amp;gt; = T extends [infer F, ...any[]] ? F : never;

type A = FirstElement&amp;lt;[string, number, boolean]&amp;gt;;  // string
type B = FirstElement&amp;lt;[]&amp;gt;;                         // never

/** infer로 클래스 인스턴스 타입 추출 */
class User {
  name: string = &quot;Alice&quot;;
}

type InstanceType&amp;lt;T&amp;gt; = T extends new (...args: any[]) =&amp;gt; infer R ? R : never;

type UserInstance = InstanceType&amp;lt;typeof User&amp;gt;;  // User&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쉬움 난이도의 문제들에서는 위와 같은 개념 및 타입 유틸리티를 알면 타입을 작성할 수 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약, 이 같은 문법들을 이해하는 것이 힘든 쌩초보라면 Type Challenge를 하기보다는 기본적인 TypeScript 학습을 하고 Challenge에 도전하는 것을 추천한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 문법까지 알아보았으니 이 문법들을 가지고 문제를 풀어보자&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;문제 풀이 (정답)&lt;/h3&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/type-challenges/type-challenges/blob/main/questions/00004-easy-pick/README.ko.md&quot;&gt;[00004] Pick&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;T에서 K 프로퍼티만 선택해 새로운 오브젝트 타입을 만드는 내장 제네릭 Pick&amp;lt;T, K&amp;gt;을 이를 사용하지 않고 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300170&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyPick&amp;lt;T, K extends keyof T &amp;gt; = {[k in K]: T[k]};&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K extends keyof T
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T의 키 중 일부를 나타내는 유니온 타입으로 K를 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[K in K]
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;K에 있는 각 키 K에 대해 반복&lt;/li&gt;
&lt;li&gt;새로운 타입의 속성으로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;T[K]
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스 형식을 통해서 원본 타입 T의 K 키에 해당하는 타입을 가져와서 새로운 타입에 매핑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/type-challenges/type-challenges/blob/main/questions/00007-easy-readonly/README.ko.md&quot;&gt;[00007] Readonly&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;T의 모든 프로퍼티를 읽기 전용(재할당 불가)으로 바꾸는 내장 제네릭 Readonly&amp;lt;T&amp;gt;를 이를 사용하지 않고 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300170&quot; class=&quot;elm&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyReadonly&amp;lt;T&amp;gt; = {
  readonly [P in keyof T] : T[P];
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;keyof T
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T의 모든 키를 나타내는 유니온 타입으로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;[P in keyof T]
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T의 모든 키를 (keyof T) P에 하나씩 대입하며 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;readonly
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;P에 해당하는 속성을 읽기 전용으로 만듬&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/type-challenges/type-challenges/blob/main/questions/00011-easy-tuple-to-object/README.ko.md&quot;&gt;[00011] Tuple to Object&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;배열(튜플)을 받아, 각 원소의 값을 key/value로 갖는 오브젝트 타입을 반환하는 타입을 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300170&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type TupleToObject&amp;lt;T extends readonly any[]&amp;gt; = {
  [K in T[number]] : K
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 위의 in과 인덱스 문법을 알면 부연 설명을 하지 않아도 되는 문제라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약에 답이 이해가 되지 않는다면 관련 문법을 한 번 더 확인해보는 것을 추천 !&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/type-challenges/type-challenges/blob/main/questions/00014-easy-first/README.ko.md&quot;&gt;[00014] First of Array&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;배열(튜플) T를 받아 첫 원소의 타입을 반환하는 제네릭 First&amp;lt;T&amp;gt;를 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300171&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type First&amp;lt;T extends unknown[]&amp;gt; = T extends [] ? never : T[0]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T extends []
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열(튜플) 형식이면 첫 원소의 타입 (T[0])&lt;/li&gt;
&lt;li&gt;아니면 never로 예외처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.ko.md&quot;&gt;[00018] Length of Tuple&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;배열(튜플)을 받아 길이를 반환하는 제네릭 Length&amp;lt;T&amp;gt;를 구현하세요&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300171&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Length&amp;lt;T extends readonly any[]&amp;gt; = T extends readonly any[] ? T['length'] : never;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이건 위에 있는 유틸리티를 사용하면 손쉽게 풀 수 있지만, 테스트 케이스에 그렇지 않은 타입이 주입되었을 때에 대한 예외 처리를 해야하는 것이 있어서 조금 더 길어졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBAsDMEFoIFEAeBjANgVwCYFNJEETSiAjATwgC0ALfRgOwHMIAKAAQC8HmWAlBADEgGyHAA2Mjy2AJaYALghlMR2JjID2TMEWF6IgCcnAHN06oAAwAqZwAujgHEGIZgKo2IgA1XAn02uIgDCGIgCPHAFKaIQAGFwFDxwBFxiEAcCcAPMcANVaDAFobAUvHowBJGwFrOhzQsPHwAHgsAGghHAD4zQB9OiEAXcerAGoHASrG4wAHJiEApUcADmohAG1rAEDW4wA5BwBSxgDpTCEAIMcAdoYAuCbNF+QBnInlKAAd8CAAlfGXsBQgAXggAWUocnAJ8gHIAQ1uIAB8IW-In19v0W5KH27KEGAwDeHxebx+REWZgmgIA4jJ5AAJbDkOwQQAvPYAGOtmEDo8nkG2Ws2BK3QdFGACtlqMNAAnFjAODAADWGjAIGAOlAEAA+vyBYKBRBADejWOsEEAKvOAHZaIKFAKQdfKFyt5EE5OnWW3Olww1wKxVKgNO9yYlG5ICVKsFEEAMouAEqHAB1LEEAAPM1O2Wq38tVcmQAWw29PkEE12wA3igAI7Ye6YEpoLboIMAXwgADM6RpfW9OCGEOSY5h8Kx9sBsPI5MtbhrNtt0PdlvsThAANpEeP4RP5ZBRmP5C5XPJ3R7g96fCG-N6PMp-MFfH5ladt1AJ+RdnuYPs63I3f4j2fjv7Dr4fafjhdFJcrtfRjf93WD5byOnKNivJjYX3kfB08HsdhCY5AQANw0GRcAEEoADE1ETTQmFPR9n1YcF30-b9zzAABdc0PU9CBABOmwAZcYgQALpsAEZrcKtb0wFAIhAUAVAnAFemiBAFeawACcbiHE8QJIkSWAMkKWpWkGSZWBgBNZYAHdv1ZdkoAY5jsVxfFCWJUllnJKkaXpRlmWWDQcHLLRVnkgjAA9OiAjCiQATluUni1P4jTBO0kS2Q5LkgA&quot;&gt;[00043] Exclude&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;T에서 U에 할당할 수 있는 타입을 제외하는 내장 제네릭 Exclude&amp;lt;T, U&amp;gt;를 이를 사용하지&amp;nbsp; 않고 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300172&quot; class=&quot;excel&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyExclude&amp;lt;T, U&amp;gt; =  T extends U ? never : T;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 직접 play ground에 들어가서 테스트 케이스를 확인해보는 걸 추천한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T extends U
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T의 각 요소가 U에 포함되는지 검사&lt;/li&gt;
&lt;li&gt;유니온 타입에서 extends는 분배 법칙처럼 작동해서 각 유니온 멤버마다 검사함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;? never : T
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T가 U에 포함된다면 never 반환, 포함되지 않는다면 T를 그대로 반환&lt;/li&gt;
&lt;li&gt;never은 빈 타입이라서 결과에서 제외 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBCMAcCcEC0ECCB3AhgSwC4FMATSZJM8kgIwE8IBZTAY23wCsIBlbAawHsAnTBAAUAAQC2TFqwDOPAZgCUEAMSAbIcADY6oAO-XuOwz8qygFdsAG1xJsAOzAkVTiIAnJwBzdDqAAU9Bo4AExiEBMGsABcYhAAYXAUPHABdGIQAQawA+hwA9xiMjAF3GIQAjxwBFxiEAZVoAaCEAJUdjElKiMwBZuwHHRpMAByYhAFtHAbtbAGJqIQBlRiEAMIazswBk6wBSxgH5PCEAIMcAdoYAuCEATlqaIAAMffUN8AB4AUQAPTHFtC3wAFWptfAA+ZYyc-KLlvYOj0-P8ZdzWjsBu0dy+nOGYxIyxBuBkJFwbwgT0OxzOFwgAF4IGs-FsZLh+HYAOaXcaQhEAJXwMlMViR9GoGBwBEIO32sNeF0uEGAwAgGKxtmxwJB4xZgBzZwAZM3MANoCbDYuyYCwQTD8XDYRjHAC6wgAFrhcNoZDM2YR8AA3AB0uF4wEkjDkfEEwEwhENmFsjCISAJJMYWO01nwu3w-GYRhkSAAjqYSYreLYkNAkAAWACs3DjyhoEFFEikbGtCjVmu1urZUtw6tMlGNjH0FqzsnkgkU-IgAHE8AAJMvRQA4gxBAC89gAY6ub5nV64Bgxjq42yY0CbHAODwYB8MAgYAOUAQAD6W+3O+3EEAN6N92KAFXnADstEEigFIOze728biArhzuynUvBETYnFnIp3UNcgG93juECADKLgAlQ4AHUsQIAAPNpKBAGAVuD6rtghwCLgEDPgA3tCYYykUewXIw6EAL4QAAZr4EAAOSiO6SDjjKxzciSwCmIqFgyFRT5QgAGhSqIbJsnI4niz4AJr8b4gnYWRLAWIQcy2KY4iUP6EDEaJUIAFqSesRibAJ+nCdyEAAD4QEpKn+pcmkIlp0C6WiBlSfphnopiOJmRAlC8LwxxOjZtnGCcFLYSW+C2HMwhRmRZKyRYxwKSI8rYopymqfwyiIiyP5ZTlti0MR3EIowmBBhSookAR+BETsuEWJsdBUlgb50jxlxFMZuIdVVuyEbgdWmDKjXNTS75iR1EAyXJSWWRl6k2QUvX9YNw1Na+tKbFpk1dV5c3WT1UDVbV2z1SNG3vvZO0eSZ5k+X5+ABYd0J9TVA2nUNDXrS1m2fkU+38ItYAqn+8EIRAgAnTYAMuMQIAF02ACM1YOAUhYCgCQLKAKgTgCvTRAgCvNYABOOABqrA4QEOhajjI46TjI078LO852rYMjoP6i68OjEDY72JNkyOY4TlOM5zggwAyH5bHYFG4JQCyEOAB6dEBuIAOBPzIOWrDmy-PU7Ts5Lo+QA&quot;&gt;[00189] Awaited&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Promise와 같은 타입에 감싸인 타입이 있을 때, 안에 감싸인 타입이 무엇인지 어떻게 알 수 있을까요?&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300172&quot; class=&quot;excel&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type MyAwaited&amp;lt;T&amp;gt; = Awaited&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;타입 유틸리티 Awaited를 알고 있는지를 묻는 문제였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBBMBsAcEC0ECSAzSyk91gRgJ4QAKAhgG4CmANhAOI0CuAzgBYDWA9hRABQABAA5l2TAC4cAlBADEgGyHAA2NyJASxoswWWToiAJycAc3VqiAHCcAuNRAAGAYUsAaCIA4ZwD7jEQDKtEQBg9gDTXAGquAUpohAAYXAUPGrABV7CEAHGsBlydcPHwDgsMsAMUtAH06IQGwewNCrDCyIQBtawBA130AOQcAUsYA6K1tAy3EAJyYqSwhABDam9DINDsAXccADmodLSMABMasMwMARUcAWbpSQwAmmwBOm2uMIQAgxwB2hgC5NyyPxTShxQiEqCABBCABeNHQAHlb2hwByMneP-HeAPigwGAECoAA9LgBjcRUAAmEHEXAg+Cun3eWHOlwgACF7o8nn0Bh8vj9-hAgSDwVQobD4YjkRB3r8sEdLJsAfRVOIABJMfCABdHADiDEEALz2ABjq9hA2OJxEIWHsgScIWxagArFi1LgtADmwDg8GA3DAIGAWlAEAA+pardarRBADejor5EEAKvOAHZaICFAKQdFptvvNEGNWgxVwwT2sDnCDjSAIeZAAdoRTSAfX7rRBADKLgBKhwAdSxBAADzg0zKdTloDJtUAFshJrxPCLlcAN4QACiAEcmP0HM3KVCIABfCDoFpcCsMgTBpBK-o0KhxrVUFjANQaNFgYMQCGiBe4gDaWG7kPETzbHZoT1DryoRO+DN+f2vf3v+57R5P-XPzwJLCvDOJMHv-5PlAB5Uq+7bvqG+BcFwM7xteDjQABqIQAAPoBdhgAAuqawLjiwSBgoeBEtMOLRrvWIIkZquKhnGTA0DQ8G3v8SbFiWECrIAMuMQIAF02ACM1bGpmWYCgFgAKAKgTgCvTRAgCvNYABOO+OKkrSrK8rAIqypqhq2q6ggwDxiwADuVAtAaXBiRAUkikpUoynKCosEqqrqpqOp6sALAwWoXBxqcEAAqsgAenRABiADgTgAnLRKtmqQ5Tlaa5ZlGiaQA&quot;&gt;[00268] If&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;조건 C, 참일 때 반환하는 타입 T, 거짓일 때 반환하는 타입 F를 받는 타입 If를 구현하세요. C는 true 또는 false이고, T와 F는 아무 타입입니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300173&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type If&amp;lt;C extends boolean, T, F&amp;gt; = C extends true ? T : F;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C extends boolean
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C는 true 또는 false라는 조건으로 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;C extends true ? T : F
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참일 때 T, 거짓일 때 F 반환할 수 있도록 조건부 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBCsDMsQLQQMIHsB2BjAhgF0iUWJMICMBPCAQQwBMAnAUyoGkGcBnNAN04GsqACgACZZrAAM-DgDYAnFk4BKCAGJANkOABsfU4GHCmEJqTEQBOTgDm6jUAFI4eOAMpYGASwAOeQBrjEAAbV9HAoAOixMXDxfCEALVcAMIcAfTohAAYXAUPGIQB2hwBKhwB1FwAXRwBxBiEAbWsAQNcANVcAOQcAUseDklMABcYhABRaIQA9xhIhAbB7AA5qAGjaOwB8xwF6pwADewAZFiDDsfEAdVYhACYHAHQ7AFrGIQAYewBfRwBFxiEAMHsANNcAXVcAfUYhAS1XACabAE6bg6whACDH0gC5733e8TkI8CncmCAASkxOABXAA2eAgAF5UOF8AAeADaAEYALr9REAJlRAD4IMBgBAmAAPP5YPBMOgQPBoCBkf4o-rYwjvXz3PEAcVceAAEiCyAUIIAXnsADHXPCAACzweHcnGeBM+WAlwQAVpxgmgGABzYBwWDAfhoMAgYBGUAQAD6VutNutEEAN6Mi3IQQAq84AdlogKUApB2W21+i0QE1GH5-WEzPDwgAq-QAqniYTgMIYwOb-X6IIAZRcygA6liCAAHnAC7jmV9abtQdcAFt3JrISH-gBvCAAUQAjiCcGD+k3SUxyRAAL4QABmDDQFYgAHIRHXEEqO2CmBgtcDgCC8K4wZwJ0ZppxayD3AvoRAUaiIFwppg98Hfv9cJxgcfEYRu2SI6322D4ehw0j0SfcQxXEcV6F8e3JeEPw7b84QjRF-1PECTzRHEQLAt9ILbaCfwieE6zQIdqQPBd+nwwi8GIpgkMZCAULQqBX17d8sK-HCERo7EMVgfoABZAOQpl+m4iA+NQ0CGPA5jPxg39EQnZEJ0EydYAnBChw7B9+jINA0AXRN+gnHjVOo+TFIgTEDJU-p1M3JgtJ0vSMAMozgNA1EzUJadOEQEk3x8-RNTAOsiQChhjzYiMMHBTsIBBegmCHVwMEpHEzRAEtSwDa5ABlxiBAAumwARmoy0tA1NUBCDxQBUCcAV6aIEAV5rAAJxsoxUlaVZXlYBFWVNUNW1XV4GARNOAAdyYBgDSNKAqtq0VxSlGU5QVTglVVdVNR1PVgG4ME11cK8Koga5AA9OiALEAHAnABOWua2sWzrlu6ta+sNY1TSAA&quot;&gt;[00533] Concat&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;JavaScript의 Array.concat 함수를 타입 시스템에서&amp;nbsp; 구현하세요. 두 타입은 인수를 받고, 인수를 왼쪽부터 concat한 새로운 배열을 반환해야 합니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300173&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Concat&amp;lt;T extends readonly any[], U extends readonly any[]&amp;gt; = [...T, ...U];&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그냥 스프레드 문법을 사용하면 되는 거였는데, 배열을 입력받을 수 있도록 extends 로 제한 해주면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;readonly는 테스트 케이스 조건에 의해서 추가해줘야 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBAcCc0QLQQJIDsDGAbArgEwFMBnSJRci0gIwE8I0cssIAKAAQGsa0CAzHDgEoIAYkA2Q4AGx0QEMATrOk0wpEaoiAJycAc3cqgApaQDdpAZQyyAlgAcALoA1xiAAMAgvMUA6c5lyEiDiIAtVwAwhwB9OiEABhcBQ8YhAHaHAEqHAHUXABdHAHEGIQBtawBA1wA1VwA5BwBSxt3CIwAFxiEAFFohAD3GQiEBsHsADmoAaR2tZHAI-QAQ2wBSmx15pLCIO0MAMHsANNcAXVcAfUYhAS1XACabAE6a3HQhACDHogC5Vh13rEihrGksCCHMiAAVzJjkAWQI0CABeVC98YgAeAG0AcgBpOREH7NH4AUSI5jw53MwIgPwA6tIALY4HCwn7GaRoaxY6Q-AC6IIAIuYAPY-AB8EGAwAgBAAHicMNYCHgINZSRAqKcHP1Bh1SLsHKsqQBxczWAASOCoKQggBeewAMdZsIAALazWSxETY0-YYVVuABWRDcpNkAHNgHBoMAOKSwCBgMpQBAAPruj2ej0QQA3o4rEhBACrzgB2WiARQCkHW6vdHXRBHcojidXth3kQPgAVOn0lloPBECCyAjSPCktBYOhYmhfQkQACqVJeledICjMc9EEAMouxQAdSxBAADzgBdx2KttvuuNO8xIyxm6zs46nADeEFBAEccANmqDGQRmRAAL4QXiyUlIuFsRMERD6gZYB7m4jAHDWa5AhPziAYaRDfMvL6kLdMtYHyruuWAfOgKY+N8-yAui4KQtC6KIiiaIgpi2K4gSIIArIQIUs0rTtBS+H-tuzLAWuAzgW8UG-DhQIgvBUIQkhyKoui6E4mgeI1j8JLkvhh4DEMxGNKRgEUaB1GQZ8XwAIzNAATM0ADMzQAKzNAAbM0ADsNa6YJhEEKJ4k7kBIFURB3iyQpEDKRAakQJpEA6RA+nNAALIJfIiSRUAAeZklWTRtlKapNaKUZbQmf5y5kRZlFgdZqbfHZDkqTWcnRURcWBeRlnJaFaZfAue41ku0gqj8Tg-PuPnCbFYkBQlwVFTJJVUKSpK3li4WORp2l6TWvkmc0o2mS1EmFdJNklcZ-VOS5bkeVy3W9WgDX8pN8XTUls2pV8o2LYNrnDeNjU5U1ZkFftKW0ZV1W1fuFUFkWJZlhWT11XuW1+c1u1BTN92yUuhbFqW5YQFVcLPeVzSPbDP1-ddU1A3dxVpVlEAAD72SjO35YlUkgyVcm4-ZWUE3lrXA5jXwMEwNY4LmfCeKy1MA0TbUHbRLOELw7N4DWjNYJzYD4s2I6jhAiyADLjECABdNgAjNdLbbjmAoCkFSgCoE4Ar00QIArzWAATjWTKmqGpajqwB6gaxqmhaVrwMAWJEAA7gQsi2vaUC6wbSoquqmrarqRD6kaJpmpa1rAEQPVPmSaAHBAVKLIAHp0QJogA4E4AJy2B5bIc22HduR47doOk6QA&quot;&gt;[00898] Includes&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;JavaScript의 Array.includes 함수를 타입 시스템에서 구현하세요. 타입은 두 인수를 받고, true 또는 false를 반환해야 합니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300173&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Includes&amp;lt;T extends readonly any[], U&amp;gt; = T extends [infer First, ...infer Rest] ? Equal&amp;lt;First, U&amp;gt; extends true ? true : Includes&amp;lt;Rest, U&amp;gt; : false;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T extends [infer First, ...infer Rest] :
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T가 비어있지 않으면 첫 번째 요소를 First에, 나머지를 Rest에 할당&lt;/li&gt;
&lt;li&gt;비어있으면 false 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Equal&amp;lt;First, U&amp;gt; extends true :
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;First와 U가 같은 타입인지 비교&lt;/li&gt;
&lt;li&gt;같으면 true 반환, 아니면 나머지(Rest)에 대해 재귀 호출&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;Rest가 비어있을 때까지 반복&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBDMAMCsB2CBaCAFArgZwBaVRSOIICMBPCAKwEsBDAOwHM9GIAKAAVsZd0YC2AUwAudAJQQAxIBshwANj0ugCcldcmAJStEQBOTgDm6NUAAYBBFWoB0ABxy4jgDXGIgHAnAJI2BazoiAQnsAgE4BFxiIA2tYAga4Aaq4Acg4ApYxaGEIAQY4A7QwBcMUapIuRWQtgAxko0ViIE6ZkQAEpZmAA2IhAAvBi2ADwA2gCMADQQAEwAup0A5ND9AHwQwMAQbZ1dA0M9BKlGMaMA4jQiABKYpIALo4A4gxCALz2ADHWJELgiIlbYieMiObgWVNgWAPZKTMBwSMAA1q9gEDADSgCAAfQhkKhkIggBvR447CCAFXnADstEEAoeOAUg7wdDcWCIECNMUhA08I0ACqdACqo3qjHUYFBeNxEEAMouAEqHAB1LEEAAPOAF3H2TjmTDCTQBFZ3jViRAAN4QACiAEdMHRKp15QAPTLZGoAXwgADMlK8BBB+pxiShsvxKpUhMwssBMCIaJVsP0iRkSdk6NgsnVJgRNdqRI0lSrKo0sGTmn0IK1hp02j1homg1qhDqw8rVVGmlNunHBiMkx1urN+im01Bg5nQ+Hc9HcC1+q1+tMK3HSK9XnbGInJq32+WzUNOt3e0JGFX2mB5oyQELhfjACdNgBlxiCAC6bACM1S+FBOBoAIo0AqBOAV6aIIBXmsABOMhU7nS7XW7Ae7Wp4vd6fb6IYCMbAAdyEJQ-gBKBTwvE4zguK4bjuB53zeD4vgQH9sF7Z0aFeBhsGPCAV0AD06ID0JxABOWqDH1gl94OeRDPn+QFgSAA&quot;&gt;[03057] Push&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Array.push의 제네릭 버전을 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300174&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Push&amp;lt;T extends unknown[], U&amp;gt; =[...T, U]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스프레드 문법 사용 가능했던 것만 활용해서 하면 될 듯 하다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBDMAMBssIFoIFUB2BnAFgSwDMAXSFZci0gIwE8IArPAQwwHNcWIAKAAUZfY4WAWwCmRJgEoIAYkA2Q4AGx2UwBOKpjTCkZOiIAnJwBzdWqAAMAgmo0A6AK7Z8xE4A1xiIAGFwKHjEQCE9gEAnAIuMQgDa1gCBrgBqrgByDgCljVsYQgBBjgDtDAFyxJmlENAAOolgAxip4mSRQGdkQAEo5NgA2RBAAvOj2hEQAPADaAIwANBAATAC6vbAAfBDAwBDtsL09-QOkaSaxYwDieEQAEjZUgAujgDiDEIAvPYAMdUkQOEREmVhJE0R5OFb0WFYA9iqswHCIwADWbzAIGAWlAEAA+pCodCoRBADejJ12EEAKvOAHZaIO5AKQdEJhuPBEGBWlKoiauBarQAKr00GNGixNGAwXjcRBADKLgBKhwAdSxBAADzgBdx9k45mwwl4YSZD51YkQADeEAAogBHGxMaq9eUAD2yuTqAF8IAQVG9hBAAOQ8YnIXJCarVURsHLAGxEPDVLCmolZEm5JhYHINKakTXatpKlXVVqYMnEDpDCCdEa9LoDEaJoNa0Q61ph1WR5oxrq9QbDRNTGbxosptNQYOZ0PK3NRhxtdqmzqmou9U3QU1xqhvN52lil9r9weiFhd9uds09qvdMALRkgIXC-GAE6bADLjEEAF02AEZrV8KCSDQKQxoBUCcAr00QQCvNYACcdCZwuVxud2AD2tz1eHy+P1gwBYLAAHdRBUf5ASgC9r1Oc5LmuW57keL93k+b4EH-LBB2dPA3mwM8IHXQAPTogAxABwJwATltgl8EPfJCXhQr4ASBEEgA&quot;&gt;[03060] Unshift&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;Array.unshift의 타입 버전을 구현하세요.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1741164300174&quot; class=&quot;scala&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;type Unshift&amp;lt;T extends unknown[], U&amp;gt; = [U, ...T];&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마찬가지로 unshift가 뭔지 알고, 스프레드 사용이 되는걸 알면 아주 쉽게 풀 문제&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.typescriptlang.org/play/?#code/PQKgUABBDM0IwCYIFoIAUCGAnDBbApgC75YDOkKyV1FARgJ4S4CWAJgPZbMBe+LEACgACLDl14sAlBADEgGyHAA2OzmAOwBmJWYQCuABwA2+WbW3N9hZKrAUZtiIAnJwBzd1qIBaGwKXjEQDgTgEkbAtZ0QAAaYOATEZAA8ACoAfIGAPp0QgC7jiYA1A4CVY4Aaq4ADkxCAUqOABzUQgDa1gCBrmYAcg4ApYwB0LhCAEGOAO0MAXPWBHYTkUADG7CqkhBBq7OwQALyC2ADmcC0Qg1wq0wA0EDMI8yrauLQkkvMAbuxsEzEQAN4AvvWE9LpGAGLaKj2EzP0heKRR90aTAFl6F8wiRSBE7g92GphqNzsBgBAANozOYLQhLVbrLDTTYQba7EgAXQoHUC9XOAHFmIQABLaWiABdHADiDEEALz2ABjr5gALQiEXSkFoIro9bk1ABWpBqnGmwFgiGAAGt2GAQMBrKAIAB9HW6vW6iCAG9GOYyIIAVecAOy0QQCh44BSDu1+sdWogauskKMQJBRDB0Qg+AAHsQVKxSIIauGZoL1ip6EiidJxucMDHzpNk-QNSAHU69RBADKLgBKhwAdSxBAADzSQL2ZzOpd6uYuF0nCG7suEAAogBHbQYfRrNv+h5vCBXYZYdi4CAAciE7uQop7hmW+FIwG0730pEn1jULzeHxUsPYAlR80WqixGy2Oz2WAOEGOp2uYB3r3e-QgtGwx5xaNoo0MyZrJerYYPMk4AIKTsOd4Pqwlw3C+e7vp+3ACDBJxwU+YAtj0GCkMuEzIhQ-aDoQESdt2+gRJ62B4N6kTutCh4xGsSJnssawEjeRIxCxxEDvgbzkV2PbUcCtGggxfxMZ+WAscif7sABKhrBc6xgZBw48XxUAkYJZEUaJNGhPR4KMTCKHyXGvErGAJJgJq1a5oAJ02ADLjECABdNgAjNVW1a1g54BQOcgCoE4Ar00QIArzWAATjmRchAvL8oKwqkKKEpSjKcrwAgwDJqQADuJBKiqQUQGF7JxQlApCsAIpipK0o4plCqkEpa77t0EDnM5gAenRAjheIAJy08nyVXJal9UZcqqrqkAA&quot;&gt;[03312] Parameters&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;내장 제네릭 Parameters&amp;lt;T&amp;gt;를 이를 사용하지 않고 구현하세요&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내장 제네릭 Parameters&amp;lt;T&amp;gt;는 함수형 T를 입력받아서, 그 함수에서 사용하고 있는 arguments의 type을 만들어주는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T extends (...args : infer P) =&amp;gt; unknown
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T 가 함수형 타입인지를 확인해서 그렇지 않으면 never로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(...args : infer P)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;infer을 통해서 arguments 타입을 추론한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;아예 처음보는 유틸리티도 있었고, infer이나 in 등을 많이 사용해본 적이 없었어서 어색하기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(TypeScript Playground가 익숙치 않은 탓도 있었다..!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 복기하면서 느낀 점은 확실히 쉬운 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너 기본은 해? 이건 알아? 이 정돈 알아야지, 알아?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약간 이런 맛의 문제들이라고나 할까 아무튼 쉬움을 풀고나니 보통의 문제들은 뭐가 달라질까 궁금도 하고 기대도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭은 다뤄봤는데, 타입스크립트를 더 깊게 공부하고 싶다면 타입스크립트 챌린지를 정말 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만 ~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅은 난이도 보통 문제 풀이로 만나용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 !&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>type challenge</category>
      <category>type challenge 정답</category>
      <category>Typescript</category>
      <category>typescript공부</category>
      <category>타스 공부</category>
      <category>타스챌린지</category>
      <category>타입스크립트</category>
      <category>타입스크립트 공부</category>
      <category>타입스크립트 챌린지</category>
      <category>타입챌린지 정답</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/135</guid>
      <comments>https://codingaring.tistory.com/entry/Type-Challenge-%EB%82%9C%EC%9D%B4%EB%8F%84-%EC%89%AC%EC%9B%80-%EC%A7%84%EC%A7%9C-%EC%89%AC%EC%9B%80#entry135comment</comments>
      <pubDate>Fri, 14 Mar 2025 09:24:27 +0900</pubDate>
    </item>
    <item>
      <title>[Type Challenge] TypeScript 실력을 길러보자 :)</title>
      <link>https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 기존 바닐라 자바스크립트, Node.js로 되어 있는 프로젝트를 TypeScript로 마이그레이션 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금 사용하고 있는 모듈의 Type들을 잘 정의해서 타입 안정성을 확보하고, 최대한 런타임 이전에 오류를 잡아내야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 지금까지 TypeScript 를 사용하면서 정말 타입 안정성이 높아졌는지를 돌아보면 그렇지는 않은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 props와 api의 data schema 정도를 정의해서 IDE의 자동완성 기능만 활용하고 있었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(물론 any 범벅에 &quot;일단 써&quot; 그 정도로 막 사용하지는 않았다 나름 열심히 써보려고 했음)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Type Challenges를 통해서 TypeScript의 다양한 타입 유틸리티를 익히려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 소스 코드를 이해함에 있어서도 TypeScript 는 굉장히 큰 역할을 하기 때문에 이 기회에 TypeScript 사용에 있어 한 단계 레벨업 하는 기회가 되었으면 좋겠다.&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;Type Challeng 하는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/type-challenges/type-challenges&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/type-challenges/type-challenges&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1741153107215&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 - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge&quot; data-og-description=&quot;Collection of TypeScript type challenges with online judge - type-challenges/type-challenges&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/type-challenges/type-challenges&quot; data-og-url=&quot;https://github.com/type-challenges/type-challenges&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iqekZ/hyYmWLw4tv/KNaKICarkhGxh1PaxiGIi0/img.png?width=1921&amp;amp;height=961&amp;amp;face=0_0_1921_961,https://scrap.kakaocdn.net/dn/bEsohA/hyYmYvKQmQ/gh49hXWdfDkMaW3MAkh36k/img.png?width=1921&amp;amp;height=961&amp;amp;face=0_0_1921_961&quot;&gt;&lt;a href=&quot;https://github.com/type-challenges/type-challenges&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/type-challenges/type-challenges&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iqekZ/hyYmWLw4tv/KNaKICarkhGxh1PaxiGIi0/img.png?width=1921&amp;amp;height=961&amp;amp;face=0_0_1921_961,https://scrap.kakaocdn.net/dn/bEsohA/hyYmYvKQmQ/gh49hXWdfDkMaW3MAkh36k/img.png?width=1921&amp;amp;height=961&amp;amp;face=0_0_1921_961');&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 - type-challenges/type-challenges: Collection of TypeScript type challenges with online judge&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Collection of TypeScript type challenges with online judge - type-challenges/type-challenges&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;위의 github에 들어가서 README 파일을 연다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cam5or/btsMAkepTTU/00TRWB903pEF2Wo01zED7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cam5or/btsMAkepTTU/00TRWB903pEF2Wo01zED7k/img.png&quot; data-alt=&quot;READMD.ko.md&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cam5or/btsMAkepTTU/00TRWB903pEF2Wo01zED7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcam5or%2FbtsMAkepTTU%2F00TRWB903pEF2Wo01zED7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;725&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;READMD.ko.md&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;README를 열면 위와 같은 파일을 볼 수 있는데 빨간 네모친 부분을 누르면 (같은 형식의 버튼들 모두)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dd.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zdGXb/btsMBQpKLqm/QnZDOJyjqbzUCrwF2S1pC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zdGXb/btsMBQpKLqm/QnZDOJyjqbzUCrwF2S1pC0/img.png&quot; data-alt=&quot;문제 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zdGXb/btsMBQpKLqm/QnZDOJyjqbzUCrwF2S1pC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzdGXb%2FbtsMBQpKLqm%2FQnZDOJyjqbzUCrwF2S1pC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1061&quot; height=&quot;574&quot; data-filename=&quot;dd.png&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;574&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;이러한 문제 페이지로 이동하고, 파란색 TS도전하기 버튼을 통해 TypeScript Playground로 이동해서 올바르게 타입을 작성했는지, 여러 테스트 케이스를 통과할 수 있는 타입 정의인지 확인해볼 수 있다.&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dddd.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;937&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpLu2p/btsMB84Gcav/5Uc0B1i2S020qAwQl8Rc0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpLu2p/btsMB84Gcav/5Uc0B1i2S020qAwQl8Rc0k/img.png&quot; data-alt=&quot;테스트 케이스 추가를 통해 기여할 수도 있어서, 다양한 input을 고려해보는 것이 좋다.주&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpLu2p/btsMB84Gcav/5Uc0B1i2S020qAwQl8Rc0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpLu2p%2FbtsMB84Gcav%2F5Uc0B1i2S020qAwQl8Rc0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;713&quot; data-filename=&quot;dddd.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;937&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 케이스 추가를 통해 기여할 수도 있어서, 다양한 input을 고려해보는 것이 좋다.주&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;하지만 ! 그럼에도 뭔가 복잡할 것 같고 어려운 느낌이 난다 그래서 시도를 못하겠다 하시는 분들을 위해 Type Challenge 하는 방법을 설명해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 노션에 정리하면서 문제를 풀 예정인데, 풀면서 사용하는 타입스크립트 문법도 정리하고 정답도 정리해나가려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 풀고, 답 맞춰보고 (모를 때는 답 확인), 사용된 문법 확인하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 블로그 포스팅을 통해서 한 번 더 복기하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로세스로 Type Challenge를 풀어나갈 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 문제를 풀 예정은 아니지만, 많이 사용되는 패턴, 알아야 활용할 수 있는 유틸리티 등은 익힐 수 있는 기회가 되길 바라고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉬움 문제 풀이 포스팅으로 봐요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>typechallenge</category>
      <category>Typescript</category>
      <category>TypeScript 공부</category>
      <category>typescript공부하기</category>
      <category>타스 공부</category>
      <category>타입스크립트</category>
      <category>타입스크립트 공부</category>
      <category>타입스크립트 챌린지</category>
      <category>타입챌린지</category>
      <category>타입챌린지 하는법</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/134</guid>
      <comments>https://codingaring.tistory.com/entry/Type-Challenge-TypeScript-%EC%8B%A4%EB%A0%A5%EC%9D%84-%EA%B8%B8%EB%9F%AC%EB%B3%B4%EC%9E%90#entry134comment</comments>
      <pubDate>Wed, 5 Mar 2025 17:51:43 +0900</pubDate>
    </item>
    <item>
      <title>어엿한 프엔 개발자가 되었습니다 :)</title>
      <link>https://codingaring.tistory.com/entry/%EC%96%B4%EC%97%BF%ED%95%9C-%ED%94%84%EC%97%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 블로그를 쓰려니 기분이 이상하다...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 못해도 한 달에 한 개는 쓰려고 하는데, 최근 들어 취업하게 되면서 정신이 좀 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 취뽀한 기념으로~! 나의 취준기를 공유해볼까 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떻게 준비했지?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;시장이 안좋다&quot;, &quot;신입 역량이 점점 높아진다&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로의 취업을 준비하고 있다면 한 번은 들어봤을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 정말 많이 들었고, 들을 때마다 그러면 내가 뭘 할 수 있지? 라는 무력감만 들었었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남는게 시간이다보니, 이런저런 생각도 정말 많이 했었는데 내가 내린 결론은 &quot;내가 할 수 있는 일을 하자&quot; 였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 말을 듣는다고 내가 할 수 있는게 없는 것도 사실이지만, 그런 말을 듣는다고 내가 뭘 못하는 것도 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 '그런 말들에 흔들리지 말자'고 생각하면서 하던 작업들을 꾸준히 해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이력서와 포트폴리오&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이력서를 피그마로 작성했었다. 노션을 많이 사용하지만, 나는 내가 원할 때 줄바꿈되고 페이지가 넘어갔으면 좋겠다고 생각했기 때문에 보기에는 최대한 깔끔하게, 원하는 레이아웃을 잡아서 이력서를 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 눈에 보기에 깔끔해보이도록 신경써서 만들었고, 이후 피드백을 해주신 멘토님도 깔끔하고 예쁘다고 말해주셨다ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서는 최대한 핵심만 간단히 적으려고 했고, 포트폴리오에 그에 대한 상세한 내용을 정리해서 포트폴리오 링크를 걸었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트폴리오는 노션을 활용한 웹 하나, 피그마로 작업한 PDF 하나 해서 버전을 두 가지 만들어두었다. PDF로 열었어도 웹 포트폴리오로 넘어갈 수 있도록 링크를 걸어두었다. 그리고 포트폴리오에서는 최대한 상세하게 내가 어떤 고민의 흐름을 거쳐 그런 결론을 도출하게 되었는지 흐름을 이해할 수 있도록 적어두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 관련 자격증은 정보처리기사를 취득했다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정처기에 대해서는 많은 사람들의 평이 갈리는데, 있을 때 지원도 해보고 없을 때도 지원해본 결과.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비전공자에게는 정처기가 꽤 큰 의미를 가진다는 결론을 내렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 서류 합격으로 면접을 보았을 때도 모든 면접관이 정보처리기사를 준비하며 어떤 걸 느꼈고, 어떤 걸 배웠는지, 어렵지는 않았는지 등 질문을 많이 하셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;이력서 수정하고 정처기 취득하고 나서는 서류합격률이 정말 확 끌어올려져서 (10개 중에 4개~5개) 면접 준비를 중점적으로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;면접 준비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 면접 준비를 하면서 알고리즘 공부도 좀 하고, 면접 잡힌 회사의 기술 스택을 미리 공부해서 가기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 사실 발표나 면접을 할 때 못하는 편은 아니라서 크게 걱정은 하지 않았었고, 내 지식 수준이나 진심을 잘 조리있게 얘기할 수 있게 스스로의 경험을 계속해서 복기하는데 집중했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 진심으로 느꼈으면 툭 쳤을 때 탁 나오기는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 모든 경험들이 그렇게 극적으로 느끼면서 하는 건 아니니 복기하는 과정은 필수다.&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;키오스크 UI를 담당할 예정이고, 회사에서 사용하는 기술 스택도 공부해야 해서, 정말 바쁜 하루를 보내고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힘들게 취업한 만큼, 개구리 올챙이적 잊어버리지 말고 겸손하게 꾸준하게 성장하는 개발자가 되려고 노력할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홧팅 !&lt;/p&gt;</description>
      <category>☝️ 당당한 개발자가 되기 위해</category>
      <category>취뽀</category>
      <category>취업 후기</category>
      <category>프론트엔드 취업 후기</category>
      <category>프엔 개발자 취뽀</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/133</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%96%B4%EC%97%BF%ED%95%9C-%ED%94%84%EC%97%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4#entry133comment</comments>
      <pubDate>Tue, 18 Feb 2025 17:18:26 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 슬라이딩 윈도우</title>
      <link>https://codingaring.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%94%A9-%EC%9C%88%EB%8F%84%EC%9A%B0</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6nyX4/btsLFhUYKqM/q7KawM3KbIAiPONpmdjLNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6nyX4/btsLFhUYKqM/q7KawM3KbIAiPONpmdjLNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6nyX4/btsLFhUYKqM/q7KawM3KbIAiPONpmdjLNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6nyX4%2FbtsLFhUYKqM%2Fq7KawM3KbIAiPONpmdjLNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;533&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;슬라이딩 윈도우란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sliding window는 배열이나 리스트 같은 연속된 데이터에서 특정 구간의 값을 효율적으로 계산하거나 탐색할 때 사용하는 알고리즘 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 특정 구간을 윈도우라고 지칭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩 테스트 중에서 특정 구간의 합, 연속된 숫자의 조합 같은 키워드가 있을 때는 이 알고리즘을 생각하며 문제를 접근하는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘 기법을 사용하면 전체를 매번 계산하지 않고, 이전 결과를 사용해서 반복적으로 계산을 갱신해나갈 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1734338182571&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function slidingWindowSum(arr, k) {
  let maxSum = 0;
  let currentSum = 0;

  // 초기 윈도우 계산
  for (let i = 0; i &amp;lt; k; i++) {
    currentSum += arr[i];
  }

  maxSum = currentSum;

  // 윈도우를 오른쪽으로 이동
  for (let i = k; i &amp;lt; arr.length; i++) {
    currentSum += arr[i] - arr[i - k]; // 새로운 값을 더하고, 이전 값을 뺌
    maxSum = Math.max(maxSum, currentSum);
  }

  return maxSum;
}

console.log(slidingWindowSum([1, 3, 5, 7, 9], 3)); // 출력: 21&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, 코드를 보면 한 칸씩 구간(윈도우)를 이동하면서 계산을 하기 때문에 시간복잡도를 O(n) 수준으로 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 예시를 보면서 비교해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N개의 수로 이루어진 수열이 주어집니다. 이 수열에서 연속부분수열의 합이 특정숫자 M이 되는 경우가 몇 번 있는지 구하는 프로그램을 작성하세요. 만약 N=8, M=6이고 수열이 다음과 같다면 1 2 1 3 1 1 1 2 합이 6이 되는 연속부분수열은 {2, 1, 3}, {1, 3, 1, 1}, {3, 1, 1, 1}로 총 3가지입니다.&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;N(1&amp;le;N&amp;le;100,000), M(1&amp;le;M&amp;le;100,000,000)&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;[1, 2, 1, 3, 1, 1, 1, 2] =&amp;gt; 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 만났을 때 체크해야하는 키워드는 '&lt;b&gt;연속부분수열의 합&lt;/b&gt;' 이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 처음에 슬라이딩 윈도우 알고리즘을 몰랐기 때문에 그렇지 않은 방법으로 접근해서 풀었다.&lt;/p&gt;
&lt;pre id=&quot;code_1734339230611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(N, M, arr) {
  let count = 0;

  for (let i = 0; i &amp;lt; N; i++) {
    let sum = 0;
    for (let j = i; j &amp;lt; N; j++) {
      sum += arr[j];

      if (sum === M) {
        count++;
        break;
      }

      if (sum &amp;gt; M) {
        break;
      }
    }
  }

  return count;
}

// 입력 예제
let N = 8, M = 6;
let arr = [1, 2, 1, 3, 1, 1, 1, 2];
console.log(solution(N, M, arr)); // 출력: 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 코드 자체는 어렵지 않으리라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 반복문을 이용해서&amp;nbsp; sum이 M과 같아졌을 때 count를 더하고, sum이 M보다 커졌을 때 이중 반복문을 벗어나서 바깥 반복문으로 되돌아가는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 슬라이딩 윈도우를 사용하면 어떻게 작성할 수 있을까?&lt;/p&gt;
&lt;pre id=&quot;code_1734339350256&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(m, arr) {
  let start = 0;
  let end = 0;
  let currentSum = 0;
  let count = 0;

  while (end &amp;lt; arr.length) {
    // 윈도우 확장: end 포인터의 값을 더함
    currentSum += arr[end];

    // 현재 합이 m을 초과하면 윈도우를 축소
    while (currentSum &amp;gt; m &amp;amp;&amp;amp; start &amp;lt;= end) {
      currentSum -= arr[start];
      start += 1;
    }

    // 현재 합이 정확히 m이라면 카운트를 증가
    if (currentSum === m) {
      count += 1;
    }

    // end 포인터를 이동하여 윈도우를 확장
    end += 1;
  }

  return count;
}

let a = [1, 2, 1, 3, 1, 1, 1, 2];
console.log(solution(6, a));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서 사용되는 변수들의 값이 바뀌는 것을 풀어서 설명하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;순회과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 첫 번째 : end = 0&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[0] &amp;rarr; currentSum = 1&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 1 &amp;lt; M &amp;rarr; 윈도우 확장 (start는 그대로)&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 1&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;두 번째 : end = 1&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[1] &amp;rarr; currentSum = 3&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 현재 합 3 &amp;lt; M &amp;rarr; 윈도우 확장 (start는 그대로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 2&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;세 번째 : end = 2&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[2] &amp;rarr; currentSum = 4&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 4 &amp;lt; M &amp;rarr; 윈도우 확장 (start는 그대로)&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 3&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;네 번째 : end = 3&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[3] &amp;rarr; currentSum = 7&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 7 &amp;gt; M &amp;rarr; currentSum -= nums[0] &amp;rarr; currentSum = 6&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(윈도우 축소, start = 1)&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 currentSum === M &amp;rarr; count++ &amp;rarr; count = 1&lt;/span&gt;&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;4. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 4&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;다섯 번째 : end = 4&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[4] &amp;rarr; currentSum = 7&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 7 &amp;gt; M &amp;rarr; currentSum -= nums[1] &amp;rarr; currentSum = 5 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(윈도우 축소, start = 2)&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 5&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;여섯 번째: end = 5&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[5] &amp;rarr; currentSum = 6&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 currentSum === M &amp;rarr; count++ &amp;rarr; count = 2&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 6&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;일곱 번째 : end = 6&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;currentSum += nums[6] &amp;rarr; currentSum = 7&lt;/span&gt;&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;2. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 7 &amp;gt; M &amp;rarr; currentSum -= nums[2] &amp;rarr; currentSum = 6 &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(윈도우 축소, start = 3)&lt;/span&gt;&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;3. &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;현재 합 currentSum === M &amp;rarr; count++ &amp;rarr; count = 3&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. end 증가 &amp;rarr; end = 7&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;여덟 번째: end = 7&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. currentSum += nums[7] &amp;rarr; currentSum = 8&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 현재 합 8 &amp;gt; M &amp;rarr; currentSum -= nums[3] &amp;rarr; currentSum = 5 (윈도우 축소, start = 4)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;end 증가 &amp;rarr; end = 8 (반복 종료)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;최종 상태&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start = 4, end = 8, currentSum = 5, count = 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 순회과정을 따라가면 어떤 범위를 확인하고 범위를 좁혀가는지 알 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 처음에 알고리즘 봤을 때는 잘 이해가 가지 않았지만, 순회할 때 로그를 찍어보면서 과정을 따라가보니 확 이해가 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번의 반복만으로도 연속부분수열을 찾을 수 있다는 점에서 정말 효율적인 알고리즘인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 ~!&lt;/p&gt;</description>
      <category>  알고리즘</category>
      <category>js 슬라이딩 윈도우</category>
      <category>JS 알고리즘</category>
      <category>js로 알고리즘</category>
      <category>슬라이딩 윈도우</category>
      <category>슬라이딩 윈도우 js</category>
      <category>알고리즘</category>
      <category>알고리즘 js</category>
      <category>알고리즘 공부</category>
      <category>연속부분수열</category>
      <category>연속부분수열1</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/132</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%AC%EB%9D%BC%EC%9D%B4%EB%94%A9-%EC%9C%88%EB%8F%84%EC%9A%B0#entry132comment</comments>
      <pubDate>Sat, 4 Jan 2025 16:06:52 +0900</pubDate>
    </item>
    <item>
      <title>shadcn으로 Dropdown을 만들어보자</title>
      <link>https://codingaring.tistory.com/entry/shadcn%EC%9C%BC%EB%A1%9C-Dropdown%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 사용하는 공통 컴포넌트(Input, Dropdown 등)를 사용할 수 있도록 제공하는 shadcn에 대해서 알게되어서 소개해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github를 봤을 때, 예전에 구현햇었던 합성 컴포넌트 방식과 약간 유사하다고 생각해서 무엇인지에 대해서 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;shadcn이 무엇인가?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a title=&quot;shadcn 공식 문서&quot; href=&quot;https://ui.shadcn.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ui.shadcn.com&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1735713343556&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;shadcn/ui&quot; data-og-description=&quot;Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.&quot; data-og-host=&quot;ui.shadcn.com&quot; data-og-source-url=&quot;https://ui.shadcn.com&quot; data-og-url=&quot;https://ui.shadcn.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/m0YWI/hyXWogAeCm/1xj6nqttAugGCJUQTsTLQk/img.jpg?width=4800&amp;amp;height=2512&amp;amp;face=0_0_4800_2512,https://scrap.kakaocdn.net/dn/c7Yewj/hyXWqk9QfE/qONkVEAjUiSFS46F75AKRk/img.png?width=1280&amp;amp;height=1214&amp;amp;face=0_0_1280_1214&quot;&gt;&lt;a href=&quot;https://ui.shadcn.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ui.shadcn.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/m0YWI/hyXWogAeCm/1xj6nqttAugGCJUQTsTLQk/img.jpg?width=4800&amp;amp;height=2512&amp;amp;face=0_0_4800_2512,https://scrap.kakaocdn.net/dn/c7Yewj/hyXWqk9QfE/qONkVEAjUiSFS46F75AKRk/img.png?width=1280&amp;amp;height=1214&amp;amp;face=0_0_1280_1214');&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;shadcn/ui&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ui.shadcn.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;컴포넌트 라이브러리가 아니고, 복사&amp;amp;붙여넣을 수 있는 재사용 가능한 컴포넌트의 모음&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 스스로 정의하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 컴포넌트 라이브러리가 아니라는 것은 npm으로 다운받아 사용하는 것이 아니라는 의미이다. 코드를 복사해서 붙여넣고, 필요에 의해 수정하는 방식으로 사용하면 된다. 의존성을 추가하지 않아도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Radix UI의 저수준 컴포넌트를 활용하여 고수준 컴포넌트로 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Radix UI는 무엇일까?&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a title=&quot;Radix UI&quot; href=&quot;https://www.radix-ui.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.radix-ui.com&lt;/a&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1735796301396&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;Radix UI&quot; data-og-description=&quot;Components, icons, and colors for building high‑quality, accessible UI. Free and open-source.&quot; data-og-host=&quot;www.radix-ui.com&quot; data-og-source-url=&quot;https://www.radix-ui.com&quot; data-og-url=&quot;https://radix-ui.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bf9aOK/hyXStKMXtx/iE6j3sfQNC6v03zNpl2j3K/img.png?width=3025&amp;amp;height=1700&amp;amp;face=0_0_3025_1700&quot;&gt;&lt;a href=&quot;https://www.radix-ui.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.radix-ui.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bf9aOK/hyXStKMXtx/iE6j3sfQNC6v03zNpl2j3K/img.png?width=3025&amp;amp;height=1700&amp;amp;face=0_0_3025_1700');&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;Radix UI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Components, icons, and colors for building high‑quality, accessible UI. Free and open-source.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.radix-ui.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 된 웹 접근성을 구성으로 구현된 컴포넌트 오픈소스로,&amp;nbsp;스타일링이 최소화된 저수준의 컴포넌트들로 이루어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tailwindCSS를 기반으로 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크린 리더와 키보드 내비게이션을 기본적으로 지원하고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #09090b; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;코드로 보자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shadcn의 Dropdown 컴포넌트를 가지고 와 보았다. 사용할 때는 합성 컴포넌트를 사용하듯이 조합해서 사용하면 되고, 사용 예시를 shadcn의 웹 사이트에서 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 shadcn의 dropdown을 활용해서 내가 사용하던 dropdown을 구현해보려고 했는데, Radix UI에 대한 사전 지식이 없어서 조금 헤맸었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Radix UI에서는 open 여부를 확인할 수 있는 onOpenChange, 선택된 value를 확인하고 변경하는 value와 onValueChange 등 기본적인 Dropdown 컴포넌트의 hook을 반영할 수 있는 여러 핸들러 함수를 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github에서 확인한 Dropdown컴포넌트&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1735802793670&quot; class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;&quot;use client&quot;

import * as React from &quot;react&quot;
import * as DropdownMenuPrimitive from &quot;@radix-ui/react-dropdown-menu&quot;
import { Check, ChevronRight, Circle } from &quot;lucide-react&quot;

import { cn } from &quot;@/lib/utils&quot;

const DropdownMenu = DropdownMenuPrimitive.Root

const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger

const DropdownMenuGroup = DropdownMenuPrimitive.Group

const DropdownMenuPortal = DropdownMenuPrimitive.Portal

const DropdownMenuSub = DropdownMenuPrimitive.Sub

const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup

const DropdownMenuSubTrigger = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.SubTrigger&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.SubTrigger&amp;gt; &amp;amp; {
    inset?: boolean
  }
&amp;gt;(({ className, inset, children, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.SubTrigger
    ref={ref}
    className={cn(
      &quot;flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&amp;amp;_svg]:pointer-events-none [&amp;amp;_svg]:size-4 [&amp;amp;_svg]:shrink-0&quot;,
      inset &amp;amp;&amp;amp; &quot;pl-8&quot;,
      className
    )}
    {...props}
  &amp;gt;
    {children}
    &amp;lt;ChevronRight className=&quot;ml-auto&quot; /&amp;gt;
  &amp;lt;/DropdownMenuPrimitive.SubTrigger&amp;gt;
))
DropdownMenuSubTrigger.displayName =
  DropdownMenuPrimitive.SubTrigger.displayName

const DropdownMenuSubContent = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.SubContent&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.SubContent&amp;gt;
&amp;gt;(({ className, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.SubContent
    ref={ref}
    className={cn(
      &quot;z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2&quot;,
      className
    )}
    {...props}
  /&amp;gt;
))
DropdownMenuSubContent.displayName =
  DropdownMenuPrimitive.SubContent.displayName

const DropdownMenuContent = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.Content&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.Content&amp;gt;
&amp;gt;(({ className, sideOffset = 4, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.Portal&amp;gt;
    &amp;lt;DropdownMenuPrimitive.Content
      ref={ref}
      sideOffset={sideOffset}
      className={cn(
        &quot;z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2&quot;,
        className
      )}
      {...props}
    /&amp;gt;
  &amp;lt;/DropdownMenuPrimitive.Portal&amp;gt;
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName

const DropdownMenuItem = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.Item&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.Item&amp;gt; &amp;amp; {
    inset?: boolean
  }
&amp;gt;(({ className, inset, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.Item
    ref={ref}
    className={cn(
      &quot;relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&amp;amp;_svg]:pointer-events-none [&amp;amp;_svg]:size-4 [&amp;amp;_svg]:shrink-0&quot;,
      inset &amp;amp;&amp;amp; &quot;pl-8&quot;,
      className
    )}
    {...props}
  /&amp;gt;
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName

const DropdownMenuCheckboxItem = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.CheckboxItem&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.CheckboxItem&amp;gt;
&amp;gt;(({ className, children, checked, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.CheckboxItem
    ref={ref}
    className={cn(
      &quot;relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50&quot;,
      className
    )}
    checked={checked}
    {...props}
  &amp;gt;
    &amp;lt;span className=&quot;absolute left-2 flex h-3.5 w-3.5 items-center justify-center&quot;&amp;gt;
      &amp;lt;DropdownMenuPrimitive.ItemIndicator&amp;gt;
        &amp;lt;Check className=&quot;h-4 w-4&quot; /&amp;gt;
      &amp;lt;/DropdownMenuPrimitive.ItemIndicator&amp;gt;
    &amp;lt;/span&amp;gt;
    {children}
  &amp;lt;/DropdownMenuPrimitive.CheckboxItem&amp;gt;
))
DropdownMenuCheckboxItem.displayName =
  DropdownMenuPrimitive.CheckboxItem.displayName

const DropdownMenuRadioItem = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.RadioItem&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.RadioItem&amp;gt;
&amp;gt;(({ className, children, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.RadioItem
    ref={ref}
    className={cn(
      &quot;relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50&quot;,
      className
    )}
    {...props}
  &amp;gt;
    &amp;lt;span className=&quot;absolute left-2 flex h-3.5 w-3.5 items-center justify-center&quot;&amp;gt;
      &amp;lt;DropdownMenuPrimitive.ItemIndicator&amp;gt;
        &amp;lt;Circle className=&quot;h-2 w-2 fill-current&quot; /&amp;gt;
      &amp;lt;/DropdownMenuPrimitive.ItemIndicator&amp;gt;
    &amp;lt;/span&amp;gt;
    {children}
  &amp;lt;/DropdownMenuPrimitive.RadioItem&amp;gt;
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName

const DropdownMenuLabel = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.Label&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.Label&amp;gt; &amp;amp; {
    inset?: boolean
  }
&amp;gt;(({ className, inset, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.Label
    ref={ref}
    className={cn(
      &quot;px-2 py-1.5 text-sm font-semibold&quot;,
      inset &amp;amp;&amp;amp; &quot;pl-8&quot;,
      className
    )}
    {...props}
  /&amp;gt;
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName

const DropdownMenuSeparator = React.forwardRef&amp;lt;
  React.ElementRef&amp;lt;typeof DropdownMenuPrimitive.Separator&amp;gt;,
  React.ComponentPropsWithoutRef&amp;lt;typeof DropdownMenuPrimitive.Separator&amp;gt;
&amp;gt;(({ className, ...props }, ref) =&amp;gt; (
  &amp;lt;DropdownMenuPrimitive.Separator
    ref={ref}
    className={cn(&quot;-mx-1 my-1 h-px bg-muted&quot;, className)}
    {...props}
  /&amp;gt;
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName

const DropdownMenuShortcut = ({
  className,
  ...props
}: React.HTMLAttributes&amp;lt;HTMLSpanElement&amp;gt;) =&amp;gt; {
  return (
    &amp;lt;span
      className={cn(&quot;ml-auto text-xs tracking-widest opacity-60&quot;, className)}
      {...props}
    /&amp;gt;
  )
}
DropdownMenuShortcut.displayName = &quot;DropdownMenuShortcut&quot;

export {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuGroup,
  DropdownMenuPortal,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuRadioGroup,
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cn을 통해서 스타일 코드를 병합하고 있고, forwardRef를 이용해서 ref도 사용할 수 있도록 하고 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;shadcn의 DropdownMenuRadioGroupDemo를 활용한 Dropdown 만들기&lt;/h4&gt;
&lt;pre id=&quot;code_1735899271141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;
import { useState } from &quot;react&quot;;
import Image from &quot;next/image&quot;;
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from &quot;@/components/ui/dropdown-menu&quot;;
import smallArrowIcon from &quot;@/public/icons/smallArrow.svg&quot;;
import smallTopArrowIcon from &quot;@/public/icons/smallTopArrow.svg&quot;;

// 다루는 value의 type을 제네릭으로 받아서 여러 dropdown에 재사용 가능하도록 했다.
type DropDownProps&amp;lt;T&amp;gt; = {
  selectValue: T;
  selectOption: T[];
  handleChangeValue: (value: T) =&amp;gt; void;
};

export function DropdownMenuRadioGroupDemo&amp;lt;T = unknown&amp;gt;({
  selectValue,
  selectOption,
  handleChangeValue,
}: DropDownProps&amp;lt;T&amp;gt;) {
  // radix에서는 data-* 클래스를 이용해서 open state를 관리하고 있고, 
  //변경되는 값을 onOpenChange를 통해 가져올 수 있어, 이를 통해 화살표 image를 바꿔주고 있다.
  const [isOpen, setIsOpen] = useState(false);

  return (
    &amp;lt;DropdownMenu onOpenChange={open =&amp;gt; setIsOpen(open)}&amp;gt;
      &amp;lt;DropdownMenuTrigger asChild&amp;gt;
        &amp;lt;button
          type=&quot;button&quot;
          className={
            &quot;flex w-full justify-between rounded-md border border-solid border-gray-200 p-2.5 text-left text-sm&quot;
          }
          id=&quot;dropdown-trigger&quot;&amp;gt;
          // value의 type이 기본적으로 string으로 되어 있어서 타입 변환을 해주고 있다.
          {selectValue ? String(selectValue) : &quot;값을 선택해주세요.&quot;}
          {isOpen ? (
            &amp;lt;Image src={smallTopArrowIcon} alt=&quot;드롭다운 열기&quot; width={20} height={20} priority /&amp;gt;
          ) : (
            &amp;lt;Image src={smallArrowIcon} alt=&quot;드롭다운 닫기&quot; width={20} height={20} priority /&amp;gt;
          )}
        &amp;lt;/button&amp;gt;
      &amp;lt;/DropdownMenuTrigger&amp;gt;
      &amp;lt;DropdownMenuContent
        className=&quot;rounded-md&quot;
        style={{
          minWidth: &quot;var(--radix-dropdown-menu-trigger-width)&quot;,
        }}&amp;gt;
        &amp;lt;DropdownMenuSeparator /&amp;gt;
        &amp;lt;DropdownMenuRadioGroup value={String(selectValue)} onValueChange={value =&amp;gt; handleChangeValue(value as T)}&amp;gt;
          {selectOption.map(option =&amp;gt; (
            &amp;lt;DropdownMenuRadioItem key={String(option)} value={String(option)}&amp;gt;
              {String(option)}
            &amp;lt;/DropdownMenuRadioItem&amp;gt;
          ))}
        &amp;lt;/DropdownMenuRadioGroup&amp;gt;
      &amp;lt;/DropdownMenuContent&amp;gt;
    &amp;lt;/DropdownMenu&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 집중하다보니 타입이 안맞는 경우 as나 String(value) 등을 통해 타입을 강제로 맞춰주었지만, interface로 되어 있는 타입을 재정의 해서 제네릭을 받아갈 수 있도록 바꿔주는 것이 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jan-03-2025 22-50-12.gif&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BPYcs/btsLBYchRTs/rEMNX1ClzDsaJhHaKl5bZ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BPYcs/btsLBYchRTs/rEMNX1ClzDsaJhHaKl5bZ0/img.gif&quot; data-alt=&quot;직무 타이틀의 두 번째 드롭다운이 shadcn으로 구현한 드롭다운이다. 키보드 내비게이션으로도 선택 가능하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BPYcs/btsLBYchRTs/rEMNX1ClzDsaJhHaKl5bZ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/BPYcs/btsLBYchRTs/rEMNX1ClzDsaJhHaKl5bZ0/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;1036&quot; height=&quot;514&quot; data-filename=&quot;Jan-03-2025 22-50-12.gif&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;직무 타이틀의 두 번째 드롭다운이 shadcn으로 구현한 드롭다운이다. 키보드 내비게이션으로도 선택 가능하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt; 확실히 웹 접근성을 고려한 컴포넌트를 활용했더니, 키보드로도 선택이 자연스럽게 된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면이 아래로 많이 스크롤 되어 있으면 자동으로 위쪽으로 드롭다운이 열리는 디테일이 있었다. 개발자의 입장에서 이런 예외상황까지 고려한 UI를 가져다가 커스텀할 수 있으니 정말 좋다는 생각이 들었다.&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;shadcn의 장단점에 대해서 생각해보자&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 당장 드롭다운 하나만 가지고 사용을 해봤는데, 확실히 키보드 내비게이션을 제공한다는 점이 너무 좋았던 것 같다. 사용자의 입장에서 너무 편하고, 개발자의 입장에서 그런 것까지 고려하지 않아도 되니 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 동작하는 로직과 아주 간단한 UI가 적용된 컴포넌트를 가지고 개발하기 때문에 일관성 유지하기에도 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Radix UI에 대한 이해도가 있어야하기 때문에, shadcn과 Radix UI에 대한 학습이 필요하다. 그래서 간단한 기능만 필요하다면 불필요한 시간을 들여야할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 TailwindCSS를 원래 자주 사용하던 터라 괜찮았지만, TailwindCSS의 유틸리티 클래스를 적극적으로 활용하고 있는 라이브러리이기 때문에 TailwindCSS가 아니라면 사용이 힘들다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;총평&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 많이 사용되지만, 은근히 고려해야하는 것이 많은. 그런 컴포넌트들에 적용하면 좋을 것 같다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 제공되는 UI가 있기 때문에 이를 활용해서 디자인 시스템을 구축해보아도 좋겠다고 생각했다. 일관성도 챙기고, 우리 프로젝트에 맞는 스타일을 입히기만 하면 되기 때문이다. 그리고 동작하는 로직에서도 내가 사용할 hook을 적절하게 결합하면 되어서, 조금 학습만 한다면 충분히 사용할 가치가 있다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 직접 개발하는 것에 초점을 맞춰서 프로젝트를 진행해왔는데, 최근에는 이런 라이브러리들을 더 잘 활용하기 위한 방법에 대해서 생각해보게 되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 ~!&lt;/p&gt;</description>
      <category> ️ 개발 이모저모</category>
      <category>radix ui</category>
      <category>shadcn</category>
      <category>shadcn dropdown</category>
      <category>shadcn 드롭다운</category>
      <category>shadcn 사용법</category>
      <category>shadcn 사용예시</category>
      <category>공통 컴포넌트 라이브러리</category>
      <category>공통 컴포넌트 제공 라이브러리</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/131</guid>
      <comments>https://codingaring.tistory.com/entry/shadcn%EC%9C%BC%EB%A1%9C-Dropdown%EC%9D%84-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90#entry131comment</comments>
      <pubDate>Fri, 3 Jan 2025 23:03:47 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 두 포인터 (Two Pointer)</title>
      <link>https://codingaring.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%ED%8F%AC%EC%9D%B8%ED%84%B0-Two-Pointer</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 풀 때마다 그 문제에 적합한 알고리즘이 무엇일지 찾으면서 공부하고 있는데, 알고리즘은 참 다양한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 예제를 통해서 이 알고리즘을 어떻게 적용하는지 알아보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ALGORITHM STUDY.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DYke6/btsLl16YmK7/knoq6uEGcvolhCYq3eKj0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DYke6/btsLl16YmK7/knoq6uEGcvolhCYq3eKj0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DYke6/btsLl16YmK7/knoq6uEGcvolhCYq3eKj0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDYke6%2FbtsLl16YmK7%2Fknoq6uEGcvolhCYq3eKj0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;464&quot; data-filename=&quot;ALGORITHM STUDY.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 포인터(Two Pointers)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열이나 리스트에서 두 개의 포인터(인덱스)를 사용하여 효율적으로 문제를 해결하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 정렬된 배열이나 리스트에서 특정 조건을 만족하는 값을 찾거나, 병합, 탐색할 때 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;두 개의 포인터를 사용&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열이나 리스트의 특정 위치를 가리키는 두 개의 포인터를 사용해 문제를 해결&lt;/li&gt;
&lt;li&gt;주로 시작점과 끝점, 또는 두 배열의 각각 첫 번째 요소에서 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열 전체를 한 번만 순회하거나, 필요한 만큼만 순회하기 때문에 시간 복잡도가 O(N) 또는 O(N + M)으로 매우&amp;nbsp; 효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주로 사용하는 경우&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정렬된 배열(리스트)에서 탐색, 병합, 교집합, 합의 조건 찾기 등&lt;/li&gt;
&lt;li&gt;구간 탐색 : 부분합이 특정 값을 넘는 최단 구간 찾기(슬라이딩 윈도우와 결합)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 1 : 두 배열을 병합하는 경우&lt;/h3&gt;
&lt;pre id=&quot;code_1734418671838&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function mergeSortedArrays(arr1, arr2) {
  let p1 = 0; // 첫 번째 배열의 포인터
  let p2 = 0; // 두 번째 배열의 포인터
  let result = []; // 결과 배열

  // 두 배열이 끝날 때까지 병합
  while (p1 &amp;lt; arr1.length &amp;amp;&amp;amp; p2 &amp;lt; arr2.length) {
  	// 정렬된 배열을 만들기 위한 조건문	
    if (arr1[p1] &amp;lt; arr2[p2]) {
      result.push(arr1[p1]); // 첫 번째 배열의 값이 작으면 추가
      p1++; // 첫 번째 배열 포인터 이동
    } else {
      result.push(arr2[p2]); // 두 번째 배열의 값이 작으면 추가
      p2++; // 두 번째 배열 포인터 이동
    }
  }

  // 남은 요소 처리
  while (p1 &amp;lt; arr1.length) result.push(arr1[p1++]);
  while (p2 &amp;lt; arr2.length) result.push(arr2[p2++]);

  return result; // 병합된 배열 반환
}

// 테스트
let arr1 = [1, 3, 5];
let arr2 = [2, 3, 6, 7, 9];
console.log(mergeSortedArrays(arr1, arr2)); // [1, 2, 3, 3, 5, 6, 7, 9]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제 2 : 두 수의 합 찾기&lt;/h3&gt;
&lt;pre id=&quot;code_1734420008883&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function twoSum(arr, target) {
  let start = 0; // 시작 포인터
  let end = arr.length - 1; // 끝 포인터

  while (start &amp;lt; end) {
    const sum = arr[start] + arr[end]; // 두 포인터가 가리키는 값의 합

    if (sum === target) {
      return [arr[start], arr[end]]; // 합이 target이면 반환
    } else if (sum &amp;lt; target) {
      start++; // 합이 target보다 작으면 start를 오른쪽으로 이동
    } else {
      end--; // 합이 target보다 크면 end를 왼쪽으로 이동
    }
  }

  return null; // target이 되는 조합이 없는 경우
}

// 테스트
let arr = [1, 2, 3, 4, 6, 8, 10];
let target = 10;
console.log(twoSum(arr, target)); // [2, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점과 단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 두 포인터 알고리즘을 사용하면 시간 복잡도가 작고 추가 메모리 사용이 적기 때문에 효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 정렬된 배열에서 조건을 탐색하는데 최적화 되어 비교적 간결하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 정렬되지 않은 배열에서는 사용할 수 없거나 먼저 정렬해야 한다. 그리고 두 포인터로 맨 앞과 뒤에서부터 탐색해나갈 수 있어야하기 때문에 양방향 탐색이 가능해야 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값의 범위가 크거나, 시간 제한이 있는 문제의 경우 O(N) 또는 O(N + M)의 시간복잡도를 갖는 두 포인터 알고리즘을 적용하는 것이 적합할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 오늘은 이만~!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안뇽 !&lt;/p&gt;</description>
      <category>  알고리즘</category>
      <category>Algorithm</category>
      <category>js 알고리즘 공부</category>
      <category>two pointers</category>
      <category>두 포인터</category>
      <category>두 포인터 알고리즘</category>
      <category>두포인터</category>
      <category>알고리즘</category>
      <category>알고리즘 공부</category>
      <category>코딩 테스트</category>
      <category>코딩 테스트 알고리즘</category>
      <author>코딩가링가링</author>
      <guid isPermaLink="true">https://codingaring.tistory.com/130</guid>
      <comments>https://codingaring.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%91%90-%ED%8F%AC%EC%9D%B8%ED%84%B0-Two-Pointer#entry130comment</comments>
      <pubDate>Tue, 17 Dec 2024 16:29:16 +0900</pubDate>
    </item>
  </channel>
</rss>