<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>j15u_k7.log</title>
        <link>https://velog.io/</link>
        <description>GSM - Front-end Developer</description>
        <lastBuildDate>Thu, 02 Apr 2026 10:29:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>j15u_k7.log</title>
            <url>https://velog.velcdn.com/images/j15u_k7/profile/fda31bf5-138b-496b-920e-33a1f5e7a977/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. j15u_k7.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/j15u_k7" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[GSM 2학년 한 달 후기...]]></title>
            <link>https://velog.io/@j15u_k7/GSM-2%ED%95%99%EB%85%84-%ED%95%9C-%EB%8B%AC-%ED%9B%84%EA%B8%B0-b1zqcckz</link>
            <guid>https://velog.io/@j15u_k7/GSM-2%ED%95%99%EB%85%84-%ED%95%9C-%EB%8B%AC-%ED%9B%84%EA%B8%B0-b1zqcckz</guid>
            <pubDate>Thu, 02 Apr 2026 10:29:05 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>벌써 2026년이 시작된 지도 3개월, 개학한 지도 한 달이 지났습니다.<br>저희 학교에서는 2학년 생활이 가장 중요하기 때문에, 이 시간을 헛되이 보내고 싶지 않아 회고를 작성하게 되었습니다.</p>
<hr>
<h2 id="1주차">1주차</h2>
<p>사실 1주차까지는 다시 일상으로 돌아오는 데 집중했던 것 같습니다.<br>방학 동안 개발도 거의 하지 않고 새벽 거의 3시에 자던 생활에서, 갑자기 6시 30분에 일어나려니 쉽지 않았습니다.  </p>
<p>또한 반과 선생님이 많이 바뀌어 새로운 환경에 적응하는 데 시간이 필요했습니다.</p>
<h3 id="프론트-전공-설명">프론트 전공 설명</h3>
<p>1학년 대상 전공 발표회에서 프론트엔드 발표를 맡게 되어 급하게 PPT를 준비하고 시청각실에서 발표를 진행했습니다.</p>
<p>많은 학생들이 들어주었지만, 프론트엔드에 관심 있는 학생이 예상보다 적어 아쉬움이 남았습니다.</p>
<h3 id="전공동아리-개편">전공동아리 개편</h3>
<p>교감 선생님께서 전공동아리를 1학년 대상 멘토링 중심으로 개편하는 방향을 제안하셔서, 이를 반영하기 위해 여러 차례 회의를 진행했습니다.</p>
<p>또한 저는 <strong>VOID</strong>라는 전공동아리를 새롭게 개설하게 되었고, 팀원들과 함께 방향성과 운영 구조를 설계했습니다.</p>
<h3 id="gif-프로젝트">GIF 프로젝트</h3>
<p>VOID 동아리 개설은 작년 12월부터 계획했던 내용입니다.<br>이후 프로젝트 기획을 시작하여, 학교 아이디어 페스티벌 전 과정을 통합 관리하는 플랫폼 <strong>GIF(GSM Idea Festival)</strong>를 개발하게 되었습니다.</p>
<p>방학 동안에는 디자인과 기초 구조를 중심으로 작업했고, 개학 이후부터 본격적인 개발을 진행하고 있습니다.</p>
<hr>
<h2 id="2주차">2주차</h2>
<h3 id="전공동아리멘토링">전공동아리/멘토링</h3>
<p>전공동아리 방식이 멘토링 형식으로 바뀌면서 프론트엔드에 경우에는 1:1 형식으로 멘토링을 진행하였습니다. </p>
<p>첫 번째 활동이라서 개발환경 구축을 도와줬습니다. 
git bash에서 사용자 정보를 저장하는 과정에서 블로그에 나와있는데로 복붙을 하려고 했는데 git bash에 경우에는 Ctrl 사용이 안되어서 Insert를 사용해야 하는데 1학년은 저희와 다른 노트북인 그램을 사용하고 있는데 Insert 버튼이 없어서 하나하나 다 치던 중 오타가 나서 선배님께 도움을 요청하고 어려움이 있었습니다... </p>
<p>이 과정을 통해 개발 환경 구축 역시 직접 경험해보는 것이 중요하다는 것을 느꼈습니다.</p>
<h3 id="v-디스코드-봇">V (디스코드 봇)</h3>
<p>GIF 프로젝트에서는 매일 아침 디스코드 포럼을 통해 데일리 스크럼을 작성하고,<br>저녁에는 이모지로 완료 여부를 확인하는 방식을 사용했습니다.</p>
<p>하지만  </p>
<ul>
<li>포럼 생성 누락  </li>
<li>작업 내용 확인 부족  </li>
<li>참여율 저조  </li>
</ul>
<p>와 같은 문제가 발생했습니다.</p>
<p>이를 해결하기 위해 <strong>V(브이)</strong>라는 디스코드 봇을 개발했습니다.</p>
<p>V의 기능:</p>
<ul>
<li>오전 8시: 자동 포럼 생성  </li>
<li>오전 10시 30분: 미작성자 리마인드  </li>
<li>오후 11시: 작업 마감 알림 및 완료 체크  </li>
<li>다음날: 미완료자 기록  </li>
</ul>
<p>이를 통해 데일리 스크럼을 자동화하고 운영 효율을 개선할 수 있었습니다.</p>
<h3 id="자격증">자격증</h3>
<p>2주차 토요일에는  </p>
<ul>
<li>프로그래밍 기능사 실기  </li>
<li>리눅스 마스터 2급 실기  </li>
</ul>
<p>시험을 응시했습니다.</p>
<p>프로그래밍 기능사는 이번이 3번째 도전입니다.<br>개편 이후 첫 시험이라 준비가 확실하지 않았고 결과에 대한 확신이 없는 상태입니다...</p>
<p>리눅스 마스터 2급은 비교적 더 열심히 준비한 만큼 좋은 결과를 기대하고 있습니다.</p>
<p>두 시험 모두 최선을 다했기 때문에 좋은 결과가 있었으면 좋겠습니다.</p>
<hr>
<h2 id="3주차">3주차</h2>
<h3 id="창체동아리">창체동아리</h3>
<p>작년에 친구들과 함께 활동했던 댄스팀 <strong>오MG졸</strong>을 창체동아리로 확장하게 되었습니다.</p>
<p>처음에는 지원자가 적을까 걱정했지만 예상보다 많은 관심을 받아 정원 초과가 발생할 정도였습니다.</p>
<p>첫 활동에서는 팀원들과 친해지는 시간을 갖고 체육대회 무대를 기획했습니다.</p>
<hr>
<p>이외에 3주차에는 멘토링과 GIF 개발에 집중하며 시간을 보냈습니다.</p>
<hr>
<h2 id="4주차">4주차</h2>
<h3 id="void">VOID</h3>
<p>VOID 10기 팀원 모집을 준비했습니다.<br>일정 수립, 구글폼 및 노션 정리, 면접 질문 구성 등을 진행했습니다.</p>
<h3 id="반장">반장</h3>
<p>올해도 반장 선거에 출마했고, 1표 차이로 당선되었습니다.  </p>
<p>새로운 반이라 어색한 친구들도 많아서 기대를 크게 하지 않았지만 당선되어 기뻤고 앞으로 반장을 맡은 만큼 책임감을 가지고 봉사하려고 합니다.</p>
<hr>
<h2 id="2학년이-되고-나서-바뀐-점">2학년이 되고 나서 바뀐 점</h2>
<h3 id="후배">후배</h3>
<p>가장 크게 느낀 변화는 후배가 생겼다는 점입니다.<br>새로운 학생들이 학교에 들어오는 것도 신기했고 저에게 인사하는 모습 또한 신기했습니다.</p>
<p>처음에는 인사를 어떻게 받아야 할지 모르겠어 생각하다가 인사를 못받아줬던 적도 많습니다...ㅜㅜㅜㅜ
하지만 어느 정도 적응하여 괜찮아진 것 같습니다.</p>
<h3 id="취업">취업</h3>
<p>이제 취업이 현실적으로 다가오고 있다는 점이 가장 크게 느껴집니다.<br>남은 시간이 약 1년이라는 것이 실감 나면서 포트폴리오와 자격증 준비의 필요성을 더 크게 느끼고 있습니다.</p>
<p>특히 이미 취업에 성공한 선배를 보며 더 빠르게 성장해야겠다는 생각이 들었습니다.</p>
<h3 id="수업">수업</h3>
<p>학교 수업도 많이 바뀐 것 같습니다. 
작년까지는 컴퓨터시스템, 자료구조, C언어, 자바 등 이론을 위주로 수업을 진행했다면 2학년이 되서는 데이터베이스, 응용프로그래밍, 네트워크프로그래밍 등 실습을 위주로 하는 수업들이 많이 생겼습니다. 
아직까지 따라가기는 괜찮은데 한 번 안들었다가 못따라 가면 어쩌지 걱정이 있습니다...</p>
<hr>
<h2 id="fe-study-향후-계획">FE STUDY 향후 계획</h2>
<h3 id="블로그">블로그</h3>
<p>블로그는 2학기 말부터 자율적으로 작성하는 방식으로 변경했는데, 저는 이 방식을 유지하는 것이 좋다고 생각합니다.<br>매주 강제로 작성하게 되면 오히려 글의 품질이 떨어질 수 있고, 발표를 하더라도 얻는 것이 적을 수 있다고 느꼈습니다.<br>다만, 2주에 한 번 정도는 발표를 진행하는 것이 적절하다고 생각합니다.</p>
<h3 id="코딩-테스트">코딩 테스트</h3>
<p>코딩 테스트는 1일 1코테를 지향하고 있지만, 강제성이 없다 보니 오히려 잘 지켜지지 않는 것 같습니다.<br>따라서 요일을 정해 해당 날에는 반드시 코딩 테스트를 진행하는 방식이 더 효과적일 것이라고 생각합니다.</p>
<h3 id="포트폴리오">포트폴리오</h3>
<p>선생님들도 많이 포트폴리오 작성을 강조하고 있으시기 때문에 지금부터 포트폴리오 틀을 잡아놓으면 좋을 것 같습니다.
한 달 정도 기간을 정해 각자 포트폴리오를 작성하고, 이후 공유하며 피드백을 주고받으면 좋을 것 같습니다.</p>
<h3 id="10기">10기</h3>
<p>FE STUDY는 지금까지 6~7월에 다음 기수를 모집해왔지만, 아직은 저희가 더 성장할 필요가 있다고 생각합니다.<br>따라서 1학기 동안은 현재 인원끼리 충분히 성장한 뒤, 2학기에 모집을 진행하는 것이 좋을 것 같습니다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>이상으로 한 달 회고를 작성해 보았습니다.<br>이번 한 달은 여러 가지를 새롭게 시작한 시기였던 것 같습니다.  </p>
<p>앞으로는 이러한 변화에 잘 적응하고, 시간을 더욱 효율적으로 활용해 나가도록 하겠습니다.<br>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dark Mode FOUC/테마 종속 문제]]></title>
            <link>https://velog.io/@j15u_k7/Dark-Mode-FOUC%ED%85%8C%EB%A7%88-%EC%A2%85%EC%86%8D-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@j15u_k7/Dark-Mode-FOUC%ED%85%8C%EB%A7%88-%EC%A2%85%EC%86%8D-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 12 Mar 2026 09:32:44 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>학교에서 진행하는 Flooding 프로젝트에서 다크모드 구현을 담당하여 진행 중 여러 문제가 발생하여 해결하는 과정을 작성해 보았습니다.</p>
<h2 id="1-페이지-새로고침-시-상태-초기화">1. 페이지 새로고침 시 상태 초기화</h2>
<pre><code class="language-js">&quot;use client&quot;;

import Moon from &quot;@/shared/asset/svg/Moon&quot;;
import Sun from &quot;@/shared/asset/svg/Sun&quot;;
import { useState } from &quot;react&quot;;

export function DarkModeToggle() {
  const [isDark, setIsDark] = useState(false);

  const toggle = () =&gt; {
    setIsDark((prev) =&gt; !prev);
    document.documentElement.classList.toggle(&quot;dark&quot;);
  };

  return (
    &lt;button
      onClick={toggle}
      className={`relative w-[70px] h-[39px] rounded-[43px] transition-colors duration-300 cursor-pointer flex items-center p-1 ${
        isDark ? &quot;bg-(--background-surface)&quot; : &quot;bg-(--color-sub-3)&quot;
      }`}
    &gt;
      &lt;span
        className={`w-[31px] h-[31px] rounded-full flex items-center justify-center transition-transform duration-300 ${
          isDark ? &quot;bg-(--color-sub-1) translate-x-[30px]&quot; : &quot;translate-x-0&quot;
        }`}
      &gt;
        {isDark ? &lt;Moon /&gt; : &lt;Sun /&gt;}
      &lt;/span&gt;
    &lt;/button&gt;
  );
}</code></pre>
<p>처음 다크모드 구현시에는 이렇게 <code>useState</code>로만 상태를 관리하여 
다크 모드를 설정했더라도 페이지 이동이나 새로고침 시 라이트 모드로 돌아가는 문제가 발생했습니다.</p>
<p>그리하여 리팩토링을 통해 <code>localStorage</code>를 사용하여 사용자의 테마 설정을 저장하고, 컴포넌트가 마운트될 때 <code>useEffect</code>를 사용해 저장된 값을 불러오도록 수정하였습니다.</p>
<p>코드↓</p>
<pre><code class="language-js">import Moon from &quot;@/shared/asset/svg/Moon&quot;;
import Sun from &quot;@/shared/asset/svg/Sun&quot;;
import { useEffect, useState } from &quot;react&quot;;

