<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>5_wintaek.log</title>
        <link>https://velog.io/</link>
        <description>물음표를 느낌표로 바꾸는 개발자</description>
        <lastBuildDate>Mon, 09 Jun 2025 14:18:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>5_wintaek.log</title>
            <url>https://velog.velcdn.com/images/5_wintaek/profile/23adec8f-f1e2-4243-a5fc-d1e4ae96b744/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 5_wintaek.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/5_wintaek" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CSS-in-JS 와 Zero Runtime(feat:kakao style 기술블로그)]]></title>
            <link>https://velog.io/@5_wintaek/CSS-in-JS-%EC%99%80-Zero-Runtimefeatkakao-style-%EA%B8%B0%EC%88%A0%EB%B8%94%EB%A1%9C%EA%B7%B8</link>
            <guid>https://velog.io/@5_wintaek/CSS-in-JS-%EC%99%80-Zero-Runtimefeatkakao-style-%EA%B8%B0%EC%88%A0%EB%B8%94%EB%A1%9C%EA%B7%B8</guid>
            <pubDate>Mon, 09 Jun 2025 14:18:27 GMT</pubDate>
            <description><![CDATA[<h3 id="들어가는-글">들어가는 글</h3>
<p>프론트엔드팀의 디자인 시스템 글을 읽다가 꼬리의 꼬리를 무는 질문이 머릿속에 마주잡이로 생겨 글을 쓰게 되었습니다.</p>
<p>CSS-in-JS는 어떤걸까 ?  스타일 캐싱이 무슨 뜻이며 SSR의 어려움이 정확히 무엇을 말하는걸까 ? 
제로 런타임은 무슨 방식인지 어떤 차이점이 있을까 ? 라는저의 질문에 답을 하기 위해 글을 한번 풀어서 써보겠습니다.</p>
<h2 id="카카오-프론트엔드-팀의-디자인-시스템-재구축기-글">카카오 프론트엔드 팀의 디자인 시스템 재구축기 글</h2>
<p>카카오 프론트엔드팀 UI/UX를 통합적으로 관리할 수 있는 디자인 시스템의 재구축 글 중 목표 설정의 있는 글입니다.</p>
<p>프로젝트 구조 정의: 모듈화를 통해 유지보수와 확장성을 개선.
스타일링 방식 선택: 런타임 CSS-in-JS 방식의 한계를 고려하여 제로 런타임 스타일링 방식으로 전환.
CSS 변수 기반 디자인 토큰과 테마 관리: 테마와 디자인 토큰 관리의 효율성 개선.
상품 카드 컴포넌트 일원화: 중복 코드를 줄이고 일관성을 높이기 위한 시스템 통합.</p>
<p>자.. 여기서 제가 메인으로 궁금한 CSS-in-JSS 는 무엇이고 제로 런타임과 무엇이 차이가 있는지 간단하게 설명을 해보겠습니다.</p>
<h3 id="css-in-js-는-무엇일까-">CSS-in-JS 는 무엇일까 ?</h3>
<p><strong>CSS-in-JS는 JavaScript 코드 안에서 CSS를 작성하는 방식입니다.</strong></p>
<p>우리가 흔히 아는 styled-components 입력 방식을 예시로 들겠습니다.</p>
<pre><code class="language-python">import styled from &#39;styled-components&#39;;

const Button = styled.button`
  background-color: blue;
  color: white;
`;</code></pre>
<h3 id="장점은-어떤게-있나-">장점은 어떤게 있나 ?!</h3>
<ul>
<li>JavaScript 코드 안에 CSS를 정의 → 모듈화, props 기반 스타일링 가능</li>
<li>컴포넌트 단위로 스타일이 분리됨</li>
<li><strong>런타임에 CSS가 생성됨</strong></li>
</ul>
<h3 id="단점들은-어떤게-있나">단점들은 어떤게 있나?</h3>
<p>styled-components 같은 라이브러리는 컴포넌트가 렌더링될 때 <strong>동적으로 CSS를 생성합니다.</strong></p>
<p>만약 같은 스타일의 버튼이 수십 개 있다면, 스타일도 매번 확인하거나 다시 생성합니다.</p>
<ul>
<li><strong>성능 문제</strong>: 앱 실행 중, 스타일을 동적으로 생성 → 초기 로딩 속도 저하</li>
<li><strong>디버깅 불편</strong>: CSS가 동적으로 생성되므로 클래스명이 직관적이지 않음.<ul>
<li>여기서 말하는 클래스명이 직관적이지 않다! 라는거는 개발자 도구 시점에서 styeld-component는 class 명이 <code>sc-kvZOFW</code> 같은 <strong>무작위 해시 클래스명</strong>이 나온다는 뜻입니다.</li>
</ul>
</li>
<li><strong>스타일 캐싱과 SSR 어려움</strong>: 서버사이드 렌더링 시 추가 처리 필요</li>
</ul>
<blockquote>
<p>여기서 말하는 <code>스타일 캐싱</code>이란 이미 생성한 스타일을 <strong>브라우저나 서버에서 재사용</strong>해 중복 생성을 피하는 기법입니다.</p>
</blockquote>
<blockquote>
<p>그리고 여기서 말하는 SSR의 어려움을 정확히 뜻하는 말은 
서버 사이드 렌더링(SSR)은 서버에서 HTML을 먼저 만들어 클라이언트로 보내는 방식인데 CSS-in-JSS를 사용하려면, 예를들어 styled-component는 ServerStyleSheet 를 사용해야합니다.</p>
</blockquote>
<p>root가 되는 app.tsx 같은 페이지에서 커스터마이징이 필수이고 설정을 하지 않게 된다면 스타일이 서버에서 적용되지 않거나, FOUC(스타일 깜빡임) 발생합니다. 또한 CSS 캐시나 최적화 처리를 수동으로 해야 합니다.</p>
<h3 id="제로-런타임-css-in-js란">제로 런타임 CSS-in-JS란?</h3>
<p><strong>Zero Runtime CSS-in-JS</strong>는 브라우저에서 CSS를 생성하지 않는 방식입니다. 즉, 빌드 타임에 CSS를 생성합니다.</p>
<p>HTML에 <code>&lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot; /&gt;</code>처럼 자동 포함이 됩니다. 또한 SSR 중 별도의 스타일 추출 없이 그냥 HTML+CSS만 렌더링하면 됨</p>
<p>대표로 <code>Vanilla Extract, Linaria</code> 가 있습니다.</p>
<p><strong>제로 런타임의 특징은 어떤게 있을까?</strong></p>
<ol>
<li><strong>CSS 변수 기반 스타일링 지원</strong>: CSS 변수를 활용해 디자인 토큰을 유연하게 관리할 수 있는지.</li>
<li><strong>유연한 테마 관리</strong>: 다크 테마, 라이트 테마 등 다양한 테마를 효율적으로 지원할 수 있는지.</li>
<li><strong>타입 안전성 및 휴먼 에러 방지</strong>: 오타로 인한 오류를 방지하고, 존재하지 않는 토큰 사용 시 개발자가 즉각 피드백을 받을 수 있는지.</li>
<li><strong>가벼운 번들 사이즈</strong>: 서비스 번들 크기를 최소화할 수 있는지.</li>
<li><strong>제로 런타임 지원</strong>: 스타일링은 빌드 시 생성되어야 하며, 런타임이 필요한 경우에도 최소한의 크기로 RSC와 호환 가능한지.</li>
</ol>
<p><strong>Vanilla Extract</strong>:</p>
<ul>
<li><strong>장점</strong>:<ul>
<li>타입 안전성과 디자인 시스템 구축에 필요한 유틸리티 제공 (e.g., styleVariants, recipes, sprinkles, createThemeContract)</li>
<li>기본적으로 빌드 시 스타일을 추출하며, 필요시 런타임 스타일도 제공</li>
<li>프레임워크에 구애받지 않음</li>
<li>런타임 스타일 사용 시 가벼운 번들 크기 (GZIP 기준 718B)</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>Tagged Template Literals 미지원</li>
<li>복잡한 CSS 선택자 사용 제한</li>
<li>초기 학습 러닝 커브 존재</li>
</ul>
</li>
</ul>
<p><strong>Linaria</strong>:</p>
<ul>
<li><strong>장점</strong>:<ul>
<li>Emotion과 유사한 사용법으로 러닝 커브가 낮음</li>
<li>스타일을 빌드 시 생성하므로 RSC와 호환 가능</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>Vanilla Extract와 비교해 고도화된 유틸리티 미제공</li>
</ul>
</li>
</ul>
<h3 id="zero-runtime은-디버깅이-용이한가">Zero-runtime은 디버깅이 용이한가?</h3>
<p>styeld-component와 다르게 빌드 시 클래스명을 정적이고 직관적으로 생성합니다.</p>
<p>build전</p>
<pre><code class="language-jsx">const button = style({
  backgroundColor: &#39;blue&#39;,
})</code></pre>
<p>build후</p>
<pre><code class="language-jsx">.button_1a2b3c {
  background-color: blue;
}</code></pre>
<h3 id="마무리-글">마무리 글</h3>
<p>꼬리의 꼬리를 무는 저의 머릿속에 남아있던 질문들을 한번 정리해보았습니다. 무심코 쓰던 styled-component가 CSS-in-JS 방식인것 또한 알았고 CSS-in-JS 이 무엇인지 머릿속에 있었습니다만! 정말 정확하게 설명하라하면 입으로 대답하기 힘들것같아 한번 글로 다시 써보며 리마인드를 시켜보았습니다. 간단하게 CSS-in-JS 와 Zero-runtime 의 차이점이라고 보면 될 것 같습니다.</p>
<h3 id="ref">Ref</h3>
<blockquote>
<p><a href="https://devblog.kakaostyle.com/ko/2024-12-13-1-rebuilding-frontend-design-system/">카카오 프론트엔드팀의 기술블로그</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[무작정 시작해보기) Next.js 는 왜 tailwind.CSS를 추천할까? - (1)]]></title>
            <link>https://velog.io/@5_wintaek/Next.js-%EB%8A%94-%EC%99%9C-tailwind.CSS%EB%A5%BC-%EC%B6%94%EC%B2%9C%ED%95%A0%EA%B9%8C-1</link>
            <guid>https://velog.io/@5_wintaek/Next.js-%EB%8A%94-%EC%99%9C-tailwind.CSS%EB%A5%BC-%EC%B6%94%EC%B2%9C%ED%95%A0%EA%B9%8C-1</guid>
            <pubDate>Tue, 20 May 2025 09:03:04 GMT</pubDate>
            <description><![CDATA[<h3 id="설명하기-앞서">설명하기 앞서</h3>
<p>무작정 Next.js App Router를 통해 토이프로젝트를 하나 만들고 싶어 글을 쓰게 되었다. 공식 홈페이지를 통해 프로젝트를 생성했는데 설치 시 다음과 같은 프롬프트가 하나 떳다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/0dfaa48f-ba52-4161-a449-39ef8b022353/image.png" alt=""></p>
<p>한번도 사용 해보지 않은 Tailwind CSS부터 생소한 Turbopack 까지 왜 사용하기를 물어보는지 궁금해서 우선적으로 제일 궁금한 &#39;왜 Tailwind CSS를 권장할까?&#39;에 대한 의문점을 풀고자 글을 쓰게 되었다.</p>
<p>설명하기 앞서 렌더링 방식에 대해 설명하고 들어가는게 좋을 것 같아 렌더링 방식 및 정적 렌더링과 동적 렌더링이 무엇인지 짚고 설명하겠습니다.</p>
<h3 id="nextjs-의-렌더링-방식">Next.js 의 렌더링 방식</h3>
<p>기본적으로 Next.js는 SSR 방식과 정적 사이트(SSG)을 지원합니다.</p>
<p> 리액트와 같은 자바스크립트 기반으로 하는 어플리케이션은 자바스크립트로 동작하기 떄문에 자바스크립트를 다운로드 한 후에 브라우저에서 자바스크립트가 실행되어야 화면에 UI가 표시됩니다.(CSR)라고 하죠. 자바스크립트가 동작하지 않는 환경에서는 화면이 표시되지 않기 때문에 검색엔진과 같은 로봇들이 컨텐츠를 이용할 수 없다는 치명적인 단점이 있습니다. 그 외에도 자바스크립트를 다운로드 하고 실행하기 전까지는 화면이 표시되지 않는 등의 문제가 있습니다</p>
<p>서버사이드 렌더링(SSR)은 서버쪽에서 자바스크립트가 실행됩니다. 브라우저로는 완성된 HTML을 전송하기 때문에 자바스크립트를 실행할 수 없는 환경에서도 잘 동작합니다. 다운로드 받는 즉시 실행되기 때문에 사용자 입장에서 눈 깜짝할 사이에 웹페이지가 표시되는 느낌을 받게 됩니다.</p>
<h3 id="정적-렌더링static-rendering">정적 렌더링(Static Rendering)</h3>
<p>우선 Tailwind CSS 를 설명하기 앞서 동적 렌더링과 정적 렌더링의 개념과 차이점을 짚고 넘어가야 이해가 더 수월할 거 같아 정리해봅니다.</p>
<p>정적 렌더링은 페이지를 빌드 시점에 미리 생성하여 HTML 파일로 저장하는 방식입니다. 사용자가 페이지를 요청하면 서버에 직접 요창하는 대신 사용자와 가장 가까운 CDN 에서 바로  캐시 된 응답을 받게 됩니다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/58f27419-c481-438f-8928-42825b20bb29/image.png" alt="">
사용자의 요청에 빠르게 응답할 수 있다는 장점과 빌드 타임에 페이지를 생성해야 하기 떄문에 어떤 데이터를 보여줘야 할지 미리 알 수 있는 경우 활용하기 좋습니다. 예시로는 블로그 포스트, 제품 상세 페이지 등 자주 변경되지 않는 컨텐츠에 정적 렌더링을 활용하기에 좋습니다.</p>
<h3 id="동적렌더링dynamic-rendering">동적렌더링(Dynamic Rendering)</h3>
<p>서비스를 개발하다 보면 빌드 타임에 페이지를 렌더링 하는데 필요한 모든 데이터를 미리 알 수 있는 경우는 많지 않습니다. 이커머스 서비스에서 사용자 맞춤화된 상품을 보여줘야 하는 경우 빌드 타임에는 사용자가 누구일지 알 수 없기 때문에 미리 페이지를 생성할 수 없습니다. 이 경우에는 <strong>런타임에 페이지를 생성하는 동적 렌더링 전략을 활용해야 합니다.</strong></p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/40042692-410f-4118-85a7-3b8cdd154ac7/image.png" alt=""></p>
<p>동적 렌더링은 각 사용자 요청 시 서버에서 페이지를 생성하여 제공하는 방식입니다. 사용자 맞춤형 데이터나 실시간 정보가 필요한 경우에 적합합니다.</p>
<h3 id="css-in-js의-한계">CSS-in-JS의 한계</h3>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/2d44c81a-153c-4426-8b47-22107f67efb7/image.png" alt="">
다시 본론으로 돌아와 왜 Next.js는 왜 Tailwind CSS를 기본적으로 추천할까요?</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/51e7e6f4-aad8-445a-897f-f6c73aeb67e1/image.png" alt=""></p>
<p>위 그림은 Next.js 공식문서 사진입니다. CSS-in-JS는 JavaScript 코드 내에 CSS를 작성하여 런타임에 스타일을 적용하는 방식입니다. 이 방식은 동적인 스타일링에 유리하지만  다음과 같은 단점이 있습니다.</p>
<ul>
<li>런타임 오버헤드 : CSS-inJS 는 스타일을 런타임에 생성하므로, JavaScript가 완전히 로드되고 실행된 후에야 스타일이 적용됩니다. 이는 초기 렌더링 속도를 저하시킬 수 있습니다.</li>
<li>서버 컴포넌트와의 비호환성 : Next.js의 App Router는 서버 컴포넌트를 기본으로 사용하며, 서버에서 렌더링되어 클라이언트에 전잘됩니다. 대부분의 CSS-in-JS 라이브러리는 클라이언트 사이드에서 스타일을 적용하므로, 서버 컴포넌트와의 통합에 어려움이 있습니다.</li>
</ul>
<p><strong>빌드 타임 :</strong> 배포되기 전에 개발자가 작성한 코드를 컴파일하거나 번들링하는 시점입니다. 이 단계에서 정적인 자원들이 생성됩니다.</p>
<p><strong>런타임</strong> : 사용자가 웹사이트를 방문하여 애플리케이션이 실행되는 시점입니다. 이때 동적인 동작이 이루어집니다.</p>
<h3 id="tailwind-css-in-nextjs">Tailwind CSS in Next.js</h3>
<p>Tailwind CSS는 유틸리티 퍼스트 CSS 프레임워크로, 클래스 이름을 통해 스타일을 적용합니다. </p>
<ul>
<li><strong>정적 스타일링</strong>: Tailwind CSS는 빌드 시점에 필요한 CSS만을 생성하므로, 런타임 오버헤드가 없습니다.</li>
<li><strong>서버 컴포넌트와의 완벽한 호환성</strong>: 스타일이 클래스 이름으로 적용되므로, 서버에서 렌더링된 HTML에 바로 적용할 수 있어 서버 컴포넌트와의 통합이 용이합니다.</li>
<li><strong>번들 최적화</strong>: 사용되지 않는 CSS는 빌드 시 제거되어 최종 번들 크기를 최소화합니다.</li>
<li><strong>빠른 개발 속도</strong>: 미리 정의된 유틸리티 클래스를 사용하여 빠르게 스타일을 적용할 수 있어 개발 속도가 향상됩니다.</li>
</ul>
<p>결국 이러한 이유로, 스타일을 빌드 타임에 미리 생성하고 정적 클래스 기반으로 빠르게 렌더링되는 Tailwind CSS를 추천하는 이유입니다.</p>
<h3 id="ref">Ref</h3>
<blockquote>
</blockquote>
<p><a href="https://www.philly.im/blog/next-js-server-rendering-strategies#%EC%A0%95%EC%A0%81-%EB%A0%8C%EB%8D%94%EB%A7%81static-rendering">Next.js 서버 렌더링 전략</a>
<a href="https://dev.to/codeparrot/nextjs-and-tailwind-css-2025-guide-setup-tips-and-best-practices-2f6h?utm_source=chatgpt.com">dev-codeparrot</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JWT란?(Access Token,Refresh Token)]]></title>
            <link>https://velog.io/@5_wintaek/JWT%EB%9E%80Access-TokenRefresh-Token</link>
            <guid>https://velog.io/@5_wintaek/JWT%EB%9E%80Access-TokenRefresh-Token</guid>
            <pubDate>Fri, 18 Apr 2025 12:29:52 GMT</pubDate>
            <description><![CDATA[<h3 id="📌-들어가기전">📌 들어가기전</h3>
<p>앞전 글에서 세션,토큰,OAuth에 대해 설명을 했는데 JWT도 정리 안하고 가면 JWT가 서운해 할 것 같아서 정리를 한번 해보려고 합니다 ! </p>
<h3 id="📬-토큰">📬 토큰</h3>
<p>JWT는 JSON Web Token의 약자로 인증에 필요한 정보들을 암호화시킨 토큰을 말합니다. JWT 기반 인증은 쿠키/세션과 유사하게 Access Token을 HTTP 헤더에 실어 서버가 클라를 식별합니다. </p>
<p>JWT를 생성하기 위해서는 Header, Payload, Verify Signature 객체를 필요로 합니다.(즉 Header,Payload,Signauture로 이루어져 있다고도 할 수 있죠)</p>
<p><strong>✅ 구조 (점 . 으로 나뉨)</strong>
<img src="https://velog.velcdn.com/images/5_wintaek/post/51843e6a-4561-424f-87e9-0c46ba793516/image.png" alt=""></p>
<p>이미지 순서대로 Header -&gt; Payload -&gt; Signature 로 나뉘어집니다 !</p>
<p>진~짜 간단하게 보면</p>
<blockquote>
<p>Header : 이 토큰이 어떤 형식인지, 어떻게 암호화됐는지 설명
 Payload : 실제 데이터가 들어감, 사용자 데이터 등이 담겨있음
 Signature : 앞의 두 부분이 정상적인지 검증하는 용도 </p>
</blockquote>
<h3 id="🧠-header">🧠 Header</h3>
<p>Header는 토큰의 타입을 나타내는 <code>typ</code>과 암호화할 방식을 정하는 <code>alg</code>로 구성되어 있습니다.</p>
<pre><code class="language-js">{
  &#39;alg&#39;: &#39;HS256&#39;,
  &#39;typ&#39;: &#39;JWT&#39;
}</code></pre>
<h3 id="📚-payload">📚 Payload</h3>
<p><code>payload</code>는 실제로 토큰에 담을 정보를 지니고 있습니다.
주로 클라이언트 고유ID, 유효 기간, 발급 일시, 발급자, 권한정보 등이 포함되어 있습니다.
여기서 하나의 정보 조각을 <code>Claim</code>으로 부릅니다. <code>key-Value</code>형식으로 이루어진 한 쌍의 정보이죠 </p>
<pre><code class="language-js">{
  &#39;sub&#39;: &#39;1234567890&#39;,
  &#39;name&#39;: &#39;John Doe&#39;,
  &#39;admin&#39;: true,
  &#39;iat&#39;: 1516239022
}</code></pre>
<h3 id="🔑-verify-signature">🔑 Verify Signature</h3>
<p>Signature는 인코딩된 Header와 Payload를 더한 뒤, 비밀키로 해싱하여 생성합니다</p>
<p>Header 및 Payload는 단순 인코딩된 값이기 때문에 해커가 복호화하고 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없습니다..</p>
<p>따라서 Signature는 토큰의 위변조 여부를 확인하는 데 사용됩니다.</p>
<pre><code class="language-js">HMACSHA256(
    base64UrlEncode(header) + &quot;.&quot; +
    base64UrlEncode(payload),
    secret_key
)</code></pre>
<h3 id="완성된-토큰">완성된 토큰</h3>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/33d32c4f-9763-499f-bb82-a46a7779de50/image.png" alt=""></p>
<p>Header, Payload는 인코딩될 뿐, 따로 암호화되지 않습니다. 따라서 Header, Payload는 누구나 디코딩하여 확인할 수 있기에 정보가 쉽게 노출될 수 있습니다. 하지만 Verify Signature는 SECRET KEY를 알지 못하면 복호화할 수 없습니다.</p>
<p>만약에 해커가 사용자의 토큰을 훔쳐 Payload의 데이터를 변경하여 토큰을 서버로 보낸다면, 서버에서 Verify Signature을 검사하게 됩니다. </p>
<p>여기서 Verify Signature는 해커의 정보가 아닌 사용자의 정보를 기반으로 암호화되었기 때문에 해커가 변경한 정보로 보낸 토큰은 유효하지 않은 토큰으로 간주합니다. 이를 통해 사용자의 SECRET KEY를 알지 못하면 토큰을 조작할 수 없다는 것을 알 수 있습니다.</p>
<h3 id="🔑-인증-순서">🔑 인증 순서</h3>
<ul>
<li>클라이언트 로그인 요청이 들어오면, 서버는 검증 후 클라이언트 고유 ID등의 정보를 Payload에 담는다.</li>
<li>암호화할 비밀키를 사용해 Access Token(JWT)을 발급한다.</li>
<li>클라이언트는 전달받은 토큰을 저장해두고, 서버에 요청할 때마다 토큰을 요청 헤더 Authorization에 포함시켜 함께 전달한다.</li>
<li>서버는 토큰의 Signature 을 비밀키로 복호화한 다음, 위변조 여부 및 유효 기간 등을 확인한다.</li>
<li>유효한 토큰이라면 요청에 응답한다.</li>
</ul>
<h3 id="✨-토큰의-장점">✨ 토큰의 장점</h3>
<ul>
<li><p>Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.</p>
</li>
<li><p>인증 정보에 대한 별도의 저장소가 필요 없다.(빠르고 가벼움)</p>
</li>
<li><p>JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됐음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.</p>
</li>
<li><p>토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다. 즉 확장성이 뛰어나다 (OAuth + JWT 조합)</p>
</li>
<li><p>인증 정보에 대한 별도의 저장소가 필요 없다. (I/O 처리 필요 없음) </p>
<blockquote>
<p>추가설명 )세션은 사용자가 로그인 하면 서버가 사용자 정보를 세션 저장소(메모리나 DB)에 저장 -&gt; 그걸 계속 들여다보며 확인 -&gt; 매 요청마다 저장소를 들여다보는 작업(I/O)필요함. 
I/O는 디스크나 메모리 등에서 데이터를 읽고 쓰는 작업을 뜻함</p>
</blockquote>
<blockquote>
<p>반면 JWT는 로그인하면 서버는 토큰만 생성해서 클라이언트에게 제공 -&gt; 그 안에 사용자 정보와 서명이 담겨있음 -&gt; 사용자가 다시 요청할 때 이 토큰만 따악 보여주면 끝 !</p>
</blockquote>
</li>
<li><p>클라의 인증 정보를 저장하는 세션과 달리(세션 방식은 로그인하면 서버가 누구인지 기억함), 서버는 무상태가 된다</p>
<blockquote>
<p>추가설명) 서버가 로그인 상태를 기억하지 않아도 된다는 뜻이다. 즉 첫 로그인떄부터 JWT 토큰만 서버가 클라이언트에게 넘겨준 후 다음부터는 클라이언트가 &quot;내가 누군지는 여기 써있어!&quot;하며 JWT를 보여주며 서버는 아무것도 기억 안해도 인증 가능)</p>
</blockquote>
</li>
</ul>
<h3 id="토큰의-단점">토큰의 단점</h3>
<ul>
<li><p>쿠키, 세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많을수록 네트워크 부하가 심해진다.</p>
</li>
<li><p>Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다. (패스워드 등)</p>
</li>
<li><p>토큰을 탈취당하면 대처하기 어렵다. 토큰은 한 번 발급되면 유효기간이 만료될 때까지 계속 사용이 가능하다.</p>
</li>
<li><p>특정 사용자의 접속을 강제로 만료하기 어렵다. (쿠키/세션 기반 인증은 서버 단에서 쉽게 삭제할 수 있지만 토큰은 그게 안 됨)</p>
</li>
</ul>
<blockquote>
<p>🤔 이러한 단점들 때문에 보완하기 위한 전략이 있습니다 ! </p>
</blockquote>
<h3 id="짧은-만료-기한-설정">짧은 만료 기한 설정</h3>
<p>토큰의 만료 기한을 짧게 설정해서 탈취되더라도 빠르게 만료시키는 방법입니다.
하지만 이는 토큰이 만료되면 사용자가 다시 로그인해야 한다는 뜻이기에 사용자 입장에서 번거로운 방법..</p>
<h3 id="sliding-session">Sliding Session</h3>
<p>서비스를 지속적으로 이용하는 클라이언트에게 자동으로 토큰 만료 기한을 늘려주는 방법입니다. </p>
<p>사용자가 활동 중이라면 = &quot;아직 살아있는 유저&quot;라고 판단</p>
<p>토큰 만료 시간을 연장해줌</p>
<p>그래서 로그아웃되거나 재로그인하는 불편함이 줄어듦</p>
<h3 id="refresh-token">Refresh Token</h3>
<p>클라이언트가 로그인할 때 Access Token 및 Refresh Token을 발급해주는 방법입니다.
Refresh Token은 Access Token보다 만료 기한이 긴 토큰입니다.
클라이언트가 요청을 보냈는데 Access Token이 만료되었을 때, Refresh Token을 이용하여 Access Token의 재발급을 요청합니다.</p>
<p>이때 서버는 DB에 저장된 Refresh Token과 비교하여 유효하면 Access Token을 발급을 합니다. 만약 Refresh Token도 만료된 경우라면 사용자에게 로그인을 요구합니다.</p>
<blockquote>
<p>이 전략의 장점은?</p>
</blockquote>
<ul>
<li>Access Token의 만료 기한을 짧게 설정하여 위의 짧은 만료 기한 설정 전략처럼 탈취되더라도 빠르게 만료될 수 있습니다.</li>
<li>또한 짧은 만료 기한에도 불구하고 자주 로그인을 할 필요가 없어진다. </li>
<li>서버가 강제로 Refresh Token을 만료시킬 수도 있다.</li>
</ul>
<blockquote>
<p>하지만 이렇게 완벽하게 보이는 Refresh Token 발급 방법도 단점은 있다. </p>
</blockquote>
<ul>
<li>Refresh Token 검증을 위해 DB(혹은 별도의 저장소)에 저장해야 함</li>
<li>자원이 소요될 뿐더러 추가적인 I/O 작업이 발생함. (JWT의 장점은 I/O 작업이 필요없는 빠른 인증 처리였다.)</li>
</ul>
<h3 id="ref">Ref</h3>
<blockquote>
<p><a href="https://velog.io/@whitebear/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0JWT-%ED%99%95%EC%8B%A4%ED%9E%88-%EC%95%8C%EA%B3%A0-%EA%B0%80%EA%B8%B0">쿠키, 세션, 토큰(JWT) 몰라도 괜찮겠어?</a>
<a href="https://velog.io/@gusdnr814/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95">로그인 인증 4가지 방법</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인 상태를 유지하는 방식에 대해 설명(쿠키,세션,OAuth)]]></title>
            <link>https://velog.io/@5_wintaek/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%83%81%ED%83%9C%EB%A5%BC-%EC%9C%A0%EC%A7%80%ED%95%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%8C%80%ED%95%B4-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@5_wintaek/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%83%81%ED%83%9C%EB%A5%BC-%EC%9C%A0%EC%A7%80%ED%95%98%EB%8A%94-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%8C%80%ED%95%B4-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Wed, 16 Apr 2025 12:38:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-웹-개발의-기본--사용자-인증과-상태-관리">🤔 웹 개발의 기본 : 사용자 인증과 상태 관리</h2>
