<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>h-seo-n.log</title>
        <link>https://velog.io/</link>
        <description>다시해보자.</description>
        <lastBuildDate>Wed, 25 Feb 2026 08:48:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>h-seo-n.log</title>
            <url>https://velog.velcdn.com/images/h-seo-n/profile/896b7916-3204-48f1-bee2-c6e7639de151/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. h-seo-n.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/h-seo-n" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[해커톤 3등 수상 프로덕트 및 회고]]></title>
            <link>https://velog.io/@h-seo-n/%ED%95%B4%EC%BB%A4%ED%86%A4-3%EB%93%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%84%ED%96%89-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@h-seo-n/%ED%95%B4%EC%BB%A4%ED%86%A4-3%EB%93%B1-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%84%ED%96%89-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 25 Feb 2026 08:48:57 GMT</pubDate>
            <description><![CDATA[<p>겨울에 <a href="https://wafflestudio.com/">와플스튜디오</a>에서 23.5기 루키로 토이프로젝트를 진행한 이후,
토이 프로젝트 팀원들 일부와 함께 다시 동아리에서 진행하는 해커톤인 <a href="https://www.instagram.com/p/DTl7DdpAUro/">와커톤</a> 에 <strong>팀장/PM</strong>, <strong>UI/UX 디자인</strong>, <strong>프론트엔드 개발</strong> 역할로 참여하게 되었다.</p>
<h3 id="타임라인">타임라인</h3>
<blockquote>
<ul>
<li><strong>팀빌딩</strong> : ~2/15 (일)</li>
<li><strong>주제 사전 발표</strong> : 2/19 (목)</li>
<li><strong>해커톤 당일</strong> : 2/21 (토) 오후 3시 ~ 2/22 (일) 
 (실제 개발 진행 : 2/21(토) 오후 4시 ~ 2/22 오전 8시)</li>
</ul>
</blockquote>
<h3 id="주제">주제</h3>
<blockquote>
<p><strong>오프라인에서 완성되는 경험</strong></p>
</blockquote>
<hr />

<h2 id="프로덕트-소개">프로덕트 소개</h2>
<p><strong>오고있니 AreUComing</strong></p>
<ul>
<li><a href="https://github.com/h-seo-n/wackathon-front">frontend repo</a></li>
</ul>
<blockquote>
<p>&quot;<em>기다림의 시간까지도 설렘으로</em>&quot;</p>
<ul>
<li>&#39;위치 추적&#39;이 아닌 상호 &#39;위치 공유&#39;로, 연인을 만나러 가는 경로를 실시간으로 시각화 🗺️</li>
<li>만나러 가는 길까지도 하나의 콘텐츠가 될 수 있게 💗</li>
<li>쌓인 만남의 기록들은 연인만의 새로운 이야기 ✍️</li>
</ul>
<p>오프라인을 대체하지 않는, 오프라인에서의 만남을 오히려 촉진하는 서비스!</p>
</blockquote>
<h3 id="로그인--회원가입--파트너-연결-흐름">로그인 / 회원가입 / 파트너 연결 흐름</h3>
<p>서비스를 이용하기 위해서는 로그인 및 회원가입이 필요하며,
회원가입 직후에는 자신이 초대 코드를 만들어 상대방에게 전달하거나,
상대방이 만든 초대 코드를 수락하는 식으로 _파트너 연결_을 하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/125faeb8-5f54-446a-9dc9-70fd8d44a717/image.png" alt="로그인/회원가입/파트너 연결 스크린 샷 3개"></p>
<h3 id="홈-화면">홈 화면</h3>
<p>파트너 연결 이후에는 &#39;위치 공유 시작&#39; 버튼을 눌러 나의 위치 공유를 먼저 시작할 수 있다.
한 명이 위치 공유를 시작하면 타인에게 그 푸시 알람이 도착하여, 상호 위치 공유를 할지 말지 선택할 수 있다.
(*푸시 알람 기능은 FCM-Firebase Cloud Messaging을 이용했으며 안드로이드에 PWA 형식으로 웹을 설치했을때, 그리고 데스크탑에서 작용하는 것 확인)</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/3e8a048c-2bfa-49e6-999f-6e35786ea267/image.png" alt="홈 화면 4가지 스크린샷"></p>
<h3 id="위치-공유">위치 공유</h3>
<p>홈의 &#39;위치 공유&#39; 버튼을 누르면 websocket 연결이 확립되고, &#39;실시간 위치 보기&#39;를 클릭해서 지도가 있는 위치 공유 페이지로 이동하면 그때부터 websocket 기반 실시간 위치 공유를 시작한다.</p>
<p>(여담으로 지도를 확대해 보면, 해커톤의 장소인 &#39;성수 엘리스랩&#39; 근방에서 팀원들이 열심히 뛰어다녔다는 것을 확인할 수 있다 😅)</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/24560050-dc44-48d6-8b18-857b0c4b80d2/image.png" width=400 />  

<p>&#39;만남 기록&#39; 버튼을 누르면 두 사람의 만남이 성사된 것으로 간주되어, 내가 이동한 거리 + 상대방이 이동한 거리를 합산한 &#39;만남 이동 거리&#39;와 만날때까지 걸린 시간이 계산 및 기록된다.
이 데이터는 &#39;만남 회고&#39; 탭에서 연인 간의 하나의 이야기 겸 콘텐츠처럼 향유할 수 있다.</p>
<h3 id="만남-회고">만남 회고</h3>
<ul>
<li><strong>스토리 탭</strong> : 각각의 만남의 평균 시간과 평균 거리를 계산해서 대시보드 형태로 보여주며,
이번 달에 몇 번 만났는지 / 만남까지 총 걸린 시간과 이동 거리, 그리고 만남까지 걸린 최단 시간을 이야기 형태로 보여준다.</li>
<li><strong>목록 탭</strong> : 모든 만남 목록을 피드 형식으로 보여준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/1c6bc3d4-1771-4821-a7bb-e5a80763295d/image.png" alt="스토리/목록 탭 스크린샷 2개"></p>
<ul>
<li><strong>지도 탭</strong> : (지도 부분은 작동하는 것을 스크린샷 찍지 못해서, 아래에 피그마 스크린샷을 첨부했다.) 실제로는 시간 부족 이슈로, 모든 만남을 오버레이해서 하나의 지도로 보여주는 것까지만 구현했다.
<img src="https://velog.velcdn.com/images/h-seo-n/post/9a609957-9a8e-47f4-8856-cb188e83f7ad/image.png" alt="&#39;지도&#39;"></li>
</ul>
<hr />

<h2 id="kpt-keep-problem-try">KPT (Keep, Problem, Try)</h2>
<h3 id="keep">Keep</h3>
<h4 id="사전-준비-워크플로우">사전 준비 워크플로우</h4>
<p>  약 이틀 간의 준비 시간을 알차게 활용할 수 있기 위해서는, 행사 이전 무엇을 어디까지 준비할 것인지에 대한 고민과 이에 따른 투두 정리가 필요할 것이라 생각했다. 따라서 다음과 같은 사전 준비 타임라인 설계를 했다.</p>
<blockquote>
<p><strong>첫 회의</strong> :   </p>
</blockquote>
<ul>
<li><p>회의 이전에 <em>아이디에이션</em> 및 _아이디어 투표_를 피그마의 FigJam을 이용해 미리 진행. </p>
</li>
<li><p>회의 때 아이디어 바탕으로 세부 기획서 작성.</p>
<blockquote>
<p><strong>두 번째 회의</strong> : </p>
</blockquote>
<ul>
<li>회의 이전에 기획서를 바탕으로 UI 스케치 및 DB 스키마/엔드포인트 목록 작성<blockquote>
<ul>
<li>두 번째 회의 때 서로 피드백 및 수정사항 반영 + 업무분배</li>
</ul>
</blockquote>
</li>
</ul>
<p>각 업무 간의 의존성을 고려한 효율적인 흐름이었기 때문에 만족한다.</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/c9b3fbcd-3cf4-4efa-8bed-4a7f032488c4/image.png" alt="슬랙 캡쳐 이미지" width="500"/>

</li>
</ul>
<h4 id="styled-components의-도입"><code>styled-components</code>의 도입</h4>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/70bb8ab4-3ad9-4539-bce9-babd7f9e70f5/image.png" alt=""></p>
<p>지금까지 계속 <code>CSS Modules</code>를 사용하다가,
CSS 파일을 페이지별로 적용할 시간이 없을 것 같아서 · 반복되는 컴포넌트 스타일이 대부분이고, 페이지 레이아웃의 경우도 반복되는 경우가 많다고 여겨서 처음으로 <code>styled-components</code>를 사용해 보았다.</p>
<p>예상대로 초기 세팅 시간을 들이고 나니 빠르게 UI를 구현할 수 있어서, 효과적인 프레임워크 세팅이라고 느꼈다.</p>
<h4 id="작은-규모의-서비스에-맞는-깔끔한-파일-구조">작은 규모의 서비스에 맞는 깔끔한 파일 구조</h4>
<p>이 <a href="https://bttrthn-ystrdy.tistory.com/91#:~:text=%EA%B1%B0%EC%9D%98%20%EB%AA%A8%EB%93%A0%20%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%8A%94%201%EA%B0%9C%20%EC%9D%B4%EC%83%81%EC%9D%98%20%EC%BB%A4%EC%8A%A4%ED%85%80%20hook%EC%9D%B4,%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%20%ED%8F%B4%EB%8D%94%EC%97%90%20%EB%8B%B4%EA%B8%B0%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90%20%EB%A7%A4%EC%9A%B0%20%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0%EA%B0%80%20%EC%89%BD%EB%8B%A4.">블로그 링크</a> 를 참고해서, 중급 정도의 파일 구조를 적용했다.</p>
<blockquote>
<ul>
<li>pages 폴더 : 안에 페이지 컴포넌트, 그리고 해당 페이지에만 사용되는 파일들 (컴포넌트 / 훅)을 넣음</li>
</ul>
</blockquote>
<ul>
<li>components 폴더 : 여러 페이지에 걸쳐 사용할 수 있는 컴포넌트들의 경우 여기에 넣음</li>
<li>hooks : 컴포넌트 폴더와 마찬가지로, 여러 페이지에 걸쳐서 사용되는 전역 hook를 저장할 수 있음</li>
<li>assets : 전역 css 스타일 /폰트 파일 / 사진들</li>
<li>context : 전역 변수 관련 파일들</li>
<li>data : 프로그램에서 사용되는 정보(전역 constant, 테마 정보 등)</li>
<li>utils : 여러 페이지에 걸쳐 사용되는 포맷팅 함수 등의 함수 파일들</li>
</ul>
<p>초기 설정에도 간편했고 address alias 설정과 함께 이용하니 파일 경로를 바꾸어도 import를 바꾸는 등의 문제가 없어서, 다음에도 프로젝트 규모가 너무 크지 않은 경우 이런 파일 구조를 차용할 예정이다.</p>
<h4 id="서비스의-완성도">서비스의 완성도</h4>
<p>서비스의 및 유저 페르소나가 명확하고 스토리텔링이 구체적이라고 느꼈다.
또한 서비스 이용 시나리오에 따른 UX 흐름이 유기적이라고 느껴서, 기능 구현 정도를 포함하여 서비스의 전반적인 완성도가 높아 만족스러웠다.</p>
<h3 id="problem--try">Problem / Try:</h3>
<ul>
<li><strong>기능 선택과 집중이 부족했다.</strong> 
  해커톤이 처음이라서 그런지 약 16시간 미만 동안 얼마나 개발할 수 있는지 감이 잘 오지 않았다. 또한 프로젝트 완성도를 추구하며 해커톤이 끝나갈 무렵 사소한 포인트 디버깅을 하느라 발표자료를 늦게 제출하게 된 탓에 (...) 프로덕트 완성도 및 기획력에 비해 등수가 낮아졌을 수도 있겠다고 생각했다.
  ➡ &#39;하면 된다&#39;는 마인드보단, <strong>현실적으로 개발 기간동안 할 수 있는 것들을 evaluate하고, 완벽한 프로덕트가 아닌 MVP를 만든다는 생각을 더 가지는 것이 필요하다</strong>. 핵심적인 기능들을 선정하고 그것을 잘 showcasing하는 것이 중요하다.</li>
</ul>
<ul>
<li><p>프로젝트와 관련해서 혼자 고민해보고, 고민 결과로 도출된 진행 방향이나 계획을 팀원들에게 공유하고 의견을 구하는 진행 방식은 익숙한 것 같다. 그러나 0에서 1을 만들기 위해 팀원들과 논의가 필요한 경우, 논의에 더 효과적으로 참여할 수 있게 하기 위한 소통 방식 i.e, human querying / prompting이 아직 덜 익숙한 것 같다. 이후 프로젝트등을 진행할때 이를 염두에 두어야겠다고 느꼈다.</p>
</li>
<li><p>PM뿐만이 아니라 다른 역할들도 맡았기 때문에, 특히 해커톤 진행 중은 프론트 개발 업무가 과중했기 때문에 각각의 팀원들의 프로젝트 진행 상황에 대해서 신경 쓰지 못했다. 그 과정에서 백엔드 개발자 분께서 본인의 업무 분담이 끝나서 프론트 쪽을 하고 있으셨는데, 그 부분이 프론트 개발자들에게 잘 전달되지 않는 등 더 효율적일 수 있던 부분이 아쉽게 느껴진다.<br>  ➡ 앞으로  여러 역할을 PM과 함께 병행해야 하는 상황이라면, 프로젝트 매니징 업무에 집중할 수 있도록 다른 역할들의 업무 부담을 조절하는 것이 필요하다.<br>  ➡ <strong>프로젝트의 전체적인 흐름을 알지 못하면 비효율이 발생할 가능성이 높다</strong>. 효율적 진행을 위해 팀원 간 소통을 연결해주기 위해서는, 프로젝트가 전체적으로 어떻게 되어가는지 아는 것이 중요하다.</p>
</li>
</ul>
<h3 id="uiux-디자인">UI/UX 디자인</h3>
<p>피그마로 UI 디자인을 처음 시도해보았다. 
툴을 써본 적은 있지만, 피그마에 UI 스케치를 시도해보며 툴을 사용한 것은 처음이었기 때문에 사전 준비 과정에서 plugin, assets 사용, flexbox처럼 frame 사용하기 등 피그마 자체 사용법을 더 잘 알게 되었다.</p>
<p><strong>Figma Make 이용</strong>:
디자인 경험이 있으신 다른 프론트엔드 담당 분께서 알려주신 덕에, Figma Make에 프로젝트에 대한 설명이 포함된 프롬프트를 넣으면 디자인과 함께 코드까지 통합적으로 나온다는 것을 알게 되었다.
<img src="https://velog.velcdn.com/images/h-seo-n/post/e9b1e01a-e5a4-446a-8752-58756471df92/image.png" alt="">
UI를 고민하고 레퍼런스를 찾아볼 시간이 사실 충분하지 않았기 때문에, 최대한의 효율으로 UI 디자인을 내기 위해, Figma Make에서 나온 AI의 UI를 다른 Figma 파일에 복사 &amp; 붙여넣기하여 AI스럽거나 어색한 부분을 수정하는 식으로 진행했다.</p>
<p>이 과정에서 AI가 한 디자인을 그대로 쓸 경우, gradient 사용이나 과도한 그림자, 투박한 색상 선정과 &#39;boxes in boxes&#39; 등의 요소들이 &#39;AI스러움&#39;을 자아내어 서비스의 질을 더 떨어져보이게 한다고 느꼈다. 
AI 시대에서 프로덕트가 양산형이 되지 않을 수 있는 방법은, UI 디자인과 UX 라이팅에 고민을 들이는 것, 그럼으로써 authencity를 챙기는 방법이라고 생각한다.</p>
<p>로고 또한 gemini에 프롬프트를 넣어 나온 초안을 참고하여 직접 피그마로 만드는 방식으로 만들었다.</p>
<ul>
<li>ai 생성 로고 :  <img src="https://velog.velcdn.com/images/h-seo-n/post/6d83b155-afed-4900-8881-c932d84b4e5a/image.png" width=400/></li>
<li>수정 후의 로고 :
  <img src="https://velog.velcdn.com/images/h-seo-n/post/3fff0eb4-e10d-49fa-808e-c9bd239d8d5a/image.png" alt="서비스 헤더"></li>
</ul>
<p>ui와 로고들을 그려본 이후 디자인 전공을 하고 있는 팀원에게 검수를 받아서 완성도를 높였다.</p>
<h4 id="사용-디자인-워크플로우-정리">사용 디자인 워크플로우 정리</h4>
<blockquote>
<ul>
<li>기획서 작성 이후, Figma Make로 디자인 생성 - 레퍼런스로 활용</li>
<li>서비스 의도와 비슷한 UI 레퍼런스를 찾아보기</li>
<li>서비스의 컨셉이 무엇일지 고민하면서 primary color등 컨셉 포인트들을 잡기</li>
<li>1, 2, 3을 바탕으로 최종 UI를 만들기</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FileZilla] Could not connect to server 오류]]></title>
            <link>https://velog.io/@h-seo-n/FileZilla-Could-not-connect-to-server-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@h-seo-n/FileZilla-Could-not-connect-to-server-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 28 Jan 2026 03:29:24 GMT</pubDate>
            <description><![CDATA[<p><strong>FileZilla</strong> : 원격 서버에서 파일을 주고 받을 수 있는 앱</p>
<p>파일을 주고 받을 서버를 연결하기 위해서는
<strong>File &gt; Site manager</strong> 에 들어가서, 연결 Protocol, Host &amp; Port, User id을 입력하면 되는데</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/b6d5e158-5456-46a8-a0ef-5639d5248e97/image.png" alt=""></p>
<p>이렇게 에러가 뜸</p>
<p>이유 : <strong>연결 Port</strong>를 잘못 입력했기 때문
<img src="https://velog.velcdn.com/images/h-seo-n/post/1672399d-d3e7-4d98-8ef4-fe85e5d583e3/image.png" alt="">
실제로 연결된 port를 서버와 연결된 터미널창에서 다음 커맨드로 확인해야 한다</p>
<pre><code class="language-bash">$ echo ${SSH_CLIENT##* }</code></pre>
<p>보통 FTP 연결, SFTP 연결이면 Port 22인데,
서버 아이디 신청 시 할당받는 port로 오해하고 있었다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] ContextProvider 이용으로 로그인 상태 관리하기]]></title>
            <link>https://velog.io/@h-seo-n/%EC%8A%A4%EB%88%84%EC%9D%B8%ED%84%B4-2-ContextProvider-%EC%9D%B4%EC%9A%A9%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@h-seo-n/%EC%8A%A4%EB%88%84%EC%9D%B8%ED%84%B4-2-ContextProvider-%EC%9D%B4%EC%9A%A9%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 28 Nov 2025 03:46:39 GMT</pubDate>
            <description><![CDATA[<p>서울대학교 개발동아리 와플스튜디오에서, 작년의 동아리원들이 만든 서비스인 &#39;스누인턴&#39;을 클론코딩하는 과제를 몇 주간 진행했다.</p>
<blockquote>
<ol>
<li><strong>로그인 / 회원가입 구현 및 내비게이션 바에 인증 상태 반영</strong></li>
<li>react-router-dom의 url parameter을 이용한 query로 서버에서 모집공고 가져오기 및 필터링, 정렬 기능 구현.</li>
<li>마이페이지 내에서 프로필 등록 여부에 따른 조건부 렌더링, 프로필 작성 및 수정 기능 구현.</li>
</ol>
</blockquote>
<p>이번 글에서는 <code>createContext</code>, <code>useContext</code> 훅으로 사용자 정보 <code>user</code> 및 로그인 상태를 리액트에서 전역변수로 관리한 구조를 기록해보고자 한다.</p>
<h1 id="authentication-context">Authentication context</h1>
<p>React에서 다른 component들이 공통된 state를 두고 동시다발적으로 그 state에 접근할 수 있는 것이 <code>context</code>이다.
유저 정보와 로그인 유무는 여러 컴포넌트에서 글로벌하게 사용되기 때문에, 이러한 context를 사용하는 것이 더 유리하다. (상위 component에서 fetch를 하고 하위 component들에게 prop으로 전달해주는 것보다)</p>
<p>➡️ <code>src/contexts/AuthContext.tsx</code>에 <code>AuthContext</code>, ContextProvider(<code>AuthProvider</code>), 그리고 context를 불러올 수 있는 커스텀 훅 <code>useAuth</code>를 모두 정의해 두었다.</p>
<blockquote>
<ul>
<li><strong><code>AuthContext</code></strong> : 다른 컴포넌트들이 사용할 수 있는 global variable</li>
</ul>
</blockquote>
<ul>
<li><strong><code>AuthContextProvider</code></strong> : <code>AuthContext</code>를 다른  컴포넌트들에게 전달해줌</li>
<li><strong><code>useAuth</code></strong> : 다른 컴포넌트가 <code>AuthContext</code>를 내부의 <code>useContext</code>를 통해 접근할 수 있게 하는 Hook.</li>
</ul>
<h3 id="파일-내부-정의된-type들">파일 내부 정의된 type들</h3>
<ul>
<li><strong><code>AuthContextType</code></strong> : 전역변수 <code>AuthContext</code>의 type (i.e, 다른 컴포넌트로 전달되는 모든 정보의 목록.)
  ➝ fetch해오는 <code>user</code> 값뿐만이 아니라, 
  context와 관련하여 <code>user</code>을 업데이트 할 수 있는 <code>login</code>, <code>signUp</code>, <code>logout</code> 함수들도 미리 정의해두고 전달해줄 수 있다.<pre><code class="language-typescript">// 인증 상태(isLogined) 관리 위한 context의 type
interface AuthContextType {
  user: User | null;
  isLoading: boolean; // api 응답이 왔는지의 여부
  /* signup, login api 요청들 : 
    SignupData or LoginData를 받아서 Promise 형태의 응답을 return. */
  signUp: (data: SignupData) =&gt; Promise&lt;void&gt;;
  login: (data: LoginData) =&gt; Promise&lt;void&gt;;
  logout: () =&gt; void;
}</code></pre>
</li>
<li><code>User</code> : <code>GET /api/auth/me</code>에서 반환되는 response type.<pre><code class="language-typescript">interface User {
  id: string;
  name: string;
  email: string;
  userRole: string;
}</code></pre>
<h3 id="authcontext-⬅️-createcontext">AuthContext ⬅️ createContext</h3>
전역변수 역할을 하는 context는 미리 지정해둔 ContextType으로 명시해두고, <code>React.createContext</code>로 만들어두기만 하면 된다.<pre><code class="language-typescript">const AuthContext = createContext&lt;AuthContextType | undefined&gt;(undefined);</code></pre>
</li>
</ul>
<h2 id="authprovider-authcontextprovider">AuthProvider (AuthContext.Provider)</h2>
<p>ContextProvider에서는 ContextType에 있는, <strong>context에서 전달해주어야 하는 모든 property</strong>들을 관리하고 반환해준다.</p>
<h3 id="setup">setup</h3>
<pre><code class="language-typescript">export const AuthProvider = ({ children }: { children: ReactNode }) =&gt; {
  const [user, setUser] = useState&lt;User | null&gt;(null);
  const [isLoading, setIsLoading] = useState(true);</code></pre>
<ul>
<li>children: 이후 <code>App.tsx</code>에서 <code>&lt;AuthProvider /&gt;</code>으로 하위 컴포넌트들을 모두 감싸주는데, 이 감싸진 컴포넌트들이 children으로 취급되어 <code>AuthContext</code>를 접근할 수 있다.</li>
<li><strong>state 관리</strong> :     context에서 api 호출의 response 값이나 로딩 여부 등의 <strong>객체 값</strong>들은 주로 <strong>ContextProvider</strong>안의 <strong>state</strong>로 관리된다. 
위 코드에서는 유저 정보 <code>user</code>과 로딩 여부 <code>isLoading</code>이 state로 관리된다.</li>
</ul>
<h3 id="useeffect---렌더-시의-로직">useEffect - 렌더 시의 로직</h3>
<p><code>AuthContext</code>에서는 context의 특성 상, 유저가 이미 로그인되어있는지의 여부를 렌더 시마다 확인하고, user 정보를 업데이트해야 한다.
무슨 context를 구현하는지에 따라 다르겠지만 이처럼 각 렌더마다 확인되어야 하는 정보들은 <code>useEffect</code>안에 넣어준다.</p>
<pre><code class="language-typescript">  // 앱 렌더 시 저장된 토큰 있는지 확인
  useEffect(() =&gt; {
    const checkLogined = async () =&gt; {
      // 저장된 인증 토큰 있는지 확인 (있으면 로그인 유지)
      const token = localStorage.getItem(&#39;authToken&#39;);

      if (token) {
        try {
          const response = await apiClient.get&lt;User&gt;(&#39;/api/auth/me&#39;);
          setUser(response.data);
        } catch (error) {
          console.error(&#39;토큰을 가진 유저 가져오기 실패&#39;, error);
          localStorage.removeItem(&#39;authToken&#39;);
        }
      }
      setIsLoading(false);
    }; // end of checkLogined()

    checkLogined();
  }, []);</code></pre>
<ul>
<li><p>로그인 여부는 서버에서 저장해주는 보안 토큰(<code>authToken</code>)이 <code>localStorage</code>에 저장되어있느냐 마냐로 결정된다.</p>
</li>
<li><p>이때, 유저 정보 설정(<code>setUser</code>)은 <code>GET /api/auth/me</code> 요청의 응답으로만 시행한다.
⇒  login / signup의 api 요청 응답도 유저 정보를 부분적으로 반환하지만, 이렇게 반환된 값은 <code>User</code>데이터타입 스키마와 일치하지 않는다. </p>
</li>
<li><p>따라서 <code>User</code> type state로 <code>user</code> 정보를 관리하기 위해, login/signup 이후에 자동 로그인 기능을 구현할 때에는, api 요청의 response 중 토큰만 이용하여 다시 <code>/api/auth/me</code> 요청을 보내고, 해당 값으로 따로 <code>setUser</code>을 한다.</p>
</li>
</ul>
<h3 id="회원가입-함수">회원가입 함수</h3>
<ul>
<li><p><code>SignupData</code>는 따로 <code>types/index.ts</code>에서 데이터타입을 정의해두었다. (<code>LoginData</code>도 마찬가지)</p>
</li>
<li><p>위에서 언급한 것처럼, api response 중 토큰만 받아와서 다시 <code>User</code> 정보를 fetch하는데 이용한다.</p>
<pre><code class="language-typescript">/* 회원가입 함수 */
const signUp = async (data: SignupData) =&gt; {
  // apiClient로 api call 보내기
  const response = await apiClient.post(&#39;/api/auth/user&#39;, {
    authType: &#39;APPLICANT&#39;, //고정
    info: {
      type: &#39;APPLICANT&#39;, // 고정
      name: data.name,
      email: data.email,
      password: data.password,
      successCode: &#39;success&#39;, // 값 변경 가능
    }, // end of info
  }); // end of apiClient.post();

  // 회원가입 response에서 token만 가져오기
  const { token } = response.data;
  // 인증 token : localStorage에 저장
  localStorage.setItem(&#39;authToken&#39;, token);

  // signup 후 자동 로그인
  const userResponse = await apiClient.get&lt;User&gt;(&#39;/api/auth/me&#39;);
  setUser(userResponse.data);
};
</code></pre>
</li>
</ul>
<pre><code>
### 로그인 함수

```typescript
  /* 로그인 함수 */
  const login = async (data: LoginData) =&gt; {
    const response = await apiClient.post(&#39;/api/auth/user/session&#39;, {
      email: data.email,
      password: data.password,
    });
    const { token } = response.data;

    // 로그인 상태
    localStorage.setItem(&#39;authToken&#39;, token);

    // 유저 정보
    const userResponse = await apiClient.get&lt;User&gt;(&#39;/api/auth/me&#39;);
    setUser(userResponse.data);
  };
</code></pre><h3 id="로그아웃-함수">로그아웃 함수</h3>
<pre><code class="language-typescript">  /* 로그아웃 함수 */
  const logout = () =&gt; {
    // try {
    //   await apiClient.delete(&quot;/api/auth/user/session&quot;);
    // } catch (error) {
    //   console.error(&quot;로그아웃 실패 - 클라이언트 측 정보 지우기만 진행&quot;, error);
    // } finally {
    //   localStorage.removeItem(&quot;authToken&quot;);
    //   setUser(null);
    // }

    // 클라이언트 토큰 값만 삭제
    localStorage.removeItem(&#39;authToken&#39;);
    setUser(null);
  };
</code></pre>
<ul>
<li>주석 처리된 부분은 원래 과제 명세에서 필요했던, api call을 통한 세션 제거 부분이다. 그러나 해당 부분 구현 과정에서 서버 세팅 오류로 인한 CORS 에러가 발생하여, 현재 구현에서는 클라이언트 토큰 값만 삭제하는 것으로 변경되었다.</li>
</ul>
<h3 id="authprovider의-return-value">AuthProvider의 return value</h3>
<p>위와 같이 다른 컴포넌트들로 반환해줄 값들의 준비가 코드 상으로 완료되면, <code>Context.Provider</code>의 형태로 컴포넌트를 반환해주면 된다.
이때 컴포넌트 prop의 <code>value</code>에는 context(global variable)에서 전달해주는 모든 property들을 명시해주면 된다. (aka <code>ContextType</code>)</p>
<pre><code class="language-typescript">export const AuthProvider = ({ children }: { children: ReactNode }) =&gt; {

...

  return (
    &lt;AuthContext.Provider value={{ user, isLoading, signUp, login, logout }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
  // end of AuthProvider
};
</code></pre>
<h2 id="custom-hook---useauth">Custom Hook - useAuth</h2>
<p>다른 컴포넌트들이 AuthProvider을 통해서 AuthContext에 접근하는 것을 보다 편리하게 하기 위해, <code>useContext</code>를 이용한 wrapper hook을 정의해준다.</p>
<pre><code class="language-typescript">
// custom Hook -&gt; 다른 컴포넌트들에서 사용
export const useAuth = () =&gt; {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error(&#39;useAuth는 AuthProvider 안에서 사용되어야 함&#39;);
  }
  return context;
  // end of useAuth
};</code></pre>
<p>매우 간단하다.</p>
<h2 id="활용">활용</h2>
<h3 id="apptsx">App.tsx</h3>
<p>가장 상위 컴포넌트인 <code>App.tsx</code>에서 <code>AuthProvider</code>로 나머지 컴포넌트들을 감싸주어야 한다.
다른 Context Provider들을 정의하더라도 비슷한 방식으로, 원래 있던 Provider을 감싸거나 그 안에 두는 식으로 작성하면 된다.</p>
<pre><code class="language-typescript">    &lt;AuthProvider&gt;
        &lt;Topbar /&gt;
        &lt;main&gt;
          &lt;Routes&gt;
            &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
            &lt;Route path=&quot;/login&quot; element={&lt;Login /&gt;} /&gt;
            &lt;Route path=&quot;/signup&quot; element={&lt;Signup /&gt;} /&gt;
            &lt;Route path=&quot;/mypage&quot; element={&lt;MyPage /&gt;} /&gt;
            &lt;Route path=&quot;/mypage/profile/new&quot; element={&lt;CreateProfile /&gt;} /&gt;
            &lt;Route path=&quot;/mypage/profile/edit&quot; element={&lt;EditProfile /&gt;} /&gt;
          &lt;/Routes&gt;
        &lt;/main&gt;
    &lt;/AuthProvider&gt;</code></pre>
<h3 id="topbartsx">Topbar.tsx</h3>
<p>과제 스펙 중 하나인, 로그인 여부에 따라서 조건부 렌더링되게 하는 것을 구현하기 위해 <code>AuthContext</code>를 활용했다.</p>
<ul>
<li><p>로그인 전 :
  <img src="https://velog.velcdn.com/images/h-seo-n/post/266b4dd9-05bc-40f6-aa7f-a92b97f6a67b/image.png" alt="로그인 전"></p>
</li>
<li><p>로그인 후 :
<img src="https://velog.velcdn.com/images/h-seo-n/post/94ac6601-e9a7-4678-b6e9-29e7e534beb6/image.png" alt="로그인 후"></p>
</li>
<li><p>코드 : <code>user</code>값을 받아와서 <code>user</code> 값이 null인지 아닌지를 바탕으로 로그인 여부를 판단하고 렌더링을 다르게 함.</p>
</li>
</ul>
<pre><code class="language-typescript">import { useAuth } from &#39;../contexts/AuthContext&#39;;
import { useNavigate } from &#39;react-router-dom&#39;

const Topbar = () =&gt; {
  const navigate = useNavigate();
  const { user, logout } = useAuth();
    // user = isLogined value와 같은 기능

  const handleLogout = async () =&gt; {
    await logout();
    navigate(&#39;/&#39;); // 로그아웃 이후 홈으로 이동
  };

  return (
    &lt;div className=&quot;topbar&quot;&gt;
      ...
          {user ? (
          &lt;&gt;
            &lt;span className=&quot;user-name&quot;&gt;{user.name}님&lt;/span&gt;
            &lt;button onClick={handleLogout} className=&quot;nav-button&quot;&gt;로그아웃&lt;/button&gt;
          &lt;/&gt;
        ) : (
          &lt;&gt;
            &lt;Link to=&quot;/login&quot; className=&quot;nav-link&quot;&gt;로그인&lt;/Link&gt;
            &lt;Link to=&quot;/signup&quot; className=&quot;nav-link&quot;&gt;회원가입&lt;/Link&gt;
          &lt;/&gt;
        )}
  ...
</code></pre>
<h3 id="signuptsx">Signup.tsx</h3>
<pre><code class="language-typescript">const { signup } = useAuth();</code></pre>
<ul>
<li><p>name, email, password : <code>state</code>로 저장해두기
  → input의 <code>onChange</code> 에 setter 함수 설정
  → form 제출 시 signup 함수에 바로 state 값 전달</p>
</li>
<li><p>회원가입 버튼을 눌렀을 때 form 요소에서 ‘제출’ 이벤트를 처리하기 위한 <code>handleSubmit</code> 함수 필요</p>
</li>
</ul>
<pre><code class="language-typescript">  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault();
    setError(&#39;&#39;);
    try {
      await signup({ name, email, password });
      navigate(&#39;/&#39;); // Redirect to home on success
    } catch (err: any) {
      setError(err.response?.data?.message || &#39;회원가입에 실패했습니다.&#39;);
    }
  };</code></pre>
<h3 id="logintsx">Login.tsx</h3>
<pre><code class="language-typescript">  const { login } = useAuth();
  const navigate = useNavigate();

  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault();
    setError(&#39;&#39;);
    try {
      await login({ email, password });
      navigate(&#39;/&#39;); // Redirect to home on success
    } catch (err: any) {
      setError(err.response?.data?.message || &#39;로그인에 실패했습니다.&#39;);
    }</code></pre>
<p><code>AuthContext</code> 와 함께한 보안인증 끝!
서버에서 토큰을 받아서 저장하고 이런 로직을 처음 알게 되었다
context의 필요성도 체감한 뜻깊은 과제였다 〰️
<img src="https://velog.velcdn.com/images/h-seo-n/post/fb251839-50c9-46a9-a94b-7d77c601cfe7/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React에서 axios 활용하여 apiClient 쓰기  ]]></title>
            <link>https://velog.io/@h-seo-n/%EC%8A%A4%EB%88%84%EC%9D%B8%ED%84%B4-1-React%EC%97%90%EC%84%9C-axios-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-apiClient-%EC%93%B0%EA%B8%B0</link>
            <guid>https://velog.io/@h-seo-n/%EC%8A%A4%EB%88%84%EC%9D%B8%ED%84%B4-1-React%EC%97%90%EC%84%9C-axios-%ED%99%9C%EC%9A%A9%ED%95%98%EC%97%AC-apiClient-%EC%93%B0%EA%B8%B0</guid>
            <pubDate>Fri, 28 Nov 2025 02:22:06 GMT</pubDate>
            <description><![CDATA[<p>서울대학교 개발동아리 와플스튜디오에서, 작년의 동아리원들이 만든 서비스인 &#39;스누인턴&#39;을 클론코딩하는 과제를 몇 주간 진행했다.</p>
<blockquote>
<ol>
<li>로그인 / 회원가입 구현 및 내비게이션 바에 인증 상태 반영</li>
<li>react-router-dom의 url parameter을 이용한 query로 서버에서 모집공고 가져오기 및 필터링, 정렬 기능 구현.</li>
<li>마이페이지 내에서 프로필 등록 여부에 따른 조건부 렌더링, 프로필 작성 및 수정 기능 구현.</li>
</ol>
</blockquote>
<p>이 과정에서 배운 것들이 많아서 이후 다른 과목의 팀 프로젝트에서도 배운 내용을 유용하게 활용할 수 있었다.</p>
<h1 id="axios를-이용한-api-client">axios를 이용한 API Client</h1>
<p>이전 &#39;<a href="https://velog.io/@h-seo-n/React-%EC%B2%9C%EA%B0%9C%EC%9D%98-%EB%A0%88%EC%8B%9C%ED%94%BC-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9">천개의 레시피</a>&#39; 클론코딩에서는 사용해야 하는 api 함수들이 한정적이라서, 따로 라이브러리를 사용하지 않았다.
이번에는 조금 더 체계성을 갖추기 위해, 따로 <code>src/api/index.ts</code> 에 <code>axios</code>를 이용한 <code>apiClient</code>를 만들어두었다.</p>
<pre><code class="language-typescript">import axios from &#39;axios&#39;;

const apiClient = axios.create({
  baseURL: &#39;https://api-internhasha.wafflestudio.com&#39;,
  // withCredentials: true, // refresh_token 첨부
});
// 인증 토큰 필요 X인 endpoint들
const publicPaths = [
  &#39;/api/auth/user&#39;, //회원가입
  &#39;/api/auth/user/session&#39;, //로그인
];

// interceptor(API 요청을 intercept하는 아이) : 인증이 필요한 endpoint들에 인증 토큰 자동 부여
apiClient.interceptors.request.use(
  (config) =&gt; {
    const isPublicPath =
      config.url &amp;&amp;
      publicPaths.includes(config.url) &amp;&amp;
      config.method === &#39;post&#39;;

    if (!isPublicPath) {
      const token = localStorage.getItem(&#39;authToken&#39;);

      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
    }

    return config;
  },
  (error) =&gt; {
    return Promise.reject(error);
  }
);

export default apiClient;</code></pre>
<ul>
<li><p><strong>client</strong> : <code>axios.create</code>로 <code>axios</code> instance를 만들어 두고(<code>apiClient</code>), 다른 파일에서 api요청을 보내야 할때 <code>import apiClient</code> 하여 간편하게 클라이언트를 활용하여 api를 쓸 수 있다.</p>
</li>
<li><p><strong><code>interceptor</code></strong> : header 관리</p>
<ul>
<li>모든 api call을 &#39;intercept&#39;하는 아이로, <strong>위에서는 모든 api 요청 헤더에 인증 토큰인 <code>authToken</code>을 넣는 역할을 하고 있다.</strong><ul>
<li>위 코드에서는 <code>publicPaths</code> array에 로그인, 회원가입의 인증 토큰이 필요하지 않은 endpoint들을 넣어 두어, 해당 요청들에 한해서 토큰을 넣지 않게 설정되어 있다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>request</strong> : 다른 파일에서 api요청을 보낼 때 <code>apiClient.{HTTPmethod}(&#39;endpoint&#39;, {body});</code>와 같은 형식으로 간편하게 활용할 수 있다.</p>
</li>
<li><p><strong>response</strong> : axios를 이용한 api call에서 응답을 받을 경우, axios client가 자동적으로 <code>Content-type</code> header을 확인한 후 json 형식일 경우 <code>response.json()</code>을 내부적으로 수행한 뒤에 json object를 반환해준다.
  ➡️ 즉, api 요청 결과 json object를 단순하게 <strong><code>response.data</code></strong>로 접근할 수 있다.</p>
<ul>
<li><p>공식 문서의 <strong>response schema</strong> :</p>
<pre><code class="language-json">    {
      // `data` is the response that was provided by the server
      data: {},

      // `status` is the HTTP status code from the server response
      status: 200,

      // `statusText` is the HTTP status message from the server response
      // As of HTTP/2 status text is blank or unsupported.
      // (HTTP/2 RFC: https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.4)
      statusText: &#39;OK&#39;,

      // `headers` the HTTP headers that the server responded with
      // All header names are lower cased and can be accessed using the bracket notation.
      // Example: `response.headers[&#39;content-type&#39;]`
      headers: {},

      // `config` is the config that was provided to `axios` for the request
      config: {},

      // `request` is the request that generated this response
      // It is the last ClientRequest instance in node.js (in redirects)
      // and an XMLHttpRequest instance in the browser
      request: {}
    }</code></pre>
</li>
<li><p>사용 예시 :</p>
<pre><code class="language-typescript">    axios.get(&#39;/user/12345&#39;)
  .then(function (response) {
    console.log(response.data);
    console.log(response.status);
    console.log(response.statusText);
    console.log(response.headers);
    console.log(response.config);
  });</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="axios-에러-핸들링-">axios <strong>에러 핸들링</strong> :</h3>
<p>  구현 과정 중 custom error code를 감지하고, 이에 따라 다른 handling을 하는 로직을 구현해야 했다. 유저의 프로필이 없을 때 반환되는 <code>APPLICANT_002</code>와 같은 에러 코드를 어떻게 확인할 수 있을지가 애매하여 조금 헤맸다.</p>
<blockquote>
<p>만약 해당 API를 쏘았을 때 상세 에러코드가 “<code>APPLICANT_002</code>”인 경우,
아직 프로필이 등록되지 않은 상태입니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/97389fd1-bfa5-4468-94fd-fcaa95febd4a/image.png" alt=""></p>
<ul>
<li><p><strong>axios 공식 문서 상 사용법</strong> :
  <code>Error</code> 객체의 <code>.response</code> property 안, 즉 <code>error.response</code> 에 <code>data</code>, <code>status</code>, <code>headers</code> 등의 error에 대한 부가 정보가 존재한다.</p>
<pre><code class="language-typescript">  axios.get(&#39;/user/12345&#39;)
.catch(function (error) {
  if (error.response) {
    // The request was made and the server responded with a status code
    // that falls out of the range of 2xx
    console.log(error.response.data);
    console.log(error.response.status);
    console.log(error.response.headers);
  } else if (error.request) {
    // The request was made but no response was received
    // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
    // http.ClientRequest in node.js
    console.log(error.request);
  } else {
    // Something happened in setting up the request that triggered an Error
    console.log(&#39;Error&#39;, error.message);
  }
  console.log(error.config);
});</code></pre>
</li>
<li><p>활용 코드 :</p>
<ul>
<li><p><code>try-catch</code> block에서 <code>try</code> 의 안에서는 <code>response.data</code>로 응답 json string을 받아오고, 
<code>catch(error)</code> 의 안에서는 <code>error.response.message</code>, <code>error.response.status</code> 등을 이용하여 에러를 표시하는 등 에러 핸들링을 한다.</p>
</li>
<li><p>이때 서버에서 명시한 에러 code는 <code>error.response.data</code> 안에 있기 때문에(<code>{ &quot;code&quot;:&quot;APPLICANT_002&quot;, &quot;message&quot;: &quot;Applicant not found&quot; }</code>와 같은 형식), typescript에게 error.response.data 안에 <code>code</code> 속성이 있다고 이야기해주는 <em>type assertion</em> 을 활용한다.</p>
<pre><code class="language-typescript">// MyPage.tsx
import { isAxiosError } from &#39;axios&#39;;

...
catch (e) {
    if (isAxiosError(e) &amp;&amp; e.response) {
        const status = e.response.status;
        const data = e.response.data as { code?:string };
        const code = data?.code;

        if (code===&#39;APPLICANT_002&#39;) {
            setProfile(null);
            return;
        }
        console.error(&#39;API error&#39;, status, code, e.response.data);
    } else {
        console.error(&#39;Unknown Error&#39;, e);
    }</code></pre>
</li>
</ul>
</li>
</ul>
<p>api 만으로도 분량이 길어져서 다음 글에서 본격적으로 context를 활용한 전역 변수 활용을 다룰 생각이다.
글을 올릴 때마다 내 글 밑에 있는 더욱 심화된 주제를 다루는 다른 글들이 추천으로 보여서 의기소침해지지만...
아무도 읽지 않아도 내가 다시 읽으면 된다! ^ . ^
다른 달팽이들은 신경쓰지말자
<img src="https://velog.velcdn.com/images/h-seo-n/post/a3b9dbda-b0a6-43eb-a295-abfeae0901e8/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 천개의 레시피 클론코딩]]></title>
            <link>https://velog.io/@h-seo-n/React-%EC%B2%9C%EA%B0%9C%EC%9D%98-%EB%A0%88%EC%8B%9C%ED%94%BC-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</link>
            <guid>https://velog.io/@h-seo-n/React-%EC%B2%9C%EA%B0%9C%EC%9D%98-%EB%A0%88%EC%8B%9C%ED%94%BC-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9</guid>
            <pubDate>Sun, 05 Oct 2025 09:29:52 GMT</pubDate>
            <description><![CDATA[<p>와플스튜디오 프론트엔드 세미나에서 React로 천개의레시피를 클론코딩하는 과제를 수행했다.
UI 구조와 디자인이 모두 정해져있어서 편리했고, 노마드코더의 리액트 초급 강의에서 들었던 내용을 활용할 수 있는 부분이 많았어서 2048 과제보다 더 쉽게 진행할 수 있었다.</p>
<blockquote>
</blockquote>
<ol>
<li><code>react-dom-router</code>을 이용해서 Multi-Page-App구현하기</li>
<li>하나의 페이지 안에서 페이지 번호를 통해 이동하는 pagination 구현하기</li>
<li>fetch로 원하는 데이터를 asynchronous하게 가져오고 가져온 데이터 저장 및 활용하기</li>
</ol>
<p>이 3가지가 쟁점이 되었던 과제였다.
2048 클론 코딩을 하면서 열심히 코딩을 하더라도 기록하지 않으면 남는 것이 많이 없는 것 같다고 느껴서, 과제 제출 이후 개발 과정 및 에러 로그를 기록했다.</p>
<h3 id="개발-과정">개발 과정</h3>
<ol>
<li><p>구조를 잡는다.
 <img src="https://velog.velcdn.com/images/h-seo-n/post/34b0757d-bc66-4916-b8b5-e12d23bef100/image.png" alt="메인페이지 ui"></p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/ae3104c0-95db-4e67-9787-dade34f61d8e/image.png" alt="상세페이지 ui"></p>
</li>
</ol>
<pre><code>메인 페이지에서 각각의 레시피 카드를 클릭하면, 상세 레시피가 나타나는 별도 path의 페이지로 이동하는 방식이다.

따라서 `react-router-dom` 라이브러리를 이용하고, 각 path에 해당하는 페이지들을 `routes/` 폴더의 `Home.tsx`, `Detail.tsx`로 두었다.

Home에 있는 레시피마다의 카드는 따로 `components/` 폴더의 `Recipe.tsx` 로 분리했다. 페이지네이션 컴포넌트도 마찬가지로 따로 `Pagination.tsx`로 두었다.

```

├── src
│   ├── App.tsx
│   ├── components
│   │   ├── Pagination.tsx
│   │   └── Recipe.tsx
│   ├── main.tsx
│   ├── routes
│   │   ├── Detail.tsx
│   │   └── Home.tsx
│   ├── styles
│   │   ├── App.module.css
│   │   ├── Detail.module.css
│   │   ├── Home.module.css
│   │   ├── Pagination.module.css
│   │   └── Recipe.module.css
```</code></pre><ol start="2">
<li><p><strong>Pagination 컴포넌트</strong> </p>
<p> React에서 pagination을 어떻게 구현할지 고민해보다가, pagination과 리액트 모두 익숙하지 않은 상황에서는 다른 사람의 코드를 검색해서 이해하는게 나을 것이라고 판단했다. </p>
<p> 따라서 검색 후에 정말 깔끔하고 범용성있는 컴포넌트 코드를 <a href="https://imdaxsz.tistory.com/37">https://imdaxsz.tistory.com/37</a> 에서 발견하고, 코드를 살펴보며 이해한 후에, 일부 수정 후 프로젝트에 적용했다.</p>
</li>
</ol>
<ol start="3">
<li><p><strong>API 확인</strong></p>
<p> 사용할 api인 <a href="https://dummyjson.com/docs/recipes">https://dummyjson.com/docs/recipes</a> 의 설명을 확인해보았다.</p>
<ul>
<li><p><strong>전체 데이터 가져오기 :</strong></p>
<p>  한번에 전체 recipe data를 array로 가져오고 페이지에 맞게 slicing해서 이용하는 대신에, api의 <code>limit</code> 과 <code>skip</code> query를 이용해서 현재 페이지에 필요한 item들을 그때그때 가져오는 식으로 구현했다.</p>
<pre><code class="language-tsx">  // Home.tsx
   const response = await fetch(
  `https://dummyjson.com/recipes?limit=${itemCountPerPage}&amp;skip=${(currentPage - 1) * itemCountPerPage}&amp;select=name,image,difficulty,tags`
  );</code></pre>
<p>  구현은 덜 귀찮았던 것 같지만, 그때그때 fetch를 해서 그런지 이미지 로드에 시간이 좀 걸렸다.  </p>
</li>
</ul>
<hr>
<ul>
<li><p><strong>Detail에서 일부 데이터 가져오기 :</strong>
<code>Detail</code>로 이동할때, 클릭된 레시피의 정보를 어떻게 전달해주어야 할까 고민했다. 모든 레시피를 가져오고 id와 같은 identifier 값을 바탕으로 찾는 레시피 데이터가 나올때까지 순회하는 것은 너무 비효율적인듯하였는데 공식 링크에서는 id 값만으로 item을 가져오는 설명이 따로 없었다. </p>
<p>⇒ 대충 전체를 가져오는 링크 <a href="https://dummyjson.com/recipes/2"><code>https://dummyjson.com/recipes/</code></a> 를 바탕으로 url을 조금씩 변경해보면서 결국 <a href="https://dummyjson.com/recipes/2%EB%A5%BC"><code>https://dummyjson.com/recipes/2</code>를</a> 시도해보았는데, 정말로 id를 통해서 하나의 object를 가져오는 valid fetch url이었다 (? 시도의 중요성)</p>
<p>  ⇒ 따라서 <code>Detail.tsx</code>에서 해당 링크를 바탕으로 <code>fetch</code>를 하고, 필요한 <code>id</code> 파라미터 값을 url 파라미터의 형식으로 <code>Recipe.tsx</code>에서 전달받기로 결정했다.</p>
</li>
</ul>
<hr>
</li>
<li><p><strong>상위 <code>App.tsx</code>에서 Route 구조 잡기</strong></p>
<pre><code class="language-tsx"> &lt;Router&gt;
   &lt;Routes&gt;
     &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
     &lt;Route path=&quot;/recipe/:id&quot; element={&lt;Detail /&gt;} /&gt;
   &lt;/Routes&gt;
 &lt;/Router&gt;</code></pre>
<h2 id="→-detail-에-이동-시-id-파라미터를-전달하기-위해-recipeid명시-필요">→ <code>&lt;Detail /&gt;</code>에 이동 시 id 파라미터를 전달하기 위해 <code>/recipe/:id</code>명시 필요</h2>
</li>
<li><p><strong><code>Home.tsx</code> 구현하기 &amp; <code>Recipe.tsx</code> 구현하기</strong></p>
<p> <code>typescript</code>를 이용하는 만큼, Home에서 <code>&lt;Recipe /&gt;</code> 로 전달해줄 prop을 <code>interface</code>를 통해서 prop 제약을 걸어두었다.</p>
<pre><code class="language-tsx"> // Home.tsx
 export interface RecipeProps {
   id: number;
   name: string;
   image: string;
   difficulty: string;
   tags: Array&lt;string&gt;;
 }
 ...
   const [recipes, setRecipes] = useState&lt;RecipeProps[]&gt;();</code></pre>
<p> Home에서는 pagination에서 전달된 <code>?page={pagenum}</code> 파라미터를 <code>useSearchParams()</code>로 받아오고, 이를 바탕으로 page에 맞는 데이터를 fetch해오는 로직이 메인이 된다.</p>
<pre><code class="language-tsx"> // Home.tsx
 ...
   useEffect(() =&gt; {
     setCurrentPage(parseInt(searchParams.get(&#39;page&#39;) || &#39;1&#39;, 10));
   }, [searchParams]);
   ...      
   const response = await fetch(
   `https://dummyjson.com/recipes?limit=${itemCountPerPage}&amp;skip=${(currentPage - 1) * itemCountPerPage}&amp;select=name,image,difficulty,tags`);</code></pre>
<p> 받아온 데이터는 <code>recipes</code> state에 저장해두고, <code>.map</code>을 통해서 각각의 데이터에 따라 <code>&lt;Recipe /&gt;</code> 컴포넌트를 만든다.</p>
<pre><code class="language-tsx"> // Home.tsx
 ...
 &lt;div className={styles.recipeGrid}&gt;
  {recipes?.map((recipe) =&gt; (
         &lt;Recipe
           key={recipe.id}
           id={recipe.id}
           name={recipe.name}
           image={recipe.image}
           difficulty={recipe.difficulty}
           tags={recipe.tags}
         /&gt;
     ))}
 &lt;/div&gt;</code></pre>
<p> Recipe에서는 받은 prop을 바탕으로 card 구조를 구현하는 것이 다이기 때문에 구조에는 css가 더 중요했다. 이외에는 Recipe 카드를 클릭했을때 Detail로 넘어갈 수 있도록, 전체 컴포넌트를 <code>&lt;Link&gt;</code>로 감싸주고, <code>id</code> 파라미터를 전달해주었다.</p>
<pre><code class="language-tsx"> //Recipe.tsx
 ...
     &lt;Link to={`/recipe/${id}`} className={styles.recipeCard} key={id}&gt;
         ...
     &lt;/Link&gt;</code></pre>
<hr>
</li>
<li><p><strong><code>Detail.tsx</code> 구현하기</strong></p>
<p> Detail에서도 id를 바탕으로 fetch해올 개별 object의 구조의 type를 명시해두었다.</p>
<pre><code class="language-tsx"> interface RecipeDetail {
   id: number;
   name: string;
   ingredients: string[];
   instructions: string[];
   prepTimeMinutes: number;
   cookTimeMinutes: number;
   servings: number;
   difficulty: string;
   cuisine: string;
   caloriesPerServing: number;
   tags: string[];
   image: string;
   rating: number;
   reviewCount: number;
   mealType: string[];
 }

 ...
   const [recipe, setRecipe] = useState&lt;RecipeDetail&gt;();</code></pre>
<p> ingredients는 string[] 형식으로 받아서, 한 줄로 출력하기 위해 <code>ingredients.join(&#39; / &#39;)</code> 을 이용했다.</p>
<pre><code class="language-tsx"> const Detail = () =&gt; {
   const [loading, setLoading] = useState(true);
   const [recipe, setRecipe] = useState&lt;RecipeDetail&gt;();
   const { id } = useParams&lt;{ id: string }&gt;();

   useEffect(() =&gt; {
     const getRecipe = async () =&gt; {
       const response = await fetch(`https://dummyjson.com/recipes/${id}`);
       const data = await response.json();
       setRecipe(data);
       setLoading(false);
     };
     getRecipe();
   }, [id]);</code></pre>
<hr>
</li>
<li><p><strong>component마다 styling 적용</strong></p>
<p> Movie clone, 2048 clone에서는 모듈별로 css를 적용해주지 않고 하나의 style sheet에서 모든 것을 해결했는데 이번에는 각각의 모듈별 style sheet을 만들었다. (ex. <code>styles/Home.module.css</code> )</p>
<p> → 확실히 컴포넌트별 css 파일을 작성하는 것이 유지보수 및 수정이 더 간편하게 느껴지고, react의 장점을 더 살리는 방법이라고 느껴졌다.</p>
<p> → 다음에는 row, col과 같은 공통되게 사용되는 class들을 공통된 style sheet나 상위의 style sheet에 두고 사용하는 보다 체계적인 방식을 사용할 예정이다.</p>
<hr>
</li>
<li><p><strong>플로팅 바 만들기 :</strong></p>
<p> Home과 Detail 모두에 공통적으로 ‘천개의 레시피’ 상단 바가 있다. 따라서 이 바는 <code>App.tsx</code> 에 <code>Router</code> 안, <code>Routes</code> 의 밖에 따로 두어서 공통적으로 생기게 했다.</p>
</li>
</ol>
<h2 id="troubleshooting">Troubleshooting</h2>
<h3 id="react">React</h3>
<ul>
<li><p>에러 메시지 : <code>Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.</code></p>
<ul>
<li><p>원인 코드 :</p>
<pre><code class="language-tsx">
  const Home = () =&gt; {
    // Page state
    const [currentPage, setCurrentPage] = useState&lt;number&gt;(1);
    const [loading, setLoading] = useState&lt;boolean&gt;(true);
    const [recipes, setRecipes] = useState&lt;RecipeProps[]&gt;();
    const [totalItems, setTotalItems] = useState&lt;number&gt;(0);

    // current page &amp; other parameters
    const [searchParams] = useSearchParams();
    const ITEM_COUNT_PER_PAGE = 6;
    const PAGE_COUNT = 10;

    // 문제의 줄
    setCurrentPage(parseInt(searchParams.get(&#39;page&#39;) || &#39;1&#39;, 10));
</code></pre>
<p>  → state의 setter 함수를 컴포넌트 body의 최상위 레벨에 직접적으로 호출하니, render 시 state가 바뀌고 바뀐 state 다시 render을 발생시키면서 앱이 무한히 render되게 됨.</p>
</li>
</ul>
</li>
</ul>
<pre><code>⇒ **교훈 :**  ⭐ state setter 함수를 컴포넌트의 메인 body에 넣어서는 안된다.

⇒ **해결책  : `useEffect` 사용하기**

```tsx
  useEffect(() =&gt; {
    const pageFromURL = parseInt(searchParams.get(&#39;page&#39;) || &#39;1&#39;, 10);
    setCurrentPage(pageFromURL);
  }, [searchParams])
```</code></pre><ul>
<li><p><code>&lt;Link&gt;</code> 이용 : <code>&lt;Link&gt;</code> 태그에 클릭가능하게 만들고자 하는 요소들을 감싸서 넣어주어야 한다는 사실을 인지하지 못하고, <code>&lt;Link ... &gt; &lt;/Link&gt;</code>로 비어있는 <code>&lt;Link&gt;</code>를 사용해서 링크 태그가 잘 작동하지 않았다.</p>
<p>  → <code>&lt;Link&gt;</code>가 전체 element를 감싸게 하기</p>
</li>
</ul>
<ul>
<li><p><code>Detail.tsx</code>에서 <code>useParams()</code>로 <code>id</code> 가져오기 :</p>
<pre><code class="language-tsx">  const id = useParams();
  ...
  useEffect(() =&gt; {
  ...
      const response = await fetch(`https://dummyjson.com/recipes/${id}`);</code></pre>
<p>  와 같이 실행했더니, 브라우저 콘솔에서 fetch link의 id 부분이 깨져있었다.</p>
<p>  → <code>const {id} = useParams&lt;{ id: string }&gt;();</code>로, 상단에서 정의한 <code>RecipeDetail</code>의 type를 이용해서 string을 받아오는 것을 명시해주었다. 그러니 문제가 해결되었다.</p>
</li>
</ul>
<hr>
<h3 id="css">CSS</h3>
<ul>
<li><p><code>classList=&quot;class-name&quot;</code>과 같은 형식이 아니라, 모듈별 css를 이용하며 <code>classList={styles.className}</code>과 같이 css class를 적용했기 때문에
class의 이름을 camelCase로 작성하지 않아서 style class가 제대로 적용되지 않았다.</p>
<p>  ⇒ css class를 애초에 camelCase로 명명하거나, bracket notation으로 <code>classList={styles[&#39;class-name&#39;]}</code>과 같이 이용하자.</p>
</li>
</ul>
<ul>
<li><p><code>Detail</code>의 css는 만들기 귀찮아서 gemini에게 외주맡겼다.</p>
<p>  그랬더니 recipe instructions와 menu info에 있는 <code>&lt;ul&gt;</code>과 <code>&lt;ol&gt;</code>의 <code>&lt;li&gt;</code> 요소들이 정상적으로 줄바꿈되지 않고, <code>inline</code> 형식으로 나열되었다.</p>
<p>  브라우저의 devtools에서 <code>&lt;li&gt;</code>의 computed styles를 확인해 보니, padding이 이상할 만큼 크게 설정되어 있고 <code>float</code> 속성이 있었다. 상위 css sheet에서 무언가 </p>
<pre><code class="language-tsx">  ol, ul {
      display: block;
  }
  li {
      float: none;
      display: list-item;
  }</code></pre>
<p>  위와 같이 초기화해주니 적용되었다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] Fetch, HTTP Protocol, fetch wrapper]]></title>
            <link>https://velog.io/@h-seo-n/JavaScript-Fetch-HTTP-Protocol-fetch-wrapper</link>
            <guid>https://velog.io/@h-seo-n/JavaScript-Fetch-HTTP-Protocol-fetch-wrapper</guid>
            <pubDate>Fri, 03 Oct 2025 10:58:01 GMT</pubDate>
            <description><![CDATA[<h3 id="json--javascript-object-notation">JSON : JavaScript Object Notation</h3>
<p><code>{ key : item, …. , key: item }</code> 형식의 데이터</p>
<p>⇒ API를 사용할때 주고받는 데이터의 형식은 대부분 JSON </p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/c961a518-10d0-4463-af41-43466cc7bd94/image.png" alt=""></p>
<ul>
<li><p><strong>JSON ⊆ Javascript Object</strong></p>
<p>  → 모든 JSON object는 Javascript object이다.</p>
<ul>
<li><p>Javascript object이지만 JSON은 아닌 예시 : object 안에 function이 포함될 경우</p>
<pre><code class="language-jsx">  const user = {
       firstName: &quot;Sam&quot;,
       lastName: &quot;Green&quot;,
       getFullName: function() {
            return `${this.firstName} ${this.lastName}`
            }
  }</code></pre>
</li>
</ul>
</li>
<li><p>JSON의 장점은 API가 무슨 프로그래밍 언어로 쓰였든 간에 JSON을 주고받을 수 있다면 다른 언어로도 API와 소통가능한 것이다.</p>
</li>
<li><p><strong>JSON Operations</strong></p>
<ol>
<li><p><strong>JSON string → Object</strong> : <code>JSON.parse(string)</code></p>
<p> 주로 <strong>API에서 받은 JSON 문자열</strong>을 <strong>자바스크립트 객체</strong>로 변환할 때 사용.</p>
<pre><code class="language-jsx"> const string = &#39;{&quot;firstName&quot;:&quot;Sam&quot;,&quot;lastName&quot;:&quot;Green&quot;,&quot;age&quot;: 32}&#39;;
 const person = JSON.parse(string);
 console.log(person.firstName); // &quot;Sam&quot;</code></pre>
</li>
<li><p><strong>JSON object → string :</strong> <code>JSON.stringify(object)</code></p>
<p> 객체를 <strong>전송/저장에 적합한 문자열(JSON)</strong> 로 변환할 때 사용.</p>
<pre><code class="language-jsx"> const person = {
   firstName: &quot;Sam&quot;,
   lastName: &quot;Green&quot;,
   age: 32
 };

 const string = JSON.stringify(person);
 console.log(string);
</code></pre>
</li>
</ol>
</li>
</ul>
<h2 id="api--http-methods">API &amp; HTTP methods</h2>
<h3 id="api--application-programming-interface"><strong>API : Application Programming Interface</strong></h3>
<p> → 한 소프트웨어와 다른 소프트웨어가 서로 소통(데이터를 주거나 요구하는 등)하게 해주는 “통로” 같은 것.</p>
<p>   “<em>공공데이터 API</em>”라고 하면, 공공데이터를 가지고 있는 기관이(서버) 다른 소프트웨어가 그 정보에 접근할 수 있도록 만든 인터페이스가 그 API라고 할 수 있다.</p>
<p>  <img src="https://velog.velcdn.com/images/h-seo-n/post/0e3e503a-cce4-4b40-96d2-f87c39617496/image.png" alt=""></p>
<p> 위 사진처럼, API는 앱과 서버 사이이 중계자라고 볼 수도 있다.</p>
<p>API를 통해 서버에게 무언가를 요구하는 것을 “<strong>API request</strong>”라고 하고, API에 요청한다고 하기도 한다.</p>
<p> 요청의 결과 돌아오는 값 등을 <strong>API response</strong>라고 한다.</p>
<p>어떻게 요청을 해야 하고, 어떤 형식의 응답이 돌아오고 어떻게 활용해야할지는 주로 API 설명서 (기술 명세서)에서 확인할 수 있다.</p>
<p>⇒ 앱을 개발하면 기능 중에 오늘의 날씨를 표시하게 할 수도 있고, 환율을 보여줘야 할 수도 있는데 이런 개인이 마련하기 힘든 데이터 / 기능을 개발에 사용할 수 있도록 정부 혹은 기업에서 API를 제공하는 것이다.</p>
<p>chatGPT를 생각해보면, 원래 chatGPT는 따로 웹/앱에서 서비스가 존재하는데, 다른 앱에서도 chatGPT를 통한 AI 기능을 제공하는 경우가 많다. 이것도 openAI가 chatGPT API를 (유료로) 제공하기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/6fd4f54a-0649-4039-815e-e7c3a9250fb7/image.png" alt=""></p>
<h3 id="http-protocol--method">HTTP protocol / method</h3>
<ul>
<li><p>API에 규정된 소통 방식의 표준 중 하나가 <strong>HTTP protocol</strong>이다. :</p>
<ul>
<li>특정 서버에 API 요청을 보낼 때, 무엇을 요청하느냐에 따라 요청 URL 뒤의 endpoint(목적지)가 달라진다. 이러한 API 요청을  <strong>HTTP 표준(=http protocol)</strong>에서는 4가지로 분류하는데, 이를 <strong>HTTP method</strong>라고 한다.<br></li>
</ul>
</li>
<li><p><strong>HTTP method</strong></p>
<ul>
<li><p><strong>GET</strong> : 정보를 가져와달라는(”get”) 요청</p>
<ul>
<li><strong>POST :</strong> 특정 정보를 추가해달라는(”post”) 요청</li>
</ul>
</li>
<li><p><strong>PUT</strong> : 특정 정보를 어떻게 수정해달라는(”put” A in B) 요청</p>
</li>
<li><p><strong>DELETE :</strong> 정보를 삭제해달라는 (”delete”) 요청</p>
<p> → Get은 read 요청, 나머지 셋은 수정을 요하는 write 요청.</p>
<p> ∴ POST, PUT, DELETE는 request URL과 별도로, “<strong><em>body</em></strong>”에 부가적인 data를 담아 요청해야 한다.</p>
</li>
</ul>
</li>
</ul>
<hr>

<h1 id="fetchurl"><code>fetch(URL)</code></h1>
<p>대표적인 asynchronous WebAPI 함수로, 브라우저에서 <strong>외부/내부 서버로 요청(request)</strong> 을 보낼 때 사용한다.</p>
<p><code>fetch()</code>의 인자로 요청할 목적지 서버인 <strong>API endpoint(URL)</strong> 를 넣으면, 해당 URL로 요청을 보내고 <strong>응답(response)</strong> 을 받아온다.
응답의 본문은 보통 <strong>JSON 문자열</strong>(정보를 담은 텍스트)이다.</p>
<p>→ 기본적으로 <strong>GET request</strong>이며,</p>
<p><code>fetch(URL, {method: ..., headers: ..., body: ...})</code> 이렇게 추가적인 argument를 넣어줌으로써 <strong>POST, PUT, DELETE</strong> request가 가능하다.</p>
<h2 id="fetch-get-사용법">Fetch (GET) 사용법</h2>
<ul>
<li><p><code>fetch(URL)</code>의 반환값은 <strong>Promise</strong> <br></p>
<p>  → 따라서 반환값을 활용하기 위해 chaining으로 <code>.then(...)</code>, <code>.catch(...)</code>, <code>.finally(...)</code>를 사용한다.<br></p>
<p>  ⇒ 서버에 요청을 보내고 응답을 받을 때까지 무작정 웹을 멈추고 기다리는 것을 막아준다.</p>
<pre><code class="language-jsx">  fetch(URL)
    .then((response) =&gt; {
      return response.json();      // JSON 본문을 파싱할 준비
    });</code></pre>
<p>  <strong>Implicit Return 형식 :</strong> </p>
<pre><code class="language-jsx">  fetch(URL)
    .then((response) =&gt; response.json());</code></pre>
</li>
<li><p><code>response.json()</code></p>
<ul>
<li><p><strong>API response가 JSON 문자열일 때,</strong></p>
</li>
<li><p><code>JSON.parse()</code>처럼 <strong>JSON string → object 변환</strong>.</p>
<p>  다만 차이점은 <code>JSON.parse()</code>는 <strong>synchronous 함수</strong>이지만, <code>.json()</code>은 <strong>Asynchronous 함수</strong>로서 <strong>Promise를 반환</strong>한다.</p>
<p>  → 따라서 parse된 json object 활용을 위해 <strong>다음 단계에서 또 <code>.then(...)</code></strong> 으로 결과를 받아야 한다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="형식">형식</h3>
<pre><code class="language-jsx">fetch(URL)
  .then((response) =&gt; response.json())  
  // 1. response → JSON parsing (Promise)
  .then((data) =&gt; {                     
  // 2. use parsed object
    console.log(data);
  });</code></pre>
<p>⇒ <strong>Promise chaining</strong> : <code>fetch</code>와 <code>response.json()</code> 모두 Promise를 반환하므로, <code>.then()</code>을 두 번 사용해주어야 한다.</p>
<ul>
<li><p><strong>관습적인 명명들 :</strong></p>
<ul>
<li><p><code>response</code> : fetch에서 처음 <code>.then()</code>으로 읽어오는 값, 진짜 데이터를 받기 전의 중간 과정</p>
</li>
<li><p><code>data</code> : <code>response.json()</code>에서 <code>.then()</code>으로 받는 값이자 우리가 관심있는 데이터 (∴ 이름을 data로 할 것을 권장)</p>
<p>  API의 종류마다 데이터 타입이 다르므로 API 명세서 확인 후 (or <code>console.log(data)</code>로 확인) API에 맞게 데이터를 다루어야 한다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="url--endpoint-"><strong>URL &amp; Endpoint</strong> :</h3>
<p>하나의 서버는 <strong>base URL</strong>이 있고, 서버에 무슨 정보를 요청하는지에 따라 <strong>endpoint</strong>가 달라진다.</p>
<blockquote>
<p><strong>요청 보내는 URL = Base URL + endpoint</strong></p>
</blockquote>
<ul>
<li><p>e.g. 트위터 API의 base url : <code>https://twitter.com/api/v1</code></p>
<ul>
<li><p>endpoints :</p>
<pre><code class="language-jsx">   /users.json
   /notifications.json
   /tweets.json
   /account/password.json</code></pre>
</li>
</ul>
<p>→ 실제 fetch url</p>
<pre><code class="language-jsx">     https://twitter.com/api/v1/users.json
      https://twitter.com/api/v1/notifications.json
      https://twitter.com/api/v1/tweets.json
      https://twitter.com/api/v1/account/password.json</code></pre>
</li>
</ul>
<h3 id="fetch-사용-예시-"><strong>fetch 사용 예시 :</strong></h3>
<pre><code class="language-jsx">fetch(”https://jsdemo-3f387-default-rtdb.europe-west1.firebasedatabase.app/chapters/all.json&quot;)
.then(response ⇒ response.json())
.then(data ⇒ console.log(data));</code></pre>
<p>→ 위 API에서 받은 데이터가 다음과 같이 array of object일때,</p>
<pre><code class="language-json">[
  { id: 1, isCompleted: true, title: &quot;Basic Functions&quot; },
  { id: 2, isCompleted: true, title: &quot;Strings&quot; },
  { id: 3, isCompleted: false, title: &quot;Numbers&quot; },
  { id: 4, isCompleted: true, title: &quot;Variables&quot; },
  { id: 5, isCompleted: false, title: &quot;Conditions&quot; },
];</code></pre>
<p>completed chapter만으로 구성된 새로운 array를 만들어서 <code>displayCompletedChapters()</code>에 넣어주고 싶다면 : </p>
<pre><code class="language-jsx">const getChapters = () =&gt; {
  fetch(
    &quot;https://jsdemo-3f387-default-rtdb.europe-west1.firebasedatabase.app/chapters/all.json&quot;
  )
    .then((response) =&gt; response.json())
    .then((data) =&gt; {
      console.log(data);
      const completedChapters = data.filter((data) =&gt; {
        return data.isCompleted == true;
      });
      displayCompletedChapters(completedChapters);
    });
};

function displayCompletedChapters(chapters) {
  console.log(&quot;Received&quot;, chapters);
}

getChapters();</code></pre>
<hr>

<h2 id="handling-fetch-errors-">Handling fetch errors :</h2>
<ul>
<li><p><strong>response status code :</strong>
API에 fetch request를 보낸 후 받은 응답에는 <strong>100~600</strong>의 범위의, <strong>authentiation status code</strong>가 함께 반환되어 응답이 정상적으로 반환되었는지 등의 상태를 알려준다. (i.e HTTP 상태 코드 표)</p>
<ul>
<li><p><strong>1XX (100~199)</strong> : informational response - 정보 제공. 임시 응답으로, 계속 진행하라는 의미</p>
</li>
<li><p><strong>2XX (200~299)</strong> : <strong>성공</strong>적인 응답! → 가장 흔히 볼 수 있는 숫자 범위
e.g. 200 + response text &quot;OK&quot; : 요청이 성공적으로 시행되었으므로 응답이 &quot;OK&quot;라는 뜻</p>
</li>
<li><p><strong>3XX (300~399)</strong> : <strong>redirect</strong> - 특정 url에서 다른 url로 이동한 후 추가 동작이 필요한 시
e.g.
→ 301 + response text &quot;Moved Permanently&quot; : 요청할 URL이 이동되었으니 그 주소로 다시 시도
→ 302 + response text &quot;Found&quot;</p>
</li>
<li><p><strong>4XX (400~499)</strong> : <strong>client error</strong> - 에러의 이유가 클라이언트(나)에게 있다
e.g.
→ 401 + response text &quot;Unauthorized&quot; : 데이터가 인증(authentiation)을 필요로 하는 경우
→ 404 : 존재하지 않는 url을 입력한 경우 (404 not found)</p>
</li>
<li><p><strong>5XX (500~599) : server error</strong> - 에러의 이유가 서버 측에 있다
e.g.
→ 500 + status text &quot;Internal Server Error&quot; : 서버 측 코드에 버그가 있음
→ 504 + status text &quot;Gateway Timeout&quot; : 서버 트래픽 과다</p>
<p>(추가 참고 : <a href="https://hongong.hanbit.co.kr/http-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%ED%91%9C-1xx-5xx-%EC%A0%84%EC%B2%B4-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC/">https://hongong.hanbit.co.kr/http-상태-코드-표-1xx-5xx-전체-요약-정리/</a>)</p>
</li>
</ul>
</li>
<li><p><strong>Error catch</strong><br>↓ 무언가 부족한 코드 예시</p>
<pre><code class="language-jsx">  fetch(URL)
       .then(response =&gt; response.json())
       .then(data =&gt; {
            console.log(data);
       })
       .catch(error =&gt; {
            console.error(error);
  });</code></pre>
<p> 위 코드에서, <code>catch</code>는 <code>fetch()</code>가 반환하는 promise가 rejected state가 될 때 실행 : client-server 연결 문제의 network error의 경우.
  <strong>하지만 <code>fetch()</code>가 정상작동했지만 서버 측에서 에러 코드를 반환한 경우, catch가 에러를 잡지 못함.</strong></p>
<p>  → 즉 <code>fetch.then()</code>안에서 별도로 에러를 다루어주어야 함.</p>
<p>  ⇒ 이 안에서의 구현, 즉 어떻게 에러를 다룰지는 API마다 다름.</p>
<hr>
<p>  json으로 전환되기 전의 response object는 fetch 성공 여부를 확인할 수 있는 <strong>“ok” 필드</strong>를 가지고 있는데, 이는 에러코드(response status code)가 2xx 범위일때(i.e,클라이언트/서버 에러 모두 없는 성공적인 응답일때) <strong>“ok” 값</strong>을 가진다.</p>
<p>  → 이용해서 에러 다룰 수 있음.</p>
</li>
</ul>
<h3 id="완전한-형식"><strong>완전한 형식</strong></h3>
<pre><code class="language-jsx">fetch(URL)
    .then(response =&gt; {
        if(!response.ok) {
            // 4xx or 5xx error
            throw new Error(“API issues.”);
        }
        /* catch로 잡히지 않는 API error을 다룬다.
        =&gt; throw문은 직접 promise를 reject
        */
        return response.json();
    })
    .then(data =&gt; {
        console.log(data);
    })
    .catch(error =&gt; {
    /* catch문에서는 network error을 다룬다. */
        console.error(error);
    });
</code></pre>
<h2 id="fetch-post--put-delete-사용법">Fetch POST,  PUT, DELETE 사용법</h2>
<p>앞서 말했듯 <code>fetch()</code>는 기본적으로 GET 요청이며, <strong>POST / PUT / DELETE 요청</strong>을 하기 위해서는 <code>method</code>, <code>headers</code>, <code>body</code>가 명시된 Javascript Object를 url 다음의 argument에 넣어주어야 한다.</p>
<ul>
<li><p><strong>Headers</strong> : 주로 보내는 요청에 관한 <strong>meta-data</strong>를 첨부하여 넣는다. 대표적인 항목은 <code>“Content-Type”</code>으로, 어떤 형식의 데이터를 주고받는지에 대한 설명이다.</p>
</li>
<li><p><strong>Body</strong> : 요청에 대한 <strong>직접적인 data</strong>를 넣는다. (ex.수정할 데이터)</p>
<ul>
<li><p>JSON object를 <code>JSON.stringify</code>를 통해 string으로 변환하여 넣어주어야 한다.</p>
<p>  → API에서 응답을 받을 때는 string → object 변환이 이루어졌던 것처럼, API에 요청 시 object를 바로 주지 않고 API가 활용할 수 있는 형식인 string으로 변환하는 것이 필요하다.</p>
</li>
</ul>
</li>
</ul>
<p><strong>템플릿</strong> :</p>
<pre><code class="language-jsx">fetch(URL, {
    method: &quot;POST&quot;, // or PUT or DELETE
    headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;
    },
    body: JSON.stringify({
        key1: &#39;value1&#39;, // replace with key/value based on documentation
        key2: &#39;value2&#39;, // same as above (if needed)
    })
})
.then(response =&gt; response.json())
.then(data =&gt; {
    console.log(data); // read server response
});
</code></pre>
<h2 id="fetch-wrapper-사용">Fetch Wrapper 사용</h2>
<ul>
<li><p>웹을 만들다 보면 동일한 base url에다 endpoint만 다른 fetch 문을 많이 사용하게 됨</p>
<p>  → fetch wrapper을 구현해서 사용할 시 더 간결.</p>
<p>  response를 json으로 미리 바꾸어주고, header의 Content-Type가 json으로 미리 설정되어 있도록, <strong>FetchWrapper class</strong>를 구현하여 사용할 수 있다.</p>
</li>
<li><p>e.g.</p>
<ul>
<li><p>fetch wrapper 없이 fetch (GET, PUT)</p>
<pre><code class="language-jsx">  fetch(`fetchURL/notifications/new.json`)
  .then(response =&gt; response.json())
  .then(data =&gt; {
      console.log(data);
  });

  fetch(`fetchURL/write.json`, {
      method: &quot;PUT&quot;,
      headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;
      },
      body: JSON.stringify({
          grade: 18
      })
  })
  .then(response =&gt; response.json())
  .then(data =&gt; {
      console.log(data); // read server response
  });
</code></pre>
</li>
<li><p>fetch wrapper 사용 시 :</p>
<pre><code class="language-jsx">  const API = new FetchWrapper(“fetchURL/”);

  API.get(“notifications/new.json”).then(data =&gt; {
      console.log(data);
  });

  API.put(“write.json”, {
      grade: 18
  }).then(data =&gt; {
      console.log(data);
  });</code></pre>
<p>  → 두번째 argument로 body만 넣어주고, body의 stringify도 fetch wrapper에서 처리하고 있는 구현.</p>
</li>
</ul>
</li>
</ul>
<h3 id="fetch-wrapper-구현">Fetch Wrapper 구현</h3>
<ul>
<li><p>사용하는 API 및 사용자의 목적에 따라 customization이 가능하지만, 
공통적으로는 base URL을 파라미터로 받는 <strong>constructor</strong>, 그리고 endpoint를 파라미터로 받는 <code>get</code>, <code>put</code>, <code>post</code>, <code>delete</code> 각각의 class method가 필요하다.</p>
<p> → <code>get</code>, <code>put</code>, <code>post</code>, <code>delete</code> 메서드가 반환하는 결과에 <code>.then</code>을 이용하여 asynchronous하게 데이터를 활용할 수 있도록, 각 method는 Promise를 return해야 한다. </p>
<p> 추가적으로는 반환되는 응답을 method에서 미리 <code>response.json()</code>을 처리해주거나,
  필요 시 특정 header을 디폴트로 보내게 하거나,
  요청의 body를 알아서 stringify하게끔 구현할 수도 있다.</p>
</li>
</ul>
<p>→ <code>get</code> 이외의 <code>put</code>, <code>post</code>, <code>delete</code> 메서드들은 작동 방식이 비슷하기 때문에, 공통적으로 사용할 수 있는 private helper method를 정의해두고 사용할 수 있다.</p>
<pre><code class="language-jsx">class FetchWrapper {
    constructor(baseURL) {
        this.baseURL = baseURL
    }
    **get**(endpoint) {
        return fetch(this.baseURL + endpoint, {
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;
            }
        }).then(response =&gt; response.json());
    }

    **post**(endpoint, body) {
        return this.#send(&quot;post&quot;, endpoint, body);
    }

    **put**(endpoint, body) {
        return this.#send(&quot;put&quot;, endpoint, body);
    }

    **delete**(endpoint, body) {
        return this.#send(&quot;delete&quot;, endpoint, body);
    }


    #send(method, endpoint, body) {
        return fetch(this.baseURL + endpoint, {
            method: method,
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;
            },
            body: JSON.stringify(body)
        }).then(response =&gt; response.json());
    }
}</code></pre>
<p>→ 필요시 wrapper에서 수행하는 작업에 authentication, error catch 등 필요한 기능을 추가할 수도 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] Asynchronous(비동기) 코드와 Promise]]></title>
            <link>https://velog.io/@h-seo-n/JavaScript-Asynchronous%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%99%80-Promise</link>
            <guid>https://velog.io/@h-seo-n/JavaScript-Asynchronous%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%99%80-Promise</guid>
            <pubDate>Fri, 03 Oct 2025 10:41:20 GMT</pubDate>
            <description><![CDATA[<h3 id="synchronous-callback---위에서-아래로-순차적-실행">Synchronous callback - 위에서 아래로 순차적 실행</h3>
<p>코드 순서가 작업 순서와 마찬가지. 순차적으로 <strong>call stack(first in, last out)</strong>에 넣어지고, 넣어지자마자 바로바로 실행됨</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/68475f63-ee13-47da-89e2-b6d4c0f3e753/image.png" alt="call stack 1"></p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/062dbf59-6ab4-4281-ada6-dfd8e34525e7/image.png" alt="call stack 2"></p>
<h2 id="asynchronous-callback---비동기-처리-함수">Asynchronous Callback - 비동기 처리 함수</h2>
<p>→ <code>fetch</code>, <code>setTimeout</code> 과 같은 Web API에서 제공되는 비동기 처리 함수를 이용할 수 있다.</p>
<p>ex.</p>
<pre><code class="language-jsx">setTimeOut(callback, milliseconds)</code></pre>
<ul>
<li><p><code>setTimeOut</code> : 실행될 callback function을 지정된 시간 후에 실행되도록 예약(queue)하는 함수.</p>
<p>  → 기존의 프로그램 흐름에서 벗어나 asynchronous하게 실행됨.
  <img src="https://velog.velcdn.com/images/h-seo-n/post/dd775636-fa64-42e9-9d9f-ec90b2ae4509/image.png" alt="call stack 1"></p>
<p>  task queue가 비어있는지 감지하고 callback을 call stack으로 이동하는 것은 javascript의 <strong>event loop의 역할.</strong></p>
</li>
</ul>
<p>⇒ <code>fetch</code>와 같은 API를 사용할 시, API request에 대한 response는 asynchronous할 것. </p>
<p>(서버에 요청을 보내면 요청이 올 때까지 시간이 좀 걸림 → 요청이 전부 수행될때까지 브라우저를 무작정 멈추어 둘 수는 없으므로, 서버의 요청이 도착하면 데이터 처리 코드를 실행하도록 aynchronous한 방식으로 코드를 작성해야 한다.)</p>
<h3 id="callback-pattern-">callback pattern :</h3>
<ul>
<li><p><strong>callback</strong> : function의 파라미터로 다른 function definition을 넣는 것.</p>
<p><strong>→ callback pattern</strong> : asynchronous한 코드를 작성하기 위해 callback을 이용할 수 있다.</p>
</li>
<li><p><strong>success callback</strong> : 함수 수행 성공 이후 실행되는 asynchronous callback</p>
<ul>
<li><p>e.g. 아래 <code>welcomeUser</code>에서 두 번째 인자로 전달해주는 callback은 success callback이다.</p>
<pre><code class="language-jsx">  // usage
  welcomeUser(&quot;Sam&quot;, () =&gt; {
      console.log(&quot;Done welcoming user&quot;);
  });

  // definition
  const welcomeUser = (name, callback) =&gt; {
      setTimeOut(() =&gt; {
          console.log(`Welcome ${name}`);
          callback(); //success callback
          }, 1_000);
  }</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><strong>error callback</strong> : 함수 수행에 모종의 이유로 실패할 시 실행되는 asynchronous callback
(e.g.  <code>reason =&gt; {console.error(reason);</code>), 주로 success callback 다음 순서의 인자로 입력된다.</li>
</ul>
<ul>
<li><p>e.g. success callback이 연산의 결과를 parameter로 받는 경우</p>
<pre><code class="language-jsx">  export const sumGrades = (grades, callback) =&gt; {
      // simulate expensive operation
      setTimeout(() =&gt; {
          const sum = grades.reduce((total, current) =&gt; total+current, 0);
          if (callback) {
              callback(sum); // call success callback with the sum
              }
          }, 1_000);
      }</code></pre>
<pre><code class="language-jsx">  const calculateSum = (grades) =&gt; {
      sumGrades(grades, result =&gt; {
          console.log(`The sum is : ${result}`);
          });
      }
      calculateSum([18,10]);</code></pre>
</li>
</ul>
<h3 id="callback-pattern-vs-promise-">Callback pattern vs Promise :</h3>
<p><strong>Promise</strong>: Promise의 결과에 따라 callback을 수행할 수 있게 하는 wrapper의 일종이라고 생각할 수 있음.</p>
<p>→ Promise를 이용하여 asynchronous code를 작성하는 것이 보다 직관적인 syntax</p>
<p>e.g.1. callback을 이용할 경우 생길 수 있는 callback hell ..</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/d537dd08-540f-4e00-b50e-799921a470af/image.png" alt="callback hell"></p>
<ul>
<li><p>비교 예시  <br></p>
<p>  <strong>callback pattern :</strong> </p>
<pre><code class="language-jsx">  sumTemperatures(temperatures, value =&gt; {
      console.log(value);
  }, reason =&gt; {
      console.error(reason);
  });</code></pre>
<p>  <strong>→ promise</strong> : <code>.then</code>과 <code>.catch()</code> 로 함수 실행 순서를 정해줄 수 있으므로 callback 인자 순서를 상관하지 않아줘도 됨, 보다 직관적.</p>
<pre><code class="language-jsx">  sumTemperatures(temperatures)
      .then(value =&gt; {
          console.log(value);
      })
      .catch(reason =&gt; {
          console.error(reason);
      });</code></pre>
</li>
</ul>
<p>그래서 promise가 뭔데?</p>
<h2 id="promises">Promises:</h2>
<ul>
<li><p><strong>비유</strong> : 키오스크에서 음식 주문 후 받는 번호표가 Promise object와 비슷하다고 볼 수 있다. (주문 후 아직 음식이 없지만, 음식이 될 수 있는 것.)</p>
<p>  주문이 그냥 배달될수도(<em>resolve</em>) 취소될수도(<em>rejected</em>) 있고, <code>.then()</code>, <code>.catch()</code> method로 배달과 취소 각각의 경우를 다룰 수 있다. (e.g. then() : 음식이 됨, catch() : 음식이 되지않음 ..)</p>
</li>
<li><p><strong><code>.then()</code> , <code>.catch()</code></strong> : Promise를 반환하는 함수에만 쓸 수 있는 method. <code>fetch</code>와 <code>getUserMedia</code> 등 여러 WebAPI가 promise를 활용하기 때문에 중요하다.
→ 각각 Promise 실행 성공/실패의 경우 무엇을 할지를 다룬다.</p>
</li>
<li><p>e.g. 아래 <code>wait()</code> 함수 : 인자로 기다릴 시간을 받고, promise를 결과로 반환</p>
<pre><code class="language-jsx">  const wait = milliseconds =&gt; {
      return new Promise(resolve =&gt; {
              setTimeout(() =&gt; {
                      resolve();
                  }, milliseconds);
              }, reject =&gt; {
                  reject(&quot;function rejected&quot;);
              });
  }</code></pre>
<p>  → 반환되는 Promise의 파라미터 :</p>
<ul>
<li><p><code>resolve</code>  : success callback과 같은 역할, <code>wait.then(resolve)</code>와 같은 형식으로 전달됨.</p>
<ul>
<li><code>reject</code> : error callback과 같은 역할, <code>wait.catch(reject)</code>와 같은 형식으로 전달된다.</li>
</ul>
<p>⇒ 이때 <code>.then()</code>과 <code>.catch()</code>는  wait이 반환하는 <strong>Promise</strong>에 불러져서 promise에 callback을 전달해주는 역할을 함.</p>
<pre><code class="language-jsx">wait(1_000).then(() =&gt; {
 console.log(&quot;waited 1 second&quot;);
})
.catch(() =&gt; {
 console.log(&quot;rejected&quot;);
});
;</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>Promise의 state</strong></p>
<ul>
<li><p><strong>pending</strong> : promise가 외부 함수에 의해 반환되었지만 아직 실행되지 않은 상태 (e.g. 성공/실패 결과가 정해지지 않음)</p>
</li>
<li><p><strong>fulfilled</strong> : promise 실행이 성공하여 이에 따른 success callback의 실행까지 완료됨</p>
<p>  → promise가 fulfilled 상태가 되면 <code>.then(callback)</code>이 실행된다</p>
</li>
<li><p><strong>rejected</strong> : promise가 성공적으로 실행되지 못했을 때의 state (ex. API 응답이 네트워크 이슈로 성공적으로 수령되지 못했을 때)</p>
<p> → 이때 실행할 에러 처리용 callback을 <code>.catch(callback)</code>으로 전달해줌으로써 에러를 다루는 로직을 구현할 수 있음(에러메시지 띄우기 등)</p>
</li>
</ul>
</li>
</ul>
<pre><code>e.g. :

```jsx
console.log(wait(1_000));
// console : Promise {&lt;pending&gt;}
```

```jsx
const result = wait(1_000);
console.log(result);
// console : Promise {&lt;pending&gt;}
```

→ Promise의 callback 실행을 위해서는 1초가 지나야 하기 때문에, 함수를 부르고 난 직후의 출력은 &lt;pending&gt; 상태를 보여준다.

```jsx
const result = wait(1_000);
console.log(result);
// Promise {&lt;pending&gt;}

result.then(() =&gt; {
    console.log(result);
    // Promise {&lt;fulfilled: undefined&gt;}
});
console.log(result);
// Promise {&lt;pending&gt;}
```

→ `wait()`이 반환하는 promise가 result에 담겨있으므로, result에 `.then`을 사용할 수 있다.

⇒ result.then() 안의 `console.log(result)`만 promise가 fulfilled 상태가 된 이후 실행되므로, 출력값은 아래와 같다

```jsx
Promise {&lt;pending&gt;}
Promise {&lt;pending&gt;}
Promise {&lt;fulfilled: undefined&gt;}
```</code></pre><h3 id="promise-만들기">Promise 만들기</h3>
<ul>
<li><p>promise를 사용하기 위해서는 Promise를 반환하는 함수를 정의해야 한다.  <br><br></p>
<p>  <strong>→ 일반적인 형식 :</strong> </p>
<pre><code class="language-jsx">  const function = (parameter) =&gt; {
                                          // Executor!
      return new Promise((resolve, reject) =&gt; {
          /* 함수에서 실행할 statement들 */
          resolve(); // promise가 성공적이면 실행될 .then(callback)
          reject(); // promise가 실패하면 실행될 .catch(callback)
      });
  }</code></pre>
<p>  이때 Promise가 초기화될때 받는 argument를 <strong><code>executor(()⇒{})</code></strong>이라고 한다. <br></p>
<p>  Promise가 실행 완료되었을 때(i.e, fulfilled state가 되었을 때) 실행하는 callback인 <code>resolve()</code>함수는 executor의 첫번째 인자이다. <br></p>
<p>  Promise가 실행 실패했을 때(i.e, rejected state가 되었을 때) 실행하는 callback인 <code>reject()</code>함수는 executor의 두번째 인자이다.  <br></p>
<p>  즉 Promise의 argument인 <strong>executor</strong>의 <strong>argument ()</strong>에 <strong>사용할 callback들(resolve, reject)</strong>이 들어가고, <strong>body {}</strong> 에는 <strong>그 callback들을 promise로 활용하는 function의 내용</strong>이 있다.</p>
</li>
</ul>
<h3 id="resolving-data">Resolving data</h3>
<ul>
<li><p>Promise가 성공적으로 실행되어 ‘fulfilled’ state가 되었을 때, promise가 “resolve”(해결) 되었다고 한다.</p>
<p>  → ”<strong>*promises can resolve with data</strong>”* : promise는 외부 함수(자신을 반환하는 함수)가 다루는 data를 자신 내부에서 활용할 수 있다.</p>
<p>  → 즉, <code>.then(resolve)</code>으로 전달되는 <code>resolve</code> 함수는, argument로 외부에서 전달될 data를 넣어줌으로써 이를 활용할 수 있다. ⇒ <code>resolve(data)</code></p>
<p>  e.g. 실제 구현의 경우 외부 API에서 반환되는 data를 활용하는 코드를 작성할 수 있다.</p>
</li>
<li><p>예시 : 사용자가 입력한 milliseconds 만큼 기다렸다가, 기다린 시간을 초 단위로 console에 보여주는 resolve callback을 실행하는 wait 함수의 구현</p>
<pre><code class="language-jsx">  const wait = milliseconds =&gt; {
      return new Promise(resolve =&gt; {
          setTimeout(() =&gt; {
          resolve(milliseconds / 1_000);
          }, milliseconds);
      });
  }
  wait(2_000).then((data) =&gt; {
      console.log(data); // 2
  });</code></pre>
</li>
</ul>
<h3 id="rejecting-data">Rejecting data</h3>
<p>executor의 두번째 callback argument는 Promise가 rejected state일때 실행되는 <code>.catch(callback)</code>과 동일한 <code>reject()</code> 함수이다.</p>
<p><code>resolve()</code>와 같이 <code>reject()</code>도 함수의 data를 전달받아 이용할 수 있다.</p>
<ul>
<li><p>e.g. 항상 reject되는 함수 :</p>
<pre><code class="language-jsx">  const alwaysFail = () =&gt; {
      return new Promise((resolve, reject) =&gt; {
          reject(&quot;Failed. That&#39;s the only thind I do.&quot;);
      });
  }
  alwaysFail()
      .then(() =&gt; {
          // never called
  }).catch(data =&gt; {
          console.error(data);
  });</code></pre>
</li>
</ul>
<h3 id="promise-chaining">Promise chaining</h3>
<p>resolve, reject와 같이 promise에서 실행하는 callback들도 Promise를 반환할 수 있다. 이 경우 다음과 같은 promise chaining이 가능하다.</p>
<ul>
<li><p>fetch example</p>
<pre><code class="language-jsx">  fetch(&quot;some-url&quot;)
      .then(response =&gt; response.json())
      .then(data =&gt; {
          console.log(data);
      });</code></pre>
</li>
</ul>
<h3 id="promisefinally-"><code>Promise.finally()</code> :</h3>
<ul>
<li><p><code>.then()</code>은 promise가 성공적으로 실행돼서 fulfilled 상태가 되면 수행하고, <code>.catch()</code>는 promise 실행에 실패해서 rejected 상태가 되면 실행한다. <br></p>
<p>  ⇒ <code>.finally()</code>는 fulfilled든 rejected든 상관없이 <strong>pending 상태에서 벗어나면</strong> callback을 실행한다. <br></p>
<p>  ⇒ <code>.then()</code>과 <code>.catch()</code>에서 공통적으로 실행하는 statement가 있으면, <code>.finally()</code>로 깔끔하게 넣어줄 수 있음. (e.g. <code>console.log(”finished”);</code> ) <br></p>
</li>
<li><p>ex. 로딩을 시작하고 끝내는 <code>startLoader();</code> <code>stopLoader()</code> 함수를 가상의 API 함수 <code>getWeatherIn()</code>의 시작과 끝에 각각 두고 싶을 때 (기상정보가 반환됐을 때 loader을 안보이게 하는 등)</p>
<pre><code class="language-jsx">  startLoader();
  getWeatherIn(&quot;Amsterdam&quot;)
      .then(data =&gt; {
              console.log(data);
          })
      .catch(error =&gt; {
              console.error(error);
          })
      .finally(() =&gt; {
              stopLoader();
      });</code></pre>
</li>
</ul>
<h3 id="promiseall--promiseany"><code>Promise.all()</code> &amp;<code></code> <code>Promise.any()</code></h3>
<p><strong><code>Promise.all()</code></strong> : </p>
<ul>
<li><p>method <code>.all()</code>이 인자로 받는 모든 promise가 fulfilled일때, <code>.all()</code>을 부른 Promise 또한 fulfilled 상태가 되고, 그 외에는 reject 상태가 된다. <br></p>
<p>  → 여러 개의 promise를 동시에 실행하고 모든 promise가 성공할 때까지 기다릴 수 있다.</p>
</li>
<li><p>e.g. Javascript file과 CSS file을 동시에 불러오는데, 두 파일 모두 불러오기 전까지 component를 render하기 싫을 때.</p>
<pre><code class="language-jsx">  Promise.all([promise1, promise2]).then(values =&gt; {
          console.log(values);
  }).catch(error =&gt; {
          console.error(error);
  });</code></pre>
</li>
</ul>
<p><strong><code>Promise.any()</code>  :</strong></p>
<ul>
<li><p><code>.all()</code>과 사용법이 비슷하지만, 인자로 받은 모든 promise가 아니라 그 중 하나의 promise라도 성공하면 가장 처음 성공하는 promise의 data를 받아온다.</p>
</li>
<li><p>e.g.</p>
<pre><code class="language-jsx">  Promise.any([promise1, promise2]).then(value =&gt; {
      console.log(value);
  }).catch(error =&gt; {
      console.error(error);
  });</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] Private class field & method (`#` 붙이기)]]></title>
            <link>https://velog.io/@h-seo-n/Javascript-Private-class-field-method-%EB%B6%99%EC%9D%B4%EA%B8%B0</link>
            <guid>https://velog.io/@h-seo-n/Javascript-Private-class-field-method-%EB%B6%99%EC%9D%B4%EA%B8%B0</guid>
            <pubDate>Fri, 05 Sep 2025 11:17:36 GMT</pubDate>
            <description><![CDATA[<p>원래는 javascript에 private class field를 정의하는 문법이 없어서, instance variable의 이름 앞에 <code>_</code>(underbar)을 붙이는 식으로만 했다. (실제 기능 X)</p>
<p>➡️ 이제는 <strong>instance variable 이름 앞에 <code>#</code>을 부티면 private field로 취급된다.</strong></p>
<ul>
<li><p>private class field는 constructor 안에서 선언될 수 없고, <strong>밖에서 선언된 이후 constructor에서 값을 할당하는 식</strong>으로 진행되어야 한다.</p>
<pre><code class="language-Javascript">class User {
     #votingAge;

     constructor(){
          this.#votingAge = 18;
     }
} </code></pre>
</li>
<li><p>private class field의 유용한 점 :
  <strong>class 바깥에서 field의 값을 임의로 수정할 수 없다</strong>.
  합법적인 수정 경로인 setter 함수를 정의해준다면 가능하다.
  또한, setter 함수에서 값에 대한 제약을 걸어두기 용이하다.</p>
<pre><code class="language-Javascript"> class User {
   #votingAge = 18;

   get votingAge(){ return this.#votingAge;}

   set votingAge(age) {
        if (age&gt;= 18) this.#votingAge = age;
   }
    }

   const user = new User();
   user.votingAge = 10; // invalid
   console.log(user.votingAge); // still 18
   user.votingAge = 20; // meets check
   console.log(user.votingAge); // changed to 20
</code></pre>
<hr>
</li>
<li><p><em>Private class method*</em> :
=&gt; class instance method의 이름 앞에도 <code>#</code>를 붙이면 private class method가 되어서 class 정의 내부에서만 사용 가능하다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Javascript] Prototype Chain 설명]]></title>
            <link>https://velog.io/@h-seo-n/Javascript-Prototype-Chain-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@h-seo-n/Javascript-Prototype-Chain-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Fri, 05 Sep 2025 10:56:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/h-seo-n/post/2685985b-be3e-412f-87cb-170a4c9c954b/image.png" alt="thumbnail">
    <a href="https://www.google.com/search?sca_esv=45f0d4426cbb3399&amp;udm=2&amp;fbs=AIIjpHxU7SXXniUZfeShr2fp4giZ1Y6MJ25_tmWITc7uy4KIeioyp3OhN11EY0n5qfq-zENyQuF3_WaPI4Qgb6AZzy-eu943xiXzczfL63eXOkOdEpPdUfGkB7DiluIjmgihL9nMjb8F41_WQHOER6pX6_4sYB7CXpwE3vFs8Kebf2_OLa7Bh5Dvy8zA_9z0ByoJa78oFJVAqsJMDlO1MyCwyegQ_FbEDw&amp;q=prototype+chain&amp;sa=X&amp;ved=2ahUKEwigjrzZusGPAxUdklYBHR6AFUwQtKgLegQIGRAB&amp;biw=1512&amp;bih=780&amp;dpr=2#vhid=WIrmafKrnJPR2M&amp;vssid=mosaic">이미지 출처</a></p>
<p>javascript에서 모든 object는 (특정 class, 혹은 원래 존재하는 primitive type의 instance) 하나의 큰 상위 <code>Object</code> class의 <code>prototype</code>를 계승한다.
이때 <strong><code>Object</code> class</strong> - <strong>parent class</strong> - <strong>child class</strong> 사이에서의 <code>prototype</code>에 대한 계승을 <strong>Prototype Chain</strong>이라 한다.</p>
<blockquote>
<p><strong>Prototype 정의 참고</strong> :
    <a href="https://velog.io/@h-seo-n/JavaScript-Class-vs.-Function-%EC%82%AC%EC%8B%A4-%EB%91%98%EC%9D%B4-%EA%B0%99%EC%9D%8C">[JavaScript] Class == Function &amp; Prototype Inheritance
  <img src="https://velog.velcdn.com/images/h-seo-n/post/a981f3e3-5b58-4018-8855-8f1b3f310395/image.png" width="60%">
</a></p>
</blockquote>
<h3 id="inheritance와-prototype-chain-">Inheritance와 prototype chain :</h3>
<pre><code class="language-Javascript">  class Parent {
    parentMethod() {
        // 
    }
  }

  class Child extends Parent {
      childMethod() {
          //
      }
  }

  const child = new Child();</code></pre>
<ul>
<li><p><code>Object.getPrototypeOf(child)</code>
  -&gt; 실행 결과 : <code>Parent {}</code>, <code>Child</code> class의 prototype는 <code>Parent</code>의 prototype를 &#39;원형(=prototype)으로 한다.</p>
</li>
<li><p><code>Object.getPrototypeOf(Object.getPrototypeOf(child))</code>
  -&gt; 실행 결과 : <code>{}</code>, <code>Parent</code> class의 prototype는 기본적인 <code>Object.prototype</code>를 &#39;원형&#39;으로 한다.</p>
</li>
<li><p><code>Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(child)))</code>
  -&gt; 실행 결과 : <code>[Object: null prototype] {}</code>, Object보다 상위의 class, 즉 <code>Object.prototype</code>보다 <strong>prototype chain</strong>에서 상위에 있는 것은 null 뿐이다.</p>
<p>➡️ 이런 꼬리물기와 같은 inheritance 관계가 바로 <strong>prototype chain</strong>이다.</p>
<br>
### **Takeaway** 
이것 때문에 `Child` class에서 `Parent` class의 `parentMethod()`를 explicitly override하지 않고도 부를 수 있음. (`child.parentMethod()`)
-> `extends`를 이용한 inheritance는 prototype의 모든 member function을 넘겨주는 식으로 작동하는 prototype inheritance라는 사실과도 연관이 있음.
<br>

</li>
</ul>
<ul>
<li><strong>예시</strong>:<pre><code class="language-Javascript">class Welcome
    sayHello() {
        return &quot;Hello World!&quot;;
    }
}
const welcome = new Welcome();
welcome.sayHello();</code></pre>
위 코드에서 <code>welcome</code> 인스턴스는 class Welcome의 인스턴스이므로 <code>Welcome.prototype</code>에 있는 Welcome의 class method에 접근할 수 있는데, 이 class Welcome은 <code>Object</code> class를 계승하므로 인스턴스가<code>Object.prototype</code>에 있는 모든 것에 접근할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] Class == Function & Prototype Inheritance]]></title>
            <link>https://velog.io/@h-seo-n/JavaScript-Class-vs.-Function-%EC%82%AC%EC%8B%A4-%EB%91%98%EC%9D%B4-%EA%B0%99%EC%9D%8C</link>
            <guid>https://velog.io/@h-seo-n/JavaScript-Class-vs.-Function-%EC%82%AC%EC%8B%A4-%EB%91%98%EC%9D%B4-%EA%B0%99%EC%9D%8C</guid>
            <pubDate>Fri, 05 Sep 2025 08:26:40 GMT</pubDate>
            <description><![CDATA[<p>사실 JS에서의 class는 c++, Java같은 하나의 별개 object로서의 class가 아니라, <strong>function</strong>에 더 가깝다.
➡️ &quot;class&quot;는 이해 + 코딩을 돕기 위한 <strong>&quot;syntatic sugar&quot;</strong> 일 뿐. (문법적 설탕; 같은 원리의 코드를 이용 및 이해하기 쉽게 새로운 형식으로 포장한 것.)
➡️ JavaScript에서 class를 만들고, <code>console.log(typeOf(classname));</code>을 실행해볼 시 직접 쉽게 확인할 수 있다.</p>
<h3 id="function-형식으로-표현한-class">function 형식으로 표현한 class</h3>
<ul>
<li><p><strong>class 정의</strong> : 마치 constructor을 정의하듯 class를 정의함</p>
<pre><code class="language-JavaScript">function classname (instanceVariable) {
    // constructor contents
}</code></pre>
</li>
<li><p><strong>instance method</strong> : <code>classname.prototype.methodname = ...</code>의 형태로 따로 정의해준다.
  예시 : <code>width</code>, <code>height</code> 두 개의 instance variable과 <code>isSquare()</code> 하나의 class method를 가지는 class <code>Rectangle</code> </p>
<pre><code class="language-JavaScript">function Rectangle = (width, height) {
      this.width = width;
    this.height = height;
  }
Rectangle.prototype.isSquare = function () {
      return this.width === this.height;
 }</code></pre>
<p>아래 class syntax 코드와 비교 </p>
<pre><code class="language-Javascript">class Rectangle {
        constructor(width, height) {
            this.width = width;
            this.height = height;
        }
        isSquare() {
            return this.width === this.height;
        }
    }</code></pre>
</li>
</ul>
<h3 id="prototype는">prototype는?</h3>
<p>특정 function (즉 class)의 모든 instance method를 포함하는 object. (최상위 class인 object에게서, 모든 하위 class instance들이 상속받는 property라 할 수 있다.)</p>
<ul>
<li><p><strong>prototype로 <code>classname</code> class에 <code>funcname</code> 클래스 메서드 추가하기</strong></p>
<pre><code class="language-Javascript">classname.prototype.funcname() = function(){...}</code></pre>
<p>  위와 같이 <code>classname.prototype</code>에 <code>funcname</code> method를 추가하면, 모든 <code>classname</code> instance가 이 method를 공유하며 method에 접근할 수 있다.
  또한 위의 <code>Rectangle</code>에서 보였듯, class의 <code>prototype</code>에 추가된 method 내부에서는 <code>this.</code>문법으로 class instance variable에 접근할 수 있다. (class method로 치부되므로)</p>
</li>
<li><p>예시 :<br>
  <code>String.prototype.trim()</code> ➡️ String instance에 <code>trim()</code>을 부를 수 있다는 뜻 (즉 trim은 <code>String</code>에 대한 instance method임)</p>
  <br>

</li>
</ul>
<h3 id="프로토타입-상속---prototypical-inheritance">프로토타입 상속 - Prototypical Inheritance</h3>
<ul>
<li><p><strong>일반적인 상속의 한계</strong> :
  한 클래스를 상속Inherit하면, 그 클래스가 가지고 있는 <strong>모든 member function(=instance method)</strong>가 함께 온다. 
  상속받을 parent class의 모든 member function이 필요하다면 괜찮지만, 일부만 필요한 경우에는 다소 쓸모없는 측면이 있다. (ex. 원치 않는 일정이 있는 패키지 여행의 비애)
  이를 극복할 수 있는 것이 Javascript의 <strong>prototype inheritance</strong>이다.</p>
  <br></li>
<li><p><strong>prototype inheritance</strong> : 원하는 instance method만 골라담아 class를 정의할 수 있는 상속.</p>
<ul>
<li><p>앞에서 <strong>class==function</strong>임을 이야기하면서, function 표현법으로 class에 instance method를 추가하기 위해선 그 class(=function) type의 prototype에 메서드를 추가하면 된다고 설명했다. <br>
➡️ 비슷하게 새로운 class를 정의할 때, 다른 class(=parent class)에서 가져오고 싶은(&#39;상속&#39;받고 싶은) instance method가 있다면,</p>
<pre><code class="language-Javascript">  class1.prototype.func1 = class2.prototype.func2;</code></pre>
<p>  와 같이 parent class prototype에서 메서드를 가져와서 child class prototype에 추가하는 방식으로 원하는 instance method만 선택적으로 상속받을 수 있다!<br>
➡️ 이렇듯 일반적인 <code>extends</code>를 이용하는 상속과는 달리, 클래스 전체가 아닌 원하는 method들만 prototype를 통해 상속받는 것을 <strong>Prototype Inheritance</strong>이다.</p>
<p>즉 <strong>prototype inheritance</strong>를 사용하면, 다른 class의 method를 원하는 대로 조합해서 새로운 class를 만들 수 있다.</p>
</li>
</ul>
</li>
<li><p><strong>ex</strong>. prototype inheritance로 <code>GorillaBanana</code> 클래스 만들기</p>
<ul>
<li><p>function syntax :</p>
<pre><code class="language-Javascript">    function Gorilla() {
    }
    function Banana() {
    }
    function GorillaBanana() {
    }

    Gorilla.prototype.eat = function() {
        console.log(&quot;Gorilla eats&quot;);
    }

    Banana.prototype.peel = function() {
        console.log(&quot;Banana peeled&quot;);
    }

    // Extend our GorillaBanana with the Gorilla&#39;s eat() method
    GorillaBanana.prototype.eat = Gorilla.prototype.eat;

    // Extend our GorillaBanana with Banana&#39;s peel() method
    GorillaBanana.prototype.peel = Banana.prototype.peel</code></pre>
</li>
<li><blockquote>
<p>eat, peel 메서드만 상속할 수 있다.
 <code>extends</code> 를 사용하지 않고 class를 정의한 후 prototype를 이용하여 <code>Gorilla</code> class에서는 <code>.eat</code>만, <code>Banana</code> class에서는 <code>.peel</code>만 가져온다!
 ⇒ 고릴라를 만들고 싶다고 해서 전체 정글을 가져오는 대신, 바나나를 까서 먹는 고릴라만 가져올 수 있는 것</p>
</blockquote>
<ul>
<li><p>class 형식 :</p>
<pre><code class="language-Javascript">     class Gorilla {
         eat() {
             console.log(&quot;Gorilla eats&quot;);
         }
     }

     class Banana {
         peel() {
             console.log(&quot;Banana peeled&quot;);
         }
     }

     class GorillaBanana {}

     // Selectively copy the methods
     GorillaBanana.prototype.eat = Gorilla.prototype.eat;
     GorillaBanana.prototype.peel = Banana.prototype.peel;

     const gb = new GorillaBanana();
     gb.eat();  // &quot;Gorilla eats&quot;
     gb.peel(); // &quot;Banana peeled&quot;</code></pre>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<br>

<h3 id="일반적인-extends를-이용한-상속-뒤에도-사실-prototypical-inheritance가-작동한다">일반적인 <code>extends</code>를 이용한 상속 뒤에도 사실 prototypical inheritance가 작동한다.</h3>
<p>⇨ parent class의 모든 member function을 가져오는 형태로!
    즉, Javascript에서 inheritance의 기본 원리는 prototype inheritance라는 것.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git, github 사용법 정리]]></title>
            <link>https://velog.io/@h-seo-n/Git-github-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@h-seo-n/Git-github-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 05 Sep 2025 07:26:21 GMT</pubDate>
            <description><![CDATA[<h3 id="git이란"><strong>git</strong>이란?</h3>
<p>코드 파일이 변경해 온 역사를 기록해둘 수 있는, 버전 관리 시스템.</p>
<ul>
<li><p><code>git init</code> :</p>
<p>  로컬 폴더 위치를 git으로 관리할 수 있게끔 git에 경로를 등록하여, 해당 폴더를 <strong>git local repository</strong>로 만든다. 폴더 내에 <code>.git</code> 폴더가 생성되며, 폴더 내 모든 변경사항이 <code>.git</code>내에 기록된다. 
  (새로 파일을 만들 때, 즉 첫 initialization 단계에서만 실행하면 된다.) <br></p>
<p>  (<strong>repository</strong> - 한 프로젝트의 파일과 버전 이력을 관리하는 공간으로, <code>.git</code> 폴더 등 로컬 환경에 있는 저장소를 local repository, github과 같이 온라인 서버에 있는 저장소를 remote repository라 한다.)</p>
</li>
</ul>
<hr>

<h3 id="변경사항-git에-기록하기-local">변경사항 git에 기록하기 (local)</h3>
<ul>
<li><code>git add</code> : git system이 파일 변경을 추적할 수 있게 파일을 ‘등록’한다.</li>
<li><code>git commit</code> : 파일 변경 사항을 기록해 둔다.<ul>
<li><strong>commit</strong> : 변경 사항을 저장할 수 있는 가장 작은 단위이다. 각 commit마다 구체적인 변경 사항이(어느 줄이 어떻게 바뀌었는지) 표시되어 쉽게 기록할 수 있다.</li>
<li><code>git commit -m “message”</code> : message에 무엇을 바꾸었는지 간략하게 설명을 작성할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="변경사항-git에-기록하기-remote">변경사항 git에 기록하기 (remote)</h3>
<p>위의 git init, git add와 git commit이 로컬 git 저장소에 변경사항을 기록하는 방법이라면, Github과 같은 온라인 서버에 원격 저장소를 만들기 위해서는 <code>git remote</code> 와 <code>git push</code> 를 이용할 수 있다.</p>
<ul>
<li><p><code>git remote add origin [https://github.com/nickname/repository-name.git](https://github.com/nickname/repository-name.git)</code>  : 로컬 저장소 위치를 원격저장소인 github repository와 연결시킨다.</p>
</li>
<li><p><code>git push {원격저장소} {branch}</code> : 명시한 원격저장소의 branch에 commit 내역을 “push”, 즉 저장한다. (<code>git add</code> 및 <code>git commit</code> 이후 실행)</p>
<ul>
<li><p>ex. <code>git push -u origin master</code> :</p>
</li>
<li><p><code>-u</code> 옵션 : <code>--set-upstream</code> 과 같은 뜻으로, 앞서 명시해준 원격저장소(<code>origin</code>)의 <code>master</code> branch와 해당 로컬 저장소 사이에 연결관계를 형성함.  <br></p>
<p>→ <code>git push</code> 만으로도 연결관계를 형성한 원격저장소에 push 가능.</p>
</li>
</ul>
</li>
</ul>
<p>  <img src="https://velog.velcdn.com/images/h-seo-n/post/05a657c1-f386-4019-93cd-851b50ae56d6/image.png" alt="explanatory image">
                (waffle studio 23.5 OT)</p>
<hr>
<h3 id="branch-"><strong>branch</strong> :</h3>
<ul>
<li><p>‘평행세계의 코드’라고 볼 수 있다. 원본 코드 파일들이 있는 main에서, 복사본인 branch들을 만들고 그곳에서 원본 코드에 영향을 주지 않으며 코드를 작성할 수 있다. 이후 main의 코드와 합칠 수 있다.</p>
<p>  → 코드의 원본 파일을 영향주지 않으면서 코드 작성을 하고 싶을때 이용할 수 있다.</p>
<ul>
<li><p>branch 만들기 : <code>git branch &lt;branch-name&gt;</code></p>
</li>
<li><p>branch로 이동 : <code>git switch &lt;branch-name&gt;</code></p>
</li>
<li><p>branch의 코드를 main과 합침 : <code>git merge &lt;from-branch&gt;</code></p>
<p>또한 협업 시, 여러 사람이 서로 다른 작업을 할 때 하나의 공동 작업공간 (organization repository)에서 각각 branch를 만들어 독립적인 작업 영역을 만들고 이후 작업이 완료되면 main으로 merge한다.</p>
<p>⇒ 공동 repository에서 개인 branch를 복사하는 것을 repository를 ‘<strong>fork</strong>’한다고 하며, 그리고 merge 이전에 공동작업자들에게 코드 변경 사항에 대한 확인을 보내는 것을 ‘<strong>Pull Request</strong>’ 즉 PR이라 한다.</p>
</li>
</ul>
</li>
<li><p><code>fork</code> repository : 외부 서버에 있는 repository를 내 repository로 복사해오는 것이라 볼 수 있다.</p>
</li>
</ul>
<h3 id="pull-request-만들기"><strong>pull request 만들기:</strong></h3>
<ul>
<li><p>오픈소스 프로젝트와 같이 외부 공개된 public repository에 있는 코드에 수정사항을 남기고 commit할 경우에는, 외부 repository를 내 repository로 <code>fork</code>해온 후 해당 repository를 로컬 저장소로 가져와준다. (<code>clone</code> , <code>pull</code> )</p>
<p>  (fork는 외부 repository에 대한 나만의 branch를 생성하는 거라 할 수 있다.)</p>
<p>  이후 해당 로컬 저장소에서 파일을 수정한 후, commit 및 push한 후 원본 repository에서 <strong>pull request</strong>를 생성할 수 있다.</p>
</li>
<li><p>pull request, 즉 <strong>PR</strong>은 자신이 fork를 통해 만든 branch를 외부 repository에 merge(=수정, 즉 commit내역을 승인)하기 전에 이를 확인 받기 위한 과정이다.
pull request 원본 repository의 소유자는 pull request를 확인하고 이를 승인/거절할 수 있다.</p>
<pre><code class="language-bash">  cd file/location/
  git clone https://github.com/nickname/repo-name.git
  git pull</code></pre>
</li>
</ul>
<h3 id="정리">정리</h3>
<pre><code class="language-bash">    local repository 
    -- | commit, push | --&gt; 내 git server 
    —- | pull request | --&gt; 외부 git server</code></pre>
<pre><code class="language-bash">    외부 git server 
    -- | fork | --&gt; 내 git repository
    --&gt; | clone | --&gt; 내 local repository</code></pre>
<hr>
<h3 id="예시">예시</h3>
<ul>
<li><p>local 저장소에 있던 gift-card-maker을 local repository로, git에 경로 등록하기</p>
<pre><code class="language-bash">       ~/projects                       
      ❯ cd gift-card-maker
      ❯ git init
      Initialized empty Git repository in /Users/seoyeonheo/projects/gift-card-maker/.git/</code></pre>
</li>
<li><p>** git add, commit :**</p>
</li>
</ul>
<pre><code class="language-bash">
        ❯ git add images
        ❯ git add index.html
        ❯ git add main.js
        ❯ git commit -m &quot;initial commit&quot;</code></pre>
<ul>
<li><strong>github repository 연결</strong></li>
</ul>
<pre><code class="language-bash">        git remote add origin https://github.com/h-seo-n/card-maker.git
        git branch -M main
        git push -u origin main</code></pre>
<hr>
<h3 id="변경사항-push--commit-과정"><strong>변경사항 push &amp; commit 과정</strong></h3>
<p>README.md 업데이트 등 원격저장소(github)에 수정이 있었을 때, 원격저장소의 데이터를 <code>git pull</code>로 로컬 저장소인 git에 최신화한 후 commit 해야 함.</p>
<p>→ 그렇지 않을 시 충돌 발생, pull 후 병합된 내용을 push하게 됨.</p>
<ul>
<li>수정 사항을 <code>git add</code> 로 스테이지에 추가<ul>
<li>모든 파일 추가 : <code>git add .</code></li>
<li>특정 파일 추가 : <code>git add main.js</code></li>
</ul>
</li>
<li>git 상태 확인 : <code>git status</code></li>
<li>커밋하기 : <code>git commit -m &quot;changes saved&quot;</code></li>
<li>로컬 저장소의 커밋을 원격 저장소로 올리기 : <code>git push</code></li>
</ul>
<hr>
<h3 id="reset--실수로-한-addcommitpush취소하기"><strong><code>reset</code></strong> : 실수로 한 <code>add</code>/<code>commit</code>/<code>push</code>취소하기</h3>
<ul>
<li><p><strong>add unstaging</strong> : commit 전, stage에 파일을 잘못 <code>add</code> 한 경우 <strong><code>git reset &lt;파일 경로 or 파일명&gt;</code></strong> 으로 해당 파일을 stage에서 제거할 수 있다.</p>
</li>
<li><p><strong>push 전 commit 취소</strong> :</p>
<ul>
<li>commit을 취소하지만 stage에는 남겨놓기 (add한 상태) : <strong><code>git reset --soft HEAD^</code></strong></li>
<li>commit을 취소하고 stage에서도 제거 : <strong><code>git reset --mixed HEAD^  == git reset HEAD^</code></strong></li>
<li>여러 개의 commit 취소 : ex. <strong><code>git reset HEAD~{개수}</code></strong></li>
<li>원격 저장소 &amp; 로컬 작업 공간 모두에서 commit 내역 삭제하기 : <strong><code>git reset --hard HEAD^</code></strong></li>
</ul>
</li>
<li><p><strong>push 취소 :</strong></p>
<ul>
<li><p><strong>가장 최근 커밋 취소</strong> : ****<code>git reset HEAD^</code></p>
</li>
<li><p>특정 커밋 취소하기 : <code>git reset {commit id}</code></p>
<p>  → reset 후 <code>git commit -m &quot;message&quot;</code> 입력 및 <code>git push {repository} {branch} -f</code> 로 강제 push를 해주면 원격저장소로 push된 커밋까지 취소할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="github-repository에서-파일-삭제하기">Github repository에서 파일 삭제하기</h3>
<p>깃헙에 개의치 않게 테스트 코드 등의 파일을 잘못 올렸을때 git rm을 이용해서 github에서만 파일을 삭제할 수 있다. (로컬 저장소에는 유지)</p>
<pre><code class="language-bash"> git rm --cached filename.code 
 git commit -m &quot;remove filename.code&quot;</code></pre>
<p><code>--cached</code>를 지우고 커맨드 실행 시 로컬 저장소에서도 삭제된다.</p>
<p>(깃헙 공식 웹에서는 웹 gui에서 삭제하는 방법이 안내되어 있으나 현재는 그 방법이 없어진 것으로 보임)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] url을 여는 액자, <iframe>]]></title>
            <link>https://velog.io/@h-seo-n/HTML-url%EC%9D%84-%EC%97%AC%EB%8A%94-%EC%95%A1%EC%9E%90-iframe</link>
            <guid>https://velog.io/@h-seo-n/HTML-url%EC%9D%84-%EC%97%AC%EB%8A%94-%EC%95%A1%EC%9E%90-iframe</guid>
            <pubDate>Mon, 18 Aug 2025 10:58:19 GMT</pubDate>
            <description><![CDATA[<p>HTML의 요소 중 <code>&lt;iframe&gt;</code>, 즉 inline frame은 <strong>다른 html 문서를(즉 다른 웹사이트를)  현재 html에서 볼 수 있게 하는 “액자”</strong>를 만든다.</p>
<h3 id="기본-형식-"><strong>기본 형식</strong> :</h3>
<p><code>src</code> 속성으로 프레임에서 보여줄 html 파일을 명시하고, <code>title</code> 속성은 프레임의 제목 역할을 하여 스크린리더(Screen reader; 웹사이트 음성 지원)가 이를 읽을 수 있게 한다.<br></p>
<pre><code>ex.`&lt;iframe src=&quot;url&quot; title=&quot;description&quot;&gt;&lt;/iframe&gt;`</code></pre><h3 id="스타일-관련-설정들-"><strong>스타일 관련 설정들 :</strong></h3>
<ul>
<li><p><strong>크기</strong> : 프레임의 크기는 style 속성에서 css의 width와 height 속성으로 설정하거나, 따로 html의 width와 height 속성으로 설정해줄 수 있다.</p>
</li>
<li><p><strong>윤곽선</strong> : 기본적으로 <code>iframe</code> 요소는 옅은 윤곽선이 있다. 이를 지우거나 수정하기 위해서는 style 속성에서 css로 <code>border</code> 속성을 작성해주면 된다.  <br></p>
<p>ex. <code>style=”border:none;”</code> 혹은 <code>style=”border:5px solid Green;”</code></p>
</li>
</ul>
<ul>
<li><p>링크 요소 <code>&lt;a&gt;</code>의 <code>target</code> 속성을 통해, 링크 요소를 누를 시 링크가 <code>iframe</code> 요소 안에서 열리도록 할 수 있다.<br></p>
<p>  ex. </p>
<pre><code class="language-html">  &lt;!DOCTYPE html&gt;
  &lt;html&gt;
  &lt;body&gt;

  &lt;h1&gt; ⬇️ iframe &lt;/h1&gt;
  &lt;iframe src=&quot;backgroundimg.htm&quot; name=&quot;iframe_ex&quot; title=&quot;iframe example&quot;&gt;&lt;/iframe&gt;
  &lt;p&gt;&lt;a href=&quot;blockinline.htm&quot; target=&quot;iframe_ex&quot;&gt;Show block inline example&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;backgroundimg.htm&quot; target=&quot;iframe_ex&quot;&gt;Show background image example&lt;/a&gt;&lt;/p&gt;

  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  <div>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/56769ac1-3b7f-4f20-81ee-7e2fb763b8ed/image.png" width=40% style="float:left">


</li>
</ul>
<pre><code>&lt;img src=&quot;https://velog.velcdn.com/images/h-seo-n/post/df40319a-92bf-4ad3-a8d2-6090803aea6b/image.png&quot; width=40% style=&quot;float:right&quot;&gt;
&lt;/div&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[ [HTML] HTML class 속성 & id 속성]]></title>
            <link>https://velog.io/@h-seo-n/HTML-HTML-class-%EC%86%8D%EC%84%B1-id-%EC%86%8D%EC%84%B1</link>
            <guid>https://velog.io/@h-seo-n/HTML-HTML-class-%EC%86%8D%EC%84%B1-id-%EC%86%8D%EC%84%B1</guid>
            <pubDate>Mon, 18 Aug 2025 10:48:07 GMT</pubDate>
            <description><![CDATA[<h3 id="클래스-"><strong>클래스 :</strong></h3>
<ul>
<li><p><code>&lt;style&gt;</code> 내부에서 <code>.classname { … }</code> 의 형식으로, 온점 <code>.</code> 뒤에 클래스의 이름을 적음으로써 클래스를 형성하고 <strong>클래스와 이에 따른 css 스타일</strong>을 설정해줄 수 있다.</p>
</li>
<li><p>무슨 요소이든 <strong>class attribute(속성)</strong>으로 (<code>class=”class1 class2”</code>)특정 클래스에 속함을 밝힐 수 있고, 속하는 class의 속성들(style 등)을 부여받게 된다.</p>
<p>이때 한 요소는 <strong>class 이름을 띄워 적음으로써 여러 개의 class에 속할 수 있고</strong>, <strong>서로 다른 종류의 여러 요소가 같은 클래스에 속할 수도 있음.</strong></p>
</li>
<li><p>e.g.</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/f0410db5-5f20-4a5b-b320-1014c41d7331/image.png" width=60%>

<pre><code class="language-html"> &lt;head&gt;
   &lt;style&gt;
   .pink_button {
           background-color:Pink;
           color:SeaShell;
           padding: 15px 25px;
           margin: 10px;
           inline-size:min-content;
           float:left;
           border-radius: 20px;
       }

    .rectangle {
           background-color:SeaShell;
           color:Pink;
           padding:15px 25px;
           margin:10px;
           float:left;
           inline-size:min-content;
       }
   &lt;/style&gt;
 &lt;/head&gt;
 &lt;body&gt;
     &lt;h1 class=&quot;pink_button&quot;&gt;Pink Button&lt;/h1&gt;
     &lt;h1 class=&quot;rectangle&quot;&gt;Rectangle&lt;/h1&gt;
 &lt;/body&gt;

</code></pre>
</li>
</ul>
<ul>
<li><strong>클래스와 자바스크립트</strong> :<ul>
<li>자바스크립트의 <code>getElementByClassName()</code> 메서드로, 한 클래스에 속하는 모든 요소들을 선택하고 자바스크립트 코드로 특정 상호작용을 지정하는 등의 코드를 짤 수 있다
➡️ 실제 활용에서는 <code>document.querySelector(.classname)</code>이 클래스로 요소를 선택하는 데에 많이 쓰인다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="아이디-"><strong>아이디 :</strong></h3>
<ul>
<li><p>클래스가 여러 html 요소를 그룹핑하는 역할이라면, 아이디는 각각의 html 요소를 구별하는 식별자 역할이라고 할 수 있다. 한 아이디는 하나의 요소에만 부여될 수 있다.</p>
</li>
<li><p><code>&lt;style&gt;</code> 내부에서 샵(<code>#</code>)과 함께, 특정 아이디에 대한 스타일을 css로 설정해줄 수 있다. 이를 적용하기 위해 각 요소의 <code>id</code> 속성에서 고유한 아이디를 부여해줄 수 있다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;style&gt;
   #green {color: SeaGreen;}
 &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
 &lt;h2&gt;This has no id&lt;/h1&gt;
 &lt;h2 id=&quot;green&quot;&gt;This has id &quot;green&quot;&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/f888ec3a-0a8e-46fa-9fa4-111f08c10b69/image.png" width=50%>


</li>
</ul>
<ul>
<li><strong>아이디와 자바스크립트</strong> :<ul>
<li><code>getElementById()</code> 를 통해 특정 아이디를 가진 요소를 선택함으로써 자바스크립트로 여러 구현을 할 수 있다.
(or <code>document.querySelector(&quot;#id&quot;)</code>)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] 블록 요소(block element)와 인라인 요소(inline element)]]></title>
            <link>https://velog.io/@h-seo-n/HTML-%EB%B8%94%EB%A1%9D-%EC%9A%94%EC%86%8Cblock-element%EC%99%80-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%9A%94%EC%86%8Cinline-element</link>
            <guid>https://velog.io/@h-seo-n/HTML-%EB%B8%94%EB%A1%9D-%EC%9A%94%EC%86%8Cblock-element%EC%99%80-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%9A%94%EC%86%8Cinline-element</guid>
            <pubDate>Sat, 16 Aug 2025 13:29:23 GMT</pubDate>
            <description><![CDATA[<h3 id="블록-요소-vs-인라인-요소"><strong>블록 요소 vs 인라인 요소</strong></h3>
<ul>
<li><p><strong>블록 요소 :</strong> 블록 요소는 요소가 페이지 좌우너비 전체를 차지하는 하나의 ‘블록’이라고 볼 수 있다.</p>
<ul>
<li>ex. <code>&lt;p&gt;</code>, <code>&lt;h1&gt;</code>-<code>&lt;h6&gt;</code>, <code>&lt;ol&gt;</code>, <code>&lt;ul&gt;</code>, <code>&lt;li&gt;</code>, <code>&lt;blockquote&gt;</code>, <strong><code>&lt;div&gt;</code></strong> 등</li>
</ul>
</li>
<li><p><strong>인라인 요소</strong> : 인라인 요소는 필요한 만큼의 너비만 차지하는 요소이다.  in-line 말 그대로 행 자체이기보다는 행의 내부에 (다른 요소들과) 있다.</p>
<ul>
<li>ex. <code>&lt;a&gt;</code>, 텍스트 포매팅 요소(<code>&lt;b&gt;</code>, <code>&lt;i&gt;</code> 등), <code>&lt;abbr&gt;</code>, <code>&lt;map&gt;</code>, <code>&lt;img&gt;</code>, <strong><code>&lt;span&gt;</code></strong> 등</li>
</ul>
</li>
<li><p>블록 요소는 블록과 인라인 요소 모두를 담을 수 있지만, 인라인 요소 안에는 블록 요소가 담길 수 없다!</p>
<hr>
</li>
</ul>
<h3 id="div와-span"><code>&lt;div&gt;</code>와 <code>&lt;span&gt;</code></h3>
<ul>
<li><p><strong><code>&lt;div&gt;</code> 태그</strong> : 페이지의 컨텐츠를 나누거나(&#39;div&#39;ide) 그룹핑하는데 사용되는, 마치 컨테이너와 같은 역할을 하는 요소로 대표적인 블록 요소이다.</p>
</li>
<li><p><strong><code>&lt;span&gt;</code> 태그</strong> : 제목이나 본문 요소 등 안에서 텍스트의 일부를 선택하는 태그. 선택할 부분을 <code>&lt;span&gt;</code> 태그 안에 넣으면 된다. 대표적인 인라인 요소.</p>
</li>
<li><p>ex.</p>
<pre><code class="language-html">&lt;div style=&quot;background-color:PaleGoldenRod&quot;&gt;
&lt;h1&gt;Div element&lt;/h1&gt;
&lt;p&gt;This is an div element. Div divides and groups these contents.&lt;/p&gt;
&lt;/div&gt;

&lt;div&gt;
&lt;h1&gt; &amp;lt;span&amp;gt; inside div&lt;/h2&gt;
&lt;p&gt;&lt;p&gt;I want to change only
&lt;span style=&quot;color:LightPink&quot;&gt;
This Part&lt;/span&gt; of the code.&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/efe52945-581c-44b6-9815-8d3347a19e2c/image.png" width=60%>

<hr>
</li>
</ul>
<h3 id="div-요소-스타일-활용하기"><code>&lt;div&gt;</code> 요소 스타일 활용하기</h3>
<ul>
<li><p><code>margin:auto;</code> : div 요소의 너비가 (스타일에 의해) 페이지 너비보다 좁다면, div 요소를 자동적으로 중앙으로 정렬해준다.</p>
</li>
<li><p><code>display: inline-block;</code> : 다른 요소의 위아래로 쌓이는 대신, 인라인 요소처럼 페이지의 여백을 채우는 식으로 작동한다.</p>
</li>
<li><p><code>float</code> : inline-block의 효과와 유사하게, float를 이용해서 여러 개의 div 요소가 양옆으로 서로 정렬되도록 할 수 있다.</p>
<ul>
<li>ex :      <pre><code class="language-html">Div {
  width: 40%;
  float:left;
  margin: 10px;
 }</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/0dd129e3-15b0-4912-8853-dd9bc1d8082e/image.png
" width=60%></li>
</ul>
</li>
<li><p>이외에도 CSS의 <code>display:flex</code> 혹은 <code>display:grid</code> 를 이용하여 더 간편하게 div를 이용한 레이아웃을 설정할 수 있다.</p>
<p>   (<a href="https://webdesignerdepot.com/when-pages-are-not-paper-the-designers-guide-to-layout-code/">https://webdesignerdepot.com/when-pages-are-not-paper-the-designers-guide-to-layout-code/</a> 참고 시 div style을 웹 레이아웃에 활용하는 방법을 더 알 수 있음)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] html 리스트 요소들 (<ul>, <ol>, <li>, ...)]]></title>
            <link>https://velog.io/@h-seo-n/HTML-html-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9A%94%EC%86%8C%EB%93%A4-ul-ol-li-</link>
            <guid>https://velog.io/@h-seo-n/HTML-html-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9A%94%EC%86%8C%EB%93%A4-ul-ol-li-</guid>
            <pubDate>Fri, 15 Aug 2025 16:49:00 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="html-리스트">HTML 리스트</h2>
<h3 id="순서-없는-리스트-unordered-list">순서 없는 리스트 (Unordered List)</h3>
<p>불렛 리스트처럼 나열식으로 된 리스트. <code>&lt;ul&gt;</code> (unordered list 이니셜) 요소 안에 <code>&lt;li&gt;</code>로 리스트의 각 항목을 작성한다.</p>
<h3 id="순서-있는-리스트-ordered-list">순서 있는 리스트 (Ordered List)</h3>
<p>1, 2, 3처럼 순서를 부여한 리스트. <code>&lt;ol&gt;</code> (ordered list 이니셜) 요소 안에 <code>&lt;li&gt;</code>로 리스트의 각 항목을 작성한다.</p>
<h3 id="설명-리스트-description-list">설명 리스트 (Description List)</h3>
<p>리스트 항목마다 설명을 같이 작성할 수 있는 리스트. </p>
<p><code>&lt;dl&gt;</code> (description list) 요소 안에 <code>&lt;dt&gt;</code> (d + term)으로 설명할 용어/개념, 그 다음 <code>&lt;dd&gt;</code> (d + description)로 설명을 작성한다.</p>
<p>(즉  <code>&lt;dt&gt;</code>와 <code>&lt;dd&gt;</code>  쌍이 하나의 리스트 항목을 구성한다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] HTML <a> <img>, 링크와 이미지 세부사항]]></title>
            <link>https://velog.io/@h-seo-n/HTML-HTML-element-HTML-%EC%9A%94%EC%86%8C%EB%93%A4</link>
            <guid>https://velog.io/@h-seo-n/HTML-HTML-element-HTML-%EC%9A%94%EC%86%8C%EB%93%A4</guid>
            <pubDate>Fri, 15 Aug 2025 16:47:21 GMT</pubDate>
            <description><![CDATA[<h2 id="html-링크-요소-세부사항">HTML 링크 요소 세부사항</h2>
<ul>
<li><p><strong>절대적인 url(Absolute URL) vs 상대적인 url(Relative URL)</strong></p>
<ul>
<li><strong>절대적인 url</strong> : https:// ~ 부터 시작하는 통상적인 웹 주소</li>
<li><strong>상대적인 url</strong> : 현재 html 웹(파일)의 주소를 기반으로 한 url (한 웹 주소가 여러 다른 하위 페이지를 가지고 있을 때, 혹은 html 파일이 위치한 폴더 안에 다른 리소스 파일들이 있을 때 이용) <br></li>
</ul>
</li>
<li><p><code>&lt;a&gt;</code> 태그 안에 <code>&lt;img&gt;</code>태그를 넣음으로써 링크 미리보기로 이미지를 띄울 수 있다.</p>
  <br></li>
<li><p>누군가에게 이메일을 바로 보내는 (유저의 이메일 프로그램을 여는) 링크 : <code>mailto:someone@example.com</code>
e.g. <code>&lt;a href=&quot;mailto:best3487@snu.ac.kr&quot;&gt;Mail Me!&lt;/a&gt;</code></p>
 <img src="https://velog.velcdn.com/images/h-seo-n/post/50a7c3c0-e067-4224-aec5-c2e8a42f2970/image.png" width=60%>

</li>
</ul>
<hr>
<h3 id="링크-색깔"><strong>링크 색깔</strong></h3>
<ul>
<li>방문 전(unvisited - 기본값) / 방문 후(<code>visited</code>) / 마우스를 위에 올려놓았을 때(<code>hover</code>) / 클릭 시(<code>active</code>) - 이 4개의 링크 상태에 따라 스타일을 다르게 지정할 수 있음.</li>
<li>링크 요소는 기본 스타일값이 밑줄이 쳐져 있는 형태이며, <code>text-decoration=none</code>이라고 css style에서 따로 명시하면 밑줄을 없앨 수 있다.</li>
</ul>
<pre><code class="language-html">&lt;style&gt;
a:link {
    background-color: #20B2AA;
    color: PapayaWhip;
    font-size: 200%;
    text-decoration: none;
} 

a:visited {
    color:LightPink;
    text-decoration: none;
}

a:hover {
    color:Tomato;
    text-decoration: underLine;
}

a:active {
    color:Snow;
    text-decoration: underLine;
}
&lt;/style&gt;</code></pre>
<ul>
<li><p>link (기본 상태)</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/4d9ea66d-19ac-46b7-bbd0-e5a2fd748932/image.png" width=40%>


</li>
</ul>
<ul>
<li>hover (마우스 띄운 상태))
<img src="https://velog.velcdn.com/images/h-seo-n/post/4ca042ce-622e-406f-900a-a4bdf7c1d47b/image.png
" width=40%></li>
</ul>
<ul>
<li>visited (링크 방문 상태)  <img src="https://velog.velcdn.com/images/h-seo-n/post/e3cfe2d3-1a8a-4737-a431-b1a72c7a329f/image.png" width=40%>



</li>
</ul>
<ul>
<li>active (링크 클릭하고 있는 상태)
<img src="https://velog.velcdn.com/images/h-seo-n/post/0e8d6da2-b51c-4078-a7ab-459dac5e53d9/image.png
" width=40%></li>
</ul>
<hr>
<h3 id="링크-버튼"><strong>링크 버튼</strong></h3>
<ul>
<li><p>CSS의 style (e.g.<code>{padding: 15px 25px; display: inline-block}</code>)를 활용해서 링크 요소가 버튼처럼 보이도록 할 수 있음. <br>(padding : 순서대로 요소의 세로 방향과 가로 방향에 적은 픽셀 만큼의 패딩을 넣는 스타일 속성)</p>
<ul>
<li><p>예시 :</p>
<pre><code class="language-html">a:link {
   background-color: #20B2AA;
   color: PapayaWhip;
   padding: 15px 25px;
   display: inline-block;
   border-radius: 20px;
} </code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/4df7c3cb-8c00-4854-8dd5-f9c7b302ab5a/image.png" width=40%>

</li>
</ul>
</li>
</ul>
<hr>
<h3 id="북마크-만들기-"><strong>북마크 만들기</strong> :</h3>
<p>웹페이지가 너무 길 경우 페이지의 특정 요소로 이동하는 북마크 요소를 만들 수 있음.</p>
<p>→ 북마크를 설정해둘 요소에 <code>id</code> 속성을 설정해두고</p>
<p>→ 북마크는 링크 요소(<code>&lt;a&gt;</code>)로 만들고, <code>href=”#(설정한id)”</code> 속성을 설정해 두면 됨. </p>
<p>→ 이런 북마크는 다른 웹페이지에서도 링크 요소로 설정해줄 수 있음. (<code>href=”링크#설정한id”</code>)</p>
<ul>
<li><p>ex</p>
<pre><code class="language-html">&lt;body&gt;
&lt;a id=&quot;top&quot; href=&quot;#bottom&quot;&gt;Move To Bottom&lt;/a&gt;

&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;

&lt;a id=&quot;bottom&quot; href=&quot;#top&quot;&gt;Move To Top&lt;/a&gt;</code></pre>
<img src = "https://velog.velcdn.com/images/h-seo-n/post/f16d9566-4610-4e15-b46a-ca1fc147a61b/image.gif" width=60%>

</li>
</ul>
<hr>
<h2 id="html-이미지-세부사항">HTML 이미지 세부사항</h2>
<h3 id="src이미지-주소"><strong>`src=”이미지 주소”</strong>`:</h3>
<ul>
<li><p>웹페이지는 열릴 때마다 <code>&lt;img&gt;</code> 태그의 <code>src</code> 속성 안에 있는 주소에서 이미지를 가져옴. 웹페이지 안에 고정된 것이 아니기 때문에, 웹페이지가 열릴 때 <code>src</code>에 명시한 주소 안에 이미지가 반드시 있어야 함.</p>
<ul>
<li><p>이미지 주소의 경우 절대적 url과 상대적 url 중, 로컬 html 파일이 위치한 폴더 기준으로 이미지 주소를 적는 <strong>상대적인 url(relative url)을</strong> 사용하는 것을 적극 권장. </p>
<p>(절대적 url을 가진 이미지는 외부 웹사이트에 호스팅된 이미지이므로, 다른 사람의 웹사이트에서 이미지를 가져오는 것은 윤리적이지도 않으며(내 웹에서 이미지가 로딩될 때의 트래픽 비용을 외부 웹사이트가 부담하는 등) 해당 주소의 이미지가 나도 모르게 바뀔 수 있으므로 불안정함. 만약 내 소유 웹사이트에서 이미지를 가져온다고 하면 괜찮을 것)</p>
</li>
</ul>
</li>
<li><p>즉, 상대적인 url을 사용하기 위해 로컬 html 파일이 위치한 폴더 안 어딘가 이미지 파일이 있어야 하고 그 위치를 임의로 바꾸면 안된다. (코드 상 이미지의 주소를 같이 바꾸지 않는 이상)</p>
</li>
<li><p>src에 명시된 링크에 이미지가 없거나 네트워크 문제로 가져오는 것을 실패한 경우, 깨진 이미지 아이콘과 함께 <code>alt</code> 속성에 설정해둔 대체 텍스트가 표시된다.</p>
<br>

</li>
</ul>
<h3 id="width-height-속성-"><strong>width, height 속성 :</strong></h3>
<ul>
<li><p>이미지의 크기 (가로 x 세로) 는 <code>width</code>, <code>height</code> 속성보다는 <code>style</code> 속성에서 css 문법으로 설정하는 것을 권장 :<br></p>
<ul>
<li><p>속성으로 크기를 정의할 경우, header에서 정의된 style element 등 CSS style sheet에서 전체적으로 정의해 준 이미지 크기가 속성으로 정의된 개별 이미지 크기를 무시하고 덮어쓸 수 있음.<br></p>
</li>
<li><p>반면 개별 이미지의 크기를 따로 css로 정의해 주면 전체 css style 설정과 개별 요소들의 설정을 모두 유지할 수 있으므로, 그냥 css style로 이미지 크기를 정의하는 것에 익숙해지자.</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/d619287f-23ce-45d4-b7ed-13ef2a6fd0c4/image.png" width=60%>

<pre><code>  (w3schools의 예시) </code></pre></li>
</ul>
</li>
</ul>
<h3 id="이미지-형식-"><strong>이미지 형식</strong> :</h3>
<p>이미지로  <code>gif</code> 애니메이팅 파일을 넣을 수도 있다.</p>
<p>이뿐만 아니라 APNG, GIF, ICO, JPEG, PNG, SVG 파일 모두를 지원함
<br></p>
<h3 id="이미지-플로팅-"><strong>이미지 “플로팅”</strong> :</h3>
<p>문단 <code>&lt;p&gt;</code> 요소 안에 이미지 요소 <code>&lt;img&gt;</code>를 넣으면 문단 안에 이미지를 위치시킬 수 있다.</p>
<p>또한 css의 float 문(e.g. <code>style=”float:right”</code>)으로, 이미지가 다른 요소 위에 ‘떠 있도록’ 할 수 있는데, 이미지와 다른 요소를 나란히 정렬시키는 데 이용된다고 보면 된다.</p>
<p><strong>floating 코드 예시 :</strong> </p>
<pre><code class="language-html">  &lt;p&gt;
  &lt;img src=&quot;images/cafe.jpeg&quot; alt=&quot;cafe that feels good&quot;
  style=&quot;float:left;width:200px&quot;&gt;
  느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 느낌 좋은 카페. 
  &lt;/p&gt;</code></pre>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/2e96777a-e839-4de8-87c7-3e4b358bf87f/image.png" width=50%>

<br>

<h3 id="이미지를-링크로-넣기-">이미지를 링크로 넣기 :</h3>
<p><code>&lt;a&gt;</code> 안에 <code>&lt;img&gt;</code>를 넣으면 된다.</p>
<ul>
<li><p>ex:</p>
<pre><code class="language-html">&lt;h1&gt;고함항아리구경하기&gt;&gt;
&lt;a href=&quot;https://github.com/h-seo-n/Scream-Jar&quot; style=&quot;float:right;&quot;&gt;
&lt;img src=&quot;images/cloud.jpeg&quot; style=&quot;width:200px&quot;&gt;
&lt;/a&gt;
&lt;/h1&gt;</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/2396bcff-4928-47e2-8b76-18f02a4bf186/image.png" width=60%>


</li>
</ul>
<br>

<hr>
<h3 id="이미지-매핑-"><strong>이미지 매핑</strong> :</h3>
<ul>
<li><p>좌표를 이용해서 이미지의 특정 구역을 클릭하면 다른 링크로 이동하는 등, 이미지의 특정 구역과 사용자와의 상호작용을 구현할 수 있다.</p>
</li>
<li><p><code>&lt;map&gt;</code> 요소에서 설정해줄 구역을 <code>shape</code>와 <code>coords</code> 속성으로 정의해주고, <code>&lt;map&gt;</code>의 name 속성과 <code>&lt;img&gt;</code>의 usemap 속성이 일치하게 함으로써 연결해주면 된다.</p>
</li>
<li><p>예시 형식 : </p>
<pre><code class="language-html">&lt;img src=&quot;images/ex1.jpg, usemap=#ex1&quot;&gt;

&lt;map name=&quot;ex1&quot;&gt;
    &lt;area shape=&quot;rect&quot; coords=&quot;34,44,270,350&quot; href=&quot;example1.htm&quot;
&gt;</code></pre>
</li>
<li><p><code>&lt;area&gt;</code>     :     <code>&lt;map&gt;</code> 요소 안에 구역을 설정해주는 <code>&lt;area&gt;</code> 요소가 있고, 추가적 속성으로 구역의 모양과 크기를 설정해줄 수 있다.</p>
<ul>
<li><code>shape=”rect”</code> : 가장 왼쪽 상단의 좌표와 가장 오른쪽 하단의 좌표, 총 두 쌍의 좌표를 coords에 적어 준다.</li>
<li><code>shape=”circle”</code> : 원의 중심 좌표와 원의 반지름, 총 세 개의 숫자를 적어준다.</li>
<li><code>shape=”poly”</code> : 여러 쌍의 좌표를 입력하면, 그 점들을 선으로 연결한 다각형 모양을 만든다. (사진 출처 : <a href="https://www.w3schools.com/html/html_images_imagemap.asp">https://www.w3schools.com/html/html_images_imagemap.asp</a>)</li>
</ul>
</li>
</ul>
<pre><code>&lt;img src=&quot;https://velog.velcdn.com/images/h-seo-n/post/b5d23f57-fc9e-4f5d-b710-2cf133180e1b/image.png&quot;&gt; | &lt;img src=&quot;https://velog.velcdn.com/images/h-seo-n/post/21d61b68-dde5-440d-9236-40e30279a584/image.png&quot;&gt;
---|---|

 &lt;br&gt;</code></pre><ul>
<li><p><code>&lt;area href = “…”&gt;</code> : 정의해준 구역을 클릭했을 때, href 속성에 적은 링크로 이동하도록 한다.</p>
<ul>
<li><strong>JavaScript</strong> : <code>&lt;area&gt;</code> 요소의 <code>onclick</code> 속성에 자바스크립트 함수 이름을 명시함으로써, 정의해준 이미지 구역(맵 요소)이 자바스크립트 함수에 정의된 대로 사용자와 상호작용하게 할 수 있다.</li>
</ul>
<p>ex. </p>
<pre><code class="language-html">&lt;area … onclick=”myfunction()”&gt; …
&lt;script&gt;
function myFunction() { … }
&lt;/script&gt;</code></pre>
<br>

</li>
</ul>
<hr>
<h3 id="배경으로-이미지-넣기-">배경으로 이미지 넣기 :</h3>
<ul>
<li><p><strong>전체 웹페이지에 배경 이미지 넣기</strong>: <br></p>
<p><code>&lt;style&gt;</code> 태그 혹은 css 시트 안에 <code>background-image: url(’이미지 주소’);</code> 를 설정해주면 된다.</p>
<ul>
<li><p>ex:</p>
<pre><code class="language-html">&lt;style&gt;
body {
    background-image: url(&#39;images/landscape.jpeg&#39;);
    background-size: cover;
    background-attachment: fixed;
}
&lt;/style&gt;</code></pre>
<img src="blob:https://velog.io/f97e4754-ffa1-4062-a63e-5dd0a1c79a2a" width=40%>


</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>특정 HTML 요소에 배경 이미지 넣기</strong> : </p>
<p>  개별 style 속성으로 설정해주면 된다.</p>
</li>
</ul>
<ul>
<li><p><strong>배경 이미지 관련 스타일 설정들</strong> :</p>
<ul>
<li><p><code>background-size</code> :</p>
<ul>
<li><p><code>“cover”</code>로 설정해둘 시, 이미지가 기존의 비율을 유지하면서 페이지(혹은 요소) 전체를 채운다.</p>
<p>  → 밑에 나오는 background-attachment 설정 없이 그냥 cover만 할 시 “100%”로 설정하는 것과 같음</p>
</li>
<li><p><code>“100%”</code>로 설정하면 기존의 비율을 유지하면서 가로 길이만 페이지(혹은 요소)의 길이에 맞춘다.</p>
</li>
<li><p><code>“100% 100%”</code>로 설정하면 비율을 유지하지 않고 페이지(혹은 요소) 전체를 덮어버린다.</p>
</li>
</ul>
</li>
<li><p><code>background-attachment</code> :</p>
<ul>
<li><p>현재 사용자가 브라우저를 통해 보고 있는 웹의 영역인 viewport에 배경이 고정되어 있는지, 혹은 페이지(혹은 요소)가 스크롤 될때 배경이 같이 스크롤 되는지의 여부. <br></p>
<p>  → <code>background-size=cover;</code> <code>background-attachment=fixed;</code> 이렇게 동시에 설정해두면 배경 이미지가 항상 페이지(혹은 요소) 전체를 가린다.</p>
</li>
</ul>
</li>
<li><p><code>background-repeat</code> :</p>
<ul>
<li>이미지의 원래 크기가 요소나 페이지보다 작으면, 페이지(혹은 요소)를 모두 덮을때까지 배경 이미지가 반복된다. 이게 싫으면 <code>background-repeat: no-repeat;</code>를 설정해주면 된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>→ css background에 대해 더 검색해보면 더 잘 알 수 있는데 실제로 만들때 그때그때 검색하면서 해도 될 것 같다.</p>
<hr>
<h3 id="picture-요소"><code>&lt;picture&gt;</code> 요소</h3>
<ul>
<li><p>모바일, 태블릿, 데스크탑 등 웹사이트를 여는 기기에 따라 브라우저의 너비가 달라지면, 화면 크기에 따라 다른 이미지를 표시할 수 있게 하는 요소이다. 사용 기기에 따라 다른 디자인을 보여주는 반응형 디자인을 구현할 때 등에 사용할 수 있다.</p>
</li>
<li><p>형식 :</p>
<pre><code class="language-html">  &lt;picture&gt;
      &lt;source media=&quot;(min-width: nnnpx)&quot; srcset=&quot;ex1.jpg&quot;&gt;
      &lt;source media=&quot;(min-width: mmmpx)&quot; srcset=&quot;ex2.jpg&quot;&gt;
      &lt;img src=&quot;default_img.jpg&quot;&gt;
  &lt;/picture&gt;</code></pre>
<p>  <code>&lt;picture&gt;</code> 내부 <code>&lt;source&gt;</code> 요소가 있고, <code>media</code> 속성으로 어떤 조건 하에 해당하는 이미지를 표시할지 설정해준다. 이때 브라우저는 소스를 순서대로 보다가 조건을 만족하는 소스를 찾으면 더 보지 않고 바로 그 소스(이미지)를 표시한다.<br></p>
<p>  <code>&lt;source&gt;</code>요소들을 다 작성한 후 마지막에 <code>&lt;img&gt;</code> 요소를 넣어주면, 모든 조건이 만족하지 않을 때의 기본값 이미지가 된다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] 브.꾸 : HTML 브라우저 탭 꾸미기 (웹 제목 및 아이콘)]]></title>
            <link>https://velog.io/@h-seo-n/HTML-%EB%B8%8C.%EA%BE%B8-HTML-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%ED%83%AD-%EA%BE%B8%EB%AF%B8%EA%B8%B0-%EC%9B%B9-%EC%A0%9C%EB%AA%A9-%EB%B0%8F-%EC%95%84%EC%9D%B4%EC%BD%98</link>
            <guid>https://velog.io/@h-seo-n/HTML-%EB%B8%8C.%EA%BE%B8-HTML-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%ED%83%AD-%EA%BE%B8%EB%AF%B8%EA%B8%B0-%EC%9B%B9-%EC%A0%9C%EB%AA%A9-%EB%B0%8F-%EC%95%84%EC%9D%B4%EC%BD%98</guid>
            <pubDate>Thu, 14 Aug 2025 11:39:56 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>파비콘(favicon) :</strong> 웹사이트를 브라우저에서 열었을 때, 브라우저 탭에서 보이는 16X16 픽셀 사이즈의 작은 아이콘으로 웹사이트를 대표한다고 할 수 있다. 웹사이트를 실제로 서버에 호스팅 할 것이라면 사용자가 내 웹사이트를 시각적으로 기억하고 인식하는 데에, 그리고 웹사이트의 브랜딩에 파비콘은 중요한 역할을 한다.</p>
</li>
<li><p><strong>html 웹사이트에서 파비콘(favicon)과 제목 설정하기 :</strong></p>
<ul>
<li><p>아이콘으로 사용할 이미지(보통 <code>.ico</code> 확장자 사용)를 html 파일이 위치한 폴더 중 어딘가에 둔다.</p>
</li>
<li><p><code>&lt;head&gt;</code> 안에 <code>&lt;title&gt;</code> 요소를 둠으로써 웹사이트 제목을, 그리고 <link> 요소로 파비콘을 설정한다. (아래 형식과 같이 입력하고 <code>href</code> 속성에 아이콘의 주소를 입력하면 됨.)</p>
<pre><code class="language-html">&lt;head&gt;
  &lt;title&gt;Page Title&lt;/title&gt;
  &lt;link rel=&quot;icon&quot; type=&quot;image/x-icon&quot; href=&quot;/images/favicon.ico&gt;&quot;
&lt;/head&gt;</code></pre>
<p>e.g.</p>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/b71e8e10-e93d-4b57-944d-1d67c57fbfae/image.png" alt="예시자료"></p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] 에.꾸 : HTML 꾸미기 (색, 텍스트 포매팅, style, 인용, css, 주석)]]></title>
            <link>https://velog.io/@h-seo-n/HTML-%EC%97%90.%EA%BE%B8-HTML-%EA%BE%B8%EB%AF%B8%EA%B8%B0-%EC%83%89-%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%8F%AC%EB%A7%A4%ED%8C%85-style-%EC%9D%B8%EC%9A%A9-css-%EC%A3%BC%EC%84%9D</link>
            <guid>https://velog.io/@h-seo-n/HTML-%EC%97%90.%EA%BE%B8-HTML-%EA%BE%B8%EB%AF%B8%EA%B8%B0-%EC%83%89-%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%8F%AC%EB%A7%A4%ED%8C%85-style-%EC%9D%B8%EC%9A%A9-css-%EC%A3%BC%EC%84%9D</guid>
            <pubDate>Thu, 14 Aug 2025 11:33:49 GMT</pubDate>
            <description><![CDATA[<h2 id="html-display--html-코드가-어떻게-화면에-표시되는지">HTML Display : HTML 코드가 어떻게 화면에 표시되는지</h2>
<ul>
<li><p>화면 크기에 따라 HTML 코드는 다르게 표시될 수 있으며, 한 칸을 초과하는 여백(space, Enter)은 HTML 표시 시 무시된다.
⇒ 줄바꿈 요소인 <code>&lt;br&gt;</code>을 입력하지 않는 한 두 줄 이상 띄울 수 X</p>
</li>
<li><p>Preformatted text(사용자 지정 형식의 텍스트)를 적을 수 있는 <code>&lt;pre&gt;</code> element를 이용하면 줄바꿈과 띄어쓰기가 보존됨.</p>
</li>
<li><p>ex :</p>
<pre><code class="language-html">&lt;h2&gt;reformatted text&lt;/h2&gt;
&lt;pre&gt;
This Mode
Should be g.  O.  O. O. D. 
To write Poems

       Right?
&lt;/pre&gt;</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/7d2d34a6-75d4-45a2-91eb-111c6fdb3df9/image.png" width=60%>

</li>
</ul>
<hr>
<h2 id="html-style-요소-꾸미기-">HTML Style (요소 꾸미기) :</h2>
<ul>
<li><p><strong>HTML Style</strong> : HTML 요소의 <code>style</code> 속성을 통해 색, 폰트, 크기 등과 같은 스타일을 조정할 수 있다.</p>
</li>
<li><p><strong>기본 형식</strong> : <code>&lt;tagname style=”property:value;”&gt;</code>  <br></p>
<p>  → 쌍따옴표 안의 <code>property:value</code>는 CSS 언어를 작성하는 거라 볼 수 있다.</p>
</li>
<li><p><strong>종류</strong> :</p>
<ul>
<li>배경색 - <code>“background-color:colorname;”</code></li>
<li>글자색 - <code>“color:colorname;”</code></li>
<li>폰트 - <code>“font-family:fontname;”</code></li>
<li>글자 크기 - <code>“font-size:100%;”</code></li>
<li>글자 정렬 - <code>“text-align:center;”</code></li>
</ul>
</li>
</ul>
<hr>
<h2 id="html-색깔-">HTML 색깔 :</h2>
<ul>
<li><p>미리 지정되어 있는 색깔 이름(140개의 색상 이름 지원), 혹은 RGB, HEX, HSL, RGBA, HSLA와 같은 색상 코드 값으로 표시할 수 있다.</p>
</li>
<li><p>style 속성을 통해 요소의 배경 색, 텍스트의 색, 그리고 테두리 색(ex. <code>”border:2px solid Tomato;”</code> )을 설정할 수 있다.</p>
</li>
<li><p><strong>RGB</strong> : <code>rgb(red, green, blue)</code> 의 형식으로, 삼원색이 각각 강한 정도를 0~255 범위의 숫자로 나타내면 됨.</p>
<ul>
<li><strong>RGBA</strong>: <code>rgba(red, green, blue, alpha)</code>  alpha 필드가 추가되었는데 불투명도를 0.0~1.0 범위의 값으로 지정해주면 됨.</li>
</ul>
</li>
<li><p><strong>HEX</strong>: hexadecimal의 줄임말으로 색을 16진수로 나타내는 형식. <code>#rrggbb</code> 빨강, 초록, 파랑 삼원색의 정도를 00<del>ff 로 나타낸다.(십진수로는 0</del>255로 rgb와 본질은 같음)</p>
</li>
<li><p><strong>HSL</strong>: <code>hsl(hue, saturation, lightness)</code>← 색, 채도, 밝기를 값으로 넣어주는 형식. 색은 색깔 종류로 0(빨강)에서 360 사이, 채도는 색의 강도로 퍼센티지 값. 밝기도 퍼센티지.</p>
<img src="https://velog.velcdn.com/images/h-seo-n/post/a4289d2f-9bd3-499b-8399-e5bc0bc13f7e/image.png" width=50%>
(출처 : https://en.wikipedia.org/wiki/HSL_and_HSV)


</li>
</ul>
<ul>
<li><strong>HSLA</strong>: rgb와 rgba와 마찬가지로, 투명도를 나타내는 alpha값이 추가된다.<br>

</li>
</ul>
<h4 id="⬇️-색-사용-예시-"><strong>⬇️ 색 사용 예시</strong> :</h4>
<pre><code class="language-html">  &lt;h1 style=&quot;background-color:Tomato;&quot;&gt;This is tomato color&lt;/h1&gt;
  &lt;p style=&quot;color:DarkOliveGreen; font-size:150%&quot;&gt;Dark Olive Green is good&lt;/p&gt;

  &lt;h2 style=&quot;border:5px solid #0A400C; background-color:#819067;
  text-align:center;&quot;&gt;
  &lt;br&gt;#819067&lt;br&gt;&lt;br&gt;
  &lt;/h2&gt;</code></pre>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/3823f653-9783-4da5-8107-d6da883d8b37/image.png" width=60%>

<hr>
<h2 id="html-text-formatting-글자-꾸미기-">HTML text formatting (글자 꾸미기) :</h2>
<ul>
<li><p>HTML은 글.꾸(글자꾸미기)를 위한 여러 &#39;formatting elements&#39;가 존재한다. (style과 달리 <em>속성_으로 설정하는게 아니라, 따로 _요소</em> 안에 꾸밀 텍스트를 넣어주는 식으로 사용해야 한다.)</p>
</li>
<li><p><strong>종류</strong> :</p>
<ul>
<li><code>&lt;b&gt;</code> : bold text, 볼드체</li>
<li><code>&lt;strong&gt;</code> : important text, 중요한 글(주로 볼드체랑 같음)</li>
<li><code>&lt;i&gt;</code> : italic text, 이탤릭체</li>
<li><code>&lt;em&gt;</code> : emphasized text, 강조되는 글(주로 이탤릭체랑 같음)</li>
<li><code>&lt;mark&gt;</code> : 글씨에 형광펜 쳐줌</li>
<li><code>&lt;small&gt;</code> : 글씨 작게 함</li>
<li><code>&lt;del&gt;</code> : 글자에 취소선</li>
<li><code>&lt;ins&gt;</code> : 삽입된inserted 글, 밑줄</li>
<li><code>&lt;sub&gt;</code> : subscript text (글을 밑으로 내림 - 예시에서 확인)</li>
<li><code>&lt;sup&gt;</code> : superscript text (글을 위로 올림 - 예시에서 확인)<br></li>
</ul>
</li>
<li><p><strong>사용 예시 :</strong></p>
<pre><code class="language-html">&lt;h1&gt;&lt;em&gt;emphasis&lt;/em&gt; is similar to &lt;I&gt;italics&lt;/I&gt;&lt;/h1&gt;
이건 중요하니까 &lt;mark&gt;밑줄쳐&lt;/mark&gt; &lt;br&gt;
근데 이건 안중요해서 &lt;small&gt;작게쓰자&lt;/small&gt;
&lt;br&gt; &lt;del&gt;취소선&lt;/del&gt;
&lt;br&gt; &lt;ins&gt;밑줄&lt;/ins&gt;
&lt;br&gt; 섭스크립트&lt;sub&gt;subscript&lt;/sub&gt;
&lt;br&gt; 수퍼스크립트&lt;sup&gt;superscript&lt;/sup&gt; </code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/9763882c-c491-4259-ae11-b82ec7a1b639/image.png" width=60%>

</li>
</ul>
<hr>
<h2 id="html-인용-요소들-">HTML 인용 요소들 :</h2>
<ul>
<li><code>&lt;blockquote&gt;</code> : 통으로 한 문단을 인용한다. 보통 인용하는 문단을 들여쓰기 하는 식으로 표시됨. 출처를 표시할 수 있는 cite 속성이 있는데, 스크린리더(청각 지원) 기능에서 주로 사용되고 따로 가시적으로 드러나지는 않는다.</li>
</ul>
<pre><code>```html
&lt;p&gt;책 블로그 홍보합니다:&lt;/p&gt;
&lt;blockquote
cite=&quot;[https://blog.naver.com/xersxe_y/223936326577](https://blog.naver.com/xersxe_y/223936326577)&quot;&gt;
고통과 문제상황 자체를 들여다보고 그것의 속절없음을 직시하며 마음의 아픔을 내어주는 것이 바로 공감이라고 생각한다.
&lt;/blockquote&gt;
```

&lt;img src=&quot;https://velog.velcdn.com/images/h-seo-n/post/1c1cc7ba-9dd4-44e4-b883-f38b14fb2403/image.png&quot; width=60%&gt;</code></pre><ul>
<li><p><code>&lt;q&gt;</code> : 짧은 한 문장을 문단 내에서 인용한다. 보통 따옴표로 인용구를 둘러싸는 형식으로 표시된다.</p>
</li>
<li><p><code>&lt;cite&gt;</code> : 작품의 제목을 나타내는 요소로 보통 이탤릭체로 안의 텍스트를 표시함.</p>
<pre><code class="language-html">  &lt;p&gt;
  &lt;cite&gt;아프다는 것에 관하여 - 앓기, 읽기, 쓰기, 살기&lt;/cite&gt;
  의 저자는 자신의 책에 대해 
  &lt;q&gt;이 책은 병condition이 삶에서 특정한 조건/상황/한계condition가 되었을 때 그 안에서 살아가며 배우고 생각한 것을 적은 책이다.
  &lt;/q&gt;라고 이야기한다.
  &lt;/p&gt;</code></pre>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/5bd88a6f-bed7-4236-8720-492651a9ff8b/image.png" width=80%>
  <br>
</li>
<li><p><code>&lt;abbr&gt;</code> : 긴 용어의 축약(abbreviation)을 표시해주는 요소로, title 속성에 원문을 작성하면 웹페이지에서 요소에 마우스를 갖다대면 원문이 표시되게 할 수 있다.</p>
</li>
<li><p><code>&lt;bdo&gt;</code>  : 안의 텍스트 방향을 바꿀 수 있다. <code>dir=”rtl”</code> 속성과 함께 하면 좌→우 방향에서 뒤집어 우→좌 방향으로 입력되게 할 수 있다.</p>
<pre><code class="language-html">  &lt;p&gt;
  밴드 커버 곡으로 좋다고 생각하는 노래 중 하나는 아이들의 
  &lt;abbr title=&quot;나는 아픈 건 딱 질색이니까&quot;&gt;아딱질&lt;/abbr&gt;
  이다. 제목을 반대로 하면 &lt;bdo dir=&quot;rtl&quot;&gt;나는 아픈 건 딱 질색이니까&lt;/bdo&gt;이다.
  &lt;/p&gt;</code></pre>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/ff0ba67d-84fd-4b3d-9c43-db1f5cf2fbb0/image.png" width=80%>

   <br>
</li>
<li><p><code>&lt;address&gt;</code> : 이메일 주소, 실제 장소 주소, 휴대폰 번호, sns 아이디 등의 연락처 정보를 표시하는 요소. <address> 요소 앞뒤로는 자동적으로 줄바꿈이 추가된다.</p>
</li>
</ul>
<pre><code>```html
&lt;address&gt;
허서연 작성 &lt;br&gt;
Take a look at my project: &lt;br&gt;
https://github.com/h-seo-n/Scream-Jar &lt;br&gt;
&lt;/address&gt;
```

&lt;img src=&quot;https://velog.velcdn.com/images/h-seo-n/post/1ab58e93-9f35-4273-bd05-37351c9edf1b/image.png&quot; width=60%&gt;</code></pre><hr>
<h2 id="html-주석-comment-">HTML 주석 (Comment) :</h2>
<ul>
<li><p>형식 : <code>&lt;!--how to write comments--&gt;</code><br></p>
<p>  시작에 느낌표가 있지만 끝에는 없음, 적고자 하는 내용을 <code>&lt;!--</code> <code>--&gt;</code>  로 감싸주어야 함. <br></p>
<p>  → 코드 설명 혹은 주의할 점을 주석으로 적을 때, 혹은 작성한 HTML 코드를 숨길 때 이용한다.</p>
<pre><code class="language-html">  &lt;p&gt;Gonna hide some elements&lt;/p&gt;
  &lt;!--
  &lt;h1&gt;Like this oversized title&lt;/h1&gt;
  --&gt;
  &lt;p&gt;You can&#39;t see it &lt;!--or this--&gt; now.&lt;/p&gt;</code></pre>
  <img src="https://velog.velcdn.com/images/h-seo-n/post/28dc789c-917c-496e-9a66-56dbed10c7e5/image.png" width=60%>

</li>
</ul>
<hr>
<h2 id="html-css">HTML CSS</h2>
<ul>
<li><p><em>cascading</em> - 위에서 아래로 내려온다는 뜻, 즉 부모 요소(parent element)에 적용한 스타일이 자식 요소(child element)까지 적용되므로 더 편하게 스타일링 가능.</p>
</li>
<li><p>이용 방식 :</p>
<ul>
<li>Inline : HTML 요소 안의 style 속성 사용 (위에서 본 대로)</li>
<li>Internal : HTML 파일 상단의 <code>&lt;head&gt;</code> 태그 안에 <code>&lt;style&gt;</code> 요소를 넣어줌<ul>
<li>하나의 html 페이지 전체에 적용되는 스타일을 정의.</li>
</ul>
</li>
<li>External: <code>&lt;link&gt;</code>요소를 사용하여 외부 CSS 파일을 연결해줌</li>
</ul>
</li>
<li><p>예시 : </p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;

&lt;head&gt;
**&lt;style&gt;**
body {text-align: center;}
h1 {background-color: SeaGreen; color: PapayaWhip;} 
p {background-color: #FAFAD2; color: #20B2AA;}
**&lt;/style&gt;**
&lt;/head&gt;

&lt;body&gt;
&lt;h1&gt;This is heading style&lt;/h1&gt;
&lt;p&gt;This is a paragraph&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<img src="https://velog.velcdn.com/images/h-seo-n/post/8eb03430-473d-4ca5-9f76-b378e3bdd756/image.png" width=60%>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HTML] HTML의 기본적 구조]]></title>
            <link>https://velog.io/@h-seo-n/HTML-HTML%EC%9D%98-%EA%B8%B0%EB%B3%B8%EC%A0%81-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@h-seo-n/HTML-HTML%EC%9D%98-%EA%B8%B0%EB%B3%B8%EC%A0%81-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Tue, 05 Aug 2025 06:40:22 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p><strong>HTML : Hyper Text Markup Language</strong></p>
<p>  ⇒ 웹페이지의 구조(무슨 요소가 어떻게 표시되는지 등)를 HTML으로 작성한다고 볼 수 있음.</p>
</li>
<li><p><strong>CSS :</strong> Cascading Style Sheets → 웹페이지의 디자인을 구현하는 데에 HTML과 함께 이용.</p>
</li>
<li><p><strong>맥에서 단순하게 HTML 코드 작성하기 : TextEdit 앱 사용</strong>  <br></p>
<p>  ⇒ 설정: 상단 바에서 <em>Format &gt; “Make Plain Text”</em> option &amp;  설정의 “<em>Open and Save” &gt; “Display HTML Files as HTML code instead of formatted text”</em>  <br></p>
<p>  ⇒ 파일 작성 후 저장 시 <code>.htm</code> 혹은 <code>.html</code> 확장자로 저장 &amp; UTF-8 encoding 선택  <br></p>
<p>  ⇒ TextEdit으로 작성한 HTML 코드 파일을 열 때, 두번째 사진과 같은 웹페이지의 형식으로 나타남.  </p>
</li>
</ul>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Page Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;h1&gt;My First Heading&lt;/h1&gt;
&lt;p&gt;My first paragraph.&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/h-seo-n/post/b201215c-6627-49c4-bfdc-3ddcdb8c5f58/image.png" alt="ex"></p>
<ul>
<li><p>모든 HTML 문서의 시작 - document type declaration(문서 형식 선언)
: <code>&lt;!DOCTYPE html&gt;</code></p>
</li>
<li><p>그 뒤에는 <code>&lt;html&gt;</code> 태그가 뒤따르며, 문서의 가장 마지막에는 <code>&lt;/html&gt;</code> 태그로 문서의 끝을 표시</p>
</li>
<li><p>사용자에게 보이는 가시적인 부분을 담당하는 코드는 <code>&lt;body&gt;</code> … <code>&lt;!body&gt;</code> 사이에 있다. (그 안의 <code>&lt;h1&gt;</code>과 <code>&lt;p&gt;</code> 은 각각 제목과 본문을 보여주는 HTML 요소들.)</p>
<br>
<hr>
## **HTML elements (HTML 요소) :**
</li>
<li><p>시작 태그부터 끝 태그까지의 전체를 HTML 요소라고 함. <code>&lt;tagname&gt;content&lt;/tagname&gt;</code></p>
<p>  (e.g. 위 코드에서 <code>&lt;h1&gt;My First Heading&lt;/h1&gt;</code> 그리고 <code>&lt;p&gt;My first paragraph.&lt;/p&gt;</code> 이 해당하며 사실상 <code>&lt;html&gt;</code>과 <code>&lt;head&gt;</code> <code>&lt;body&gt;</code>도 요소라고 볼 수 있음.)</p>
<p>  (HTML 요소의 끝 태그(end tag)를 생략해도 코드가 돌아갈 때도 있지만 … 가급적이면 쓰는 것을 잊지 말자!)</p>
</li>
<li><p>한 HTML 요소 안에 다른 HTML 요소가 중첩되어 있다. (HTML elements can be nested)</p>
<p>  ⇒ HTML 문서는 문서 형식 선언 뒤, 핵심 요소(root element)인 <code>&lt;html&gt;</code> 태그와 그 안의 <code>&lt;body&gt;</code> 안에 중첩된 여러 기타 HTML 요소로 구성되어있다고 볼 수 있음.</p>
</li>
<li><p>하나의 태그만으로 구성된 기타 HTML 요소 :</p>
<ul>
<li><code>&lt;br&gt;</code> : 줄바꿈을 하게 하는 요소</li>
<li><code>&lt;hr&gt;</code> : 가로로 된 구분선을 표시하는 요소</li>
</ul>
</li>
</ul>
<h3 id="기본적인-html-요소들"><strong>기본적인 HTML 요소들</strong></h3>
<ul>
<li><p><strong>HTML 제목 (Headings) :</strong></p>
<p>  제목 크기에 따라 태그 <code>&lt;h1&gt;</code> ~ <code>&lt;h6&gt;</code> 까지 있음. ➡️ 서치엔진이 제목을 이용해 웹페이지 내용을 구조화하고, 웹페이지 유저도 제목으로 웹을 훑곤 하기에 제목은 중요! 단순 텍스트 크기를 크게 하려고 heading을 쓰는 것은 권장하지 X.<br></p>
<ul>
<li>제목 폰트 크기 수정 - <code>&lt;h1 style=“font-size:60px;”&gt;Heading 1&lt;/h1&gt;</code><br></li>
</ul>
</li>
<li><p><strong>HTML 본문 (Paragraph) :</strong></p>
<p>  본문은 <code>&lt;p&gt;</code> 태그로 정의됨, 본문은 항상 새로운 줄에서 시작함. : <code>&lt;p&gt; content &lt;/p&gt;</code></p>
  <br></li>
<li><p><strong>HTML 링크(URL) :</strong> URL은 <code>&lt;a&gt;</code> 태그로 정의됨 <br></p>
<p>  e.g. <code>&lt;a href=“[https://example.url](https://example.url/)”&gt;attribute-url name&lt;/a&gt;</code> </p>
  <br></li>
<li><p><strong>HTML Image :</strong></p>
<p>  <code>&lt;img&gt;</code> 태그와 함께 소스 파일 <code>src</code>, 대체 텍스트 <code>alt</code>, 너비와 길이 <code>width</code> &amp; <code>height</code>을 제공. :
  e.g. <code>&lt;img src=“name.jpg” alt=“text” width=“104” height=“142”&gt;</code></p>
<p>  → 넣고자 하는 이미지가 html 코드 파일과 같은 위치에 있어야 함</p>
<p>  <br><br></p>
</li>
<li><p>종합 예시 코드 및 페이지</p>
</li>
</ul>
<pre><code>```html
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;

&lt;h1&gt;The Biggest Header&lt;/h1&gt;
&lt;h2&gt;The Smaller One&lt;/h2&gt;
&lt;p&gt;now the paragraph&lt;/p&gt;
&lt;h6&gt;The Smallest Header&lt;/h6&gt;
&lt;p&gt;for the GitHub link: &lt;/p&gt;
&lt;a href=&quot;https://github.com/h-seo-n/Scream-Jar&quot;&gt;Scream Jar URL&lt;/a&gt;

&lt;/body&gt;
&lt;/html&gt;
```

![](https://velog.velcdn.com/images/h-seo-n/post/ff31af9d-7ec6-4fc5-a419-a003ad83c9b9/image.png)</code></pre><blockquote>
</blockquote>
<p>실제 웹사이트에서 우클릭 후 검사(inspect)를 누름으로써 HTML 요소를 코드로 확인할 수 있다.
전체 소스 코드는 Ctrl + U 또는 우클릭을 하고 &quot;페이지 소스 보기&quot;를 누름으로써(view page source) 가능하다.</p>
<br>
<hr>

<h2 id="html-attributes-html-속성-">HTML Attributes (HTML 속성) :</h2>
<ul>
<li>HTML 요소에 대해 추가적인 정보를 제공하는 코드로, HTML 요소의 시작 태그 안에 작성된다. 주로 <code>name=“value”</code> 의 형태로 작성됨.</li>
</ul>
<h3 id="예시-">예시 :</h3>
<ul>
<li><p><strong>url 링크 요소</strong>의 속성들</p>
<ul>
<li><p><code>href</code> : URL 요소에서 페이지의 주소를 명시함</p>
<p>  e.g. <code>&lt;a href=“[https://example.url](https://example.url/)”&gt;url name&lt;/a&gt;</code></p>
</li>
<li><p><code>target</code> : url을 어디서 열지 명시하는 속성 (명시하지 않으면 현재 열린 브라우저에서 엶)</p>
<ul>
<li><p><code>target=”_self”</code> : 디폴트 값, 클릭된 브라우저 창에서 열림</p>
</li>
<li><p><code>_blank</code> : 새 브라우저 창에서 링크가 열림</p>
</li>
<li><p><code>_parent</code> : 현재 창이 프레임으로 구성되었을 때 부모 프레임(parent frame)에서 링크가 열림</p>
</li>
<li><p><code>_top</code> : 현재 창이 프레임으로 구성되었을 때 모든 프레임이 사라지고 전체 화면에서 링크 페이지가 열림</p>
<p>  (참고: <a href="https://m.blog.naver.com/mathesis_time/222039284932">https://m.blog.naver.com/mathesis_time/222039284932</a>) <br></p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>이미지 요소</strong>의 속성들 :</p>
<ul>
<li><p><code>src</code> : 이미지의 주소를 명시(외부 웹에 있는 이미지의 경우 url을 입력하거나, 현재 웹 내부에 이미지를 두고 현재 웹의 주소에 대한 ‘상대적인 url(relative url / e.g. <code>src=“/images/img_girl.jpg</code>”)’를 입력)</p>
</li>
<li><p><code>width</code>, <code>height</code> : 너비, 높이 / <code>alt</code> : 대체 텍스트(alternative text)</p>
<p>e.g. <code>&lt;img src=“name.jpg” alt=“text” width=“104” height=“142”&gt;</code> </p>
</li>
</ul>
</li>
<li><p><code>lang</code> : html 요소의 시작 태그 안에서 웹페이지의 언어가 무엇인지 알려줌. 첫 두 글자는 언어를, 뒤 두 글자는 국가를 명시.  <br> </p>
<p>  e.g. <code>&lt;html lang=“en-US”&gt;</code> <br></p>
</li>
<li><p><code>title</code> : html 요소의 이름을 지정한다고 할 수 있음. 웹페이지에서 요소에 마우스를 갖다댔을때 tooltip(설명 메시지)를 나타나게 함.<br></p>
<p>  <code>&lt;p title=“I’m a tooltip”&gt;This is a paragraph.&lt;/p&gt;</code></p>
</li>
</ul>
<p>⇒ Attribute 작성 시 <code>name=&quot;value&quot;</code>에서 이름을 소문자로 적기를 권장, 그리고 값을 따옴표로 감싸주기를 권장.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[강의] 현대 딥러닝이 가능했던 이유: Hardware Lottery의 승자 GPU]]></title>
            <link>https://velog.io/@h-seo-n/%EA%B0%95%EC%9D%98-%ED%98%84%EB%8C%80-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%B4-%EA%B0%80%EB%8A%A5%ED%96%88%EB%8D%98-%EC%9D%B4%EC%9C%A0-Hardware-Lottery%EC%9D%98-%EC%8A%B9%EC%9E%90-GPU</link>
            <guid>https://velog.io/@h-seo-n/%EA%B0%95%EC%9D%98-%ED%98%84%EB%8C%80-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%B4-%EA%B0%80%EB%8A%A5%ED%96%88%EB%8D%98-%EC%9D%B4%EC%9C%A0-Hardware-Lottery%EC%9D%98-%EC%8A%B9%EC%9E%90-GPU</guid>
            <pubDate>Tue, 29 Jul 2025 12:12:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>서울대학교 차세대데이터 여름학교 2025 단기 특강</p>
</blockquote>
<h3 id="some-history-hardware-lottery">Some History, Hardware Lottery</h3>
<ul>
<li><p>AI winter for Neural Networks :</p>
<ul>
<li><p>원래 Neural Networks는 지금 hype 된 것보다 성능이 훨씬 별로였다</p>
<ul>
<li><p>Marvin Minsky (1969) : “Nerual Networks” cannot learn the XOR relationship .. only linearly separable problems … and become too large for complex knowlege</p>
<p>→ 하지만 Single layer이 아닌 <strong>“Deep” neural network</strong>의 학습이 가능한 하드웨어적 기반이 마련되면서 NN의 성능 비약적 향상</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong><em>The Hardware Lottery</em> :</strong></p>
<ul>
<li><p>Sara Hooker이 Communications of the ACM, 2021에 주장한 개념. GPU의 발달으로 DNN이 AI 핵심 기술로 거듭난 것에 대한 설명</p>
<blockquote>
<p>Some ideas win, not because they are better, but because they work better with existing hardware</p>
</blockquote>
</li>
<li><p><strong>Hardware Lottery Winners: General-Purpose CPU Threads</strong></p>
<ul>
<li><p>chip의 집적도가 증가한다는 Moore’s Law + chip 성능 향상에도 power density가 일정하게 유지된다는 Dennard Scaling = Dependable performance scaling</p>
</li>
<li><p>위와 같이 Moore’s Law와 Dennard Scaling이 건재할 때에는 미래에 더 빠른 general-purpose hardware(cpu)가 등장할 것이므로, 굳이 (gpu와 같은) 특화된 specific-purpose hardware을 개발할 필요가 없었다.</p>
<p>  → Resources focused on making general purpose CPUs faster</p>
</li>
<li><p>이러한 Von-Neumann general-purpose CPU는 :</p>
<ul>
<li><p>not very good with parallel execution 병렬 계산 별로 X</p>
<p>  &amp; not much emphasis on memory bandwidth 메모리 접근 속도도 별로 X</p>
</li>
<li><p>Thus, efficient with branch-heavy expert systems : favors symbolic approaches to AI (LISP, Prolog)</p>
</li>
<li><p>But inefficient with neural networks with massivel parallel matrix multiplication! → 즉 hardware lottery losers: nerual network</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Hardward Lottery Losers: Nerual Nets and the AI Winter</strong></p>
<ul>
<li><p>general-purpose CPU 기반 연구에 집중하다 보니, 원래의 하드웨어가 잘 수행할 수 있는 symbolic approaches에만 집중. Neural net은 cpu로 학습시키기 너무 비효율적이었고 학습을 위한 cpu 여력이 부족하기도 했다.</p>
</li>
<li><p>Neural Network 이론 자체는 이미 등장했지만, lost the hardware lottery (cpu로 학습시키기 힘듦) → AI winter</p>
</li>
<li><p>NN을 위한 특화된 하드웨어를 개발하기 위한 벤처 기업들이 등장하긴 했지만(e.g. “connection machine”, 1985) 흐지부지됨</p>
<p>→ 그런데 어떻게 NN이 이렇게 승승장구 할 수 있었는가?</p>
<p>바로 다른 것도 아닌 <strong>Video game</strong> 때문!</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>요약</strong> : AI 흐름과 별개로 Video game 업체에서 3D 그래픽 게임에 대한 수요가 점점 높아지며, 이를 구현하기 위한 하드웨어 개발이 진행되며 3D Accelerator card, 그리고 gpu가 당연한 수순처럼 등장했다.</p>
</li>
<li><p>Graphic Processing History</p>
<ul>
<li><p>1990’ : 게임을 위한 Real-time 3D rendering 보편화 (Doom, Quake, Descent, … ) + 3D 그래픽 처리가 점점 컴퓨팅 연산 많이 필요</p>
<p>  <img src="https://velog.velcdn.com/images/h-seo-n/post/fdbc520d-e614-48af-9993-adace422de2c/image.png" alt="game"></p>
</li>
</ul>
</li>
</ul>
<pre><code>    3D accelerator(gpu)가 도입되기 전에 cpu의 연산으로 감당할 수 있는 게임 그래픽을 구현할 수 있도록 하는 gimmick들이 있었다

    - Doom (1993) : “*Affine texture mapping*”

        → linearly maps textures to screen location (3D model → 2D space), disregarding depth;

        게임 내 비스듬한 평면이 없었던 이유는 이를 가리기 위해

    - Quake III arena (1999) : “*Fast inverse square root*”

        → 3d graphic에 필요한 연산을 우회적인 연산식으로 수행 (magic!)

        ![affine](https://velog.velcdn.com/images/h-seo-n/post/1078154a-2410-4a13-b525-3e7de4a17b0b/image.png)


- 그러다가 Starfox(1993)에서 처음으로 dedicated accelerator (전용 가속기 칩) 등장
    - 가정용 게임기에서 처음으로 3d 그래픽을 구현한 super Fx chip : 게임기 안에 cpu를 하나 더 넣은 것이라 생각할 수 있음 (16-bit RISC processor + some plotting commands, Dynamic Memory Access)

        ![](https://velog.velcdn.com/images/h-seo-n/post/d042f1e3-a408-4b58-a820-625615e0e2c0/image.png)


        - DMA; Dynamic Memory Access 가벼운 설명

            ![](https://velog.velcdn.com/images/h-seo-n/post/c6cdf995-184c-475b-8d21-eb00669a7a8e/image.png)


            Dynamic Memory Access; 즉 DMA unit이 CPU와 독립적이게 메모리 작업을 할 수 있게 하는 구조

            CPU는 DMAC, 즉 DMA control unit을 통해 메모리 작업 시작 명령을 내리고 다른 작업을 하다가 DMAC에서 작업 완료 신호를 받을 수 있음.

- Introduction of 3D Accelerator Cards :

    → 3D 게임에 대한 수요가 많아지며 3D Accelerator card가 당연한 수순처럼 등장

    3D 그래픽 처리는 많은 양의 데이터를 반복 처리하는 짧은 알고리즘 → 전용 가속기accelerator에서는 단순 연산을 빠르게 병렬적으로 계산해 줌.

    기본 원리 : 그래픽 카드에서 렌더링의 많은 연산을 대신 해준 후 모니터로 보내는 방식</code></pre><br>

<p>→ 요컨대 AI를 빼어놓고 보아도 graphic card, gpu의 등장은 게임 측면의 marketing pressure에 의한 당연한 수순이었다!</p>
<ul>
<li><p>단순 병렬 연산을 수행하는 core의 개수가 많아지고, 그에 비해 cache/control이 차지하는 비율은 ↓</p>
<p>  <img src="https://velog.velcdn.com/images/h-seo-n/post/05362ca8-d11f-431e-a5df-518787e25aee/image.png" alt="chip"></p>
</li>
</ul>
<ul>
<li>High-Performance Graphics Memory<ul>
<li>100K+ threads place enormous pressure on memory!  </li>
</ul>
</li>
</ul>
<pre><code>    → gpu의 많은 연산 thread를 위해 효과적으로 빠른 메모리 필요

- Modern GPUs empoy 3-D stacked memory via a silicon interposer

    → 이런 HBM (High-Bandwidth Memory) 구조는 더 넓은 data bus를 제공함으로써 이런 문제 방지

    ![](https://velog.velcdn.com/images/h-seo-n/post/5586bada-43c0-402f-aa5a-e0cd44e42bee/image.jpg)


    e.g. HBM2 in Volta, Ampere, etc

    → HBM2도 AI 이전 게임 때문에 등장</code></pre><br>

<ul>
<li><strong>Thus New Hardware Lottery Winners : GPUs</strong></li>
</ul>
<pre><code>→ 이렇게 공교롭게 3D 게임을 위해 등장한 GPU가 nerual network를 가능하게 함

→ gpus originally designed for gaming; massively parallel; high memory bandwith; re-purposed for training.</code></pre><ul>
<li><p>CNNs and GPUs → perfect match!</p>
<p>  ⇒ CNN은 동일한 matrix multiplication(multiply-add) 연산을 필요로 하므로 GPU로 병렬 연산하기 좋음</p>
<ul>
<li>two papers using CNNs to identify cats :</li>
</ul>
</li>
</ul>
<pre><code>    * “_Building high-level features using large scale unsupervised learning_”(2012) : 16000 cpu 사용

    * “*Deep learning with COTS HPC systems*”(2013) : two cpu cores + two gpus 사용

    → 둘의 결과가 맞먹을 정도로 gpu의 위력이 대단했다!

- “what other ideas are we missing due to the hardware lottery?”
    - 좋은 idea인데도 실현가능한 하드웨어가 없어서 놓치고 있을 수도 있음
    &lt;br&gt;</code></pre><ul>
<li><p><strong>Yet another lottery winners : specialized hardware</strong></p>
<ul>
<li><p>DNN에 더욱 최적화된 하드웨어 개발 진행 : tensore cores in GPUs, bfloat units in CPUs, TPUs …</p>
<ul>
<li><p>Quantizied arithmetic, unstructured pruning 등 dnn 외에는 적용할 게 없는 매우 specific 것들도 하드웨어 개발에 적용</p>
<p>→ 이런 specialized hardware은 더욱 큰 AI 모델을 가능하게 함(더 많은 파라미터)</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Yet another lottery losers : Non-DNN Models</strong></p>
<ul>
<li><p>하지만 DNN만큼 복잡한 대안 알고리즘이 TPU/GPU로 training 가능하지 않다면; 실현가능하지 않으므로 역사의 뒷전으로 빠짐 … (e.g. “capsule network”)</p>
<ul>
<li><p>다른 예시 graph nerual network : convolutions on graph-structure data, reasoning about non-euclidean data structure</p>
<p>  → graph data는 주로 매우 sparse하기 때문에 accelerator/gpu에 잘 맞지 않음</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Thus ; researchers will gravitate towards models/algorithms well-suited for GPU/TPU/Matrix multiplication since it is the most feasible</p>
<p>  → what great ideas are we missing because they lost the hardware lottery?</p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>