export function DarkModeToggle() {
  const [isDark, setIsDark] = useState(false);

  useEffect(() =&gt; {
    const stored = localStorage.getItem(&quot;theme&quot;);
    const prefersDark = window.matchMedia(
      &quot;(prefers-color-scheme: dark)&quot;,
    ).matches;
    const shouldBeDark = stored ? stored === &quot;dark&quot; : prefersDark;

    // eslint-disable-next-line react-hooks/set-state-in-effect
    setIsDark(shouldBeDark);
    document.documentElement.classList.toggle(&quot;dark&quot;, shouldBeDark);
  }, []);

  const toggle = () =&gt; {
    const next = !isDark;
    setIsDark(next);
    document.documentElement.classList.toggle(&quot;dark&quot;, next);
    localStorage.setItem(&quot;theme&quot;, next ? &quot;dark&quot; : &quot;light&quot;);
  };

  return (
    &lt;button
      onClick={toggle}
      className={`relative w-[70px] h-[39px] rounded-[43px] transition-colors duration-300 cursor-pointer flex items-center p-1 ${
        isDark ? &quot;bg-background-surface&quot; : &quot;bg-sub-3&quot;
      }`}
    &gt;
      &lt;span
        className={`w-[31px] h-[31px] rounded-full flex items-center justify-center transition-transform duration-300 ${
          isDark ? &quot;bg-sub-1 translate-x-[30px]&quot; : &quot;translate-x-0&quot;
        }`}
      &gt;
        {isDark ? &lt;Moon /&gt; : &lt;Sun /&gt;}
      &lt;/span&gt;
    &lt;/button&gt;
  );
}</code></pre>
<h2 id="2fouc테마-종속-문제">2.FOUC/테마 종속 문제</h2>
<h3 id="21-fouc-문제">2.1 FOUC 문제</h3>
<p><code>useEffect</code>는 클라이언트 <code>hydration</code> 이후에야 실행되므로, 
그 전까지는 <code>localStorage</code>에 접근할 수 없어 테마가 적용되지 않은 상태로 먼저 렌더링됩니다. 
이후 <code>useEffect</code>가 실행되며 테마가 전환되면서 화면이 깜빡이게 됩니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/af87caae-ba75-457f-80b3-89245dd522c1/image.gif" alt=""></p>
<h3 id="22-테마-종속-문제">2.2 테마 종속 문제</h3>
<p>테마 초기화 로직이 DarkModeToggle 컴포넌트 내부에 종속되어 이 컴포넌트를 사용하지 않는 페이지에서 테마를 불러올 수 없는 문제가 발생합니다. 
로그인 페이지에는 헤더가 없으므로 테마를 적용하는 <code>useEffect</code>가 실행되지 않습니다. 결과적으로 <code>localStorage</code>에 저장된 사용자 설정을 읽지 못하고, <code>layout.tsx</code>에 하드코딩된 <code>className=&quot;dark</code>만 적용되게 됩니다.</p>
<h3 id="해결-방법">해결 방법</h3>
<p>FOUC 문제는 <code>useEffect</code>가 클라이언트에서 뒤늦게 실행되면서 <code>localStorage</code>를 읽고 <code>classList.toggle</code>을 호출할 때 이미 렌더링된 화면이 순간적으로 바뀌어 발생하였습니다. 
이를 해결하기 위해 <code>layout.tsx</code>에 인라인 스크립트를 추가하였는데
인라인 스크립트는 HTML 파싱 시점에 즉시 실행되어 <code>React hydration</code>보다 먼저 dark 클래스를 적용하므로 깜빡임이 발생하지 않습니다.
따라서 <code>useEffect</code>에서는 <code>localStorage</code>를 다시 읽는 중복 로직을 제거하고
인라인 스크립트가 적용한 DOM 상태를 <code>classList.contains(&quot;dark&quot;)</code>로 읽어와 <code>React state</code>와 동기화하도록 수정하였습니다.</p>
<pre><code class="language-js">export default function RootLayout({
  children: React.ReactNode;
}) {
  return (
    &lt;html lang=&quot;ko&quot; suppressHydrationWarning //className=&quot;dark&quot;&gt;
      &lt;body className=&quot;antialiased&quot;&gt;
        &lt;script
          dangerouslySetInnerHTML={{
            __html: `
    (function() {
      var stored = localStorage.getItem(&#39;theme&#39;);
      var prefersDark = window.matchMedia(&#39;(prefers-color-scheme: dark)&#39;).matches;
      document.documentElement.classList.toggle(&#39;dark&#39;, stored ? stored === &#39;dark&#39; : prefersDark);
    })();
  `,
          }}
        /&gt;
        &lt;Providers&gt;{children}&lt;/Providers&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<pre><code class="language-js">import Moon from &quot;@/shared/asset/svg/Moon&quot;;
import Sun from &quot;@/shared/asset/svg/Sun&quot;;
import { useEffect, useState } from &quot;react&quot;;

export function DarkModeToggle() {
  const [isDark, setIsDark] = useState&lt;boolean | null&gt;(null);

  useEffect(() =&gt; {
    const stored = localStorage.getItem(&quot;theme&quot;);
    const prefersDark = window.matchMedia(
      &quot;(prefers-color-scheme: dark)&quot;,
    ).matches;
    const shouldBeDark = stored ? stored === &quot;dark&quot; : prefersDark;

    // eslint-disable-next-line react-hooks/set-state-in-effect
    setIsDark(document.documentElement.classList.contains(&quot;dark&quot;));
  }, []);

  const toggle = () =&gt; {
    if (isDark === null) return;
    const next = !isDark;
    setIsDark(next);
    document.documentElement.classList.toggle(&quot;dark&quot;, next);
    localStorage.setItem(&quot;theme&quot;, next ? &quot;dark&quot; : &quot;light&quot;);
  };

  if (isDark === null) {
    return &lt;div className=&quot;w-[70px] h-[39px]&quot; /&gt;;
  }

  return (
    &lt;button
      onClick={toggle}
      className={`relative w-[70px] h-[39px] rounded-[43px] transition-colors duration-300 cursor-pointer flex items-center p-1 ${
        isDark ? &quot;bg-background-surface&quot; : &quot;bg-sub-3&quot;
      }`}
    &gt;
      &lt;span
        className={`w-[31px] h-[31px] rounded-full flex items-center justify-center transition-transform duration-300 ${
          isDark ? &quot;bg-sub-1 translate-x-[30px]&quot; : &quot;translate-x-0&quot;
        }`}
      &gt;
        {isDark ? &lt;Moon /&gt; : &lt;Sun /&gt;}
      &lt;/span&gt;
    &lt;/button&gt;
  );
}</code></pre>
<h2 id="마무리">마무리</h2>
<p>이렇게 첫 Darkmode를 구현해 보았습니다.
단순히 상태 관리만으로 구현했다가 FOUC, 테마 종속 문제 등 여러 문제를 마주하게 되었고 이를 해결하는 과정에서 SSR 환경에서의 렌더링 흐름과 hydration에 대해 더 깊이 이해할 수 있었습니다. 
앞으로도 이런 경험들을 쌓아가며 성장해 나가겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025년 회고]]></title>
            <link>https://velog.io/@j15u_k7/2025%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@j15u_k7/2025%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 17 Jan 2026 16:36:06 GMT</pubDate>
            <description><![CDATA[<p>벌써 2026년이 다가왔습니다!
지난 1년을 돌아보며 2025년 회고를 작성해 보았습니다.</p>
<h2 id="마이스터고에-지원했던-배경과-동기">마이스터고에 지원했던 배경과 동기</h2>
<p>저는 현재 광주소프트웨어마이스터고 1학년에 재학 중입니다.
벌써 학교에 입학한 지도 어느덧 1년이 지났습니다.</p>
<p>솔직히 말하면, 학교에 지원했던 계기는 그리 깊지 않았습니다.
친구가 지원한다 해서 함께 알아보게 되었고 집과 가깝다는 이유로 지원하게 되었습니다.
당시 공부를 좋아하지 않았던 저에게는 &quot;공부 부담이 적은 학교&quot;라는 인식이 크게 작용했던 것 같습니다.</p>
<p>지금 돌아보면 학교에 대해 제대로 알아보지 않은 채 다소 책임감 없이 지원했던 선택이었습니다.
하지만 입학 이후 학교 생활을 하며 전공을 접하고 직접 공부하고 개발을 경험하면서 개발이라는 분야에 흥미를 느끼게 되었고 점점 목표가 생기게 되었습니다.</p>
<p>그 결과 지난 1년 동안 학교 생활에 꽤 열심히 임했던 시간이었다고 느낍니다.</p>
<h2 id="1년-동안의-학교-생활">1년 동안의 학교 생활</h2>
<p>1학년 동안 가장 기억에 남는 활동은 25-26 회장단 선거에 출마해 부회장으로 당선되어 학생회 활동을 했던 경험입니다.
이전에도 회장단 활동을 해본 적은 있었지만 이처럼 다양한 활동에 적극적으로 참여한 것은 처음이었습니다. </p>
<p>처음에는 선배님들께 질문하는 것조차 어려웠지만 활동을 이어가며 오히려 학교 생활에 더 적응할 수 있게 해준 원동력이 되어줬던 것 같습니다.</p>
<p>또한 2학기에는 반장 선거에 출마해 반장으로 활동하게 되었습니다.
출마 과정에서 담임 선생님과 많은 이야기를 나누었는데 이미 맡고 있는 활동이 많아 반 관리에 소홀해질 수 있다는 우려가 있으셨습니다.
그럼에도 도전하지 않으면 후회가 남을 것 같아 출마를 결심했고 당선 이후에는 그 걱정에 보답하고 싶다는 마음으로 임했습니다.</p>
<p>하지만 약 5개월 동안 반장 역할을 수행하며 아쉬움도 많이 남았습니다.
지금 돌아보면 혼자 모든 일을 감당하려는 경향이 강했고 부반장이나 친구들에게 충분히 도움을 요청하지 못했던 것이 가장 큰 문제였던 것 같습니다.
그 결과 일의 완성도가 떨어지거나 마무리가 아쉬웠던 순간들이 있었습니다.</p>
<p>이 경험을 통해 책임감은 혼자 짊어지는 것이 아니라 역할을 나누고 소통하는 것이라는 점을 배우게 되었습니다.</p>
<p>이 외에도 프론트엔드 스터디 활동을 하며 선배님들과의 교류가 기억에 남습니다.
평소에는 선배님들과 직접적으로 소통할 수 있던 기회가 많지 않았습니다.
스터디에 면접을 보고 참여하며 선배님들과 소통하고 전공에 대해 이야기할 수 있었고,
새로운 기술과 라이브러리에 대해 배우는 동시에 공부한 내용을 글로 정리하는 경험을 통해 성장할 수 있었습니다.
이를 통해 단순히 배우는 것에 그치지 않고, 정리하고 공유하는 과정의 중요성을 느끼게 되었습니다.</p>
<h2 id="어떠한-개발자가-되고-싶은가">어떠한 개발자가 되고 싶은가</h2>
<p>저는 책임감 있는 개발자가 되고 싶습니다.
말과 행동에 책임을 지고 함께 일하는 사람들이 신뢰할 수 있는 개발자가 되고 싶습니다.</p>
<p>지난 1년 동안의 경험을 돌아보면 생각 없이 말하거나 행동했던 순간들도 있었고 그로 인해 아쉬움이 남았던 적도 많았습니다.
앞으로는 이러한 부분을 의식적으로 돌아보며 행동 하나하나에 책임을 질 수 있는 개발자로 성장하고 싶습니다.</p>
<h2 id="나의-개발-공부-방식">나의 개발 공부 방식</h2>
<p>지난 1년 동안 저는 직접 해보며 배우는 것을 중심으로 개발 공부를 해왔습니다. </p>
<p>그러던 중 한 선배님께서 개발과 전공 공부를 분리해서 해야 한다는 조언을 해주셨고 그 말을 계기로 제 공부 방식을 다시 돌아보게 되었습니다.
프로젝트에 필요한 부분만 공부하다 보니 새로운 지식을 체계적으로 쌓지 못했고 이론에 점점 약해지고 있다는 것을 깨달았습니다.</p>
<p>이 방식이 잘못 되었던 것은 아니지만 저에게는 맞지 않았던 방식이었던 것 같습니다.
그래서 최근에는 하루에 개발 시간과 전공 공부 시간을 나누어 활용하려고 노력하고 있습니다.</p>
<h2 id="프로젝트">프로젝트</h2>
<h3 id="gsmc">GSMC</h3>
<p>8월에 합류하여 참여하게 된 프로젝트로 GSM 인증제(학생 역량 평가 제도)를 웹으로 디지털화한 통합 플랫폼입니다.
이 프로젝트는 제가 본격적으로 참여한 첫 프로젝트에 가까웠고 그만큼 많은 것을 배우게 된 경험이었습니다.</p>
<p>Next.js, TanStack Query, Tailwind CSS 등 대부분의 기술을 이 프로젝트를 통해 처음 접했고 처음에는 개발 과정에서 많은 어려움을 겪었습니다.
하지만 선배님들께 질문하고 직접 찾아보며 하나씩 해결해 나가는 과정에서 기술적인 성장뿐 아니라 협업의 방식에 대해서도 배울 수 있었습니다.</p>
<h3 id="whis">Whis</h3>
<p>Whis는 아이디어 패스티벌 프로젝트로 GSM 학생들이 자유롭게 글을 작성하고 의견을 나눌 수 있는 커뮤니티 플랫폼입니다.
이 프로젝트에서는 팀장을 맡아 회의 주도와 의견 조율 등 팀 운영 전반에 적극적으로 참여했습니다.</p>
<p>모두가 처음 하는 프로젝트였던 만큼 시행착오와 갈등도 많았고 의견차이로 말다툼이 생기기도 했습니다.
하지만 그 과정 속에서 문서화, 일정 관리, 데드라인의 중요성을 깨닫게 되었습니다.
이 경험은 이후 프로젝트를 바라보는 제 태도를 바꾸는 계기가 되었습니다.</p>
<p>이 외에도 여러 프로젝트를 진행했지만 끝까지 마무리하지 못한 경우가 많았습니다. 
비록 완성도는 부족했지만 각 프로젝트를 통해 배운 점은 분명히 있었고, 아쉬움이 남는 만큼 방학 기간을 활용해 다시 이어서 진행해 볼 계획입니다.</p>
<h2 id="2026-목표">2026 목표</h2>
<ul>
<li>AI 의존도 낮추기
2025년에는 프로젝트를 진행하며 AI에 많이 의존했던 것 같습니다.
2026년에는 스스로 고민하고 작성한 코드의 비중을 늘려 저만의 코드 스타일을 만들어 가고 싶습니다.</li>
<li>문서화 습관 들이기
저는 지금까지 문서화를 어떤 방식으로 해야할지 어느 부분을 적어야 할지 모르겠고 문서화에 중요성조차 잘 알지 못했습니다.
하지만 Whis 프로젝트를 통해 문서화 부족이 팀 전체에 영향을 줄 수 있다는 것을 느껴 2026년에는 문서화를 꼼꼼히 기록하는 습관을 만들고 싶습니다.</li>
<li>전공 공부 시간을 넓히기
개발과 전공 공부 시간을 나눠 하루 일정 속에서 전공 공부의 비중을 점차 늘려가고 싶습니다.</li>
<li>더 많은 목표를 가지기
지난 1년 동안은 왜 개발을 하는지에 대한 고민이 많았습니다.
하지만 학기 초반으로 돌아가 왜 제가 개발에 흥미를 느꼈는지 다시 한 번 생각해보며 더 구체적인 목표를 세우며 성장해 나가고 싶습니다.</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>이렇게 2025년 회고를 진행해봤습니다.
약 2주 동안 쓰고 고치며 지난 1년을 정리하는 시간이 되었고 그만큼 많은 생각을 하게 되었습니다.</p>
<p>아직 부족한 점이 많지만 2026년에는 더 책임감 있고 성장한 제가 되기를 바라며 회고를 마칩니다.</p>
<p>읽어주셔서 감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ESLint]]></title>
            <link>https://velog.io/@j15u_k7/ESLint</link>
            <guid>https://velog.io/@j15u_k7/ESLint</guid>
            <pubDate>Wed, 19 Nov 2025 09:25:55 GMT</pubDate>
            <description><![CDATA[<h1 id="eslint-란">ESLint 란?</h1>
<p>ESLint는 ECMAScript/JavaScript와 Lint가 합쳐진 단어로 Lint는 코드에서 문제점, 버그, 스타일 오류를 찾아주는 도구이다.</p>
<h2 id="왜-써야하는가">왜 써야하는가??</h2>
<p>그러면 이런 버그나 문제점을 그냥 수정하면 되는데 왜? ESLint를 사용하는 걸까?</p>
<p><strong>1. 팀 프로젝트에서 각자 코드 스타일이 다르다면?</strong>
팀원들 모두 코드 스타일이 완벽하게 같을 수는 없다.</p>
<p>그러면 어떤 파일은 <code>&quot;</code>, 어떤 파일은 <code>&#39;</code>, 또 어떤 파일은 <code>;</code> . . .
이렇게 파일마다 각자 다른 스타일을 유지한다면 코드가 혼란스럽고 코드 리뷰가 어렵고 생산성을 하락하게 한다.</p>
<p>ESLint가 이런걸 통일 시켜준다.</p>
<p><strong>2. 실수를 미리 잡아줌!</strong></p>
<pre><code class="language-js">const num = 10;
console.log(numm);</code></pre>
<p>예를 들어 이렇게 num을 numm으로 쓰는 타이핑 실수들을 ESLint가 잡아준다.</p>
<p><strong>3. React에서 흔한 실수들도 잡아줌</strong></p>
<ul>
<li>useEffect dependency 실수</li>
<li>props를 잘못 넘기는 경우</li>
<li>변수를 선언만 하고 사용하지 않는 경우</li>
<li>unused import</li>
<li>console.log 제거</li>
</ul>
<p>여기서 React에서 흔한 실수들을 잡아준다고 했다.
그러면 Next.js에서는 이러한 실수들을 잡아주지 않는걸까?</p>
<p>전혀 그렇지 않다!!❌❌</p>
<p><strong>Next.js에는 ESLint가 기본 내장되어 있다.</strong></p>
<h3 id="nextjs-eslint-기본-내장">Next.js ESLint 기본 내장</h3>
<p>React는 ESLint를 직접 설치해야 하지만 Next.js는 프로젝트를 생성하면 자동으로 ESLint 설정이 붙어있다.</p>
<p>Next.js는 Next.js 전용 ESLint 플러그인을 제공하여 React보다 더 많은 규칙을 추가로 검사한다</p>
<p><strong>1. 이미지 최적화 오류</strong></p>
<pre><code class="language-html">&lt;img src=&quot;kimjiyu.png&quot; /&gt;</code></pre>
<p>Next.js에서는 img 태그를 사용하게 되면 성능이 떨어진다고 판단해 Image 컴포넌트를 사용하라고 경고를 표시한다.</p>
<p><strong>2. Link 컴포넌트 사용 실수</strong></p>
<pre><code class="language-html">&lt;a href=&quot;/jiyu&quot;&gt;kim&lt;/a&gt;</code></pre>
<p>a 태그 사용시 Link 태그를 사용하라고 표시한다.</p>
<p>이외에도 Next.js 사용시 ESLint는 렌더링, 성능 등 더 많은 것을 검사하게 된다.</p>
<h2 id="동작-방식">동작 방식</h2>
<p>그렇다면 ESLint는 이런 검사를 어떤 방식으로 진행하게 될까?</p>
<p>ESLint는 코드를 <strong>AST(Abstract Syntax Tree)</strong>로 변환한다.
여기서 AST는 추상 구문 트리로 코드를 트리 구조로 변환한 것 이다.</p>
<p>-&gt; 즉 사람이 쓴 코드 문장을 컴퓨터가 이해할 수 있는 형태로 구조화한 것 이다.</p>
<p>예를 들어 코드가 이렇게 있다.</p>
<pre><code class="language-js">const a = 10 + 20;</code></pre>
<p>이 한 줄 코드를 AST로 바꾸면
기본 구조는 이런 트리 형태로 변한다.</p>
<pre><code class="language-scss">Program
 └─ VariableDeclaration (const)
      └─ VariableDeclarator
           ├─ Identifier (a)
           └─ BinaryExpression (+)
                ├─ Literal (10)
                └─ Literal (20)</code></pre>
<p>이렇게 노드들도 쪼개진 구조가 된다.
그리고 각 규칙들이 이런 AST 구조를 보고 문제가 있고 스타일이 다르다 이런 식으로 검사한다.</p>
<h2 id="설치">설치</h2>
<p>ESLint에 설치 방법으로는</p>
<pre><code class="language-bash">npm init @eslint/config@latest</code></pre>
<p>명령어로 설치한 후</p>
<p>프로젝트 루트에는 <code>.eslintrc.json</code> 파일이 생긴다.</p>
<pre><code class="language-json">{
  &quot;env&quot;: {
    &quot;browser&quot;: true,
    &quot;es2021&quot;: true
  },
  &quot;extends&quot;: [
    &quot;eslint:recommended&quot;,
    &quot;plugin:react/recommended&quot;
  ],
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: 12,
    &quot;sourceType&quot;: &quot;module&quot;
  },
  &quot;plugins&quot;: [&quot;react&quot;],
  &quot;rules&quot;: {
  }
}</code></pre>
<p>원하면 여기서 규칙을 추가하면 된다.</p>
<p>예를 Unused import 경고를 하려면</p>
<pre><code class="language-json">&quot;rules&quot;: {
  &quot;no-unused-vars&quot;: &quot;warn&quot;
}</code></pre>
<p>코드를 추가하면 된다.</p>
<h2 id="prettier와의-차이">Prettier와의 차이</h2>
<p>그러면 ESLint를 언급하면 자주 나오는 Prettier는 무엇이고 ESLint와의 차이점은 무엇일까?</p>
<p>Prettier는 포멧터이고
ESLint는 분석기이다.</p>
<p>둘은 목적 자체가 완전히 다르다.</p>
<h3 id="prettier가-하는-일">prettier가 하는 일</h3>
<p>그러면 prettier는 뭘 하는 것일까?</p>
<p><strong>1. 개행-들여쓰기 자동정리
2. 세미콜론 여부
3. 따옴표 통일
4. 코드 포맷팅 자동 통일</strong></p>
<p>등이 있다.</p>
<p>-&gt; 즉, <strong>예쁘고 일관된 코드 스타일을 유지</strong>하기 위해 사용한다.</p>
<p>ESLint는 내가 정한 규칙을 통해 버그나 규칙에 어긋나는 것을 찾아 경고 해준다.</p>
<p>즉, 코드를 올바르게 만드는 게 목적이다.</p>
<p>Prettier는 &#39;들여쓰기, 줄바꿈, 따옴표, 세미콜론&#39; 같은 스타일만 관리하며 개발자가 규칙을 만들 필요 없고 저장할 때 자동으로 전체 코드를 포맷팅 해준다.</p>
<p>즉, 코드를 예쁘게 만드는 게 목적이다.</p>
<p>정리하자면 </p>
<ul>
<li><strong>ESLint = 버그 방지 + 팀 규칙 강제</strong></li>
<li><strong>Prettier = 코드 스타일 자동 정리</strong></li>
</ul>
<p>라고 말할 수 있다.
그래서 대부분은 ESLint와 Prettier 동시에 사용하고 충돌 나는 규칙은 eslint-config-prettier 제거한다.</p>
<p>결국 ESLint와 Prettier는 서로의 역할이 다르기 때문에 함께 사용할 때 가장 강력해진다.
버그 없는 코드를 만들고 싶다면 ESLint를, 일관된 스타일로 정리된 코드를 원한다면 Prettier를 적용하면 된다.</p>
<h2 id="마무리">마무리</h2>
<p>지금까지 ESLint에 대해 알아보았다!</p>
<p>ESLint는 작은 실수까지 잡아주며 코드 품질을 지켜주는 필수 도구이다.
프로젝트를 진행한다면 설정해두면 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비선형 자료구조!]]></title>
            <link>https://velog.io/@j15u_k7/%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@j15u_k7/%EB%B9%84%EC%84%A0%ED%98%95-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Mon, 10 Nov 2025 17:05:08 GMT</pubDate>
            <description><![CDATA[<h3 id="목적">목적</h3>
<p>학교 자료구조 과목 수행으로 비선형 자료구조에 관해 블로그를 작성해야 되서 알아보게 되었다.</p>
<h3 id="비선형-자료구조란">비선형 자료구조란?</h3>
<p>비선형 자료구조는 데이터가 일렬로 나열되지 않고, 자료 간의 복잡한 관계를 표현할 수 있는 구조이다.
하나의 자료가 여러 자료와 연결될 수 있으며 대표적으로 트리와 그래프가 있다.</p>
<p>이 중 먼저 트리에 대해 알아볼 것이다.</p>
<h1 id="트리">트리</h1>
<p>트리 (Tree)란 노드들이 나무 가지처럼 연결된 비선형 계층적 자료구조이다.</p>
<p>트리는 또한 트리 내에 다른 하위 트리가 있고 그 하위 트리 안에는 또 다른 하위 트리가 있는 재귀적 자료구조이기도 한다.</p>
<h2 id="트리의-용어">트리의 용어</h2>
<p>트리의 용어로는</p>
<ul>
<li>노드(node) : 트리의 구성요소</li>
<li>루트(root) : 부모가 없는 노드</li>
<li>서브트리(subtree) : 하나의 노드와 그 노드들의 자손들로 이루어진 트리</li>
<li>단말노드 : 자식이 없는 노드</li>
<li>비단말노드 : 적어도 하나의 자식을 가지는 노드</li>
<li>레벨(level) : 트리의 각층의 번호</li>
<li>높이(height) : 트리의 최대 레벨</li>
<li>차수(degree) : 노드가 가지고 있는 자식 노드의 개수</li>
</ul>
<h2 id="트리의-종류">트리의 종류</h2>
<h3 id="1-이진트리">1) 이진트리</h3>
<p>모든 노드가 2개의 서브트리를 가지고 있는 트리이다.</p>
<ul>
<li>이진 트리의 노드에는 최대 2개까지의 자식 노드가 존재한다.</li>
<li>모든 노드의 차수가 2 이하가 된다.</li>
<li>이진 트리에는 서브 트리간의 순서가 존재한다.</li>
</ul>
<p><strong>이진트리의 분류</strong></p>
<ul>
<li>포화 이진트리 : 꽉찬 이진트리
<img src="https://velog.velcdn.com/images/j15u_k7/post/4a385848-1ce3-4216-a57d-52ee7c1abacd/image.png" alt=""></li>
<li>완전 이진트리 : 레벨 1부터 k-1까지는 노드가 모두 채워져 있고 마지막 레벨 k에서는 왼쪽부터 오른쪽으로 노드가 순서대로 채워져 있는 이진트리
<img src="https://velog.velcdn.com/images/j15u_k7/post/aa6df826-6e08-42c7-850e-e74af177172a/image.png" alt=""></li>
<li>편향 이진 트리 : 최소 개수의 노드 개수를 가지며 왼쪽 혹은 오른 쪽의 서브 트리만을 가진 트리
<img src="https://velog.velcdn.com/images/j15u_k7/post/fdbe427e-7044-43ed-8bf6-dbb9ed64981a/image.png" alt=""></li>
</ul>
<h3 id="2-이진-탐색-트리">2) 이진 탐색 트리</h3>
<p>이진 탐색 트리는 자신보다 작은 값은 왼쪽 서브트리에, 자신보다 큰 값은 오른쪽 서브트리에 저장한다.</p>
<p>key(왼쪽 서브트리) ≤ key(루트노드) ≤ key(오른쪽 서브트리) 이다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/2a31c3b9-6727-4cb6-81ce-8f3b47b85563/image.png" alt=""></p>
<h2 id="트리-탐색-기법">트리 탐색 기법</h2>
<h3 id="깊이-우선-탐색---dfs-스택">깊이 우선 탐색 - DFS (스택)</h3>
<p>한 방향으로 갈 수 있을 때까지 가다가 더 이상 갈 수 없게 되면 돌아오는 방식이다.</p>
<p><strong>1) 전위 순회</strong>
루트 -&gt; 왼쪽 서브트리 -&gt; 오른쪽 서브트리 순으로 방문한다.</p>
<pre><code class="language-c">void preorder(Node* root) {
    if (root == NULL) return; // 탐색할 노드가 없으면 빠져 나옴
    printf(&quot;%c &quot;, root-&gt;data); // 데이터 출력
    preorder(root-&gt;left); // 왼쪽 노드 탐색
    preorder(root-&gt;right); // 오른쪽 노드 탐색
}</code></pre>
<p><strong>2) 중위 순회</strong>
왼쪽 서브트리 -&gt; 루트 -&gt; 오른쪽 서브트리 순으로 방문한다.</p>
<pre><code class="language-c">void inorder(Node* root) {
    if (root == NULL) return; // 탐색할 노드가 없으면 빠져 나옴
    inorder(root-&gt;left); // 왼쪽 노드 탐색
    printf(&quot;%c &quot;, root-&gt;data); // 데이터 출력
    inorder(root-&gt;right); // 오른쪽 노드 탐색
}</code></pre>
<p><strong>3) 후위 순회</strong>
왼쪽 서브트리 -&gt; 오른쪽 서브트리 -&gt; 루트 순으로 방문한다.</p>
<pre><code class="language-c">void postorder(Node* root) {
    if (root == NULL) return; // 탐색할 노드가 없으면 빠져 나옴
    postorder(root-&gt;left); // 왼쪽 노드 탐색
    postorder(root-&gt;right); // 오른쪽 노드 탐색
    printf(&quot;%c &quot;, root-&gt;data); // 데이터 출력
}</code></pre>
<h3 id="너비-우선-탐색---bfs-큐">너비 우선 탐색 - BFS (큐)</h3>
<p>BFS는 레벨 순회와 같은 방식으로 한 레벨에 있는 노드를 모두 방문한 후 다음 레벨로 내려가 탐색을 계속하는 방식이다.</p>
<pre><code class="language-c">void bfs(int start) {
    queue[rear++] = start;      // 시작 노드를 큐에 삽입
    visited[start] = 1;         // 시작 노드 방문 표시

    while (front &lt; rear) {      // 큐가 비어있지 않으면 반복
        int node = queue[front++];  // 큐에서 노드 하나 꺼냄
        printf(&quot;%d &quot;, node);        // 꺼낸 노드 출력

        for (int i = 0; i &lt; n; i++) {           // 모든 노드를 확인
            if (graph[node][i] &amp;&amp; !visited[i]) { // 인접하고 아직 방문 안했으면
                queue[rear++] = i;               // 큐에 추가
                visited[i] = 1;                  // 방문 표시
            }
        }
    }
}</code></pre>
<h3 id="코드">코드</h3>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

