<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>min_ha.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드를 공부하고 있는 학생입니다🐌</description>
        <lastBuildDate>Mon, 13 Jan 2025 05:53:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>min_ha.log</title>
            <url>https://velog.velcdn.com/images/min_ha/profile/cad4ccb2-d0f0-46f4-9041-eba045add9d4/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. min_ha.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/min_ha" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY91 - Vue(7) vue-router & pinia]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue7-vue-router-pinia</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue7-vue-router-pinia</guid>
            <pubDate>Mon, 13 Jan 2025 05:53:05 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-url-구조">✅ URL 구조</h2>
<p><img src="https://velog.velcdn.com/images/min_ha/post/d2d3fb03-182d-49c0-8fc4-36552d207462/image.png" alt=""></p>
<p><br /><br /></p>
<h2 id="✅-vue-router">✅ Vue Router</h2>
<h3 id="1-세팅-및-라우터-인스턴스-생성">1. 세팅 및 라우터 인스턴스 생성</h3>
<ul>
<li>설치 : <code>npm install vue-router</code></li>
<li>main.js 설정</li>
</ul>
<pre><code class="language-tsx">import { createApp } from &#39;vue&#39;;
import App from &#39;./App.vue&#39;;
import router from &#39;./router&#39;;

const app = createApp(App);
app.use(router);
app.mount(&#39;#app&#39;);</code></pre>
<pre><code class="language-tsx">// router/index.js

const router = createRouter({
    history: createWebHistory(), // HTML5 History API를 기반으로 하는 라우팅 모드
    routes: [ ]
});</code></pre>
<br />

<h3 id="2-router-생성">2. router 생성</h3>
<ul>
<li>정적 import<ul>
<li>장점 : 미리 불러오기 때문에 리소스에 대한 로딩이 빠르다</li>
<li>단점 : 필요없는 페이지에 대한 컴포넌트도 모두 불러온다</li>
</ul>
</li>
<li>동적 import<ul>
<li>장점 : 필요한 페이지만 불러와서 초기 로딩 속도가 빠르다</li>
<li>단점 : 페이지 이동 시마다 컴포넌트를 불러와야 한다</li>
</ul>
</li>
<li>vue 파일 내에서 vue-router 관련 import는 따로 하지 않아도, 전역으로 깔려있다.</li>
</ul>
<h4 id="형식">형식</h4>
<pre><code class="language-tsx">const routes = createRouter({
  history: // 히스토리 관리 방식식 정의
  routes: // 라우트 객체 포함
});</code></pre>
<h4 id="history-속성">history 속성</h4>
<ul>
<li>지정할 수 있는 값 종류<ul>
<li><code>createWebHistory</code> : HTML5 히스토리 모드 사용. 슬래시(/)로 URL 관리</li>
<li><code>createWebHashHistory</code> : 해시 모드 사용. 해시(#)로 URL 관리</li>
</ul>
</li>
<li>인자에 baseURL를 넣어 기본 URL 설정 가능</li>
</ul>
<h4 id="코드">코드</h4>
<pre><code class="language-tsx">routes: [ // 라우트 정의
  { // 정적 import
    path: &#39;/&#39;, // URL 경로
    name: &#39;Home&#39;, // 라우터 고유한 이름
    component: Home, // URL로 접근했을 때 표시될 컴포넌트
  },
  { // 동적 import
      path: &#39;/about&#39;,
      name: &#39;About&#39;,
      component: () =&gt; import (&quot;@/pages/About.vue&quot;),
      redirect: { name: &#39;AboutNew&#39; }, // path를 바꾸는 과도기 단계, 리다이렉트 시키기
    },
    {
    // 기존 /about을 이 경로의 이름을 바꾸고 싶을 때
    path: &#39;/about-new&#39;,
    name: &#39;AboutNew&#39;,
    component: () =&gt; import(&#39;@/pages/About.vue&#39;),
    meta: { // 라우트에 대한 추가 정보 제공
      title: &#39;About&#39;,
      key: &#39;value&#39;,
  },
  {
    path: &#39;/profile/:id&#39;, // 동적 params
    name: &#39;Profile&#39;,
    redirect: (to) =&gt; {
      return {
        name: &#39;Profile-new&#39;,
        params: { id: to.params.id }, // 리디렉션 할 때, 동적 params 전달 방식
        query: { q: to.query.q }, // 리디렉션 할 때, query 전달 방식
      };
    },
  },
  {
    path: &#39;/profile-new/:id&#39;,
    name: &#39;Profile-new&#39;,
    component: () =&gt; import(&#39;@/pages/Profile.vue&#39;),
    props: (route) =&gt; ({
        ...route.params, // URL 파라미터를 그대로 props로 전달
        customProp: &#39;someValue&#39;, // 추가적인 props
      }),
  },
  {
    path: &#39;/dashboard&#39;,
    name: &#39;Dashboard&#39;,
    components: {
        // 동일한 이름의 RouterView로 매칭되어 렌더링
      default: () =&gt; import(&#39;@/pages/Dashboard.vue&#39;),
      header: () =&gt; import(&#39;@/components/DashboardHeader.vue&#39;),
      footer: () =&gt; import(&#39;@/components/DashboardFooter.vue&#39;),
    },
    alias: &#39;/dash&#39;, // URL 경로 별칭 설정 (/dashboard, /dash 둘 다 가능, 배열로 여러 개 설정 가능)
  },
  {
    path: &#39;/product&#39;,
    name: &#39;Product&#39;,
    component: () =&gt; import(&#39;@/pages/Product.vue&#39;), // 이게 없으면 묶어주는 역할로만
    children: [ // 하위 path
      {
        path: &#39;info&#39;,
        name: &#39;ProductInfo&#39;,
        component: () =&gt; import(&#39;@/pages/ProductInfo.vue&#39;),
      },
      {
        path: &#39;:item&#39;,
        name: &#39;ProductItem&#39;,
        component: () =&gt; import(&#39;@/pages/ProductItem.vue&#39;),
        props: true, // URL 파라미터를 props로도 내려줌
      },
    ],
  },
  {
    path: &#39;/:pathMatch(.*)*&#39;, // 와일드카드 경로 매칭 (사용자 정의 외 다른 모든 경로)
    name: &#39;NotFound&#39;,
    component: () =&gt; import(&#39;@/pages/NotFound.vue&#39;),
  },
  {
    path: &#39;/user-:afterUser(.*)&#39;, // user-* 형식의 url 매칭
    component: () =&gt; import(&#39;@/pages/UserGeneric.vue&#39;),
  },
],</code></pre>
<br />

<h3 id="3-vue-에-라우팅-적용">3. vue 에 라우팅 적용</h3>
<h4 id="라우팅된-컴포넌트-렌더링">라우팅된 컴포넌트 렌더링</h4>
<pre><code class="language-tsx">&lt;template&gt;
    &lt;RouterView /&gt;
&lt;/template&gt;

혹은

&lt;template&gt;
    &lt;RouterView name=&quot;header&quot; /&gt;
    &lt;RouterView /&gt;
    &lt;RouterView name=&quot;footer&quot; /&gt;
&lt;/template&gt;</code></pre>
<h4 id="링크-생성">링크 생성</h4>
<pre><code class="language-tsx">&lt;template&gt;
  &lt;nav&gt;
    &lt;RouterLink to=&quot;/&quot;&gt;Home&lt;/RouterLink&gt;
    &lt;RouterLink to=&quot;/about&quot;&gt;About&lt;/RouterLink&gt;
    &lt;RouterLink :to=&quot;{ name: &#39;About&#39; }&quot;&gt;About2&lt;/RouterLink&gt; // 이 방식도 가능
    &lt;RouterLink
      :to=&quot;{
        name: &#39;User&#39;,
        params: { user: &#39;mike&#39;, id: &#39;1&#39; },
        query: { lang: &#39;ko&#39; },
      }&quot;&gt;
      User
    &lt;/RouterLink&gt;
    &lt;RouterLink :to=&quot;{ name: &#39;ProductInfo&#39; }&quot;&gt;Product&lt;/RouterLink&gt;
    &lt;RouterLink :to=&quot;{ name: &#39;ProductItem&#39;, params: { item: &#39;2&#39; } }&quot;
      &gt;Product2&lt;/RouterLink
    &gt;
  &lt;/nav&gt;
&lt;/template&gt;</code></pre>
<h4 id="프로그래밍-탐색">프로그래밍 탐색</h4>
<ul>
<li>useRouter 사용<ul>
<li>Vue Router에서 제공하는 컴포저블(composable) 훅</li>
<li>라우터 인스턴스에 접근할 수 있도록 해주는 훅</li>
<li>프로그래밍적으로 라우팅을 변경하거나, 라우트 탐색 제어</li>
</ul>
</li>
<li>useRouter로 사용할 수 있는 메서드<ul>
<li>push</li>
<li>replace : 이전 페이지의 히스토리를 남기지 않음</li>
<li>forward, back, go</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  const router = useRouter();
  const handleMethod = () =&gt; {
    //router.push(&#39;/&#39;);
    //router.replace(&#39;/&#39;);
    router.push({
      name: &#39;User&#39;,
      params: { user: &#39;mike&#39;, id: &#39;1&#39; },
      query: { lang: &#39;ko&#39; },
    });
  };
&lt;/script&gt;

&lt;template&gt;
  &lt;h1&gt;Not Found&lt;/h1&gt;
  &lt;button @click=&quot;handleMethod&quot;&gt;홈으로 돌아가기&lt;/button&gt;
&lt;/template&gt;</code></pre>
<h4 id="동적-라우트-파라미터--쿼리-스트링">동적 라우트 파라미터 &amp; 쿼리 스트링</h4>
<ul>
<li>useRoute 사용<ul>
<li>현재 라우트 정보를 가져오는 컴포저블(composable) 훅</li>
<li>현재 경로, 동적 파라미터, 쿼리 문자열 등 현재 라우트와 관련된 정보 접근</li>
</ul>
</li>
<li>useRoute로 사용할 수 있는 속성<ul>
<li>path : 현재 경로</li>
<li>params : 동적 세그먼트</li>
<li>query : 쿼리 파라미터</li>
<li>name : 라우트 이름</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  const route = useRoute(); // 리액트에서는 훅, vue에서는 컴포져블
  const { id, user } = route.params;
  const { lang } = route.query;

  // 라우트 설정 시, props: true하면 사용 가능
  // props는 params만 가능 (즉, 세그먼트만 가능)
  // query string은 불가
  const props = defineProps({
    user: String,
    id: String,
  });
&lt;/script&gt;

&lt;template&gt;
  &lt;h1&gt;User&lt;/h1&gt;
  &lt;p&gt;{{ id }} / {{ user }} / {{ lang }}&lt;/p&gt;
  &lt;p&gt;{{ props.id }} / {{ props.user }}&lt;/p&gt;
&lt;/template&gt;
</code></pre>
<br />

<h3 id="4-네비게이션-가드">4. 네비게이션 가드</h3>
<ul>
<li>라우팅 과정 중에 특정 지점에서 라우팅 전환을 가로채서 사용자 정의 기능을 추가할 때 사용<ul>
<li>라우트 접근 제어 가능</li>
<li>다른 라우트로 리다이렉션 가능</li>
<li>라우트 변경 전에 데이터 불러오기 가능</li>
</ul>
</li>
</ul>
<h4 id="전역-가드">전역 가드</h4>
<ul>
<li>모든 라우트가 공통적으로 영향 받음</li>
<li>라우트 전환 전 방식 : <code>beforeEach((현재값, 이전값, 이동 함수) =&gt; {})</code></li>
<li>라우팅 전환 직전 방식 : <code>router.beforeResolve((현재값, 이전값, 이동 함수) =&gt; {})</code><ul>
<li>beforeEach로 라우트 확정된 이후 호출</li>
</ul>
</li>
<li>라우트 전환 후 방식 : <code>afterEach((현재값, 이전값, 실패 정보}) =&gt; {})</code></li>
</ul>
<pre><code class="language-tsx">const router = createRouter({
  routes: [],
})

// 라우트 전환 전 방식
router.beforeEach((to, from, next) =&gt; {
    // next를 반드시 호출해야 페이지 이동 가능
    // next(false)로 의도적으로 페이지 이동 막기 가능

    // 권한 체크를 하는 등의 로직
    const isAuthenticated = false;
    if (to.path === &#39;/dashboard&#39;, &amp;&amp; !isAuthenticated) {
      next(&#39;/login&#39;)
    }

    // meta 속성을 이용해 탭 이름 변경 가능
    if(to.meta.title) {
      document.title = to.meta.title
    }

    next()
})

// 라우트 전환 직전 발생
router.beforeResolve((to, from, next) =&gt; {})

// 라우트 전환 후 실행
router.afterEach((to, from, failure) =&gt; {
  // ...
});</code></pre>
<h4 id="라우트별-가드">라우트별 가드</h4>
<ul>
<li>특정 라우트 전환에서만 호출됨</li>
<li>라우트 객체 내에서 직접 설정</li>
<li><code>beforeEnter(현재값, 이전값, 이동 함수)</code></li>
</ul>
<pre><code class="language-tsx">{
  path: &#39;/about&#39;,
  name: &#39;about&#39;,
  component: () =&gt; import(&#39;../views/AboutView.vue&#39;),
  beforeEnter: (to, from, next) =&gt; {
    // beforeEach와 동일
    // ...

    next()
  },
},</code></pre>
<h4 id="컴포넌트-내-가드">컴포넌트 내 가드</h4>
<ul>
<li><code>onBeforeRouteUpdate((미래값, 현재값) =&gt; {})</code> : 동적 경로 매칭 - 동적 세그먼트 값 변경 시 호출</li>
<li><code>onBeforeRouteLeave()</code> : 라우트 벗어나기 전 호출</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
onBeforeRouteUpdate((to, from) =&gt; {
  // 동적 세그먼트 값 변경 시 호출
    return true; // 페이지 이동 허용
    return false; // 페이지 이동 금지
}

onBeforeRouteLeave((to, from) =&gt; {
    // 라우트 떠나기 전 호출
    return true; // 페이지 전환 허용
    return false; // 페이지 전환 금지
})
&lt;/script&gt;</code></pre>
<br />

<h3 id="5-스크롤-동작">5. 스크롤 동작</h3>
<ul>
<li>SPA 기반이기 때문에 라우트 전환 시 페이지가 새로고침 되지 않고 DOM만 업데이트 됨. 따라서 페이지가 전환되어도 스크롤이 유지됨</li>
<li>스크롤을 다시 상단으로 올리고 싶다면 스크롤 동작 함수 사용 가능<ul>
<li><code>scrollBehavior((미래값, 현재값, 스크롤 위치 객체) =&gt; {})</code></li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">const router = createRouter({
    history: ...
  routes: ...
  scrollBehavior((to, from, savedPosition) =&gt; {
      // savedPosition은 브라우저의 앞/뒤 버튼을 눌렀을 때만 사용 가능
      // 일반적인 라우트 전환에서는 undefined

      // 브라우저 앞/뒤 버튼 누를 시, 스크롤 유지
      // 그 외 라우트는 맨 위로
      if(savedPosition) return savedPosition;
      esle return { top: 0; }
  })</code></pre>
<p>`</p>
<h4 id="해시-주소를-이용해서-스크롤-이동">해시 주소를 이용해서 스크롤 이동</h4>
<pre><code class="language-tsx">scrollBehavior((to, from, savedPosition) =&gt; {
    if(savedPosition) return savedPosition;

    // id의 속성값과 to.hash가 일치한 요소로
    // 위치에서 10px 만큼 위로 이동
    // 부드러운 스크롤 이동
    if(to.hash) return { el: to.hash, top:10,  behavior: &#39;smooth&#39; }
})</code></pre>
<p><br /><br /></p>
<h2 id="✅-pinia">✅ Pinia</h2>
<ul>
<li>Vue의 전역 상태 관리 라이브러리</li>
</ul>
<h3 id="1-세팅">1. 세팅</h3>
<ul>
<li>설치 : <code>npm install pinia</code></li>
<li>main.js 설정</li>
</ul>
<pre><code class="language-tsx">const pinia = createPinia(); // pinia 인스턴스 생성
const app = createApp(App);

app.use(pinia); // pinia 인스턴스 등록
app.mount(&#39;#app&#39;);</code></pre>
<br />

<h3 id="2-스토어">2. 스토어</h3>
<ul>
<li>애플리케이션의 상태를 중앙에서 관리하기 위한 것</li>
<li>보통 stores 폴더를 만들어 관리</li>
<li>기본 형태 (셋업 스토어)</li>
</ul>
<pre><code class="language-tsx">export const useCountStore = defineStore(&#39;countStore&#39;, () =&gt; {});</code></pre>
<h4 id="예시">예시</h4>
<pre><code class="language-tsx">export const useCountStore = defineStore(&#39;countStore&#39;, () =&gt; {
    const count = ref(0);
    const doubleCount = computed(() =&gt; count.value * 2);
    const increment = () =&gt; count.value++;

    return { count, doubleCount, increment };
});</code></pre>
<br />

<h3 id="3-전역-상태-사용">3. 전역 상태 사용</h3>
<ul>
<li>store를 호출하여 사용</li>
<li>객체 비구조화 할당<ul>
<li>반응형이 깨져 화면에 업데이트가 되지 않음. 함수는 가능</li>
<li>storeToRefs로 해결 가능</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  const countStore = useCountStore();
&lt;/script&gt;
&lt;template&gt;
  &lt;p&gt;{{ countStore.count }}&lt;/p&gt;
  &lt;button @click=&quot;countStore.increment&quot;&gt;증가&lt;/button&gt;
&lt;/template&gt;</code></pre>
<pre><code class="language-tsx">&lt;script setup&gt;
  const countStore = useCountStore();
  const { count, doubleCount } = storeToRefs(countStore);
  const { increment } = countStore;
&lt;/script&gt;
&lt;template&gt;
  &lt;p&gt;{{ count }} / {{ doubleCount }}&lt;/p&gt;
  &lt;button @click=&quot;increment&quot;&gt;증가&lt;/button&gt;
&lt;/template&gt;</code></pre>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY90 - Vue(6) slot & composition api]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue6-slot-composition-api</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue6-slot-composition-api</guid>
            <pubDate>Sat, 11 Jan 2025 18:18:27 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-컴포넌트-슬롯">✅ 컴포넌트 슬롯</h2>
<h3 id="1-슬롯이란">1. 슬롯이란</h3>
<ul>
<li>컴포넌트의 특정 영역을 대체하는 기술</li>
<li>리액트의 Children과 동일한 역할<ul>
<li>부모가 템플릿 조각을 자식 컴포넌트에 전달</li>
<li>자식 컴포넌트가 전달받은 템플릿 조각을 렌더링할 수 있게 하는 기능</li>
</ul>
</li>
<li>슬롯을 설정해두지 않으면 자식 컴포넌트로 감싸도 렌더링되지 않음</li>
<li>부모 컴포넌트에서 동일한 슬롯 이름으로 여러번 호출 시, 마지막 요소만 적용</li>
</ul>
<br />

<h3 id="2-슬롯--사용-방식">2. 슬롯  사용 방식</h3>
<h4 id="이름이-없는-슬롯">이름이 없는 슬롯</h4>
<ul>
<li><code>&lt;slot&gt;&lt;/slot&gt;</code></li>
<li>자식 컴포넌트 사이에 들어가는 콘텐츠가 <code>&lt;slot&gt;&lt;/slot&gt;</code> 대신에 들어가게 됨</li>
</ul>
<pre><code class="language-tsx">// 부모 컴포넌트
&lt;Button&gt;버튼&lt;/Button&gt;

// Button 컴포넌트
// &lt;slot&gt;&lt;/slot&gt; 대신 버튼이 들어감
&lt;slot&gt;&lt;/slot&gt;</code></pre>
<h4 id="이름이-있는-슬롯">이름이 있는 슬롯</h4>
<ul>
<li><code>&lt;slot name=&quot;값&quot;&gt;&lt;/slot&gt;</code></li>
<li>부모 컴포넌트에서 <code>v-slot</code>으로 전달하는  값과 자식 컴포넌트의  name이 동일한 곳에 콘텐츠가 대신 들어감</li>
<li>단, 부모 컴포넌트에서의 <code>v-slot</code> 는 반드시 <code>&lt;template&gt;</code> 태그에 선언해야 함</li>
<li>일치하는 슬롯이 없으면 보여지지 않음</li>
<li>축약형 : <code>v-slot:값</code> ⇒ <code>#값</code></li>
</ul>
<pre><code class="language-tsx">// 부모 컴포넌트
&lt;template v-slot:값&gt;
    어쩌구 저쩌구
&lt;/template&gt;

// Layout 컴포넌트
&lt;slot name=&quot;값&quot;&gt;&lt;/slot&gt;</code></pre>
<h4 id="혼합-슬롯">혼합 슬롯</h4>
<ul>
<li>이름이 없는 슬롯 + 이름이 있는 슬롯을 함께 사용하는 것</li>
<li><code>&lt;slot&gt;&lt;/slot&gt;</code>은 <code>name=&quot;default&quot;</code>가 자동으로 적용된 상태</li>
<li><code>v-slot</code> 혹은 <code>#</code>으로 넘어오는 값이 유효하지 않거나 없는 경우, default로 정의된 slot에 들어감</li>
</ul>
<pre><code class="language-tsx">&lt;slot name=&quot;name&quot;&gt;&lt;/slot&gt;
&lt;slot&gt;&lt;/slot&gt; // 디폴트</code></pre>
<h4 id="슬롯의-기본값">슬롯의 기본값</h4>
<ul>
<li>부모에서 콘텐츠 없이 자식 컴포넌트만 호출한 경우, 보여질 기본값</li>
<li>콘텐츠가 있다면 기본값은 적용되지 않음</li>
</ul>
<pre><code class="language-tsx">// 부모 컴포넌트
&lt;Child&gt;&lt;/Child&gt; // &quot;기본값&quot;으로 렌더링
&lt;Child&gt;안녕&lt;/Child&gt; // &quot;안녕&quot;으로 렌더링

// Child 컴포넌트
&lt;slot&gt;기본값&lt;/slot&gt;</code></pre>
<h4 id="동적-슬롯">동적 슬롯</h4>
<ul>
<li>슬롯의 이름을 동적으로 지정하는 것</li>
<li>동적으로 지정한다 === 슬롯의 이름을 변수로 지정해 넣는다</li>
<li>자식 컴포넌트의 slot 이름은 지정이 되어야함</li>
<li><code>v-slot=&quot;데이터&quot;</code> 아님 유의</li>
</ul>
<pre><code class="language-tsx">&lt;template v-slot:[dynamicName]&gt;&lt;/template&gt;
&lt;template #[dynamicName]&gt;&lt;/template&gt;</code></pre>
<h4 id="슬롯-기본-범위">슬롯 기본 범위</h4>
<ul>
<li>정리) 스타일, 데이터 모두 기본적으로 부모 컴포넌트의 영향을 받음</li>
<li>슬롯 자리에 들어가 렌더링 되는 요소는 각각 고유한 범위가 있음</li>
<li>슬롯의 스타일은 부모 컴포넌트의 영향을 받음<ul>
<li>부모에서 사용된 요소들은 자식 컴포넌트 스타일이 적용되지 않음</li>
</ul>
</li>
<li>슬롯의 데이터는 부모 컴포넌트의 영향을 받음<ul>
<li>부모에는 없으면서 자식에는 있는 데이터를 사용하는 경우에도 전혀 적용되지 않음</li>
</ul>
</li>
<li>슬롯의 범위를 지정하면 자식 컴포넌트의 데이터 사용 가능</li>
</ul>
<h4 id="슬롯-범위-지정">슬롯 범위 지정</h4>
<ul>
<li>자식 컴포넌트의 범위에 접근할 수 있는 방법 제공<ul>
<li>부모에 전달할 데이터를 슬롯에 v-bind로 미리 정의 (computed를 포함한 모든 데이터 가능)</li>
<li>부모는 전달된 데이터를 v-slot을 통해 객체 형식으로 받음</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">// 자식 컴포넌트
// 데이터는 이미 정의되었다고 가정
&lt;slot :childData=&quot;childData&quot;&gt;&lt;/slot&gt;

// 부모컴포넌트
// 자식 컴포넌트가 전달하는 데이터들은 객체 형식으로 받음
// 받을 데이터 명은 원하는대로 지정 가능
&lt;Child v-slot=&quot;myKey&quot;&gt;{{ myKey.childData }}&lt;/Child&gt;

// 객체 분할로 사용 가능
&lt;Child v-slot=&quot;{ childData }&quot;&gt;{{ childData }}&lt;/Child&gt;</code></pre>
<ul>
<li>이름있는 슬롯으로 사용할 경우 아래와 같음</li>
</ul>
<pre><code class="language-tsx">// 자식 컴포넌트
&lt;slot name=&quot;section&quot; :childData=&quot;childData&quot;&gt;&lt;/slot&gt;

// 부모 컴포넌트
&lt;Child #section=&quot;myKey&quot;&gt;{{ myKey.childData }}&lt;/Child&gt;
&lt;Child #section=&quot;{ childData }&quot;&gt;{{ childData }}&lt;/Child&gt;</code></pre>
<p><br /><br /></p>
<h2 id="✅-컴포지션-api">✅ 컴포지션 API</h2>
<ul>
<li>Vue3에 추가된 문법 ⇒ <code>setUp()</code> 훅을 통해 사용 가능</li>
<li>Vue3.2에 추가된 설탕 문법 ⇒ <code>&lt;script setup&gt;&lt;/script&gt;</code></li>
</ul>
<h3 id="1-반응형-데이터-ref와-reactive">1. 반응형 데이터, ref와 reactive</h3>
<ul>
<li><code>ref</code> : 기본 자료형을 반응형 데이터로 정의할 때</li>
<li><code>reactive</code> : 참조 자료형을 반응형 데이터로 정의할 때<ul>
<li>기본 자료형은 변경 감지 안 됨</li>
</ul>
</li>
</ul>
<h4 id="script-내부에서-반응형-데이터-접근">script 내부에서 반응형 데이터 접근</h4>
<ul>
<li>ref로 정의된 데이터는 value를 통해서 접근</li>
<li>reactive로 정의된 데이터는 일반 객체처럼 접근</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  import { computed, reactive, ref } from &#39;vue&#39;;

    const count = ref(0);
    const array = reactive([1, 2, 3]);

    console.log(count.value, array[0]);
&lt;/script&gt;</code></pre>
<h4 id="template-내부에서-반응형-데이터-접근">template 내부에서 반응형 데이터 접근</h4>
<pre><code class="language-tsx">&lt;template&gt;
  &lt;div&gt;{{ count }}&lt;/div&gt;
  &lt;div&gt;{{ array[0] }}&lt;/div&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="2-계산된-속성-computed">2. 계산된 속성, computed</h3>
<ul>
<li>데이터 가공, 캐싱에 사용 (읽기 전용)</li>
<li><code>computed</code> 함수 사용</li>
<li>script 태그 내부에서 사용할 때, value를 통해서 접근</li>
<li>template 태그 내부에서는 바로 사용</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
    import { computed, ref } from &#39;vue&#39;;

  const count = ref(0);
  const doubleCount = computed(() =&gt; count.value * 2);

  console.log(doubleCount.value)
&lt;/script&gt;
&lt;template&gt;
  &lt;div&gt;{{ doubleCount }}&lt;/div&gt;
&lt;/template&gt;</code></pre>
<ul>
<li>computed 속성을 직접 변경하는 방법 ⇒ get과 set 함수로 세분화</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
    import { computed, ref } from &#39;vue&#39;;

  const firstName = ref(&quot;Minha&quot;);
  const lastName = ref(&quot;An&quot;);

  const fullName = computed({
      get() {
          return `${lastName.value} ${firstName.value}`
        },
      set(value) {
          [lastName.value, firstName.value] = value.split(&quot; &quot;);
        },
  });
&lt;/script&gt;
&lt;template&gt;
  &lt;div&gt;{{ doubleCount }}&lt;/div&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="3-함수">3. 함수</h3>
<ul>
<li>함수 선언식, 함수 표현식, 화살표 함수 모두 사용 가능</li>
<li>ref, reactive, computed 모두 접근 가능</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
    import { ref } from &#39;vue&#39;;

    const count = ref(0);
    const increment = count.value++;
&lt;/script&gt;</code></pre>
<br />

<h3 id="4-감시자-속성-1---watch">4. 감시자 속성 1 - watch</h3>
<ul>
<li>반응형으로 선언된 데이터의 값이 변경되었을 때, 이를 감시하여 코드를 실행시키는 속성</li>
<li>인자 목록<ul>
<li>첫번째 인자 : 감시할 반응형 데이터</li>
<li>두번째 인자 : 콜백함수 (감시한 데이터가 변경될 때 실행 / 현재값, 이전값이 순서대로 인자로 들어옴)</li>
<li>세번째 인자 : (선택) 보통 deep 여부 선택.</li>
</ul>
</li>
<li>참조 차료형을 감시하는 경우, 현재값과 이전값이 동일한 점 유의</li>
</ul>
<h4 id="ref-감시">ref 감시</h4>
<ul>
<li>변수 자체를 감시</li>
<li>기본 : 참조 자료형을 ref로 선언하는 경우, 변경 감지 불가</li>
<li>{ deep: true } 설정 : 참조 내부까지 변경 감지</li>
</ul>
<h4 id="reactive-감시">reactive 감시</h4>
<ul>
<li>참조 자료형 감시</li>
<li>기본 : 참조 내부까지 변경 감지</li>
</ul>
<h4 id="옵션">옵션</h4>
<ul>
<li><code>deep(true)</code> : 참조 내부까지 감시 (ref)</li>
<li><code>immediate(true)</code> : 처음 디폴트 값을 넣을 때 부터 감시 및 실행 (기본값은 변경될 때만)</li>
<li><code>once(true)</code> : 최초 한 번만 실행</li>
<li><code>flush(선택값)</code> : 감시자가 실행되는 시점 설정<ul>
<li><code>pre</code> : (기본값) 감시자가 DOM 업데이트 이전에 실행</li>
<li><code>post</code> : 감시자가 DOM 업데이트 후에 실행</li>
<li><code>sync</code> : 반응형 데이터가 변경되는 즉시 동기적으로 실행 (DOM에 반영 전)</li>
</ul>
</li>
</ul>
<br />

<h3 id="5-감시자-속성-2---watcheeffect">5. 감시자 속성 2 - watchEEffect</h3>
<ul>
<li>watch와 비슷한 역할</li>
<li>watch와 다른 점<ul>
<li>deep 옵션 사용하지 않음</li>
<li>immediate 옵션을 true로 고정 ⇒ 처음 디폴트 값을 넣을 때 부터 감시 및 실행</li>
<li>감시 대상을 따로 지정하지 않으나, 콜백 함수 내에서 사용하고 있는 반응형 데이터들을 자동 감시</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">watchEffect(() =&gt; {
  console.log(count.value); // count 변수 감시
  console.log(state.count); // state 객체 중에서 count의 변경 감시
  console.log(state); // state의 참조값 변경 감시</code></pre>
<br />

<h3 id="6-감시자-속성-3---watchposteffect">6. 감시자 속성 3 - watchPostEffect</h3>
<ul>
<li>watchEffect와 flush를 ‘post’의 결합</li>
<li>DOM이 갱신된 후 실행</li>
</ul>
<br />

<h3 id="7-라이프사이클-훅">7. 라이프사이클 훅</h3>
<ul>
<li>Options API에서 사용하던 beforeCreate()와 create()는 setup 영역으로 대체</li>
<li>나머지 훅은 <code>on + 기존 메서드명</code> 으로 변경</li>
</ul>
<br />

<h3 id="8-props-전달">8. props 전달</h3>
<ul>
<li><code>defineProps()</code> 함수를 사용하여 props 받음</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  import { defineProps } from &#39;vue&#39;;
  const props = defineProps({
      count: Number // 여러 자료형을 지정하고 싶다면 [Number, String]
      text: {
        type: String,
        default() { return &#39;기본&#39; }
    })
&lt;/script&gt;
&lt;template&gt;
    &lt;p&gt;{{ props.count }}&lt;/p&gt;
    &lt;p&gt;{{ props.text }}&lt;/p&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="9-이벤트-받기">9. 이벤트 받기</h3>
<ul>
<li><code>defineEmits()</code> 함수를 사용하여 이벤트를 받음</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
  import { defineEmits } from &#39;vue&#39;;
  const emit = defineEmits([&#39;event1&#39;, &#39;event2&#39;])

  const handler = () =&gt; {
    emit(&#39;event1&#39;)
  }
&lt;/script&gt;
&lt;template&gt;
    &lt;button @click=&quot;handler&quot;&gt;버튼1&lt;/button&gt;
    &lt;button @click=&quot;emit(&#39;event1&#39;)&quot;&gt;버튼2&lt;/button&gt;
    &lt;button @click=&quot;emit(&#39;event2&#39;, 10, [20])&quot;&gt;버튼3&lt;/button&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="10-provide--inject">10. provide &amp; inject</h3>
<ul>
<li>props drilling을 방지할 수 있는 방법</li>
<li>props를 사용하지 않고 데이터를 전달할 수 있는 방법</li>
<li>provide를 사용한  컴포넌트부터 하위 컴포넌트들이 inject로 접근해서 사용 가능</li>
<li>형식<ul>
<li><code>provide(고유한 식별자, 제공할 데이터)</code> ⇒ 데이터는 일반,  ref,  reactive, coumputed 모두 가능</li>
<li><code>const value = inject(고유한 식별자, 기본값)</code> ⇒ 기본값은 생략 가능</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;script setup&gt;
    const count = ref(0);
    const array = reactvie([1, 2, 3])
    const increment = () =&gt; (count.value++)

    provide(&#39;provideCount&#39;, count)
    provide(&#39;provideArray&#39;, array)
&lt;/script&gt;</code></pre>
<pre><code class="language-tsx">&lt;script setup&gt;
    const count = inject(&#39;provideCount&#39;, 0);
    const array = inject(&#39;provideArray&#39;);
&lt;/script&gt;</code></pre>
<p><br /><br /></p>
<h2 id="✅-플러그인">✅ 플러그인</h2>
<h3 id="unplugin-auto-import">unplugin-auto-import</h3>
<ul>
<li>모든 컴포넌트에서 메서드를 import 하지 않아도 사용 가능</li>
</ul>
<pre><code class="language-tsx">import AutoImport from &#39;unplugin-auto-import/vite&#39;

export default defineConfig({
  plugins: [ ..., AutoImport({ imports: [&#39;vue&#39;] })],
  ...
</code></pre>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY89 - Vue(5)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY89-Vue5</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY89-Vue5</guid>
            <pubDate>Wed, 08 Jan 2025 12:25:22 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-컴포넌트-속성-정의">✅ 컴포넌트 속성 정의</h2>
<h3 id="1-사용자-정의-속성">1. 사용자 정의 속성</h3>
<ul>
<li>컴포넌트 호출과 함께 속성 정의 ⇒ 원하는 값 전달</li>
<li>기본 자료형 : 문자열(String) 자료형</li>
<li>String형이 아닌 다른 데이터 타입으로 전달받고 싶다면<ul>
<li><code>v-bind</code> 디렉티브로 바인딩해서 정의 (ex. <code>:age=&quot;20&quot;</code>)</li>
<li>단 문자열 자료형을 v-bind로 넘겨줄 땐, 반드시 백틱으로 한 번 감싸야 함</li>
</ul>
</li>
</ul>
<h4 id="호출-방식">호출 방식</h4>
<pre><code class="language-tsx">&lt;ChildComponent name=&quot;Minha&quot; age=&quot;20&quot; /&gt;
&lt;ChildComponent :name=&quot;`Minha`&quot; :age=&quot;20&quot; /&gt;</code></pre>
<h4 id="속성-받는-방식">속성 받는 방식</h4>
<ul>
<li>2가지 방식<ul>
<li>props 속성을 배열로 할당</li>
<li>props 속성을 객체로 할당 (간단하게 자료형만 지정하거나 더 상세하게 지정 가능)</li>
</ul>
</li>
<li>속성값은 전달받은 속성명 그대로 사용</li>
<li>전달받은 값은 data 속성처럼 사용 가능</li>
<li>부모 컴포넌트가 전달해준 속성을 자식 컴포넌트가 받지 않는다면<ul>
<li>String형 데이터로 전달한 속성은 자식 컴포넌트의 루트 요소의 속성으로 적용됨</li>
<li>루트 요소가 2개 이상이면 적용되지 않음</li>
</ul>
</li>
<li>props 속성<ul>
<li>type : 속성의 타입 지정 (강제성이 없기 때문에 경고만 띄워짐)<ul>
<li>String, Number, Array. Object, Boolean, Function, Symbol</li>
</ul>
</li>
<li>default : 속성을 받지 못할 때의 기본 값 (값 혹은 함수로 지정)</li>
<li>required : 속성 전달의 필수 여부</li>
<li>validator : 속성 값의 유효성 검사 (Boolean 반환, 유효하지 않으면 경고 발생)</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;script&gt;
  export default {
    props: [&#39;name&#39;, &#39;age&#39;]
  }
&lt;/script&gt;</code></pre>
<pre><code class="language-tsx">&lt;script&gt;
  export default {
    props: {
      name: String,
      age: {
        type: Number, // 여러 타입 지정 가능 =&gt; [Number, String]
        default() {
          // 로직이 필요할 때 작성
          return 10
        },
        required: true
      }
    }
  }
&lt;/script&gt;</code></pre>
<h4 id="속성-이름-작성-시-유의사항">속성 이름 작성 시 유의사항</h4>
<ul>
<li>컴포넌트 속성은 케밥 케이스에 따라 작성 (<code>kebab-case</code>)</li>
<li>data 옵션 속성이나, props 옵션 속성에서는 카멜 케이스에 따라 작성 (<code>calmelCase</code>)<ul>
<li>컴포넌트 속성명을 케밥 케이스로 작성했어도, 받을 때 카멜 케이스로 받을 수 있음</li>
</ul>
</li>
</ul>
<h4 id="사용자-정의-속성-기본값">사용자 정의 속성 기본값</h4>
<ul>
<li>속성 지정만 하고 값을 입력하지 않으면 자동으로 true(Boolean)로 값이 정해짐</li>
<li>자식 컴포넌트에서 특정 속성을 Boolean으로 처리 &amp; 부모가 속성 지정을 하지 않으면 false로 처리</li>
</ul>
<br />

<h3 id="2-사용자-정의-이벤트">2. 사용자 정의 이벤트</h3>
<ul>
<li>컴포넌트에 함수를 전달하는 방법 2가지<ul>
<li>props로 전달</li>
<li>사용자 정의 이벤트로 전달</li>
</ul>
</li>
<li><code>v-on</code> 디렉티브로 특정 이벤트 타입에 해당하는 이벤트 연결 가능<ul>
<li>컴포넌트에 전달하는 것이기 때문에 Web API에 정의된 이벤트 이상으로 전달 가능</li>
<li>이벤트 타입은 사용자가 직접 지정</li>
<li>이벤트 핸들러도 당연히 사용자가 직접 지정</li>
</ul>
</li>
</ul>
<h4 id="호출-방식-1">호출 방식</h4>
<pre><code class="language-tsx">&lt;script&gt;
  // import ~
  export default {
    components: {
      ChildComponent,
    },
    data() {
      return { };
    },
    methods: {
      printHello() {
        console.log(&#39;Hello&#39;);
      },
    },
  };
&lt;/script&gt;
&lt;template&gt;
    &lt;ChildComponent @print-hello=&quot;printHello&quot; /&gt;
&lt;/template&gt;</code></pre>
<h4 id="함수-받는-방식">함수 받는 방식</h4>
<ul>
<li>props로 전달 : props 옵션 객체에 할당</li>
<li>이벤트로 전달 : emits 옵션에 할당 (꼭 하지 않아도 되나, 하기를 권장)<ul>
<li>배열 할당, 객체 할당 가능</li>
<li>객체 할당의 경우 유효성 검증이 필요할 때 사용</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">// emits 옵션 - 배열로 할당
export default {
    emits: [&#39;printHello&#39;],
};</code></pre>
<pre><code class="language-tsx">// emits 옵션 - 객체로 할당
export default {
  emits: {
    printHello: null;
    printHello() {
      // 검증 로직
  },
};</code></pre>
<h4 id="함수-사용-방식">함수 사용 방식</h4>
<ul>
<li><code>$emit()</code> 내장 메서드를 이용해 함수 사용</li>
<li>인라인 핸들러 방식<ul>
<li><code>&lt;button @onClick=&quot;$emit(&#39;printHello&#39;)&quot;&gt;클릭&lt;/button&gt;</code></li>
</ul>
</li>
<li>메서드 핸들러 방식<ul>
<li>methods 옵션 객체에서 <code>this.$emit(&#39;printHello&#39;)</code> 사용하는 메서드 정의</li>
<li>새롭게 만든 메서드를 이벤트 핸들러로 지정</li>
</ul>
</li>
<li>인자 전달은 <code>$emit()</code> 내장 메서드의 2번째부터 인자로 전달<ul>
<li>ex) <code>$emit(&#39;printHello&#39;, value1, value2)</code></li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="✅-provide와-inject">✅ provide와 inject</h2>
<ul>
<li>props drilling을 방지할 수 있는 방법</li>
<li>props를 사용하지 않고 데이터를 전달할 수 있는 방법</li>
</ul>
<h3 id="1-provide">1. provide</h3>
<ul>
<li>컴포넌트에서 정의한 데이터(함수도 가능)를 하위 컴포넌트에 공유하는 기능</li>
<li>provide 옵션 속성 이용<ul>
<li>객체 형식 가능 ⇒ data나 computed 활용 불가</li>
<li>함수 형식 가능 ⇒ this 키워드를 통해 data나 computed 활용 가능</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">provide: {
  message: &quot;Hello&quot;,
  name: &quot;Minha&quot;,
}</code></pre>
<pre><code class="language-tsx">provide() {
  return {
    message: &quot;Hello&quot;,
    name: this.name,
    // 함수 형식일 때만 data 속성, computed 속성에 있는 값 사용 가능
  }
}</code></pre>
<br />

<h3 id="2-inject">2. inject</h3>
<ul>
<li>상위 컴포넌트에서 provide 옵션 속성으로 제공하는 데이터를 가져오는 기능</li>
<li>inject 옵션 속성 이용<ul>
<li>배열 형식 가능</li>
<li>객체 형식 가능 ⇒ 다른 이름으로 받기 가능</li>
</ul>
</li>
<li>하위 컴포넌트의 provide는 inject로 받을 수 없음<ul>
<li>상위 컴포넌트가 먼저 렌더링 → 하위 컴포넌트 렌더링</li>
<li>하위 컴포넌트가 렌더링 되기 전에 상위 컴포넌트가 렌더링되어 참조 시도 ⇒ 실패</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">inject: [&#39;message&#39;, &#39;name&#39;],</code></pre>
<pre><code class="language-tsx">inject: {
  newKey: {
    from: &#39;message&#39;, // 다른 이름으로 받기 가능
    default: 40, // 기본값 설정
  },
},</code></pre>
<br />

<h3 id="3-반응형">3. 반응형</h3>
<blockquote>
<p>🔍 반응형이란?
데이터 변경에 따라 UI를 자동으로 업데이트하는 메커니즘</p>
</blockquote>
<ul>
<li>Vue는 데이터와 DOM 간의 연동을 자동으로 처리됨<ul>
<li>data : 객체에 정의된 모든 속성은 기본적으로 반응형</li>
<li>computed : 의존하는 데이터가 변경될 때만 다시 계산 (캐싱)</li>
<li>watch : 특정 데이터가 변경될 때 실행</li>
</ul>
</li>
<li>데이터가 변경되면 이를 감지하고 UI를 다시 렌더링</li>
<li>궁금) props도 반응형으로 동작?<ul>
<li>props : 부모 컴포넌트에서 반응형이면 자식 컴포넌트에서도 반응형</li>
</ul>
</li>
<li>궁금) 반응형으로 동작하지 않는 건 어떤 것?<ul>
<li>데이터가 변경되어도 이를 감지하지 못하고 UI 업데이트 발생 X</li>
<li>데이터가 변경되고 있긴 함</li>
</ul>
</li>
</ul>
<h4 id="provide와-inject에서의-반응형">provide와 inject에서의 반응형</h4>
<ul>
<li>provide에 데이터를 일반 객체로 제공하면 반응형으로 동작하지 않음</li>
<li>데이터를 computed로 감싸야 inject로 받는 곳에서 반응형으로 동작</li>
</ul>
<pre><code class="language-tsx">provide() {
  return {
    value: computed(() =&gt; this.value)
  }
}</code></pre>
<br />

<h3 id="4-symbol-활용">4. Symbol 활용</h3>
<ul>
<li>여러 상위 컴포넌트에서 동일한 이름으로 데이터를 제공하고 있다면?<ul>
<li>가장 가까운 컴포넌트의 값을 받아옴</li>
</ul>
</li>
<li>그런 일이 없도록 하려면?<ul>
<li>Symbol 활용</li>
<li>js 파일 하나 만들어서 고유한 키값을 Symbol로 만들고 모아두기</li>
<li>Symbol을 키로 하여 provide 객체에 데이터 정의</li>
<li>inject는 객체로 정의하여 사용하고 싶은 키로 변경</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">export const key = Symbol();
export const func = Symbol();</code></pre>
<pre><code class="language-tsx">// key랑 func import 하기
provide() {
  return {
    [key]: computed(() =&gt; this.value),
    [func]: this.func
  }
}</code></pre>
<pre><code class="language-tsx">// key랑 func import 하기
inject: {
  // Symbol 활용 시, 반드리 객체 형태로
  newKey: { from: key },
  fn: { from: func },
}</code></pre>
<p><br /><br /></p>
<h2 id="✅-컴포넌트-참조">✅ 컴포넌트 참조</h2>
<p>자식 컴포넌트가 부모 컴포넌트로 값을 전달할 방법은 없음
그러나 부모 컴포넌트가 자식 컴포넌트의 값을 참조할 방법은 있음</p>
<br />

<h3 id="1-자식-컴포넌트-참조">1. 자식 컴포넌트 참조</h3>
<ul>
<li>자식 컴포넌트 호출 시, ref 속성 사용<ul>
<li><code>&lt;ChildComponent ref=&quot;ref명&quot; /&gt;</code></li>
<li>ref 속성으로 등록한 ref명은 $refs 라는 객체에 등록 (ref명 중복 주의)</li>
<li><code>$ref 객체</code>를 통해 자식 컴포넌트의 data, computed, methods 옵션 속성 접근 가능</li>
<li><code>console.log(this.$refs.ref명.data명)</code> / <code>this.$refs.ref명.method명()</code></li>
<li>ref를 통해 접근한 data는 직접 조작도 가능 (computed는 readonly이므로 조작 불가)</li>
</ul>
</li>
<li>ref 참조하는 시점은 자식 컴포넌트가 마운트 된 이후여야 함<ul>
<li>그 전에 ref를 참조하면 에러가 발생하지는 않으나, 자식 컴포넌트와 미연결 상태</li>
</ul>
</li>
<li>그러나 자식 컴포넌트의 데이터에 직접 접근하거나 조작하는 건 권장하지 않음</li>
</ul>
<br />

<h3 id="2-부모-컴포넌트-참조">2. 부모 컴포넌트 참조</h3>
<ul>
<li><code>$parent 객체</code>를 통해 부모 컴포넌트 접근 가능 (따로 설정 필요 X)<ul>
<li>자식 컴포넌트와 내용 동일 (접근 가능, 직접 조작 가능, 함수 호출 가능)</li>
<li><code>console.log(this.$parent.data명)</code></li>
</ul>
</li>
<li>역시나 권장하지 않음</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY88 - Vue(4)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY88-Vue4-33hfd79o</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY88-Vue4-33hfd79o</guid>
            <pubDate>Tue, 07 Jan 2025 16:42:38 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-컴포넌트-등록">✅ 컴포넌트 등록</h2>
<h3 id="1-전역-컴포넌트-등록">1. 전역 컴포넌트 등록</h3>
<ul>
<li>main.js에서 컴포넌트 등록</li>
<li>import문 없이 언제든지 어디서든 자유롭게 사용 가능</li>
<li>컴포넌트 이름 마음대로 지정 가능</li>
<li>장점 : 별도의 컴포넌트 등록 과정없이 쉽게 사용 가능</li>
<li>단점 : 컴포넌트가 필요없어도 불러옴</li>
</ul>
<pre><code class="language-tsx">import { createApp } from &#39;vue&#39;;
import App from &#39;./App.vue&#39;;
import First from &#39;@/components/First.vue&#39;;
import Second from &#39;@/components/Second.vue&#39;;

const app = createApp(App);

// 컴포넌트 등록 1 - 하나씩 등록
app.component(&#39;First&#39;, First);
app.component(&#39;Second&#39;, Second);

// 컴포넌트 등록 2 - 체이닝 등록
app.component(&#39;First&#39;, First).component(&#39;Second&#39;, Second);

app.mount(&#39;#app&#39;);</code></pre>
<br />

<h3 id="2-지역-컴포넌트-등록">2. 지역 컴포넌트 등록</h3>
<ul>
<li>script 태그 내에서 컴포넌트 import</li>
<li>components에 불러온 컴포넌트 등록</li>
</ul>
<pre><code class="language-tsx">&lt;script&gt;
  import First from &#39;./components/First.vue&#39;;
  import Second from &#39;./components/Second.vue&#39;;

  export default {
    name: &#39;App&#39;,
    components: {
      First,
      Second,
    },
    data() {
      return {};
    },
  };
&lt;/script&gt;</code></pre>
<p><br /><br /></p>
<h2 id="✅-그-외-컴포넌트-관련">✅ 그 외 컴포넌트 관련</h2>
<h3 id="1-컴포넌트-스타일-범위---scoped-속성">1. 컴포넌트 스타일 범위 - scoped 속성</h3>
<ul>
<li>scoped 속성을 설정하면 해당 컴포넌트 내부에서만 스타일 적용</li>
<li>뷰의 특징) 루트 요소 공유<ul>
<li>설정한 스타일 요소가 자식 컴포넌트의 루트 요소로 있을 경우 scoped여도 반영됨</li>
<li>반영되기를 원하지 않는다면 다른 태그로 한 번 래핑해야 함</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-컴포넌트-props-전달">2. 컴포넌트 props 전달</h3>
<ul>
<li>자식 컴포넌트의 속성값으로 전달</li>
<li>자식 컴포넌트는 props로 전달받은 속성을 배열로 등록</li>
<li>props로 받지 않는 속성들은 자식의 루트 요소에 속성값으로 적용<ul>
<li>루트가 여러개라면 받지 못함</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="✅-감시자-속성">✅ 감시자 속성</h2>
<h3 id="1-watch-디렉티브">1. watch 디렉티브</h3>
<ul>
<li>감시자 속성으로, 특정 데이터의 변화를 감지하여 특정 로직 수행</li>
<li>watch 내부에 감시하고자 하는 데이터와 동일한 이름으로 메서드 정의<ul>
<li>감시하고 있는  데이터가 변경될 때마다 실행</li>
<li>인자로 ‘현재값’과 ‘이전값’을 순서대로 받음</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">watch: {
  count(cur, prev) {
    console.log(cur, prev)
  }
}</code></pre>
<br />

<h3 id="2-watch-디렉티브---깊은-사용법">2. watch 디렉티브 - 깊은 사용법</h3>
<ul>
<li>참조 자료형인 객체와 배열은 데이터가 변경되어도 watch로 감지 불가<ul>
<li>deep을 true로 설정하여 감지되도록 설정</li>
</ul>
</li>
<li>단, 인자로 받던 ‘현재값’과 ‘이전값’은 모두 ‘현재값’으로만 받음</li>
</ul>
<pre><code class="language-tsx">watch: {
  array: {
    handler(cur, prev) {
      console.log(cur, prev)
    },
    deep: true,
  }
}</code></pre>
<p><br /><br /></p>
<h2 id="✅-vue의-생명주기lifecycle">✅ Vue의 생명주기(lifecycle)</h2>
<h3 id="1-단계">1. 단계</h3>
<ul>
<li>setup</li>
<li>beforeCreate : 옵션스 API 초기화 직전</li>
<li>created : 옵션스 API 초기화 직후 및 템플릿 컴파일 직전<ul>
<li>보통 여기서 API 호출을 많이함 (beforMount보다 조금 더 빠르기 때문)</li>
</ul>
</li>
<li>beforeMount : 마운트 직전</li>
<li>mounted : 마운트</li>
<li>beforeUpdate : 데이터 변경 직전 (그러나 출력할 땐 이미 변경된 값으로 나옴)</li>
<li>updated : 데이터 변경</li>
<li>beforeUnmount : 마운트 해제 직전</li>
<li>unmounted : 마운트 해제</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY87 - Vue(3)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue3</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY87-Vue3</guid>
            <pubDate>Tue, 07 Jan 2025 16:01:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🔌-mvvmmodel-view-viewmodel">🔌 MVVM(Model-View-ViewModel)</h2>
<ul>
<li>Vue가 사용중인 아키텍처</li>
</ul>
<br />

<h3 id="1-model">1. Model</h3>
<ul>
<li>애플리케이션의 데이터</li>
<li><code>data</code> 객체로 관리</li>
<li>변경 시, ViewModel에 통보</li>
</ul>
<br />

<h3 id="2-viewmodel">2. ViewModel</h3>
<ul>
<li>Model과 View를 연결하는 중간 역할</li>
<li><code>computed</code> , <code>methods</code> , <code>watch</code> 같은 기능을 통해 데이터 처리</li>
<li>View와 동기화</li>
</ul>
<br />

<h3 id="3-view">3. View</h3>
<ul>
<li>사용자와 상호작용하는 UI 부분</li>
<li><code>v-bind</code> , <code>v-model</code> , <code>v-for</code> , <code>v-if</code> 등의 디렉티브 사용</li>
<li>ViewModel과 자동 동기화</li>
</ul>
<p><br /><br /></p>
<h2 id="🔌-이벤트">🔌 이벤트</h2>
<h3 id="1-v-on-디렉티브">1. v-on 디렉티브</h3>
<ul>
<li>형식 : <code>v-on:이벤트타입=&quot;이벤트 핸들러&quot;</code></li>
<li>ex) <code>v-on:click=&quot;clickHandler&quot;</code></li>
<li>축약 가능 : <code>@click=&quot;clickHandler&quot;</code></li>
<li>이벤트 객체 전달 가능 : <code>@click=&quot;clickHandler($event)&quot;</code></li>
<li>인자 전달 가능 : <code>@click=&quot;clickHandler(10)&quot;</code></li>
<li>이벤트 객체, 인자 함께 전달 가능 : <code>@click=&quot;clickHandler($event, 10)&quot;</code></li>
<li>이벤트 핸들러 없이 직접 조작 가능<ul>
<li><code>@click=&quot;count = count + 1&quot;</code></li>
<li><code>@input=&quot;($event) =&gt; (query = $event.target.value)&quot;</code></li>
</ul>
</li>
</ul>
<h4 id="이벤트-타입">이벤트 타입</h4>
<ul>
<li>click : 클릭 핸들러</li>
<li>input : 입력 핸들러</li>
<li>keyup : 키 입력 핸들러</li>
</ul>
<br />

<h3 id="2-methods-옵션">2. methods 옵션</h3>
<ul>
<li>data 속성을 정의한 것처럼 methods 속성 정의</li>
<li>methods 내부에 메서드 정의하듯이 이벤트 핸들러 및 함수 정의</li>
<li>data 접근을 위해서는 <code>this.데이터명</code>으로 접근</li>
</ul>
<br />

<h3 id="3-수식어">3. 수식어</h3>
<ul>
<li>리액트에서는 없는 개념으로, 이벤트에 보조적인 기능을 부여해주는 것</li>
<li>형식 : <code>v-on:이벤트타입.수식어=&quot;이벤트 핸들러&quot;</code></li>
<li>수식어 종류<ul>
<li><code>prevent</code> , <code>once</code> , <code>enter</code> 등</li>
<li>굉장히 많음</li>
<li><a href="https://ko.vuejs.org/guide/essentials/event-handling.html#event-modifiers">문서</a></li>
</ul>
</li>
</ul>
<h4 id="예시---once">예시 - once</h4>
<ul>
<li>이벤트 핸들러가 딱 한 번만 실행되도록 함</li>
<li><code>&lt;button @click.once=&quot;decrease&quot;&gt;감소&lt;/button&gt;</code><ul>
<li>무한으로 클릭해도 오로지 딱 한 번만 실행됨</li>
</ul>
</li>
</ul>
<h4 id="예시---prevent">예시 - prevent</h4>
<ul>
<li>이벤트의 기본 동작을 중단시킴</li>
<li><code>&lt;a href=&quot;http://www.naver.com&quot; @click.prevent&gt;네이버&lt;/a&gt;</code><ul>
<li>링크 이동이 안됨</li>
</ul>
</li>
</ul>
<h4 id="예시---입력키">예시 - 입력키</h4>
<ul>
<li>특정 입력키를 눌러야 이벤트 발생</li>
<li><code>@keyup.enter=&quot;handleKeyup&quot;</code><ul>
<li>enter를 눌러야 실행</li>
</ul>
</li>
<li><code>@keyup.ctrl.enter=&quot;handleKeyup&quot;</code><ul>
<li>ctrl를 누른 상태에사 enter를 눌러야 실행</li>
</ul>
</li>
</ul>
<h4 id="예시---마우스-버튼">예시 - 마우스 버튼</h4>
<ul>
<li>지정한 특정 마우스 버튼을 클릭해야 이벤트 핸들러가 실행되도록 함</li>
<li>종류 : left, right, middle</li>
<li><code>&lt;button @click.right=&quot;decrease&quot;&gt;감소&lt;/button&gt;</code><ul>
<li>오른쪽 마우스 클릭을 해야 decrease 실행</li>
<li>왼쪽 마우스 클릭으로는 반응하지 않음</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🔌-메모이제이션">🔌 메모이제이션</h2>
<h3 id="1-v-memo-디렉티브">1. v-memo 디렉티브</h3>
<ul>
<li>형식 : <code>v-memo=&quot;[name]&quot;</code></li>
<li>배열 안에 있는 데이터들 중 하나라도 바뀌어야 변경된 값을 반영</li>
</ul>
<pre><code class="language-tsx">// age나 gender가 바뀌어도 반영되지 않음
// name이 바뀌어야 그동안 반영되지 않았던 다른 변경사항들도 함께 반영
&lt;div v-memo=&quot;[name]&quot;&gt;
    &lt;h1&gt;{{ name }}&lt;/h1&gt;
    &lt;h1&gt;{{ age }}&lt;/h1&gt;
    &lt;h1&gt;{{ gender }}&lt;/h1&gt;
&lt;/div&gt;</code></pre>
<p><br /><br /></p>
<h2 id="🔌-computed">🔌 computed</h2>
<ul>
<li>데이터를 기반으로 어떠한 가공을  해야할 때 주로 사용 (데이터 가공은 여기서 하는 것을 권장)</li>
<li>data 속성과 methods 속성을 정의한 것처럼 computed 속성 정의</li>
<li>computed 내부에 메서드 정의하듯이 속성 정의</li>
<li>메서드로 정의하나, 메서드가 아니기 때문에 파라미터 전달 불가능<ul>
<li>오로지 데이터로만 가공할 때 사용</li>
</ul>
</li>
<li>읽기 전용이므로 메서드 내에서 변경 및 할당 불가</li>
<li>재사용할 때, 내부적으로 캐시되어 있는 데이터로 출력됨<ul>
<li>참조하고  있는 데이터가 변경되면 캐싱을 풀고 다시 계산 &amp; 캐싱</li>
</ul>
</li>
<li>그래도 변경하고 싶다면 getter와 setter로 설정 가능</li>
</ul>
<pre><code class="language-tsx">computed: {
  fullName: {
    get() { ... }
    set(value) { ... }
  }
}</code></pre>
<p><br /><br /></p>
<h2 id="🔌-그외">🔌 그외</h2>
<h3 id="1-한글은-조합문자">1. 한글은 조합문자</h3>
<ul>
<li>영어와는 다르게 한글은 입력한 글자가 바로 한단계씩 느리게 반영됨<ul>
<li>입력 중인 input  태그에는 문제없음</li>
<li>input에 입력된 내용을 바로 다른 방식으로 출력할 때 문제</li>
</ul>
</li>
<li>그래도 제출할 땐 누락없이 제출됨 (focus out 되면서 마저 반영하고 제출되기 때문)</li>
<li>위에 상황 때문에 글자 개수를 제대로 인식하지 못하는 문제 발생<ul>
<li>v-model를 사용 → v-bind로 value에 넣고 input 이벤트 사용</li>
</ul>
</li>
<li>그러나 사용자 입력값을 실시간으로 확인할 필요가 없을 때는 딱히 문제되지 않음</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY84 - Vue(2)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY84-Vue2</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY84-Vue2</guid>
            <pubDate>Fri, 03 Jan 2025 12:53:56 GMT</pubDate>
            <description><![CDATA[<h2 id="🎨-디렉티브">🎨 디렉티브</h2>
<h3 id="1-v-for">1. v-for</h3>
<h4 id="배열">배열</h4>
<ul>
<li>값만 참조 : <code>v-for=&quot;value in array&quot;</code></li>
<li>값 &amp; 인덱스 참조 : <code>v-for=&quot;(value, index) in array&quot;</code></li>
<li>배열 처리<ul>
<li>필터 : <code>v-for=&quot;value in array.filter(...)&quot;</code></li>
<li>포함 : <code>v-for=&quot;value in array.includes(&#39;a&#39;)&quot;</code></li>
<li>하던대로!</li>
</ul>
</li>
</ul>
<h4 id="객체-배열">객체 배열</h4>
<ul>
<li>값의 속성 접근 : <code>&lt;li v-for=&quot;value in array&quot;&gt;{{ value.name }}&lt;/li&gt;</code></li>
</ul>
<h4 id="객체">객체</h4>
<ul>
<li>값과 키 참조 : <code>v-for=&quot;(value, key) in obj&quot;</code></li>
</ul>
<h4 id="template-태그">template 태그</h4>
<ul>
<li>최상위의 template 태그는 vue의 컴포넌트를 하나로 묶는 역할을 한다. (하나만 존재)</li>
<li>최상위의 template 태그 안에 template 태그가 존재할 수 있다.<ul>
<li>이때, template 태그 자체는 DOM에 렌더링되지 않는다.</li>
</ul>
</li>
<li><code>&lt;template *v-for*=&quot;option in product.options&quot;&gt;&lt;p&gt;{{ option.name }}&lt;/p&gt;&lt;/template&gt;</code></li>
</ul>
<br />

<h3 id="2-v-model">2. v-model</h3>
<ul>
<li>v-model로 폼 요소를 다룸</li>
<li>입력한 요소의 값을 가져올 때 사용</li>
<li>script 태그의 데이터와 template 태그의 HTML 요소가 양방향으로 연결됨<ul>
<li>폼을 이용해 값이 변경될 경우, script 태그의 데이터에도 변경된 값이 적용됨</li>
</ul>
</li>
</ul>
<h4 id="input">input</h4>
<pre><code class="language-tsx">&lt;label for=&quot;uid&quot;&gt;
  &lt;span&gt;아이디&lt;/span&gt;
  &lt;input v-model=&quot;uid&quot; type=&quot;text&quot; id=&quot;uid&quot; /&gt;
&lt;/label&gt;</code></pre>
<h4 id="textarea">textarea</h4>
<pre><code class="language-tsx">&lt;label for=&quot;description&quot;&gt;
  &lt;span&gt;상세내용&lt;/span&gt;
  &lt;textarea v-model=&quot;description&quot; id=&quot;description&quot; /&gt;
&lt;/label&gt;</code></pre>
<h4 id="checkbox">checkbox</h4>
<pre><code class="language-tsx">&lt;div&gt;
    &lt;label&gt;
      &lt;span&gt;Seoul&lt;/span&gt;
      &lt;input
          v-model=&quot;cities&quot;
        type=&quot;checkbox&quot;
        id=&quot;seoul&quot; /&gt;
    &lt;/label&gt;
    &lt;label&gt;
      &lt;span&gt;Incheon&lt;/span&gt;
      &lt;input
          v-model=&quot;cities&quot;
        type=&quot;checkbox&quot;
        id=&quot;incheon&quot; /&gt;
    &lt;/label&gt;
    &lt;label&gt;
      &lt;span&gt;Busan&lt;/span&gt;
      &lt;input
          v-model=&quot;cities&quot;
        type=&quot;checkbox&quot;
        id=&quot;busan&quot; /&gt;
    &lt;/label&gt;
&lt;/div&gt;</code></pre>
<h4 id="radio">radio</h4>
<pre><code class="language-tsx">&lt;label&gt;
    &lt;input
        v-model=&quot;gender&quot;
        type=&quot;radio&quot;
        name=&quot;gender&quot;
        value=&quot;male&quot;
    /&gt;
    male
&lt;/label&gt;
&lt;label&gt;
    &lt;input
        v-model=&quot;gender&quot;
        type=&quot;radio&quot;
        name=&quot;gender&quot;
        value=&quot;female&quot;
    /&gt;
    female
&lt;/label&gt;</code></pre>
<h4 id="range">range</h4>
<pre><code class="language-tsx">&lt;label&gt;
  &lt;input
    v-model=&quot;range&quot;
    type=&quot;range&quot;
    min=&quot;0&quot;
    max=&quot;100&quot;
  /&gt;
&lt;/label&gt;</code></pre>
<h4 id="select-option">select-option</h4>
<pre><code class="language-tsx">&lt;label&gt;
    아이템
    &lt;select v-model=&quot;selectedItem&quot;&gt;
        &lt;option value=&quot;item 1&quot;&gt;item 1&lt;/option&gt;
        &lt;option value=&quot;item 2&quot;&gt;item 2&lt;/option&gt;
        &lt;option value=&quot;item 3&quot;&gt;item 3&lt;/option&gt;
    &lt;/select&gt;
&lt;/label&gt;</code></pre>
<p><br /><br /></p>
<h2 id="🎨-css-적용하기">🎨 CSS 적용하기</h2>
<h3 id="1-인라인-스타일">1. 인라인 스타일</h3>
<pre><code class="language-tsx">&lt;template&gt;
    &lt;h1 style=&quot;color: red; font-weight: bold;&quot;&gt;인라인 스타일&lt;/h1&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="2--v-bind-디렉티브">2.  v-bind 디렉티브</h3>
<pre><code class="language-tsx">&lt;script&gt;
    export default {
        data() {
            return {
                color: &#39;red&#39;,
        weight: &#39;bold&#39;,
            }
        }
    }
&lt;/script&gt;
&lt;template&gt;
    &lt;h1 :style=&quot;{ color: color, fontWeight: weight }&quot;&gt;v-bind 디렉티브&lt;/h1&gt;
&lt;/template&gt;</code></pre>
<br />

<h3 id="3-내부-스타일">3. 내부 스타일</h3>
<ul>
<li>scoped : 선택 가능한 설정<ul>
<li>설정하지 않으면 모든 컴포넌트에 적용</li>
<li>설정하면 해당 컴포넌트에만 적용</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">&lt;template&gt;
    &lt;h1&gt;내부 스타일&lt;/h1&gt;
&lt;/template&gt;
&lt;style scoped&gt;
    h1 {
        color: red;
    }
&lt;/style&gt;</code></pre>
<br />

<h3 id="4-외부-스타일">4. 외부 스타일</h3>
<ul>
<li>css 파일을 따로 작성하는 방법<ul>
<li>보통 뷰에서는 <code>src/assets/css/</code> 안에 작성함</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">// index.css
h1 {
    color: red;
}</code></pre>
<pre><code class="language-tsx">&lt;style&gt;
    @imort &#39;../src/assets/index.css&#39;;

    // 혹은 아래와 같이 alias로 사용 가능
    @import &#39;~/index.css&#39;;
&lt;/style&gt;</code></pre>
<h4 id="alias-적용">alias 적용</h4>
<pre><code class="language-tsx">// vite.config.js

export default defineConfig({
  resolve: {
    alias: {
      &#39;@&#39;: fileURLToPath(new URL(&#39;./src&#39;, import.meta.url)),
      &#39;~&#39;: fileURLToPath(new URL(&#39;./src/assets&#39;, import.meta.url)),
    },
  },
});</code></pre>
<p><br /><br /></p>
<h2 id="🎨-css-라이브러리-적용하기">🎨 CSS 라이브러리 적용하기</h2>
<h3 id="1-bootstrap">1. BootStrap</h3>
<ul>
<li>설치 ⇒ <code>npm install bootstrap</code></li>
<li>최상위에 import문 추가<ul>
<li><code>import &#39;bootstrap&#39;;</code></li>
<li><code>import &#39;bootstrap/dist/css/bootstrap.css&#39;;</code></li>
</ul>
</li>
</ul>
<br />

<h3 id="2-tailwind-css">2. Tailwind CSS</h3>
<ul>
<li>평소 하던대로 (프레임워크 가이드에 맞춰서 설정)</li>
<li>tailwind.config.js에서 파일 확장자에 vue 추가</li>
</ul>
<br />

<h3 id="3-sass">3. SASS</h3>
<ul>
<li>설치 ⇒ <code>npm install --save-dev sass sass-loader</code></li>
<li>style 태그에 scss 적용 가능 ⇒ <code>&lt;style lang=&quot;scss&quot; scoped&gt;...&lt;/style&gt;</code></li>
</ul>
<br />

<h3 id="4-styled-components">4. Styled Components</h3>
<ul>
<li><a href="https://vue-styled-components.com/">Vue Styled Components</a></li>
<li>설치 ⇒ <code>npm install @vue-styled-components/core</code></li>
<li>script 태그 추가 ⇒ <code>&lt;script setup&gt; 여기에 스타일 컴포넌트 작성 &lt;/script&gt;</code></li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY52~53 - API & JWT Token]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-API-JWT-Token</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-API-JWT-Token</guid>
            <pubDate>Fri, 03 Jan 2025 08:49:01 GMT</pubDate>
            <description><![CDATA[<h2 id="🪁-api-연동">🪁 API 연동</h2>
<h3 id="1-fetch">1. fetch</h3>
<ul>
<li>HTTP 요청을 보내는 데 사용되는 도구</li>
<li>브라우저에서 기본적으로 제공하는 API</li>
<li>따로 설치할 필요 없음</li>
</ul>
<pre><code class="language-tsx">const getData = async () =&gt; {
  const response = await fetch(&quot;url&quot;)
  const data = await response.json();
}</code></pre>
<br />

<h3 id="2-axios">2. axios</h3>
<ul>
<li>HTTP 요청을 보내는 데 사용되는 도구</li>
<li>따로 라이브러리를 설치해야 함</li>
<li>자동으로 json을 적용하여 response 객체를 바로 반환 (편리)</li>
<li>fetch에서는 없는 기능들 제공</li>
</ul>
<pre><code class="language-tsx">const getData = async () =&gt; {
  const { data } = await axios.get(&quot;url&quot;);
}</code></pre>
<br />

<h3 id="3-crud">3. CRUD</h3>
<ul>
<li>POST : 데이터 추가</li>
<li>PUT : 데이터 수정 (데이터 식별을 위한 고유 아이디를 제외한 모든 데이터 수정)</li>
<li>PATCH : 데이터 수정 (일부 내용만 수정)</li>
<li>DELETE : 데이터 삭제</li>
</ul>
<p><br /><br /></p>
<h2 id="🪁-jwt-token">🪁 JWT Token</h2>
<h3 id="1-로그인-및-토큰-데이터를-전역-상태로-관리">1. 로그인 및 토큰 데이터를 전역 상태로 관리</h3>
<pre><code class="language-tsx">import { create } from &quot;zustand&quot;;

interface AuthStore {
  isLoggedIn: boolean;
  accessToken: string | null;
  login: (accessToken: string) =&gt; void;
  logout: () =&gt; void;
}

export const useAuthStore = create&lt;AuthStore&gt;((set) =&gt; ({
  isLoggedIn: false,
  accessToken: null,
  login: (accessToken: string) =&gt; set({ isLoggedIn: true, accessToken }),
  logout: () =&gt; set({ isLoggedIn: false, accessToken: null }),
}));</code></pre>
<br />

<h3 id="2-액세스-토큰을-api-요청에-액세스-토큰-담아-전송">2. 액세스 토큰을 API 요청에 액세스 토큰 담아 전송</h3>
<pre><code class="language-tsx">export const axiosInstance = axios.create({
  baseURL: `${import.meta.env.VITE_API_URL}`,
  withCredentials: true,
});

axiosInstance.interceptors.request.use((config) =&gt; {
  const token = useAuthStore.getState().accessToken;
  if (token) {
    config.headers[&quot;Authorization&quot;] = `Bearer ${token}`;
  }
  return config;
});</code></pre>
<h3 id="3-액세스-토큰이-없어-에러가-발생-리프레시-토큰으로-재발급하고-재요청">3. 액세스 토큰이 없어 에러가 발생, 리프레시 토큰으로 재발급하고 재요청</h3>
<pre><code class="language-tsx">let retry = false;

axiosInstance.interceptors.response.use(
  (response) =&gt; response,
  async (error) =&gt; {
    const originalRequest = error.config;
    if (error.response?.status === 403 &amp;&amp; !retry) {
      // retry를 설정해야 한 번만 실행됨. 그렇지 않으면 무한 루프에 빠짐
      console.log(&quot;token 실패&quot;);
      retry = true;
      try {
        const { data } = await axiosInstance.post(&quot;/token&quot;);
        console.log(data);
        useAuthStore.setState({
          accessToken: data.accessToken,
          isLoggedIn: true,
        });
        originalRequest.headers[&quot;Authorization&quot;] = `Bearer ${data.accessToken}`;
        // 다시 설정해주고 원래 요청으로 보내주기
        return axiosInstance(originalRequest);
      } catch (err) {
        console.log(err);
      }
    }
  }
);</code></pre>
<br />

<h3 id="4-새로고침하면-전역-상태로-관리하던-액세스-토큰이-날라가는-문제">4. 새로고침하면 전역 상태로 관리하던 액세스 토큰이 날라가는 문제</h3>
<ul>
<li>리프레시 토큰으로 액세스 토큰 다시 받아오기</li>
<li>App.tsx에서 useEffect를 이용해 첫 렌더링 시, 토큰 발급하는 api 호출</li>
</ul>
<br />

<h3 id="5-리프레시-토큰을-api-요청에-함께-보내는-방법">5. 리프레시 토큰을 API 요청에 함께 보내는 방법</h3>
<ul>
<li><code>withCredentials</code> : HTTP 요청에서 쿠키, 인증 헤더, 도는 기타 자격 증명을 포함하도록 설정하는 옵션<ul>
<li>axios에서 withCredentials를 true로 설정하면, 클라이언트와 서버 간의 요청에 자격 증명 포함 가능</li>
<li>자격 증명을 필요로 하는 경우에만 true로 설정 (리프레시 토큰 받을 때, 보낼 때)</li>
</ul>
</li>
<li>이 방식 말고<ul>
<li>Authorization 헤더에 토큰을 담아 전송하는 방식도 있음</li>
</ul>
</li>
</ul>
<br />

<h3 id="6-private-route-구현">6. Private Route 구현</h3>
<ul>
<li>로그인 여부에 따라 유저의 접근을 제한해야 하는 경우</li>
</ul>
<pre><code class="language-tsx">import { useEffect, useState } from &quot;react&quot;;
import { Outlet, useNavigate } from &quot;react-router&quot;;
import { useAuthStore } from &quot;../stores/authStore&quot;;

export default function Private() {
  const navigate = useNavigate();
  const [show, setIsShow] = useState(false);
  const isLoggedIn = useAuthStore((state) =&gt; state.isLoggedIn);
  useEffect(() =&gt; {
    if (!isLoggedIn) {
      navigate(&quot;/login&quot;);
      return;
    }
    setIsShow(true);
  }, []);

  // show를 사용한 이유는 찰나의 순간 보일 수도 있기 때문에 막은 것
  return &lt;&gt;{show &amp;&amp; &lt;Outlet /&gt;}&lt;/&gt;;
}</code></pre>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY52~53 - 전역 상태 관리 라이브러리]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC</guid>
            <pubDate>Fri, 03 Jan 2025 08:45:53 GMT</pubDate>
            <description><![CDATA[<h2 id="📡-context-api">📡 Context API</h2>
<ul>
<li>리액트에 내장되어 있는 라이브러리</li>
<li>리액트 컴포넌트를 같은 문맥(Context)으로 묶어, 데이터 공유를 위한 일관된 인터페이스 제공</li>
</ul>
<h3 id="1-타입-생성">1. 타입 생성</h3>
<pre><code class="language-tsx">interface User {
  name: string;
  age: number;
}

interface AuthContextType {
  user: User | null;
  isLoggedIn: boolean;
  login: (user: User) =&gt; void;
  logout: () =&gt; void;
}</code></pre>
<br />

<h3 id="2-context-객체-생성">2. Context 객체 생성</h3>
<pre><code class="language-tsx">// contexts/AuthContext.ts

import { createContext } from &quot;react&quot;;

export const AuthContext = createContext&lt;AuthContextType | null&gt;(null);</code></pre>
<br />

<h3 id="3-생성한-context-객체의-provider-생성">3. 생성한 Context 객체의 Provider 생성</h3>
<pre><code class="language-tsx">// contexts/providers/AuthProvider.ts

export default function AuthProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState&lt;User | null&gt;(null);
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const login = (user: User) =&gt; {
    setUser(user);
    setIsLoggedIn(true);
  };

  const logout = () =&gt; {
    setUser(null);
    setIsLoggedIn(false);
  };

  return (
    &lt;AuthContext.Provider value={{ user, isLoggedIn, login, logout }}&gt;
      {children}
    &lt;/AuthContext.Provider&gt;
  );
}</code></pre>
<br />

<h3 id="4-전역-상태-공유">4. 전역 상태 공유</h3>
<pre><code class="language-tsx">// main.tsx

import { createRoot } from &quot;react-dom/client&quot;;
import App from &quot;./App.tsx&quot;;
import AuthProvider from &quot;./contexts/providers/AuthProvider.tsx&quot;;

createRoot(document.getElementById(&quot;root&quot;)!).render(
  &lt;AuthProvider&gt;
    &lt;App /&gt;
  &lt;/AuthProvider&gt;
);</code></pre>
<br />

<h3 id="5-필요-시-사용">5. 필요 시 사용</h3>
<ul>
<li>useContext 훅을 사용하여 필요한 컨텍스트 호출</li>
</ul>
<pre><code class="language-tsx">import React, { useContext } from &quot;react&quot;;
import { AuthContext } from &quot;../contexts/AuthContext&quot;;

export default function AuthCheck({ children }: { children: React.ReactNode }) {
  const { isLoggedIn, login, logout } = useContext(AuthContext)!;
  return (
    &lt;&gt;
      {isLoggedIn &amp;&amp; children}
      {!isLoggedIn &amp;&amp; (
        &lt;button onClick={() =&gt; login({ name: &quot;James&quot;, age: 20 })}&gt;
          로그인
        &lt;/button&gt;
      )}
      {isLoggedIn &amp;&amp; &lt;button onClick={logout}&gt;로그아웃&lt;/button&gt;}
    &lt;/&gt;
  );
}</code></pre>
<br />

<h3 id="번외---context-api에-메모이제이션-적용">번외 - Context API에 메모이제이션 적용</h3>
<h4 id="1-value를-다루는-함수에-메모이제이션-적용">1) value를 다루는 함수에 메모이제이션 적용</h4>
<pre><code class="language-tsx">// main.tsx

import { createRoot } from &quot;react-dom/client&quot;;
import App from &quot;./App.tsx&quot;;
import CounterProvider from &quot;./context/provider/CounterProvider.tsx&quot;;

createRoot(document.getElementById(&quot;root&quot;)!).render(
  &lt;CounterProvider&gt;
    &lt;App /&gt;
  &lt;/CounterProvider&gt;
);
</code></pre>
<pre><code class="language-tsx">// context/CounterContext.ts

import { createContext } from &quot;react&quot;;

interface CounterContextType {
  count: number;
}

interface CounterActionContextType {
  increment: () =&gt; void;
  decrement: () =&gt; void;
  reset: () =&gt; void;
}

// count 값 제공하는 컨텍스트
export const CounterContext = createContext&lt;CounterContextType | null&gt;(null);

// count 값을 다루는 함수를 제공하는 컨텍스트 (메모이제이션)
export const CounterActionContext =
  createContext&lt;CounterActionContextType | null&gt;(null);</code></pre>
<pre><code class="language-tsx">// context/provider/CounterProvider.tsx

import { ReactNode, useMemo, useState } from &quot;react&quot;;
import { CounterActionContext, CounterContext } from &quot;../CounterContext&quot;;

export default function CounterProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = useState(0);

  const increment = () =&gt; { setCount((count) =&gt; count + 1); };
  const decrement = () =&gt; { setCount((count) =&gt; count + 1); };
  const reset = () =&gt; { setCount(0); };

    // 함수지만 객체로 감싸 하나의 값으로 만들었기에 useMemo 적용
  const memo = useMemo(() =&gt; ({ increment, decrement, reset }), []);

  return (
    &lt;&gt;
      &lt;CounterActionContext.Provider value={ memo }&gt;
        &lt;CounterContext.Provider value={{ count }}&gt;
          {children}
        &lt;/CounterContext.Provider&gt;
      &lt;/CounterActionContext.Provider&gt;
    &lt;/&gt;
  );
}</code></pre>
<h4 id="2-잘못된-메모이제이션">2) 잘못된 메모이제이션</h4>
<pre><code class="language-tsx">// 함수에 useCallback을 적용한 건 문제가 되지 않으나
// value로 함수를 넘길 때, 객체로 묶어서 전달하는 과정에서 count의 변경으로
// 객체의 참조값이 변경되어 메모이제이션에 제대로 안됨

const increment = useCallback(() =&gt; {
  setCount((count) =&gt; count + 1);
}, []);

const decrement = useCallback(() =&gt; {
  setCount((count) =&gt; count + 1);
}, []);

const reset = useCallback(() =&gt; {
  setCount(0);
}, []);

return (
  &lt;&gt;
    &lt;CounterActionContext.Provider value={{ increment, decrement, reset }}&gt;
      &lt;CounterContext.Provider value={{ count }}&gt;
        {children}
      &lt;/CounterContext.Provider&gt;
    &lt;/CounterActionContext.Provider&gt;
  &lt;/&gt;
);</code></pre>
<p><br /><br /></p>
<h2 id="📡-redux-toolkit">📡 Redux-toolkit</h2>
<ul>
<li>기본적인 설정과 코드량이 많아 다소 러닝 커브가 높은 전역 상태 관리 라이브러리</li>
<li>toolkit으로 redux를 쉽게 사용할 수 있음</li>
<li>크게 2가지로 구분 ⇒ 상태, 액션(상태를 변경하기 위해 조작하는 함수)</li>
<li>슬라이스 안에 상태와 액션을 정의해서 사용 (슬라이스는 여러 개 있을 수 있음)</li>
</ul>
<h3 id="1-슬라이스-생성하기">1. 슬라이스 생성하기</h3>
<ul>
<li>createSlice : 슬라이스에 필요한 액션 생성자(actions)와 리듀서(reducer) 생성</li>
</ul>
<pre><code class="language-tsx">import { createSlice } from &quot;@reduxjs/toolkit&quot;;

interface User {
  name: string;
  agE: number;
}

const authSlice = createSlice({
  name: &quot;authSlice&quot;,
  // 초기 상태
  initialState: {
    user: null as User | null,
    isLoggedIn: false,
  },
  // 상태를 변경하는 함수 정의 (액션)
  reducers: {
    login: (state, action) =&gt; {
      state.user = action.payload;
      state.isLoggedIn = true;
    },
    logout: (state) =&gt; {
      state.user = null;
      state.isLoggedIn = false;
    },
  },
});

export const { login, logout } = authSlice.actions;
export default authSlice.reducer;</code></pre>
<br />

<h3 id="2-스토어-생성하기">2. 스토어 생성하기</h3>
<ul>
<li>configure : configurre 객체로 스토어 생성</li>
<li>reducer : 여러 개의 슬라이스를 결합하여 하나의 스토어로 관리할 수 있도록</li>
</ul>
<pre><code class="language-tsx">import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import authSlice from &quot;./slice/authSlice&quot;;
import { counterSlice } from &quot;./slice/counterSlice&quot;;

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    auth: authSlice,
  },
});

export type RootState = ReturnType&lt;typeof store.getState&gt;; // 상태를 위한 타입
export type AppDispatch = typeof store.dispatch; // 액션을 위한 타입</code></pre>
<br />

<h3 id="3-필요-시-사용">3. 필요 시 사용</h3>
<ul>
<li>변수: useSelector를 사용하여 리듀서에 접근 -&gt; 원하는 슬라이스로 접근</li>
<li>함수: useDispatch로 원하는 함수 사용</li>
</ul>
<pre><code class="language-tsx">export default function AuthCheck({ children }: { children: React.ReactNode }) {
  const user = useSelector((state: RootState) =&gt; state.auth.user);
  const isLoggedIn = useSelector((state: RootState) =&gt; state.auth.isLoggedIn);
  const dispatch = useDispatch&lt;AppDispatch&gt;();

  return (
    &lt;&gt;
      {isLoggedIn &amp;&amp; user?.name}
      {isLoggedIn &amp;&amp; children}
      {!isLoggedIn &amp;&amp; (
        &lt;button onClick={() =&gt; dispatch(login({ name: &quot;James&quot;, age: 20 }))}&gt;
          로그인
        &lt;/button&gt;
      )}
      {isLoggedIn &amp;&amp; (
        &lt;button onClick={() =&gt; dispatch(logout())}&gt;로그아웃&lt;/button&gt;
      )}
    &lt;/&gt;
  );
}</code></pre>
<p><br /><br /></p>
<h2 id="📡-zustand">📡 Zustand</h2>
<ul>
<li>사용법 매우 간단. 로직도 간단. 가벼움</li>
<li>리액트 훅처럼 선언하여 사용</li>
<li>리액트에 종속적인 것이 아닌 JS를 위한 라이브러리</li>
<li>앞으로 더 인기가 많아질 라이브러리가 될 것</li>
</ul>
<h3 id="1-상태-store-생성하기">1. 상태 store 생성하기</h3>
<ul>
<li>상태 store : 공유되는 상태를 저장하고 관리하는 중앙 저장소를 의미함<ul>
<li>전역에서 접근 가능</li>
<li>상태를 변경하는 로직을 스토어 안에 두어 관리</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">interface CounterStoreType {
  count: number;
  increment: () =&gt; void;
  decrement: () =&gt; void;
  reset: () =&gt; void;
}

export const useCounterStore = create&lt;CounterStoreType&gt;((set) =&gt; ({
  count: 0, // 초기 상태
  increment: () =&gt; set((state) =&gt; ({ count: state.count + 1 })), // 상태 변경 로직
  decrement: () =&gt; set((state) =&gt; ({ count: state.count - 1 })), // 상태 변경 로직
  reset: () =&gt; set({ count: 0 }), // 상태 변경 로직
}));</code></pre>
<br />

<h3 id="2-상태-store-사용하기">2. 상태 store 사용하기</h3>
<pre><code class="language-tsx">export default function App() {
    const count = useCounterStore((state) =&gt; state.count);
  const increment = useCounterStore((state) =&gt; state.increment);
    const decrement = useCounterStore((state) =&gt; state.decrement);

  return (
    &lt;&gt;
      &lt;h1&gt;Count: {count}&lt;/h1&gt;
      &lt;button onClick={decrement}&gt;감소&lt;/button&gt;
      &lt;button onClick={increment}&gt;증가&lt;/button&gt;
    &lt;/&gt;
  );
;</code></pre>
<br />

<h3 id="3-잘못된-상태-구독">3. 잘못된 상태 구독</h3>
<pre><code class="language-tsx">const { count, increment, decrement } = useCounterStore();</code></pre>
<ul>
<li>위의 방식은 상태 store를 전체 구독하는 방식</li>
<li>상태가 변경될 때마다 스토어 객체 전체가 새로운 참조값을 가지므로 컴포넌트의 리렌더링 발생<ul>
<li>이로 인해 컴포넌트에 React.memo 처리를 한 경우, 무의미해짐</li>
</ul>
</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY52~53 - 메모이제이션]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY5253-%EB%A9%94%EB%AA%A8%EC%9D%B4%EC%A0%9C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Thu, 02 Jan 2025 16:55:04 GMT</pubDate>
            <description><![CDATA[<h2 id="🚀-리액트-훅">🚀 리액트 훅</h2>
<h3 id="useeffect">useEffect</h3>
<ul>
<li>함수형 컴포넌트에서 생명주기를 다룰 때 사용하는 리액트 훅<ul>
<li>생명주기 : 컴포넌트가 태어나고 죽을 때까지</li>
</ul>
</li>
<li>렌더링 이후의 Side Effect를 발생시키는 역할</li>
<li><code>useEffect(콜백 함수, 의존성 배열)</code></li>
<li>클린업 함수<ul>
<li>컴포넌트가 언마운트되기 직전 실행시키는 함수</li>
</ul>
</li>
</ul>
<pre><code class="language-typescript">useEffect(() =&gt; {
    const interval = setInterval(() =&gt; {
        console.log(&quot;interval&quot;);
    }, 1000);

    // 클린업 함수
    return () =&gt; {
        clearInterval(interval);
    };
}, []);</code></pre>
<p><br /><br /></p>
<h2 id="🚀-메모이제이션">🚀 메모이제이션</h2>
<blockquote>
<p>🔍 메모이제이션이란?
중복 계산을 피하기 위해 계산 결과를 저장해두고, 동일한 입력 값에 대해서는 이미 계산된 결과를 재사용하는 방법</p>
</blockquote>
<h3 id="1-reactmemo">1. React.memo()</h3>
<ul>
<li>컴포넌트 메모이제이션<ul>
<li><code>const MyComponent = React.memo(() =&gt; {})</code></li>
</ul>
</li>
<li>동일한 props로 렌더링될 경우, 이전 렌더링 결과 재사용 ⇒ 불필요한 리렌더링 방지</li>
<li>함수형 컴포넌트에서만 사용 가능</li>
<li>props가 자주 변경되거나 비교하는 cost가 높다면 오히려 성능 저하 초래<pre><code class="language-typescript">const MyComponent = React.memo(({ count }) =&gt; {
  return &lt;div&gt;{count}&lt;/div&gt;
})</code></pre>
</li>
</ul>
<br />

<h3 id="2-usecallback">2. useCallback()</h3>
<ul>
<li>리액트 훅, 함수 메모이제이션</li>
<li>컴포넌트가 리렌더링될 때 동일한 함수 인스턴스를 유지할 수 있도록</li>
<li>의존성 배열에 있는 값이 변경되지 않는다면, 메모이제이션 된 함수 재사용<pre><code class="language-typescript">import { useState, useCallback } from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>const MyComponent = () =&gt; {
  const [count, setCount] = useState(0);</p>
<p>  const increment = useCallback(() =&gt; {
    setCount((prevCount) =&gt; prevCount + 1);
  }, []);</p>
<p>  return <button onClick={increment}>Increment</button>;
};</p>
<pre><code>
&lt;br /&gt;

### 3. useMemo()
- 리액트 훅, 값 메모이제이션
- 컴포넌트가 리렌더링될 때 복잡한 계산을 반복하지 않도록
- 의존성 배열에 있는 값이 변경되지 않는다면, 이전 계산 결과 재사용
```typescript
const MyComponent = () =&gt; {
    const [count, setCount] = useState(0);

    const expensiveCalculation = useMemo(() =&gt; {
    return count * 2;
  }, [count]);

   return (
    &lt;div&gt;
      &lt;p&gt;Result: {expensiveCalculation}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount((prev) =&gt; prev + 1)}&gt;Increment&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre><br />

<h3 id="정리">정리</h3>
<table>
<thead>
<tr>
<th align="center">기능</th>
<th align="center">React.memo</th>
<th align="center">useCallback</th>
<th align="center">useMemo</th>
</tr>
</thead>
<tbody><tr>
<td align="center">사용 목적</td>
<td align="center">컴포넌트 재렌더링 방지</td>
<td align="center">함수 재생성 방지</td>
<td align="center">계산 재실행 방지</td>
</tr>
<tr>
<td align="center">리턴 값</td>
<td align="center">메모이제이션된 컴포넌트</td>
<td align="center">메모이제이션된 함수</td>
<td align="center">메모이제이션된 계산 결과</td>
</tr>
<tr>
<td align="center">사용 대상</td>
<td align="center">컴포넌트</td>
<td align="center">함수</td>
<td align="center">값</td>
</tr>
<tr>
<td align="center">주요 사용 시점</td>
<td align="center">props가 변경되지 않을 때</td>
<td align="center">콜백을 자식 컴포넌트에 전달할 때</td>
<td align="center">복잡한 연산이 필요할 때</td>
</tr>
</tbody></table>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY83 - Vue(1)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY83-Vue1</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY83-Vue1</guid>
            <pubDate>Thu, 02 Jan 2025 15:07:47 GMT</pubDate>
            <description><![CDATA[<h2 id="✨-뷰-시작하기">✨ 뷰 시작하기</h2>
<h3 id="1-스캐폴딩scaffolding">1. 스캐폴딩(scaffolding)</h3>
<pre><code class="language-terminal">npm create vue@latest</code></pre>
<ul>
<li>JSX : 설정 X. 뷰는 기본적으로 template를 지원함. JSX는 리액트에서 사용</li>
<li>Pinia : Vue3에서 사용 가능한 상태 관리 패키지</li>
</ul>
<br />

<h3 id="2-기본-구조">2. 기본 구조</h3>
<ul>
<li>package.json<ul>
<li>private : 애플리케이션 공개 여부 (대부분 true, 공개 X)</li>
<li>dependencies : 실행할 때 필요한 의존성 모듈 정의. 프로덕션(배포 및 서비스) 환경에서도 사용</li>
<li>devdependencies : 개발할 때 필요한 의종성 모듈 정의. 개발 환경에서만 사용</li>
</ul>
</li>
<li>main.js : 뷰 애플리케이션 초기화 &amp; 구성 역할<ul>
<li>createApp() : 뷰 애플리케이션의 인스턴스 생성</li>
</ul>
</li>
<li>App.vue : 루트 컴포넌트 (가상 상위 컴포넌트)</li>
</ul>
<br />

<h3 id="3-sfc-single-file-component">3. SFC, Single File Component</h3>
<ul>
<li>Vue에서 컴포넌트를 작성하는 방식 중 하나</li>
<li>하나의 파일 안에 HTML, CSS, JS 포함</li>
<li>3가지 태그<ul>
<li><code>&lt;script&gt;</code> : JavaScript 작성, 0~1개</li>
<li><code>&lt;template&gt;</code> : HTML 작성, 필수로 1개</li>
<li><code>&lt;style&gt;</code> : CSS 작성, 0~1개<pre><code class="language-vue">&lt;script&gt;
export default {
name: &quot;컴포넌트 이름&quot;,
data() {
    return { ... }
},
...
}
&lt;/script&gt;
&lt;template&gt;
&lt;/template&gt;
&lt;style&gt;
&lt;/style&gt;</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="4-options-api--composition-api">4. Options API &amp; Composition API</h3>
<ul>
<li>SFC내, script 태그 안에 코드 작성 시 지켜야 하는 문법 규칙</li>
<li>Options API : 기존부터 있던 방식</li>
<li>Composition API : Vue3에서 도입된 방식</li>
</ul>
<br />

<h3 id="5-데이터-보간법">5. 데이터 보간법</h3>
<ul>
<li>template 태그 내에서 data를 사용하는 방법</li>
<li>콧수염 문법 (mustache syntax) 사용 <code>{{ 데이터 속성 }}</code></li>
<li>데이터를 직접 입력 O, JS 표현식 O<ul>
<li>단, 최대한 데이터로만 넣어서 사용하도록</li>
</ul>
</li>
</ul>
<br />

<h3 id="6-디렉티브">6. 디렉티브</h3>
<ul>
<li>template 태그 내에서 data 옵션 속성을 사용하기 위한 방법</li>
<li><code>v-[]</code>로 시작하는 뷰에서만 사용 가능한 특별한 속성들</li>
<li>하단에 자세히 설명</li>
</ul>
<p><br /><br /></p>
<h2 id="✨-디렉티브-종류">✨ 디렉티브 종류</h2>
<h3 id="1-v-html">1. v-html</h3>
<ul>
<li>값에 HTML 태그가 포함될 때, 태그를 html로 반영해서 출력</li>
<li>XSS(Cross-Site Scripting) 공격에 매우 취약<ul>
<li>사용자기 입력한 데이터를 출력할 때 사용X</li>
<li>개발자가 직접 작성한 데이터를 출력할 때만 사용<pre><code class="language-vue">&lt;script&gt;
export defulat {
data() {
message: &quot;&lt;strong&gt;message&lt;/strong&gt;&quot;,
}
}
&lt;/script&gt;
&lt;template&gt;
&lt;h1&gt;{{ message }}&lt;/h1&gt;
&lt;h1 v-html=&quot;message&quot;&gt;&lt;/h1&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-v-text">2. v-text</h3>
<ul>
<li>HTML 태그가 포함될 때, 태그까지 텍스트로 치환 출력<pre><code class="language-vue">&lt;template&gt;
&lt;h1 v-text=&quot;message&quot;&gt;&lt;/h1&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
<br />

<h3 id="3-v-pre">3. v-pre</h3>
<ul>
<li>컴파일을 건너뜀</li>
<li>컴파일 해야 할 코드양은 줄여주어 성능에 도움이 됨</li>
<li>script 영역 내 뷰 문법을 사용하지 않으면 v-pre로 성능 개선 가능<pre><code class="language-vue">&lt;template&gt;
&lt;h1 v-pre&gt;{{ message }}&lt;/h1&gt;
&lt;/template&gt;</code></pre>
컴파일을 진행하지 않으므로 위의 결과는 <code>{{ message }}</code></li>
</ul>
<br />

<h3 id="4-v-bind">4. v-bind</h3>
<ul>
<li>데이터를 속성에 바인딩할 때 사용</li>
<li>vue 3.4 이상부터 v-bind를 제외하고 <code>:속성</code>으로 축약 가능</li>
<li>속성명과 데이터명이 같다면 더 짧게 축약 가능</li>
<li>조건 처리 가능<pre><code class="language-vue">&lt;template&gt;
&lt;h1 v-bind:id=&quot;id&quot;&gt;&lt;/h1&gt;
&lt;h1 :id=&quot;id&quot;&gt;&lt;/h1&gt;
&lt;h1 :id&gt;&lt;/h1&gt;
&lt;h1 :class=&quot;isActive ? &#39;active&#39; : &#39;inactive&#39;&quot;&gt;text&lt;/h1&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
<br />

<h3 id="5-v-if-v-else-if-v-else">5. v-if, v-else-if, v-else</h3>
<ul>
<li>조건부 렌더링</li>
<li>조건에 맞아야 보여짐<pre><code class="language-vue">&lt;template&gt;
&lt;p v-if=&quot;weather === &#39;sunny&#39;&quot;&gt;sunny&lt;/p&gt;
&lt;p v-else-if=&quot;weather === &#39;rainy&#39;&quot;&gt;rainy&lt;/p&gt;
&lt;p v-esle-if=&quot;weather === &#39;cloudy&#39;&quot;&gt;cloudy&lt;/p&gt;
&lt;p v-esle&gt;unknown&lt;/p&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
<br />

<h3 id="6-v-show">6. v-show</h3>
<ul>
<li>조건부 렌더링</li>
<li>보여주냐, 안 보여주냐</li>
<li>자주 스위칭 되는 경우, 해당 디렉티브가 도움이 된다<pre><code class="language-vue">&lt;template&gt;
&lt;p v-show=&quot;isVisible&quot;&gt;hello&lt;/p&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
<br />

<h3 id="7-v-cloak">7. v-cloak</h3>
<ul>
<li>렌더링 되면 없어지는 요소</li>
<li>이 디렉티브를 잘 사용하면 디테일 보완 가능<ul>
<li>초기 렌더링 시, 컴파일 속도가 느리면 찰나의 깜빡임이 있음</li>
<li>렌더링이 완료되면 v-cloak 설정 제거됨<pre><code class="language-vue">&lt;template&gt;
&lt;h1 v-cloak&gt;{{ message }}&lt;/h1&gt;
&lt;/template&gt;
&lt;style scoped&gt;
[v-cloak] {
display: none;
}
&lt;/style&gt;</code></pre>
</li>
</ul>
</li>
</ul>
<br />

<h3 id="8-v-for">8. v-for</h3>
<ul>
<li>for문처럼 배열 데이터 순회<pre><code class="language-vue">&lt;template&gt;
&lt;ul&gt;
  &lt;li v-for=&quot;item in items&quot; :key=&quot;item.id&quot;&gt;{{ item.name }}&lt;/li&gt;
&lt;/ul&gt;
&lt;/template&gt;</code></pre>
</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY52 - React Hook (useReducer)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY52-React-Hook-useReducer</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY52-React-Hook-useReducer</guid>
            <pubDate>Wed, 04 Dec 2024 15:04:05 GMT</pubDate>
            <description><![CDATA[<h2 id="🖊️-usereducer">🖊️ useReducer</h2>
<ul>
<li>useState와 유사하며, 로컬 상태를 정의할 때 사용</li>
<li><code>const [상태, 액션발생함수] = useReducer(리듀서 함수, 초기값)</code></li>
<li>useState와의 차이점<ul>
<li>useState: 상태를 변경하는 함수가 별도의 로직으로 여러 개 정의되어 상태 예측이 어려움. 간단함</li>
<li>useReducer: 상태를 변경하는 함수를 한 곳에 관리하므로 상태 예측이 가능함. 복잡함</li>
</ul>
</li>
</ul>
<br />

<h3 id="사용-예시---1">사용 예시 - 1</h3>
<pre><code class="language-tsx">// 리듀서 함수에서 사용되는 state와 action 타입 정의
type ReducerState = number;
type ReducerAction = string;</code></pre>
<pre><code class="language-tsx">// 상태를 업데이트 하는 리듀서 함수 (오로지 이 함수로만 상태 변경 가능)
// 상태를 업데이트 하기 때문에 무조건 값(새로운 상태)을 반환해야 함
function reducer(state: ReducerState, action: ReducerAction) {
  switch (action) {
    case &quot;decrement&quot;:
      return state - 1;
    case &quot;reset&quot;:
      return 0;
    case &quot;increment&quot;:
      return state + 1;
    default:
      return state;
  }
}    </code></pre>
<pre><code class="language-tsx">function App() {
  const [state, dispatch] = useReducer(reducer, 0);

  // dispatch를 통해 액션 발생
  // reducer 함수를 직접 호출해도 작동하지 않음
  return (
    &lt;&gt;
      &lt;h1&gt;Count: {state}&lt;/h1&gt;
      &lt;button onClick={() =&gt; dispatch(&quot;decrement&quot;)}&gt;감소&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(&quot;reset&quot;)}&gt;리셋&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch(&quot;increment&quot;)}&gt;증가&lt;/button&gt;
    &lt;/&gt;
  )</code></pre>
<br />

<h3 id="사용-예시---2">사용 예시 - 2</h3>
<pre><code class="language-tsx">interface ReducerState {
  email: string;
  password: string;
  agree: boolean;
  error: string;
}

type ReducerAction =
  | {
      type: &quot;SET_EMAIL&quot;;
      payload: string;
    }
  | {
      type: &quot;SET_PASSWORD&quot;;
      payload: string;
    }
  | {
      type: &quot;SET_AGREE&quot;;
      payload: boolean;
    }
  | {
      type: &quot;SET_ERROR&quot;;
      payload: string;
    };</code></pre>
<pre><code class="language-tsx">function reducer(state: ReducerState, action: ReducerAction) {
  switch (action.type) {
    case &quot;SET_EMAIL&quot;:
      return { ...state, mail: action.payload };
    case &quot;SET_PASSWORD&quot;:
      return { ...state, password: action.payload };
    case &quot;SET_AGREE&quot;:
      return { ...state, agree: action.payload };
    case &quot;SET_ERROR&quot;:
      return { ...state, error: action.payload };
    default:
      return state;
  }
}</code></pre>
<pre><code class="language-tsx">const initialState: ReducerState = {
  email: &quot;&quot;,
  password: &quot;&quot;,
  agree: false,
  error: &quot;&quot;,
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const validationForm = () =&gt; {
    if (!state.email) {
      dispatch({ type: &quot;SET_ERROR&quot;, payload: &quot;이메일을 입력해주세요.&quot; });
      return false;
    }
    // ...
  };

  return(
    &lt;form&gt;
      &lt;input
          type=&quot;text&quot;
          placeholder=&quot;someone@example.com&quot;
          name=&quot;mail&quot;
          value={state.email}
          onChange={(e) =&gt; {
              dispatch({ type: &quot;SET_EMAIL&quot;, payload: e.target.value });
             }}
            /&gt;
    &lt;/form&gt;
)</code></pre>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY46~49 - React]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY4649-React</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY4649-React</guid>
            <pubDate>Tue, 03 Dec 2024 17:09:05 GMT</pubDate>
            <description><![CDATA[<h2 id="🍑-트랜스파일러--모듈-번들러">🍑 트랜스파일러 &amp; 모듈 번들러</h2>
<h3 id="1-트랜스-파일러">1. 트랜스 파일러</h3>
<ul>
<li>서로 다른 언어 규약으로 작성된 파일을 웹 브라우저가 이해할 수 잇는 형태의 파일로 변환해주는 역할<ul>
<li>JSX를 JS 코드로 변환</li>
<li>최신 버전의 JS, TS 문법을 다운그레이드하여 변환</li>
</ul>
</li>
<li>예시) 바벨, swc</li>
</ul>
<br />

<h3 id="2-모듈-번들러">2. 모듈 번들러</h3>
<ul>
<li>여러 개의 프로젝트 파일들을 각 종류 별로 하나의 파일로 묶어주는 역할</li>
<li>묶어주는 과정에서 사용하지 않는 파일들을 제거해줌 (트리세이킹) ⇒ 빌드 속도, 성능 향상</li>
<li>묶어주는 과정에서 이미지에 대한 용량 최적화 진행 (public 폴더는 제외)</li>
<li>예시) 웹팩, vite(웹팩보다 더 빠르고 리액트와 궁합이 좋음)</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-tailwind-관련-플러그인">🍑 Tailwind 관련 플러그인</h2>
<ul>
<li>tailwind-merge : 스타일 충돌없이 tailwind css 클래스를 js로 효율적으로 병합하는 유틸리티 함수</li>
<li>tailwind-linecamp : 말줄임표 처리 도움. 현재는 기본 내장</li>
<li>tailwind-forms : Tailwind의 기본 폼 스타일을 추가해주는 패키지</li>
</ul>
<br />

<h3 id="1-tailwlind-관련-추가-설정">1. Tailwlind 관련 추가 설정</h3>
<ul>
<li><p>모바일에서의 hover 스타일 막기</p>
<ul>
<li><p>tailwind.config.js 파일에 아래 코드 추가</p>
<pre><code class="language-css">module.exports = {
future: {
  hoverOnlyWhenSupported: true,
},
}</code></pre>
</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-폰트-적용하기">🍑 폰트 적용하기</h2>
<h3 id="1-구글-폰트">1. 구글 폰트</h3>
<ul>
<li>구글 폰트 사이트에서 <code>&lt;link&gt;</code> 태그 복사 ⇒ head 태그 안에 붙여넣기<ul>
<li>preconnect 속성 : 브라우저가 특정 외부 리소스를 더 빨리 로드할 수 있도록 함</li>
</ul>
</li>
<li>구글 폰트 사이트에서 <code>@import</code> 코드 복사 ⇒ css의 제일 상단에 붙여넣기<ul>
<li><code>https://링크&amp;display=swap</code></li>
</ul>
</li>
<li>font-display : 웹 폰트를 사용할 때 글꼴 로딩 전략 ⇒ FOUT 문제 해결 가능<ul>
<li>swap : 기본 글꼴로 먼저 표시 → 웹 폰트로 대체</li>
<li>auto : (기본값) 브라우저가 최적의 동작 판단</li>
<li>block : 폰트 로딩 중 텍스트 숨김(FOIT 발생 가능)</li>
<li>fallback : 폰트 로드 시간이 너무 길면 기본 글꼴 유지</li>
<li>optional : 네트워크 상태에 따라 웹 폰트 로드 생략</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-눈누-폰트">2. 눈누 폰트</h3>
<ul>
<li><p>눈누 사이트에서 font-face 복사</p>
<pre><code class="language-css">  @font-face {
    font-family: &#39;font&#39;;
    src: url(&#39;https://링크.woff&#39;) format(&#39;woff&#39;);
    font-weight: 400;
    font-style: normal;
  }

  .font {
    font-family: &quot;font&quot;;
  }</code></pre>
<pre><code class="language-html">  &lt;h1 className=&quot;font&quot;&gt;Hello World&lt;/h1&gt;</code></pre>
</li>
</ul>
<br />

<h3 id="3-로컬-폰트">3. 로컬 폰트</h3>
<ul>
<li><p>폰트를 다운로드하고 src 폴더 하위에 위치시키기</p>
</li>
<li><p>font-face 등록</p>
<pre><code class="language-css">  @font-face {
    font-family: &quot;font&quot;;
    src: url(&quot;../assets/fonts/font.woff2&quot;) format(&quot;woff2&quot;),
        url(&quot;../assets/fonts/font.woff&quot;) format(&quot;woff&quot;);
      font-weight: normal;
      font-style: normal;
  }

  .font {
    font-family: &quot;font&quot;;
  }</code></pre>
<pre><code class="language-html">  &lt;h1 className=&quot;font&quot;&gt;Hello World&lt;/h1&gt;</code></pre>
<ul>
<li>woff (web open font format) : 기존 폰트 파일 형식 + zlib 압축 알고리즘 사용하여 압축한 파일. 오래된 브라우저와의 호환성 좋음</li>
<li>woff2 : woff보다 더 높은 압축률 제공. 오래된 브라우저에서 지원하지 않을 수 있음</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-스타터-팩">🍑 스타터 팩</h2>
<ul>
<li>버전 관리에 지속적으로 신경써야 함.</li>
</ul>
<h3 id="1-프로젝트의-의존성-관리">1. 프로젝트의 의존성 관리</h3>
<ul>
<li>npm 모듈 버전 확인 : <code>npm outdated</code></li>
<li>npm 최신 버전 확인 : <code>npm i npm-check</code> 설치 / <code>npx npm-check</code> 실행<ul>
<li>import 하고 있지 않는 패키지를 체크하고 알려줌</li>
<li>단점 : 애초에 import를 할 필요 없는 라이브러리까지 알려줌</li>
</ul>
</li>
<li>버전 업데이트 : <code>npx npm-check-updates 모듈명 -u</code><ul>
<li>특정 라이브러리의 호환성을 맞춰줄 수 있는 명령어</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-npm--npx--yarn">🍑 npm &amp; npx &amp; yarn</h2>
<ul>
<li>npm : Nodej.s의 기본 패키지 관리자</li>
<li>npx : 패키지 실행 및 없으면 npm으로 설치</li>
<li>yarn : 병렬로 패키지를 설치하여 npm 보다 속도 향상. 이전에 설치된 패키지를 다시 다운로드하지 않고 설치 가능</li>
<li>pnpm : npm의 발전된 버전</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-시맨틱-버저닝-semver">🍑 시맨틱 버저닝 (SemVer)</h2>
<ul>
<li>소프트웨어의 버전 변경 규칙</li>
<li><code>Major</code> <code>Minor</code> <code>Patch</code> 순서<ul>
<li>Major : 하위 버전과의 호환 불가</li>
<li>Minor : 하위 호환성을 지키면서 기능 추가</li>
<li>Patch : 하위 호환성을 지키면서 버그 수정</li>
</ul>
</li>
<li>범위 지정 - 틸드 범위 (~)<ul>
<li>Minor 버전이 지정 O ⇒ Patch 레벨 변경 허용</li>
<li>Minor 버전이 지정 X ⇒ Patch + Minor 레벨 변경 허용</li>
<li>ex) <code>~1.3.2</code> : Minor 지정 O ⇒ Patch 레벨 변경 허용</li>
<li>eX) <code>~2</code> : Minor 지정 X ⇒ Minor 레벨 변경 허용</li>
</ul>
</li>
<li>범위 지정 - 캐럿 범위 (^)<ul>
<li>가장 왼쪽에 0이 아닌 요소를 수정하지 않는 범위로 변경을 허용</li>
<li>ex) <code>^1.3.x</code> : Minor + Patch 레벨 변경 허용</li>
<li>ex) <code>^0.1.x</code> : 0이 아닌 처음 요소는 Minor ⇒ Patch 레벨 변경 허용</li>
<li>ex) <code>^0.0.3</code> : 0이 아닌 처음 요소는 Patch ⇒ 변경 허용 X</li>
</ul>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-이미지-경로">🍑 이미지 경로</h2>
<ul>
<li>src 폴더나 public 폴더에 넣을 수 있음</li>
<li>어떤 폴더에 넣느냐에 따라 활용 방법이 달라짐</li>
<li>폴더 별 특징<ul>
<li>public 폴더 : 공용으로 사용 가능. 배포 후, 도메인을 통한 접근 가능</li>
<li>src/assets 폴더 : 공개적으로 노출 X (오로지 리액트 시스템 내부에서 활용). 빌드 과정에서 자동으로 이미지를 최적화 하여 public 폴더에 삽입됨</li>
</ul>
</li>
<li>결론 : 빌드 프로세스에 의해 처리되지 않는 이미지는 public 폴더로 (주로 파비콘). 그  외는 src/asseets 폴더 하위로</li>
</ul>
<br />

<h3 id="1-src-폴더-하위">1. src 폴더 하위</h3>
<ul>
<li>모듈 번들러(웹팩 등)에 의해 처리되어, 빌드 결과에 포함됨 (이미지 파일명의 변동 발생)</li>
<li>JSX에서<ul>
<li>import를 통해 사용 / 번들러의 관리 대상 ⇒ 이미지 경로가 자동 처리</li>
<li>직접 경로를 넣는 건 불가능 / 번들링 과정에서 위치나 파일명의 변경되어 올바르게 연결 불가 ⇒ 번들러가 이를 처리해줄 수 없음</li>
</ul>
</li>
<li>CSS에서<ul>
<li>직접 경로 넣어 사용(상대 경로) /  번들러의 관리 대상 ⇒ 경로를 자동으로 수정</li>
</ul>
</li>
<li>HTML에서<ul>
<li>직접 경로 넣는 건 불가능 / 번들러의 관리 대상 X ⇒ 경로를 자동으로 수정 X</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-public-폴더-하위">2. public 폴더 하위</h3>
<ul>
<li>공용 폴더이기 때문에 절대 경로 접근 가능 ⇒ img 태그에 직접 경로 삽입 가능</li>
<li><code>/</code> 로 시작할 경우 public 폴더로 인식</li>
<li>JSX : import 가능. 직접 경로 넣기 가능</li>
<li>CSS : 직접 경로 넣기 가능</li>
<li>HTML : 직접 경로 넣기 가능</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-리액트에서의-변수">🍑 리액트에서의 변수</h2>
<h3 id="1-변수-변경">1. 변수 변경</h3>
<ul>
<li>변수의 값이 변경되어도 화면에서는 변경 사항이 반영 X ⇒ 변경사항을 추적하지 않기 때문</li>
<li>리액트에서 변수를 선언할 때 2가지로 구분<ul>
<li>화면 렌더링에 영향을 주는 변수인지 ⇒ 이를 state(상태)라 함</li>
<li>화면 렌더링에 영향을 주지 않는 변수인지 ⇒ let, const, var</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-상태-정의-및-관리">2. 상태 정의 및 관리</h3>
<ul>
<li>16.8버전에 도입된 리액트 훅을 이용하여 상태를 정의하고 관리함</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-리액트-훅">🍑 리액트 훅</h2>
<h3 id="1-usestate">1. useState</h3>
<ul>
<li><code>const [state, setState] = useState(초기값)</code> : 상태와 상태 변경 함수를 배열로 반환</li>
<li>setState를 통해 상태를 변경하는 방법 2가지<ul>
<li>원하는 값을 직접 지정하여 변경 ⇒ <code>setState(값)</code></li>
<li>현재 상태를 이용하여 변경 ⇒ <code>setState((state) =&gt; state + 1)</code></li>
</ul>
</li>
<li>상태 업데이트는 비동기적으로 처리되기 때문에 즉시 업데이트 되지 않음 (효율을 위해 한번에 처리)</li>
<li>state는 불변성을 지켜야 하므로, 수정하지 않도록 유의</li>
</ul>
<br />

<h3 id="2-useref">2. useRef</h3>
<ul>
<li><code>document.querySelector</code> 같은 역할</li>
<li>직접 DOM에 접근하지 않는 이유 : 리액트는 가상 DOM을 사용하기 때문에 직접적인 접근 불가능</li>
</ul>
<br />

<h2 id="🍑-폼-제어-방법">🍑 폼 제어 방법</h2>
<ul>
<li><p>제어 컨트롤러 : 상태를 정의하여 요소 제어 ⇒ useState</p>
</li>
<li><p>비제어 컨트롤러 : DOM에 접근하여 요소 제어 ⇒ useRef</p>
<pre><code class="language-jsx">  function App() {
      const ref = useRef&lt;HTMLInputElement | null&gt;(null);
      const submitHandler = (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
          e.preventDefault();
          console.log(ref.current?.value);
    }

      return (
          &lt;form onSubmit={submitHandler}&gt;
              &lt;input ref={ref} /&gt;
          &lt;/form&gt;
      )
  }</code></pre>
</li>
</ul>
<p><br /><br /></p>
<h2 id="🍑-용어">🍑 용어</h2>
<ul>
<li>프레젠테이션 컴포넌트 : 받아온 props를 사용하지 않고, 바로 자식 컴포넌트에 넘겨주는 역할만 수행</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 Lv.4] 매출 하락 최소화]]></title>
            <link>https://velog.io/@min_ha/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.4-%EB%A7%A4%EC%B6%9C-%ED%95%98%EB%9D%BD-%EC%B5%9C%EC%86%8C%ED%99%94</link>
            <guid>https://velog.io/@min_ha/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.4-%EB%A7%A4%EC%B6%9C-%ED%95%98%EB%9D%BD-%EC%B5%9C%EC%86%8C%ED%99%94</guid>
            <pubDate>Tue, 03 Dec 2024 14:25:11 GMT</pubDate>
            <description><![CDATA[<h3 id="🥹-풀이-시작-전에">🥹 풀이 시작 전에...</h3>
<p>AI 추천 문제 풀었다가 우울해졌다.</p>
<p>이 문제를 볼 때, 일단 트리로 접근해야하는 건 확신했다.
그래서 DFS로 풀어야겠다고 생각하고 열심히 시도해봤다.</p>
<p>그런데 아무리 생각해봐도 직원 배열이 최대 30만개인데,
DFS로 하면 100%, 500%, 1000% 시간 초과가 발생할 게 뻔했다.
그래서 고민 끝에 알고리즘 힌트라도 얻고자 풀이를 찾아봤다.</p>
<p>힌트를 보니 드는 생각은
<strong>아니, AI는 도대체 어떤 분석으로 내가 이 문제를 풀 수 있다는 결론을 어떻게 내린거야....?</strong>
이 문제는 트리 DP로 풀어야 한다는데, 트리 DP는 처음 들어봤다ㅎ</p>
<p>이건 내가 혼자 풀 수 있는 게 절대 아니라는 걸 깨닫고 트리 DP의 원리를 공부해서 겨우 풀었다.
문제 다 풀고 확인하니까 무려 Lv4에 정답률이 12%였다..</p>
<p>아래의 풀이는 힌트 70%, 내 머리 30%가 합쳐진 결과이다.
원리 덕분에 풀 수 있었기 때문에 내 머리는 30% 정도라고 생각했다.
(실은 30%도 많은 듯..)</p>
<h2 id="◼️-문제">◼️ 문제</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/72416">매출 하락 최소화</a></p>
<h3 id="1-문제-설명">1. 문제 설명</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/c7219340-a94d-4a1e-9222-97f8b5ad648c/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/6a2d106e-e371-432e-a134-078127a5685f/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/6fcc2d29-2feb-4fd1-b92f-83875eee8f03/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/34337d5c-8ed1-4bf3-8198-2c134ca026ae/image.png" alt=""></p>
<br />

<h3 id="2-제한사항">2. 제한사항</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/5a8a38a4-4308-4507-9d7b-e6f6b23db9ab/image.png" alt=""></p>
<br />

<h3 id="3-입출력">3. 입출력</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/50224638-6548-433d-bcdb-8ba94e77a134/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/af1b04b7-885a-4ccb-af88-7e3fca6bf49e/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/2f6e42f7-e8f1-4414-a5ab-a4aced729d51/image.png" alt="">
<img src="https://velog.velcdn.com/images/min_ha/post/a128656c-eb22-437e-9f75-33f6b3aa07c6/image.png" alt=""></p>
<p><br/><br /></p>
<h2 id="◼️-풀이">◼️ 풀이</h2>
<h3 id="🧠-알고리즘">🧠 알고리즘</h3>
<p>내가 힌트로 참고한 글에 의하면 트리 DP로 풀었다고 한다.
트리도 알고 DP도 아는데, 트리 DP는 뭘까...?ㅠㅠㅠ
이름만 듣기엔 감이 어느정도 오긴 했지만, 이미 몇시간 째 문제를 부여잡고 있던 나에겐 이해가 공부가 절실하게 필요했다.</p>
<h4 id="dp-dynamic-programming">DP (Dynamic Programming)</h4>
<p>DP의 특징은 큰 문제를 작은 문제로 나누어 푼다는 점이다.
작은 문제의 답을 먼저 구한 다음 작은 문제의 답을 이용해 큰 문제를 구하는 방식이다.</p>
<h4 id="트리-dp">트리 DP</h4>
<p>그렇다면 트리 DP는 무엇일까.
큰 트리를 작은 트리로 나누어 문제를 해결하는 방식인 것이다.
작은 트리를 활용해 큰 트리의 최종적인 답을 구하는 방식이기 때문에,
리프에서 루트 방향으로 문제를 해결해가면 된다.</p>
<p>이 문제에서는 dp로 해결하는 과정에서,
현재 팀장이 참석할 경우와 현재 팀장이 참석하지 않을 경우에 대해 각각 최소값을 구하는 것이 핵심이다.</p>
<h3 id="✏️-전반적인-풀이과정">✏️ 전반적인 풀이과정</h3>
<ol>
<li>팀장과 팀원의 정보가 담긴 links 배열을 하나씩 확인하며, 조직도를 트리 형태로 만든다.</li>
<li>treeDP 함수를 이용해 워크숍에 참석하는 직원들이 최소 매출액을 갖는 결과를 찾아낸다. (재귀)
2-1. 만약 현재 팀장의 팀원의 dp에 대한 정보가 없을 경우, 팀원의 dp 정보부터 구한다.
2-2. 현재 팀장이 참석하지 않을 경우에 대한 최소값을 구한다.
2-3. 현재 팀장이 참석할 경우에 대한 최소값을 구한다.</li>
</ol>
<br />

<h3 id="📃-풀이과정-상세-설명">📃 풀이과정 상세 설명</h3>
<h4 id="재귀-함수로-리프에서-루트-방향으로-구하기">재귀 함수로 리프에서 루트 방향으로 구하기</h4>
<p>treeDP 함수를 통해 현재 팀장이 참석할 경우와 참석하지 않을 경우에 대한 매출액을 구하기 위해서는
팀원들의 dP 결과 이미 존재해야 한다는 가정이 필요하다.</p>
<p>팀원들의 dp 결과가 없다면, 팀원들의 dp부터 계산하는 방식으로 구현하기 위해 재귀를 택했다.
이렇게 하면, 최종적으로 가장 리프노드부터 dp를 계산하여 최종적으로 루트노드의 dp를 계산하게 된다.</p>
<pre><code class="language-javascript">// treeDP 함수 일부
const children = tree[index]

children.forEach((child) =&gt; {
  if (!dp[child]) treeDP(sales, tree, dp, child)
})</code></pre>
<br />

<h4 id="현재-팀장이-참석하지-않을-경우-최소-매출액">현재 팀장이 참석하지 않을 경우 최소 매출액</h4>
<pre><code>최소 매출액 = 팀원들의 최소값</code></pre><p>우선 팀원들이 각각 참석할 때와 참석하지 않았을 때의 매출 최소액을 구해야 한다.
팀원들 배열을 돌면서 <code>dp[0]</code>과 <code>dp[1]</code> 중 작은 값만 선택해 계산하면 될 것이다.</p>
<p>위의 케이스에서 팀원 중 최소 한 명이라도 참석을 하면 문제가 없겠으나,
팀원 모두 참석을 하지 않으면 문제가 생긴다. 현재 팀장도 참여를 안 하기 때문이다.</p>
<p>그래서 위의 계산을 진행하면서 한 명이라도 참석을 하는지도 함께 확인을 해야 한다.
만약 아무도 참석하지 않을 경우 최소 매출액을 위해서 한 명만 참석할 때의 케이스를 구하고 그 중 가장 최소값으로 현재 팀장의 <code>dp[0]</code>을 확정지어야 한다.</p>
<br />

<h4 id="현재-팀장이-참석할-경우-최소-매출액">현재 팀장이 참석할 경우 최소 매출액</h4>
<pre><code>최소 매출액 = 팀원들의 최소값 + 현재 팀장 매출액</code></pre><p>위와 같이 팀원 모두가 참석하지 않을 경우를 굳이 확인하지 않아도 된다.
팀장이 참석하는 것이 확정되었기 때문이다.</p>
<p>따라서 팀원들 배열을 돌면서 작은 값들의 합과 현재 팀장의 매출액까지 합하면 된다.</p>
<br />

<h4 id="마무리">마무리</h4>
<p>제일 리프노드부터 루트노드(CEO)까지의 dp를 모두 구했다.
<code>dp[0]</code>과 <code>dp[1]</code> 중 작은 값을 고르면 그것이 최소 매출액이다.</p>
<p><br /><br /></p>
<h3 id="💻-코드">💻 코드</h3>
<pre><code class="language-javascript">function solution(sales, links) {
  const tree = {}
  const dp = {}
  links.forEach((link) =&gt; {
    const [leader, member] = link
    if (!tree[leader]) tree[leader] = []
    if (!tree[member]) tree[member] = []
    tree[leader].push(member)
  })
  treeDP(sales, tree, dp, 1)
  return Math.min(...dp[1])
}

function treeDP(sales, tree, dp, index) {
  dp[index] = [0, 0]
  const children = tree[index]
  children.forEach((child) =&gt; {
    if (!dp[child]) treeDP(sales, tree, dp, child)
  })

  // dp[index][0] 구하기 - 1
  let isAttend = false
  dp[index][0] = children.reduce((sum, child) =&gt; {
    if (dp[child][0] &lt; dp[child][1]) return (sum += dp[child][0])
    isAttend = true
    return (sum += dp[child][1])
  }, 0)

  // dp[index][0] 구하기 -2
  if (!isAttend) {
    let minimum = Infinity
    for (let i = 0; i &lt; children.length; i++) {
      let sum = 0
      for (let j = 0; j &lt; children.length; j++) {
        if (i !== j) sum += dp[children[j]][0]
        else sum += dp[children[j]][1]
      }
      minimum = Math.min(minimum, sum)
    }
    if (minimum !== Infinity) dp[index][0] = minimum
  }

  // dp[index][1] 구하기
  dp[index][1] =
    children.reduce((sum, child) =&gt; (sum += Math.min(...dp[child])), 0) + sales[index - 1]
}</code></pre>
<p>너무 열받아 하면서 푼 덕분에 트리 DP는 절대 안 잊을 것 같다^_^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY45 - React(1)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY45-React1</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY45-React1</guid>
            <pubDate>Mon, 25 Nov 2024 17:37:11 GMT</pubDate>
            <description><![CDATA[<h3 id="🌽-크리티컬-렌터링-패스">🌽 크리티컬 렌터링 패스</h3>
<p><a href="https://velog.io/@min_ha/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-%EC%A0%95%EB%A6%AC">이미 공부했더랬지</a></p>
<ul>
<li>웹 페이지의 성능을 높이기 위해서는 리플로우와 리페인트를 줄여야 함</li>
<li>JS로 보여지는 여부를 결정해야 한다면, transform이나 opacity를 사용하는 것 권장 (display, position 노놉)<ul>
<li>이 속성들은 GPU가 관여할 수 있는 속성이기 때문에 리플로우 생략됨</li>
</ul>
</li>
<li>프로젝트의 규모가 커질수록, 리페인트와 리플로우 최적화에 신경쓰기 =&gt; 리액트가 도와줌</li>
</ul>
<br />

<h3 id="🌽-가상-dom">🌽 가상 DOM</h3>
<ul>
<li>리액트는 가상 DOM 2개 소유 중<ul>
<li>렌더링 이전의 화면 구조를 나타내는 DOM / 렌더링 이후 화면 구조를 나타내는 DOM</li>
</ul>
</li>
<li>브라우저는 DOM 트리나 Render 트리를 만들고 직접 다루는 것에 비해 리액트는 그저 객체 형태로만 DOM을 갖고 있기 때문에 아주 가벼움</li>
</ul>
<br />

<h3 id="리액트의-렌더링-단계">리액트의 렌더링 단계</h3>
<h4 id="1-render-phase--컴포넌트들이-가상-dom으로-구성되는-단계">1. render phase : 컴포넌트들이 가상 DOM으로 구성되는 단계</h4>
<ul>
<li>컴포넌트 파일들을 해석하여 실제 돔과 똑같은 구조의 트리 형태로 가상 DOM 생성</li>
<li>이전 DOM과 비교하여 변경된 요소들 찾기 =&gt; 이를 diffing이라 함<ul>
<li>굉장히 효율적인 알고리즘 사용</li>
<li>속성값만 변경되었다면 속성값만 업데이트</li>
<li>태그나 컴포넌트가 변경되었다면 해당 노드를 포함한 하위의 모든 노드 제거 &amp; 새 걸로 교체</li>
</ul>
</li>
<li>Reconciliation (재조정) 단계 진행<ul>
<li>변경된 요소들만 찾아 실제 브라우저 화면에 반영</li>
<li>리액트의 이 과정이 효율적인 이유 = Batch Update 덕분</li>
<li>변경된 요소들을 집단으로 묶어 한번에 적용시키는 방식이 Batch Update</li>
</ul>
</li>
</ul>
<h4 id="2-commit-phase--가상-dom을-실제-dom에-반영하는-단계">2. commit phase : 가상 DOM을 실제 DOM에 반영하는 단계</h4>
<ul>
<li>다시 파싱하는 방식이 아님 - 렌더 트리를 새로 만들지 않음. 필요한 부분만 갱신</li>
<li>리액트가 가상 DOM을 사용해 변경사항을 계산한 뒤, 필요한 부분에 대한 DOM 조작 명령을 브라우저에 전달</li>
<li>브라우저는 변경된 부분에 대해서만 리플로우, 리페인트 진행</li>
</ul>
<h4 id="가상-dom은-어떤-점에-있어서-성능에-도움이-되는건데">가상 DOM은 어떤 점에 있어서 성능에 도움이 되는건데?</h4>
<ul>
<li>가상 DOM없이 DOM을 여러 번 조작하면 리페인트와 리플로우가 계속 반복</li>
<li>가상 DOM은 변경사항들을 모아 한 번에 적용함으로써 리페인트와 리플로우 최소화</li>
<li>setState를 여러번 호출해도 마지막 내용만 반영되는 것도 이러한 이유</li>
</ul>
<br />

<h3 id="🌽-컴포넌트-파일-확장자">🌽 컴포넌트 파일 확장자</h3>
<h4 id="js">.js</h4>
<ul>
<li>일반적인 자바스크립트 파일 형식</li>
<li>트랜스파일러로 인해 JSX도 사용이 가능함 (가능하긴 하지만 JSX는 .jsx에서 사용하자)</li>
</ul>
<h4 id="jsx">.jsx</h4>
<ul>
<li>HTML 마크업을 편하게 작성하기 위해 개발된 새로운 언어 형식</li>
</ul>
<h4 id="tsx">.tsx</h4>
<ul>
<li>JSX의 타입스크립트 버전</li>
</ul>
<h4 id="ts">.ts</h4>
<ul>
<li>JSX 사용 불가</li>
</ul>
<br />

<h3 id="🌽-패키지">🌽 패키지</h3>
<ul>
<li><code>react-scripts</code> : CRA에서 사용할 수 있는 스크립트 모음. 웹팩, 바벨과 같은 스크립트 포함</li>
<li><code>@vitejs/plugin-react</code> : swc. 리액트에서 웹팩과 바벨의 역할을 하는 패키지</li>
</ul>
<br />

<h3 id="🌽-클래스형-컴포넌트--함수형-컴포넌트">🌽 클래스형 컴포넌트 &amp; 함수형 컴포넌트</h3>
<ul>
<li>아직 2가지 방식 모두 지원하고 있지만, 함수형 컴포넌트를 사용하자</li>
</ul>
<h4 id="클래스형-컴포넌트">클래스형 컴포넌트</h4>
<ul>
<li>리액트 초기 컴포넌트 형식</li>
<li>함수형 컴포넌트에 비해 복잡</li>
</ul>
<h4 id="함수형-컴포넌트">함수형 컴포넌트</h4>
<ul>
<li>최근 사용하는 컴포넌트 형식</li>
<li>함수형 컴포넌트가 초기에는 리액트의 생명주기를 다루지 못해 사용하기에 문제가 있었음</li>
<li>리액트 16.8부터 생명주기를 다룰 수 있는 리액트 훅을 제공하면서 함수형 컴포넌트가 매우 효율적인 방식이 됨</li>
</ul>
<h4 id="리액트-훅">리액트 훅</h4>
<ul>
<li>useState, useEffect, useContext, useReducer, useRef</li>
</ul>
<br />

<h3 id="🌽-리액트에서의-css-스타일링">🌽 리액트에서의 CSS 스타일링</h3>
<h4 id="기본-스타일">기본 스타일</h4>
<ul>
<li>인라인 스타일 : style 속성 사용</li>
<li>외부 스타일 : 별도의 css 파일에 작성하고 import하는 방식<ul>
<li>부모 요소가 아닌 제일 하위의 자식 요소에서 css 파일을 import해도 전역에 반영됨</li>
</ul>
</li>
<li>css modules : 특정 컴포넌트에만 종속되는 css 코드 작성 방식. 파일명 <code>*.module.css</code><ul>
<li>직관적으로 사용하기 위해 컴포넌트명과 파일명 일치시킴 + 같은 폴더 내에 위치</li>
</ul>
</li>
</ul>
<h4 id="css-in-js">CSS-In-JS</h4>
<ul>
<li>tailwind css</li>
<li>styled-components</li>
<li>emotion</li>
<li>vanila extract</li>
</ul>
<h4 id="관련-라이브러리-or-패키지">관련 라이브러리 or 패키지</h4>
<ul>
<li>classnames 패키지 : 조건에 따라 클래스명을 동적으로 결합하거나 관리할 때 유용 (서드파티 라이브러리)<ul>
<li>서드파티 라이브러리 : 외부의 제 3자 개발자가 제공하는 코드나 도구<pre><code class="language-jsx">import classNames from &quot;classnames/bind&quot;;
import styles from &quot;./Header.module.css&quot;;
</code></pre>
</li>
</ul>
</li>
</ul>
<p>...</p>
<p>const cx = classNames.bind(styles);
return &lt;h1 className={cx(&quot;title&quot;, &quot;decoration&quot;, { test: true })}&gt;제목</h1></p>
<pre><code>```jsx
import classNames from &quot;classnames&quot;;
import classNamesBind from &#39;classnames/bind&#39;;

...

return (
    &lt;header&gt;
      &lt;h1 className={cx(&quot;title&quot;, &quot;decoration&quot;)}&gt;제목&lt;/h1&gt;
      &lt;h2 className={classNames(&quot;title&quot;, { decoration: false })}&gt;부제목&lt;/h2&gt;
    &lt;/header&gt;
  );</code></pre><p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스 Lv.3] 단어 변환]]></title>
            <link>https://velog.io/@min_ha/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.3-%EB%8B%A8%EC%96%B4-%EB%B3%80%ED%99%98</link>
            <guid>https://velog.io/@min_ha/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.3-%EB%8B%A8%EC%96%B4-%EB%B3%80%ED%99%98</guid>
            <pubDate>Fri, 22 Nov 2024 14:03:49 GMT</pubDate>
            <description><![CDATA[<h2 id="◼️-문제">◼️ 문제</h2>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43163">단어 변환</a></p>
<h3 id="1-문제-설명">1. 문제 설명</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/79250b39-1206-46b8-9023-78fcd4e48bfd/image.png" alt=""></p>
<br />

<h3 id="2-제한사항">2. 제한사항</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/bf98f5f0-46e5-4cfa-8190-857b3d82b996/image.png" alt=""></p>
<br />

<h3 id="3-입출력">3. 입출력</h3>
<p><img src="https://velog.velcdn.com/images/min_ha/post/51906619-89bc-4ee3-8586-4b1549fe01ce/image.png" alt=""></p>
<p><br/><br /></p>
<h2 id="◼️-풀이">◼️ 풀이</h2>
<h3 id="✏️-전반적인-풀이과정">✏️ 전반적인 풀이과정</h3>
<p>가장 짧은 단계의 변환을 찾아야 한다 =&gt; 전형적인 BFS 방식이다.</p>
<ol>
<li>words 배열에 target이 존재하지 않으면 변환이 불가능 하므로 0을 반환한다.</li>
<li>BFS 순회를 위해 방문해야 할 단어를 저장하는 need_visited 배열, 방문한 단어를 기록하는 visited 객체를 생성한다.</li>
<li>begin부터 시작해야 하므로, need_visited에 begin을 넣고, visitied에 0번째 순서로 방문했다는 기록을 남긴다.</li>
<li>need_visited 배열에 아무것도 없을 때까지 target을 찾는다.
4-1. 현재 방문한 단어를 알기 위해 need_visited의 첫번째 요소를 꺼낸다.
4-2. 만약 현재 단어가 target과 같다면 현재 단계를 결과로 반환한다.
4-3. target이 아니라면, words를 하나씩 확인하며 다음 단계로 넘어갈 수 있는 단어를 찾는다. 찾은 단어가 방문하지 않았던 단어라면, need_visited 배열에 추가하고 몇 단계에 방문했는지와 함께 방문 기록을 visited 객체에 기록한다.</li>
<li>need_visited 배열에 아무것도 없을 때까지 변환을 했음에도 target에 도달할 수 없다면 0을 반환한다.</li>
</ol>
<br />

<h3 id="📃-풀이과정-상세-설명">📃 풀이과정 상세 설명</h3>
<h4 id="target이-words에-없는-경우">target이 words에 없는 경우</h4>
<p>단어를 계속 변환할 때 words 배열에 있는 단어로 변경해주면서 최종적으로 target을 만들어야 하기 때문에 words에 target이 없다면 무의미한 탐색이다.</p>
<pre><code class="language-javascript">// target이 words에 없다 === 변환이 불가능하다.
if (!words.includes(target)) return 0;</code></pre>
<p>따라서 제일 초반에 target이 words에 없는 경우에는 결과를 바로 리턴해주도록 한다.</p>
<br />

<h4 id="bfs를-위한-need_visited-visited-세팅">BFS를 위한 need_visited, visited 세팅</h4>
<pre><code class="language-javascript">const need_visited = []; // 방문해야 할 단어를 기록하는 배열 (큐)
const visited = {}; // 방문한 단어를 기록하는 객체</code></pre>
<p>BFS를 구현하기 위해 가장 필수적인 세팅이라고 볼 수 있다.</p>
<p>need_visitied는 앞으로 방문해야 할 단어를 넣어두는 대기실의 개념이다. 
방문해야 할 단어들은 push를 통해 뒤에 차곡차곡 쌓이게 될텐데 BFS의 개념은 너비우선탐색이기 때문에 넣어둔 단어는 앞에서부터 꺼내주어야 한다. 따라서 큐의 형식으로 사용할 예정이다.</p>
<p>visitied는 방문한 단어를 기록하는 객체이다. visitied는 배열이든, 객체든, Map이든, Set이든 각자 원하는대로 만들면 되는데, 나는 가장 익숙하고, 방문 여부를 기록함과 동시에 몇단계에서 방문한 단어인지 함께 기록하기 위해 객체를 택했다.</p>
<br />

<h4 id="begin-부터-시작">begin 부터 시작</h4>
<pre><code class="language-javascript">need_visited.push(begin);
visited[begin] = 0;</code></pre>
<p>begin부터 시작해야 하므로, 방문해야 할 요소로 need_visited 배열에 넣어준다.
그리고 정확히는 아직 방문하지 않았지만, need_visited 배열에 넣은 순간 방문은 확정된 것이나 다름없기 때문에 visitied에도 함께 기록한다.</p>
<p>begin에서 시작하는 건 아직 변환이 시작된 건 아니기에 0단계로 기록해준다.</p>
<br />

<h4 id="isnext-함수-구현">isNext 함수 구현</h4>
<p>본격적으로 BFS 탐색을 하기 전에 필요한 함수를 먼저 구현했다.</p>
<pre><code class="language-javascript">function isNext(word, target) {
  let count = 0;

  for (let i = 0; i &lt; word.length; i++) {
    if (word[i] !== target[i]) {
      count++;
      if (count &gt; 1) return false;
    }
  }

  return count === 1;
}</code></pre>
<p>이 함수는 현재 단어인 word가 target으로 변경이 가능한지 확인하는 함수이다.
단어 길이가 동일하다는 건 문제에서 보장해주었으니, 길이 걱정은 하지 않고 철자 하나하나를 비교하면 된다. </p>
<p>철자가 2개 이상 다르다면 바로 변환이 불가능하다는 의미의 false를 리턴한다.
모든 철자를 확인했을 때, 철자가 다른 개수가 1개라면 변환이 가능하다는 의미의 true를,
철자가 모두 동일하다면 변환이 불가능 하다는 false를 리턴한다.</p>
<p>여기서 문제에 words에는 중복되는 단어가 없다고 해주었기 때문에 count가 0인 경우도 고려를 해야하나 싶지만, begin이 words에 없다는 보장은 없기 때문에 필요하다!
(이 부분을 고려하지 않아서 처음 제출할 땐 틀렸다..)</p>
<br />

<h4 id="bfs-탐색-시작">BFS 탐색 시작</h4>
<pre><code class="language-javascript">while (need_visited.length &gt; 0) {
  // 현재 단어 꺼내기
  const current = need_visited.shift();

  // 현재 단어가 target이라면 바로 결과 리턴
  if (current === target) return visited[target];

  // 현재 단어가 target이 아니라면 변환 가능한 단어 검색
  words.forEach((word) =&gt; {
    if (isNext(current, word) &amp;&amp; !visited[word]) {
      need_visited.push(word);
      visited[word] = visited[current] + 1;
    }
  });
}</code></pre>
<p>방문해야 할 단어가 없을 때까지 계속 target을 찾으면 된다.</p>
<p>현재 단어를 알아야 하므로 need_visited의 첫번째 요소를 꺼내, target과 일치하는지 확인한다. 만약 target이 맞다면, 이미 visited에 몇단계인지 기록을 했을테니 결과를 리턴해주면 된다.</p>
<p>target이 아니라면 현재 단어를 기준으로 변환 가능한 단어를 찾아야 하기 때문에, words 배열을 순회하며 isNext 함수로 변환 가능 여부를 확인한다.
특정 단어로 변환이 가능하다는 것을 확인했다면 그 단어가 이미 방문한(혹은 방문할) 단어인지도 확인해야 한다. (확인할 거 투성이네;;)</p>
<p>만약 이미 방문 기록이 있다면 패스.
아직 방문하지 않은 단어라면 need_visited에 넣어주고, 방문하게 될 단계와 함께 방문 기록을 visitied에 남겨준다.</p>
<p>이걸 target을 찾을 때까지 혹은 need_visited가 빌 때까지 반복하면 된다. need_visited에 아무것도 없을 때까지 확인했음에도 target을 못찾았다면 변환이 불가능한 케이스이므로 0을 반환한다.</p>
<p><br /><br /></p>
<h3 id="💻-코드">💻 코드</h3>
<pre><code class="language-javascript">function solution(begin, target, words) {
  if (!words.includes(target)) return 0;

  const need_visited = [];
  const visited = {};

  need_visited.push(begin);
  visited[begin] = 0;

  while (need_visited.length &gt; 0) {
    const current = need_visited.shift();

    if (current === target) return visited[target];

    words.forEach((word) =&gt; {
      if (isNext(current, word) &amp;&amp; !visited[word]) {
        need_visited.push(word);
        visited[word] = visited[current] + 1;
      }
    });
  }

  return 0;
}

function isNext(word, target) {
  let count = 0;

  for (let i = 0; i &lt; word.length; i++) {
    if (word[i] !== target[i]) {
      count++;
      if (count &gt; 1) return false;
    }
  }

  return count === 1;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY38 - TypeScript(3)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY38-TypeScript3</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY38-TypeScript3</guid>
            <pubDate>Wed, 20 Nov 2024 14:29:04 GMT</pubDate>
            <description><![CDATA[<h2 id="🥨-타입-별칭-type-alias">🥨 타입 별칭 (type alias)</h2>
<h3 id="1-기본적인-타입-별칭">1. 기본적인 타입 별칭</h3>
<pre><code class="language-typescript">type ID = string | number;</code></pre>
<br />

<h3 id="2-객체-타입-별칭">2. 객체 타입 별칭</h3>
<pre><code class="language-typescript">type User = {
  name: string;
  age: number;
};</code></pre>
<br />

<h3 id="3-함수-타입-별칭">3. 함수 타입 별칭</h3>
<pre><code class="language-typescript">type SumFunc = (n1: number, n2: number) =&gt; number;
const sum: SumFunc = function sum(n1, n2) {
  return n1 + n2;
};</code></pre>
<br />

<h3 id="4-제네릭-타입-별칭">4. 제네릭 타입 별칭</h3>
<ul>
<li>제네릭은 여러 개 사용 가능</li>
<li>관용적으로 대문자 사용<pre><code class="language-typescript">type Car&lt;T&gt; = {
name: string;
color: string;
option: T;
};
</code></pre>
</li>
</ul>
<p>const car1: Car<string> = {
  name: &quot;benz&quot;,
  color: &quot;black&quot;,
  option: &quot;key&quot;,
};</p>
<p>const car3: Car&lt;{ giftcard: boolean }&gt; = {
  name: &quot;benz&quot;,
  color: &quot;black&quot;,
  option: {
    giftcard: true,
  },
};</p>
<pre><code>
```typescript
type Car2&lt;T, U&gt; = {
  name: string;
  color: string;
  option: T;
  other: U;
};</code></pre><br />

<h3 id="5-튜플-타입-별칭">5. 튜플 타입 별칭</h3>
<pre><code class="language-typescript">type Point = [number, number];
const point: Point = [10, 20];</code></pre>
<br />

<h3 id="6-인터섹션-타입-별칭">6. 인터섹션 타입 별칭</h3>
<pre><code class="language-typescript">type Nameable = {
  name?: string;
};

type Aageable = {
  age?: number;
};

type Person = Nameable &amp;
    Aageable &amp; {
      gender?: string;
    };
const person: Person = {};</code></pre>
<br />

<h3 id="7-리터럴-타입-별칭">7. 리터럴 타입 별칭</h3>
<ul>
<li>리터럴 타입에 설정되어 있는 값만 사용 가능하므로 자동 완성 기능 지원<pre><code class="language-typescript">type Direction = &quot;LEFT&quot; | &quot;RIGHT&quot; | &quot;UP&quot; | &quot;DOWN&quot;;
const direction: Direction = &quot;LEFT&quot;;</code></pre>
</li>
</ul>
<br />

<h3 id="8-조건부-타입-별칭">8. 조건부 타입 별칭</h3>
<ul>
<li><code>T extends U ? X : Y</code><pre><code class="language-typescript">type IsString&lt;T&gt; = T extends string ? &quot;Yes&quot; : &quot;No&quot;;
</code></pre>
</li>
</ul>
<p>const test1: IsString<string> = &quot;Yes&quot;;
const test2: IsString<number> = &quot;No&quot;;</p>
<pre><code>
&lt;br /&gt;

### 9. 키 선택 타입 별칭
```typescript
type Person = {
  name: string;
  age: number;
  address: string;
};

type PersonKeys = keyof Person; // &quot;name&quot; | &quot;age&quot; | &quot;address&quot;</code></pre><br />

<h3 id="10-인덱스-시그니처-타입-별칭">10. 인덱스 시그니처 타입 별칭</h3>
<ul>
<li>인덱스 시그니처 + 다른 속성 타입 지정할 수 있음<pre><code class="language-typescript">type UserMap = {
age: number;
[key: string]: string; // 인덱스 시그니처
};
</code></pre>
</li>
</ul>
<p>let user: UserMap = {
  name: &quot;John&quot;,
  gender: &quot;male&quot;,
  age: 20
};</p>
<pre><code>
&lt;br /&gt;&lt;br /&gt;

## 🍡 인터페이스 (interface)
- 타입은 툴팁으로 상세 정보 확인 가능한데, 인터페이스는 안된다ㅠ

### 1. 객체 타입 인터페이스
```typescript
interface User {
  name: string;
  age: number;
}</code></pre><br />

<h3 id="2-옵셔널">2. 옵셔널</h3>
<pre><code class="language-typescript">interface User {
  name: string;
  age?: number;
}</code></pre>
<br />

<h3 id="3-읽기-전용">3. 읽기 전용</h3>
<ul>
<li>튜플이나 배열은 상수로</li>
<li>객체는 속성 상수로<pre><code class="language-typescript">interface User {
readonly name: string;
age: number;
}</code></pre>
</li>
</ul>
<br />

<h3 id="4-인덱스-시그니처">4. 인덱스 시그니처</h3>
<ul>
<li>자동 완성 지원 X<pre><code class="language-typescript">interface User {</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
&lt;br /&gt;

### 5. 함수 타입 인터페이스
- 거의 안 쓰지만 가능은 하다는 거 잊지 말기
```typescript
interface SumFunc {
  (n1: number, n2: number): number;
}

const sum: SumFunc = function (n1, n2) {
  return n1 + n2;
};</code></pre><br />

<h3 id="6-병합">6. 병합</h3>
<ul>
<li>똑같은 인터페이스를 여러번 선언하면 자동 병합</li>
<li>타입 별칭은 불가능</li>
<li>같은 속성, 다른 타입은? =&gt; 에러</li>
</ul>
<br />

<h3 id="7-상속">7. 상속</h3>
<ul>
<li>인터페이스 확장 =&gt; <code>interface Developer extends Person {}</code></li>
<li>다중 상속도 가능</li>
<li>타입 별칭도 인터섹션으로 비슷하게는 가능</li>
</ul>
<br />

<h3 id="8-그외">8. 그외</h3>
<h4 id="단축-메서드-표현">단축 메서드 표현</h4>
<pre><code class="language-typescript">interface Animal {
  name: string;
  sound(): void;
  bark: () =&gt; void;
  play?(): void; // 그래서 타입 가드 한 번 써줘야 함
}</code></pre>
<h4 id="제네릭--상속">제네릭 &amp; 상속</h4>
<pre><code class="language-typescript">interface Container&lt;T&gt; {
  value: T;
}

interface Box&lt;T&gt; extends Container&lt;T&gt; {
  label: string;
}

const box: Box&lt;number&gt; = {
  label: &quot;grid box&quot;,
  value: 10,
};</code></pre>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY37 - TypeScript(2)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY37-TypeScript2</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY37-TypeScript2</guid>
            <pubDate>Tue, 19 Nov 2024 13:36:12 GMT</pubDate>
            <description><![CDATA[<h2 id="🦾-타입-오퍼레이터">🦾 타입 오퍼레이터</h2>
<h3 id="1-유니언-타입">1. 유니언 타입</h3>
<ul>
<li>매개변수, 반환 값 모두 유니언 타입을 적용할 수 있다.</li>
<li>단, 어떤 타입이 올 지 확실하지 않기 때문에 특정 타입에만 사용 가능한 메서드를 호출하면 에러가 발생한다.<ul>
<li>타입 가드를 사용한다. (typeof)</li>
</ul>
</li>
</ul>
<h4 id="타입-가드">타입 가드</h4>
<ul>
<li>코드에서 값의 타입을 좁히거나 확실하게 추론할 수 있도록 도와주는 방법이다.</li>
</ul>
<br />

<h3 id="2-인터렉션-타입">2. 인터렉션 타입</h3>
<ul>
<li>여러 개의 타입을 모두 만족하고 싶을 때 사용한다.</li>
<li>어떤 타입과 any와 결합하면 any로 평가된다.</li>
<li>어떤 타입과 never와 결합하면 never로 평가된다.</li>
</ul>
<br />

<h3 id="3-함수-오버로드">3. 함수 오버로드</h3>
<ul>
<li>함수의 타입이 여러 개 전달될 수 있을 때, 다양한 동작을 처리할 수 있도록 정밀하게 타입을 지정하는 방법</li>
<li>중간에 다른 코드가 끼어들면 안된다. (주석이나 빈 줄은 가능)</li>
<li>코드가 복잡해질 수 있으나 오버로드 시그니처를 보여주는 기능이 있어 어떤 타입을 넣었을 때 어떤 타입이 리턴되는지를 빠르게 알 수 있다.</li>
<li>함수 선언문으로만 가능하다. 함수 표현식이나 화살표 함수로는 불가능하다.</li>
</ul>
<pre><code class="language-typescript">function combine(a: number, b: string): string; // 오버로드 시그니처
function combine(a: string, b: number): string;
function combine(a: number, b: number): number;
function combine(a: string, b: string): string;
function combine(a: number | string, b: number | string): number | string { 
  if (typeof a === &quot;number&quot; &amp;&amp; typeof b === &quot;number&quot;) return a + b;
    return `${a}${b}`;
}</code></pre>
<br />

<h3 id="4-타입-추론-vs-타입-표명">4. 타입 추론 vs 타입 표명</h3>
<h4 id="타입-추론">타입 추론</h4>
<ul>
<li>타입스크립트 엔진이 기본적으로 수행하는 작업</li>
<li>변수에 할당되는 것을 보고 자동으로 타입을 추론하여 적용해주는 기능</li>
<li>초기화를 하지 않는 변수는 타입 추론이 불가능하기 때문에 any로 추론</li>
<li>함수 매개변수는 타입 추론 불가 (기본값 할당하면 가능)</li>
<li>타입 추론을 적극적으로 사용하면 깔끔한 코드를 작성할 수 있다.</li>
</ul>
<pre><code class="language-typescript">let num1 = 10; // number로 타입 추론
const num2 = 30; // 🚨 30(리터럴 타입)으로 타입 추론</code></pre>
<h4 id="타입-표명">타입 표명</h4>
<ul>
<li>직접 타입을 지정하는 것</li>
</ul>
<br />

<h3 id="5-타입-별칭-type-alias">5. 타입 별칭 (type alias)</h3>
<ul>
<li>나만의 타입 만들기</li>
<li>대문자로 시작하는 것이 규칙</li>
<li>타입 식별자와 변수 식별자는 이름이 같아도 따로 관리됨 (중복 OK)<pre><code class="language-typescript">type MyType = string | number | booleanl;</code></pre>
</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY36 - TypeScript(1)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY36-TypeScript1</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY36-TypeScript1</guid>
            <pubDate>Mon, 18 Nov 2024 12:19:25 GMT</pubDate>
            <description><![CDATA[<h2 id="타입스크립트의-함수-타입">타입스크립트의 함수 타입</h2>
<h3 id="1-함수의-타입">1. 함수의 타입</h3>
<ul>
<li>매개변수와 반환 값의 타입을 지정하는 것</li>
<li>void: 매개변수 타입. 아무것도 반환하지 않는다는 뜻</li>
<li>never: 매개변수 타입. 반환 값으로만 사용하는 타입<ul>
<li>무한루프나 에러 던질 때 사용</li>
</ul>
</li>
</ul>
<br />

<h3 id="2-타입-오퍼레이터-type-operator">2. 타입 오퍼레이터 (type operator)</h3>
<ul>
<li>&amp;(AND) : intersection 타입<ul>
<li>기본 타입에서는 사용 불가</li>
<li>보통 객체에서 사용됨</li>
<li>같은 이름의 속성명은 같은 타입이면 자연스럽게 합쳐짐</li>
<li>같은 이름의 속성명이 다른 타입이면 문제가 생김 (개발자가 알아서 신경써야 함)</li>
</ul>
</li>
<li>|(OR) : union 타입</li>
</ul>
<br />
오늘은 쉬웠다~!]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY33 - TypeScript(1)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY33-TypeScript1</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY33-TypeScript1</guid>
            <pubDate>Sun, 17 Nov 2024 11:07:32 GMT</pubDate>
            <description><![CDATA[<h2 id="🧽-타입스크립트-준비">🧽 타입스크립트 준비</h2>
<h3 id="1-자바스크립트-복습">1. 자바스크립트 복습</h3>
<ul>
<li>템플릿 문자열</li>
<li>함수 (함수 선언문, 함수 표현식, 화살표 함수)</li>
<li>비구조화 할당 : 배열과 객체에서 값을 추출하여 변수에 할당하는 것<ul>
<li>배열</li>
<li>객체</li>
</ul>
</li>
<li>spread 연산자 (...)<ul>
<li>객체 혹은 배열 합치기에 편리</li>
<li>깊은 복사가 불가능 하다는 점</li>
</ul>
</li>
</ul>
<h4 id="궁금증">궁금증</h4>
<p>spread 연산자와 rest 파라미터의 차이</p>
<ul>
<li>사용 방법은 동일</li>
<li>함수의 파라미터로 쓰이면 rest 파라미터 (단, 함수 매개변수의 가장 마지막에 작성)</li>
<li>객체나 배열 등에 사용되면 spread 연산자 (위치 상관없음. 여러번 사용 가능)</li>
</ul>
<br />

<h3 id="2-타입스크립트-세팅">2. 타입스크립트 세팅</h3>
<ul>
<li><code>npm init -y</code> : 노드 패키지 초기화 (필요한 값은 알아서 세팅)</li>
<li><code>npm ls -g --depth=0</code> : 설치되어 있는 개발 라이브러리 목록<ul>
<li>이 출력 결과가 텅텅 비어있게 관리하면 좋음</li>
<li>항상 최신 버전을 사용해야 하는데, 글로벌로 설치된 라이브러리의 버전으로 설치되기 때문</li>
<li><code>npm install -g</code> 명령어 주의</li>
</ul>
</li>
<li><code>npm cache verify</code> : 컴퓨터에 있는 캐시 파일을 확인하면서 불필요한 파일 삭제</li>
<li><code>npm install typescript --save-dev</code> : 타입스크립트 설치</li>
<li><code>npm tsc --init</code> : 타입스크립트 초기화 (tsconfig.json 파일 생성)</li>
<li><code>npm tsc src/index.ts</code> : 타입스크립트로 작성된 파일이 자바스크립트 코드로 만들어 줌<ul>
<li>tsc는 TS를 JS로 변경해주는 컴파일러</li>
<li>파일 하나면 컴파일 할 때는 tsconfig.json 참고 X</li>
<li><code>npm tsc</code> : tsconfig.json 참고하며 컴파일 진행</li>
</ul>
</li>
</ul>
<br />

<h3 id="3-타입스크립트와-자바스크립트의-차이">3. 타입스크립트와 자바스크립트의 차이</h3>
<ul>
<li>JS는 모든 것이 런타임에 결정. 코드를 실행해봐야 알 수 잇음</li>
<li>TS는 컴파일 단계에서 알 수 있음. 컴파일 과정에서 오류가 있는지 없는지 확인 가능<ul>
<li>이런 이유 때문에 TS는 안정적인 언어라고 함.</li>
<li>코드 실행 이전에 오류를 찾을 수 있으니까.</li>
</ul>
</li>
<li>타입스크립트(TS) = 자바스크립트(JS) + 타입 시스템(type system)</li>
</ul>
<h4 id="궁금증-1">궁금증</h4>
<p>lock.json 파일은 무엇인가요?</p>
<ul>
<li>package.json 파일을 한 번더 검증하기 위한 파일</li>
<li>깃허브에 코드를 올릴 때, node_modules나 package-lock.json은 올릴 필요가 없습니다.</li>
</ul>
<p>왜 TS를 JS로 변경해야 하나요?</p>
<ul>
<li>ndex.html은 TS 파일 실행 불가</li>
<li>브라우저는 오로지 html, css, js만 해석 가능<ul>
<li>css만 해석 가능 =&gt; SCSS, SASS 해석 불가</li>
<li>html만 해석 가능 =&gt; pug 해석 불가</li>
<li>js만 해석 가능 =&gt; ts 해석 불가</li>
</ul>
</li>
<li>TS는 웹브라우저가 해석할 수 없는 언어이기 때문에 JS로 변경해주어야 한다.</li>
</ul>
<p>npx는 무엇인가요?</p>
<ul>
<li>npx는 node package executor (npm을 실행 시킴)</li>
<li>설치한 노드 패키지를 실행할 때 사용하는 도구</li>
<li><code>npm tsc</code> : tsc라는 패키지를 실행하라!</li>
<li><code>npx ts-node -v</code> : 로컬 환경에 ts-node가 없다면 npx가 설치할지 말지 물어봐줌 &amp; 설치도 해줌</li>
</ul>
<p>ts를 코드 러너로 실행하면 tsconfig.json 설정으로 실행되나요?</p>
<ul>
<li>노놉. 코드 러너 기본 내장 설정으로 실행</li>
</ul>
<p><br /><br /></p>
<h2 id="🎟️-타입스크립트">🎟️ 타입스크립트</h2>
<h3 id="1-타입시스템의-타입">1. 타입시스템의 타입</h3>
<ul>
<li>string</li>
<li>number</li>
<li>boolean<ul>
<li>truty나 falsy는 !!로 붙여주어 boolean으로 완전히 바꿔주기</li>
<li>&quot;true&quot;, &quot;false&quot; 문자열은 JSON.parse()로 변경 가능</li>
</ul>
</li>
<li>object<ul>
<li>상당히 포괄적인 타입 (일반 객체, 빈 객체, 배열, 함수 전부 가능)</li>
<li>object 타입을 사용하기보다는 객체 속성을 명시적으로 작성해주는 방식으로 사용하기</li>
</ul>
</li>
<li>array<ul>
<li>타입명은 따로 없고 대괄호를 넣으면 됨</li>
<li>예전에는 <code>Array&lt;string&gt;</code> 이렇게 쓰이기도 했으나, 요즘엔 <code>string[]</code>으로 쓰임</li>
</ul>
</li>
<li>tuple<ul>
<li>튜플은 여러가지 타입의 데이터를 배열에 넣은 것. (길이 고정)</li>
<li>옵셔널(?) : 있어도 되고 없어도 되고</li>
<li>rest 연산자 사용 가능</li>
<li><code>const [item, ...rest]: [string, ...number[]] = [&quot;a&quot;, 1, 2, 3]</code></li>
</ul>
</li>
<li>null, undefined<ul>
<li>null 병합 연산자 (??)</li>
<li><code>const c:string = x ?? &quot;test&quot;</code></li>
</ul>
</li>
<li>any<ul>
<li>모든 타입을 받을 수 있음 (타입을 체크하지 않겠다라는 의미로 봐도 무방)</li>
<li>사용 시 주의</li>
<li>지금 당장 어떤 타입까지 고려하기 어려울 때 사용</li>
</ul>
</li>
<li>unknown<ul>
<li>나중에 뭐가 들어갔는지 확인해야 함</li>
<li>타입가드 (타입 검사)</li>
<li><code>if (Array.isArray(value)) { ... }</code></li>
<li>any로 사용할 바에는 unknown 사용하기</li>
</ul>
</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스/TIL] DAY29 - Node.js(2)]]></title>
            <link>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY29-Node.js2</link>
            <guid>https://velog.io/@min_ha/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4TIL-DAY29-Node.js2</guid>
            <pubDate>Mon, 11 Nov 2024 15:14:32 GMT</pubDate>
            <description><![CDATA[<h2 id="🗂️-파일-시스템">🗂️ 파일 시스템</h2>
<h3 id="1-nodejs의-비동기-메서드">1. Node.js의 비동기 메서드</h3>
<ul>
<li>Promise 리턴<ul>
<li>콜백 or async/await 처리</li>
</ul>
</li>
</ul>
<br />


<h3 id="2-버퍼와-스트림">2. 버퍼와 스트림</h3>
<ul>
<li>버퍼 : 메모리에서 직접 바이트 데이터 처리<ul>
<li>한 번에 메모리에 올릴 때 유용</li>
<li>대용량 데이터에는 비효율적</li>
<li>ex) fs 모듈 - <code>readFile</code></li>
</ul>
</li>
<li>스트림 : 청크 단위로 데이터를 처리하여 메모리 사용량 절약<ul>
<li>읽기 스트림 / 쓰기 스트림</li>
<li>ex) fs 모듈 - <code>createReadStream</code>, <code>createWriteStream</code></li>
</ul>
</li>
</ul>
<br />

<h3 id="3-버퍼를-base64로-인코딩">3. 버퍼를 base64로 인코딩</h3>
<ul>
<li><p>읽어온 이미지 파일을 base64 인코딩 -&gt; 웹 브라우저에 직접 렌더링 가능</p>
<ul>
<li>base64 : 바이너리 데이터를 문자열로 변환하는 방법</li>
<li>이메일이나 JSON 데이터로 이미지 전송할 때 유용<pre><code class="language-javascript">fs.readFile(&#39;example.jpg&#39;, (err, data) =&gt; {
if (err) throw err;
</code></pre>
</li>
</ul>
<p>const base64Image = data.toString(&#39;base64&#39;);
console.log(&#39;이미지:&#39;, base64Image);</p>
<p>/*</p>
<img src="data:image/jpeg;base64,${base64Image}" />
*/
});
```
<br /><br />

</li>
</ul>
<h2 id="🎉-이벤트">🎉 이벤트</h2>
<ul>
<li>Node.js는 싱글 스레드 이벤트 루프로 비동기 작업 처리 (여러 작업 동시 처리 가능)<ul>
<li>비동기 처리로 이벤트 큐에 등록, 이벤트 루프는 다른 작업 수행</li>
</ul>
</li>
<li>각 작업이 완료될 때 이벤트 발생 -&gt; 바인딩된 콜백 함수 실행</li>
<li>고성능 네트워크 애플리케이션 구축에 유리 (IO 작업)</li>
</ul>
<h4 id="이벤트가-왜-필요한데">이벤트가 왜 필요한데?</h4>
<ul>
<li>이벤트는 콜백 함수가 비동기적으로 실행<ul>
<li>처리중인 작업이 끝나면 알아서 실행되는 것</li>
</ul>
</li>
<li>반면 함수는 작업이 완료될 때까지 대기 =&gt; 비동기 처리 어려움</li>
</ul>
<h3 id="1-events-모듈">1. events 모듈</h3>
<ul>
<li><code>events</code> 모듈 (내장 객체)</li>
<li><code>events</code> 모듈의 <code>EventEmitter</code> 클래스로 이벤트 생성 및 처리</li>
<li>이벤트 정의 &amp; 발생 &amp; 처리 리스너 연결<pre><code class="language-javascript">const EventEmitter = require(&#39;events&#39;);
const myEmitter = new EventEmitter();</code></pre>
</li>
</ul>
<br />

<h3 id="2-이벤트-생성--리스너-등록">2. 이벤트 생성 &amp; 리스너 등록</h3>
<ul>
<li><code>on</code> 메서드 : 이벤트 리스너 등록</li>
<li><code>emit</code> 메서드 : 이벤트 발생</li>
<li>특정 이벤트에 여러 개의 리스너 등록 가능(이벤트 발생 시, 순서대로 호출)<pre><code class="language-javascript">// greet라는 이벤트가 발생하면 이런 콜백함수를 실행할거야
myEmitter.on(&quot;greet&quot;, (name) =&gt; {
console.log(`안녕하세요. ${name}님!`);
});
</code></pre>
</li>
</ul>
<p>// greet 이벤트 발생!!!
myEmitter.emit(&#39;greet&#39;, &#39;영희&#39;);</p>
<pre><code>- 리스너 내부에서 비동기 작업 처리 가능
```javascript
myEmitter.on(&#39;asyncEvent&#39;, async () =&gt; {
  const data = await Promise.resolve();
  console.log(data);
});

myEmitter.emit(&#39;asyncEvent&#39;);</code></pre><h4 id="on-메서드--once-메서드">on 메서드 &amp; once 메서드</h4>
<ul>
<li><code>on(event, listener)</code> : 이벤트가 여러번 발생할 때마다 호출</li>
<li><code>once(event, listener)</code> : 첫 이벤트만 듣고 이후 이벤트는 듣지 않음<ul>
<li>첫 이벤트 발생 후, 리스너 삭제</li>
</ul>
</li>
<li>궁금) 첫 이벤트 발생 후, 같은 이벤트와 콜백함수를 다시 등록하면?<ul>
<li>그럼 괜찮음 ^_^</li>
</ul>
</li>
</ul>
<br />

<h3 id="3-이벤트-리스너-삭제">3. 이벤트 리스너 삭제</h3>
<ul>
<li><code>removeListener(event, listener)</code> : 특정 이벤트에 등록된 리스너 제거<ul>
<li>리스너를 먼저 변수에 담아 등록 &amp; 삭제</li>
</ul>
</li>
<li><code>removeAllListeners(event)</code> : 특정 이벤트에 연결된 모든 리스너 제거</li>
</ul>
<br />

<h3 id="4-에러-처리">4. 에러 처리</h3>
<ul>
<li>에러 이벤트 리스너를 정의하고 에러가 발생하면 에러 이벤트를 발생시키는 방식<pre><code class="language-javascript">myEmitter.on(&#39;error&#39;, (err) =&gt; {
console.error(&#39;에러 발생:&#39;, err.message);
});
</code></pre>
</li>
</ul>
<p>myEmitter.on(&#39;someEvent&#39;, () =&gt; {
  // 일반적인 에러가 아니라 명시적으로 &quot;error&quot; 이벤트를 발생시킴
  myEmitter.emit(&#39;error&#39;, new Error(&#39;명시적인 에러 발생&#39;));
});</p>
<pre><code>
### 5. 이벤트 루프 &amp; 비동기 작업
```javascript
// 현재 실행 작업이 완료 된 직후 실행
process.nextTick(() =&gt; console.log(&#39;nextTick 실행&#39;));

// 다음 이벤트 루프 주기에서 실행
setImmediate(() =&gt; console.log(&#39;setImmediate 실행&#39;));

console.log(&#39;일반 코드 실행&#39;);</code></pre><pre><code>일반 코드 실행
nextTick 실행
setImmediate 실행</code></pre><p><br /><br /></p>
<h2 id="웹-소켓">웹 소켓</h2>
<h3 id="1-websocket-모듈">1. Websocket 모듈</h3>
<pre><code class="language-javascript">const Websocket = require(&quot;ws&quot;); // 외부 모듈

// 웹 소켓 서버(인스턴스) 생성
const wss = new Websocket.Server({ port: 8080 });</code></pre>
<h3 id="2-클라이언트-접속">2. 클라이언트 접속</h3>
<ul>
<li><p>connection 이벤트 : 클라이언트가 접속했을 때 발생</p>
<ul>
<li>서버 실행도 connection 이벤트 발생</li>
</ul>
</li>
<li><p>웹소켓 서버에서 기본적으로 제공하는 이벤트</p>
</li>
<li><p>emit 없어도 자체적으로 발생</p>
<pre><code class="language-javascript">// 클라이언트 하나만을 위한 고유한 연결 (각각 연결됨)
// ws는 클라이언트와의 연결을 나타내는 웹소켓 객체 (클라이언트의 정보 소유)
wss.on(&quot;connection&quot;, (ws) =&gt; {
ws.id = new Date().getTime().toString().slice(-3);
console.log(&quot;연결됐음&quot;);

// 클라이언트로부터 메시지 수신
ws.on(&quot;message&quot;, (message) =&gt; {
  console.log(`${ws.id}로부터 메시지 수신: ${message}`);

  // 메시지 발신한 유저 외 나머지 유저에게 메시지 전달
  wss.clients.forEach((client) =&gt; {
    if (client !== ws) {
      client.send(message.toString());
    }
  });

// 클라이언트와의 연결이 끊길 때
ws.on(&quot;close&quot;, () =&gt; {
  console.log(&quot;연결 종료&quot;);
});</code></pre>
</li>
</ul>
<br />

<h3 id="3-클라이언트에서는">3. 클라이언트에서는</h3>
<pre><code class="language-javascript">const socket = new WebSocket(&quot;ws://localhost:8080&quot;);

socket.addEventListener(&quot;open&quot;, function() {
  console.log(&quot;서버와 연결&quot;);
});

socket.addEventListener(&quot;message&quot;, function(e) {
  console.log(e.data);
});

something.addEventListener(&quot;이벤트&quot;, function() {
  socket.send(&quot;메시지&quot;);
}</code></pre>
<p><br /><br /></p>
<h2 id="🎢-express">🎢 Express</h2>
<h3 id="1-express-모듈">1. express 모듈</h3>
<ul>
<li>HTTP 요청의 경로 &amp; HTTP 메서드를 통해 쉽게 API 요청 가능 (간단!)<pre><code class="language-javascript">const express = require(&quot;express&quot;); // 외부 모듈
const app = express();
const port = 5000;</code></pre>
</li>
</ul>
<br />

<h3 id="2-미들웨어-등록">2. 미들웨어 등록</h3>
<ul>
<li>요청이 발생되기 전에 먼저 거쳐 가는 부분</li>
<li>서버가 요청을 처리하기 전에 실행할 기능 설정<pre><code class="language-javascript">// 요청 body를 자동 파싱 =&gt; 객체 형태로 전달
// Content-type: application/json일 때 유용
app.use(express.json());
</code></pre>
</li>
</ul>
<p>// URL-encoded 파라미터 파싱 =&gt; 객체 형태로 전달
app.use(express.urlencoded({ extended: true }));</p>
<pre><code>
&lt;br /&gt;

### 3. API 요청
#### GET : 항목 조회
```javascript
app.get(&#39;/path/:id&#39;, (req, res) =&gt; { ... });
// req.qeury -&gt; 쿼리 문자열에 포함된 파라미터들을 담고 있는 객체
// req.params -&gt; URL 파라미터 파싱
// res.json(...) -&gt; JSON 형식으로 클라이언트에게 응답 보냄
// res.status(404).send(&#39;메시지&#39;) -&gt; 메시지와 함께 404 에러 보냄</code></pre><h4 id="post--새-항목-추가">POST : 새 항목 추가</h4>
<pre><code class="language-javascript">app.post(&#39;/path&#39;, (req, res) =&gt; { ... });
// req.body -&gt; 클라이언트가 보낸 본문이 담긴 데이터
// res.status(201).json(데이터) -&gt; 데이터와 함께 201 성공 보냄</code></pre>
<h4 id="put--전체-항목-업데이트">PUT : 전체 항목 업데이트</h4>
<pre><code class="language-javascript">app.put(&#39;/path/:id&#39;, (req, res) =&gt; { ... });</code></pre>
<h4 id="patch--부분-업데이트">PATCH : 부분 업데이트</h4>
<pre><code class="language-javascript">app.patch(&#39;/path/:id&#39;, (req, res) =&gt; { ... });</code></pre>
<h4 id="delete--항목-삭제">DELETE : 항목 삭제</h4>
<pre><code class="language-javascript">app.delete(&#39;/path/:id&#39;, (req, res) =&gt; { ... });</code></pre>
<h4 id="options--cors-및-허용-메서드-확인">OPTIONS : CORS 및 허용 메서드 확인</h4>
<ul>
<li><p>options 메서드 : 클라이언트가 서버에 어떤 HTTP 메서드를 지원하는지 확인하려고 할 때 사용</p>
</li>
<li><p>CORS 요청이나 프리플라이트 요청에 의해 자동으로 발생</p>
<pre><code class="language-javascript">app.options(&#39;/items&#39;, (req, res) =&gt; {
// 서버가 지원하는 메서드 명시
res.set(&#39;Allow&#39;, &#39;GET, POST, PUT, PATCH, DELETE, OPTIONS&#39;);

// 상태 코드 반환 (204 = 본문 내용 X, 요청 성공 O)
res.sendStatus(204);
});</code></pre>
</li>
</ul>
<br />

<h3 id="4-서버-시작">4. 서버 시작</h3>
<pre><code class="language-javascript">app.listen(PORT, () =&gt; {
  console.log(`Server running :${PORT}`);
});</code></pre>
<p><br /><br /></p>
<h2 id="✏️-메모">✏️ 메모</h2>
<ul>
<li>nodemon =&gt; 서버를 실행하면서 코드를 수정하면 자동으로 서버 재시작<ul>
<li>설치 : <code>npm install nodemon</code></li>
<li>실행 : <code>npx nodemon 파일.js</code></li>
</ul>
</li>
<li>웹소켓에 클라이언트가 엄청나게 발생하면 문제가 없나?<ul>
<li>연결로는 문제 없음</li>
<li>단, 받은 메세지에 대한 로직 처리의 부담은 존재</li>
<li>실무에서는 연결을 잘게 쪼개 각 서버에 분산</li>
</ul>
</li>
</ul>
<p><br/><br/><br/><br/></p>
<h4 id="📌-출처">📌 출처</h4>
<p>수코딩(<a href="https://www.sucoding.kr">https://www.sucoding.kr</a>)</p>
]]></description>
        </item>
    </channel>
</rss>