<p>웹 개발에서 사용자 인증과 상태 관리는 중요한 부분입니다. 사용자가 로그인을 하면, 그 상태를 유지하여 사용자가 다시 방문했을 때 재로그인 없이 서비스를 이용할 수 있게 해야 합니다. </p>
<p>사용자 경험을 향상시키고, 서비스의 접근성을 높이기 위해서는 로그인 상태의 유지가 필수적이기 떄문입니다.</p>
<p>이를 위해 쿠키,세션,토큰 등 다양한 방법을 사용하여 사용자의 로그인 상태를 관리하고 유지합니다.</p>
<p>특성과 상황에 따라 장단점이 각각 있으며, 적절한 방법을 선택해서 사용해야 합니다 !</p>
<p>그러면 조금 더 자세하게 알아볼까요 ? </p>
<h2 id="🍪-쿠키와-세션을-이용한-방법">🍪 쿠키와 세션을 이용한 방법</h2>
<p>쿠키와 세션은 웹 개발에서 가장 전통적으로 사용되는 사용자 상태 관리 방법입니다. 쿠키와 세션을 이용하면 서버와 클라이언트 간의 상태 정보를 유지할 수 있기 있습니다.</p>
<p>사용자가 로그인을 하면, 서버는 세션ID를 생성하고 이를 쿠키에 저장하여 클라이언트에 전송합니다. 사용자가 다시 웹사이트를 방문하면 브라우저는 쿠키에 저장된 세션ID를 서버에 전송하여 사용자를 인식합니다.</p>
<p>이 방법은 간단하고 널리 사용되지만, 쿠키의 보안 취약점과 세션의 서버 자원 사용이 단점입니다.
보안이 중요한 애플리케이션에서는 추가적인 보안 조치와 함꼐 사용해야 합니다.</p>
<blockquote>
<p><code>🔸 쿠키 (Cookie)</code></p>
</blockquote>
<ul>
<li>클라이언트(브라우저)에 저장되는 작은 데이터 조각.</li>
<li>서버가 Set-Cookie 헤더로 응답하면, 브라우저는 쿠키를 저장하고 이후 요청마다 자동으로 전송함.</li>
</ul>
<blockquote>
<p><code>🔸 세션 (Session)</code></p>
</blockquote>
<ul>
<li>서버 측에서 사용자 상태(예: 로그인 정보)를 유지하는 저장소.</li>
<li>보통 세션 ID를 쿠키로 클라이언트에 저장하고, 그 ID로 서버의 세션 저장소에서 사용자 데이터를 조회함.</li>
</ul>
<h3 id="💻-세션과-쿠키를-이용한-인증-순서">💻 세션과 쿠키를 이용한 인증 순서</h3>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/55e996dd-c8f0-4961-8925-59b096295fb4/image.png" alt=""></p>
<ul>
<li><p>사용자가 로그인 합니다.</p>
</li>
<li><p>서버가 사용자 확인 -&gt; 사용자에게 고유한 ID값을 부여하여 세션 저장소에 저장한 후, 이와 연결되는 Sessin ID를 발급합니다.</p>
</li>
<li><p>서버는 HTTP 응답 헤더에 발급된 Sessin ID를 실어 보냅니다. 이후 매 요청마다 HTTP 요청 헤더에 Sessin ID가 담긴 쿠키를 실어서 보냅니다.</p>
</li>
<li><p>서버에서는 쿠키를 받아 세션 저장소에서 대조를 한 후 대응되는 정보를 가져옵니다.</p>
</li>
<li><p>인증이 완려되고 서버는 사용자에 맞는 데이터를 보내줍니다.</p>
</li>
</ul>
<h3 id="✨-장점">✨ 장점</h3>
<ul>
<li><p>사용자의 정보는 세션 저장소에 저장되고, 쿠키는 그 저장소를 통과할 수 있는 출입증 역할을 합니다. 따라서 쿠키가 담긴 HTTP 요청이 도중에 노출되더라도 쿠키 자체에는 유의미한 값을 갖고 있지 않아서 쿠키에 사용자 정보를 담아 인증을 거치는 것 보다 안전합니다.</p>
</li>
<li><p>각각의 사용자는 고유의 Session ID를 발급받기 때문에 회원정보를 일일이 확인할 필요가 없어 서버 자원에 접근하기 용이합니다.</p>
</li>
</ul>
<p>조금 더 쉽게 다가가보면 ?</p>
<blockquote>
<p> 쿠키는 열쇠가 아니라, 열쇠고리다 🔐</p>
</blockquote>
<ul>
<li>쿠키엔 사용자 정보가 직접 들어있지 않음!</li>
<li>그냥 &quot;세션 창고에 있는 내 물건 번호&quot;만 들고 있음.</li>
<li>그래서 누군가 쿠키를 훔쳐가도, 직접적인 정보는 없음.</li>
<li>👉 쿠키에 로그인 정보가 직접 담겨있는 방식보다 더 안전!</li>
</ul>
<blockquote>
<ol start="2">
<li>서버는 한 번만 확인하면 돼요 📁</li>
</ol>
</blockquote>
<ul>
<li>로그인할 때만 사용자의 정보를 확인하고</li>
<li>이후에는 &quot;Session ID&quot;만 보고 바로 찾아냄.</li>
<li>매번 로그인 검사 안 해도 돼서 서버도 편함!</li>
<li>👉 빠르고 효율적!</li>
</ul>
<h3 id="✨-단점">✨ 단점</h3>
<ul>
<li><p>쿠키에 사용자 정보를 담아 인증을 거치는 것 보다 안전하지만, 해커가 쿠키를 탈취한 후 그 쿠키를 이용해 HTTP 요청을 보내면 서버는 사용자로 오인해 정보를 전달하게 됩니다. 이를 세션 하이재킹 공격이라고 합니다. 해결책으로는 HTTPS 프로토콜 사용과 세션에 만료 시간을 넣어주는 것 입니다.</p>
</li>
<li><p>서버에서 세션 저장소를 사용하기 때문에 추가적인 저장공간을 필요로 합니다.</p>
</li>
</ul>
<p>조금 더 쉽게 다가가보면 ? </p>
<blockquote>
<ol>
<li>쿠키를 훔쳐가면? 😨</li>
</ol>
</blockquote>
<ul>
<li>쿠키 자체엔 정보가 없지만, 그 쿠키는 열쇠처럼 작동해요.</li>
<li>누가 내 쿠키를 복사해서 쓰면, 그 사람도 나인 척 가능!</li>
<li>👉 이를 세션 하이재킹이라고 해요.</li>
</ul>
<blockquote>
<p>🔒 해결 방법
HTTPS를 사용해서 쿠키 전송을 암호화하고
세션에 시간 제한을 걸어두면 위험을 줄일 수 있어요.</p>
</blockquote>
<blockquote>
<ol start="2">
<li>서버 저장소가 필요해요 💾</li>
</ol>
</blockquote>
<ul>
<li>사용자마다 세션 데이터를 서버에 따로 저장해야 해요.</li>
<li>사용자 많아지면, 서버 저장공간도 점점 필요해짐.</li>
<li>👉 트래픽이 많을수록 서버 관리가 더 복잡해져요.</li>
</ul>
<h2 id="oauth와-소셜-로그인">OAuth와 소셜 로그인</h2>
<p><strong>❓ OAuth란 ?</strong>
외부 서비스의 인증을 위한 개방형 표준 프로토콜입니다.쉽게 말해서 <span style= "color : yellow">“다른 서비스의 인증을 빌려 쓰는 방식”</span> 입니다.</p>
<p>소셜 로그인은 OAuth를 이용하여 사용자가 구글,네이버, 카카오톡 등의 소셜 미디어 계정으로 웹사이트에 로그인 할 수 있게 하는 기능입니다.</p>
<pre><code>✨ 진짜 쉬운 비유

🏢 당신은 어떤 건물에 입장하고 싶음 (웹사이트에 로그인)

🔐 보안요원이 입구에서 신분증 보여달라고 함

🪪 그런데 당신은 구글 공무원증을 꺼냄 (OAuth Provider)