// 트리 노드 구조체 정의
typedef struct Node {
    char data;
    struct Node* left;
    struct Node* right;
} Node;

// 새 노드 생성 함수
Node* createNode(char data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode-&gt;data = data;
    newNode-&gt;left = NULL;
    newNode-&gt;right = NULL;
    return newNode;
}
// 전위 순회
void preorder(Node* root) {
    if (root == NULL) return;
    printf(&quot;%c &quot;, root-&gt;data);
    preorder(root-&gt;left);
    preorder(root-&gt;right);
}

// 중위 순회
void inorder(Node* root) {
    if (root == NULL) return;
    inorder(root-&gt;left);
    printf(&quot;%c &quot;, root-&gt;data);
    inorder(root-&gt;right);
}

// 후위 순회
void postorder(Node* root) {
    if (root == NULL) return;
    postorder(root-&gt;left);
    postorder(root-&gt;right);
    printf(&quot;%c &quot;, root-&gt;data);
}

int main() {
    Node* A = createNode(&#39;A&#39;);
    Node* B = createNode(&#39;B&#39;);
    Node* C = createNode(&#39;C&#39;);
    Node* D = createNode(&#39;D&#39;);
    Node* E = createNode(&#39;E&#39;);

    A-&gt;left = B;
    A-&gt;right = C;
    B-&gt;left = D;
    B-&gt;right = E;

    printf(&quot;전위 순회 : &quot;);
    preorder(A);
    printf(&quot;\n&quot;);

    printf(&quot;중위 순회 : &quot;);
    inorder(A);
    printf(&quot;\n&quot;);

    printf(&quot;후위 순회 : &quot;);
    postorder(A);
    printf(&quot;\n&quot;);

    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/eeda2b3e-e4bf-45f0-b44a-f0ffe58cbe3c/image.png" alt=""></p>
<p>이런식으로 구현할 수 있다.</p>
<h1 id="그래프">그래프</h1>
<p>그래프란 정점과 간선으로 구성된 자료구조로, 객체 간의 복잡한 관계를 표현하고 분석하는데 사용된다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/65a1698c-9f41-48e7-ae1c-7834976b412c/image.png" alt=""></p>
<h2 id="용어">용어</h2>
<ul>
<li>정점 : 그래프를 구성하는 기본 단위로 객체나 개체를 나타낸다.</li>
<li>간선 : 두 정점을 연결하는 선으로 객체 간의 관계나 연결을 나타낸다.<h2 id="그래프의-종류">그래프의 종류</h2>
<h3 id="무방향-그래프">무방향 그래프</h3>
<img src="https://velog.velcdn.com/images/j15u_k7/post/30c2a1ff-2653-4cf5-89ff-4b4c9c9f8f28/image.png" alt="">
간선에 방향이 없는 그래프이다.
두 정점 사이가 양방향으로 연결되어 있다.</li>
<li>나타낼 때 소괄호()로 묶어 표시한다.</li>
<li>(0,1)과 (1.0)은 같은 간선이다.</li>
</ul>
<p>예시) E(G) = {(0,1),(0,2),(1,0),(2,0)}</p>
<h3 id="방향-그래프">방향 그래프</h3>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/b535ef09-d19b-4eae-ba5a-b074b0ba8b76/image.png" alt="">
간선에 방향이 있는 그래프이다.</p>
<ul>
<li>&lt;&gt; 꺽쇠로 묶어 표시한다.</li>
<li>&lt;0,1&gt;과 &lt;1,0&gt;은 다른 간선이다.</li>
</ul>
<p>예시) E(G) = {&lt;0,1&gt;,&lt;0,2&gt;,&lt;1,0&gt;,&lt;2,0&gt;}</p>
<h2 id="그래프-표현-방식">그래프 표현 방식</h2>
<h3 id="인접-행렬">인접 행렬</h3>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/12705c42-0b88-4530-9071-737453dcf07c/image.png" alt="">
인접 행렬은 두 정점의 간선의 유무를 이중 배열 형태로 저장하게 된다.</p>
<p><strong>무방향 그래프</strong>
양방향 구조로 0-1이 연결되어 있으면 1-0도 연결되어야 한다.</p>
<p><strong>방향 그래프</strong>
한 쪽 방향으로만 연결되어 있어 0-&gt;1은 연결되어 있지만 1-&gt;0은 연결되어 있지 않을 수 있다.</p>
<h3 id="인접-리스트">인접 리스트</h3>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/87a39526-9ddd-49fa-8e0e-eaf3d1ed7eb0/image.png" alt="">
인접 리스트는 두 정점의 간선의 유무를 연결 리스트로 저장한다.
어느 정점과 연결되어 있는지만 저장한다.</p>
<h2 id="그래프-탐색">그래프 탐색</h2>
<h3 id="dfs">DFS</h3>
<p>한 방향으로 갈 수 있을 때까지 간다.
갈 수 없게 되면 가장 가까운 갈림길로 돌아가는 순회 방법이다.</p>
<p>인접 행렬로 나타내면 이렇게 구현할 수 있다.</p>
<pre><code class="language-c">void dfs(int v) {
    visited[v] = 1; // 현재 노드 방문 표시
    printf(&quot;%d &quot;, v); // 방문한 노드 출력
    for (int i = 0; i &lt; MAX; i++) {
        if (graph[v][i] &amp;&amp; !visited[i]) { // i노드가 현재 노드와 연결되어 있고 방문하지 않았는지 확인
            dfs(i); // 그렇다면 깊이 들어감
        }
    }
}</code></pre>
<h3 id="bfs">BFS</h3>
<p>시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨여져 있는 정점을 나중에 방문하는 순회 방법이다.</p>
<p>인접 행렬로 나타내면 이렇게 구현할 수 있다.</p>
<pre><code class="language-c">void bfs(int start) {
    int queue[MAX];
    int front = 0, rear = 0;

    visited[start] = 1; // 시작 노드 방문 표시
    queue[rear++] = start; // 큐에 시작 노드 삽입

    while (front &lt; rear) { // 큐가 빌때까지
        int v = queue[front++]; // 큐에서 하나씩 꺼냄
        printf(&quot;%d &quot;, v); // 방문한 노드 출력
        for (int i = 0; i &lt; MAX; i++) {
            if (graph[v][i] &amp;&amp; !visited[i]) { // 연결되어 있고 방문하지 않은 노드인지 확인
                visited[i] = 1; // 방문 표시
                queue[rear++] = i; // 큐에 추가
            }
        }
    }
}
</code></pre>
<h3 id="전체-코드">전체 코드</h3>
<p>전체 코드로 구현하면 이렇게 나타낼 수 있다.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

#define MAX 5

int graph[MAX][MAX] = {
    // 0  1  2  3  4
    {0, 1, 1, 0, 0}, 
    {1, 0, 0, 1, 0},
    {1, 0, 0, 1, 1},
    {0, 1, 1, 0, 1},
    {0, 0, 1, 1, 0} 
};

int visited[MAX];

// DFS
void dfs(int v) {
    visited[v] = 1;
    printf(&quot;%d &quot;, v);
    for (int i = 0; i &lt; MAX; i++) {
        if (graph[v][i] &amp;&amp; !visited[i]) {
            dfs(i);
        }
    }
}

// BFS
void bfs(int start) {
    int queue[MAX];
    int front = 0, rear = 0;

    visited[start] = 1;
    queue[rear++] = start;

    while (front &lt; rear) {
        int v = queue[front++];
        printf(&quot;%d &quot;, v);
        for (int i = 0; i &lt; MAX; i++) {
            if (graph[v][i] &amp;&amp; !visited[i]) {
                visited[i] = 1;
                queue[rear++] = i;
            }
        }
    }
}

int main() {
    printf(&quot;DFS 탐색 순서: &quot;);
    for (int i = 0; i &lt; MAX; i++) visited[i] = 0; // 초기화
    dfs(0);
    printf(&quot;\n&quot;);

    printf(&quot;BFS 탐색 순서: &quot;);
    for (int i = 0; i &lt; MAX; i++) visited[i] = 0; // 초기화
    bfs(0);
    printf(&quot;\n&quot;);

    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/357186b7-0ad0-440e-91c0-f2ff7f5c8402/image.png" alt=""></p>
<h3 id="마무리">마무리</h3>
<p>이상으로 비선형 자료구조의 트리와, 그래프에 대해 알아보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[시간 관리하는 법]]></title>
            <link>https://velog.io/@j15u_k7/%EC%8B%9C%EA%B0%84-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@j15u_k7/%EC%8B%9C%EA%B0%84-%EA%B4%80%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Mon, 10 Nov 2025 09:29:52 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>요즘들어 시간 관리를 잘하지 못하고 있다는 생각을 했습니다.
오늘 해야할 일을 적어놓아도 그 일을 완료하지 못하고 하루를 끝내는 날이 대다수라서 스트레스를 받고 있었어서 시간 관리하는 법에 대해 찾아보아 블로그로 적어보게 되었습니다.</p>
<h1 id="시간-관리">시간 관리</h1>
<h2 id="시간-관리가-어려운-이유">시간 관리가 어려운 이유</h2>
<p>시간 관리는 누구에게나 어려운 주제입니다.
그 이유는 우리가 가진** 시간의 양은 한정되어** 있지만 해야 할 일은 그보다 훨씬 많기 때문입니다.
또한 우선순위를 제대로 정하지 않거나 계획 없이 하루를 보내다 보면 어떤 일부터 시작해야 할지 몰라 시간을 허비하기도 합니다.</p>
<p>게다가 스마트폰 알림, SNS, 유튜브 같은 디지털 요소들이 우리의 집중력을 자꾸 분산시켜 더더욱 효율적인 시간 활용을 어렵게 만듭니다.
결국 &#39;시간이 부족하다&#39;는 말은 실제로 시간이 없는게 아니라 <strong>시간을 사용하는 방법을 제대로 배우지 못했기 때문인 경우가</strong> 많다고 생각합니다.</p>
<p>그럼 시간 관리는 어떻게 해야 잘 관리할 수 있을까요?</p>
<h2 id="시간-관리-하는-법">시간 관리 하는 법</h2>
<h3 id="1-하루-계획-세우기">1. 하루 계획 세우기</h3>
<p>시간 관리를 잘 하려면 하루를 미리 계획하는 것이 중요합니다.
아침이나 전날 밤에 그 날 할 일을 정리해두면 하루가 훨씬 매끄럽게 흘러갑니다.
이 과정에서 계획을 거창하게 정할 필요 없이 간단하게 이거는 꼭 해야지 하는 정도로만 계획하는 것도 큰 도움이 됩니다.</p>
<h3 id="2-우선순위-설정하기">2. 우선순위 설정하기</h3>
<p>모든 일을 한 번에 다 하려고 하면 결국 아무 일도 제대로 끝내지 못하게 됩니다.
따라서 해야 할 일의 <strong>중요도와 긴급도를</strong> 기준으로 우선순위를 정하는 것이 좋습니다.</p>
<p>대표적인 방법으로는 <strong>&#39;아이젠하워 메트릭스&#39;</strong>가 있습니다.
이 방법은 뉴로우에서도 지향하는 방법으로 뉴로우에서 훈련도 가능합니다.</p>
<ul>
<li><strong>중요하고 긴급한 일 :</strong> 바로 해야 할 일입니다.</li>
<li>*<em>중요하지만 긴급하지 않은 일 : *</em>계획적으로 진행해야 할 일입니다.</li>
<li><strong>중요하지 않지만 긴급한 일 :</strong> 가능하면 위임하거나 최소화하는 것이 좋습니다.</li>
<li><strong>중요하지도, 긴급하지도 않은 일 :</strong> 과감히 버리는 것이 좋습니다.</li>
</ul>
<p>이렇게 분류해보면 하루 일정이 훨씬 명확해지고, 시간 낭비를 줄일 수 잇습니다.</p>
<h3 id="3-불필요한-일-거르기">3. 불필요한 일 거르기</h3>
<p>하루에 할 수 있는 일은 한정되어 있는데 이것저것 다 하려고 하면 오히려 효율이 떨어집니다.
불필요하거나 시급하지 않은 일들을 과감하게 거절하거나 계획하지 않은 것도 시간 관리의 중요한 부분입니다.</p>
<h3 id="4-멀티태스킹-지양하기">4. 멀티태스킹 지양하기</h3>
<p>한꺼번에 여러 가지 일을 처리하려는 멀티내스킹은 여러가지 일을 동시에 하다 보니 집중력이 분산되고 효율이 떨어질 수 있습니다.
그러니 한 번에 한 가지 일에만 집중하는 <strong>싱글 태스킹을</strong> 지향하는 것이 좋습니다.
한 가지 일에 몰두하면 더 빨리 끝내고 그 다음 일도 더 수월하게 할 수 있습니다.</p>
<h3 id="5-디지털-디톡스-시간-가지기">5. 디지털 디톡스 시간 가지기</h3>
<p>사실 저한테는 이 부분이 제일 중요한 부분인 것 같습니다.
해야할 일이 있지만 잠깐 핸드폰을 켰다가 SNS를 확인하거나 알림을 보다가 시간이 훅 지나가는 경우가 많은 것 같습니다.
이런 경우 하루 중 일정 시간을 디지털 디톡스 시간을 정해 스마트폰과 잠깐 멀리하면 집중력이 올라갈 수 있습니다.</p>
<h2 id="시간-관리의-이점">시간 관리의 이점</h2>
<p>그럼 이런 시간 관리를 하면 어떤 이점이 있는지 알려드리겠습니다.</p>
<h3 id="1-생산성-향상">1. 생산성 향상</h3>
<ul>
<li>계획적으로 시간을 사용하면 해야 할 일을 더 효율적으로 처리할 수 있습니다.</li>
<li>우선순위를 정해 중요한 일부터 처리하면 같은 시간에도 더 많은 일을 완수할 수 있습니다.<h3 id="2-스트레스-감소">2. 스트레스 감소</h3>
</li>
<li>일정이 체계적이면 &#39;뭐부터 해야 하지?&#39;라는 고민이 줄어듭니다.</li>
<li>미뤄두던 일로 인한 압박감이나 불안이 줄어 마음이 한결 편해집니다.<h3 id="3-자기-통제력-향상">3. 자기 통제력 향상</h3>
</li>
<li>스스로 계획을 세우고 지키는 습관이 생기면 자기 통제력이 강화됩니다.</li>
<li>꾸준함과 책임감을 기를 수 있습니다.</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>이상으로 시간 관리하는 법에 대해 알아보았습니다.
이 글을 읽은 모두가 시간 관리를 효율적으로 해서 스트레스 받지 않고 좋은 나날들만 가득했으면 좋겠습니다.
감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LF vs CRLF]]></title>
            <link>https://velog.io/@j15u_k7/LF-vs-CRLF</link>
            <guid>https://velog.io/@j15u_k7/LF-vs-CRLF</guid>
            <pubDate>Wed, 15 Oct 2025 10:39:02 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>전공동아리 프로젝트 진행 중 LF, CRLF 경고가 발생 해 처음 알게 된 개념이라 블로그에 작성하면 좋을 것 같아 작성해본다.</p>
<h1 id="lf-crlf">LF, CRLF</h1>
<h2 id="줄바꿈">줄바꿈?</h2>
<p><strong>LF, CRLF는 줄 바꿈 문자입니다.</strong>
그럼 <strong>줄 바꿈</strong>이란 무엇일까요?
텍스트 파일은 눈에 보이는 &#39;줄&#39;이 아니라, <strong>문자들의 연속된 데이터</strong>입니다.
우리가 엔터를 칠 때, 실제로는 눈에 보이지 않는 &#39;<strong>제어 문자</strong>&#39;가 삽입되어 줄이 끝났음을 표시합니다.
이걸 <strong>EOL(End Of Line)</strong> 또는 <strong>줄 바꿈 문자(Line Ending)</strong> 라고 합니다.</p>
<p>이런 줄 바꿈이 운영체제마다 문자가 다릅니다.
그래서 다른 운영체제의 줄 바꿈 문자로 작성하면 Git에서 <strong>LF/CRLF</strong> 경고가 발생하게 됩니다.
프로젝트에서는 일관된 EOL 규칙을 정하고 .gitattributes + 에디터 설정 + Git 설정으로 강제하는 것이 가장 안전합니다.</p>
<p>줄 바꿈 문자로는 LF/CRLF가 있습니다.
먼저 LF에 대해 알아보겠습니다.</p>
<h2 id="lf">LF</h2>
<p>LF는 Line Feed에 약자로 Unix, Linux, MaxOS에서 쓰는 줄 바꿈 문자로 <code>\n</code>을 사용합니다.</p>
<h3 id="lf-권장-이유">LF 권장 이유</h3>
<p>Git이나 대부분의 개발도구는 LF를 기본이자 권장 형식으로 사용 합니다.</p>
<p>그 이유는 크게 세 가지 입니다.</p>
<p><strong>1. Unix 기반 환경 표준</strong>
Git, Node.js, Docker, Python 등 요즘 사용하는 대부분의 개발 도구와 서버 환경은 Unix/Linux 기반 입니다.
이 환경에서는 LF(<code>\n</code>)가 줄바꿈의 기본 값입니다.</p>
<p>즉 Mac이나 서버 환경에서 문제없이 동작하려면 LF가 더 안전한 선택이라고 할 수 있습니다.</p>
<p><strong>2. 협업과 버전 관리에 유리</strong>
CRLF와 LF가 섞이면 Git에서 diff가 전부 바뀐 것처럼 보일 수 있습니다.
이는 코드 리뷰를 어렵게 만들고 충돌도 늘어납니다.</p>
<p>그래서 대부분의 협업 환경은 LF로 일관된 줄바꿈을 유지하는 걸 권장합니다.</p>
<p><strong>3. 자동 변환 기능이 LF 중심으로 동작</strong>
Git의 <code>core.autocrlf</code> 설정도 대부분 LF 중심입니다.
Windows에서는 CRLF를 써도 커밋 시 LF로 변환되고. 다른 환경에서 가져올 때도 LF 형태로 유지됩니다.</p>
<p>즉 Git 내부 저장소에서는 항상 LF 형태로 관리됩니다.
그래서 <code>.gitattributes</code>에서도 아래처럼 <code>eol=lf</code>로 지정하는 경우가 많습니다.</p>
<h2 id="crlf">CRLF</h2>
<p>CRLF는 Carriage Return + Line Feed에 약자로 Windos에서 쓰는 줄 바꿈 문자로 <code>\r\n</code> 을 사용합니다.</p>
<h2 id="git이-경고를-띄우는-이유">Git이 경고를 띄우는 이유</h2>
<p>Git은 파일의 줄바꿈 문자까지 포함해서 파일내용을 추적합니다.
그런데 협업 중 팀원이 서로 다른 운영체제를 쓴다면 파일의 줄바꿈이 뒤섞이게 됩니다.</p>
<p>예를 들어 
Mac을 사용하는 팀원이 LF로 커밋한 파일을 Windows를 사용하는 팀원이 수정하고 커밋하면 Git은 파일의 줄바꿈 방식이 바뀌었다고 인식합니다.</p>
<p>그러면 이런 경고가 뜨게 됩니다.</p>
<pre><code>warning: LF will be replaced by CRLF the next time Git touches it</code></pre><p>오류는 아니지만 Git이 자동으로 줄바꿈을 변환할 예정이란 의미입니다.
하지만 줄바꿈이 뒤섞이면 앞서 말한 것 처럼 diff가 엉망으로 표시되어 코드 리뷰나 병합 시 불편함이 생길 수 있습니다.</p>
<h2 id="해결-방법">해결 방법</h2>
<p>줄바꿈 문제는 크게 두 가지 방법으로 해결할 수 있습니다.</p>
<h3 id="1-git-설정-변경">1. Git 설정 변경</h3>
<p>Git에는 줄바꿈을 자동으로 변환해주는 설정이 있습니다.</p>
<table>
<thead>
<tr>
<th>환경</th>
<th>명령어</th>
<th>동작</th>
</tr>
</thead>
<tbody><tr>
<td>Windows</td>
<td><code>git config --global core.autocrlf true</code></td>
<td>커밋할 때 CRLF -&gt; LF로 변환</td>
</tr>
<tr>
<td>MacOS / Linux</td>
<td><code>git config --global core.autocrlf input</code></td>
<td>LF 그대로 유지</td>
</tr>
</tbody></table>
<p>Windows -&gt; <code>true</code>
macOS/Linux -&gt; <code>input</code>
으로 맞추면 됩니다.</p>
<p>이 설정은 로컬에서 Git이 자동으로 줄바꿈을 관리하도록 돕는 역할을 합니다.</p>
<h3 id="2-프로젝트-내부에서-통일">2. 프로젝트 내부에서 통일</h3>
<p>팀 프로젝트에서는 각자 OS 설정이 다를 수 있으므로 저장소 단위로 일관된 규칙을 강제하는 게 가장 안전합니다.</p>
<p>루트 폴더에 <code>.gitattributes</code> 파일을 만들고 다음을 작성합니다.</p>
<pre><code class="language-bash"># 모든 텍스트 파일은 LF로 저장
* text=auto eol=lf

# 바이너리 파일은 변환하지 않음
*.png binary
*.jpg binary
*.ttf binary</code></pre>
<p>이후 저장하고 커밋한 후 실행하면 기존 파일의 줄바꿈도 정리됩니다.
이렇게 되면 앞으로는 모든 팀원이 어떤 운영체제를 쓰든 자동으로 LF 형식으로 통일됩니다.</p>
<p><strong>3. 에디터 확인 가능</strong>
VSCODE 같은 에디터에서는 오른쪽 하단에 <code>CRLF</code>,<code>LF</code> 표시가 있습니다.
이걸 클릭해서 현재 파일의 줄바꿈 형식을 바로 바꿀 수 있습니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/2b38e143-3697-48dd-87a8-67ece61eca41/image.png" alt=""></p>
<h2 id="결론">결론</h2>
<p>각 운영체제마다 그에 맞는 줄바꿈 방식이 있다.
LF는 Unix, Linux, MaxOS에서 쓰는 줄 바꿈 문자로 <code>\n</code>을 사용합니다.
CRLF는 Windos에서 쓰는 줄 바꿈 문자로 <code>\r\n</code> 을 사용합니다.
LF와 CRLF는 단순히 줄바꿈 방식의 차이이지만 협업 환경에서는 생각보다 큰 영향을 미치기 때문에 통일해야 합니다.</p>
<p>이상으로 LF, CRLF를 알아보았습니다.
감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tailwind CSS는 무엇일까?]]></title>
            <link>https://velog.io/@j15u_k7/Tailwind-CSS%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@j15u_k7/Tailwind-CSS%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Mon, 13 Oct 2025 10:27:38 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>이번에 전공동아리 프로젝트에서 Tailwind CSS를 적용하여 개발하게 되어서 Tailwind CSS에 대해 자세히 알아보면 좋을 것 같아 작성하게 되었습니다.</p>