👮 보안요원: “오, 구글에서 인증됐네? 통과하세요~”</code></pre><h3 id="✨-장점-1">✨ 장점</h3>
<p>OAuth를 통해 사용자는 별도의 로그인 없이도 여러 서비스를 이용할 수 있으며, 서비스 제공자(Provider)는 사용자의 인증 정보를 안전하게 관리할 수 있기 때문입니다.</p>
<p>사용자의 로그인 과정을 간소화하고, 서비스의 가입률을 높일 수 있습니다.</p>
<h3 id="✨-단점-1">✨ 단점</h3>
<ol>
<li>구현 복잡성 -&gt; 인증 토큰, 콜백, 리디렉션 등 처리해야 할 로직이 많음.</li>
<li>외부 서비스 의존성 -&gt; 구글/카카오가 다운되면 로그인도 안 됨.</li>
<li>제한된 사용자 정보 -&gt; 일부 제공자(GitHub 등)는 이메일 등 제한된 정보만 줌.</li>
<li>권한/보안 설정 복잡 -&gt; 토큰 유효 기간, 범위(scope) 등을 잘못 설정하면 보안 이슈 생김.</li>
<li>직접 회원 관리 어려움 -&gt; 비밀번호 분실/변경 같은 로직을 자체적으로 처리하기 어려움.</li>
</ol>
<h3 id="ref">Ref</h3>
<blockquote>
<p><a href="https://velog.io/@gusdnr814/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9D%B8%EC%A6%9D-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95">RHUK2 - 로그인 인증 4가지 방법</a>
<a href="https://f-lab.kr/insight/maintaining-login-state-in-web-development">F-Lab - 웹 개발에서의 로그인 상태 유지 방법</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[이터러블과 이터레이터]]></title>
            <link>https://velog.io/@5_wintaek/%EC%9D%B4%ED%84%B0%EB%9F%AC%EB%B8%94%EA%B3%BC-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@5_wintaek/%EC%9D%B4%ED%84%B0%EB%9F%AC%EB%B8%94%EA%B3%BC-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Tue, 01 Apr 2025 07:14:49 GMT</pubDate>
            <description><![CDATA[<h3 id="이터러블iterable">이터러블(iterable)</h3>
<p>이터러블이란 반복 가능한 객체를 의미합니다.즉 요소를 하나씩 차례대로 접근할 수 있는 객체를 말합니다.</p>
<ul>
<li>for...of 문으로 순회할 수 있습니다.</li>
<li><strong>전개 연산자(...)</strong>를 사용할 수 있습니다.</li>
<li>Symbol.iterator 메서드를 가지고 있어야 합니다. (이 메서드는 <strong>이터레이터(Iterator)</strong>를 반환합니다.)</li>
</ul>
<p>배열</p>
<pre><code class="language-jsx">const arr = [1, 2, 3];
for (const item of arr) {
  console.log(item); // 1, 2, 3
}</code></pre>
<p>문자열</p>
<pre><code class="language-jsx">const str = &quot;hello&quot;;
for (const char of str) {
  console.log(char); // h, e, l, l, o
}</code></pre>
<h3 id="이터레이터iterator">이터레이터(iterator)</h3>
<p>{value : 값 , done : true/false} 형태의 이터레이터 객체를 리턴하는 next() 메서드를 가진 객체.next 메서드로 순활할 수 있는 객체입니다.</p>
<pre><code class="language-jsx">const arr = [1,2,3]; //arr는 그냥 평범한 배열

const iter = arr[Symbol.iterator](); 
/*
문법 파헤치기 : key값을 문자열이 아닌 변수로 주기위해 arr[변수] 형태를 가진다.
위 사진에서 보듯이, Symbol.iterator 라는 key값을 가지고 value는 함수이다. 
이를 접근해서 함수실행() 시키면 이터레이터 객체가 반환되어 iter에 담기게 된다.
*/

iter.next()
//&gt;{value:1,done: false}
iter.next()
//&gt;{value:2, done: false},
iter.next()
//{value:3, done: false}
iter.next()
//{value: undefined, done: true}</code></pre>
<h3 id="유사배열-vs-이터러블">유사배열 vs 이터러블</h3>
<p>이터러블(iterable) : 위에서 설명한 바와 같이 메서드 Symbol.iterator가 구현된 객체.
유사 배열(array-like) : 인덱스와 length 프로퍼티가 있어서 배열처럼 보이는 객체.</p>
<p>우선 배열과 유사배열의 차이점을 알아보겠습니다.</p>
<pre><code class="language-jsx">var array = [1, 2, 3];
array; // [1, 2, 3]
var nodes = document.querySelectorAll(&#39;div&#39;); // NodeList [div, div, div, div, div, ...]
var els = document.body.children; // HTMLCollection [noscript, link, div, script, ...]</code></pre>
<p>위 예제에서는 els 와 nodes 가 유사배열이고 array가 배열입니다. 겉만 봐서는 잘 모르지만 Array.isArray메서드를 사용해서 뭐가 배열인지 확인해보겠습니다.</p>
<pre><code class="language-jsx">Array.isArray(array); // true
Array.isArray(nodes); // false
Array.isArray(els); // false</code></pre>
<p>직접 배열 리터럴로 선언한 array만 배열입니다. nodes나 els처럼 []로 감싸져 있지만 배열이 아닌 이들을 <strong>유사배열</strong>이라고 합니다.</p>
<pre><code class="language-jsx">let arrayLike = { // 인덱스와 length프로퍼티가 있음 =&gt; 유사 배열
  0: &quot;Hello&quot;,
  1: &quot;World&quot;,
  length: 2
};


for (let item of arrayLike) {} // Symbol.iterator가 없으므로 에러 발생</code></pre>
<p>이터러블과 유사 배열은 배열이 아니기 떄문에 push,pop 등의 메서드를 지원하지 않습니다. </p>
<p>배열과 유사배열을 구분해야 하는 이유는, 유사배열의 경우 배열의 메서드를 쓸 수 없기 때문입니다.</p>
<p>어떻게 하면 이터러블과 유사 배열에 메서드를 적용할 수 있을까요 ?</p>
<h3 id="arrayfrom-메서드">Array.from 메서드</h3>
<p>Array.from은 이터러블이나 유사 배열을 받아 &#39;진짜&#39;Array를 만들어 줍니다.</p>
<pre><code class="language-jsx">let arrayLike = { //유사배열
  0: &quot;Hello&quot;,
  1: &quot;World&quot;,
  length: 2
};
Array.from(arrayLike); // [&quot;Hello&quot;, &quot;World&quot;]


let arr = Array.from(arrayLike); // [&quot;Hello&quot;, &quot;World&quot;] 배열이 됨으로서 이터러블 객체도 된다.
for (let item of arr) {} // 1,2,3,4,5 (배열-문자열 형 변환이 제대로 동작합니다.)</code></pre>
<h3 id="map과-set-자료형도-이터러블">Map과 Set 자료형도 이터러블</h3>
<p>맵과 셋은 독리된 자료형이지 객체나 배열이 아닙니다.
그럼에도 불구하고 for..of문과 동작하는 이유는 인덱스로 접근하는게 아닌 이터러블 프로토콜을 따르고 있기 때문입니다.
자체적으로 내장 forEach()메서드를 지원하기도 합니다.</p>
<pre><code class="language-jsx">const set = new Set([1,2,3])
for (cosnt a of set) console.log(a) // 1,2,3</code></pre>
<pre><code class="language-jsx">const map = new Map([[&#39;a&#39;,1],[&#39;b&#39;,2],[&#39;c&#39;,3]]);
// Map(3) {&quot;a&quot; =&gt; 1, &quot;b&quot; =&gt; 2, &quot;c&quot; =&gt; 3}

const iter = map[Symbol.itertator](); // 심볼.이터레이터가 자체 내장되었기에 불러오기만 하면
iter.next();
// {value: Array(2), done: false}


for(const a of map)
    console.log(a); // [&#39;a&#39;,1],[&#39;b&#39;,2],[&#39;c&#39;,3]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Callback 함수와 Promise]]></title>
            <link>https://velog.io/@5_wintaek/Callback-%ED%95%A8%EC%88%98%EC%99%80-Promise</link>
            <guid>https://velog.io/@5_wintaek/Callback-%ED%95%A8%EC%88%98%EC%99%80-Promise</guid>
            <pubDate>Tue, 25 Mar 2025 14:31:38 GMT</pubDate>
            <description><![CDATA[<h3 id="📌-자바스크립트-콜백-함수란-">📌 자바스크립트 콜백 함수란 ?</h3>
<p>콜백(Callback)함수는 매개변수로 함수 객체를 전달해서 호출 함수 내에서 매개변수 함수를 실행하는 것을 말합니다. 콜백함수는 콜백함수를 전달받은 함수의 의해서 실행됩니다.
즉, 콜백 함수란 파라미터로 일반적인 변수나 값을 전달하는 것이 아닌 함수 자체를 전달하는 것을 말한다고 보면 됩니다. 아래 예시를 통해 보겠습니다.</p>
<h3 id="예시">예시</h3>
<pre><code class="language-jsx">  const main = (x: () =&gt; void) =&gt; {
    x(); // 매개변수의 함수호출
  };

  const sayHi = () =&gt; {
    console.log(&#39;안녕&#39;);
  };

  main(sayHi); // 안녕</code></pre>
<p>main 함수를 호출할 때 sayHi라는 콜백 함수를 인자로 전달해줬기 때문에 main 함수에 x 매개변수로 sayHi가 들어가게 된겁니다.</p>
<p>이제 main 함수 내부에 있는 코드가 실행이 되면서 우리가 전달해준 콜백함수가 x() 에서 호출이 되는 것입니다.</p>
<p>여기서 중요한점은 우리가 전달해준 sayHi 콜백함수는 main 함수 내부 안에서 구현되는 것입니다.</p>
<p>즉 ! 콜백함수가 언제 어떻게 호출될지는 main 함수 구현의 달려있는 것입니다. 아래의 코드처럼 말이죠.</p>
<pre><code class="language-jsx">const main = (x: () =&gt; void) =&gt; {
    console.log(&#39;구현중입니다...&#39;);
    x();
    console.log(&#39;준비작업 완료...&#39;);
  };

  const sayHi = () =&gt; {
    console.log(&#39;안녕&#39;);
  };

  main(sayHi);</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/1e780df3-f2d1-41be-97e3-1433b80a0770/image.png" alt=""></p>
<p>자바스크립트에서 콜백 함수를 사용하는 여러가지 메서드를 사용해왔지만 이 중 Promise를 통해 간략하게 설명하겠습니다.</p>
<h3 id="📌-비동기-작업">📌 비동기 작업</h3>
<p>설명하기 전 비동기 작업이라는 단어를 설명하고 넘어가겠습니다. 비동기 작업이란 특정 코드의 로직이 끝날 때까지 기다리지 않고 나머지 코드를 먼저 실행하는 것을 의미합니다. 서버에서 데이터를 불러올 때 오래 걸릴 수도 있는데, 그동안 다른 코드를 실행핮 않고 가만히 기다리면 웹 사이트를로딩하는게 굉장히 오래 걸리기 때문입니다.</p>
<p>비동기 작업을 순차적으로 실행하기 위해서 JavaScript에서는 콜백 함수를 사용합니다. 콜백 함수는 특성 로직이 끝났을 때 원하는 코드를 실행할 수 있습니다. 하지만 콜백에 또 콜백을 계속 호출하게 되면 코드가 복잡해지고 에러 처리도 용이하지 않습니다.</p>
<p>이런 단점을 Promise를 사용해서 해결할 수 있습니다.</p>
<h3 id="📌-promise">📌 Promise</h3>
<p>Promise는 비동기 함수가 반환하는 객체입니다. 함수의 성공 또는 실패 상태를 알려줍니다. 콜백을 직접 호출하는 방법 대신, Promise로 콜백을 부를 수 있습니다. 이런 특징 때문에 Promise를 사용하면 비동기 처리 시점, 비동기 함수의 결과를 쉽게 확인할 수 있고 에러 파악도 편리합니다.</p>
<h3 id="📌-promise를-생성하는-방법">📌 Promise를 생성하는 방법</h3>
<p>toss 블로그에서 가져온 예시를 들어보겠습니다.</p>
<p>아래 코드는 결제 코드를 보여주고 있습니다. 리턴 값으로 Promise를 생성하고 있구요!</p>
<p>결제가 요청되기 전에는 promise가 대기(Pending)상태입니다. 결제 요청에 성공하면 Promise 생성 함수에 있는 <code>resolve()</code>메서드가 호출됩니다. Promise가 성공 상태로 바뀌고 <code>data</code> 값을 가지게 되구요. 성공 상태를 가진 Promise는 fullfilled된 상태라고 말합니다. </p>
<p>하지만 결제 요청에 문제가 있다면 <code>reject()</code>를 호출해서 Promise를 실패 상태로 바꾸고 <code>error</code> 데이터를 가지게 됩니다. 실패 상태를 가진 Promise는 rejected 된 상태라고 말합니다.</p>
<pre><code class="language-jsx">function requestPayment(paymentData) {
    // ...
    return new Promise((resolve, reject) {    // Pending 상태
        if(isSuccess) {
            resolve(data)                     // 성공 상태
        }
        else {
            reject(error)                     // 실패 상태
        }
    })
}</code></pre>
<p>정리하면 Promise는 세 개의 상태를 가질 수 있습니다!</p>
<ul>
<li><code>대기(Pending)</code>: 비동기 함수가 아직 시작하지 않은 상태</li>
<li><code>성공(Fulfilled)</code>: 비동기 함수가 성공적으로 완료된 상태</li>
<li><code>실패(Rejected)</code>: 비동기 함수가 실패한 상태
<img src="https://velog.velcdn.com/images/5_wintaek/post/6d0ea966-3619-41b9-a154-64c7be285f6f/image.png" alt=""></li>
</ul>
<h3 id="📌-promise를-처리하는-방법">📌 Promise를 처리하는 방법</h3>
<p>Promise를 처리할 때는 <code>then()</code> 또는 <code>catch()</code> 메서드를 사용할 수 있습니다. 각 메서드 파라미터에는 콜백 함수를 넣어요! </p>
<p>결제 요청에 성공 상태의 Promise가 반환되면 <code>then()</code>메서드가 호출됩니다. 반대로 실패 상태의 Promise가 반환되면 <code>then()</code> 메서드를 건너뛰고 <code>catch()</code>메서드가 바로 호출됩니다.</p>
<p>아래 toss코드를 예시로 들어보겠습니다. <code>requsetPayment()</code>가 반환하는 Promise를 처리하기 위해 <code>then()</code>과 <code>catch()</code>를 연결해놓은 모습입니다.</p>
<ul>
<li>결제 요청이 성공 -&gt; <code>then()</code> 메서드가 실행. -&gt; 성공 상태의 Promise가 가진 data를 콘솔 출력 </li>
<li>결제 요청 실패 -&gt; <code>catch()</code> 메서드 실행. <code>then()</code> 메서드는 실행x -&gt; 실패 상태의 Promise가 갖고 있는 error 값 호출</li>
</ul>
<p>즉 then과 catch 시점에서 콜백 함수가 실행되는 모습을 볼 수 있습니다.</p>
<pre><code class="language-jsx">paymentWidget.requestPayment({
  orderId: &quot;t9JI0Bs1SVdJxRs8yjiQJ&quot;,
  orderName: &quot;토스 티셔츠 외 2건&quot;,
})
.then(function (data) {
  console.log(data); // 결제 완료 후 실행
})
.catch(function (error) {
  if (error.code == &quot;NEED_CARD_PAYMENT_DETAIL&quot;) {
    console.log(error.message); // 결제 실패시 실행
  }
})</code></pre>
<blockquote>
<h3 id="ref">Ref</h3>
<p><a href="https://docs.tosspayments.com/blog/using-promises#promise-%EC%8B%A4%EC%A0%84%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0">toss - Promise 실전에서 사용해보기</a>
<a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98">콜백 함수(Callback) 개념 &amp; 응용 - 완벽 정리</a>
<a href="https://www.youtube.com/watch?v=8FhRtDUhp1Q&amp;t=3s">별코딩-콜백함수</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[제어 컴포넌트와 비제어 컴포넌트]]></title>
            <link>https://velog.io/@5_wintaek/%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@5_wintaek/%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-%EB%B9%84%EC%A0%9C%EC%96%B4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Tue, 25 Mar 2025 12:00:13 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-들어가며">📌 들어가며</h2>
<p>리액트를 개발하다 프로젝트에서 그룹생성 기능을 맡고 기능 개발을 하였습니다. 서버에서 get 해온 데이터를 렌더링하고 유저가 input 데이터를 입력하고 Form 제출을 서버에 put 요청을 보내는 작업이 많았었는데요. 
저는 너무나 당연하게도 제어 컴포넌트(controlled)를 사용했습니다. 물론 input 태그가 엄청나게 많지는 않아서 이슈는 없었지만 리렌더링 관련된 생각이 나서 글을 정리하게 되었습니다.</p>
<h2 id="📌-form-tage-elements">📌 Form Tage Elements</h2>
<p>제어,비제어 컴포넌트를 설명하기 앞서 Form Tage ELements를 설명하겠습니다.</p>
<p>대표적인 input,textarea,select 태그들이 있습니다. 사용자가 입력한 값에 대해서 그 값을 가쟈올 수 있는데 value Attribute 를 통해 값에 접근할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/cb5b8d7c-5e09-4df9-9364-215951011f2e/image.png" alt=""></p>
<ul>
<li>Value Attribute 라고 하면 Value Attribute를 통해 사용자가 입력한 값을 가져올 수 있습니다. </li>
<li>즉 사용자가 입력한 값이 Value Attribute에 저장됩니다.</li>
<li>이 Value Attribute 는 DOM에 속해있습니다.</li>
<li>사용자가 입력한 값은 DOM 에 저장됩니다.</li>
</ul>
<h2 id="📌-신뢰-가능한-단일-출처single-source-of-truth">📌 신뢰 가능한 단일 출처(Single Source Of Truth)</h2>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/2f5cde7e-e38a-4d01-b3f0-c7f74e7c225f/image.png" alt=""></p>
<p>신뢰 가능한 단일 출처란 하나의 상태는 한곳에만 있어야 한다.라는 뜻입니다.
input 을 보면 DOM 에 있는 Value Attribute 를 실시간으로 보여줍니다.</p>
<ul>
<li>Value Attribute 는 최신화 된 값을 가지고 있습니다.</li>
<li>그렇기에 Value Attribute 는 신뢰가 가능합니다.</li>
<li>inputTage 의 Value Attribute 는 유일합니다</li>
<li>따라서 Value Attribute 는 신뢰 가능한 단일 출처라는것을 알 수 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/d2b830af-8c14-4b0b-9915-624f3bd3b81b/image.png" alt=""></p>
<p>코드를 보시면 지금 변수 Variable 그리고 Value Attribute 2가지를 볼 수 있습니다. 이 코드를 그림으로 표현해보면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/b56c89d9-287c-4e22-8339-cfc3b82cd92e/image.png" alt=""></p>
<p>현재 Input에서 2개의 Varibale 과 Value Attribute를 받고 있는것을 볼 수 있습니다. 
Value Attribute 가 계속 최신화가 되고 그 값을 Varibale 갖고 있습니다.</p>
<p>만약 A컴포넌트는 Value Attribute 가 갖고있고, B컴포넌트는 Varibale가 갖고 있다고 가정합니다.</p>
<p>만약 동료 개발자가 실수로 인해 변수 코드를 지운다면 Varibale로 선언된 변수는 갱신되지 못하게 됩니다.</p>
<p>상태는 하나였지만 두가지 출처로 인해 이러한 차이점이 발생할 수 있기 떄문에 신뢰 가능한 단일 출처를 받는것이 중요합니다.</p>
<h2 id="📌-react에서-form을-다루는-방법제어비제어">📌 React에서 Form을 다루는 방법(제어,비제어)</h2>
<p><strong>제어 컴포넌트</strong>
이에 React는 제어 컴포넌트를 통해서 문제를 해결할 수 있습니다. 대표적인 예시를 코드를 보여드리겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/fe138345-2845-4abb-8a1c-a5446cd1b3cd/image.png" alt=""></p>
<p>Value Attribute와 state를 결합하여 name State를 신뢰 가능한 단일 출처로 만든 사진입니다.
따라서 제어 컴포넌트는 Form의 사용자 입력값을 React가 제어한다고 보면 되겠습니다.</p>
<p><strong>비제어 컴포넌트</strong>
비제어 컴포넌트는 다음과 같은 코드블록이 대표적인 예시입니다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/c438b678-5587-4ba6-a5eb-942b59abed7c/image.png" alt=""></p>
<p>state와 달리 <code>useRef</code>를 사용해준 모습입니다. Ref 같은 경우엔 순수 자바스크립트 객체이기 때문에 <code>input</code> 태그에 직접 접근할 수 있고, 값이 변하더라도 리렌더링이 발생하지 않는 것을 볼 수 있습니다.</p>
<p>따라서 비제어 컴포넌트는 React가 Form의 입력값을 제어하지 않습니다. Value Attribute가 신뢰 가능한 단일 출처를 갖고 있다고 보면 되겠습니다.</p>
<h2 id="📌-제어-컴포넌트와-비제어-컴포넌트-비교">📌 제어 컴포넌트와 비제어 컴포넌트 비교</h2>
<h3 id="제어-컴포넌트-단점">제어 컴포넌트 단점</h3>
<p>컴포넌트 분리를 잘 해주면 리렌더링 이슈가 없는지도 모르겠습니다. 하지만 유저가 많은 데이터를 수정했을 때, 데이터들을 한 데 모아 서버에 한번에 보내기 위해서는 각 <code>value</code> 값을 <code>input</code> 태그들의 부모 컴포넌트에서 관리하게 됩니다. 이 경우 하나의 <code>input</code> 태그의 데이터를 변경하기만 해도 부모 컴포넌트가 갖고 있는 모든 태그가 리렌더링이 됩니다.</p>
<p><code>input</code> 태그가 몇 개 되지 않는 경우 리렌더링이 오래 걸리지 않기 때문에 문제가 되지는 않습니다. 하지만 부모 컴포넌트가 갖고 있는 자식 컴포넌트(input태그)가 수백개가 될 경우 리렌더링이 상당히 오래 걸리게 됩니다. 이 경우에는 UX가 당연히 좋게 보이지 않을 수 밖에 없습니다.</p>
<h3 id="비제어-컴포넌트를-고려해야-하는-상황">비제어 컴포넌트를 고려해야 하는 상황</h3>
<p>비제어 컴포넌트를 사용할 경우 위에서 설명했듯이 DOM에게 직접적으로 맡기게 됩니다. 유저가 입력한 값의 동기화는 이뤄지지 않는다는 이야기죠. 대신 리렌더링을 거치지 않고 유저의 입력 값을 화면에 빠르게 보여줄 수 있게 됩니다. </p>
<p>관리하고 있는 프로젝트에 유저 수가 많다고 가정하고!! 수정 버튼을 눌러 변경 사항을 서버에 보내야 할 경우에 <code>useRef</code>를 활용하여 <code>input</code>태그의 값들을 불러올 수 있습니다. </p>
<h2 id="📌-정리">📌 정리</h2>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/910e6cba-f6c7-40b2-a1eb-7f638f1096cb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/e9efe7ee-bb9b-45f3-8eb3-3a4d6f200c25/image.png" alt=""></p>
<p>리액트 공식문서에서는 모든 상황에서 제어 컴포넌트를 사용할 것을 권장합니다. 하지만 특별한 케이스에서는 비제어 컴포넌트를 사용할 수도 있다고 합니다. </p>
<p>제어 컴포넌트를 사용하면 <code>input</code> 값이 항상 동기화 되어있기 때문에 서버에 요청을 보낼 떄 따로 <code>useRef</code>를 사용해서 값을 불러올 팔요가 없습니다. 또한 이메일,비밀번호 형식 등을 <code>input</code> 값의 매 변경때마다 유효성 검사를 하고 싶다면 매번 동기화되는 제어 컴폰넌트를 사용하는게 훨씬 수월 할 수 있습니다.</p>
<p>개인적으로 만약 내 프로젝트에서 부모 컴포넌트에서 관리해야하는 <code>input,textarea,select</code>태그가 많고, 수많은 데이터를 한번에 수정해야 한다는 상황이 온다면 UX가 조금 더 수월한 비제어 컴포넌트를 사용하는 것이 더 좋다고 생각합니다. </p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://velog.io/@kskim625/React-controlled-vs.-uncontrolled-components">React controlled vs. uncontrolled components</a></p>
<p><a href="https://www.youtube.com/watch?v=PBgQKK6nelo">10분 테코톡</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LocalStorage 와 SesseionStorage ]]></title>
            <link>https://velog.io/@5_wintaek/LocalStorage-%EC%99%80-SesseionStorage</link>
            <guid>https://velog.io/@5_wintaek/LocalStorage-%EC%99%80-SesseionStorage</guid>
            <pubDate>Wed, 19 Mar 2025 16:58:35 GMT</pubDate>
            <description><![CDATA[<h2 id="localstorage-와-sessionstroage">localStorage 와 SessionStroage</h2>
<p>웹 스토리지 객체(web storage object)인 localStorage와 sessionStorage는 브라우저 내에 키-값 쌍을 저장할 수 있게 해줍니다. 둘 다 저장공간으로 사용할 수 있는데 둘의 가장 큰 차이점이라면 저장소로서의 기능은 대부분 동일하며 단지 sessionStorage의 경우 <strong>세션</strong>이 종료되면 저장된 데이터도 함께 사라진 다는 점이 다른 점입니다.</p>
<p>두 스토리지 객체는 동일한 메서드와 프로퍼티를 제공합니다.</p>
<hr>
<ul>
<li>setItem(key, value) – 키-값 쌍을 보관합니다.</li>
<li>getItem(key) – 키에 해당하는 값을 받아옵니다.</li>
<li>removeItem(key) – 키와 해당 값을 삭제합니다.</li>
<li>clear() – 모든 것을 삭제합니다.</li>
<li>key(index) – 인덱스(index)에 해당하는 키를 받아옵니다.</li>
<li>length – 저장된 항목의 개수를 얻습니다.</li>
</ul>
<hr>
<h3 id="localstorage">localStorage</h3>
<p>로컬스토리지를 한번 봐볼까요 ?</p>
<ul>
<li>오리진이 같은 경우 데이터는 모든 탭과 창에서 공유됩니다.</li>
<li>브라우저나 OS가 재시작하더라도 데이터가 파기되지 않습니다.</li>
<li><code>오리진</code>은 프로토콜(http,https), 호스트(example.com),포트(:80) 의 조합</li>
<li>유효기간 없이 데이터를 저장하고, JavaScript를 사용하거나 브라우저 캐시 또는 로컬 저장 데이터를 지워야만 사라집니다.</li>
<li>저장 공간이 셋 중 제일 큽니다.</li>
</ul>
<p>아래 예제를 실행해볼까요?</p>
<p><code>localStorage.setItem(&#39;test&#39;, 1);</code></p>
<p>그리구 브라우저를 닫고 열어보거나 아니면 다른창에서 열어봐도 됩니다. 그러면 밑에와 같이 결과가 나옵니다. 참고로 두개 이미지는 다른 탭에서 실행한 결과입니다!</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/49144953-30ae-4bb6-914a-ceea4d99ae9a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/71385481-e94d-4efb-b448-bb1168f7c429/image.png" alt=""></p>
<h3 id="sessionstorage">sessionStorage</h3>
<p>보통 세션이라는 의미는 일반적으로 브라우저의 &#39;종료&#39;라고 뜻하는데요
새창을 띄우건 새탭을 띄우건 이 행위는 세선종료를 뜻하지 않습니다.</p>
<ul>
<li>sessionStoarge는 탭 단위로 독립적입니다. </li>
<li>즉, 각 브라우저 탭은 자신만의 sessionStorage를 가지며, 서로 다른 탭 간에는 데이터가 공유되지 않습니다. </li>
<li>따라서 값도 침범을 할 수 없는것이죠.</li>
<li>데이터를 절대 서버로 전송하지 않습니다.</li>
<li>저장 공간이 쿠키보다 큽니다. (최대 5MB)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/03b1bf15-f620-4063-be06-87bb61126f6d/image.png" alt=""></p>
<p>하지만 예외도 있습니다. <strong>하나의 탭에 여러 개의 iframe이 있는 경우엔 동일한 오리진에서 왔다고 취급되기 때문에 sessionStorage가 공유됩니다.</strong></p>
<p>여기서 iframe 이란 예를들어, 유튜브 동영상을 웹 페이지에 넣거나, 다른 웹 사이트의 내용을 현재 페이지에 표시할 떄 사용된다고 생각하시면 편합니다!</p>
<p>위에서 설명드린것 처럼 세션은 새로고침을 하면 null 이 반환되는것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/f041983e-ea86-4e3d-a774-28565b50a1a2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/3140bff6-9792-46ce-8648-eafc0f106b7d/image.png" alt=""></p>
<h3 id="storage-이벤트">storage 이벤트</h3>
<p>localstorag 나 sessionStorage의 데이터가 갱신될 때, storage 이벤트가 실행됩니다. storage 이벤트는 다음과 같은 프로퍼티를 지원합니다.</p>
<p>여기서 storage이벤트란 저장 공간이 변경될 떄 Window 객체에서 발생합니다.</p>
<hr>
<ul>
<li><p>event.key: 변경된 데이터의 키(key)입니다.</p>
</li>
<li><p>event.newValue: 변경된 새로운 값입니다.</p>
</li>
<li><p>event.oldValue: 변경 전의 값입니다.</p>
</li>
<li><p>event.url: 데이터가 변경된 문서의 URL입니다.</p>
</li>
<li><p>event.storageArea : 갱신이 일어난 localStorage나 sessionStorage 객체</p>
</li>
</ul>
<hr>
<p>여기서 중요한 점은 storage 이벤트가 이벤트를 발생시킨 스토리지를 &#39;제외&#39;하고 스토리지에서 접근 가능한 window 객체 전부에서 일어난다는 사실입니다.</p>
<p>예시를 들어보겠습니다. 두 개의 창에 같읕 사이트를 띄워놨다고 가정합니다.이러면? 창은 다르지만 localstorage는 서로 공유됩니다.</p>
<p>두 개의 브라우저 창에 띄워봅시다. 두 창에서 모두 storage 이벤트를 수신하고 있기 때문에 한 창에서 아래 예시를 실행해 데이터를 갱신하면 다른 창에 해당 사항이 반영되는 것을 확인할 수 있습니다.</p>
<p>아래 이미지를 보면 두 개의 브라우저 창에서 storage 이벤트를 발생시키니 다른 창에서도 localstrage 값이 저장되는것을 확인할 수 있습니다. </p>
<pre><code class="language-js">// 문서는 다르지만, 갱신은 같은 스토리지에 반영됩니다.
window.onstorage = event =&gt; =&gt; {와 같습니다.
  if (event.key != &#39;now&#39;) return;
  alert(event.key + &#39;:&#39; + event.newValue + &quot; at &quot; + event.url);
};

localStorage.setItem(&#39;now&#39;, Date.now());</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/bb47bccd-f2a4-428f-9f1f-fd17f5bde8bf/image.png" alt=""></p>
<p>여기서 now의 value값을 한번 바꿔보면 아래와 같은 alert 창이 나옵니다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/87f5b0d4-e7c9-4b4e-a320-1ce113d39b1e/image.png" alt=""></p>
<p>storage이벤트의 또 다른 특징은 evert.url이 있어 데이터가 갱신된 문서의 URL을 알 수 있다는 점입니다. 
또한 event.storageArea에는 스토리지 객체가 포함되어 있는데, storage 이벤트는 session,local 이 변경될 때 모두 발생하기 때문에 evet.stroageArea는 스토리지 종류 상관없이 실제 수정이 일어난 것을 참조한다는 것 역시 중요한 특징입니다. 변경이 일어났을 때 우리는 event.storageArea에 무언가를 설정해 &#39;응답&#39;이 가능하도록 할 수 있습니다.</p>
<p><strong>이런 특징을 이용하면 오리진이 같은 창끼리 메시지를 교환하게 할 수 있습니다.</strong></p>
<p>모던 브라우저는 오리진이 같은 창끼리 통신할 수 있도록 해주는 브로드캐스트 채널 API(broadcast channel API)Visit Website를 지원합니다.</p>
<h3 id="쿠키-vs-로컬-스토리지-비교">쿠키 vs 로컬 스토리지 비교</h3>
<p>로컬 저장소를 웹에서 사용할 경우 가장 많이 사용되는 방법은 쿠키 그리고 로컬스토리지 입니다.</p>
<p>쿠키와 로컬스토리지 둘 다 다른 목적을 가지고 있기 때문에 무엇이 옳다고 하기는 애매합니다. 각각 쓰임새에 따라 방법을 선택해야 하죠.</p>
<p>쿠키는 서버측과 클라이언트측 양쪽에서 쿠키 데이터를 사용하는 API 가 존재합니다.
이와 달리 로컬스토리지는 로컬 환경에서만 컨트롤이 되죠.</p>
<p>이런 이유로 쿠키 데이터의 쓰임이 양쪽 모두이냐를 고려해야하며 만약 서버쪽 사용이 필수적이고 잦다면 클라이언트와 서버와의 인터렉션이 효과적인 쿠키값을 사용하는 것이 좋을 수 있습니다.</p>
<p>종종 웹사이트에서 보는 광고글 밑에 <strong>광고 7일동안 보지 않기</strong>기능 역시 쿠키 기능입니다.
로컬스토리지는 기간 기능이 없기 때문에 사용할 수 없습니다.</p>
<h3 id="웹스토리지의-우위성">웹스토리지의 우위성</h3>
<p>웹스토리지는 자바스크립트 객체 자체를 넣는것이 가능하며 문자열에 크기 제한이 있는 쿠키에 비해서 비교 우위를 가집니다.</p>
<p>그리고 쿠키를 이용시 모든 HTTP 요청과 응답이 쿠키에 포함되므로 원래 비용이 더 큰 HTTP 통신에 더 큰 부하를 줍니다.</p>
<p>그러나 웹스토리지는 그냥 개발자가 선별해서 넘기면 되므로 HTTP 통신에도 부하를 줄일 수 있죠. </p>
<p>아래와 같이 예시를 비교하자면.</p>
<p><strong>쿠키를 사용할 때</strong></p>
<ul>
<li><p>사용자가 로그인하면, 서버는 세션 ID를 쿠키로 브라우저에 전송합니다.</p>
</li>
<li><p>이후 모든 요청(이미지, CSS, JavaScript 파일 요청 포함)에 이 쿠키가 포함됩니다.</p>
</li>
<li><p>예를 들어, 10KB 크기의 쿠키가 모든 요청에 포함되면, 100번의 요청에서 1MB의 추가 데이터가 전송됩니다.</p>
</li>
</ul>
<p><strong>WebStorage를 사용할 때</strong></p>
<ul>
<li><p>사용자가 로그인하면, 서버는 세션 ID를 WebStorage에 저장합니다.</p>
</li>
<li><p>이후 필요한 경우(예: API 요청)에만 세션 ID를 서버로 전송합니다.</p>
</li>
<li><p>불필요한 요청(이미지, CSS, JavaScript 파일 요청)에는 세션 ID가 포함되지 않으므로, 네트워크 트래픽이 크게 줄어듭니다.</p>
</li>
</ul>
<p>또한 쿠키는 자신의 변화를 감지할 방법이 없지만 웹스토리지는 자신의 변화를 이벤트로 감지할 수 있습니다. </p>
<p>alue값을 객체로 이용하기
localStorage의 키와 값은 반드시 문자열이어야 한다.
숫자나 객체 등 다른 자료형을 사용하게 되면 문자열로 자동 변</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/f92e2a0f-64dc-4ab4-aa32-8607243f6168/image.png" alt=""></p>
<h3 id="출처">출처</h3>
<p><a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-localStorage-sessionStorage">Inpa Dev 👨‍💻:티스토리</a>
<a href="">코어자바스크립트 - localStorage와 sessionStorage</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] Rest 연산자와 Spread 연산자에 대해 설명]]></title>
            <link>https://velog.io/@5_wintaek/JS-Rest-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-Spread-%EC%97%B0%EC%82%B0%EC%9E%90%EC%97%90-%EB%8C%80%ED%95%B4-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@5_wintaek/JS-Rest-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-Spread-%EC%97%B0%EC%82%B0%EC%9E%90%EC%97%90-%EB%8C%80%ED%95%B4-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Mon, 17 Feb 2025 12:46:39 GMT</pubDate>
            <description><![CDATA[<h1 id="📚-spread">📚 spread</h1>
<p>spread 라는 단어가 가지고 있는 의미는 &#39;펼치다&#39;,&#39;퍼뜨리다&#39;입니다. 즉, 객체 혹은 배열을 펼칠수있습니다.
스프레드 문법을 사용할 수 있는 대상은 이터러블 객체이면 전개 구문을 사용할 수 있습니다.</p>
<p>예시로 다음과 같은 객체들이 있다고 가정해봅시다.</p>
<pre><code class="language-jsx">const slime = {
  name: &#39;슬라임&#39;
};

const cuteSlime = {
  name: &#39;슬라임&#39;,
  attribute: &#39;cute&#39;
};

const purpleCuteSlime = {
  name: &#39;슬라임&#39;,
  attribute: &#39;cute&#39;,
  color: &#39;purple&#39;
};

console.log(slime);
console.log(cuteSlime);
console.log(purpleCuteSlime);</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/c1508599-a3aa-4677-8776-ec6511ca4a25/image.png" alt=""></p>
<p>위 코드에서의 핵심은 기존의 것을 건들이지 않고, 새로운 객체를 만든다는 것입니다.  이러한 상황에 사용할 수 있는 유용한 문법이 바로 spread 입니다. 밑에 코드에서 … 문자가 바로 spread 연산자 입니다.</p>
<pre><code class="language-jsx">const slime = {
  name: &#39;슬라임&#39;
};

const cuteSlime = {
  ...slime,
  attribute: &#39;cute&#39;
};

const purpleCuteSlime = {
  ...cuteSlime,
  color: &#39;purple&#39;
};

console.log(slime);
console.log(cuteSlime);
console.log(purpleCuteSlime);</code></pre>
<p>다음은 배열입니다. </p>
<pre><code class="language-jsx">const animals = [&#39;개&#39;, &#39;고양이&#39;, &#39;참새&#39;];
const anotherAnimals = [...animals, &#39;비둘기&#39;];
console.log(animals);
console.log(anotherAnimals);</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/693ab9b3-7631-475b-918d-d4f5aeaffdb1/image.png" alt=""></p>
<p>기전 animals를 건들이지 않으면서 새로운 anotherAnimals 배열에 animals 가 가지고 있는 내용을 모두 집어넣고, ‘비둘기’ 라는 항목을 추가적으로 넣었습니다.</p>
<p>아래와 같이 이터러블 객체 여러 개를 전달하는 것도 가능합니다.</p>
<pre><code class="language-jsx">let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // 8</code></pre>
<p>아래와 같이 스프레드 문법을 평범한 값과 혼합해서 사용도 가능합니다.</p>
<pre><code class="language-jsx">let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25</code></pre>
<p>아래와 같이 전개 구문은 배열을 합칠 때도 활용 가능합니다.</p>
<pre><code class="language-jsx">let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

let merged = [0, ...arr, 2, ...arr2];

alert(merged); // 0,3,5,1,2,8,9,15 (0, arr, 2, arr2 순서로 합쳐집니다.)</code></pre>
<p>문자열을 문자 배열로 변환 시켜 보겠습니다 !</p>
<pre><code class="language-jsx">let str = &quot;Hello&quot;;

alert( [...str] ); // [&quot;H&quot;, &quot;e&quot;, &quot;l&quot;, &quot;l&quot;, &quot;o&quot;]
</code></pre>
<p>전개 구문은 <code>for..of</code> 와 같은 문자열을 구성하는 문자가 반환됩니다.</p>
<p><code>Array.from</code>은 이터러블 객체인 문자열을 배열로 바꿔주기 때문에 <code>Array.from</code>을 사용해도 동일한 작업을 할 수 있습니다.</p>
<pre><code class="language-jsx">let str = &quot;Hello&quot;;

// Array.from은 이터러블을 배열로 바꿔줍니다.
alert( Array.from(str) ); </code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/2f7d9bba-5bf5-40bd-a6b1-d77a35b70d4f/image.png" alt=""></p>
<h1 id="📚-rest-연산자">📚 rest 연산자</h1>
<p>Rest 연산자는 나머지 값을 합치는 느낌으로 사용됩니다.</p>
<h2 id="배열에서의-rest">배열에서의 rest</h2>
<pre><code class="language-jsx">const numbers = [0, 1, 2, 3, 4, 5, 6];

const [one, ...rest] = numbers;

console.log(one);
console.log(rest); // [1, 2, 3, 4, 5, 6]</code></pre>
<p>밑에 코드를 봐볼까요 ?</p>
<pre><code class="language-jsx">function myFunc(a, b, ...rest) {
  console.log(a, b);
  console.log(rest);
}

myFunc(1, 2, 3, 4, 5); // 1 2 / [3, 4, 5]</code></pre>
<p>함수 myFunc의 인자 목록에서 …rest 는 첫번째와 두번째 인자를 제외한 나머지 인자들을 배열 형태로 rest 변수에 저장합니다. </p>
<h2 id="객체에서의-rest">객체에서의 rest</h2>
<pre><code class="language-jsx">const user = { name: &quot;John&quot;, age: 30, city: &quot;New York&quot;, job: &quot;developer&quot; };
const { name, age, ...details } = user;

console.log(name); // John
console.log(age); // 30
console.log(details); // { city: &quot;New York&quot;, job: &quot;developer&quot; }</code></pre>
<p>객체 리터럴에서 <code>...rest</code>는 특정 속성들을 제외한 나머지 속성들을 새로운 객체 <code>details</code>에 담습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이터럴,이터레이블 ]]></title>
            <link>https://velog.io/@5_wintaek/%EC%9D%B4%ED%84%B0%EB%9F%B4%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%EB%B8%94</link>
            <guid>https://velog.io/@5_wintaek/%EC%9D%B4%ED%84%B0%EB%9F%B4%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%EB%B8%94</guid>
            <pubDate>Thu, 16 Jan 2025 15:08:50 GMT</pubDate>
            <description><![CDATA[<p>코테를 풀다가 궁금한게 있어 좀 적어본다.</p>
<h3 id="🤔-arr-는-리스트인데-가장-작은-값을-반환하라-할-때-왜-숫자로-나오는걸까-">🤔 arr 는 리스트인데 가장 작은 값을 반환하라 할 때 왜 숫자로 나오는걸까 ?</h3>
<pre><code class="language-python">arr = list(map(int,input().split())) # 8,-5,10
# 여기서 arr 를 출력해보면 -&gt; [8,-5,10]

print(min(arr)) # -5
</code></pre>
<p>A : list 안에서 가장 작은 값을 반환하기 때문이다. </p>
<h3 id="🤔-map은-이터러블반복-가능한-객체의-모든-항목에-함수를-적용한-결과를-반환한다-abc-할당-부분에서-map-객체에서-값을-하나씩-거내서-각각-abc-변수에-할당하니까-값이다-리터럴값인건가-">🤔 map은 이터러블(반복 가능한 객체)의 모든 항목에 함수를 적용한 결과를 반환한다. a,b,c 할당 부분에서 map 객체에서 값을 하나씩 거내서 각각 a,b,c 변수에 할당하니까 ‘값’이다. 리터럴(값)인건가 ?</h3>
<pre><code class="language-python">a,b,c = map(int,input().split()) # 8,-5,10

print(a,b,c)
</code></pre>
<p>A :</p>
<p>우선 이터러블(iterable)은 자료를 반복할 수 있는 <code>객체</code> 를 말하는 것이다.
우리가 흔히 쓰는 배열 역시 이러러블 객체이다.
이때 반환되는 객체는 map 객체이며, 이터레이터이다.</p>
<p>반면에 리터럴은 소스 코드에 직접 나타내는 값 그 자체를 의미한다. 예를들어, 10, 3.14, &quot;hello&quot;, [1, 2, 3], (4, 5), { &#39;a&#39;: 1, &#39;b&#39;: 2 } 등은 모두 리터럴이다. 이들은 코드에 직접 값을 나타낸다.</p>
<p>밑에코드를 다시 살펴보자</p>
<pre><code class="language-python">a,b,c = map(int,input().split()) # 8,-5,10

print(a,b,c)
</code></pre>
<ol>
<li><code>input().split()</code>: 입력받은 문자열을 공백으로 분리하여 문자열 리스트를 생성한다. 예를 들어 &quot;8 -5 10&quot;을 입력하면 <code>[&#39;8&#39;, &#39;-5&#39;, &#39;10&#39;]</code>이 생성된다. 이때 <code>&#39;8&#39;</code>, <code>&#39;-5&#39;</code>, <code>&#39;10&#39;</code>은 문자열 <em>리터럴이다.</em></li>
<li><code>map(int, ...)</code>: <code>map</code> 함수는 문자열 리스트의 각 요소에 <code>int</code> 함수를 적용한다. <code>int</code> 함수는 문자열을 정수로 변환한다. <code>map</code>의 결과는 map 객체 (이터레이터)이다.</li>
<li><code>a, b, c = ...</code>: 이 할당 부분에서 map 객체는 값을 생성하여 <code>a</code>, <code>b</code>, <code>c</code>에 각각 할당한다.</li>
</ol>
<p>이때 a,b,c에 할당되는 값은 8,-5,10 이다. 이 값들은 정수 값이다. 하지만 int() 함수를 통해 문자열에서 변환된 값이디 때문에 리터럴이라고 할 수 없다.</p>
<h3 id="map-객체는-이터레이터이기-때문에-한-번-순회하면-더-이상-값을-생성하지-않는다-">map 객체는 이터레이터이기 때문에 한 번 순회하면 더 이상 값을 생성하지 않는다 !</h3>
<p><strong>예시 코드를 보여주자면</strong></p>
<pre><code class="language-python">numbers = [&#39;1&#39;, &#39;2&#39;, &#39;3&#39;, &#39;4&#39;, &#39;5&#39;]
mapped_numbers = map(int, numbers)

# 첫 번째 순회
print(&quot;첫 번째 순회:&quot;)
for num in mapped_numbers:
    print(num)

# 두 번째 순회
print(&quot;\n두 번째 순회:&quot;)
for num in mapped_numbers:
    print(num)

# list()로 변환 후 순회
mapped_numbers = map(int, numbers) # map 객체를 다시 생성해야 함
list_numbers = list(mapped_numbers)

print(&quot;\nlist()로 변환 후 첫 번째 순회:&quot;)
for num in list_numbers:
    print(num)

print(&quot;\nlist()로 변환 후 두 번째 순회:&quot;)
for num in list_numbers:
    print(num)</code></pre>
<p><strong>실행 결과</strong></p>
<pre><code class="language-python">첫 번째 순회:
1
2
3
4
5

두 번째 순회:

list()로 변환 후 첫 번째 순회:
1
2
3
4
5

list()로 변환 후 두 번째 순회:
1
2
3
4
5</code></pre>
<h3 id="❗️결론">❗️결론</h3>
<p>이터레이터는 한 번 순회하면 모든 값을 소진한다. 따라서 다시 사용하려면 이터레이터를 다시생성해야 한다. <code>list()</code>, <code>tuple()</code>, <code>set()</code> 등을 사용하여 이터레이터의 모든 값을 컨테이너 객체(리스트, 튜플, 집합)로 변환하면 여러 번 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] Thread와 Process 차이]]></title>
            <link>https://velog.io/@5_wintaek/CS-Thread%EC%99%80-Process-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@5_wintaek/CS-Thread%EC%99%80-Process-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Thu, 16 Jan 2025 12:22:42 GMT</pubDate>
            <description><![CDATA[<h2 id="쓰레드와-프로세스의-차이점">쓰레드와 프로세스의 차이점</h2>
<p>스레드와 프로세스 차이점을 알기 위해 프로그램을 먼저 알아야 한다.</p>
<h3 id="-📚-프로그램이란-">** 📚 프로그램이란 ?**</h3>
<p>파일이 저장 장치에 저장되어 있지만 “메모리에는 올라가 있지 않은 정적”인 상태를 뜻한다. </p>
<blockquote>
<p>메모리에 올라가지있지 않은 아직 운영체제가 프로그램에게 독립적인 메모리 공간을 할당해주지 않았다는 뜻. 모든 프로그램은 운영체제가 프로글매에게 실행되기 위한 메모리 공간을 할당해 줘야 실행될 수 있다.</p>
</blockquote>
<blockquote>
<p>정적인 상태 : 정적이라는 단어 그대로, 움직이지 않는 상태라는 뜻. 한 마디로 아직 실행되지 않고 가만히 있다는 뜻이다.</p>
</blockquote>
<p>쉽게 말해 프로그램이라는 단어는 아직 실행되지 않은 파일 그 자체를 가리키는 말. 윈도우 exe 파일이나 MacOS의 dmg 파일 등등 사용자가 눌러서 실행하기 전 파일을 말한다. 쉽게 말해 코드 덩어리다.</p>
<h3 id="📚-프로세스"><strong>📚 프로세스</strong></h3>
<p>프로그램을 실행하는 순간 해당 파일은 컴퓨터 메모리에 올라가게 되고, 이 상태를 <code>동적</code> 인 상태라고 하며, 이 상태의 프로그램을 <code>프로세스</code> 라고 한다.</p>
<h3 id="📚-쓰레드란-"><strong>📚 쓰레드란 ?</strong></h3>
<p>운영체제는 안정성을 위해서 프로세스마다 자신에게 할당된 메모리 내의 정보에만 접근할 수 있도록 제약을 두고 있고, 이를 벗어나는 정보에 접근하려면 오류가 발생한다. 프로세스와는 다른 더 작은 실행 단위 개념이 스레드.</p>
<p>스레드는 프로세스와 다르게 스레드 간 메모리를 공유하며 작동한다. 스레드끼리 프로세스의 자원을 공유하면서 프로세스 실행 흐름의 일부가 되는 것이다. 스레드도 코드에 비유하자면 스레드는 코드 내에 선언된 함수들이 되고 main 함수 또한 일종의 스레드라고 볼 수 있게 되는 것.</p>
<blockquote>
<p>한줄 요약 : 프로세스와는 다른 더 작은 실행 단위 개념. 
스레드는 프로세스의 코드에 정의된 절차에 따라 실행되는 특정한 수행 경로이다.</p>
</blockquote>
<h3 id="프로세스와-스레드의-작동-방식에-대한-자세한-설명">프로세스와 스레드의 작동 방식에 대한 자세한 설명</h3>
<p>프로세스가 메모리에 할당될 때 운영체제로부터 시스템 자원을 할당받는다. 이 떄 운 영체제는 프로세스마다 각각 독립된 메모리 영역을 Code/Data/Stack/Heap 의 형식으로 할당해 준다. 각각 독립된 메모리 영역을 할당해 주기 때문에 프로세스는 다른 프로세스의 변수나 자료에 접근할 수 없다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/75b548f7-dde4-495d-80ee-aee8d4e2143a/image.png" alt=""></p>
<p>이와 다르게 스레드는 메모리를 서로 공유할 수 있다. 프로세스가 할당받은 메모리 영역 내에서 <code>Stack 형식으로 할당된 메모리 영역은 따로 할당받고,</code> 나머지 Code/Data/Heap 형식으로 할당된 메모리 영역을 공유한다. 따라서 <code>각각의 스레드는 별도의 스택을 가지고 있지만 힙 메모리는 서로 읽고 쓸 수 있게 된다.</code></p>
<p><strong>Heap</strong></p>
<p> 힙은 스레드 간에 공유된다. 동적 메모리 할당을 통해 생성되며, 프로세스 전체에서 공유된다. 스레드는 힙 메모리에서 데이터를 할당/해제하며 서로 데이터를 읽고 쓸 수 있다.</p>
<p><strong>Stack</strong></p>
<p>스택은 스레드마다 별도로 할당되므로 다른 스레드가 다른 스레드의 스택에 접근할 수 없다.</p>
<p>→ 스택은 스레드의 독립성을 유지하기 위한 것이며, 다른 스레드가 접근할 수 있다면 실행 중인 함수 호출이 꼬이거나 데이터 무결성이 깨질 수 있기 때문이다.</p>
<p><strong>Code</strong></p>
<p>모든 스레드는 같은 프로그램 코드를 실행하므로, 코드 영역은 스레드 간에 공유가 된다.
하지만 <code>읽기 전용</code> 이므로 스레드가 이 영역을 수정할 수 없다.</p>
<p>→ 이유는 코드 수정이 가능하다면 프로그램의 동작이 의도치 않게 변경될 위험 때문이다.</p>
<p><strong>Data</strong></p>
<p><strong>데이터 영역은 경합이 발생할 수 있다</strong>: 데이터 영역의 공유는 가능하지만, 동기화 없이 사용할 경우 경합 문제가 발생한다.</p>
<blockquote>
<p><strong>코드(Code)   |</strong>    가능 (읽기 전용)  | 모든 스레드가 같은 명령어를 실행. 수정은 불가능.
<strong>데이터(Data) |</strong>    가능 (주의 필요)     | 전역 변수와 정적 변수를 공유. 동기화 문제 주의.
<strong>힙(Heap)       |</strong>     가능  (주의 필요) | 동적 메모리를 공유. 동기화 메커니즘 필요.
<strong>스택(Stack)   |</strong> 불가능 (독립적)  | 각 스레드는 고유의 스택을 가짐. 다른 스레드는 접근 불가.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/6d02cda0-dba9-4e85-995c-f5847d518643/image.png" alt=""></p>
<p>프로세스와 스레드의 중요한 차이를 하나 더 알 수 있게 된다. 만약 한 프로세스를 실행하다가 오류가 발생해서 프로세스사 강제로 종료된다면, 다른프로세스에게 어떤 영향이 있을까 ? 공유하고 있는 파일을 손상시키는 경우나 아니라면 아무런 영향도 주지 않는다는걸 알 수 있다. </p>
<p>하지만 스레드의 경우는 다르다. 스레드는 Code/Data/Heap 메모리 영역의 내용을 공유하기 떄문에 어떤 스레드 하나에서 오류가 발생한다면 같은 프로세스 내의 다른 스레드 모두가 강제로 종료된다. </p>
<p>스레드를 코드내에서 함수에 빗대어 표현하면 이해하기 쉬워진다. 코드 내 어떤 함수 하나에서 오류가 발생한 경험이 있을 것이다. 이 오류가 어떤 함수에서 발생했든 간에 해당 코드는 다른 함수 모두에 대한 작업을 중단하고 프로세스 실행을 끝내버린다. </p>
<h2 id="결론">결론</h2>
<p>프로세스와 스레드는 개념의 범위부터가 다르다. 스레드는 프로세스 안에 내포되어 있기 떄문이다.</p>
<p>운영체제가 프로세스에게 Code/Data/Heap 메모리 영역을 할당해 주고 최소 작업 단위로 삼는 반면, 스레드는 프로세스 내에서 Stack 메모리 영역을 제외한 다른 메모리 영역을 같은 프로세스 내 다른 스레드와 공유한다. 이 같이 차이점을 알아보았다.</p>
<h2 id="references">References</h2>
<blockquote>
<p><a href="https://velog.io/@raejoonee/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EC%B0%A8%EC%9D%B4">프로세스와 스레드의 차이점</a>
<a href="https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html">[OS]프로세스와 스레드의 차이</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿼리 파라미터(Query Parameter) 에 대하여 (feat:RequestBody)]]></title>
            <link>https://velog.io/@5_wintaek/%EC%BF%BC%EB%A6%AC-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0Query-Parameter-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-featRequestBody</link>
            <guid>https://velog.io/@5_wintaek/%EC%BF%BC%EB%A6%AC-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0Query-Parameter-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-featRequestBody</guid>
            <pubDate>Mon, 25 Nov 2024 12:16:43 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-들어가며">🤔 들어가며</h3>
<p>프로젝트를 하면서 스웨거 명세서를 보며 API 작업을 여태껏 진행했어요. 하지만 초기에 대충 알고만 넘어가고 제대로 정리하며 넘어간적이 없어서 다시 한번 제대로 알아보기 위해 적어봐요.</p>
<h3 id="📚-쿼리-파라미터query-parameter">📚 쿼리 파라미터(Query Parameter)</h3>
<p>쿼리는 &#39;문의&#39; 또는 &#39;물음표&#39;라는 뜻을 가지고 있어요. 쿼리 파라미터는 URL 뒤에 물음표와 함께 붙는 키-값(key-Value)쌍입니다. 여러 개의 쿼리 파라미터를 전달하려면 파라미터 사이에 앰퍼샌드(&amp;)를 추가해서 하나의 문자열로 전달해요. 쿼리 파라미터로는 문자열뿐만 아니라 숫자, 리스트 등 다양한 데이터를 넣을 수 있어요.</p>
<pre><code class="language-jsx">const { data } = await api.get(
    `/rooms/search?keyword=${keyword}&amp;page=${page}`
  );</code></pre>
<p>여기서 첫번째 파라미터는 keyword 이 키, <code>${keyword}</code> 값이에요. 위에 명세서 이미지를 보면 추가적으로 Page 라는 파라미터를 요구하죠? 때문에 앰퍼샌드(&amp;)를 추가해서 값을 전달해주는 모습을 볼 수 있어요.</p>
<p>쿼리 파라미터는 클라이언트에서 서버로 간단히 데이터를 전달하고 싶을 때 유용해요. 
밑에 사진을을 예로 들어볼게요. 내가 원하는 그룹(방)을 찾고 싶을 때 사용하는 API 명세서에요.
이처럼 클라이언트에서 서버로 간단히 내가 원하는 데이터를 요청할때 사용한답니다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/868169c3-afb0-4bc7-b1e5-51568594115c/image.png" alt="">
<img src="https://velog.velcdn.com/images/5_wintaek/post/7ed30651-e9d0-4d2d-97e0-ad4fcf3762c1/image.gif" alt=""></p>
<h3 id="🤔-쿼리-파라미터는-어디에-사용할까">🤔 쿼리 파라미터는 어디에 사용할까</h3>
<ul>
<li>API 요청 : API엔드포인트에 쿼리 파라미터를 붙이면 주로 결과를 조회화거나 필터링하는 데 사용돼요.</li>
<li>검색 : 많은 검색 엔진이 쿼리 파라미터를 사용합니다. 예를 들어, 구글 검색 결과 페이지의 URL을 자세히 보면 쿼리 파라미터가 여러 개 붙어있는데, 쿼리를 서버에 전달하고 결과를 필터링하는 역할을 해요.</li>
</ul>
<h3 id="🔥-느낀점">🔥 느낀점</h3>
<p>사실 파라미터에 대한 사용 방법을 항상 대충으로만 알고 넘어가다가, 글을 작성하면서 사용법에 대한 정보들을 한번 더 자세하게 알아본게 좋다고 생각하구, 역시 기술 하나를 사용하더라도 제대로 된 정보들을 찾아보고 사용하는게 맞다는 생각이 다시 한번 생각이 든다 ! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[navigate 호출 시 url만 바뀌고 화면은 그대로인 현상]]></title>
            <link>https://velog.io/@5_wintaek/navigate-%ED%98%B8%EC%B6%9C-%EC%8B%9C-url%EB%A7%8C-%EB%B0%94%EB%80%8C%EA%B3%A0-%ED%99%94%EB%A9%B4%EC%9D%80-%EA%B7%B8%EB%8C%80%EB%A1%9C%EC%9D%B8-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@5_wintaek/navigate-%ED%98%B8%EC%B6%9C-%EC%8B%9C-url%EB%A7%8C-%EB%B0%94%EB%80%8C%EA%B3%A0-%ED%99%94%EB%A9%B4%EC%9D%80-%EA%B7%B8%EB%8C%80%EB%A1%9C%EC%9D%B8-%ED%98%84%EC%83%81</guid>
            <pubDate>Wed, 06 Nov 2024 12:59:17 GMT</pubDate>
            <description><![CDATA[<h3 id="🤔-문제상황">🤔 문제상황</h3>
<p>기존에 정의된 코드에서는 정상적인 url로 접근하기 전 잘못된 주소를 거치기 때문에, 새로운 알림을 클릭했을 때 화면 깜빡임과 뒤로가기를 실행했을 때 잘못된 주소로 접근했다는 에러가 발생했어요.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/0d03e065-30d8-4c79-ab0a-f98675f73af6/image.gif" alt=""></p>
<p>• 화면 깜빡임 발생 이유: 잘못된 주소로 접근 -&gt; 빈 화면 띄워줌 -&gt; 정상적인 주소로 라우팅 -&gt; 실제 데이터 띄워줌 -&gt; 이 과정에서 깜빡임이 있는 것<code>처럼</code> 느껴짐 (사실은 잘못된 주소를 거쳐가는 과정)</p>
<p>• 뒤로 가기 실행 시 잘못된 주소로 접근했다는 에러 발생 이유: 정상적인 주소 -&gt; 뒤로가기 -&gt; 잘못된 주소로 접근하기 때문 (위에서 이야기한 것러럼, 정상적인 주소로 접근하기 위해 반드시 잘못된 주소를 거쳐가기 때문에 뒤로가기를 실행했을 때는 이전 루트인 잘못된 주소로 갈 수 밖에 없음)</p>
<h3 id="❗️해결방법">❗️해결방법</h3>
<ul>
<li>앞서 정의된 잘못된 주소로 향하는 navigate 코드를 모두 삭제</li>
<li>그룹/ 팔로워 id가 변경되었을 때 최신 데이터를 불러오도록 하기 위해 react-query querykey에 그룹/ 팔로워 id 추가</li>
<li>그룹/ 팔로워 id가 변경되었을 때도 최신 데이터를 불러옴</li>
</ul>
<p><strong>문제가 되고 있는 handleAlarmClick</strong>
<img src="https://velog.velcdn.com/images/5_wintaek/post/892221ed-2377-4b2a-bdac-f802bdf02e9c/image.png" alt=""></p>
<p>지금 AlarmModal.tsx 를 보고 있는데 여기서 navigate를 2중으로 해준것이 문제였습니다. 이것을 처음 넣어준 이유는, 예시를 통해 알아보는게 좋을것 같아요. 알람을 클릭했을 때 URL 이 follower/36 이라고 가정해보면 ! </p>
<ol>
<li>follwer/36 이동</li>
<li>follower 관련 알람을 클릭 </li>
<li>URL만 변경되고 화면이 리렌더링이 되지 않음</li>
</ol>
<p>강제적으로 리렌더링이 필요하기 때문에 navigate를 처음에 사용하였어요. 이동은 되는데 잠시 화면이 깜빡이면서 routes 에 맞지 않는 경고창을 뱉었죠.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/863b1b39-cfe1-4e05-a81e-2a265c99b706/image.png" alt=""></p>
<p>하지만 이는 잘못된 생각이였어요. 우리는 이미 query를 사용하고 있는데 강제로 navigate를 2중으로 사용하거나, useEffect를 사용해서 리렌더링을 해줄 필요가 없는거죠. </p>
<p>그래서 querykey 호출 부분에 추가로 Id값을 넣어줘서 해결하였습니다.</p>
<p>이번 프로젝트를 통해 querykey 를 제대로 한번 더 짚고 넘어가려고 해요.</p>
<h3 id="querykey">querykey</h3>
<p><code>queryKey</code>는 React Query에서 데이터를 고유하게 식별하는 데 있어 중요한 요소에요. <code>queryKey</code>는 문자열 또는 문자열과 직렬화 가능한 객체를 포함하는 배열로 구성될 수 있습니다. 이를 통해 여러 매개변수에 따라 복잡한 쿼리를 정의할 수 있습니다. (Docs 참고)</p>
<p>예를 들어, 특정 할 일(todo) 항목을 가져오는 쿼리가 있다고 가정할 때, <code>queryKey</code>를 다음과 같이 정의할 수 있습니다:</p>
<p>여기서 <code>todoId</code>는 할 일 항목을 고유하게 식별하는 변수입니다. 쿼리 함수가 추가적인 변수에 의존하는 경우, 이러한 변수들을 <code>queryKey</code>에 포함시켜 쿼리가 독립적으로 <strong>캐시</strong>되고, 변수 변경 시 자동으로 다시 가져올 수 있도록 합니다. (Docs참고)</p>
<pre><code class="language-jsx">useQuery({ queryKey: [&#39;todos&#39;, todoId], queryFn: () =&gt; fetchTodoById(todoId) })</code></pre>
<p>이 코드를 예시로 설명을 한번 들어볼게요. querykey를 호출하는 함수에요.</p>
<p>지금 querykey 에 groupId 를 추가했는데요. </p>
<ol>
<li>캐시된 데이터가 없는 경우</li>
</ol>
<ul>
<li><code>roomid</code>가 101 일 때 queryKey는 useGetRoomInfo를 호출하면 queryKey는 [&#39;get-room-info&#39;, 101]</li>
<li>이 때 React Query는 queryKey로 캐시된 데이터가 없는지 확인하고, 없으면 <code>queryFn</code>인 <code>getRoomInfo({ roomId })</code> 함수를 실행하여 데이터를 가져와요.</li>
</ul>
<ol>
<li>같은 roomId로 다시 호출할 경우</li>
</ol>
<ul>
<li>같은 <code>roomId</code>(<code>101</code>)로 <code>useGetRoomInfo</code>를 호출하면 <code>queryKey</code>는 여전히 <code>[&#39;get-room-info&#39;, 101]</code>입니다.</li>
<li>React Query는 이미 캐시된 데이터가 있기 때문에 서버에 다시 요청하지 않고, 캐시된 데이터를 반환합니다.</li>
</ul>
<ol>
<li><strong>다른 roomId로 호출할 경우 (내가 필요한 경우)</strong></li>
</ol>
<ul>
<li><code>roomId</code>가 <code>202</code>로 바뀌면 <code>queryKey</code>는 <code>[&#39;get-room-info&#39;, 202]</code>가 되요.</li>
<li>React Query는 <code>[&#39;get-room-info&#39;, 202]</code>라는 새로운 <code>queryKey</code>로 캐시를 확인하고, 캐시가 없으면 새 데이터를 서버에서 가져와요.</li>
<li>가져온 데이터는 <code>[&#39;get-room-info&#39;, 202]</code>라는 <code>queryKey</code>로 캐시에 저장되요.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/3401d02a-7a12-449c-afd4-314536e6f9f3/image.png" alt=""></p>
<p>여기 코드는 handleAlarmClick 함수에서 mutation을 처리해주는 코드에요. 즉 Query를 사용하는 부분이죠. navigate를 할 때마다 각각 URL id값이 존재하니 id값을 불러와 문제를 해결해주었어요.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/b91997c3-7628-4f9a-b21c-fbeb83e847f4/image.png" alt=""></p>
<p>🤔 queryKey는 리렌더링이 발생하나 ?</p>
<p><code>queryKey</code> 자체는 리렌더링을 직접적으로 유발하지 않습니다. 그러나 <code>queryKey</code>가 변경되면 React Query는 해당 쿼리를 다시 실행하여 데이터를 가져옵니다. 이 과정에서 컴포넌트가 리렌더링될 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수 호출할 때마다 async-await을 계속 써야되는지 궁금점 ( 'await 키워드를 두 번 연속으로 사용해도 괜찮을까?')]]></title>
            <link>https://velog.io/@5_wintaek/%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C%ED%95%A0-%EB%95%8C%EB%A7%88%EB%8B%A4-async-await%EC%9D%84-%EA%B3%84%EC%86%8D-%EC%8D%A8%EC%95%BC%EB%90%98%EB%8A%94%EC%A7%80</link>
            <guid>https://velog.io/@5_wintaek/%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C%ED%95%A0-%EB%95%8C%EB%A7%88%EB%8B%A4-async-await%EC%9D%84-%EA%B3%84%EC%86%8D-%EC%8D%A8%EC%95%BC%EB%90%98%EB%8A%94%EC%A7%80</guid>
            <pubDate>Thu, 13 Jun 2024 07:49:46 GMT</pubDate>
            <description><![CDATA[<h3 id="궁금한점">궁금한점</h3>
<p>API를 분리하여 QuizPage에서 가져다가 쓰고 있는 상태이다. 하지만 이런 의문점이 든다. API분리 시점에서 이미 async/await을 처리해줬는데 굳이 또 QuizPage안에 있는 handleClickOnBtn 함수 안에서 다시 async/await 처리를 해줘야하나 ? 라는 의문점이 들었다.</p>
<pre><code class="language-tsx">import { api } from &#39;../api&#39;;

export const postAnswer = async (corretIc: boolean) =&gt; {
try {
const quizId = sessionStorage.getItem(&#39;quizId&#39;);
const userId = !sessionStorage.getItem(&#39;userId&#39;)
? 1
: sessionStorage.getItem(&#39;userId&#39;);
const { data } = await api.post(`/api/v1/quiz/${quizId}/solve/${userId}`, {
  answer: corretIc ? 1 : 0,
});
// correctIC 나 failIC 를 눌렀을 떄 둘 다 true가 뜨는데 correctIC 조건 처리를 왜 하는지 모르겠다.
const { correct } = data.data;
return correct;
} catch (error) {
throw new Error(String(error));
}
};</code></pre>
<pre><code class="language-tsx">const handleClickOnBtn = async () =&gt; {
try {
const res = await postAnswer(correctIc);
console.log(res);
navigate(&#39;/result&#39;);
} catch (error) {
throw new Error(String(error));
}
};</code></pre>
<p><code>handleClickOnBtn</code>에서 <code>postAnswer</code> 을 불러오고 있는데, <code>handleClickOnBtn</code> 에서 또 다시 <code>async/await</code>을 사용해야하나 ? 라는 질문</p>
<h3 id="해결방법">해결방법</h3>
<p><code>postAnswer</code> 함수가 비동기 함수이기 때문</p>
<p><code>postAnswer</code> 함수는 <code>async</code> 함수로 정의되어있다. 이는 <code>Promise</code>를 반환하는 함수를 의미.</p>
<p><code>await postAnswer</code>사용하여 비동기 함수가 완료될 때까지 기다리고, 그 결과 값을 반환받을 수 있다.</p>
<p>만약 <code>await</code>를 사용하지 않으면 <code>postAnswer</code> 함수는 즉시 <code>Promise</code> 객체를 반환.</p>
<p>await 말고 Promise의 try,catch 메서드 사용가능, 하지만 가독성이 떨어짐.</p>
<hr>
<h3 id="trycatch-로-대안">try,catch 로 대안</h3>
<p>바꿔서 사용은 가능하다. 하지만 코드 가독성을 떨어뜨림으로 async/await을 사용하자 </p>
<pre><code class="language-tsx">const handleClickOnBtn = () =&gt; {
  postAnswer(correctIc)
    .then((res) =&gt; {
      console.log(res);
      navigate(&#39;/result&#39;);
    })
    .catch((error) =&gt; {
      throw new Error(String(error));
    });
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[해커톤 리펙토링(OX QuizAPI)]]></title>
            <link>https://velog.io/@5_wintaek/%ED%95%B4%EC%BB%A4%ED%86%A4-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81OX-QuizAPI</link>
            <guid>https://velog.io/@5_wintaek/%ED%95%B4%EC%BB%A4%ED%86%A4-%EB%A6%AC%ED%8E%99%ED%86%A0%EB%A7%81OX-QuizAPI</guid>
            <pubDate>Thu, 13 Jun 2024 06:07:47 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>해커톤에서 백엔드와 협업을 처음 해보았다. 또한 해커톤의 특성상 짧은 시간안에 코드를 짜야하고 각자 분업이 있다보니 API 연결을 거의 시도조차 하지 못했었다. 때문에 리펙토링을 하며 코드를 이해하고 분석하고 싶었다.</p>
<h2 id="request">Request</h2>
<hr>
<h3 id="parameter">Parameter</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Desc</th>
<th>Type</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td>quizId</td>
<td>퀴즈 ID</td>
<td>Long</td>
<td>O</td>
</tr>
</tbody></table>
<h3 id="body">Body</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Desc</th>
<th>Type</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td>answer</td>
<td>사용자 답변</td>
<td>Boolean</td>
<td>O</td>
</tr>
</tbody></table>
<h2 id="response">Response</h2>
<hr>
<h3 id="success">Success</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Desc</th>
<th>Type</th>
</tr>
</thead>
<tbody><tr>
<td>quizId</td>
<td>퀴즈 ID</td>
<td>Long</td>
</tr>
<tr>
<td>correct</td>
<td>정답 여부</td>
<td>Boolean</td>
</tr>
</tbody></table>
<pre><code class="language-json">{
    &quot;code&quot;: 200,
    &quot;message&quot;: &quot;success&quot;,
    &quot;data&quot;: {
            &quot;quizId&quot; : 1,
            &quot;correct&quot; : true
    }
}</code></pre>
<hr>
<h2 id="❓이전코드">❓이전코드</h2>
<p>완성된 코드를 갖고 오진 않았고, API 연결 직전의 코드를 갖고 왔다.</p>
<pre><code class="language-jsx">
  const handleClickOnBtn = () =&gt; {
     const data = api.post(`/api/v1/quiz/${quizId}/solve/${userId}`, {answer: correctIc ? 1 : 0});
     data.data.correct ? setIsCorrect(true) : setIsCorrect(false);
     navigate(&#39;/result&#39;, {state : isCorrect});
  };</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/1bafb9f1-d99e-421c-a1e2-45684d7128c2/image.png" alt=""></p>
<hr>
<h2 id="❗️해결방법">❗️해결방법</h2>
<p>이 부분의 대한 api 연결은 내가 연결하지 않아 이해가 가지 않았다. 우선 api 명세서를 제대로 이해하기 위해 명세서부터 한번 파보았다. </p>
<ul>
<li>URL은 <code>/api/v1/quiz/${quizId}/solve/${userId}</code></li>
<li>요청 본문에는 <code>answer</code>가 포함. <code>corretIc</code>가 <code>true</code>이면 <code>answer</code>는 1, 그렇지 않으면 0</li>
</ul>
<p>또한 필요없는 state 부분을 지워주고 그 자리에 sessionStorage를 사용하여 사용자 분기처리를 해주었다. api 를 따로 분리하여 QuizPage에 불러오면서 코드를 조금 더 깔끔하게 정리하였다.</p>
<pre><code class="language-tsx">// postAnswerAPI
import { api } from &#39;../api&#39;;
export const postAnswer = async (corretIc: boolean) =&gt; {
  try {
    const quizId = sessionStorage.getItem(&#39;quizId&#39;);
    const userId = !sessionStorage.getItem(&#39;userId&#39;)
      ? 1
      : sessionStorage.getItem(&#39;userId&#39;);

    const { data } = await api.post(`/api/v1/quiz/${quizId}/solve/${userId}`, {
      answer: corretIc ? 1 : 0,
    });
    const { correct } = data.data;
    return correct;
  } catch (error) {
    throw new Error(String(error));
  }
};
</code></pre>
<p>각 코드의 대해서 설명을 하겠다.</p>
<p><code>const quizId = sessionStorage.getItem(&#39;quizId&#39;);</code> </p>
<p>→ <code>sessionStorage</code> 에서 <code>quizID</code>를 가져온다. API 요청의 파라미터로 간다.</p>
<hr>
<p><code>const userId = !sessionStorage.getItem(&#39;userId&#39;) ? 1 : sessionStorage.getItem(&#39;userId&#39;);</code></p>
<p>→ <code>sessionStorage</code>에서 <code>userId</code>를 가져온다. 만약 <code>userId</code>가 존재하지 않으면 기본값으로 1을 사용한다.</p>
<hr>
<p><code>const { data }</code></p>
<p>→ 사실 여기서 구조분해할당을 도대체 왜 사용한거지..? 라는 의문이 들었다. 구조분해할당이 된 상태에서 console.log를 찍어보았다.</p>
<pre><code class="language-tsx">const { data } = await api.post(`/api/v1/quiz/${quizId}/solve/${userId}`, {
      answer: corretIc ? 1 : 0,
    });
console.log(data)</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/d4cb3241-c945-445f-b4f5-9c4dff70197c/image.png" alt=""></p>
<p>그런 다음 구조분해를 푼 뒤 data 의 console.log를 찍어보았다.</p>
<pre><code class="language-tsx">const data = await api.post(`/api/v1/quiz/${quizId}/solve/${userId}`, {
      answer: corretIc ? 1 : 0,
    });
  console.log(data)</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/9050bca6-f57c-4768-a01a-da096eec277e/image.png" alt=""></p>
<p><code>data</code>의 <code>depth</code>를 보면 data → data → data 밑에 true 가 있는것을 볼 수 있다. 우리는 여기서 <code>correct</code> 의 값인 <code>boolean</code> 을 전달해야 한다. 때문에 구조분해할당을 사용한 것이다.</p>
<hr>
<p><code>const { correct } = data.data;</code></p>
<p>→ 이제는 depth 가 2단계만 남았으니 안에있는 correct 의 값을 구조분해할당을 통해 값을 전달해주는 역할이다. 처음엔 이름을 왜 correct로 지었지..? 라는 생각을 갖고있었는데 당연히 data 안에 값 이름이 correct 이기 때문에 <code>const { correct }</code>로 지어준 것이다. 밑에 사진은 correct을 console.log를 찍어준 모습이다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/42efe6f5-c569-4861-80f9-ef82854dd27d/image.png" alt=""></p>
<hr>
<p>분리한 API 코드를 QuizPage에 갖고온 후 async/await 으로 감싼 뒤, 비동기처리를 해주면서 오류 및 API 분기 처리를 한꺼번에 처리하였다.</p>
<pre><code class="language-jsx">// QuizPage.tsx
const handleClickOnBtn = async () =&gt; {
    try {
      const res = await postAnswer(correctIc);
      console.log(res);
      navigate(&#39;/result&#39;);
    } catch (error) {
      throw new Error(String(error));
    }
  };
</code></pre>
<p>결과하면을 보면 true를 뱉는 모습을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/22bd9ecb-2647-4467-a64b-95891ff1c826/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CORS 오류 및 API 연결 오류]]></title>
            <link>https://velog.io/@5_wintaek/CORS-%EC%98%A4%EB%A5%98-%EB%B0%8F-API-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@5_wintaek/CORS-%EC%98%A4%EB%A5%98-%EB%B0%8F-API-%EC%97%B0%EA%B2%B0-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Wed, 12 Jun 2024 07:57:25 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가며">들어가며</h2>
<p>해커톤을 통해 백엔드와 협업을 하며 api 연결하는게 처음임과 동시에, 명세서를 받은 후 api 연결하는 법을 알아보고 싶어 글을 쓰게 되었다. 또한 배포과정에서 계속해서 CORS 오류가 떠 해결하고자 한 방법들을 적어가며 상기시키기 위해 글을 작성하였다.</p>
<h2 id="문제점">문제점</h2>
<p>1️⃣ CORS 에러</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/2a84a6c8-fe38-4ba3-89f2-051e3af854ef/image.png" alt=""></p>
<p>2️⃣ api 연걸 안됌
<img src="https://velog.velcdn.com/images/5_wintaek/post/68cbbf40-3937-4df8-85a0-e5a7c0e7b857/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/1316aa8f-58ef-4087-bffc-f328d938b632/image.png" alt=""></p>
<h2 id="해결방법">해결방법</h2>
<p>1️⃣ CORS를 해결하기 전에 우선 프록시부터 알아보자.</p>
<h3 id="프록시proxy란-무엇인가">프록시(Proxy)란 무엇인가?</h3>
<p>프록시(Proxy)는 클라이언트와 서버 사이에 위치하여 요청과 응답을 중계하는 서버 또는 소프트웨어를 말합니다. 프록시는 클라이언트의 요청을 받아 실제 서버로 전달하고, 서버의 응답을 다시 클라이언트로 전달하는 역할을 합니다.</p>
<h3 id="프록시의-주요-역할">프록시의 주요 역할</h3>
<ol>
<li><p><strong>중계</strong></p>
<p> 클라이언트의 요청을 받아서 실제 서버로 전달하고, 서버의 응답을 받아 클라이언트에게 전달하는 역할</p>
</li>
<li><p><strong>보안</strong></p>
<p> 프록시는 클라이언트와 서버 간의 직접적인 통신을 차단하여 보안을 강화할 수 있다.</p>
<p> 예를 들어, 클라이언트의 IP 주소를 숨기거나, 특정 요청을 차단할 수 있다.</p>
</li>
<li><p><strong>캐싱</strong></p>
<p> 자주 요청되는 데이터를 캐싱하여 서버의 부하를 줄이고 응답 속도를 높일 수 있다.</p>
</li>
<li><p><strong>로드 밸런싱</strong></p>
<p> 여러 서버에 걸쳐 요청을 분산하여 서버의 부하를 고르게 분산시킬 수 있다.</p>
</li>
<li><p><strong>CORS 우회</strong></p>
<p> 클라이언트와 다른 도메인 간의 통신에서 발생하는 CORS(Cross-Origin Resource Sharing) 문제를 해결할 수 있습니다.</p>
</li>
</ol>
<pre><code class="language-tsx">proxy: {
      &#39;/api&#39;: {
        target: &#39;https://k1c436ba7077fa.user-app.krampoline.com&#39;,
        changeOrigin: true,
        rewrite: (path) =&gt; path.replace(/^\/api/, &#39;&#39;),
        secure: false,
        ws: true,
      },
    },</code></pre>
<p><code>vite.config.js</code> 파일에 추가하여 프록시 설정을 통해 CORS 오류를 해결하는 방법이다. 이 설정은 개발 환경에서 프론트엔드 애플리케이션이 백엔드 API 서버와 통신할 때 발생할 수 있는 CORS 문제를 해결하는 데 사용된다.</p>
<h3 id="옵션-설명">옵션 설명</h3>
<ul>
<li><strong><code>/api</code></strong>:<ul>
<li>프록시 설정을 적용할 경로를 지정. <code>/api</code>로 시작하는 모든 요청이 프록시를 통해 전달</li>
</ul>
</li>
<li><strong><code>target</code></strong>:<ul>
<li>프록시할 대상 서버의 URL을 지정. 여기서는 <code>https://k1c436ba7077fa.user-app.krampoline.com</code>이 대상 서버</li>
<li>프론트엔드에서 <code>/api</code>로 시작하는 요청은 모두 이 URL로 전달</li>
</ul>
</li>
<li><strong><code>changeOrigin</code></strong>:<ul>
<li><code>true</code>로 설정하면, 프록시 요청의 호스트 헤더가 대상 URL의 호스트로 변경. 이는 일부 서버에서 호스트 헤더를 기반으로 요청을 처리하는 경우 유용</li>
</ul>
</li>
<li><strong><code>rewrite</code></strong>:<ul>
<li>경로 재작성 기능을 정의하는 함수. 여기서는 요청 경로에서 <code>/api</code> 제거하고 젇날하는 역할.</li>
<li>예를 들어, <code>/api/users</code> 요청은 프록시를 통해 <code>https://k1c436ba7077fa.user-app.krampoline.com/users</code>로 전달.</li>
</ul>
</li>
<li><strong><code>secure</code></strong>:<ul>
<li><code>false</code>로 설정하면, SSL 인증서가 자체 서명된 경우에도 프록시를 통해 요청을 허용.</li>
</ul>
</li>
<li><strong><code>ws</code></strong>:<ul>
<li><code>true</code>로 설정하면 웹소켓 프록시를 활성화 하는 역할. 웹소켓 연결을 프록시하려는 경우 필요로 함.</li>
</ul>
</li>
</ul>
<p>이 설정을 통해 개발 서버는 <code>/api</code>로 시작하는 모든 요청을 지정된 대상 서버로 프록시하고, CORS 문제를 해결하는 방법이다.</p>
<h3 id="2️⃣-api-연결-오류-해결">2️⃣ api 연결 오류 해결</h3>
<p>전에 연결한 코드는 <code>asnyc/await</code> 을 사용하지 않은 <strong>Promise</strong> 로만 연결을 했다. 코드의 가독성이 좋지 않아 <code>async/await</code> 을 사용하여 코드의 가독성을 첫번째로 높였다.</p>
<pre><code class="language-jsx">const handleClickOnBtn = async () =&gt; {
    try {
      const data = axios.post(&#39;/api/v1/quiz&#39;, {
        keywords: [
          { keyword: selectedCategory[0] },
          { keyword: selectedCategory[1] },
          { keyword: selectedCategory[2] },
        ],
      });
      console.log(data);
    } catch (err) {
      console.log(err);
    }
  };</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/dd44ecb7-473f-46ac-975c-052c14b20dac/image.png" alt=""></p>
<p>fullfiled 뜬 이유는 async/await 처리를 하면서 await을 넣지 않은 문제였다.</p>
<p>→ 다시 await 넣고 처리해봤다.</p>
<hr>
<p>await 처리 했더니 html이 넘어왔다.</p>
<p>서버에 값이 제대로 도착을 안해서 HTML 문서를 다 반환하는거다.</p>
<pre><code class="language-jsx">const handleClickOnBtn = async () =&gt; {
    try {
      const data = await axios.post(&#39;/api/v1/quiz&#39;, {
        keywords: [
          { keyword: selectedCategory[0] },
          { keyword: selectedCategory[1] },
          { keyword: selectedCategory[2] },
        ],
      });
      console.log(data);
      const quizId = data.code;
      console.log(quizId); // undefiend</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/df7277ef-74f0-4471-88e3-f0f99deb3490/image.png" alt=""></p>
<hr>
<p>하지만 코드를 잘 보면 axios.post를 해준것을 볼 수있다. 여기서 html만 넘어온 문제점을 확인할 수 있다. async/await 로 코드를 리펙토링 하다가 따로 만들어놓은 <code>axios.create</code> 을 불러오지 않은것이다. </p>
<p><code>axios.create</code> 를 사용하지 않아 <code>baseURL</code> 지정이 되지 않았고, 결국 <code>console.log</code> 에는 HTML 을 뱉는 모습을 볼 수 있었다.</p>
<pre><code class="language-tsx">import axios from &#39;axios&#39;;

export const api = axios.create({
  // 크램플린 배포 BASE_URL
  baseURL: import.meta.env.VITE_APP_BASE_URL,
});
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[구름톤 10기 후기]]></title>
            <link>https://velog.io/@5_wintaek/%EA%B5%AC%EB%A6%84%ED%86%A4-10%EA%B8%B0-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@5_wintaek/%EA%B5%AC%EB%A6%84%ED%86%A4-10%EA%B8%B0-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 11 Jun 2024 10:31:58 GMT</pubDate>
            <description><![CDATA[<h2 id="🙋-지원하게-된-점">🙋‍ 지원하게 된 점</h2>
<h3 id="나는-지금-성장하고-있는가">나는 지금 성장하고 있는가?</h3>
<p>23년도에는 다양하게 개발 교육 및 공모전에 참여하여 개발공부에 기초 토대를 다지는 년도였다. 하지만 24년도에는 혼자 공부하는 시간이 유독 길었다. 개인적인 생각이긴 하지만 개발은 절대로 혼자 공부한다고 해서 실력이 금방 뛴다고 생각하지 않는다. 때문에 나는 다른 사람들과 함께 협업을 진행하며 다른 사람들이 짠 코드는 어떠한지, 배울점이 있는지, 내 현위치는 지금 어디인지, 또한 해커톤을 통해 문제해결 능력을 기르고 싶다는 생각에 지원을 하게 되었다. </p>
<h2 id="📚-자기소개서-및-지원동기">📚 자기소개서 및 지원동기</h2>
<p>만약 구름톤 후기를 찾아보는 사람들이 가장 알고싶은 내용이 이 내용이 아닐까 싶다. 해주고 싶은 말은 뻔한 말이지만 글에 대한 &#39;솔직함&#39;이 합격률이 가장 높다고 생각한다. 내가 여기서 무엇을 하고 싶은건지, 붙는다면 어떠한 아이디어를 통해 해커톤에서 나의 어필을 할지가 가장 중요하다고 생각한다. 솔직하게 글을 잘 풀어 쓴다면 분명 붙을것이다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/e1d6f48e-db61-4ee9-986b-7875e65aa437/image.jpg" alt=""></p>
<h2 id="🙇🏻♂️-1일차">🙇🏻‍♂️ 1일차</h2>
<p>1일차에 구름톤에 참가하게 된다면 랜덤으로 뽑기를 통해 자리 선정과 구름톤 관련 굿즈들과 명찰표들을 받게 된다. 나는 처음에 랜덤으로 배정된 조가 해커톤이 끝날때까지 같은 팀으로 이어지는줄 알았다. 자리배정을 받고 사람들과 대화를 나누는데 그건 아니였다는걸 알게되었다. 우리테이블에는 프론트엔드가 4명이 있었기 때문이다. 처음 민난 사람들과 대화를 하니 정말 어색해서 죽는줄알았다.. </p>
<p>또한 간단한 해커톤이 어떻게 진행되는지에 대한 과정 소개와 카카오에서 사용하는 크램플린IDE와 디자인을 교육받게 되는데 이건 자기가 직접 골라야한다. 나는 크램플린IDE가 궁금하여 교육을 듣게 되었다. 어차피 꾸준하게 새로운 기술들을 마주하는게 프론트엔드의 숙명이다 라는 생각도있고, 정말 궁금해서 들은 경향도 있다. 디자인을 들어보지는 않았지만 프론트엔드라면 크램플린IDE를 듣는걸 추천한다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/58727b95-12cc-49cb-9b09-d80e0ba7bcc4/image.jpeg" alt=""></p>
<h2 id="🍺-2일차">🍺 2일차</h2>
<p>2일차부터는 카카오 본사를 간 후, 클라우드에 대한 교육과정을 듣는다. 이 후 One page Presentation 을 통해 내가 생각해온 아이디어들을 사람들에게 발표하는 시간을 갖는다. 아이디어 주제는 기수마다 다른 것 같다. 우리 기수의 주제는 <code>#제주 #클라우드 #K-SDGs</code> 라는 주제로 발표하였다. 여기서 자기 어필을 하는게 가장 중요하다고 생각한다. 왜냐하면!! 이후에 바로 팀원을 편성하는 시간이 있기 때문이다. 구름톤 관계자분께서 바로 팀원을 편성하라고 말씀하기 때문이다. 혼자 둥둥 떠다니는 신세가 되기 싫다면 참신한 아이디어를 준비해서 어필을 꼭! 꼭! 하기를 바란다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/b67886d0-cc5e-45f5-ab75-46a0e4e318f1/image.jpeg" alt=""></p>
<hr>
<p>팀 구성은 총 5명이다. 기획자1 백엔드1 디자인1 프엔2
같이 지원한 친구(프엔)에게 &#39;우린 서로 떨어져서 다른 사람들의 코드를 보고 배운다는 느낌으로 가자!&#39; 라고 말을 한 뒤 갈라지게 되었다. 우연히 앞에 있는 테이블 분들이 벌써 3명 팀 구성을 끝낸게 보여 간절하게 &#39;저도 껴두될까요..? 뽑아주세요..&#39; 라고 말을 하였더니 바로 뽑아주었다. 지금 생각해도 어떻게 뽑힌거지? 라는 생각이 들긴한다. 왜냐?  One page Presentation 에서 임펙트있는 발표를 하지 않았기 때문이다. 우열곡절 끝에 나는 간신히 간택(?)당해서 이렇게 팀원이 구성되었다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/a501d3ee-373f-452e-8dc5-2f2421af4721/image.jpeg" alt=""></p>
<hr>
<p>팀원 구성이 끝난 후 구름톤에서 제공해주는 숙소에서 머물게 된다. 바로 성산 플레이스캠프! 나는 여길 처음 와봤는데 꽤나 유명한 곳이였다. 각각 1인1실을 배정받아서 너무 좋았다. 이때는 몰랐다. 어차피 숙소방에서 있을 시간이 없다는것을..</p>
<p>해커톤에 꽃이라는 비어파티가 있기 전 2~3시간 텀이 있었다. 이 사이에는 자유시간이라고 해주셔서 바로 팀원들과 함께 아이디어 회의에 들어갔다. 개발하는 시간 이외에는 이때가 정말 머리아픈 시간이였다.. 주어진 시간 안에 할 수 있냐 없냐 라는 현실적인 문제와 마주했기 때문이다. </p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/c6b93e0c-e754-47f9-9d9b-cf8a02852d53/image.jpeg" alt=""></p>
<hr>
<p>그렇게 머리아픈 시간이 잠시 흘러가고, 비어파티에 들어갔다. 사실 사진이 이래서 그렇지 음식은 보이는것보다 훨씬 더 많았다. 나는 술을 잘 마시지 못해서 술 말고 음식에 먹는것을 집중했다. 처음 자리는 각각 같은 기술로 지원하게 된 사람들과 마주하여 음식을 먹었다. </p>
<p>그리구 점점 자리가 뒤섞이면서 여러 사람들과 함께 이야기하며 재밌는 시간을 보냈다. 해커톤 2일차 비어파티까지가 정말 딱 안힘들고? 사람들과 대화를 많이 한 시간이었던 것 같다. 비어파티가 끝나면 꼭꼭 앞에 바다를 보러가든 산책을 하든 조금이라도 놀기를 바란다. 이때밖에 시간이 없으니까 말이다😂</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/edd43205-8a6c-4176-9685-6f853655e800/image.jpeg" alt=""></p>
<hr>
<h2 id="💻-34일차">💻 3~4일차</h2>
<p>이때부터는 진짜 내가 죽는다는 각오로 임하고 해야한다. 4일차 오전까지 개발 및 배포를 끝내야하기 때문에 실질적인 시간은 딱! 하루였다. 나는 첫 해커톤이여서 많은 긴장을 하고 개발에 임했다. 나는 내 실력이 정말 부족하다는걸 알고 있기 때문에 팀원들에게 피해를 주기 싫었기 때문이다. 특히 같은 프엔 개발자인 아름이에게 무조건적으로 도움이 되고 싶었다. </p>
<p>그렇게 다들 밤을 꼬박 새다싶이 하여 하나의 프로젝트가 완성되었다. K-SDGs와 지속가능한 제주 환경에 대한 낮은 인식에 대해 고민을 하여 만든 프로젝트이다. 제주 환경의 대한 주제를 바탕으로 정보제공 및 마일리지 적립을 통해 사용자를 끌어들이는 주제였다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/736a4fe6-978d-4620-be17-d09e28b8c768/image.gif" alt=""></p>
<hr>
<p>그렇게 4일차 오전에 각 팀원의 조장이 나와 프로젝트를 발표한 후 카카오 및 구름 심사위원분들이 평가하여 점수를 매기는 형식으로 진행되었다. 아쉽게도 우리조는 수상을 하지는 못했다. 마일리지를 어떻게 활용한것인지, 그리고 타겟층이 누구인지, 이 프로젝트가 유저를 계속해서 끌어들일 수 있는지에 대한 문제점이 있었기 때문이다. 맞는말이여서 우리 팀원들도 다같이 수긍을 하긴 했다. </p>
<p>하지만 뭐.. 후회는없다. 아이디어는 다같이 오케이를 외쳐서 한 것이기도 하고, 수상보다는 이러한 값진 경험을 한것과 좋은 사람들을 만났다는점. 그리고 무엇보다 같은 프엔인 아름이에게 많은 것을 배웠기 때문이다. 물론 수상을 하면 더 좋았겠지만, 그거는 부수적인거구! </p>
<h2 id="🤔-후기">🤔 후기</h2>
<p>먼저 해커톤을 함께 지원해보자는 문주한테 고맙다는 말을 전하구 싶다. 문주가 아니였다면 해커톤이 있는지도 모르고 그냥 지나쳤을텐데..</p>
<p>그리구 처음부터 반모하자고 권유해준 승재형한테 고맙다. 형이 아니였더라면 우리조는 다른조와 마찬가지로 적당히 친하게 지내다가 말았을 것이다. 반모 덕분에 분위기도 편해지고 서로에게 장난치며 더욱 더 가까운 사이가 되었던것 같다. </p>
<p>모자란 실력을 뒷받쳐준 팀원한테도 고맙다. 사실 내 실력이 너무 부족해서 해커톤에 지원을 해도 되나 싶을정도로 고민이 많았는데, 좋은 팀원들을 만나 개발하면서 점점 나의 고민은 사라져갔다. 뭐.. 사실 아름이가 아니였다면 굉장히 자책감을 갖고 팀원들한테 미안해했을텐데 나를 끝까지 잘 잡아주고 칭찬도 많이 해줘서 정말로 큰 도움이 되었다. </p>
<p>구름톤을 지원하려 생각하는 사람들에게 꼭 지원해보라고 전하고싶다. 짧은 시간안에 문제 해결 능력을 길러주고, 내가 무엇이 부족한지 알게되고, 나 이외에도 세상에는 정말 잘하는 사람들이 많구나 라는걸 느끼게 되는 시간이기 때문이다. 또한 덤으로 좋은 사람들과 인연을 갖게 되니 1석2조이지 않을까 싶다. </p>
<p><img src="blob:https://velog.io/19205b62-f8aa-4240-8b1c-929926e77162" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useRef 와 forwardRef]]></title>
            <link>https://velog.io/@5_wintaek/useRef-%EC%99%80-forwardRef</link>
            <guid>https://velog.io/@5_wintaek/useRef-%EC%99%80-forwardRef</guid>
            <pubDate>Wed, 27 Mar 2024 11:44:12 GMT</pubDate>
            <description><![CDATA[<h3 id="ref-란-무엇인가-">ref 란 무엇인가 ?</h3>
<p>간단한 사이드 프로젝트인 countdown-game을 제작하면서 dialog 와 timer를 전달을 위해 useRef를 사용하였다. </p>
<blockquote>
<p>useRef는 렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook입니다.(React공식문서)</p>
</blockquote>
<p>JavaScript를 사용할 떄 특정 DOM을 선택해야 하는 상황에서 우리는 <code>getElementById,</code> 같은 DOM Selector 함수를 사용해서 DOM을 선택한다. 리액트를 사용하는 프로젝트에서도 가끔 DOM을 선택해야 하는 상황이 발생한다. DOM에 직접적으로 컨트롤 할 떄 사용하는것이 바로 <code>useRef</code>이다.</p>
<ul>
<li>Ref는 상태값들처럼 유실되지 않는다.</li>
<li>컴포넌트 함수를 다시 실행하도록 하지 않는다.</li>
<li>Ref는 UI와 직접적 영향이 없는 경우 사용하는것이 좋은데, timer 자체가 UI와 직접적 영향이 없으므로 사용하기 좋은 사례</li>
</ul>
<h3 id="사용법-ref-로-값-참조하기">사용법. ref 로 값 참조하기</h3>
<p>useRef는 처음에 제공한 초기값으로 설정된 단일 current 프로퍼티가 있는 ref 객체를 반환한다.
ref를 변경해도 리렌더링을 촉발하지 않는다. 즉 ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합하다. Timer를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있다. ref 내부의 값을 업데이트 하려면 current 프로퍼티를 수동으로 변경해야 한다.</p>
<p>ref를 사용하면 다음을 보장한다.</p>
<ul>
<li>(렌더링할 때마다 재설정되는 일반 변수와 달리)리렌더링 사이에 정보를 저장할 수 있다.</li>
<li>리렌더링을 촉발하는 state 변수와 달리 변경해도 리렌더링을 촉발하지 않는다.</li>
<li>각각의 컴포넌트에 로컬로 저장된다.</li>
</ul>
<p>다음 코드는 React 공식문서에 있는 코드이다.
ref값을 참조하여 버튼을 클릭할 떄 마다 횟수가 증가한다.</p>
<pre><code class="language-jsx">import { useRef } from &#39;react&#39;;

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert(&#39;You clicked &#39; + ref.current + &#39; times!&#39;);
  }

  return (
    &lt;button onClick={handleClick}&gt;
      Click me!
    &lt;/button&gt;
  );
}</code></pre>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/80fb26d6-e47e-43cd-9036-16f76ccf1fce/image.png" alt=""></p>
<h3 id="state-와-ref-의-차이점">state 와 Ref 의 차이점</h3>
<p> state : 상태값들은 컴포넌트 함수를 재실행함 상태는 UI에 바로 반영되어야 하는 값들이 있을때만 사용
 ref :시스템 내부에 보이지 않는 쪽, UI에 직접적인 영향을 끼치지 않는 것들에 사용</p>
<h3 id="forwardref-란">forwardRef 란?</h3>
<blockquote>
<p>forwardRef 를 사용하면 컴포넌트가 ref.를 사용하여 부모 컴포넌트의 DOM 노드를 노출할 수 있습니다.</p>
</blockquote>
<p>ref(참조)는 다른 컴포넌트로 전달할 수 없다. 대신 만약에 참조를 컴포넌트에서 다른 컴포넌트로 전달하고 그 값을 사용하고 싶다면 forwardRef 를 사용</p>
<p>open을 사용해서 modal창을 이용하면 modal창 background 색상이 회색이 되고 싶게 만들고 싶은데 , ref와 forwardRef 를 이용해서 TimerChallenge.jsx에 있는 ref값을 가져와 만들것이다.</p>
<p>수정전 코드</p>
<pre><code class="language-jsx">function ResultModal({result, targetTime}) {
  return (
    &lt;dialog open&gt;
      &lt;h2&gt;You {result}&lt;/h2&gt;
      &lt;p&gt;The target time was {targetTime} seconds.&lt;/p&gt;
      &lt;p&gt;You Stop the timer with X seconds lefts&lt;/p&gt;
      &lt;form method=&quot;text&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
      &lt;/form&gt;
    &lt;/dialog&gt;
  );
}

export default ResultModal;</code></pre>
<h3 id="예제로-보는-forwwardref-사용법">예제로 보는 forwwardRef 사용법</h3>
<p>TimerChallenge 컴포넌트 ⇒ ResultModal 컴포넌트 전달</p>
<ul>
<li>컴포넌트 함수를 감싸야 한다.</li>
<li>첫번째는 props 받지만, 두번째는 ref 매개변수를 받는다.</li>
<li><strong><code>forwardRef</code></strong>를 사용하여 <strong><code>ref</code></strong>를 받아오고, <strong><code>dialog</code></strong> 엘리먼트에 <strong><code>ref={ref}</code></strong>를 설정하여 부모 컴포넌트에서 <strong><code>ref</code></strong>를 통해 접근할 수 있도록 하였다. 이로 인해 <strong><code>TimerChallenge</code></strong> 컴포넌트에서 <strong><code>dialog.current.showModal();</code></strong>을 호출할 때 <strong><code>showModal</code></strong> 함수가 호출되어야 한다.</li>
</ul>
<p>수정후 ResumtModal</p>
<pre><code class="language-jsx">import {forwardRef} from &#39;react&#39;;

const ResultModal = forwardRef(function ResultModal({result, targetTime}, ref) {
  return (
    // dialog 는 기본값이 close라 open 이라는 것을 넣어줘야함
    &lt;dialog ref={ref} className=&quot;result-modal&quot;&gt;
      &lt;h2&gt;You {result}&lt;/h2&gt;
      &lt;p&gt;The target time was {targetTime} seconds.&lt;/p&gt;
      &lt;p&gt;You Stop the timer with X seconds lefts&lt;/p&gt;
      &lt;form method=&quot;dialog&quot;&gt;
        &lt;button&gt;Close&lt;/button&gt;
      &lt;/form&gt;
    &lt;/dialog&gt;
  );
});

export default ResultModal;</code></pre>
<ul>
<li>showModal 은 표준 브라우저 기능 중 하나이다.</li>
<li><strong><code>TimerChallenge</code></strong> 컴포넌트는 부모 컴포넌트에서 전달되는 <strong><code>title</code></strong>과 <strong><code>targetTime</code></strong>을 받아와 화면에 렌더링한다</li>
<li><strong><code>useState</code></strong> 훅을 사용하여 타이머가 시작되었는지(<strong><code>timerStarted</code></strong>)와 타이머가 만료되었는지(<strong><code>timerExpired</code></strong>) 상태를 관리한다.</li>
<li><strong><code>useRef</code></strong> 훅을 사용하여 타이머와 결과 모달 다이얼로그에 접근하는 데 사용되는 <strong><code>timer</code></strong>와 <strong><code>dialog</code></strong>를 생성한다.</li>
<li><strong><code>handleStart</code></strong> 함수는 타이머를 시작하고, 지정된 시간 후에 타이머가 만료되면 <strong><code>ResultModal</code></strong> 컴포넌트를 열어줍니다.</li>
<li><strong><code>handleStop</code></strong> 함수는 타이머를 중지한다.</li>
<li>JSX를 통해 화면에는 도전 과제의 제목, 목표 시간, 시작/중지 버튼 등이 렌더링되며, 만료되었을 때 <strong><code>ResultModal</code></strong> 컴포넌트가 나타난다.</li>
</ul>
<pre><code class="language-jsx">import {useState, useRef} from &#39;react&#39;;
import ResultModal from &#39;./ResultModal&#39;;

function TimerChallenge({title, targetTime}) {
  const timer = useRef();
  const dialog = useRef();
  const [timerStarted, setTimerStarted] = useState(false);
  const [timerExpired, setTimerExpired] = useState(false);

  const handleStart = () =&gt; {
    timer.current = setTimeout(() =&gt; {
      setTimerExpired(true);
      dialog.current.showModal();
    }, targetTime * 1000);

    setTimerStarted(true);
  };

  const handleStop = () =&gt; {
    clearTimeout(timer.current);
  };

  return (
    &lt;&gt;
      &lt;ResultModal ref={dialog} targetTime={targetTime} result=&quot;lost&quot; /&gt;
      &lt;section className=&quot;challenge&quot;&gt;
        &lt;h2&gt;{title}&lt;/h2&gt;
        &lt;p className=&quot;challenge-time&quot;&gt;
          {targetTime} second{targetTime &gt; 1 ? &#39;s&#39; : &#39;&#39;}
        &lt;/p&gt;
        &lt;p&gt;
          &lt;button onClick={timerStarted ? handleStop : handleStart}&gt;
            {timerStarted ? &#39;Stop&#39; : &#39;Start&#39;}
          &lt;/button&gt;
        &lt;/p&gt;
        &lt;p className={timerStarted ? &#39;active&#39; : undefined}&gt;
          {timerStarted ? &#39;Time is running out ~&#39; : &#39;Timer inactive&#39;}
        &lt;/p&gt;
      &lt;/section&gt;
    &lt;/&gt;
  );
}

export default TimerChallenge;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[크래프톤 지원후기와 느낀점]]></title>
            <link>https://velog.io/@5_wintaek/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A7%80%EC%9B%90%ED%9B%84%EA%B8%B0%EC%99%80-%EB%8A%90%EB%82%80%EC%A0%90</link>
            <guid>https://velog.io/@5_wintaek/%ED%81%AC%EB%9E%98%ED%94%84%ED%86%A4-%EC%A7%80%EC%9B%90%ED%9B%84%EA%B8%B0%EC%99%80-%EB%8A%90%EB%82%80%EC%A0%90</guid>
            <pubDate>Mon, 25 Mar 2024 13:18:01 GMT</pubDate>
            <description><![CDATA[<h2 id="📎-공채준비">📎 공채준비</h2>
<p>24년도 첫번째 공채 준비를 하게 되었다. 나한테도 살면서 이력서를 써내려가며 다듬고 수정을 거치는 과정을 거칠줄은 몰랐다. 물론 급하게 이력서를 썻던것도 아니다. 23년도 마지막 학기를 다닐 때 부터였나..어느정도 &#39;이력서&#39; 라는 틀을 갖고 조금씩 채워가긴 했다. 이력서 뿐이겠나? 이력서는 기본이고 각 회사마다 원하는 자기소개서 유형도 맞춰가며 제출을 해야했다. </p>
<p>3월 공채 시즌을 준비하며 이렇게까지 &#39;진짜 가보고 싶다&#39; 라고 생각을 하게 만든 회사가 하나 있었는데 <code>크래프톤</code> 이였다. 이유는 간단했다. 그냥 내가 게임 자체를 너무 좋아하기 떄문이였다. 또한 게임 회사에 대한 막연한 로망 같은게 있어서 그런지 더더욱 가고 싶었던 마음이 컸다. 그래서 자소서를 작성하면서 여러 사람한테 피드백을 받으면서 수정을 엄청나게 했던 기억이 있다. 이렇게 또 하나의 자소서 틀이 완성이 되었다. </p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/147fcd84-68f0-4574-a90c-cd3185eba8a7/image.png" alt=""></p>
<h3 id="🎉-1차-합격">🎉 1차 합격</h3>
<p>지금은 사이트가 닫혀서 보이진 않지만 크래프톤은 지원날짜가 명확하게 써져있지 않았다. 즉 마감이 언제 될지도 모른다는 소리다. 내가 크래프톤 자기소개서를 준비한건 3월14일부터 였다. 우연치않게 지인을 통해서 프론트엔드 개발자를 뽑고 있다는 소식을 듣고 급하게 준비하였다. 너무 시간을 끌어도 안됐구, 너무 대충쓰자니 정말 가고싶다는 마음이 컸기에 많은 피드백을 받은 것이다. 그렇게 해서 3월 19일날 제출을 하였다. 
<img src="https://velog.velcdn.com/images/5_wintaek/post/816a23ca-40eb-4d1b-a312-a5e4042ff37e/image.png" alt=""></p>
<p>음..사실 합격 메일을 받고 조금 의심을 했다. 피드백을 받으며 정말 열심히 쓰긴 했지만 진짜로 뽑힐지 몰랐기 때문이다. 자기소개서를 쓰기만하면 합격을 시켜주는건 아닌가? 라는 생각도 해봤다. 그리구 19일 오후 3시에 제출을 하였는데 당일 오후 8시에 합격 메일을 받은것도 의심에 한몫했다.</p>
<h3 id="📚-2차-직무-테스트">📚 2차 직무 테스트</h3>
<p>사실 나는 코딩테스트에 매우 자신이 없구 취약하다고 생각한다. 아니, 생각이 아니고 맞다. 개발을 시작한지 이제 막 1년이 조금 넘었고, 아직까지 많은 알고리즘을 이해하지도, 풀어보지도 않았다. 어렵기도 하고,,그래서  크래프톤은 &#39;직무테스트&#39; 라는 문구에 열심히 작성하여 지원서를 넣은것이다. </p>
<p>테스트는 1주일이라는 기간이 주어진 후 테스트 시작! 을 누르면 제한시간안에 문제를 풀어서 제출하는 거였다. 테스트를 보기 전에 대체 어떤게 나올지 몰라 긴장을 정말 많이 했지만 무슨 문제가 나올지 모르기도 하고, 그렇다고 코딩테스트 유형대로 나오지 않을 것 같아 평소대로 공부한 뒤 시험을 시작했다.</p>
<p>하지만,,막상 테스트를 시작하고 문제를 보니 그냥 코딩테스트 였다. 보안 문제상 시험문제를 이야기 하진 않겠지만 내가 예상했던 문제는 하나도 나오지 않았고 딱 1문제를 제외하고 정말 처음 보는 유형에 문제였다. 뭐랄까,, 코딩테스트에도 정해진 틀이 있지만, 그 틀에서 벗어난 문제라고 해야하나 ? </p>
<p>다행히 반은 맞추고 제출버튼을 눌렀다. 거의 2시간 이상을 앉아있으며 문제를 고민하고 해석해서 그런지 진이 다 빠졌다,, 내가 이렇게까지 집중을 할 수 있구나 라는 생각도 들기도 하면서 뿌듯했지만 문제를 다 풀지 못하고 제출하여서 아쉬움도 많이 남는 테스트였다. 아마 떨어질 가능성이 크다,,</p>
<h3 id="🏃🏻-느낀점">🏃🏻 느낀점</h3>
<p>크래프톤 시험을 본 뒤로, 다른 회사 지원을 많이 꺼려했다. 이유는 간단했다. 거의 모든 회사는 이렇게 코딩테스트를 치룰텐데 실력이 이렇게 없어서야 1차를 지원해서 뭐하나,,라는 생각이 컸다. 하지만 자기소개서의 경험을 늘리자 라는 생각으로 준비중이다. </p>
<p>또한 코딩테스트는 진짜 어쩔 수 없이 같이 끌고 가야하는 운명이구나,, 라는 생각이 들게 한 시험이었다. 만약 다음에도 이렇게까지 간절하게 가고 싶은 회사가 또 있다면 그 회사가 코딩테스트를 안본다는 보장이 없지 않으니 말이다. 그래서 글을 쓴 오늘부터 꾸준하게 코딩테스트를 준비하며 다음 공채 시즌을 준비 할 계획이다. </p>
<p>참 우울하게 3월달을 마무리 하는것같다. 정말 준비해야 할 것이 산더미 같이 쌓인 기분이 든다. 사실 나는 공부머리를 갖고 태어난 것이 아니라고 생각하기에 개발자 라는 길을 너무 쉽게 선택하고 준비하게 되었나 라는 생각이 드는 달이였다. 하지만 뭐 어떻하겠나,, 개발일을 하고 싶어 편입도 한거기도 하고 내가 선택한 직무인데 끝은 봐야지 라는 생각도 든다. </p>
<p>그리고 뭐,, 막말로 아직 1년 조금 넘짓한 기간으로 개발을 한거니까 못하는것도 당연하다는 생각이 든다. 다른 사람들은 정말 몇년을 죽어라 준비하여 회사에 지원을 하고 경쟁할텐데 나는 아직 거기에 미치지도 못하니깐 더 열심히해야지.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저의 렌더링과 리플로우 리페인팅]]></title>
            <link>https://velog.io/@5_wintaek/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81%EA%B3%BC-%EB%A6%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%A6%AC%ED%8E%98%EC%9D%B8%ED%8C%85-ibrzk7si</link>
            <guid>https://velog.io/@5_wintaek/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81%EA%B3%BC-%EB%A6%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%A6%AC%ED%8E%98%EC%9D%B8%ED%8C%85-ibrzk7si</guid>
            <pubDate>Thu, 29 Feb 2024 06:34:24 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-들어가며">📚 들어가며</h2>
<p>오늘 면접 스터디에서는 리플로우와 리페인팅에 대해 설명 및 예상 면접 질문들을 만들어 팀원들에게 소개하였다. 우선, 리플로우와 리페인팅 설명 전에는 브라우저 렌더링의 대해 간단하게 설명하는게 좋다고 생각하여 렌더링 설명을 추가하였다. 또한 블로그에 한번 더 정리하면서 짚고 넘어가는게 좋다는 생각을 하여 이 글을 작성하게 되었다.</p>
<h2 id="브라우저의-렌더링이란-">브라우저의 렌더링이란 ?</h2>
<p>렌더링이란 HTML,CSS,JS 등 개발자가 작성한 문서가 브라우저에서 출력되는 <strong>과정</strong>을 말한다.</p>
<h3 id="렌더링-동작과정은-어떻게-될까">렌더링 동작과정은 어떻게 될까?</h3>
<ol>
<li>HTML 파일과 CSS 파일을 파싱해서 각각 Tree를 만든다. (Parsing)</li>
<li>두 Tree를 결합하여 Rendering Tree를 만든다. (Style)</li>
<li>Rendering Tree에서 각 노드의 위치와 크기를 계산한다. (Layout)</li>
<li>계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만든다. (Paint)</li>
</ol>
<h3 id="parsing">parsing</h3>
<p>브라우저가 페이지를 렌더링 하려면 가장 먼저 받아온 HTML파일을 해석해야 한다. Parsing 단계는 HTML 파일을 해석하여 DOM TREE를 구성하는 단계이다. </p>
<p>파싱 단계에서 HTML에 CSS가 포함되어 있다면 CSSOM Tree 구성 작업도 함께 진행이 된다.</p>
<h3 id="style">Style</h3>
<p>style은 parsing 단계에서 생성된 DOM Tree와 CSSOM Tree를 매칭시켜 하나의 Render Tree로 구성한다. 여기서 Render Tree란 실제로 화면에 그려질 Tree이다.</p>
<p>예시로 들면 Render Tree를 구성할 때 visibility: hidden은 요소가 공간을 차지하고, 보이지만 않기 때문에 Render Tree에 포함이 되지만, display: none 의 경우 Render Tree에서 제외된다.</p>
<h3 id="layout">Layout</h3>
<p>Layout 단계에서는 Render Tree를 화면에 어떻게 배치해야 할 것인지 노드의 정확한 위치와 크기를 계산하는 단게를 뜻한다.</p>
<p>루트부터 위로 올라가면서 노드를 순회한다. 노도의 정확한 크기와 위치를 계산하고 Render Tree에 반영한다. 만약 크기 값을 %로 지정하였다면, Layout 단계에서 %값을 계산하여 픽셀 단위로 변환하는 역할을 한다.</p>
<h3 id="paint">paint</h3>
<p>Paint 단게에서는 Layout 단게에서 계산된 값을 이용하여 Render Tree의 각 노드를 화면상의 실제 픽셀로 변환한다. 이 때 픽셀로 변환된 결과는 하나의 레이어가 아니라 여러 개의 레이어로 관리된다.</p>
<p>스타일이 복잡할수록 Paint 시간도 늘어난다.  예시로 들면, 단색 배경의 경우 시간과 작업 비용이 적게 들어가지만, 그림자 효과는 시간과 작업 비용이 더 많이 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/e55b651f-d505-4836-9fb6-19f2749a588d/image.png" alt=""></p>
<figcaption style></figcaption>

<h2 id="🤔-그렇다면-브라우저는-어떻게-동작하는가">🤔 그렇다면 브라우저는 어떻게 동작하는가</h2>
<ol>
<li>사용자가 <a href="http://www.example.com">www.example.com</a>(도메인 주소)에 접속한다.</li>
<li>이 때 브라우저는 도메인 주소로 네트워크 요청을 보낸다.</li>
<li>만약 이 사이트를 한번도 방문한 적이 없다면 브라우저는 DNS 조회 요청한다.</li>
<li>찾은 IP 주소로 요청으 전송하여 도메인 주소에 해당하는 리소스를 응답으로 받는다.</li>
</ol>
<h3 id="dnsdomain-name-system">DNS(Domain Name System)</h3>
<p>인터넷에서 사용되는 도메인 이름과 IP 주소를 연결하는 시스템이다. 인터넷 상에서 컴퓨터나 네트워크 장치는 IP 주소를 사용하여 통신한다. 하지만 이 IP 주소는 사람이 기억하기 어렵고 숫자로 이루어져 있어 사용자에게 어려움을 제공한다.</p>
<p>DNS는 이러한 불편함을 해소하기 위해 도메인 이름(예: <a href="http://www.example.com)%EA%B3%BC">www.example.com)과</a> IP 주소를 매핑해 주는 시스템으로 작동한다. 사용자가 웹 브라우저에 도메인 이름을 입력하면, DNS는 해당 도메인 이름에 대응하는 IP 주소를 찾아내고 이를 통해 요청된 서버에 접속한다. 이렇게 하면 사용자는 기억하기 쉬운 도메인 이름을 사용하여 웹사이트에 접속할 수 있다.</p>
<h2 id="🤔-브라우저-렌더링과-reflowrepaint의-관계">🤔 브라우저 렌더링과 reflow,repaint의 관계</h2>
<ul>
<li>Layout은 앞서 설명했듯이 요소의 위치,크기를 계산하여 화면에 그리는 과정인데 reflow 와 관련있다.</li>
<li>paint는 배치된 요소에 스타일을 적용하는 과정으로 repaint와 관련있다.</li>
</ul>
<h3 id="✔️-리플로우reflow와-리페인팅repaint">✔️ 리플로우(reflow)와 리페인팅(repaint)</h3>
<p>reflow와 repaint는 요소가 시각적으로 변경되었을 때, 변화를 계산하여 화면에 그려주는 작업을 말한다. DOM이 시각적으로 변경되면 reflow가 발생하여 렌더트리를 재생성하고 생성된 렌더트리를 기반으로 요소를 화면에 그리는 repaint가 발생한다.</p>
<h3 id="✔️-리플로우reflow">✔️ 리플로우(reflow)</h3>
<ul>
<li>리플로우는 브라우저가 페이지를 렌더링할 때 발생하는 과정 중 하나로, 요소들의 크기나 위치 등이 변경될 떄 다시 계산되고 레이아웃이 갱신되는 과정을 말한다.</li>
<li>리플로우는 비용이 큰 작업인데, 그 이유는 특정 요소에서 리플로우가 발생하면 주변 요소(부모,자식,형제)에도 영향을 주기 때문이다.</li>
</ul>
<blockquote>
<h3 id="🤔-특정-요소의-너비-변경이-주변-요소에-리플로우를-일으킨다-무슨말일까">🤔 특정 요소의 너비 변경이 주변 요소에 리플로우를 일으킨다? 무슨말일까?</h3>
</blockquote>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/b11e47db-9b67-4dc2-b0e6-6936b9844550/image.png" alt=""></p>
<ul>
<li>만약 세 개의 요소가 있다고 가정하고, 이 중 형제 2의 너비를 변경했다.</li>
<li>형제의 경우 너비를 변경하므로 리플로우가 발생한다.</li>
<li>그러나 형제2의 너비 변경으로 인해, 인접한 형제3의 위치도 변경되어 리플로우가 발생한다. </li>
<li>요소 하나의 변화가 주변 요소의 위치나 크기에 영향을 주어, DOM트리 계산 작업이 발생하고 렌더 트리가 재생성 되는 것이다.</li>
</ul>
<blockquote>
<h3 id="🤔-reflow-발생-시점은-언제일까">🤔 reflow 발생 시점은 언제일까?</h3>
<p>a. DOM 요소의 크기,위치,구조 등 속성이 변경될 떄(width,height 등)
b.  브라우저 사이즈가 변할 때
c. 텍스트 내용, 이미지 등 컨텐츠가 동적으로 변경될 때 d. JS DOM관련 메소드를 실행하거나 속성에 접근할 때</p>
</blockquote>
<h3 id="✔️-리페인트repaint">✔️ 리페인트(repaint)</h3>
<ul>
<li>변경된 요소를 화면에 그려주는 작업을 리페인트 라고 한다.</li>
<li>리페인트는 요소의 스타일이 변경되었을 때 발생하므로 리플로우 보다 비용이 적다.</li>
</ul>
<blockquote>
<h3 id="🤔-리페인트-발생-시점은-언제일까-">🤔 리페인트 발생 시점은 언제일까 ?</h3>
<p>a. 리플로우가 발생했을 때
b. 요소의 스타일(색상,배경색 등)이 변경되었을 때
c. visibility 속성, opacity 속성이 변경될 때
d. 요소의 포커스가 변경될 때</p>
</blockquote>
<blockquote>
<h3 id="🤔-visibility가-변경되는게-리페인트면-displaynone-도-리페인트인가요-">🤔 visibility가 변경되는게 리페인트면 display:none 도 리페인트인가요 ?</h3>
</blockquote>
<ul>
<li><p>앞에서 렌더링 과정에서 설명했듯이 <code>visibility : hidden</code>은 단순히 보이지 않을뿐 크기나 위치가 변하는게 아니기에 리페인트만 발생한다.</p>
</li>
<li><p><code>display:none</code> 같은 경우에는 영역을 차지하지 않으면서 보이지 않는다. 즉, Render Tree에서 제외 된다는 말이므로, <code>display:none</code>이 적용된 요소는 사라지면서 주변 요소의 위치, 크기에도 영향을 주기 때문에 리플로우,리페인트가 발생한다.</p>
</li>
</ul>
<h3 id="🤔-리플로우-줄이는-방법이-뭐가-있을까요-">🤔 리플로우 줄이는 방법이 뭐가 있을까요 ?</h3>
<p>1) display : none 이용하기</p>
<p>display none 이 적용되면 그 요소는 렌더 트리에서 제외되고 없는 요소 취급을 당하기 때문에 이 요소에 다른 기하학적 변화가 일어나도 리플로우나 리페인트는 발생하지 않는다.</p>
<pre><code class="language-js">div.style.display = &quot;none&quot;;   // (A) 렌더트리에서 제외시키기

// 스타일 수정

div.style.display = &quot;block&quot;;  // (B) 스타일 수정 후 렌더트리에 추가하기</code></pre>
<p>(A),(B)에서 각각 리플로우 리페인트가 1번씩 발생하기에 많은 스타일이 변경되는 경우 비용을 절감할 수 있다. </p>
<p>2) 이벤트 핸들러 사용시 DOM을 조작하는 경우도 리플로우가 발생하니 이벤트를 효율적으로 처리</p>
<p>JS로 여러 DOM의 속성을 변경할 때 코드 순서에 따라 리플로우 횟수를 줄일 수 있다.</p>
<pre><code class="language-js">// BAD
const el1 = document.querySelector(&#39;.target-first&#39;);
el1.style.width = &#39;10px&#39;;

const el2 = document.querySelector(&#39;.target-second&#39;);
el2.style.width = &#39;10px&#39;;

const el3 = document.querySelector(&#39;.target-third&#39;);
el3.style.width = &#39;10px&#39;;</code></pre>
<p>아래와 같이 DOM의 스타일을 변경하는 코드를 모아두면 리폴로우 횟수를 1번으로 줄일 수 있다. </p>
<pre><code class="language-js">// GOOD

const el1 = document.querySelector(&#39;.target-first&#39;);
const el2 = document.querySelector(&#39;.target-second&#39;);
const el3 = document.querySelector(&#39;.target-third&#39;);

// dom의 스타일 변경 코드를 한 곳으로 모아둠
el1.style.width = &#39;10px&#39;;
el2.style.width = &#39;10px&#39;;
el3.style.width = &#39;10px&#39;;</code></pre>
</br>

<p>3)  리플로우 유발 함수의 호출을 제한하기
리플로우를 발생시키는 함수나 속성을 매번 호출하지 않고 변수에 저장하는 방법이다.</p>
<pre><code class="language-js">// 나쁜 예
for(let i=1; i&lt;10; i++){
    div.style.left = (div1.getBoundingClientRect().left + i) + &#39;px&#39;;
}

// 좋은 예
let {left} = div1.getBoundingClientRect();  // reflow 유발 메서드는 변수에 저장해 사용 
for(let i=1; i&lt;10; i++){
    div.style.left = (left + i) + &#39;px&#39;;
    left += i;
}</code></pre>
</br>

<p>4) CSS 클래스로 스타일 변경하기
스크립트로 CSS 속성을 여러번 수정하는 것은 리플로우를 여러번 유발시키게 된다.CSS 수정이 많이 필요한 경우에는 CSS 클래스를 정의해두고 사용하자</p>
<pre><code class="language-js">// BAD
el.style.width = &quot;10px&quot;;
el.style.height = &quot;10px&quot;;
el.style.borderRadius = &quot;5px&quot;;
el.style.backgroundColor = &quot;red&quot;;
el.style.left = &quot;20px&quot;;

// GOOD
&lt;body&gt;
  &lt;script&gt;
    el.className = &#39;small&#39;;
  &lt;/script&gt;
&lt;/body&gt;

&lt;style&gt;
.small {
  width: 10px;
  height: 10px;
  border-radius: 5px;
  background-color: red;
  left: 20px;
}
&lt;/style&gt;
    &lt;/script&gt;</code></pre>
<h3 id="🤔-리페인트-줄이는-방법은-뭐가-있을까요-">🤔 리페인트 줄이는 방법은 뭐가 있을까요 ?</h3>
<p>1) 가상 DOM 사용하기 ex) :React</p>
<p>*<em>2) 이미지 및 그래픽 최적화 하기 *</em></p>
<p>a. 이미지 포맷 최적화 하기
JPEG vs. PNG vs. GIF: 각 이미지 포맷은 서로 다른 사용 사례가 있다. JPEG는 사진과 같은 복잡한 이미지에 적합하며, PNG는 투명성이 필요한 이미지에, GIF는 간단한 애니메이션에 유용하다.</p>
<p><strong>3) 성능 분석 도구를 사용하여 최적화 할 수 있는 부분 식별하기</strong></p>
<p>a. 우선 [개발자도구] &gt; [성능] 탭을 연다.
b. 기록 버튼을 클릭한다.
c. 기록이 되는 동안, 요소의 글자 색상을 변경한다.
d. 기록을 중단하고 결과를 보면, 요소의 글자 색상을 변경했을 때 페인트만 발생한 걸 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/5_wintaek/post/3c74d5ed-01c9-4c58-9399-c7c9345e3405/image.png" alt=""></p>
<blockquote>
<p>만약 브라우저에서 클릭이나 스크롤 동작을 했을 때 리플로우 리페인트가 발생하는지 알고 싶다면 이렇게 해보자.</p>
</blockquote>
<p>a. [개발자도구] &gt; [렌더링] 탭을 열면 페인트, 레이아웃이 발생할 때마다 하이라이트를 할 수 있다.
<img src="https://velog.velcdn.com/images/5_wintaek/post/6c815f3c-a6e0-4c9c-b502-6c592c7fa599/image.png" alt=""></p>
<h3 id="😎-마무리">😎 마무리</h3>
<p>이로써 리플로우와 리페인트를 공부하면서 브라우저 렌더링 과정까지 알게 되는 좋은 시간을 갖게 되었다. 이론적으로는 공부를 많이 해보았으니 실제로 프로젝트에 적용하면서 몸으로 익히는 습관을 늘려야겠다는 생각을 하게된다.</p>
<blockquote>
<p>Ref 
<a href="https://mong-blog.tistory.com/entry/%EB%A6%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EB%A6%AC%ED%8E%98%EC%9D%B8%ED%8A%B8%EC%99%80-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">리플로우, 리페인트와 브라우저 렌더링 알아보기</a>
<a href="https://d2.naver.com/helloworld/59361">Naver.D2</a>
<a href="https://tecoble.techcourse.co.kr/post/2021-10-24-browser-rendering/">테코블 - 브라우저 렌더링 과정 이해하기</a>
<a href="https://kwangsunny.tistory.com/42">브라우저 렌더링 - 리플로우(reflow) 와 리페인트(repaint)</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>