<h1 id="tailwind-css">Tailwind CSS?</h1>
<p>Tailwind CSS는 클래스가 포함된 유틸리티 중심 CSS 프레임워크로, 마크업에서 직접 모든 디자인을 구축하도록 구성할 수 있습니다.</p>
<h2 id="장점">장점</h2>
<p>Tailwind CSS의 장점으로는</p>
<p><strong>1. CSS를 작성할 때 시간이 절약된다.</strong></p>
<pre><code class="language-html">&lt;button class=&quot;btn&quot;&gt;click&lt;/button&gt;</code></pre>
<pre><code class="language-css">.btn {
  background-color: #3490dc;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  font-size: 1rem;
  text-align: center;
}</code></pre>
<p>css로 작성하면 이렇게 길게 작성하게 되는데 tailwind css로는 html 안에서 바로 css 적용이 가능하고 css 파일을 따로 만들 필요가 없어집니다.</p>
<pre><code class="language-html">&lt;button className=&quot;bg-blue-500 text-white px-4 py-2 rounded text-base text-center&quot;&gt;
  click
&lt;/button&gt;</code></pre>
<p><strong>2. 스타일 클래스의 이름을 고민할 필요가 없다.</strong></p>
<ul>
<li>기존 CSS에서는 콘텐츠와 스타일을 고려해 클래스를 만들고, 재사용 여부, 명명 규칙, 기능/외관 구분 등 많은 것을 고민해야 합니다. 이는 팀 내 일관성을 유지하려고 할수록 인지 부하가 커지고, 종종 사소한 이름 논쟁(바이크 쉐딩)으로 이어지기도 합니다.
반면 Tailwind CSS는 문서에 있는 유틸리티 클래스를 그대로 적용하면 되므로, 클래스 이름 고민이 크게 줄고 대부분 직관적으로 사용할 수 있습니다.</li>
</ul>
<p><strong>3. 반응형 레이아웃을 쉽게 구현할 수 있다.</strong></p>
<ul>
<li>Tailwind CSS는 미디어 쿼리를 이용하여 다양한 화면 크기에 맞춰 웹 페이지의 레이아웃을 동적으로 조정하는 기능을 제공합니다.
레이아웃 설정을 위해 Tailwind는 사이즈 별로 클래스를 적용할 수 있는 시스템을 제공합니다.<blockquote>
<p><strong>sm</strong> =&gt; @media (min-width: 640px) { ... } </p>
</blockquote>
</li>
<li><em>md*</em> =&gt; @media (min-width: 768px) { ... } </li>
<li><em>lg*</em> =&gt; @media (min-width: 1024px) { ... } </li>
<li><em>xl*</em> =&gt; @media (min-width: 1280px) { ... } </li>
<li><em>2xl*</em> =&gt; @media (min-width: 1536px) { ... }</li>
</ul>
<p><strong>4. 스타일 유지 관리가 쉽다.</strong></p>
<ul>
<li>Tailwind CSS는 컴포넌트의 클래스만 변경하면 스타일을 바로 수정할 수 있어 큰 CSS 파일을 찾아 수정할 필요가 없습니다.
덕분에 스타일 변경으로 인한 충돌이나 예기치 않은 영향을 최소화할 수 있습니다.</li>
</ul>
<h2 id="단점">단점</h2>
<p>Tailwind CSS의 단점으로는</p>
<p><strong>1. 빌드 단계가 필요하다.</strong></p>
<ul>
<li>Tailwind CSS를 생성하기 위해 빌드 프로세스를 거쳐야 하며, 이는 초기 설정에 약간의 오버헤드를 추가합니다.</li>
</ul>
<p><strong>2. 설치 및 설정이 필요하다.</strong></p>
<ul>
<li>Tailwind CSS를 사용하려면 프로젝트에 설치하고 빌드 환경을 설정해야 하므로, 초기 준비 과정이 번거로울 수 있습니다.</li>
</ul>
<p><strong>3. CSS 파일이 커질 수 있다.</strong></p>
<ul>
<li>Tailwind CSS는 새로운 스타일을 적용할 때마다 CSS 클래스가 생성되므로, 결과적으로 CSS 파일의 크기가 커지고 페이지 로딩 시간이 늘어날 수 있습니다.
또한 Tailwind로 구현하기 어려운 스타일을 별도의 CSS를 작성해야 해 프로젝트 내 스타일 연관성을 유지하기 어려울 수 있습니다.</li>
</ul>
<h2 id="사용법">사용법</h2>
<p><strong>tailwind CLI</strong></p>
<h3 id="1-설치">1. 설치</h3>
<pre><code>npm install -D tailwindcss
npm tailwindcss init</code></pre><p>npm을 통해 설치 <code>tailwindcss</code> 합니다.</p>
<h3 id="2-탬플릿-경로-설정하기">2. 탬플릿 경로 설정하기</h3>
<ul>
<li><code>tailwind.config.js</code> 파일 안에 있는 모든 템플릿 파일에 경로를 추가하기</li>
<li><code>tailwind.config.js</code> 파일에서는 커스텀 클래스와 색상, 폰트 등의 속성을 추가할 수 있음<pre><code class="language-js">module.exports = {
content: [&quot;./src/**/*.{html,js}&quot;],
theme: {
  extend: {},
},
plugins: [],
}</code></pre>
<h3 id="3-css에-tailwind-directive-추가">3. CSS에 Tailwind directive 추가</h3>
</li>
<li>메인 CSS 파일의 Tailwind의 모든 layer에 <code>@tailwind</code> directive를 추가한다.<pre><code class="language-js">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre>
<h3 id="4-tailwind-cli-빌드-프로세스-시작">4. Tailwind CLI 빌드 프로세스 시작</h3>
<pre><code>npx@tailwindcss/cli -i ./src/input.css -o ./src/output.css --watch</code></pre><h3 id="5-html에서-tailwind-사용-시작">5. HTML에서 Tailwind 사용 시작</h3>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;link href=&quot;./output.css&quot; rel=&quot;stylesheet&quot;&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1 className=&quot;text-3xl font-bold underline&quot;&gt;
  Hello world!
&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li>컴파일된 CSS 파일을 head에 추가하고 tailwind의 유틸리티 클래스를 사용하여 콘텐츠 스타일을 지정한다.</li>
</ul>
<h3 id="사이즈-설정">사이즈 설정</h3>
<p>Tailwind CSS를 사용하여 웹 페이지의 요소들에 길이/크기를 설정하는 것은 매우 간단합니다.</p>
<p>넓이를 설정할 때는 <code>w</code> 다음에 숫자를 넣어주면 되는데 Tailwind는 미리 정해진 숫자 값들을 제공합니다.
<code>w-1</code>, <code>w-12</code>, <code>w-64</code> 등과 같이 사용하거나 <code>w-1/2</code>, <code>w-full</code>와 같이 비율로도 설정이 가능합니다.</p>
<p>높이를 설정할 때의 방식도 비슷하다. h 다음에 숫자를 넣어서 높이를 지정하게 되는데 <code>h-16</code>, <code>h-52</code>와 같이 사용합니다.
비율로 설정하는 것도 가능하며 <code>h-full</code>과 같이 사용할 수도 있습니다.</p>
<p>또한, 화면의 최대 크기를 정할 수도 있습니다. 양 옆으로 <code>auto margin</code>을 주기 위해서는 <code>mx-auto</code>를 사용하면 됩니다.</p>
<h3 id="색상-설정">색상 설정</h3>
<p>Tailwind CSS에서 색상 설정은 
<strong>텍스트 색상 : text-{색상}-{명도}
배경 색상 : bg-{색상}-{명도}</strong> 으로 나타낼 수 있습니다.</p>
<pre><code class="language-html">&lt;p class=&quot;text-red-500&quot;&gt;빨간색 텍스트&lt;/p&gt;
&lt;div class=&quot;bg-yellow-300&quot;&gt;노란색 배경&lt;/div&gt;</code></pre>
<h3 id="디스플레이-설정">디스플레이 설정</h3>
<p>Tailwind css는 웹 페이지의 레이아웃을 구성할 때 필요한 다양한 디스플레이 설정을 제공합니다.
이러한 설정은 일반적으로 사용되는 flex, inline, inline-block, inline-flex와 같은 속성들을 포함합니다.
Tailwind css를 사용하면 이러한 디스플레이 속성을 클래스 형태로 쉽게 적용할 수 있어 레이아웃을 빠르고 효과적으로 구성할 수 있습니다.</p>
<h3 id="레이아웃-설정">레이아웃 설정</h3>
<p>레이아웃 설정은 장점에서 말했던 것처럼 미디어 쿼리(<code>sm</code>, <code>md</code>, <code>lg</code>, <code>xl</code>, <code>2xl</code>)를 이용하여 설정할 수 있습니다.</p>
<h3 id="여백-설정">여백 설정</h3>
<p>p(패딩) m(마진)을 사용하여 요소들 사이의 여백 공간을 조절합니다. 
이러한 여백 설정은 p-숫자 또는 m-숫자 형식으로 적용되며 이를 통해 웹 페이지의 요소들 간에 적절한 공간을 생성하고 관리할 수 있습니다.</p>
<p>예를 들어, p-3은 요소 전체 방향에 0.75rem의 간격으로 패딩을 추가합니다.
가로축과 세로축 방향의 여백을 조절하고 싶다면, px-3 또는 py-3을 사용하여 쉽게 적용할 수 있습니다.</p>
<h3 id="폰트-설정">폰트 설정</h3>
<p>Tailwind CSS는 폰트 패밀리, 크기, 무게를 쉽게 설정할 수 있어 디자인에 명확성과 다양성을 더할 수 있습니다.</p>
<blockquote>
<p>폰트 패밀리 : font-sans, font-serif, font-mono 등
폰트 크기 : text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl 등
폰트 무게 : font-thin, font-light, font-normal, font-semibold, font-bold</p>
</blockquote>
<h3 id="상태-변화-설정">상태 변화 설정</h3>
<p>Tailwind CSS는 마우스 오버 효과와 같은 상태 변화를 쉽게 구현할 수 있는 기능을 제공합니다. 
이 기능은 hover:접두사를 사용하여 적용할 수 있으며 요소에 마우스를 올렸을 때의 시각적 변화를 정의할 수 있습니다.</p>
<p>예를 들어, hover:bg-blue-500 클래스를 버튼에 적용하면 사용자가 버튼 위에 마우스를 올렸을 때 배경색이 파란색으로 변경됩니다.</p>
<h2 id="마무리">마무리</h2>
<p>여기까지 Tailwind CSS에 대해 알아보았습니다!
이 글을 읽고 더욱 효율적이게 tailwind를 사용하면 좋겠습니다.
감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠키 세션 토큰!]]></title>
            <link>https://velog.io/@j15u_k7/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0</link>
            <guid>https://velog.io/@j15u_k7/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0</guid>
            <pubDate>Sun, 28 Sep 2025 05:32:11 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>학교에서 3학년 취업자 선배님들께 배우는 도제 교육에서 쿠키, 세션, 토큰에 대해 간단히 설명을 들었는데 자세히 알아보고 공부하면 좋을 것 같아 이렇게 블로그로 작성하게 되었습니다.</p>
<p>먼저 쿠키에 대해 알아보도록 하겠습니다.</p>
<h1 id="쿠키">쿠키</h1>
<p>쿠키는 공개 가능한 정보를 사용자의 브라우저에 저장시킵니다.</p>
<h2 id="쿠키-동작-방식">쿠키 동작 방식</h2>
<ol>
<li>브라우저가 서버에 요청을 보낸다.</li>
<li>서버는 클라이언트의 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담는다.</li>
<li>이후 해당 클라이언트는 요청을 보낼 때, 저장된 쿠키를 요청 헤더의 Cookie에 담아 보낸다.</li>
</ol>
<h2 id="쿠키-특징">쿠키 특징</h2>
<ul>
<li>쿠키의 속성에 따라 쿠키가 언제 어느 사이트로 전송되는지 다릅니다.</li>
<li>쿠키는 보약에 취약합니다. 탈취해서 클라이언트인 척 할 수 있습니다.</li>
<li>크롬, 사파리 같은 브라우제에는 쿠키를 관리하기 위한 전용 메모리가 있습니다. 보통 확인할 수 없게 되어있지만, 개발자 도구-&gt;application-&gt;storage-&gt;cookies 항목에 들어가면 현재 사이트의 쿠키를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/2a33adfd-b6f1-4a9d-a9c1-652453d1ed2c/image.png" alt=""><h2 id="쿠키의-종류">쿠키의 종류</h2>
쿠키는 만료시간 세팅 여부에 따라 지속 쿠키, 세션 쿠키로 나뉘어집니다.</li>
<li>지속 쿠키<ul>
<li>브라우저를 꺼도 만료기간까지 남아있습니다. Max-Age, Expires 속성을 사용해서 만료 기간을 정할 수 있습니다.</li>
</ul>
</li>
<li>세션 쿠키<ul>
<li>브라우저를 끄면 없어집니다.</li>
</ul>
</li>
</ul>
<h1 id="세션">세션</h1>
<p>세션은 기본적으로 서버의 메모리의 존재합니다. 스프링 어플리케이션의 경우 세션은 톰캣이 점유하는 메모리상에 있습니다.</p>
<h2 id="세션-동작-방식">세션 동작 방식</h2>
<ol>
<li>클라이언트가 로그인 요청을 보냅니다.</li>
<li>서버는 세션을 생성하고 고유한 세션 ID를 발급합니다.</li>
<li>서버는 세션 ID를 쿠키(set-cookie)로 클라이언트에 전달합니다.</li>
<li>이후 클라이언트가 요청할 때 쿠키에 담긴 세션 ID를 전송합니다.</li>
<li>서버는 세션 ID로 사용자 상태를 조회해 인증을 처리합니다.<h2 id="세션의-특징">세션의 특징</h2>
</li>
</ol>
<ul>
<li>서버 중심 관리<ul>
<li>사용자 상태(로그인 여부, 장바구니, 권한 등)를 서버에서 직접 관리합니다.</li>
</ul>
</li>
<li>클라이언트에는 최소한의 정보만 저장<ul>
<li>세션 ID만 쿠키로 저장되고, 실제 데이터는 서버가 들고 있으므로 보안에 유리합니다.</li>
</ul>
</li>
<li>상태 유지가 용이<ul>
<li>서버가 세션에 데이터를 자유롭게 넣고 뺄 수 있어, 로그인/로그아웃, 장바구니, 최근 본 상품 등 상태 기반 기능 구현이 쉽습니다.</li>
</ul>
</li>
<li>만료와 무효화가 간단<ul>
<li>서버가 세션을 지워버리면 즉시 무효화됩니다. (토큰처럼 강제 무효화가 어려운 문제가 없습니다.)</li>
</ul>
</li>
<li>프레임워크/서버 지원이 풍부<ul>
<li>스프링, Django, Express 등 대부분의 웹 프레임워크에서 기본적으로 세션 관리 기능을 제공합니다.<h2 id="세션--쿠키-인증-방식">세션 + 쿠키 인증 방식</h2>
</li>
</ul>
</li>
<li>서버가 세션 ID를 생성하고, 클라이언트에 쿠키 형태로 전달합니다.</li>
<li>클라이언트는 이후 요청마다 세션 ID 쿠키를 전송합니다.</li>
<li>서버는 세션 ID를 기반으로 사용자 정보를 확인합니다.<h2 id="세션의-장단점">세션의 장단점</h2>
<h3 id="장점">장점</h3>
</li>
<li>보안성이 높다<ul>
<li>쿠키처럼 사용자 브라우저에 중요힌 데이터를 직접 저장하지 않고, 서버에서 관리하니까 탈취 위험이 상대적으로 적습니다.</li>
</ul>
</li>
<li>유연한 관리
  -서버가 세션을 들고 있으니까 언제든지 개별 사용자 세션을 강제로 만료시킬 수 있습니다.
  -쿠키/토큰은 클라이언트 쪽에서 지우지 않으면 계속 유효할 수 있는데, 세션은 서버 마음대로 통제가 가능하다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>서버 메모리/저장소 부담이 큽니다.</li>
<li>서버 확장 시 세션 공유/동기화 문제 발생.<h1 id="토큰">토큰</h1>
토큰은 사용자 인증 정보를 담은 문자열 입니다.
서버가 별도 상태를 저장하지 않아도, 클라이언트가 들고 있는 토큰만 검증해서 인증할 수 있습니다.<h2 id="토큰-동작-방식">토큰 동작 방식</h2>
</li>
</ul>
<ol>
<li>클라이언트가 로그인 요청을 보낸다.</li>
<li>서버는 사용자 정보를 확인하고, 해당 정보를 바탕으로 토큰을 발급한다.</li>
<li>클라이언트는 토큰을 로컬 스토리지/세션 스토리지/쿠키 등에 저장한다.</li>
<li>이후 요청 시 클라이언트는 토큰을 <code>Authorization: Bearer &lt;토큰&gt;</code> 헤더에 담아 전송한다.</li>
<li>서버는 토큰을 검증하고, 유효하다면 요청을 처리합니다.<h2 id="토큰의-특징">토큰의 특징</h2>
</li>
</ol>
<ul>
<li>서버에 별도의 상태 저장소가 필요 없습니다.</li>
<li>토큰 자체에 사용자 정보, 만료 시간 등을 포함합니다.</li>
<li>주로 JWT(JSON Web Token) 형식을 사용합니다.</li>
<li>서버는 토큰이 변조되지 않았는지만 확인하면 됩니다.</li>
<li>유효기간이 지나면 토큰이 무효화 됩니다.<h2 id="jwt-구조">JWT 구조</h2>
<pre><code class="language-scss">헤더(header).페이로드(Payload).서명(Signature)</code></pre>
</li>
<li><strong>헤더</strong> : 토큰의 타입(JWT), 알고리즘 정보(HS256 등).</li>
<li><strong>페이로드</strong> : 사용자 정보, 만료기간, 발급자 등.</li>
<li><strong>서명</strong> : 비밀키로 서명해 변조 여부 확인.<h2 id="토큰의-종류">토큰의 종류</h2>
<h3 id="액세스-토큰">액세스 토큰</h3>
</li>
<li>로그인 성공 시 발급되는 기본 토큰</li>
<li>보통 짧은 유효 기간(15분~1시간)</li>
<li>서버 API 요청 시 인증 수단으로 사용됩니다.</li>
<li>짧게 두는 이유 : 탈취되더라도 피해를 줄이기 위함.<h3 id="리프레시-토큰">리프레시 토큰</h3>
</li>
<li>액세스 토큰이 만료되었을 때, 새로운 엑세스 토큰을 발급받기 위한 토큰</li>
<li>상대적으로 긴 유효 기간(며칠~몇 주)</li>
<li>직접 API 요청에는 사용되지 않고, 오직 재발급 용도로만 쓰입니다.</li>
<li>주로 서버 DB나 보안 쿠키에 안전하게 보관됩니다.<h2 id="토큰의-장단점">토큰의 장단점</h2>
<h3 id="장점-1">장점</h3>
</li>
<li>서버가 상태를 저장하지 않으므로 확장성에 유리합니다.</li>
<li>여러 서비스간 인증에 편리합니다.<h3 id="단점-1">단점</h3>
</li>
<li>토큰 탈취 시 악용 가능성이 큽니다.</li>
<li>토큰 자체가 커지면 요청마다 전송 부담이 생길 수 있습니다.</li>
<li>토큰을 발급받고 나면 만료 전까지 강제로 무효화하기 어렵습니다.<h2 id="토큰-보안-강화-방식">토큰 보안 강화 방식</h2>
</li>
<li>토큰 만료 시간을 짧게 설정하고, 리프레시 토큰을 따로 발급해 관리.</li>
<li>HTTPS를 통해 전송하고, 가능한 경우 쿠키의 <code>HttpOnly</code>, <code>Secure</code> 속성 활용</li>
</ul>
<blockquote>
<p><strong>HttpOnly</strong>란
의미: 자바스크립트에서 쿠키에 접근하지 못하게 막는 속성입니다.
목적:** XSS(교차 스트립팅) **공격으로부터 쿠키를 보호</p>
</blockquote>
<blockquote>
<p><strong>Secure</strong>란?
의미: HTTPS 연결에서만 쿠키를 전송하도록 제한하는 속성
목적: 평문 HTTP에서 쿠키가 탈취되는 걸 방지</p>
</blockquote>
<ul>
<li>민감한 정보는 페이로드에 직접 넣지 않고, 최소한의 식별자만 담습니다.</li>
</ul>
<hr>
<h2 id="마무리">마무리</h2>
<p>여기까지 쿠키, 세션, 토큰에 대해 알아보았습니다!
감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axios!]]></title>
            <link>https://velog.io/@j15u_k7/Axios</link>
            <guid>https://velog.io/@j15u_k7/Axios</guid>
            <pubDate>Sat, 27 Sep 2025 16:45:26 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>학교 취업 동아리에서 훅, 로컬스토리지를 활용해서 날씨 API를 가져와 날씨를 알려주는 웹을 만드는 과제를 받았습니다.
이 과제를 수행하면서 Axios에 대해 공부하게 되었습니다.
React에서 API를 연동하는 방법 중 제일 많이 사용되는 방법이 바로 <strong>Axios</strong>라고 합니다.</p>
<p>그래서 오늘은 <strong>Axios</strong>를 한 번 파해쳐 보겠습니다!</p>
<h1 id="axios란">Axios란?</h1>
<blockquote>
<p><strong>Axios</strong>는 브라우저, Node.js를 위한 Promise API를 활용하는 HTTP 비동기 통신 라이브러리입니다.</p>
</blockquote>
<p>내부적으로 브라우저에서는 <code>XMLHttpRequest</code> 객체를 사용하고, Node.js에서는 <code>http</code> 모듈을 활용하여 요청을 처리합니다.</p>
<p>특히 Axios는 Promise API 기반으로 동작하기 때문에 비동기 코드 작성이 깔끔하고, <code>async/await</code> 구문과 자연스럽게 어울립니다.
단순히 요청을 보내는 기능뿐만 아니라, 응답 데이터 변환, 요청 취소, 인터셉터, XSRF 보호 같은 다양한 편의 기능을 제공한다는 점에서 fetch보다 더 생산적입니다.</p>
<h2 id="axios-기본-사용법">Axios 기본 사용법</h2>
<ul>
<li><p>설치
<code>npm install axios</code>
Axios는 외부 라이브러리이므로 먼저 설치해야 합니다.</p>
</li>
<li><p>불러오기</p>
<pre><code class="language-js">import axios from &#39;axios&#39;;</code></pre>
</li>
<li><p>GET method 방식</p>
<pre><code class="language-js">axios.get(&#39;https://api.example.com/data&#39;)
.then(response =&gt; {
  console.log(response.data);   // 서버가 보내준 데이터
  console.log(response.status); // HTTP 상태 코드
})
.catch(error =&gt; {
  console.error(error);         // 에러 처리
});</code></pre>
<p>GET 방식에서는 axios<strong>.get</strong>으로 요청할 URL을 작성하고, <code>.then()</code>에서 성공 시 데이터를 처리하고, <code>.catch()</code>에서 에러를 처리합니다.</p>
</li>
<li><p>POST method 방식</p>
<pre><code class="language-js">axios.post(&#39;https://api.example.com/data&#39;, {
name: &#39;jiyu&#39;,
age: 17
})
.then(response =&gt; {
  console.log(response.data);   // 서버 응답
})
.catch(error =&gt; {
  console.error(error);         // 에러 처리
});
</code></pre>
</li>
</ul>
<pre><code>POST 방식에서는 axios.**post**를 사용하고, URL과 함께 서버로 보낼 데이터를 작성합니다.
그리고 GET 방식과 마찬가지로 요청이 성공하면 `.then()`에서 응답 데이터를 처리하고,
요청이 실패하면 `.catch()`에서 에러를 처리합니다.
## Axios 장점
#### 1. 더 많은 기능 및 편의성
- Axios는 다른 통신 방법들 보다 더 많은 기능을 제공하며, 요청과 응답을 보다 쉽게 다룰 수 있는 편의성을 제공합니다.
예를 들어, 요청과 응답을 가공하거나 인터셉터를 사용하여 요청이나 응답 전에 특정 작업을 수행할 수 있습니다.
#### 2. 자동 JSON 변환
- Axios는 자동으로 JSON 데이터를 파싱하거나 시리얼라이즈 해주는 특징을 가지고 있습니다.
반면, fetch는 이러한 기능을 기본적으로 제공하지 않아서 수동으로 처리해야 합니다.
#### 3. 브라우저 호환성
- Axios는 브라우저 호환성을 고려하여 개발되었기 때문에 일부 오래된 브라우저에서도 문제없이 동작합니다.
반면, fetch는 IE11과 같은 오래된 브라우저에서는 지원이 부족할 수 있습니다.
#### 4. 에러 핸들링
- Axios는 HTTP 에러 상태 코드를 자동을 확인하고 에러가 발생했을 때 더 자세한 정보를 제공하는 특징을 가지고 있습니다.
#### 5. HTTP 요청 취소
- Axios는 요청을 취소하는 기능이 내장되어 있습니다.
이는 특정 요청을 중단하고 싶을 때 유용합니다.
#### 6. 요청/응답 인터셉트 기능
- Axios의 가장 큰 장점 중 하나는 인터셉터 기능입니다. 
요청이 서버로 전달되기 전에, 혹은 응답이 애플리케이션에 도착하기 전에 데이터를 가로채서 수정하거나 추가 로직을 실행할 수 있습니다.
#### 7. XSRF(CSRF) 공격 방지 지원
- 웹 애플리케이션에서 자주 문제 되는 보안 위협 중 하나가 XSRF 공격입니다. 
이는 사용자가 모르는 사이에 악성 사이트가 사용자의 인증 정보를 도용해 요청을 보내는 방식인데, Axios는 이를 막기 위한 클라이언트 사이드 기능을 제공합니다. 
서버가 쿠키에 담아 전달한 토큰을 Axios가 자동으로 읽어서 요청 헤더에 함께 전송하도록 해주기 때문에, 서버 측 검증과 맞물려 안전한 통신을 구현할 수 있습니다.
## Axios 단점
- import, require로 모듈 설치가 필요
  - Axios는 자바스크립트 기본 내장 기능이 아니라 외부 라이브러리입니다. 
 따라서 프로젝트에 사용하려면 `npm install axios`와 같은 설치 과정이 필요하고, 코드 안에서도 axios from &quot;axios&quot;sk require(&quot;axios&quot;) 방식으로 불러와야 합니다.
 반면 fetch는 브라우저와 Node.js에서 기본적으로 제공되므로 별도의 설치 없이 바로 사용할 수 있습니다.
 &lt;br&gt;
- 번들 크기가 상대적으로 큰 편
  - Axios는 다양한 편의 기능(인터셉트, 요청 취소 등)을 제공하는 만큼 라이브러리 크기가 fetch보다 큽니다. 번들 크기가 커지면 초기 로딩 속도에 영향을 줄 수 있습니다.



## 내 코드
다음으로 Axios를 사용해서 제가 짠 코드를 통해 axios를 설명해 보겠습니다.

```js
const fetchWeather = async (cityName) =&gt; {
    if (!cityName) return;

    try{
      const res = await axios.get(
        &quot;https://api.openweathermap.org/data/2.5/weather&quot;,
        {
          params: {
            q: cityName,
            appid: API_KEY,
            units: &quot;metric&quot;,
            lang: &quot;kr&quot;,
          },
        }
      );

      if(res.status === 200){
        if(weather){
          setRecent([weather.name]);
          localStorage.setItem(&quot;weather&quot;, JSON.stringify([weather.name]));
        }
        setWeather(res.data);
      }
    } catch(err) {
      if(err.status &amp;&amp; err.status === 404)
        alert(&quot;지역을 찾을 수 없습니다.&quot;);
    }
  };
</code></pre><p>이 코드는 파일에 Axios가 있는 일부 코드만 가져온 것 입니다.</p>
<p>이 코드는 api key와 온도 단위 mertic(섭씨), api 응답 언어를 한국어로 설정하게 하는 파라미터를 전달하며 날씨 api를 불러오고 있습니다.
여기서 params란 기능은 Axios에서 URL 쿼리 파라미터를 쉽게 붙여주는 옵션입니다.
<code>https://api.openweathermap.org/data/2.5/weatherq=Seoul&amp;appid=API_KEY&amp;units=metric&amp;lang=kr</code>
fetch로 사용하게 되면 URL에 직접 이어 붙여야 하는 번거로움이 있습니다.
즉, params는 Axios 전용 편의 기능입니다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>여기까지 Axios에 대해 알아보았습니다.
이 글을 읽고 보다 편리하게 Axios를 사용하였으면 좋겠습니다.
감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[npm 이란...?]]></title>
            <link>https://velog.io/@j15u_k7/npm-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@j15u_k7/npm-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 10 Sep 2025 10:37:08 GMT</pubDate>
            <description><![CDATA[<p>오늘은 자바스크립트를 사용한다면 한번쯤 써봤을 npm에 대해 알아보도록 하겠습니다!</p>
<h1 id="npm">npm?</h1>
<p>npm은 <strong>&#39;Node Package Manager&#39;</strong>의 약자로 <strong>Node.js</strong> 환경에서 사용되는 자바스크립트 패키지 관리자입니다. </p>
<p>웹사이트에서 수많은 자바스크립트 라이브러리(패키지)를 검색, 설치, 관리할 수 있으며, <strong>Node.js를 설치하면 기본적으로 함께 설치되는 도구입니다.<br> 
*<em>개발자는 <code>npm install [모듈 이름]</code>과 같은 간단한 명령어로 필요한 패키지를 설치하고, *</em>package.json</strong> 파일을 통해 프로젝트의 의존성을 관리할 수 있습니다. </p>
<h2 id="패키지란">패키지란?</h2>
<blockquote>
<p>프로그램을 개발할 때 필요한 코드, 라이브러리, 실행 파일, 설정 등을 묶어 놓은 소프트웨어 단위입니다.</p>
</blockquote>
<p>쉽게 말해, 다른 사람이 만들어둔 기능 모음집을 한 덩어리로 묶어서 쓸 수 있게 해둔 것입니다.</p>
<p>그럼 패키지 매니저는 무엇일까요?</p>
<h2 id="패키지-매니저란">패키지 매니저란?</h2>
<p>방금 말한 패키지를 쉽게 설치, 업데이트, 삭제, 관리할 수 있도록 도와주는 도구입니다.</p>
<p>패키지 매니저는 프로그램 언어마다 다릅니다.</p>
<p>예시로는</p>
<pre><code>Python - pip, conda, poetry
Java - Maven, Gradle
Javascript - npm, yarn, pnpm</code></pre><p>등이 있습니다.</p>
<p>저는 이중 Javascript에 npm 패키지 매니저에 대해 설명하겠습니다.</p>
<h2 id="npm-기본-사용법">npm 기본 사용법</h2>
<p>npm 패키지는 <code>npm install 패키지명</code> 명령어로 설치 가능합니다.</p>
<p>이렇게 설치된 패키지는 프로젝트의 <code>node_modules</code> 폴더 안에 저장 됩니다.</p>
<p>또한 특정 패키지를 전역적으로 설치해야 할 때는 <code>npm install -g 패키지명</code>과 같이 <code>-g</code> 옵션을 붙이면 됩니다.</p>
<p>이는 개발 도구나 CLI처럼 시스템 전반에서 사용되는 패키지를 설치할 때 자주 활용됩니다.
필요하지 않은 패키지를 제거할 때는 <code>npm uninstall 패키지명</code> 명령어를 사용하고, 특정 버전의 패키지가 필요하면 <code>npm install 패키지명@버전</code> 형태로 설치하면 됩니다.</p>
<p>이처럼 npm은 설치, 제거, 버전 관리까지 직관적으로 사용할 수 있다는 장점이 있습니다.</p>
<h2 id="packagejson">package.json</h2>
<p>npm을 사용하는 프로젝트에서 빠질 수 없는 파일이 바로 <code>package.json</code>입니다.</p>
<p>이 파일은 프로젝트의 메타 정보를 담고 있으며, 어떤 패키지가 의존성으로 필요한지를 기록하는 역할을 합니다.</p>
<p>예를 들어 dependencies에는 실제 서비스 실행에 필요한 패키지가 기록되고, devDependencies에는 개발 환경에서만 사용하는 패키지가 기록됩니다. </p>
<blockquote>
<p><strong>dependencies</strong>란?
package.json 안에서 프로젝트가 실행될 때 꼭 필요한 패키지 목록을 기록하는 부분입니다.</p>
</blockquote>
<p>또한 <code>scripts</code>라는 항목을 통해 반복적으로 사용하는 명령어를 등록해 두고, <code>npm start</code>나 <code>npm run build</code>처럼 간단한 명령으로 실행할 수 있습니다. 
즉, package.json은 단순히 패키지 목록을 관리하는 것을 넘어 프로젝트의 핵심 설정 파일로 활용된다고 볼 수 있습니다.</p>
<h2 id="npm-주요-명령어">npm 주요 명령어</h2>
<h3 id="--save-dev-d">--save-dev (D)</h3>
<p>개발환경에서만 사용 ( package.json 안에 devDependencies에 기록)</p>
<h3 id="--global--g">--global (-g)</h3>
<p>전역 설치</p>
<h3 id="npm-init">npm init</h3>
<p>package.json을 만드는 명령어</p>
<h3 id="npm-install">npm install</h3>
<p>package.json 파일 및 해당 종속성에 나열된 모든 모듈을 설치</p>
<h3 id="npm-update">npm update</h3>
<p>설치된 패키지를 업데이트하는 명령어</p>
<h3 id="npm-start">npm start</h3>
<p>package.json 의 scripts에 있는 start명령어를 실행하는 부분
만약 start 명령어를 따로 설정하지 않았다면 node server.js가 실행된다.</p>
<h3 id="npm-run">npm run</h3>
<p>script를 실행하는 명령어
만약 scripts에 build명령어가 있다면, npm run build 하면 됩니다.</p>
<h2 id="yarn-pnpm">yarn, pnpm</h2>
<p>자바스크립트 생태계에는 npm 이외에도 <strong>yarn과 pnpm **같은 패키지 매니저가 존재합니다.
npm은 가장 오랫동안 사용되어 왔고</strong> Node.js** 설치 시 기본으로 제공되기 때문에 사용자가 많습니다.</p>
<p>반면 <strong>yarn</strong>은 속도와 안정성을 개선하기 위해 등장했으며, 캐시 기능을 강화하여 반복 설치 시 매우 빠른 성능을 보여줍니다.</p>
<p>최근에는 <strong>pnpm</strong>이라는 새로운 패키지 매니저가 등장해 디스크 공간을 효율적으로 사용하는 방식으로 주목 받고 있습니다.
pnpm은 설치된 패키지를 중복 저장하지 않고 링크 방식으로 관리하기 때문에 대규모 프로젝트에서 특히 강점을 발휘합니다.</p>
<p>결국 어떤 패키지 매니저를 선택할지는 팀의 환경과 요구사항에 따라 달라질 수 있지만, npm은 기본적이고 보편적인 선택지라는 점에서 여전히 널리 활용되고 있습니다.</p>
<h2 id="마무리">마무리</h2>
<p>npm은 자바스크립트 개발을 시작하면 꼭 만나게 되는 도구입니다.
이렇게 자주 사용되는 npm에 대해 알아보아서 좋았습니다.
감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[명세서 작성법과 꿀팁!]]></title>
            <link>https://velog.io/@j15u_k7/%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%91%EC%84%B1%EB%B2%95%EA%B3%BC-%EA%BF%80%ED%8C%81</link>
            <guid>https://velog.io/@j15u_k7/%EB%AA%85%EC%84%B8%EC%84%9C-%EC%9E%91%EC%84%B1%EB%B2%95%EA%B3%BC-%EA%BF%80%ED%8C%81</guid>
            <pubDate>Tue, 26 Aug 2025 11:17:27 GMT</pubDate>
            <description><![CDATA[<h2 id="목적">목적</h2>
<p>학교에서 아이디어 페스티벌 프로젝트를 진행하면서 기능 명세서를 작성 해봤습니다.
처음에 작성할 때는 어떻게 작성해야 하는지도 모르겠고 막막 했기 때문에 선배들의 자료를 참고하거나 인터넷에서 찾아보며 작성 해봤습니다.
그러면서 기능 명세서 작성법을 글로 정리해두면 좋을거 같아 작성하게 되었습니다.</p>
<h1 id="작성법">작성법</h1>
<h2 id="기능-명세서">기능 명세서</h2>
<blockquote>
<p><strong>기능 명세서란?</strong>
소프트웨어 제품이 사용자에게 어떤 기능을 제공할 것인지, 어떻게 동작해야 하는지 상세하게 기술한 문서이다.</p>
</blockquote>
<p>기능 명세서는 제일 중요한 역할을 합니다.
왜냐하면 디자이너는 우리가 작성한 기능 명세서를 기반으로 디자인을 진행하기 때문입니다.
만약 기능 명세서를 정확히 작성 하지 않으면, 디자이너는 의도를 이해하지 못하거나 자기 방식대로 디자인할 수 있으며, 그 과정에서 불필요한 의견 충돌이 생길 수도 있습니다. </p>
<p>그럼 기능 명세서는 어떻게 작성해야 할까요?</p>
<h3 id="1-기능은-명확하고-짧게-작성한다">1. 기능은 명확하고 짧게 작성한다.</h3>
<p>내가 작성하려는 기능이 어떤 역할을 하는지 <strong>한 문장으로 정리</strong>하면 좋습니다.</p>
<p>예를 들어 &quot;사용자는 학교 이메일을 통해 회원가입 할 수 있다.&quot; </p>
<p>이렇게 짧고 명확한 정의를 작성하면 불필요한 설명은 제거하고 핵심 기능을 빠르게 전달할 수 있습니다.</p>
<h3 id="2-사용자-시나리오를-구제척으로-작성한다">2. 사용자 시나리오를 구제척으로 작성한다.</h3>
<p>단순히 &quot;버튼을 누르면 다음 화면으로 이동한다&quot; 같은 설명이 아니라, 
실제 사용자가 어떤 흐름으로 기능을 이용하는지 <strong>스토리텔링 방식</strong>으로 작성하면 효과적으로 작성 할 수 있습니다.</p>
<p>에시)
-사용자는 회원가입 버튼을 클릭한다.
-이메일, 비밀번호를 입력한다.
-이메일 인증을 완료하면 회원가입이 완료된다.</p>
<p>이렇게 작성하면 더욱 쉽게 이해 할 수 있습니다.</p>
<h3 id="3-화면-설계를-첨부한다">3. 화면 설계를 첨부한다.</h3>
<p>기능 명세서에는 <strong>화면 설계도(와이어 프레임)</strong>가 반드시 포함되어야 합니다.
텍스트로만 설명하면 오해가 생길 가능성이 크기 때문에 figma 같은 툴을 활용 해 기본적인 화면 설계를 공유하는 방법도 효과적입니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/1d8e2c93-85b4-4b24-a33f-189df7fc45c8/image.png" alt=""></p>
<h3 id="4-입력값과-출력값을-명확히-정의한다">4. 입력값과 출력값을 명확히 정의한다.</h3>
<p><strong>데이터 정의</strong>는 기능 명세서에서 핵심적인 부분입니다.
사용자가 입력하는 값과 그에따른 시스템의 출력 결과를 표로 정리하면 훨씬 이해하기 쉽습니다.</p>
<h3 id="5-예외-처리를-작성한다">5. 예외 처리를 작성한다.</h3>
<p><strong>예외 상황</strong>을 기능 명세서에 작성하지 않으면 디자이너는 기본 기능만 구현하고 넘어갈 수도 있습니다.
그러면 이후 QA 단계에서 문제가 발생할 가능성이 커집니다.</p>
<p>예외 처리 예시)
-이메일 형식이 잘못된 경우: <strong>&quot;올바른 이메일을 입력하세요&quot;</strong>
-비밀번호 형식이 다른 경우: <strong>&quot;비밀번호는 최소 8자 이상이어야 합니다&quot;</strong></p>
<p>이렇게 예외 상황을 꼭 명시해서 작성 해주어야 합니다.</p>
<br>
여기까지 제가 알려드리는 기능 명세서 작성시 꿀팁이고,
다음은 API 명세서 작성법에 대해 알려 드리겠습니다.

<h2 id="api-명세서">API 명세서</h2>
<p>API 명세서는 백엔드 직종이 작성 하지만 알아보면 좋을 것 같아서 작성합니다.</p>
<blockquote>
<p><strong>API 명세서란?</strong>
API 명세서는 클라이언트(앱, 웹 등)와 서버가 데이터를 주고받을 때 어떤 규칙으로 요청·응답할지를 정리한 문서이다.</p>
</blockquote>
<p>API 명세서에 포함되는 기본적인 요소들을 알아보겠습니다.</p>
<h3 id="메서드method">메서드(Method)</h3>
<p>메서드는 요청 방식을 뜻하며** GET, POST, PUT, PATCH, DELETE** 다섯개가 있습니다.</p>
<p>먼저 <strong>GET</strong>은 데이터 조회, <strong>POST</strong>는 새로운 데이터 생성,** PUT<strong>은 데이터 전체 수정, **PATCH</strong>는 데이터 부분 수정, <strong>DELETE</strong>는 데이터 삭제를 의미합니다.</p>
<p>API 명세서에는 이 각 기능들의 메서드 방식을 표시해야 합니다.</p>
<h3 id="엔드-포인트endpoint">엔드 포인트(Endpoint)</h3>
<p>엔드 포인트는 클라이언트가 요청을 보낼 때 접근하는** API 주소(URL)**를 의미합니다.
쉽게 말해 어떤 기능을 호출할지를 구분하는 위치 정보라고 할 수 있습니다.</p>
<p>예를 들어 <code>/api/users/signup</code> 은 회원가입을 처리하는 엔드 포인트이고, <code>/api/users/signin</code> 은 로그인을 처리하는 엔드 포인트 입니다.</p>
<h3 id="요청-데이터">요청 데이터</h3>
<p>요청 데이터는 <strong>클라이언트가 서버로 보내는 정보</strong>입니다.
URL, 경로 변수, 헤더, 본문 등 여러 방식으로 전달될 수 있습니다.</p>
<p>예를 들어 회원가입에서 이메일과 비밀 번호가 요청 데이터가 됩니다.
이에 대해 서버가 돌려주는 결과가 응답 데이터 입니다.
보통 JSON 형식으로 반환되며, 요청이 성공했는지 실패했는지를 알려주고 필요한 데이터를 함께 제공 합니다.</p>
<pre><code class="language-json">{
  &quot;status&quot;: &quot;error&quot;,
  &quot;message&quot;: &quot;올바른 이메일 형식을 입력하세요.&quot;
}</code></pre>
<h3 id="상태-코드">상태 코드</h3>
<p>상태 코드는 요청 처리 결과를 숫자로 표현하는 HTTP 표준 코드입니다.</p>
<ul>
<li><code>200 OK</code> → 성공</li>
<li><code>201 Created</code> → 새 데이터 생성 성공</li>
<li><code>400 Bad Request</code> → 잘못된 요청 (입력값 오류 등)</li>
<li><code>401 Unauthorized</code> → 인증 실패</li>
<li><code>403 Forbidden</code> → 권한 없음</li>
<li><code>404 Not Found</code> → 요청한 리소스 없음</li>
<li><code>500 Internal Server Error</code> → 서버 내부 오류</li>
</ul>
<p>이런식으로 나타낼 수 있습니다.</p>
<hr>
<br>
이상으로 명세서 작성법과 꿀팁에 대해 알아보았습니다.<br>
이 글을 읽고 명세서 작성하는데에 도움이 되었으면 좋겠습니다.<br>
감사합니다
]]></description>
        </item>
        <item>
            <title><![CDATA[fetch 함수를 통한 API 연동법]]></title>
            <link>https://velog.io/@j15u_k7/fetch-%ED%95%A8%EC%88%98%EB%A5%BC-%ED%86%B5%ED%95%9C-API-%EC%97%B0%EB%8F%99%EB%B2%95</link>
            <guid>https://velog.io/@j15u_k7/fetch-%ED%95%A8%EC%88%98%EB%A5%BC-%ED%86%B5%ED%95%9C-API-%EC%97%B0%EB%8F%99%EB%B2%95</guid>
            <pubDate>Wed, 20 Aug 2025 09:13:23 GMT</pubDate>
            <description><![CDATA[<p>API를 연동하는 방법은 여러 가지가 있습니다.
주요 방법 4가지로는** fetch(), axios, XMLHttpRequest, jQuery.ajax()** 가 있습니다.</p>
<h2 id="api-연결-방법">API 연결 방법</h2>
<p><strong>fetch()</strong>는 모던 자바스크립트에서 대표적으로 사용하는 비동기 요청 방식으로, Promise 기반 API를 제공해 가독성과 활용성이 좋습니다.</p>
<p><strong>axios</strong>는 브라우저와 Node.js 모두에서 사용할 수 있는 HTTP 비동기 통신 라이브러리로, React 등 프런트엔드 프레임워크 사용자들이 특히 많이 활용합니다.</p>
<p><strong>XMLHttpRequest</strong>는 가장 오래된 API 요청 방식으로, Ajax의 핵심 기반 기술이지만 코드 가독성이 떨어져 요즘은 잘 쓰이지 않습니다.</p>
<p><strong>jQuery.ajax()</strong> jQuery 라이브러리의 Ajax 기능으로, 브라우저 호환성이 좋지만 jQuery 자체를 로드해야 하므로 현대적인 프로젝트에서는 거의 사용되지 않습니다.</p>
<p>이런 여러 방식의 연동 방식이 있지만
오늘은 <strong>fetch()</strong> 함수를 이용해 API를 연동해 보겠습니다.</p>
<h2 id="fetch-함수란">fetch() 함수란?</h2>
<blockquote>
<p>fetch API 브라우저에서 제공하는 자바스크립트 인터페이스로, 네트워크로 요청을 보내고 응답 처리를 하는 기능을 제공한다.</p>
</blockquote>
<p>브라우저에서 fetch() 함수를 지원하기 전에는 클라이언트 단에서 직접 HTTP를 요청하고 응답을 받는 게 복잡해서 axios, jQuery와 같은 라이브러리를 사용하는 것이 합리적이었습니다. </p>
<p>하지만 요즘에는 굳이 이러한 라이브러리의 도움없이도 브라우저에서 내장된 <strong>fetch() 함수</strong>를 이용하면 대부분의 경우 충분하기 때문에 오히려 이러한 라이브러리를 사용하는 것이 자바스크립트 번들(bundle) 파일의 크기만 늘려서 낭비가 될 수 있습니다.</p>
<blockquote>
<p><strong>번들(bundle) 파일이란??</strong>
여러 파일을 하나로 결합하는 작업을 나타낸다.
이를 통해 웹 애플리케이션의 성능을 최적화하고 관리를 더 쉽게 만드는데 도움이 된다.</p>
</blockquote>
<h3 id="사용법">사용법</h3>
<p><code>fetch()</code> 함수는 첫번째 인자로 <strong>URL</strong>, 두번째 인자로 <strong>옵셥 객체</strong>를 받고 Promise 타입의 객체를 반환합니다.
반환된 객체는, API 호출이 성공했을 경우에는 응답(response) 객체를 resolve하고, 실패했을 경우에는 예외(error) 객체를 reject 합니다.</p>
<pre><code class="language-javascript">fetch(url, options)
  .then((response) =&gt; console.log(&quot;response:&quot;, response))
  .catch((error) =&gt; console.log(&quot;error:&quot;, error));</code></pre>
<p>옵션 객체에는 HTTP 방식, HTTP 요청 헤더, HTTP 요청 전문 등을 설정해줄 수 있습니다.
응답 객체로 부터는 HTTP 응답 상태, HTTP 응답 헤더, HTTP 응답 전문 등을 읽어올 수 있습니다.</p>
<p>fetch method 에는 <strong>GET, POST, PUT, PATCH, DELETE</strong>가 있습니다.</p>
<h3 id="get">GET</h3>
<p>먼저 단순히 원격 API에 있는 데이터를 가져올 때 사용하는 GET 방식입니다.</p>
<p>인터넷에 공개된 API를 사용해서 예제 코드를 작성하면
<code>fetch()</code> 함수는 디폴트로 GET 방식으로 작동하고 GET 방식은 요청 전문을 받지 않기 때문에 옵션 인자가 필요 없습니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;).then((response) =&gt;
  console.log(response)
);</code></pre>
<p>응답 객체를 통해 응답 상태가 <code>200 OK</code>(요청이 정상적으로 실행되었다)인 것을 금방 알 수 있습니다.</p>
<p>대부분에 API들은 Json 형태의 데이터를 응답하기 때문에, 응답 객체는 <code>json()</code>메서드를 제공합니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;)
  .then((response) =&gt; response.json())
  .then((data) =&gt; console.log(data));</code></pre>
<p>이 <code>json()</code> 메서드를 호출하면 응답 객체로부터 json 포맷의 응답 전문을 자바스크립트 객체로 변환하여 얻을 수 있습니다.</p>
<blockquote>
<p><strong>응답 전문이란?</strong>
웹에서 클라이언트가 서버에 요청을 보내면, 서버는 그에 대한 응답을 HTTP 응답 메시지 형태로 보내는데 크게 세 부분으로 </p>
</blockquote>
<ol>
<li>상태 줄 2. 헤더 3. 본문 으로 나뉘어 응답 전문은 본문에 해당합니다.</li>
</ol>
<pre><code class="language-js">{
  &quot;userId&quot;: 1,
  &quot;id&quot;: 1,
  &quot;title&quot;: &quot;sunt aut facere repellat provident occaecati excepturi optio reprehenderit&quot;,
  &quot;body&quot;: &quot;quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto&quot;
}</code></pre>
<p>단순히 특정 API에 저장된 데이터를 보여주는 웹페이지나 애플리케이션에서는 GET 방식의 HTTP 통신으로 충분합니다.</p>
<h3 id="post">POST</h3>
<p>원격 API에서 관리하고 있는 데이터를 생성해야 한다면 요청 전문을 포함할 수 있는 POST 방식의 HTTP 통신이 필요합니다.</p>
<p>동일한 API로 새로운 포스팅을 생성하기 위해서 <code>fetch()</code> 함수를 사용해보겠습니다.
이에 경우 <code>method</code> 옵션을 <code>POST</code>로 지정해주고 <code>headers</code> 옵션을 통해 JSON 포맷을 사용한다고 알려줘야하고 요청 전문을 JSON 포맷으로 직렬화하여 가장 중요한 <code>body</code> 옵션에 설정해줍니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
  },
  body: JSON.stringify({
    title: &quot;Test&quot;,
    body: &quot;I am testing!&quot;,
    userId: 1,
  }),
}).then((response) =&gt; console.log(response));</code></pre>
<p><code>POST</code>에 경우에는 <code>201 Created</code> 나타납니다.
<code>200 OK</code>와 <code>201 Created</code>의 차이로는 <code>200 OK</code>는 요청이 정상적으로 실행되었다 라면 <code>201 Created</code>는 요청이 실행되었고 리소스가 만들어졌다는걸 의미합니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
  },
  body: JSON.stringify({
    title: &quot;Test&quot;,
    body: &quot;I am testing!&quot;,
    userId: 1,
  }),
})
  .then((response) =&gt; response.json())
  .then((data) =&gt; console.log(data));</code></pre>
<p>마찬가지 방법으로 응답 객체의 <code>json()</code> 메서드를 호출하면 응답 전문을 객체 형태로 얻을 수 있습니다.</p>
<pre><code class="language-js">{title: &quot;Test&quot;, body: &quot;I am testing!&quot;, userId: 1, id: 101}</code></pre>
<h3 id="put-delete">PUT, DELETE</h3>
<p>GET과 POST만큼은 아니지만, 원격 API에서 관리하는 데이터의 <strong>수정과 삭제</strong>를 위해서 PUT과 DELETE 방식의 HTTP 호출을 사용할 때가 있습니다.</p>
<p>PUT 방식은 <code>method</code> 옵션만 <code>put</code>으로 설정한다는 점 빼놓고는 <strong>POST 방식과 매우 비슷</strong>합니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;, {
  method: &quot;PUT&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
  },
  body: JSON.stringify({
    title: &quot;finish&quot;,
    body: &quot;I am die&quot;,
    userId:21,
  }),
})
  .then((response) =&gt; response.json())
  .then((data) =&gt; console.log(data));</code></pre>
<pre><code class="language-js">{title: &quot;Test&quot;, body: &quot;I am testing!&quot;, userId: 1, id: 1}</code></pre>
<p>DELETE 방식에서는 보낼 데이터가 없기 때문에 <code>headers</code>와 <code>body</code> 옵션이 필요 없습니다.</p>
<pre><code class="language-js">fetch(&quot;https://jsonplaceholder.typicode.com/posts/1&quot;, {
  method: &quot;DELETE&quot;,
})
  .then((response) =&gt; response.json())
  .then((data) =&gt; console.log(data));</code></pre>
<h3 id="내-코드">내 코드</h3>
<p>그럼 제가 공개 API를 통해 <code>GET</code> <code>method</code>를 사용하여 만든 영화 검색기 코드를 알아보겠습니다.</p>
<pre><code class="language-js">import config from &quot;./config.js&quot;;
const {API_KEY}=config;


document.querySelector(&#39;#btn&#39;).addEventListener(&quot;click&quot;, searchMovie);
function searchMovie(){
    const title=document.getElementById(&#39;ss&#39;).value;
    const movieinfo = document.getElementById(&#39;movie-info&#39;);
    fetch(`https://www.omdbapi.com/?t=${title}&amp;apikey=${API_KEY}`)
        .then(response=&gt;response.json())
        .then(data=&gt;{
            if(data.Response===&#39;True&#39;){
                document.querySelector(&#39;#movie-title&#39;).textContent = `${data.Title} (${data.Year})`;
                document.querySelector(&#39;#movie-poster&#39;).src = data.Poster;
                document.querySelector(&#39;#movie-director&#39;).textContent = data.Director;
                document.querySelector(&#39;#movie-plot&#39;).textContent = data.Plot;
                document.querySelector(&#39;#movie-rating&#39;).textContent = data.imdbRating;

                document.querySelector(&#39;#error&#39;).textContent = &#39;&#39;;
                movieinfo.style.display = &#39;flex&#39;;
            } else {
                document.querySelector(&#39;#error&#39;).textContent = &#39;영화를 찾을 수 없습니다.&#39;;
                movieinfo.style.display=&#39;none&#39;;
            }      
        })
}</code></pre>
<p>한 줄 씩 알아보자면</p>
<pre><code class="language-js">import config from &quot;./config.js&quot;;
const {API_KEY}=config;</code></pre>
<p>apikey가 따로 있기 때문에 config.js 파일에 작성한 것을 가져오고있고</p>
<pre><code class="language-js">document.querySelector(&#39;#btn&#39;).addEventListener(&quot;click&quot;, searchMovie);</code></pre>
<p>id=&quot;btn&quot; 인 버튼을 찾아서 클릭하면 <code>searchMovie</code> 함수를 실행하도록 이벤트 리스너를 등록합니다.
<code>searchMovie</code>함수에는</p>
<pre><code class="language-js">  const title = document.getElementById(&#39;ss&#39;).value;
  const movieinfo = document.getElementById(&#39;movie-info&#39;);</code></pre>
<p>입력창 &#39;ss&#39;에 사용자가 입력한 영화 제목과 검색결과를 표시할 컨테이너 id=&quot;movie-info&quot;를 가져옵니다.</p>
<pre><code class="language-js">  fetch(`https://www.omdbapi.com/?t=${title}&amp;apikey=${API_KEY}`)</code></pre>
<p>검색할 영화 제목과 내 API 키를 불러오고 OMDB 공개 API에 요청을 보냅니다.
즉, 특정 영화 정보를 가져오기 위해 GET 요청을 보냅니다.</p>
<pre><code class="language-js">    .then(response =&gt; response.json())</code></pre>
<p>서버에 응답을 받으면 json 형식으로 바꿉니다.</p>
<pre><code class="language-js">.then(data=&gt;{
            if(data.Response===&#39;True&#39;){
                document.querySelector(&#39;#movie-title&#39;).textContent = `${data.Title} (${data.Year})`;
                document.querySelector(&#39;#movie-poster&#39;).src = data.Poster;
                document.querySelector(&#39;#movie-director&#39;).textContent = data.Director;
                document.querySelector(&#39;#movie-plot&#39;).textContent = data.Plot;
                document.querySelector(&#39;#movie-rating&#39;).textContent = data.imdbRating;

                document.querySelector(&#39;#error&#39;).textContent = &#39;&#39;;
                movieinfo.style.display = &#39;flex&#39;;</code></pre>
<p>API 응답에서 &quot;Response&quot;: &quot;True&quot;이면 영화 제목과 개봉 연도를 표시하고
이미지 태그 src 속성을 영화 포스터 이미지 URL로 설정합니다.
감독 이름과 줄거리, IMDB 평점을 표시하고
document.querySelector(&#39;#error&#39;).textContent = &#39;&#39;; 검색에 성공했으니 오류가 없기 때문에 에러 메시지를 비워주고 원래 display: none; 였던 속성들을 flex로 바꿔 화면에 보이도록 설정합니다.</p>
<pre><code class="language-js">      } else {
        document.querySelector(&#39;#error&#39;).textContent = &#39;영화를 찾을 수 없습니다.&#39;;
        movieinfo.style.display = &#39;none&#39;;
      }</code></pre>
<p>만약 &quot;Response&quot;: &quot;False&quot;라면 에러 메시지 &quot;영화를 찾을 수 없습니다.&quot; 를 표시하고 영화 정보 영역은 숨깁니다.</p>
<p>제가 만든 영화 검색기는 <a href="https://movie-dhcqtcbaw-s25023-6950s-projects.vercel.app/">https://movie-dhcqtcbaw-s25023-6950s-projects.vercel.app/</a> 에서 찾아볼 수 있습니다.
이상으로 <code>fetch</code> 함수를 통한 API 연동법에 대해 알아보았습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[야 너도 할 수 있어 서버 배포.]]></title>
            <link>https://velog.io/@j15u_k7/%EC%95%BC-%EB%84%88%EB%8F%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EC%96%B4-%EC%84%9C%EB%B2%84-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@j15u_k7/%EC%95%BC-%EB%84%88%EB%8F%84-%ED%95%A0-%EC%88%98-%EC%9E%88%EC%96%B4-%EC%84%9C%EB%B2%84-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Fri, 15 Aug 2025 13:00:26 GMT</pubDate>
            <description><![CDATA[<h3 id="많은-종류의-배포-방법">많은 종류의 배포 방법...</h3>
<p>서버 배포 방법은 정말 여러가지가 있다.
대표적으로 AWS, VERCEL, Firebase 등이 있는데</p>
<p>그 중 제일 간편하고 쉽게 배포할 수 있는 Vercel 배포 방법을 살표 보자!</p>
<h2 id="vercel">Vercel</h2>
<p>Vercel은 프론트엔드 웹사이트와 간단한 서버리스 API를 빠르게 배포할 수 있는 서비스입니다!</p>
<p>자신이 배포하고 싶은 서비스가 <strong>프론트엔드 중심의 개인 프로젝트</strong>라면 vercel로 배포하는 것이 가장 쉽고 간단한 배포 방법입니다.</p>
<hr>
<p>그럼 지금부터 Vercel을 통한 배포 방법을 알아보겠습니다!</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/b34a4258-1d4f-4265-821d-5c354cfa054a/image.png" alt="">
먼저 개인 이메일 계정이나 깃허브 계정으로 회원가입 후 로그인을 진행합니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/28b8a1c8-1e18-40d4-8c17-f7461f69c467/image.png" alt="">
깃허브 계정이 아닌 이메일로 회원가입을 하신 분들은 계정 설정에 들어가 깃허브 계정과 연결을 해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/e7276099-8a68-4c42-b05a-6416f30b283e/image.png" alt="">
다시 홈화면으로 돌아와 Add New Project 를 선택 해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/23b1d990-d56d-413c-ac4a-d588082081e0/image.png" alt="">
깃허브 계정을 추가 후 자신이 배포하고 싶은 저장소를 선택하고 import 합니다.</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/8cdaed05-3f7f-43db-8072-d4f630d0ff96/image.png" alt="">
프로젝트 이름을 설정 후 배포하면 끝!</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/242b0770-3353-4135-bda9-317125a568c7/image.png" alt="">
그럼 이렇게 정보를 확인할 수 있게 되고 나와있는 링크를 통해 배포한 서비스를 확인 하시면 됩니다.</p>
<p><a href="https://movie-bice-sigma.vercel.app/">https://movie-bice-sigma.vercel.app/</a>
저는 영화 검색 사이트를 만들어 배포하였습니다.</p>
<br>
지금까지 Vercel을 이용한 서버 배포 방법을 알아보았습니다!]]></description>
        </item>
        <item>
            <title><![CDATA[배열로 데이터 컬렉션을 관리하라!]]></title>
            <link>https://velog.io/@j15u_k7/%EB%B0%B0%EC%97%B4%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%84-%EA%B4%80%EB%A6%AC%ED%95%98%EB%9D%BC</link>
            <guid>https://velog.io/@j15u_k7/%EB%B0%B0%EC%97%B4%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%84-%EA%B4%80%EB%A6%AC%ED%95%98%EB%9D%BC</guid>
            <pubDate>Wed, 06 Aug 2025 03:39:18 GMT</pubDate>
            <description><![CDATA[<p>오늘은 <code>자바스크립트 코딩의 기술</code> 이라는 책의 제2장 <strong>배열로 데이터 컬렉션을 관리하라</strong>  부분을 소개 해보겠습니다.</p>
<p>먼저 제목의 등장하는 컬렉션을 알아보겠습니다.</p>
<h1 id="컬렉션이란">컬렉션이란?</h1>
<blockquote>
<p><strong>&#39;프로그래밍 언어가 제공하는 값을 담을 수 있는 컨테이너&#39;</strong></p>
</blockquote>
<p>자바스크립트에서 컬렉션은 인덱스 기반과 키 기반으로 나눌 수 있습니다.</p>
<p>인덱스 기반의 컬렉션에는 <strong>Arrays, TypedArray.</strong>
키 기반의 컬렉션에는** Objects, Map, Set, WeakMap, WeakSet** 으로 나뉩니다.
ES5까지는 Array, Object 만 있다가 ES6 부터 Map,Set 등이 추가 되었습니다.
<br>
그럼 이제 본격적인 내용으로 들어가 보겠습니다.</p>
<h2 id="배열로-유연한-컬렉션을-생성하라">배열로 유연한 컬렉션을 생성하라</h2>
<p>컬렉션을 선택할 때는 해당 정보로 어떤 작업을 수행할 것인지 고려해야 합니다.
어떤 형태로든 조작 해야 한다면 배열이 가장 적합한 컬렉션입니다.
또한, 배열을 사용하지 않는 경우에도 반드시 배열에 적용되어 개념을 빌리게 됩니다.</p>
<h3 id="배열의-유연성">배열의 유연성</h3>
<p>배열은 놀라운 수준의 <strong>유연성</strong>을 갖추고 있습니다.
배열은 순서를 갖기 때문에 이를 기준으로 값을 <strong>추가</strong>하거나 <strong>제거</strong>할 수 있고 모든 위치에 <strong>값이 있는지 확인</strong>할 수도 있고 배열을 정렬해서 <strong>순서를 새로 지정</strong>할 수도 있습니다.</p>
<h3 id="객체와-배열의-연결">객체와 배열의 연결</h3>
<p>우리는 배열 외 다른 컬렉션도 사용하기는 해야 합니다.
그렇지만 먼저 배열을 깊이 이해하면 코드를 상당히 개선할 수 있습니다.</p>
<p>예를 들어 객체를 순회하려면 먼저 <strong><code>Object.keys()</code></strong>를 실행해서 객체의 키를 배열에 담은 후 생성한 배열을 이용해 순회합니다. 
배열을 객체와 반복문의 가교를 활용하는 것입니다.</p>
<h3 id="배열과-이터러블">배열과 이터러블</h3>
<p>배열은 여기저기 어디에나 등장하는데, 배열에 <strong>이터러블(iterable)</strong>이 내장되어 있기 때문입니다.
이터러블은 간단히 말해 <strong>&#39;컬렉션의 현재위치를 알고 있는 상태에서 컬렉션의 항목을 한 번에 하나씩 처리하는 방법&#39;</strong>입니다.
문자열처럼 자체적으로 이터러블이 존재하거나** <code>Object.keys()</code>** 처럼 이터러블로 변환할 수 있는 데이터 형식이라면 배열에 수행하는 모든 동작을 동일하게 실행할 수 있습니다.</p>
<h3 id="배열을-다른-구조와-확장">배열을 다른 구조와 확장</h3>
<p>또한 컬렉션 개념의 거의 대부분을 배열 형태로 표현할 수 있습니다.
즉, 배열을 특별한 컬렉션으로 쉽게 변환하거나 다시 배열로 만들 수 있습니다.</p>
<p>이와 같은 키-값 저장소의 경우</p>
<pre><code class="language-javascript">const dog={
    name: &#39;Don&#39;,
    color: &#39;black&#39;
};
dog.name;</code></pre>
<p>키-값 저장소와 동일한 개념을 2차원 배열로 설명할 수 있습니다.
내부의 배열은 두 가지 항목만 갖습니다.
첫 번째 항목은 키이고, 두 번째 항목은 값입니다.
이러한 구조를 <strong>키-값 쌍</strong>이라고 부르며,
특정한 키의 값을 찾을 때는 먼저 일치하는 키 이름을 찾고 두 번째 항목을 반환하면 됩니다.</p>
<pre><code class="language-javascript">const dogPair=[
    [&#39;name&#39;,&#39;Don&#39;],
    [&#39;color&#39;, &#39;black&#39;],
];
function getName(dog){
    return dog.find(attribute=&gt;{
        return attribute[0]===&#39;name&#39;;
    })[1];
}</code></pre>
<h2 id="includes로-존재-여부를-확인하라">Includes()로 존재 여부를 확인하라</h2>
<p>배열을 다룰 때는 <strong>존재 여부 확인</strong>이 필요합니다.
ES5때 까지는 존재 여부 확인이 다소 번거로웠습니다.</p>
<p>예를 들어 배열이 특정 문자열을 포함하고 있는지 확인하려면 문자열의 위치를 찾고 문자열이 존재하면 문자열의 색인으로 위치를 확인 할 수 있습니다.
반대로 문자열이 존재하지 않으면 -1이 반환됩니다.</p>
<p>이 문제는 색인이 0이 될 수 있는데 자바스크립트에서 0은 false(거짓)으로 평가됩니다.</p>
<p>따라서 실제로 <strong>존재하는 값이라도 확인 결과가 false</strong>로 평가될 수 있습니다.
그렇기 때문에 코드에는 숫자와 비교하는 과정을 거쳐야 합니다.</p>
<pre><code class="language-javascript">const sections=[&#39;contact&#39;, &#39;shipping&#39;];

function displayShipping(sections){
    return sections.indexOf(&#39;shipping&#39;) &gt; -1;
}</code></pre>
<p>하지만 <strong>ES6</strong>에 추가된 새로운 기능으로 이런 비교 절차를 생략할 수 있게 되었습니다.</p>
<p><strong>includes()</strong> 라는 새로운 배열 메서드를 이용하면 값이 배열에 존재하는지 여부를 확인해서 불값으로 true 또는 false를 반환합니다.</p>
<pre><code class="language-javascript">const sections=[&#39;contact&#39;, &#39;shipping&#39;];

function displayShipping(sections){
    return sections.includes(&#39;shipping&#39;);
}</code></pre>
<p>이렇게 includes()를 사용하면 -1로 비교하는 것을 누락해서 색인이 0인 경우를 false로 처리해버리는 실수를 피할 수 있습니다.</p>
<h2 id="펼침-연산자로-배열을-본떠라">펼침 연산자로 배열을 본떠라</h2>
<p>배열은 데이터를 다룰 때 높은 수준의 <strong>유연성</strong>을 제공합니다.
그렇지만 배열에 수많은 메서드가 있으므로 혼란스럽거나 조작과 부수 효과로 인한 문제에 맞닥뜨릴 수 있습니다.</p>
<p>이러한 문제는 <strong>펼침 연산자</strong>를 사용하면 최소한의 코드로 배열을 빠르게 생성하고 조작할 수 있습니다.</p>
<p>펼침 연산자는 <strong>마침표 세 개(...)</strong>로 표시하며 펼침 연산자의 기능으로는 배열에 포함된 항목을 <strong>목록</strong>으로 바꿔줍니다. 
목록은 매개변수 또는 새로운 배열을 생성할 때 사용할 수 있는 일련의 항목입니다.
펼침 연산자는 이런 작은 기능으로 여러 가지 이점을 가져다줍니다.</p>
<p>먼저 배열에서 항목을 제거하려고 할때 반복문만 사용한다면 다음과 같이 작성할 수 있습니다.</p>
<pre><code class="language-javascript">function removeItem(Items,removable){
    const updated=[];
    for (let i=0;i&lt;Items.length;i++){
        if(items[i]!==removable){
            updated.push(items[i]);
        }
    }
    return updated;
}</code></pre>
<p>이 코드는 꽤 많습니다.
가능 하면 코드를 단순하게 유지하는 것이 좋습니다.</p>
<p>코드를 단순하게 하려고 배열 메서드인 <code>splice()</code> 를 사용할 수도 있습니다.
이 메서드로 배열에서 항목을 제거할 수 있습니다.
위의 함수를 리팩토링을 거쳐 다음과 같이 단순한 함수로 수정합니다.</p>
<pre><code class="language-javascript">function removeItem(items, removable){
    const index=items.indexOf(removable);
    items.splice(index, 1);
    return items;
}</code></pre>
<p>이 코드의 문제는 <code>splice()</code> 메서드가 원본 배열을 조작한다는 점입니다.</p>
<pre><code class="language-javascript">const books=[&#39;practical vim&#39;, &#39;moby dick&#39;, &#39;the dark tower&#39;];
const recent=removeItem(books,&#39;moby dick&#39;);
const novels=removeItem(books,&#39;practical vim&#39;);</code></pre>
<p>배열 novels에는 &#39;the dark tower&#39;만 포함되어 있습니다.
처음 <code>removeItem()</code>을 호출할 때 배열 books를 전달하고 &#39;moby dick&#39;을 제거한 배열을 반환받습니다.
그렇지만 이 과정에서 배열 books도 변경었습니다.
다음 함수에 배열 books를 전달했을 때는 배열에 두 가지 항목만 남아있습니다.</p>
<p>이처럼 원본 배열이 직접 변경되는 상황은 예기치 못한 오류를 유발할 수 있어, 조작은 되도록 피하는 것이 좋습니다.
위에 경우에는 books를 <strong>const</strong>로 할당하기도 했습니다.
따라서 조작되지 않을 것이라고 생각할 수도 있지만 항상 그렇지는 않습니다.</p>
<p> <code>splice()</code> 메서드가 for 문의 괜찮은 대안처럼 보이겠지만, 조작은 많은 혼란을 가져오므로 가능하면 피하는 것이 좋습니다.</p>
<h3 id="slice">slice()</h3>
<p>끝으로 배열에는 <code>slice()</code>라는 방법이 하나 더 있습니다.
<code>slice()</code> 메서드는 원본 배열을 변경하지 않고 배열의 일부를 반환합니다.
<code>slice()</code> 메서드에 인수로 <strong>시작점과 종료점</strong>을 전달하면 그 사이에 있는 모든 항목을 반환합니다.
또는 종료점을 생략하고 시작점만 인수로 전달하면 시작점부터 배열의 마지막 항목까지 반환합니다.
그 후에 <code>concat()</code>을 이용해서 배열 조각을 연결할 수 있습니다.</p>
<pre><code class="language-javascript">function removeItem(items, removable){
    const index=items.indexOf(removable);
    return items.slice(0, index).concat(items.slice(index+1));
}
</code></pre>
<p>이 코드는 원본 배열을 변경하지도 않고 새로운 배열을 생성했으며 코드도 많지 않습니다.
그렇지만 무엇이 반환되는지 정확하지 않습니다.
무슨일이 일어나는지를 눈으로만 봐서는 정확히 어떤 작업을 하는 코드인지 알기 어렵습니다.</p>
<p>바로 이런 곳이 <strong>펼침 연산자</strong>를 사용하기에 적합합니다.
실제 배열처럼 보이기도 하고, 원래 배열에 영향을 주지 않고 새로운 배열을 생성해줍니다.</p>
<pre><code class="language-javascript">function removeItem(items, removable){
    const index=items.indexOf(removable);
    return [...items.slice(0,index),...items.slice(index+1)];
}
</code></pre>
<h2 id="push-매서드-대신-펼침-연산자로-원본-변경을-피하라">push() 매서드 대신 펼침 연산자로 원본 변경을 피하라</h2>
<p>배열을 조작하기 위해 흔히 사용하는 <code>push()</code> 메서드는 새로운 항목을 배열 뒤에 추가해 원본 배열을 변경합니다.
즉, 항목을 추가하면 원본 배열을 조작하는 것이다.
이 문제는 펼침 연산자를 이용하면 원본 배열이 조작되는 부수 효과를 방지할 수 있습니다.</p>
<p>장바구니 상품 목록을 받아서 내용을 요약하는 간단한 함수입니다.
이 함수는 할인 금액을 확인하고 할인 상품이 두 개 이상이면 오류 객체를 반환합니다. 만약 오류가 없다면 상품을 많이 구매한 사람에게 사은품을 줍니다.</p>
<pre><code class="language-javascript">const cart = [{
    name: &#39;The Foundation Triology&#39;,
    price: 19.99,
    discount: false,
},
{
    name: &#39;Godel, Escher, Bach&#39;,
    price: 15.99,
    discount: false,
},
{
    name: &#39;Red Mars&#39;,
    price: 5.99,
    discount: true,
},
];
const reward = {
    name: &#39;Guide to Science Fiction&#39;,
    discount: true,
    price: 0,
};
function addFreeGift(cart){
    if(cart.length&gt;2){
        cart.push(reward);
        return cart;
    }
    return cart;
}
function summarizeCart(cart){
    const discountable=cart.filter(item=&gt;item.discount);
    if(discountable.length&gt;1){
        return {
            error: &#39;할인 상품은 하나만 주문할 수 있습니다.&#39;,
        };
    }
    const cartWithReward=addFreeGift(cart);
    return {
        discounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward,
    };
}
</code></pre>
<p>이 코드는 조작이 위험해 보이지 않는 예시 입니다.
하지만 코드 정리를 위해 모든 변수를 함수의 상단으로 옮기면 어떻게 될까요?</p>
<pre><code class="language-javascript">function summarizeCartUpeated(cart){
    const cartWithReward=addFreeGift(cart);
    const discountable=cart.filter(item=&gt;item.discount);
    if(discountable.length&gt;1){
        return {
            error: &#39;할인 상품은 하나만 주문할 수 있습니다.&#39;,
        };
    }
    return {
        discounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward,
    };
}</code></pre>
<p>함수 addFreeGift()를 사용하면 배열 cart를 조작합니다.
상품이 세 개 이상이면, 할인이 적용된 아이템이 추가됩니다.
반환값, 즉 장바구니에 사은품을 추가해 새로운 변수에 할당하더라도, 원본 배열인 cart가 이미 조작된 후 입니다.
결국 상품을 세 가지 이상 선택하고 그 중 하나가 할인 상품인 모든 고객에게 오류가 발생합니다.</p>
<p>그러면 이 문제를 해결할 방법은 바로 펼침 연산자 입니다.</p>
<pre><code class="language-js">function addGift(cart){
    if (cart.length&gt;2){
        return [...cart,reward];
    }
    return cart;
}
function summarizeCartSpread(cart){
    const cartWithReward=addGift(cart);
    const discountable=cart.filter(item=&gt;item.discount);
    if(discountable.length&gt;1){
        return {
            error: &#39;할인 상품은 하나만 주문할 수 있습니다.&#39;,
        };
    }
    return {
        discounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward,
    };
}</code></pre>
<p>기존의 배열을 가져다 대괄호에 펼쳐 넣고, 새로운 상품을 배열의 마지막에 추가하면 됩니다.
결론적으로 내용을 목록으로 다시 쓰기만 하면 됩니다.
이렇게 하면 새로운 배열을 생성하기 때문에 원본 배열을 변경할 가능성은 전혀 없습니다.
<strong>원본의 내용만 재사용해 새로운 배열을 만드는 것입니다.</strong></p>
<h2 id="펼침-연산자로-정렬에-의한-혼란을-피하라">펼침 연산자로 정렬에 의한 혼란을 피하라</h2>
<p>지금까지 펼침 연산자를 이용해서 여러 가지 조작 함수를 대체하는 방법을 살펴보았습니다.
그렇다면 대체하기 쉽지 않은 함수가 있을 때는 어떻게 해야 할까요?
답은 펼침 연산자로 원본 배열의 <strong>사본을 생성</strong>하고 사본을 <strong>조작</strong>하면 됩니다.</p>
<p>직원 정보가 담긴 배열을 이름 또는 근속 연수를 기준으로 정렬하는 애플리케이션을 개발하려고 합니다.</p>
<p>먼저 직원 정보가 담긴 배열입니다.</p>
<pre><code class="language-js">const staff=[
    {
        name: &#39;Joe&#39;,
        years:10,
    },
    {
        name: &#39;Theo&#39;,
        years:5,
    },
    {
        name: &#39;Dyan&#39;,
        years:10,
    },
];</code></pre>
<p>다음으로 이름 또는 근속 연수로 정렬하는 몇가지 함수를 추가합니다.</p>
<pre><code class="language-js">function sortByYears(a,b){
    if (a.years===b.years){
        return 0;
    }
    return a.years-b.years;
}
const sortByName=(a,b)=&gt;{
    if (a.name===b.name){
        return 0;
    }
    return a.name&gt;b.name?1:-1;
};
</code></pre>
<p>이제 사용자가 열의 제목을 클릭하면 배열에서 정렬 함수를 호출합니다.
예를 들어 사용자가 근속 연수에 따라 정렬시키면 함수가 배열을 정렬하고 갱신합니다.</p>
<pre><code class="language-js">staff.sort(sortByYears);
// [
//     {
//         name: &#39;Theo&#39;,
//         years:5,
//     },
//     {
//         name: &#39;Joe&#39;,
//         years:10,
//     },
//     {
//         name: &#39;Dyan&#39;,
//         years:10,
//     },
// ]</code></pre>
<p>배열을 정렬할 때 해당 배열은 변경됩니다.
함수 실행이 끝난 것처럼 보일지라도, 실제로는 배열의 변경 사항이 그대로 유지됩니다.</p>
<p>이번에는 사용자가 이름순으로 정렬한 경우입니다.
역시나 배열은 조작됩니다.</p>
<pre><code class="language-js">staff.sort(sortByYears);
// [
//     {
//         name: &#39;Dyan&#39;,
//         years:10,
//     },
//     {
//         name: &#39;Joe&#39;,
//         years:10,
//     },
//     {
//         name: &#39;Theo&#39;,
//         years:5,
//     },
// ]</code></pre>
<p>이때 만약 사용자가 근속 연수를 기준으로 다시 정렬한다면 무슨 일이 벌어질까요?
근속 연수로 두 번째 정렬을 하고 나면 처음과는 전혀 다른 결과가 나옵니다.</p>
<pre><code class="language-js">staff.sort(sortByYears);
// [
//     {
//         name: &#39;Theo&#39;,
//         years:5,
//     },
//     {
//         name: &#39;Dyan&#39;,
//         years:10,
//     },
//     {
//         name: &#39;Joe&#39;,
//         years:10,
//     },
// ]</code></pre>
<p>이제 수백 명의 직원 목록에서 근속 연수가 동일한 직원이 여럿 있는 경우를 생각해봅시다.
사용자가 정렬 버튼을 누를 때마다 조금씩 순서가 바뀝니다.
매번 순서가 바뀐다면 사용자는 애플리케이션을 신뢰하지 못할 것입니다.</p>
<p>이를 해결하기 위해서는 원본 데이터를 조작하지 않으면 됩니다.
그 대신에 사본 을 만들고 사본을 조작합니다.
배열을 정렬하기 전에 원본 배열과 펼침 연산자로 새로운 배열을 만듭니다.</p>
<pre><code class="language-js">[...staff].sort(sortByYears);
// [
//     {
//         name: &#39;Theo&#39;,
//         years:5,
//     },
//     {
//         name: &#39;Joe&#39;,
//         years:10,
//     },
//     {
//         name: &#39;Dyan&#39;,
//         years:10,
//     },
// ]</code></pre>
<p>원본 배열을 변경하지 않으므로 이제 사용자는 마음대로 정렬 기능을 사용할 수 있습니다.
또한, 같은 기준으로 정렬했을 때 항상 같은 결과를 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[살아가며 꼭! 해야 하는 것]]></title>
            <link>https://velog.io/@j15u_k7/%EC%82%B4%EC%95%84%EA%B0%80%EB%A9%B0-%EA%BC%AD-%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%83</link>
            <guid>https://velog.io/@j15u_k7/%EC%82%B4%EC%95%84%EA%B0%80%EB%A9%B0-%EA%BC%AD-%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%83</guid>
            <pubDate>Tue, 22 Jul 2025 08:57:23 GMT</pubDate>
            <description><![CDATA[<p>살아가다 보면 이런 이야기들이 들립니다.</p>
<blockquote>
<p><strong><em>&quot;아 이거 한 번 해보고 싶은데 떨어질 거 같아서 못하겠어ㅜㅜ&quot;</em></strong></p>
</blockquote>
<blockquote>
<p><em><strong>&quot;도전해 보고 싶은데 안되면 너무 슬플거 같아서 안하려고..&quot;</strong></em></p>
</blockquote>
<p>이외에도 주변 사람들은 여러 이유로 인해 도전하는 것을 피합니다.</p>
<br>

<p>하지만 우리는 살아가며 꼭 해봐야 하는 것이** 도전**입니다.</p>
<p>저는 정말 여러 도전을 해봤습니다.
예를 들어 <strong>방송부, FE STUDY, 부회장, 해커톤, 프로젝트 등</strong> 
이런 도전을 해보며 도전을 쉽게 할 수 있는 방법, 어떻게 해야 나에게 도움이 되는 도전을 할 수 있는지 알게 된 거 같습니다.</p>
<p>그럼 제 경험들을 바탕으로 도전을 어떻게 해야 잘하고 쉽게 할 수 있는지 알아보겠습니다.</p>
<hr>
<h2 id="나의-방향-명확히-알기">나의 방향 명확히 알기</h2>
<p>먼저 내가 나아가고자 하는 방향을 분명히 아는 것이 중요하다고 생각합니다.
내가 어떤 진로를 선택할 것이고, 앞으로 무엇을 하고 싶은지 명확히 알고 있으면 내가 해야 할 일이 보이고, 그만큼 도전하는 것도 쉬워집니다.</p>
<p>예를 들어 내가 개발자가 되고 싶은데 영어 교육법 강의가 멋져 보여서 듣게 된다면, 그 강의가 나에게 도움이 될 수도 있겠지만 결국 내가 가려는 방향에 도움이 되지는 않습니다.</p>
<p>이처럼 나의 목표와 방향을 분명히 알고 있어야 쓸데없는 길로 새지 않고, 더 집중해서 나아갈 수 있다고 생각합니다.</p>
<h2 id="구체적인-목표-설정">구체적인 목표 설정</h2>
<p>먼저 내가 어떤 목표가 있고 그 목표가 어떤 방향으로 향하고 있는지, 내가 왜 이 목표를 세웠는지에 대한 명확한 이유가 있어야 한다고 생각합니다.</p>
<p>그러기 위해서는 구체적인 목표를 설정하는 것이 중요합니다.
구체적인 목표를 설정하기 위해서는 내가 가고자 하는 방향을 바탕으로, 작은 일부터 목표를 세우고 실천해보는 경험이 필요합니다.</p>
<p>예를 들어 내가 영어 과목 내신을 향상 시키고 싶다면 
단순히 <strong>&#39;영어 공부 열심히 하기&#39;</strong>가 아니라 <strong>&#39;영어 단어 10개 외우기&#39;</strong> 처럼 구체적이고 수치로 확인할 수 있는 목표를 세우는 것이 효과적입니다.</p>
<h2 id="실패를-두려워하지-않기">실패를 두려워하지 않기</h2>
<p>저는 사실 이게 제일 중요한 것 같습니다.
실패를 두려워하는 사람들은 그 두려움 때문에 도전하는 것을 피합니다.</p>
<p>하지만 진짜 중요한 것은 바로 그 실패를 통해 배우고 성장하는 것입니다.
실패를 두려워하지 않고, 오히려 실패를 발판 삼아 다시 도전하며 한 단계씩 올라가는 것이 중요하다 생각합니다.</p>
<p>한 유튜브 채널에서는 이렇게 말했습니다.</p>
<blockquote>
<p>&quot;실패는 추억이 될 수도, 후회가 될 수도 있다. 
하지만 도전하지 않는 것은 항상 후회로 남는다&quot;</p>
</blockquote>
<p>저는 이 말에 적극 동의 합니다.
실패를 통해서 우리는 자신의 부족한 점을 알게 되고, 그 부족함을 채워가며 다음 도전에서는 더 나은 결과를 만들어낼 수 있습니다.
하지만 도전조차 하지 않는다면, 우리는 스스로의 한계나 부족한 점을 알 기회조차 얻지 못합니다.</p>
<p>그렇기 때문에 우리는 실패를 두려워하지 말고, 끊임없이 도전하며 배우고 성장해 나가는 것 입니다.</p>
<h2 id="결과에-집착하지-않기">결과에 집착하지 않기</h2>
<p>결과에만 집착하게 되면 그 도전은 무의미한 도전이 되어버릴 수도 있습니다.</p>
<p>과도한 결과만을 바라보게 되면, 그 부담감 때문에 아예 도전을 포기해 버릴 수도 있기 때문에 앞서 말한 것처럼 결과보다는 과정에 집중하고, 꾸준히 노력하며 그 과정 속에서 내가 무엇을 배우고 깨달았는지를 느끼는 것이 더 중요하다고 생각합니다.</p>
<p>결과는 노력의 자연스러운 결과일 뿐, 과정 속에서 얻는 성장과 배움이 진짜 가치라고 믿습니다.</p>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>이처럼 우리는 살아가며 꼭 해야하는 것이 아니라, 누구나 자연스럽게 하고 있는 것이 바로 <strong>도전</strong>입니다.
우리는 일상 속에서 자기도 모르게 도전하고 있고, 그 과정에서 성장하고 있습니다.</p>
<p>도전은 거창한 것이 아닌 단순히 내가 변화를 향해 나아가기 위한 첫걸음이자 발판이라고 생각합니다.
<br>
<br>
도전하는 것을 두려워하거나 피하는 사람이 있다면 이 글을 읽고 자신감을 가지고 다양한 도전에 나섰으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Position]]></title>
            <link>https://velog.io/@j15u_k7/Position</link>
            <guid>https://velog.io/@j15u_k7/Position</guid>
            <pubDate>Sun, 15 Jun 2025 13:39:09 GMT</pubDate>
            <description><![CDATA[<h1 id="position이란">Position이란?</h1>
<blockquote>
<p>** position 이란 css  코드에서 html 문서 상의 요소들이 배치되는 방식을 결정하는 속성입니다.**</p>
</blockquote>
<p>쉽게 말해 요소들의 <strong>위치</strong>를 결정합니다.
<br></p>
<h2 id="position-속성-종류">Position 속성 종류</h2>
<p>종류로는 <strong>static, relative, absolute, fixed, sticky</strong> 다섯가지가 있습니다.</p>
<p>이 요소들은 정확한 위치를 위해 <strong>top, left, bottom, right</strong> 속성과 함께 사용됩니다.</p>
<h2 id="static">static</h2>
<p>모든 태그들은 position 속성을 별도로 지정해주지 않으면 <strong>기본값인 static</strong> 적용됩니다.
position 속성이 static 인 요소는 HTML 문서 상에서 있어야 하는 위치에 배치됩니다.
그리하여 top, left, bottom, right 속성 값은 position: static 일 때 <strong>무시</strong>됩니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/8d6125f5-d3f5-4ef3-a1f4-13f74c19122b/image.png" alt=""></p>
<h2 id="relative">relative</h2>
<p>position 속성을 relative 로 설정하게 되면 요소를 원래 위치에서 <strong>벗어나게 배치</strong>할 수 있습니다.</p>
<p>요소의 위치 지정은 top, bottom, left, right 속성을 이용해서 요소가 원래 위치에 있을 때의 상하좌우로 부터 얼마나 떨어지게 할지를 지정합니다.</p>
<p><img src="https://velog.velcdn.com/images/j15u_k7/post/db715559-b447-4a95-a9df-58041c51043f/image.png" alt=""></p>
<p><code>.relative1{
    position:relative;
    top: 15px;
}</code></p>
<p>여기서 <strong>bottom</strong> 이 아닌 <strong>top</strong> 을 썼는데 왜 static1 아래로 갔을까요?
그 이유는 relative 속성은 각각의 방향을 기준으로 <strong>태그 안쪽으로 이동</strong>하기 때문입니다.</p>
<h2 id="absolute">absolute</h2>
<p>relative 가 static인 상태를 기준으로 주어진 픽셀만큼 움직였다면, absolute 는 position: static; 속성을 가지고 있지 않은 부모 요소를 기준으로 움직입니다.
만약 부모 요소 중에 position 이 relative, absolute, fixed 인 태그가 없다면 뷰포트가 기준이 됩니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/cd7b4102-d053-4c10-b1d1-66a696167930/image.png" alt="">
<img src="https://velog.velcdn.com/images/j15u_k7/post/fbc5dbb0-796e-4a08-a027-0d6384afd232/image.png" alt="">
<img src="https://velog.velcdn.com/images/j15u_k7/post/d2d3f321-7194-479a-a77e-e6c5a3d9050a/image.png" alt=""></p>
<p>위 코드를 보면 absolute 는 부모 태그 중 postion: relative 인 것이 없기 때문에 body 를 기준으로 <strong>가장 오른쪽</strong>으로 달라붙었습니다. 
하지만 child는 부모 태그인 parent 가** position: relative**이기 때문에 그것을 기준으로 가장 오른쪽으로 달라붙었습니다.</p>
<p>참고로 absolute가 되면 div여도 더는 width: 100%가 아닙니다.!</p>
<h2 id="fixed">fixed</h2>
<p>position 속성을 fixed 로 지정하면 요소를 항상 <strong>고정된 위치</strong>에 배치할 수 있습니다.
이게 가능한 이유는 fixed 속성 값의 배치 기준이 자신이나 부모 요소가 아닌 <strong>뷰포트(viewport)</strong>, 즉 브라우저 전체화면이기 때문입니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/91d12f00-72f7-49fe-9ecb-2e191f4eadec/image.gif" alt=""></p>
<h2 id="sticky">sticky</h2>
<p>position sticky 는 요소가 평소에는 relative 처럼 흐름대로 배치되다가, 
스크롤로 특정 위치(top, left 등)에 도달하면 fixed 처럼 고정 되는 속성입니다.
<img src="https://velog.velcdn.com/images/j15u_k7/post/a484632b-4e49-4c29-96fe-d0af981b4ea8/image.gif" alt="">
<code>.sticky{
  position: sticky;
  top: 100px;
}</code>
위 코드는 .sticky 가 스크롤 되다가 top: 100px; 도달 하면 그 위치에 고정 된다는 의미입니다.</p>
]]></description>
        </item>
    </channel>
</rss>