<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>YELOPER</title>
    <link>https://01exsilver.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 02:11:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>예글</managingEditor>
    <image>
      <title>YELOPER</title>
      <url>https://tistory1.daumcdn.net/tistory/5424528/attach/185d081cf2cb47c389048159f7804cc8</url>
      <link>https://01exsilver.tistory.com</link>
    </image>
    <item>
      <title>[VUE3] defineEmits vs defineExpose</title>
      <link>https://01exsilver.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 일하다... datepicker와 input radio 선택 후 모달 취소버튼을 누르면 초기화작업을 해야하는데 아무리 emit으로 보내도 반응이 없었는데... 다행히 천사같은 선임님이 알려주셔서 해결할 수 있었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;defineExpose를 이때 알게 되었는데 둘의 차이를 확실히 해두면 좋을 것 같아서 작성한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ defineExpose&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 컴포넌트 내에서 부모 컴포넌트에게 &lt;b&gt;노출할 변수나 함수를 정의&lt;/b&gt;하는 데 사용.&lt;/li&gt;
&lt;li&gt;자식 컴포넌트에서 expose를 사용하여 노출된 속성들을 정의.&lt;/li&gt;
&lt;li&gt;부모 컴포넌트에서 자식 컴포넌트의 인스턴스에 접근하여 해당 노출된 속성을 사용할 수 있음&lt;/li&gt;
&lt;li&gt;자식에서 부모로 데이터를 노출시킬 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1714823293730&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ChildComponent.vue
import { defineComponent, defineExpose } from 'vue';

export default defineComponent({
  setup() {
    const message = 'Hello from child';
    
    // Expose message to parent
    defineExpose({
      message,
    });

    return {};
  }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1714823310163&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- ParentComponent.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;ChildComponent ref=&quot;childRef&quot; /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import { ref } from 'vue';

export default {
  setup() {
    const childRef = ref(null);

    // Accessing the exposed message
    console.log(childRef.value.message);

    return { childRef };
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ defineEmits&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 컴포넌트 내에서 이벤트를 정의하여 부모 컴포넌트로 보낼 때 사용.&lt;/li&gt;
&lt;li&gt;이벤트는 부모 컴포넌트에서 리스닝하여 자식 컴포넌트의 동작을 조작하거나 상태를 변경하는 데 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자식에서 부모로 이벤트를 발생시킬 때 사용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/78</guid>
      <comments>https://01exsilver.tistory.com/78#entry78comment</comments>
      <pubDate>Sat, 4 May 2024 22:55:39 +0900</pubDate>
    </item>
    <item>
      <title>[VUE] npm ERR! gyp verb check python checking for Python executable &amp;quot;python2&amp;quot; in the PATHnpm ERR! gyp verb `which` failed Error: not found: python2</title>
      <link>https://01exsilver.tistory.com/75</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 사이드 프로젝트로 하던 거를 집에서도 작업하려고 깃 클론을 하고 npm install을 했는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1시간이나 여기서 잡아먹혔다 ㅠㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결했던 방법은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 설치가 아닌..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713793340992&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1- npm uninstall node-sass

2- npm i -D sass  ---&amp;gt; 에러나서

3- npm install -D sass-loader@^10 sass ---&amp;gt; 이렇게 했더니 성공

3- npm i&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이었다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-22 오후 10.42.42.png&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YDfuO/btsGNNfoHI2/MKQ0EWO2XF4VmW1yOBe5bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YDfuO/btsGNNfoHI2/MKQ0EWO2XF4VmW1yOBe5bk/img.png&quot; data-alt=&quot;이 분의 방법을 따라하였다.. 드디어 프로젝트 켜졌다.. 하...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YDfuO/btsGNNfoHI2/MKQ0EWO2XF4VmW1yOBe5bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYDfuO%2FbtsGNNfoHI2%2FMKQ0EWO2XF4VmW1yOBe5bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1884&quot; height=&quot;626&quot; data-filename=&quot;스크린샷 2024-04-22 오후 10.42.42.png&quot; data-origin-width=&quot;1884&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 분의 방법을 따라하였다.. 드디어 프로젝트 켜졌다.. 하...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/75</guid>
      <comments>https://01exsilver.tistory.com/75#entry75comment</comments>
      <pubDate>Mon, 22 Apr 2024 22:43:18 +0900</pubDate>
    </item>
    <item>
      <title>[VUE] VUEX 알아보기</title>
      <link>https://01exsilver.tistory.com/74</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  VUEX란?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무수히 많은 컴포넌트의 데이터를 관리하기 위한 상태 관리 패턴이자 라이브러리&lt;/li&gt;
&lt;li&gt;React의 Flux 패턴에서 기인함&lt;/li&gt;
&lt;li&gt;Vue.js 중고급 개발자로 성장하기 위한 필수 관문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Flux란?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVC 패턴의 복잡한 데이터 흐름 문제를 해결하는 개발 패턴 - Unidirectional data flow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6efvO/btsGMLIs6yc/ouZH0wSXUdTciTUfTklkWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6efvO/btsGMLIs6yc/ouZH0wSXUdTciTUfTklkWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6efvO/btsGMLIs6yc/ouZH0wSXUdTciTUfTklkWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6efvO%2FbtsGMLIs6yc%2FouZH0wSXUdTciTUfTklkWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;245&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;action: 화면에서 발생하는 이벤트 또는 사용자의 입력&lt;/li&gt;
&lt;li&gt;dispatcher: 데이터를 변경하는 방법, 메서드&lt;/li&gt;
&lt;li&gt;model: 화면에 표시할 데이터&lt;/li&gt;
&lt;li&gt;view: 사용자에게 비춰지는 화면&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터의 흐름이 여러 갈래로 나뉘지 않고 단방향으로만 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duTcdR/btsGM9Cax6Z/8HALFpGKrcNmQOH9TkziX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duTcdR/btsGM9Cax6Z/8HALFpGKrcNmQOH9TkziX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duTcdR/btsGM9Cax6Z/8HALFpGKrcNmQOH9TkziX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduTcdR%2FbtsGM9Cax6Z%2F8HALFpGKrcNmQOH9TkziX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;386&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vuex가 필요한 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 애플리케이션에서 컴포넌트의 개수가 많아지면 컴포넌트 간에 데이터 전달이 어려워짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vuex로 해결할 수 있는 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MVC 패턴에서 발생하는 구조적 오류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 컴포넌트 간 데이터 전달 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 여러 개의 컴포넌트에서 같은 데이터를 업데이트 할 때 동기화 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vuex 컨셉&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State: 컴포넌트 간에 공유하는 데이터 data()&lt;/li&gt;
&lt;li&gt;View: 데이터를 표시하는 화면 template&lt;/li&gt;
&lt;li&gt;Action: 사용자의 입력에 따라 데이터를 변경하는 methods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OpcHL/btsGOHki1y2/1VomFlwcNi9eT7825Fd311/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OpcHL/btsGOHki1y2/1VomFlwcNi9eT7825Fd311/img.png&quot; data-alt=&quot;단방향 데이터 흐름 처리를 단순하게 도식화한 그림&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OpcHL/btsGOHki1y2/1VomFlwcNi9eT7825Fd311/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOpcHL%2FbtsGOHki1y2%2F1VomFlwcNi9eT7825Fd311%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;866&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단방향 데이터 흐름 처리를 단순하게 도식화한 그림&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vuex 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 -&amp;gt; 비동기 로직 -&amp;gt; 동기 로직 -&amp;gt; 상태&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdvTuE/btsGPbetRMw/k03Fl5wQCONYMSBmOFgtm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdvTuE/btsGPbetRMw/k03Fl5wQCONYMSBmOFgtm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdvTuE/btsGPbetRMw/k03Fl5wQCONYMSBmOFgtm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdvTuE%2FbtsGPbetRMw%2Fk03Fl5wQCONYMSBmOFgtm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;551&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vuex 시작하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1️⃣ src 폴더 밑에 store 폴더를 만들고 store.js 파일 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1713701399592&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Vue from &quot;vue&quot;;
import Vuex from &quot;vuex&quot;;

Vue.use(Vuex);

export const store = new Vuex.Store({});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2️⃣ main.js에 불러오기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1713701439922&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Vue from &quot;vue&quot;;
import App from &quot;./App.vue&quot;;
import { store } from &quot;./store/store&quot;;

new Vue({
  el: &quot;#app&quot;,
  store,
  render: (h) =&amp;gt; h(App),
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣&amp;nbsp; Vuex 기술 요소&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;state: 여러 컴포넌트에 공유되는 데이터 &lt;i&gt;data()&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;getters: 연산된 state 값에 접근하는 속성 &lt;i&gt;computed()&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;mutations: state 값을 변경하는 이벤트 로직, 메서드 &lt;i&gt;methods&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;actions: 비동기 처리 로직을 선언하는 메서드 &lt;i&gt;async methods&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. state란?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컴포넌트 간에 공유할 데이터 - 상태&lt;/p&gt;
&lt;pre id=&quot;code_1713701741094&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Vue
data : {
  message: 'Hello Vue.js!'
}

// Vuex
state : {
  message: 'Hello Vue.js!'
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713701798151&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- Vue --&amp;gt;
&amp;lt;p&amp;gt; {{ message }} &amp;lt;/p&amp;gt;

&amp;lt;!-- Vuex --&amp;gt;
&amp;lt;p&amp;gt; {{ this.$store.state.message }} &amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. getters란?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;state 값을 접근하는 속성이자 computed()처럼 미리 연산된 값을 접근하는 속성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1713701965261&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// store.js

state:{
  num : 10
},
getters: {
  getNumber(state) {
    return state.num;
  },
  doubleNumber(state) {
    return state.num * 2;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1713702045498&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;p&amp;gt; {{ this.$store.getters.getNumber }} &amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt; {{ this.$store.getters.doubleNumber }} &amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/74</guid>
      <comments>https://01exsilver.tistory.com/74#entry74comment</comments>
      <pubDate>Sun, 21 Apr 2024 21:27:10 +0900</pubDate>
    </item>
    <item>
      <title>[VUE] 카테고리 필터링 기능 / vue transition 적용하기</title>
      <link>https://01exsilver.tistory.com/73</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  vue Transition&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Transition&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 엘리먼트나 컴포넌트가 돔에 들어갈 때와 나갈 때 애니메이션을 적용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt; 내장된 컴포넌트. 그래서 등록할 필요없이 모든 컴포넌트의 템플릿에서 사용할 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;v-if 를 통한 조건적인 렌더링&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;v-show 를 통한 조건적인 디스플레이&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #666666;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;스페셜 엘리먼트인 &amp;lt;component&amp;gt;를 통해 동적인 컴포넌트 토글링&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. TransitionGroup&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;list와 같이 여러 개의 태그가 들어가는 경우를 고려한 Transition 컴포넌트&lt;/li&gt;
&lt;li&gt;감싸는 태그가 없이 v-for 등을 이용해 여러 개 태그가 들어가야함. 대신&amp;nbsp;tag&amp;nbsp;라는 속성을 통해 감쌀(wrap) 태그를 지정할 수 있음&lt;/li&gt;
&lt;li&gt;리스트의 태그들은 각자 나타나거나 사라지고 순서만 바뀌기 때문에, 서로 영향을 주지 않음. 따라서&amp;nbsp;transition mode(out-in, in-out)가 없음.&lt;/li&gt;
&lt;li&gt;각 태그들은 반드시 고유한&amp;nbsp;key&amp;nbsp;속성 값을 가지고 있어야 함.&lt;/li&gt;
&lt;li&gt;v-xxx와 같은 클래스는 그걸 감싸는 컨테이너(container)가 아닌 태그 각각에 들어감.&lt;/li&gt;
&lt;li&gt;만약 컴포넌트의 template 속성을 사용한다면 &amp;lt;TransitionGroup&amp;gt;이 아닌 &amp;lt;transition-group&amp;gt;으로 사용해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. HomeView.vue&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1713509899117&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;home&quot; ref=&quot;parent&quot;&amp;gt;
   ...

    &amp;lt;!-- 카테고리 --&amp;gt;
    &amp;lt;Category :categoryType=&quot;categoryType&quot; @setCategory=&quot;setCategory&quot; /&amp;gt;

    &amp;lt;!-- 상품 리스트 --&amp;gt;
    &amp;lt;MainList :categoryType=&quot;categoryType&quot; /&amp;gt;

  ...
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  components: {
    ...
  },
  data() {
    return {
      categoryType: &quot;all&quot;,
    };
  },

  methods: {
    setCategory(value) {
      this.categoryType = value;
    },
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리와 상품 리스트 컴포넌트에 데이터, 함수를 전달해야 하므로 그들의 상위 컴포넌트인 HomeView.vue에 변수, 함수 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Category.vue&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1713510016222&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;!-- isScrollDown이 true일 때 categoryTop 클래스 적용 --&amp;gt;
  &amp;lt;nav class=&quot;categoryWrap&quot; :class=&quot;{ categoryTop: isScrollDown }&quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'all')&quot;&amp;gt;전체&amp;lt;/li&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'outwear')&quot;&amp;gt;아우터&amp;lt;/li&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'top')&quot;&amp;gt;상의&amp;lt;/li&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'footwear')&quot;&amp;gt;신발&amp;lt;/li&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'bags')&quot;&amp;gt;가방&amp;lt;/li&amp;gt;
      &amp;lt;li @click=&quot;$emit('setCategory', 'stuff')&quot;&amp;gt;잡화&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/nav&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &quot;Category&quot;,
  props: [&quot;categoryType&quot;],
  data() {
    return {
      nowScrollY: 0,
      isScrollDown: false,
      navTop: 0,
    };
  },

  methods: {
   ...
  },

  ...
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;
  ...
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;해당 li 클릭하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;$emit('setCategory')에 그 카테고리에 맞는 value 부여&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. MainList.vue&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1713510060411&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h2&amp;gt;상품 목록&amp;lt;/h2&amp;gt;
    &amp;lt;List :data=&quot;product&quot; :categoryType=&quot;categoryType&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  components: { List },
  props: [&quot;categoryType&quot;],
  data() {
    return {
      product,
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;
  ...
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. List.vue&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1713510093666&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;transition-group tag=&quot;ul&quot; class=&quot;listWrap&quot;&amp;gt;
    &amp;lt;template v-for=&quot;item in data&quot;&amp;gt;
      &amp;lt;div
        v-if=&quot;categoryType === 'all' || item.category === categoryType&quot;
        :key=&quot;item.id&quot;
      &amp;gt;
        &amp;lt;MainItem :item=&quot;item&quot; /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/template&amp;gt;
  &amp;lt;/transition-group&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import MainItem from &quot;@/components/Molecules/MainItem.vue&quot;;
export default {
  props: [&quot;data&quot;, &quot;categoryType&quot;],
  components: { MainItem },
  data() {
    return {};
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;
.listWrap {
  padding: 1.5rem;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}

/* 트랜지션 전용 스타일 */
.v-enter-active,
.v-leave-active,
.v-move {
  transition: opacity 1s, transform 1s;
}
.v-leave-active {
  position: absolute;
}
.v-enter {
  opacity: 0;
  transform: translateY(-20px);
}
.v-leave-to {
  opacity: 0;
  transform: translateY(20px);
}

...
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 카테고리에 맞는 데이터만 보여줌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 여러 개의 태그가 들어가는 경우이므로 transition-group 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) &amp;lt;template&amp;gt; cannot be keyed. place the key on real elements instead. 라는 에러가 떠러 template 하위 태그인 div에 :key=&quot;item.id&quot; 적음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/73</guid>
      <comments>https://01exsilver.tistory.com/73#entry73comment</comments>
      <pubDate>Fri, 19 Apr 2024 16:46:46 +0900</pubDate>
    </item>
    <item>
      <title>[VUE] 2. Vue 핵심문법 알아보기 / typescript 모듈 또는 해당 형식 선언을 찾을 수 없습니다. 오류 해결</title>
      <link>https://01exsilver.tistory.com/72</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ UI를 컨트롤하는 디렉티브 - UserInterface&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 선언적 렌더링&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vue.js 데이터바인딩의 가장 기본&lt;/li&gt;
&lt;li&gt;데이터 바인딩의 가장 기본적인 형태: 이중 중괄호 {{ }} 문법을 사용한 텍스트 보간법&lt;/li&gt;
&lt;li&gt;이중 중괄호는 데이터를 HTML이 아닌 일반 텍스트로 해석. 실제 HTML을 출력하려면 v-html 디렉티브를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Class와 Style 바인딩&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) HTML Class 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML 클래스 바인딩을 하기 위해선 v-bind: class (축약형 - :class)를 사용&lt;/li&gt;
&lt;li&gt;객체로 바인딩 되며 클래스를 동적으로 토글하기 위해 객체를 :class에 전달할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712642801311&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :class=&quot;{ active: isActive }&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isActive: state 영역 즉, data 영역에 선언한 임의의 변수이며 이 변수는 true와 flase 값을 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) HTML Style 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v-bind 디렉티브를 활용하여 클래스 바인딩과 동일하고 객체로 바인딩함&lt;/li&gt;
&lt;li&gt;:style은 HTML Element의 style 속성에 해당하는 자바스크립트 객체에 대해 바인딩을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712643070403&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div :style=&quot;{ color: activeColor, fontSize: fontSize + 'px' }&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 프로퍼티 속성은 카멜케이스로 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712645177571&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;{{ rawHtml }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ rawHtml2 }}&amp;lt;/div&amp;gt;
  &amp;lt;!-- html처럼 사용하기 위해서 --&amp;gt;
  &amp;lt;h1 v-html=&quot;rawHtml2&quot;&amp;gt;&amp;lt;/h1&amp;gt;

  &amp;lt;h2 v-bind:class=&quot;{ active: isActive }&quot;&amp;gt;클래스 바인딩 테스트입니다.&amp;lt;/h2&amp;gt;
  &amp;lt;!-- 축약형 --&amp;gt;
  &amp;lt;h2 :class=&quot;{ active: isActive }&quot;&amp;gt;클래스 바인딩 테스트입니다.&amp;lt;/h2&amp;gt;
  &amp;lt;button @click=&quot;change&quot;&amp;gt;버튼&amp;lt;/button&amp;gt;

  &amp;lt;h3 style=&quot;color: red; font-size: 24px&quot;&amp;gt;스타일 바인딩 테스트입니다.&amp;lt;/h3&amp;gt;
  &amp;lt;h3 :style=&quot;{ color: fontColor, fontSize: fontSize + 'px' }&quot;&amp;gt;
    스타일 바인딩 테스트입니다.
  &amp;lt;/h3&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      rawHtml: &quot;이것은 텍스트입니다.&quot;,
      rawHtml2: '&amp;lt;span style=&quot;color: red&quot;&amp;gt;이것은 빨간색이어야 합니다.&amp;lt;/span&amp;gt;',
      isActive: false,
      fontColor: &quot;#888888&quot;,
      fontSize: 48,
    };
  },
  methods: {
    change() {
      this.isActive = !this.isActive;
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
h2.active {
  color: green;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 조건부 렌더링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) v-if / v-else-if / v-else&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조건부로 블록을 렌더링하는 데 사용&lt;/li&gt;
&lt;li&gt;블록은 디렉티브 표현식이 truthy값을 반환하는 경우에만 렌더링 됨&lt;/li&gt;
&lt;li&gt;전환 비용이 더 높음 -&amp;gt; 실행 중에 조건이 변경되지 않을 경우에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) v-show&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v-if와 사용법이 크게 다르지 않음&lt;/li&gt;
&lt;li&gt;다만, v-show가 있는 엘리먼트는 항상 렌더링 되어 DOM에 남아있다는 것이 차이점&lt;/li&gt;
&lt;li&gt;v-show는 엘리먼트의 display CSS 속성만 전환이 됨&lt;/li&gt;
&lt;li&gt;초기 렌더링 비용이 더 높음 -&amp;gt; 매우 자주 전환해야 하는 경우에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712647182906&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;div v-if=&quot;isVisible&quot; class=&quot;red&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div v-if=&quot;isVisible&quot; class=&quot;blue&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div v-else class=&quot;black&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;div v-if=&quot;count &amp;gt; 1&quot; class=&quot;red&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div v-else class=&quot;blue&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;button @click=&quot;count++&quot;&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;button @click=&quot;count--&quot;&amp;gt;감소&amp;lt;/button&amp;gt;

    // v-show는 false여도 다 렌더링 됨
    &amp;lt;div v-show=&quot;isVisible&quot; class=&quot;red&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div v-show=&quot;!isVisible&quot; class=&quot;blue&quot;&amp;gt;&amp;lt;/div&amp;gt;
    
    // v-if는 ture인 것만 렌더링 됨
    &amp;lt;div v-if=&quot;isVisible&quot; class=&quot;black&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      isVisible: false,
      count: 0,
    };
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 리스트 렌더링&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열 데이터를 기반으로 동일한 구조의 UI를 반복호출하는 기능&lt;/li&gt;
&lt;li&gt;v-for 디렉티브 사용&lt;/li&gt;
&lt;li&gt;배열을 리스트로 렌더링 할 수 있음&lt;/li&gt;
&lt;li&gt;item in items 형식의 특별한 문법이 필요 (item: 배열 내 반복되는 엘리먼트의 별칭 / items: 선언한 배열 데이터)&lt;/li&gt;
&lt;li&gt;객체의 속성을 반복하는 데 사용 가능&lt;/li&gt;
&lt;li&gt;순회순서: 해당 객체를 Object.keys()를 호출한 결과에 기반&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712643677704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;li v-for=&quot;item in sampleArray&quot; :key=&quot;item&quot;&amp;gt;{{ item }}&amp;lt;/li&amp;gt;
    &amp;lt;li v-for=&quot;user in otherArray&quot; :key=&quot;user.id&quot;&amp;gt;
      {{ user.id }} / {{ user.name }}
    &amp;lt;/li&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      sampleArray: [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;],

      otherArray: [
        { id: 0, name: &quot;John&quot; },
        { id: 1, name: &quot;Kim&quot; },
        { id: 2, name: &quot;Lee&quot; },
        { id: 3, name: &quot;Park&quot; },
      ],
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ Data를 컨트롤하는 디렉티브&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 이벤트 핸들링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 인라인 핸들러&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트가 트리거 될 때, 실행되는 인라인 JavaScript 기능&lt;/li&gt;
&lt;li&gt;어떤 기능을 동작하는 코드가 HTML Element 내에 직접 할당되는 것을 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 메서드 핸들러&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 script 부분에 정의된 메서드(함수)를 이벤트 핸들러에 할당해주는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712674354771&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;button v-on:click=&quot;count++&quot;&amp;gt;인라인 핸들러&amp;lt;/button&amp;gt;
    &amp;lt;h1&amp;gt;{{ count }}&amp;lt;/h1&amp;gt;

    &amp;lt;button v-on:click=&quot;changeName&quot;&amp;gt;메서드 핸들러&amp;lt;/button&amp;gt;
    &amp;lt;h1&amp;gt;{{ name }}&amp;lt;/h1&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      count: 0,
      name: &quot;Vue.js&quot;,
    };
  },

  methods: {
    changeName() {
      this.name = &quot;변경된 텍스트 데이터입니다.&quot;;
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Computed&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정의: 함수처럼 코드를 작성하지만, return 시키는 데이터를 사용하기 때문에 데이터 취급을 하는 공통적으로 사용되는 로직 혹은 복잡한 로직을 미리 처리하여 반복된 로직처리를 방지하는 계산된 형태의 데이터를 만드는 속성&lt;/li&gt;
&lt;li&gt;사용 이유: 너무 많은 연산을 스크립트 혹은 템플릿 HTML 안에서 처리하면 코드가 비대해지고, 유지보수가 어렵다는 치명적인 단점이 있음&lt;/li&gt;
&lt;li&gt;연산이 복잡한 형태라면 계산된 데이터 형태로 만드는 computed 속성을 사용해서 해결할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712745783197&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;h1&amp;gt;{{ text }}&amp;lt;/h1&amp;gt;
  &amp;lt;h1&amp;gt;changeText 함수 호출 값: {{ changeText() }}&amp;lt;/h1&amp;gt;
  &amp;lt;h1&amp;gt;changeText 함수 호출 값: {{ changeText() }}&amp;lt;/h1&amp;gt;
  &amp;lt;h1&amp;gt;changeText 함수 호출 값: {{ changeText() }}&amp;lt;/h1&amp;gt;

  &amp;lt;h2&amp;gt;{{ computedText }}&amp;lt;/h2&amp;gt;
  &amp;lt;h2&amp;gt;{{ computedText }}&amp;lt;/h2&amp;gt;
  &amp;lt;h2&amp;gt;{{ computedText }}&amp;lt;/h2&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      text: &quot;Computed 테스트 데이터 문구입니다.&quot;,
    };
  },
  // methods 부분에 선언된 함수와 동일한 로직일 때,
  // 캐싱 기능이 없는 methods는 호출될 때마다 console 값이 출력 됨
  // 반면에, computed는 캐싱 기능이 있기 때문에 methods와 어떤 차이점이 있는지 주의 깊게 살펴보는 것이 포인트
  computed: {
    computedText() {
      console.log(&quot;Computed 기능을 생성하였습니다.&quot;);
      return this.text.split(&quot;&quot;).reverse().join(&quot;&quot;);
    },
  },
  methods: {
    changeText() {
      console.log(&quot;함수 호출&quot;);
      console.log(this.text);

      return this.text.split(&quot;&quot;).reverse().join(&quot;&quot;);
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-10 오후 7.41.56.png&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kI37m/btsGuPkeT11/4qZzdP97g938KxxGodz5sK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kI37m/btsGuPkeT11/4qZzdP97g938KxxGodz5sK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kI37m/btsGuPkeT11/4qZzdP97g938KxxGodz5sK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkI37m%2FbtsGuPkeT11%2F4qZzdP97g938KxxGodz5sK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3020&quot; height=&quot;1450&quot; data-filename=&quot;스크린샷 2024-04-10 오후 7.41.56.png&quot; data-origin-width=&quot;3020&quot; data-origin-height=&quot;1450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;똑같이 3번 호출하였음에도 methods는 3번 다 호출 computed는 한 번만 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Watch&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 데이터를 감시하고 있다가 그 데이터의 값이 변했을 때, 그것을 감지하여 그와 관련된 함수, 로직, 연산 등 다양한 기능을 추가적으로 활용할 수 있도록 도와주는 속성&lt;/li&gt;
&lt;li&gt;데이터 변경에 대한 응답으로 비동기식 또는 시간이 많이 소요되는 조작을 수행하려는 경우에 가장 유용&lt;/li&gt;
&lt;li&gt;예) 게시판 페이지를 변경하거나, 선택한 페이지에 대한 리스트만 불러올 경우, 변경된 페이지번호만 감지하여 해당 데이터만 호출할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712746465179&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;changeMessage&quot;&amp;gt;{{ message }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      message: &quot;안녕하세요. Vue.js Watch 기능 테스트 오리지널 문구&quot;,
    };
  },

  // message라는 변수가 바뀌는 것을 지켜보고 있다가 changeMessage()를 통해 바뀌게 되면 alert창을 띄워줌
  // 데이터 뿐만 아니라 computed로 계산된 형태의 데이터도 watch로 감지할 수 있음
  // 보통 게시판에서 한 컬럼을 선택하였을 때, 고유한 id 값이 바뀜을 감지하고
  // 이때, 그 id 값에 따른 상세 데이터를 호출할 때 주로 사용함
  watch: {
    message() {
      window.alert(&quot;message 변수에 담긴 데이터가 변경되었습니다.&quot;);
    },
    id() {
      // 해당 상세데이터를 조회하는 api 호출
    },
  },

  methods: {
    changeMessage() {
      console.log(&quot;함수 호출&quot;);
      this.message = &quot;변경된 message 데이터입니다.&quot;;
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Props와 Emits&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Props&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Emits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;상위 컴포넌트 -&amp;gt; 하위 컴포넌트&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;하위 컴포넌트 -&amp;gt; 상위 컴포넌트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OptionsAPI: props:{} 사용&lt;br /&gt;CompositionAPI: defineProps 사용&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;OptionsAPI: this.$emits 사용&lt;br /&gt;CompositionAPI: defineEmits 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Prop 받은 데이터의 타입 설정&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;첫 번째 인자: 이벤트 이름&lt;br /&gt;두 번째 인자: 보낼 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;1) Options API - Props&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712748367068&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;ChildComponent
      v-bind:sendProps1=&quot;title&quot;
      v-bind:sendProps2=&quot;createdAt&quot;
      v-bind:sendProps3=&quot;obj&quot;
    /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import ChildComponent from &quot;./components/ChildComponent.vue&quot;;

export default {
  components: {
    ChildComponent,
  },
  data() {
    return {
      title: &quot;부모 컴포넌트에서 선언된 데이터입니다.&quot;,
      createdAt: 2024,
      obj: {
        id: 2024,
        name: &quot;John&quot;,
      },
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- ChildComponent.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712749284344&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;{{ sendProps1 }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ sendProps2 }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ sendProps3.id }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ sendProps3.name }}&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  props: {
    sendProps1: String,
    sendProps2: Number,
    sendProps3: Object,
  },
  data() {
    return {};
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;2) CompositionAPI - Props&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712749025367&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;ChildComponent
      :sendProps1=&quot;title&quot;
      :sendProps2=&quot;createdAt&quot;
      :sendProps3=&quot;obj&quot;
    /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { reactive, ref } from &quot;vue&quot;;
import ChildComponent from &quot;./components/ChildComponent.vue&quot;;

interface Obj {
  id: number;
  name: string;
}

const title = ref&amp;lt;string&amp;gt;(&quot;부모컴포넌트에서 선언된 데이터입니다.&quot;);
const createdAt = ref&amp;lt;number&amp;gt;(2024);
const obj = reactive&amp;lt;Obj&amp;gt;({
  id: 2024,
  name: &quot;John&quot;,
});
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;compositionAPI 에서는 setup을 꼭 추가해줘야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;'./components/ChildComponent.vue'&amp;nbsp;모듈&amp;nbsp;또는&amp;nbsp;해당&amp;nbsp;형식&amp;nbsp;선언을&amp;nbsp;찾을&amp;nbsp;수&amp;nbsp;없습니다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;갑자기 이런 오류가 나타나서 구글링 해봤더니 개발도구인 VSCode에서 개발에 제공되는 내부 타입스크립트 플러그인과 뷰와 관련된 타입스크립트의 중첩으로 발생하는 문제였다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lts0606.tistory.com/663&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lts0606.tistory.com/663&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712749138487&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Vuejs] 타입스크립트(Typescript) 적용, 모듈 또는 해당 형식 선언을 찾을 수 없습니다.&quot; data-og-description=&quot;* 개발도구 : vs코드(VSCode) * 운영체제 : 윈도우 뷰에서 타입스크립트(typescript)를 적용해 보았는데 아무 행위도 안했는데 만났던 오류 입니다. 난 그저 단지 설치하고 소스코드를 눌러서 보려고 했&quot; data-og-host=&quot;lts0606.tistory.com&quot; data-og-source-url=&quot;https://lts0606.tistory.com/663&quot; data-og-url=&quot;https://lts0606.tistory.com/663&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b8UjUb/hyVMLsqN5n/gaI66LDJGdnF2CSvD1gpVk/img.png?width=703&amp;amp;height=530&amp;amp;face=0_0_703_530,https://scrap.kakaocdn.net/dn/gxeUX/hyVMLFYlqf/O53kXPmoaZzIKiMEPkBob0/img.png?width=703&amp;amp;height=530&amp;amp;face=0_0_703_530,https://scrap.kakaocdn.net/dn/ccSXd2/hyVMX0HdFw/JWuWZwMfIl2KnZfyZfydMk/img.jpg?width=668&amp;amp;height=669&amp;amp;face=295_99_455_259&quot;&gt;&lt;a href=&quot;https://lts0606.tistory.com/663&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://lts0606.tistory.com/663&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b8UjUb/hyVMLsqN5n/gaI66LDJGdnF2CSvD1gpVk/img.png?width=703&amp;amp;height=530&amp;amp;face=0_0_703_530,https://scrap.kakaocdn.net/dn/gxeUX/hyVMLFYlqf/O53kXPmoaZzIKiMEPkBob0/img.png?width=703&amp;amp;height=530&amp;amp;face=0_0_703_530,https://scrap.kakaocdn.net/dn/ccSXd2/hyVMX0HdFw/JWuWZwMfIl2KnZfyZfydMk/img.jpg?width=668&amp;amp;height=669&amp;amp;face=295_99_455_259');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Vuejs] 타입스크립트(Typescript) 적용, 모듈 또는 해당 형식 선언을 찾을 수 없습니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;* 개발도구 : vs코드(VSCode) * 운영체제 : 윈도우 뷰에서 타입스크립트(typescript)를 적용해 보았는데 아무 행위도 안했는데 만났던 오류 입니다. 난 그저 단지 설치하고 소스코드를 눌러서 보려고 했&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;lts0606.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 블로그에서 하신 대로 했더니 바로 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- ChildComponent.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712749476178&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;{{ props.sendProps1 }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ props.sendProps2 }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ props.sendProps3.id }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ props.sendProps3.name }}&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
interface Obj {
  id: number;
  name: string;
}
interface Props {
  sendProps1: String;
  sendProps2: Number;
  sendProps3: Obj;
}

// import 할 필요 없음
const props = defineProps&amp;lt;Props&amp;gt;();
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;lt;CompositionAPI에서 타입스크립트를 활용하는 방법&amp;gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;① Prop 받은 데이터 형식에 맞게 타입을 지정해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;②&amp;nbsp;Prop 받은 데이터 이름 자체에 미리 선언해둔 타입을 설정해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1712749896044&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Obj {
  id: number;
  name: string;
}

interface Props {
  sendProps1: String;
  sendProps2: Number;
  sendProps3: Obj;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;③ &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;defineProps, toRefs 내장함수를 통해 Prop 받은 데이터를 활용하여 템플릿 부분에 호출하여 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;defineProps는 import 할 필요 X&lt;/li&gt;
&lt;li&gt;toRefs는 import 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712749957818&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const props = defineProps&amp;lt;Props&amp;gt;();
const { sendProps1, sendProps2, sendProps3 } = toRef(props);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;3) CompositionAPI - Emits&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- ChildComponent.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712750989200&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;sendEvent&quot;&amp;gt;자식컴포넌트에서 만든 버튼&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { ref } from &quot;vue&quot;;

const data = ref&amp;lt;string&amp;gt;(&quot;자식 컴포넌트에서 선언된 데이터입니다.&quot;);
const emit = defineEmits([&quot;send-event&quot;]);
const sendEvent = () =&amp;gt; {
  emit(&quot;send-event&quot;, data.value);
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- App.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712750979716&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    부모컴포넌트 레이아웃
    &amp;lt;ChildComponent @send-event=&quot;parentEvent&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import ChildComponent from &quot;./components/ChildComponent.vue&quot;;

const parentEvent = (event: string) =&amp;gt; {
  console.log(event);
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;4) OptionsAPI - Emits&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- ChildComponent.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712751355374&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button @click=&quot;sendEvent&quot;&amp;gt;자식컴포넌트에서 만든 버튼&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      text: &quot;자식 컴포넌트에서 선언된 데이터입니다.&quot;,
    };
  },
  methods: {
    sendEvent() {
      this.$emit(&quot;send-event&quot;, this.text);
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- App.vue&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712751372566&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    부모컴포넌트 레이아웃
    &amp;lt;ChildComponent @send-event=&quot;parentEvent&quot; /&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import ChildComponent from &quot;./components/ChildComponent.vue&quot;;

export default {
  components: {
    ChildComponent,
  },
  methods: {
    parentEvent(event) {
      console.log(event);
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. v-model&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 양방향 데이터 바인딩&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'Props와 Emits의 기능이 동시에 진행된다.'&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) input의 기능에 따라&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 v-model은 input 태그의 inputValue 값과 연관지어 많이 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1712751853977&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;!-- 영어로 입력받을 때 --&amp;gt;
    &amp;lt;input type=&quot;text&quot; v-model=&quot;inputValue1&quot; /&amp;gt;

    &amp;lt;!-- 한글을 입력받을 땐 이 방법이 권장됨 --&amp;gt;
    &amp;lt;input
      type=&quot;text&quot;
      :value=&quot;inputValue2&quot;
      @input=&quot;inputValue2 = $event.target.value&quot;
    /&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ inputValue1 }}&amp;lt;/div&amp;gt;
  &amp;lt;div&amp;gt;{{ inputValue2 }}&amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      inputValue1: &quot;&quot;,
      inputValue2: &quot;&quot;,
    };
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/72</guid>
      <comments>https://01exsilver.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 9 Apr 2024 16:38:49 +0900</pubDate>
    </item>
    <item>
      <title>[VUE] 1. vite로 Vue 시작하기 / npm cache 오류 해결 / 라이프사이클 훅 학습하기</title>
      <link>https://01exsilver.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이직하게 된 회사에서 React나 Next가 아닌 VUE를 사용하게 되어서 출근하기 전까지 조금이라도 공부하기 위해 유튜브에 올라온 강의를 보며 학습을 시작하였다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=9lWaIhE05m8&amp;amp;t=430s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=9lWaIhE05m8&amp;amp;t=430s&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=9lWaIhE05m8&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/C06CK/hyVMUvpP6I/TBe2zTKDZWfk9v3LN33ZP0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/9lWaIhE05m8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구디사는 개발자 9Din님께서 유튜브에 올려주신 강의로 학습하였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ VUE 시작하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링해본 결과 VUE는 Vue-CLI나 Vite 둘 중 하나로 시작을 하는 것 같았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트로 예를 들면 CRA vs Vite 이런 느낌일라나..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  Vue-CLI vs Vite&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-id=&quot;a92edc99f7b049adbb6c69d2ba303719&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;1. Vue-CLI의 장단점&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다양한 기능을 제공하며 Vue.js 애플리케이션의 빌드, 테스트 및 배포를 위한 다양한 구성을 제공합니다.&lt;/li&gt;
&lt;li&gt;프로젝트의 개발을 위한 설정을 쉽게 커스텀 할 수 있는 설정 파일을 제공합니다.&lt;/li&gt;
&lt;li&gt;대규모 운영 애플리케이션에 적합합니다.&lt;/li&gt;
&lt;li&gt;공식 Vue.js 팀에서 유지 보수하므로 업데이트와 일관된 개발 경험을 보장합니다.&lt;/li&gt;
&lt;li&gt;Vue2, Vue3 모두 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vue-CLI는 간단한 프로젝트를 구성할 때 설정이 복잡하다 느낄 수 있습니다.&lt;/li&gt;
&lt;li&gt;프로젝트의 규모가 커질수록 빌드나 서버 실행의 속도가 Vite보다 느려집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #37352f; text-align: start;&quot; data-id=&quot;edeec0bbb0324d719f8aa900c1837244&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;2. Vite의 장단점&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ESM(ES Module)을 기반으로 한 빠른 개발 서버입니다.&lt;/li&gt;
&lt;li&gt;설정이 간소화 되어있어 직관적인 구성 설정을 제공합니다.&lt;/li&gt;
&lt;li&gt;Vue3에 최적화되어 있으며, Vue2가 사용 종료될 시점이 다가오므로 상당한 이점으로 작용합니다.&lt;/li&gt;
&lt;li&gt;속도와 간결성 때문에 중소 규모 프로젝트를 빌드하는 데 탁월합니다.&lt;/li&gt;
&lt;li&gt;Vue 뿐만 아니라 Vanilla, React, Preact, Lit, Svelte, Solid, Qwik 등의 다른 프레임워크와도 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #37352f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vue-CLI만큼 많은 최적화 및 프로덕션 기능을 제공하지 않을 수 있습니다.&lt;/li&gt;
&lt;li&gt;Vue-CLI에 비해 플러그인이나 커뮤니티 등이 부족할 수 있습니다.&lt;/li&gt;
&lt;li&gt;좀 더 전문화하기 위해서는 Webpack을 직접 이해하고 구성해야 할 수 있어 Vite의 모든 기능을 끌어내지 못할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난 강의에서 vite로 시작하였기에 똑같이 vite로 시작!&lt;/p&gt;
&lt;pre id=&quot;code_1712589973495&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm create vite@latest&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Project name: vue-baic&lt;/li&gt;
&lt;li&gt;Select a framework: Vue&lt;/li&gt;
&lt;li&gt;Select a variant: JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 지정해주고 내 프로젝트 폴더로 들어가서&lt;/p&gt;
&lt;pre id=&quot;code_1712590061150&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주었는데 오류!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Your cache folder contains root - owned files, due to a bug in &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;npm ERR! previous versions of npm which has since been addressed&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치부터 오류가 발생하다니...  &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 구글링을 통해 빠르게 해결해버리기-&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://velog.io/@zzaoman/npm-cache-%EB%AC%B8%EC%A0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@zzaoman/npm-cache-%EB%AC%B8%EC%A0%9C&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712590225835&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;npm cache 문제&quot; data-og-description=&quot;실습을 하는 도중에 평소처럼 git clone해서 npm install을 하는데 에러가 발생했다..!Your cache folder contains root-owned files, due to a bug in previous versions of npm which ha&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@zzaoman/npm-cache-%EB%AC%B8%EC%A0%9C&quot; data-og-url=&quot;https://velog.io/@zzaoman/npm-cache-문제&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmzlYp/hyVJWO9L4x/0TOr1bJXdvpMRdhElCu7r0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/zBYdM/hyVJS62MMe/157LfojCnJ7xCAjs5d3Xn1/img.png?width=1612&amp;amp;height=522&amp;amp;face=0_0_1612_522,https://scrap.kakaocdn.net/dn/c5i0rY/hyVJ5rOSIc/qtPFkXdOl60SH73oc6vQg0/img.png?width=1222&amp;amp;height=398&amp;amp;face=0_0_1222_398&quot;&gt;&lt;a href=&quot;https://velog.io/@zzaoman/npm-cache-%EB%AC%B8%EC%A0%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@zzaoman/npm-cache-%EB%AC%B8%EC%A0%9C&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmzlYp/hyVJWO9L4x/0TOr1bJXdvpMRdhElCu7r0/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500,https://scrap.kakaocdn.net/dn/zBYdM/hyVJS62MMe/157LfojCnJ7xCAjs5d3Xn1/img.png?width=1612&amp;amp;height=522&amp;amp;face=0_0_1612_522,https://scrap.kakaocdn.net/dn/c5i0rY/hyVJ5rOSIc/qtPFkXdOl60SH73oc6vQg0/img.png?width=1222&amp;amp;height=398&amp;amp;face=0_0_1222_398');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;npm cache 문제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;실습을 하는 도중에 평소처럼 git clone해서 npm install을 하는데 에러가 발생했다..!Your cache folder contains root-owned files, due to a bug in previous versions of npm which ha&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 블로그에서 처음에 하신 대로&lt;/p&gt;
&lt;pre id=&quot;code_1712590253436&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo npm cache clean --force&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주었더니 바로 해결되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ VSCode 확장 프로그램 설치하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의에서는 TypeScript Vue Plugin, Vue Language Features(Volar), Vue VSCode Snippets를 설치하셨다고 했는데 2024.04.09 기준 TypeScript Vue Plugin, Vue Language Features(Volar) 이 두 개는 지원하지 않고 Vue-Official로 대체되었다고 떴다. 그래서 나는 Vue-Official과 Vue VSCode Snippets를 설치하였다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-09 오전 12.38.28.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mcUC3/btsGsXhDXlJ/C7lOKzp0XSFL5mpo7DkZ51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mcUC3/btsGsXhDXlJ/C7lOKzp0XSFL5mpo7DkZ51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mcUC3/btsGsXhDXlJ/C7lOKzp0XSFL5mpo7DkZ51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmcUC3%2FbtsGsXhDXlJ%2FC7lOKzp0XSFL5mpo7DkZ51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;278&quot; data-filename=&quot;스크린샷 2024-04-09 오전 12.38.28.png&quot; data-origin-width=&quot;568&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Snippets를 사용하면서 v-b를 치면 이렇게 많은 템플릿을 사용할 수 있어서 아주 편리하다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-04-09 오전 12.39.10.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;660&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JbSVg/btsGu5lhQx6/FYgXLkaaCckI1LfFphzDak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JbSVg/btsGu5lhQx6/FYgXLkaaCckI1LfFphzDak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JbSVg/btsGu5lhQx6/FYgXLkaaCckI1LfFphzDak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJbSVg%2FbtsGu5lhQx6%2FFYgXLkaaCckI1LfFphzDak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;660&quot; data-filename=&quot;스크린샷 2024-04-09 오전 12.39.10.png&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;660&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의에서는 vbase-3을 사용하였다&lt;/p&gt;
&lt;pre id=&quot;code_1712590805168&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;

  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  setup () {
    

    return {}
  }
}
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;

&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이렇게 뙇!!! 아주 편리하다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ 라이프사이클 훅 학습하기&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-09-14-43-27.jpeg&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;1307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxyku9/btsGt66X8Cn/3kS1nqQCzCr2RuPUK5m5H0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxyku9/btsGt66X8Cn/3kS1nqQCzCr2RuPUK5m5H0/img.jpg&quot; data-alt=&quot;출처: 구디사는 개발자 9Diin님 블로그&amp;amp;amp;nbsp;https://blog.naver.com/PostView.naver?blogId=weartstudio&amp;amp;amp;logNo=223242014244&amp;amp;amp;categoryNo=13&amp;amp;amp;parentCategoryNo=&amp;amp;amp;from=thumbnailList&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxyku9/btsGt66X8Cn/3kS1nqQCzCr2RuPUK5m5H0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxyku9%2FbtsGt66X8Cn%2F3kS1nqQCzCr2RuPUK5m5H0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;1307&quot; data-filename=&quot;KakaoTalk_Photo_2024-04-09-14-43-27.jpeg&quot; data-origin-width=&quot;924&quot; data-origin-height=&quot;1307&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 구디사는 개발자 9Diin님 블로그&amp;amp;nbsp;https://blog.naver.com/PostView.naver?blogId=weartstudio&amp;amp;logNo=223242014244&amp;amp;categoryNo=13&amp;amp;parentCategoryNo=&amp;amp;from=thumbnailList&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. beforeCreate(), created()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712592219353&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    {{ count }}
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      count: 0,
    };
  },
  beforeCreate() {
    console.log(&quot;lifeCycle is beforeCreate&quot;, this.count); // undefined
    this.test(); // 에러 남
  },
  created() {
    console.log(&quot;lifeCycle is created&quot;, this.count);
    this.test();
  },
  
  methods: {
    test() {
      console.log(&quot;함수 호출!!&quot;);
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;beforeCreate()에서는 컴포넌트를 생성하기 전이므로 data, method에 접근할 수 없다&lt;/li&gt;
&lt;li&gt;따라서, count는 undefined이 뜨고 test를 불러오면 에러가 난다&lt;/li&gt;
&lt;li&gt;created()에서는 컴포넌트 생성 후 this 키워드를 사용해 data, method에 접근 가능하다&lt;/li&gt;
&lt;li&gt;따라서, count는 0이 뜨고 test를 불러오면 '함수 호출'이라는 결과물이 나타난다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. beforeMount(), mounted()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712592304703&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  ...
  &amp;lt;h1&amp;gt;Vue.js 라이프사이클 테스트&amp;lt;/h1&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {

 ...

  beforeMount() {
    console.log(&quot;lifeCycle is beforeMount&quot;, document.querySelector(&quot;h1&quot;)); // null
  },
  mounted() {
    console.log(&quot;lifeCycle is mounted&quot;, document.querySelector(&quot;h1&quot;));
  },
  methods: {
    test() {
      console.log(&quot;함수 호출!!&quot;);
    },
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style lang=&quot;scss&quot; scoped&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;beforeMount()에서는 UI가 렌더링되기 전이라 null이 뜸&lt;/li&gt;
&lt;li&gt;mounted()에서는 UI가 렌더링된 직후여서 해당 UI가 뜸&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술면접 준비하면서 그냥 마구 외웠을 때는 이해가 잘 안 갔는데 이렇게 그림과 코드를 같이 보면서 공부하니까 확실히 이해가 더 되는 것 같다!&lt;/p&gt;</description>
      <category>VUE</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/71</guid>
      <comments>https://01exsilver.tistory.com/71#entry71comment</comments>
      <pubDate>Tue, 9 Apr 2024 01:06:07 +0900</pubDate>
    </item>
    <item>
      <title>[NEXT] 8. 1차 개발 끝 / Vercel로 배포하기 / 배포 후 오류 해결</title>
      <link>https://01exsilver.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 1차 개발이 완료되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤식으로 배포를 진행할까 고민하던 찰나.. vercel로 많이 한다길래 처음이니까 ㅎㅎ vercel로 배포를 진행했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Vercel이란?&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js에서 제공하는 배포 플랫폼&lt;/li&gt;
&lt;li&gt;빌드, 배포, 호스팅 제공, 공식문서에서도 Vercel을 통한 Front Project 배포 권장&lt;/li&gt;
&lt;li&gt;Github Repository를 통해 쉽게 배포 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ pakage.json에 script 추가 및 수정&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1711285203352&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;,
    &quot;build&quot;: &quot;next build&quot;,
    &quot;start&quot;: &quot;next start&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 블로그를 보며 이렇게 script를 수정하고 빌드를 진행하였는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #ee2323; text-align: left;&quot;&gt;PrismaClientInitializationError: Prisma has detected that this project was built on Vercel, which caches dependencies. This leads to an outdated Prisma Client because Prisma's auto-generation isn't triggered. To fix this, make sure to run the `prisma generate` command during the build process.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;이러한 오류가 떠서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;다시 검색 진행...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #000000; text-align: left;&quot;&gt;알고보니 prisma와 무슨 연관이 있는 것이었다&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711285145301&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev&quot;,
    &quot;start&quot;: &quot;next start&quot;,
    &quot;lint&quot;: &quot;next lint&quot;,
    &quot;postinstall&quot;: &quot;prisma generate&quot;,
    &quot;build&quot;: &quot;prisma generate &amp;amp;&amp;amp; next build&quot;,
    &quot;vercel-deploy&quot;: &quot;next build &amp;amp;&amp;amp; next export&quot;
  },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트를 이렇게 바꾸니 오류 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ Vercel 회원가입 또는 로그인&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 이 곳에 들어가 github로 계정 연동하기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vercel.com/solutions/nextjs&quot;&gt;https://vercel.com/solutions/nextjs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711285354814&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Next.js on Vercel - Vercel &amp;ndash; Vercel&quot; data-og-description=&quot;Get the fast builds and simple setup that developers love, now integrated into a single, automated workflow.&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.com/solutions/nextjs&quot; data-og-url=&quot;https://vercel.com/solutions/nextjs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3dART/hyVDxnBm8l/6tqGaWOBmaxOfj54zy7Os1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/l40x9/hyVDFsnQyj/KSb2dYHFty2pgKbcSB4lDK/img.png?width=1162&amp;amp;height=938&amp;amp;face=0_0_1162_938,https://scrap.kakaocdn.net/dn/JkhEl/hyVDw93Xhz/2fMAWv85EJ0tDxpSAnZIv1/img.png?width=1162&amp;amp;height=938&amp;amp;face=464_775_513_829&quot;&gt;&lt;a href=&quot;https://vercel.com/solutions/nextjs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.com/solutions/nextjs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3dART/hyVDxnBm8l/6tqGaWOBmaxOfj54zy7Os1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/l40x9/hyVDFsnQyj/KSb2dYHFty2pgKbcSB4lDK/img.png?width=1162&amp;amp;height=938&amp;amp;face=0_0_1162_938,https://scrap.kakaocdn.net/dn/JkhEl/hyVDw93Xhz/2fMAWv85EJ0tDxpSAnZIv1/img.png?width=1162&amp;amp;height=938&amp;amp;face=464_775_513_829');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Next.js on Vercel - Vercel &amp;ndash; Vercel&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Get the fast builds and simple setup that developers love, now integrated into a single, automated workflow.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. repository import 하기&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Settings에서 Build Command 수정하기&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 10.04.12.png&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RDJfV/btsF4ViRxAH/PmBQcwXSKoi9PWGkeFPHkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RDJfV/btsF4ViRxAH/PmBQcwXSKoi9PWGkeFPHkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RDJfV/btsF4ViRxAH/PmBQcwXSKoi9PWGkeFPHkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRDJfV%2FbtsF4ViRxAH%2FPmBQcwXSKoi9PWGkeFPHkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;953&quot; height=&quot;550&quot; data-filename=&quot;스크린샷 2024-03-24 오후 10.04.12.png&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 빌드 진행 후 배포 완료!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-24 오후 10.05.52.png&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwloJk/btsF4VXsInM/ZINDYrnpfHBzP4F5Kd4p51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwloJk/btsF4VXsInM/ZINDYrnpfHBzP4F5Kd4p51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwloJk/btsF4VXsInM/ZINDYrnpfHBzP4F5Kd4p51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwloJk%2FbtsF4VXsInM%2FZINDYrnpfHBzP4F5Kd4p51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1271&quot; height=&quot;561&quot; data-filename=&quot;스크린샷 2024-03-24 오후 10.05.52.png&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포가 잘 진행되면 이런식으로 Ready가 뜬다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ 배포 후 만난 오류들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포가 끝나고 신나는 마음으로 페이지에 들어가서 테스트를 해보았는데... 회원가입부터 시작해서 로그인, 게시판까지 모두 작동을 안 하는 것이었다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;Error querying the database: Error code 14: Unable to open the database file&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-21 오후 10.39.40.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzQsv6/btsF3FUVfoV/EsSNtewUnsDyStucOxUo60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzQsv6/btsF3FUVfoV/EsSNtewUnsDyStucOxUo60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzQsv6/btsF3FUVfoV/EsSNtewUnsDyStucOxUo60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzQsv6%2FbtsF3FUVfoV%2FEsSNtewUnsDyStucOxUo60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;142&quot; data-filename=&quot;스크린샷 2024-03-21 오후 10.39.40.png&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류 로그를 보니까 데이터베이스를 찾을 수 없는 것이 문제였다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 검색을 통해 알아낸 것은&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Vercel은 SQLite와 같은 로컬 파일 시스템을 기반으로 하는 데이터베이스를 직접적으로 지원하지 않습니다. SQLite는 파일 기반의 경량 데이터베이스이며, 여러 인스턴스 간에 데이터를 공유하기 어렵습니다. Vercel과 같은 서버리스 호스팅 플랫폼은 각 인스턴스가 독립적인 환경을 갖기 때문에 SQLite와 같은 데이터베이스는 적합하지 않습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 db로 SQLite를 사용 중이었는데 vercel이 지원하지 않아 연결이 안 되었던 것이었다!!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결방법&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 계속 SQLite를 사용하며 배포를 다른 방식으로 진행하기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. DB를 바꾸기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;--&amp;gt; 현실적으로 다른 배포 방법을 사용하기 보다는 vercel에서 지원하는 데이터베이스를 사용하는 것이 공수가 덜 들어갈 것 같아서 DB를 Postgresql로 변경하였다&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vercel.com/yegris-projects/cock-user/stores&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vercel.com/yegris-projects/cock-user/stores&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711286097794&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dashboard&quot; data-og-description=&quot;&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.com/yegris-projects/cock-user/stores&quot; data-og-url=&quot;https://vercel.com/login?next=%2Fyegris-projects%2Fcock-user%2Fstores&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uiTJr/hyVDIo8cgv/DTKy2MuQjEYnIndzsOZ5d0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/GnZVZ/hyVDGSmYs3/5MrAMMzKXn7bgmgBd4MAy1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900&quot;&gt;&lt;a href=&quot;https://vercel.com/yegris-projects/cock-user/stores&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.com/yegris-projects/cock-user/stores&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uiTJr/hyVDIo8cgv/DTKy2MuQjEYnIndzsOZ5d0/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900,https://scrap.kakaocdn.net/dn/GnZVZ/hyVDGSmYs3/5MrAMMzKXn7bgmgBd4MAy1/img.png?width=1600&amp;amp;height=900&amp;amp;face=0_0_1600_900');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dashboard&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 database를 만들고&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711286208380&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vercel link
vercel env pull .env.development.local&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 터미널에서 연동해주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 그럼 자동으로 .env.local 파일이 만들어주는데 여기서 파일 이름을 .env로 변경해주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- postgres 설치하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1711286286040&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install @vercel/postgres&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- db 정보 변경해주기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711286319589&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 어떤 종류의 DB를 쓰는지, 해당 파일에 대한 url
datasource db {
  provider = &quot;postgresql&quot;
  url = env(&quot;POSTGRES_PRISMA_URL&quot;) // uses connection pooling
  directUrl = env(&quot;POSTGRES_URL_NON_POOLING&quot;) // uses a direct connection
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주니까 오류 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 401 Unauthorized 오류&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며칠을 헤맸던 오류...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번에서 만났던 오류를 해결하니까 회원가입은 잘 되었는데 로그인이 안 되어서... 콘솔을 봤더니 401 오류가 뜨는 것이었다&lt;/p&gt;
&lt;pre id=&quot;code_1711286689522&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST https://cock-user-yegris-projects.vercel.app/api/auth/callback/credentials 401 (Unathorized)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글에 모든 401에러를 검색 해보았으나... 명확한 해답은 없고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;있다 하더라고 나에겐 맞지 않은 솔루션들 뿐이었다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것저것 실행해보다가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 페이지에서 콘솔을 찍어보았는데&lt;/p&gt;
&lt;pre id=&quot;code_1711286777050&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const result = await signIn(&quot;credentials&quot;, {
      username: id,
      password: password,
      redirect: false,
 });

console.log(result);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-23 오후 9.08.06.png&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwanRu/btsF2ya8Snp/X6qYkWqvZt2dZKQ6ExXHdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwanRu/btsF2ya8Snp/X6qYkWqvZt2dZKQ6ExXHdk/img.png&quot; data-alt=&quot;로컬에서는 이렇게 뜨고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwanRu/btsF2ya8Snp/X6qYkWqvZt2dZKQ6ExXHdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwanRu%2FbtsF2ya8Snp%2FX6qYkWqvZt2dZKQ6ExXHdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1550&quot; height=&quot;282&quot; data-filename=&quot;스크린샷 2024-03-23 오후 9.08.06.png&quot; data-origin-width=&quot;1550&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로컬에서는 이렇게 뜨고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-03-23 오후 9.08.31.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FwW4T/btsF4VQHrtI/PHyC0Q3PQByZahKurImX2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FwW4T/btsF4VQHrtI/PHyC0Q3PQByZahKurImX2k/img.png&quot; data-alt=&quot;콘솔에서는 이렇게 떴다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FwW4T/btsF4VQHrtI/PHyC0Q3PQByZahKurImX2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFwW4T%2FbtsF4VQHrtI%2FPHyC0Q3PQByZahKurImX2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;528&quot; height=&quot;129&quot; data-filename=&quot;edited_스크린샷 2024-03-23 오후 9.08.31.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;콘솔에서는 이렇게 떴다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;error : &quot;Cannot read properties of null (reading 'state')&quot; &lt;span style=&quot;color: #000000;&quot;&gt;이런 오류가 뜨면서 url에 아무것도 안 뜨는 것이었다?????&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어라라 그럴리가 없는데 하며 .env 파일을 까보는 순간&lt;/p&gt;
&lt;pre id=&quot;code_1711286898038&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;      async authorize(credentials, req) {
        const res = await fetch(`${process.env.NEXTAUTH_URL}/api/login`, {
          method: &quot;POST&quot;,
          headers: {
            &quot;Content-Type&quot;: &quot;application/json&quot;,
          },
          body: JSON.stringify({
            username: credentials?.username,
            password: credentials?.password,
          }),
        });
        const user = await res.json();
        console.log(user);

        if (user.state === 401) {
          console.log(&quot;error&quot;);
        }

        if (user) {
          return user;
        } else {
          return null;
        }
      },
    }),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 들어가야 할 NEXTAUTH_URL이 없는 것이었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스를 바꾸면서 .env 파일도 바뀌었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 입력해 놓은 NEXTAUTH_URL이 사라져 버린 것!!&lt;/p&gt;
&lt;pre id=&quot;code_1711287013515&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;NEXTAUTH_URL=&quot;https://cock-user-yegris-projects.vercel.app&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env 파일에 이렇게 입력해주고 다시 배포 진행하니까 드디어!!! 드디어!! 모든 것이 잘 돌아갔다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 감격  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 왜 콘솔 안찍어보고 애먼 것만 하고 있었냐고...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 디버깅은 콘솔찍기가 진리인 것 같다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 1차 개발의 모든 것이 끝나고!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차근차근 디테일 잡으면서 반응형도 시작하고... 그래야겠다!&lt;/p&gt;</description>
      <category>NEXT.JS/Cock! 칵테일 프로젝트</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/70</guid>
      <comments>https://01exsilver.tistory.com/70#entry70comment</comments>
      <pubDate>Sun, 24 Mar 2024 22:31:45 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조/알고리즘] 슬라이딩 윈도우 알고리즘</title>
      <link>https://01exsilver.tistory.com/69</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1️⃣ 슬라이딩 윈도우란?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고정 사이즈의 윈도우가 이동하면서 윈도우 내에 있는 데이터를 이용해 문제를 풀이하는 알고리즘&lt;/li&gt;
&lt;li&gt;배열이나 리스트의 요소의 일정 범위 값을 비교할 때 사용하면 매우 유용&lt;/li&gt;
&lt;li&gt;배열과 그 부분 배열을 어떤 조건하에서 계산하는 경우에 주로 사용&lt;/li&gt;
&lt;li&gt;예) 구간 합 구하기, 부분 문자열 구하기 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2️⃣ 구간마다 일일이 합을 구할 경우 vs 슬라이딩 윈도우 알고리즘을 활용할 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 구간마다 일일이 합을 구할 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞에서 구했던 값이 있음에도 불구하고 다음 번에 또 구하고.. 또 구하고.. 시간복잡도가 늘어날 수밖에 없다 -&amp;gt; 효율성이 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 슬라이딩 윈도우 알고리즘을 활용할 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 윈도우에 대해서만 값을 구하고&lt;/li&gt;
&lt;li&gt;한 칸씩 윈도우를 밀 때에는 이전 구간 합을 활용함 -&amp;gt; 효율성 좋음&amp;nbsp;&lt;/li&gt;
&lt;li&gt;윈도우에서 빠진 원소의 값을 빼주고 윈도우에 들어온 원소의 값을 더해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3️⃣ 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-20 오후 9.45.32.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qA6Zy/btsFW93Iyx5/36WrETFczFajo2PWI8pVR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qA6Zy/btsFW93Iyx5/36WrETFczFajo2PWI8pVR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qA6Zy/btsFW93Iyx5/36WrETFczFajo2PWI8pVR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqA6Zy%2FbtsFW93Iyx5%2F36WrETFczFajo2PWI8pVR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;659&quot; data-filename=&quot;스크린샷 2024-03-20 오후 9.45.32.png&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1710938548118&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const solution = (elements) =&amp;gt; {
    const sumSet = new Set();
    
    const len = elements.length;
    for (let i=1; i&amp;lt;=len; i++) { // 연속 부분 수열의 길이
        // 슬라이딩 윈도우 
        let sum = 0;
        for (let j=0; j&amp;lt;len; j++) { // 연속 부분 수열 시작 지점의 인덱스
            if (j === 0) { // 최초 한 번의 창문에 대해서만 직접 합을 구하기
                for (let k=0; k&amp;lt;i; k++) {
                    sum += elements[k];
                }
            }
            else { // 이후 창문들에 대해서는 이전에 구한 합을 활용하기
                sum -= elements[j-1]; // 윈도우에서 빠진 값 빼주고
                sum += elements[(j+i-1) % len]; // 윈도우에 들어온 값 더하기
            }
            sumSet.add(sum);
        }
    }
    
    // 원형 수열의 연속 부분 수열 합으로 만들 수 있는 수의 개수를 return
    return sumSet.size;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참고 블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@dianestar/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-JavaScript-%EA%B5%90%EA%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-48%EC%A3%BC%EC%B0%A8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@dianestar/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-JavaScript-%EA%B5%90%EA%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-48%EC%A3%BC%EC%B0%A8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710938459415&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[프로그래머스] 연속 부분 수열 합의 개수 (JavaScript) | 교공 알고리즘 스터디 48주차&quot; data-og-description=&quot;연속 부분 수열 합의 개수 | 문제 바로가기 ✔ 아래의 캡쳐본은 문제의 일부입니다. 보다 자세한 제한 사항 및 입출력 예시는 위의 링크를 참고해주세요! ⭐ 풀이의 핵심 |   슬라이딩 윈도우 &quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@dianestar/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-JavaScript-%EA%B5%90%EA%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-48%EC%A3%BC%EC%B0%A8&quot; data-og-url=&quot;https://velog.io/@dianestar/프로그래머스-연속-부분-수열-합의-개수-JavaScript-교공-알고리즘-스터디-48주차&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOvY7c/hyVDtxGiEo/tMUkk8MO757wtGtuMkPkq1/img.png?width=530&amp;amp;height=855&amp;amp;face=0_0_530_855,https://scrap.kakaocdn.net/dn/xlS1F/hyVAKnxOJ5/9cKM5mFeWtlcnHtsZbia30/img.png?width=530&amp;amp;height=855&amp;amp;face=0_0_530_855,https://scrap.kakaocdn.net/dn/b1AYfm/hyVDvI1ZRb/gmknyYkfp4aXWfeBaGFmE0/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884&quot;&gt;&lt;a href=&quot;https://velog.io/@dianestar/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-JavaScript-%EA%B5%90%EA%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-48%EC%A3%BC%EC%B0%A8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@dianestar/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-JavaScript-%EA%B5%90%EA%B3%B5-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94-48%EC%A3%BC%EC%B0%A8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOvY7c/hyVDtxGiEo/tMUkk8MO757wtGtuMkPkq1/img.png?width=530&amp;amp;height=855&amp;amp;face=0_0_530_855,https://scrap.kakaocdn.net/dn/xlS1F/hyVAKnxOJ5/9cKM5mFeWtlcnHtsZbia30/img.png?width=530&amp;amp;height=855&amp;amp;face=0_0_530_855,https://scrap.kakaocdn.net/dn/b1AYfm/hyVDvI1ZRb/gmknyYkfp4aXWfeBaGFmE0/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[프로그래머스] 연속 부분 수열 합의 개수 (JavaScript) | 교공 알고리즘 스터디 48주차&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;연속 부분 수열 합의 개수 | 문제 바로가기 ✔ 아래의 캡쳐본은 문제의 일부입니다. 보다 자세한 제한 사항 및 입출력 예시는 위의 링크를 참고해주세요! ⭐ 풀이의 핵심 |   슬라이딩 윈도우&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVASCRIPT/문법</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/69</guid>
      <comments>https://01exsilver.tistory.com/69#entry69comment</comments>
      <pubDate>Wed, 20 Mar 2024 21:47:31 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조/알고리즘] 탐욕법 (greedy) 알고리즘</title>
      <link>https://01exsilver.tistory.com/68</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1️⃣ 그리디 알고리즘이란?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;선택의 순간마다 당장 눈 앞에 보이는 최적의 상황만을 쫓아 최종적인 해답에 도달하는 방법&lt;/li&gt;
&lt;li&gt;그리디 알고리즘을 해결하기 위해서는 최적이라 생각되는 해답을 찾고 이를 통해 최종 문제의 해답에 도달하는 방식으로 접근해야 함&lt;/li&gt;
&lt;li&gt;locally optimal solution -&amp;gt; globally optimal solution&lt;/li&gt;
&lt;li&gt;그러나, 이런 방법은 항상 최적의 결과를 보장하지 않음 (특정한 상황에서만 보장)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2️⃣ 그리디 알고리즘을 사용할 수 있는 두 가지 조건&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;탐욕적 선택 속성(Greedy Choice Property) : 앞의 선택이 이후의 선택에 영향을 주지 않는다&lt;/li&gt;
&lt;li&gt;최적 부분 구조(Optimal Substructure) : 문제에 대한 최종 해결 방법은 부분 문제에 대한 최적 해결 방법으로 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3️⃣ 그리디 알고리즘 예제 문제&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 짐나르기(backpack Problem)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;김코딩과 박해커는 사무실 이사를 위해 짐을 미리 싸 둔 뒤, 짐을 넣을 박스를 사왔다. 박스를 사오고 보니 각 이사짐의 무게는 들쭉날쭉한 반면, 박스는 너무 작아서 한번에 최대 2개의 짐 밖에 넣을 수 없었고 무게 제한도 있었다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;예를 들어, 짐의 무게가 [70kg, 50kg, 80kg, 50kg]이고 박스의 무게 제한이 100kg이라면 2번째 짐과 4번째 짐은 같이 넣을 수 있지만 1번째 짐과 3번째 짐의 무게의 합은 150kg이므로 박스의 무게 제한을 초과하여 같이 넣을 수 없다.&lt;/span&gt;&lt;br /&gt;&lt;b&gt;박스를 최대한 적게 사용하여 모든 짐을 옮기려고 합니다.&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;짐의 무게를 담은 배열 stuff와 박스의 무게 제한 limit가 매개변수로 주어질 때, 모든 짐을 옮기기 위해 필요한 박스 개수의 최소값을 return 하도록 movingStuff 함수를 작성하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1710744146576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function movingStuff(stuff, limit){
	let count = 0; //옮긴 횟수
	let sortedStuff = stuff.sort((a,b) =&amp;gt; a - b);

	while (sortedStuff.length !== 0){
		if(sortedStuff[0] + sortedStuff[sortedStuff.length-1] &amp;lt;= limit){
			count++;
			sortedStuff.shitf(); //shift() 메서드는 배열에서 첫 번째 요소를 제거하고, 제거된 요소를 반환합니다. 이 메서드는 배열의 길이를 변하게 합니다.
			sortedStuff.pop();
		}else{
			count++;
			sortedStuff.pop(); //가장 무거운 걸 나른다.
		}
	}
	return count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 무거운 것과 가장 가벼운 것을 박스에 넣기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그게 안 된다면 가장 무거운 것 부터 넣기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 편의점 알바 (Coin Change Problem)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;편의점에서 아르바이트를 하고 있는 중에, 하필이면 피크 시간대에 손님에게 거스름돈으로 줄 동전이 부족하다는 것을 알게 되었습니다.현재 가지고 있는 동전은 1원, 5원, 10원, 50원, 100원, 500원으로 오름차순으로 정렬되어 있고, 각 동전들은 서로 배수 관계에 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;동전 개수를 최소화하여 거스름돈 K를 만들어야 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;이때, 필요한 동전 개수의 최솟값을 구하는 함수를 작성해 주세요.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710744411175&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function partTimeJob(k){
	let count = 0; 
	const arr = [500,100,50,10,5,1];
	for(let item of arr){
		count = count + Math.floor(k/item); //동전의 갯수
		k = k - item * Math.floor(k/item); //남은 돈 계산
	}
	return count; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 동전의 최솟값을 구하는 문제이므로 가장 큰 동전부터 주기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참고 블로그&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@hhhminme/Javascript%EB%A1%9C-%ED%92%80%EC%96%B4%EB%82%B8-greedy-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@hhhminme/Javascript%EB%A1%9C-%ED%92%80%EC%96%B4%EB%82%B8-greedy-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&lt;/a&gt;&lt;/p&gt;</description>
      <category>JAVASCRIPT/문법</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/68</guid>
      <comments>https://01exsilver.tistory.com/68#entry68comment</comments>
      <pubDate>Mon, 18 Mar 2024 15:52:07 +0900</pubDate>
    </item>
    <item>
      <title>[NEXT] 7. 게시판 만들기, 기본 CRUD</title>
      <link>https://01exsilver.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 마지막 관문인 게시판 만들기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 회사에서도 CRUD 정도는 해봤지만,, 백엔드 분이 만들어 주신 api를 받아서 프론트 쪽 작업만 했었기에,, 항상 api 만드는 과정이 궁금했었다   다음 개인프로젝트에서 무조건 게시판을 만들어봐야겠다라고 결심한지 어언 몇 개월,, 드디어 실행에 옮겼다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1️⃣ 글 작성페이지 만들기 (CREATE)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 퍼블리싱은 다 해놓은 상태니,, 데이터만 붙이이면 다 되니까 빨리 끝나겠지 ㅎㅎ 라고 생각했다면 경기도 오산  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1.&amp;nbsp; schema.prisma 에서 필드 정의하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710594617376&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;model Post {
  id        String     @default(cuid()) @id
  title     String
  content   String?
  published Boolean  @default(false)
  author    User?    @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime?
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 게시글 작성 페이지&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710594705157&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;
import { useSession } from &quot;next-auth/react&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { SyntheticEvent, useEffect, useState } from &quot;react&quot;;

const PostForm = () =&amp;gt; {
  const [title, setTitle] = useState(&quot;&quot;);
  const [content, setContent] = useState(&quot;&quot;);
  const [authorId, setAuthorId] = useState&amp;lt;Number | null&amp;gt;(null);

  const { data: session } = useSession();
  const author = session?.user?.name;
  const router = useRouter();

  useEffect(() =&amp;gt; {
    // 세션 데이터가 로드되면 사용자 식별자를 설정
    if (session?.user?.id) {
      setAuthorId(session.user.id);
    }
  }, [session]);

  const handleTitleChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setTitle(e.target.value);
  };

  const handleContentChange = (e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; {
    setContent(e.target.value);
  };

  const handleSubmit = async (e: SyntheticEvent) =&amp;gt; {
    e.preventDefault();

    // authorId가 null인 경우 처리
    if (authorId === null) {
      console.error(&quot;사용자 식별자가 없습니다. 사용자를 다시 로그인하세요.&quot;);
      return;
    }

    try {
      await fetch(&quot;/api/addpost&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ title, content, authorId }),
      });
      router.refresh();
    } catch (error) {
      console.log(error);
    }

    setTitle(&quot;&quot;);
    setContent(&quot;&quot;);
  };

  return (
    &amp;lt;&amp;gt;
      {/* &amp;lt;h1&amp;gt;{initialData.title ? &quot;글 수정하기&quot; : &quot;글 작성하기&quot;}&amp;lt;/h1&amp;gt; */}

      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;label htmlFor=&quot;title&quot;&amp;gt;글 제목&amp;lt;/label&amp;gt;
            &amp;lt;input
              type=&quot;text&quot;
              id=&quot;title&quot;
              name=&quot;title&quot;
              value={title}
              onChange={handleTitleChange}
              required
            /&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div&amp;gt;
          &amp;lt;label htmlFor=&quot;content&quot;&amp;gt;내용&amp;lt;/label&amp;gt;
          &amp;lt;textarea
            name=&quot;content&quot;
            id=&quot;content&quot;
            value={content}
            onChange={handleContentChange}
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div&amp;gt;
          &amp;lt;button type=&quot;submit&quot;&amp;gt;저장하기&amp;lt;/button&amp;gt;
          &amp;lt;button&amp;gt;취소하기&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default PostForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 작성자 이름을 보내려고 했으나,, db에서 author가 user와 관련되어 있어서 type 지정할 때 계속 오류가 나는 것이었다,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 시간은 헤맨 듯 ㅠㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 그냥 authorId를 받아서 서버로 보내주기,, 생각해보니까 이름은 동명이인이 있을 수 있으니 id를 받아서 보내는 게 더 좋을 것 같다는 생각이 들었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. addPost api 만들기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710594879536&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import prisma from &quot;@/app/lib/prisma&quot;;
import { NextResponse } from &quot;next/server&quot;;

type PostCreateInput = {
  title: string;
  content: string;
  published: boolean;
  authorId: number;
};

export async function POST(request: Request) {
  const res: PostCreateInput = await request.json();

  const { title, content, authorId } = res;

  const result = await prisma.post.create({
    data: {
      title,
      content,
      published: true,
      authorId,
      updatedAt: new Date(),
    },
  });

  return NextResponse.json({ result });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서도 계속 오류가 났었는데... 저 updatedAt 때문이었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 updatedAt이 필수로 지정해져 있어서 optional 하게 바꾸었는데도,,, 그래서 저렇게 new Date()로 지정해놓으니까 드디어 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 맞는 방법인지 모르겠지만,, 차근차근 고쳐나가도록 해야겠다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. postForm 컴포넌트에 붙이기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710838238190&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;
import { useSession } from &quot;next-auth/react&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { SyntheticEvent, useEffect, useState } from &quot;react&quot;;
import * as styles from &quot;./index.css&quot;;

const PostForm = () =&amp;gt; {
  const [title, setTitle] = useState(&quot;&quot;);
  const [content, setContent] = useState(&quot;&quot;);
  const [authorId, setAuthorId] = useState&amp;lt;Number | null&amp;gt;(null);

  // 로그인한 유저 데이터
  const { data: session } = useSession();
  const router = useRouter();

  useEffect(() =&amp;gt; {
    // 세션 데이터가 로드되면 사용자 식별자를 설정
    if (session?.user?.id) {
      setAuthorId(session.user.id);
    }
  }, [session]);

  const handleTitleChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setTitle(e.target.value);
  };

  const handleContentChange = (e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; {
    setContent(e.target.value);
  };

  const handleSubmit = async (e: SyntheticEvent) =&amp;gt; {
    e.preventDefault();

    // authorId가 null인 경우 처리
    if (authorId === null) {
      console.error(&quot;사용자 식별자가 없습니다. 사용자를 다시 로그인하세요.&quot;);
      return;
    }

    try {
      await fetch(&quot;/api/addpost&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ title, content, authorId }),
      });
      alert(&quot;저장하시겠습니까?&quot;);
      router.push(&quot;/board&quot;);
      router.refresh();
    } catch (error) {
      console.log(error);
    }

    setTitle(&quot;&quot;);
    setContent(&quot;&quot;);
  };

  return (
    &amp;lt;div className={styles.formBox}&amp;gt;
      {/* &amp;lt;h1&amp;gt;{initialData.title ? &quot;글 수정하기&quot; : &quot;글 작성하기&quot;}&amp;lt;/h1&amp;gt; */}

      &amp;lt;form onSubmit={handleSubmit} className={styles.form}&amp;gt;
        &amp;lt;div className={styles.inputBox}&amp;gt;
          &amp;lt;label htmlFor=&quot;title&quot; className={styles.label}&amp;gt;
            제목
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            type=&quot;text&quot;
            id=&quot;title&quot;
            name=&quot;title&quot;
            value={title}
            onChange={handleTitleChange}
            required
            className={styles.input}
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.textAreaBox}&amp;gt;
          &amp;lt;label htmlFor=&quot;content&quot; className={styles.label}&amp;gt;
            내용
          &amp;lt;/label&amp;gt;
          &amp;lt;textarea
            name=&quot;content&quot;
            id=&quot;content&quot;
            value={content}
            onChange={handleContentChange}
            required
            className={styles.textArea}
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.buttonBox}&amp;gt;
          &amp;lt;button type=&quot;submit&quot; className={styles.saveBtn}&amp;gt;
            저장하기
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.buttonBox}&amp;gt;
          &amp;lt;p
            onClick={() =&amp;gt; {
              alert(
                &quot;작성 중인 내용은 저장되지 않습니다. 그래도 취소하시겠습니까?&quot;
              );
              router.push(&quot;/board&quot;);
            }}
            className={styles.cancelTxt}
          &amp;gt;
            취소하기
          &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default PostForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-03-19 오후 5.49.36.png&quot; data-origin-width=&quot;1909&quot; data-origin-height=&quot;1001&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/075lN/btsFVVDVrfX/2Vk4FNnhmwTN5M1szeR8y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/075lN/btsFVVDVrfX/2Vk4FNnhmwTN5M1szeR8y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/075lN/btsFVVDVrfX/2Vk4FNnhmwTN5M1szeR8y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F075lN%2FbtsFVVDVrfX%2F2Vk4FNnhmwTN5M1szeR8y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1909&quot; height=&quot;1001&quot; data-filename=&quot;edited_스크린샷 2024-03-19 오후 5.49.36.png&quot; data-origin-width=&quot;1909&quot; data-origin-height=&quot;1001&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.49.44.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/89QOl/btsFVuT18Yx/npoHbpCz7rd2GYxHWvH0w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/89QOl/btsFVuT18Yx/npoHbpCz7rd2GYxHWvH0w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/89QOl/btsFVuT18Yx/npoHbpCz7rd2GYxHWvH0w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F89QOl%2FbtsFVuT18Yx%2FnpoHbpCz7rd2GYxHWvH0w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1512&quot; height=&quot;440&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.49.44.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. DB에서 확인&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.52.47.png&quot; data-origin-width=&quot;2890&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6rutS/btsFTgbo4wE/Ip4vqT9BBBktzKupqTGoV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6rutS/btsFTgbo4wE/Ip4vqT9BBBktzKupqTGoV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6rutS/btsFTgbo4wE/Ip4vqT9BBBktzKupqTGoV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6rutS%2FbtsFTgbo4wE%2FIp4vqT9BBBktzKupqTGoV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2890&quot; height=&quot;426&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.52.47.png&quot; data-origin-width=&quot;2890&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 잘 들어오는 것이 확인된다,,&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2️⃣ 게시글 데이터 불러오기 (READE)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 전체 게시글 리스트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710595521182&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 게시글 가져오는 함수
async function getPosts() {
  const posts = await prisma.post.findMany({
    where: { published: true },
    include: {
      author: {
        select: { name: true }, // 작성자의 이름만을 선택
      },
    },
  });

  // 각 게시물의 작성자 이름만을 추출하여 반환
  return posts.map((post) =&amp;gt; ({
    ...post,
    authorName: post.author?.name || &quot;Unknown&quot;, // 작성자 이름 또는 'Unknown'으로 설정
  }));
}

export default async function BoardListPage() {
  const posts = await getPosts();

  return (
    &amp;lt;div className={styles.root}&amp;gt;
      {/* &amp;lt;div className={styles.hotPost}&amp;gt;
        &amp;lt;h2&amp;gt;인기 게시글&amp;lt;/h2&amp;gt;
        &amp;lt;div className={styles.hotPostItemBox}&amp;gt;
          &amp;lt;BoardItem id={dummy.id} /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt; */}

      &amp;lt;div className={styles.newPost}&amp;gt;
        &amp;lt;div className={styles.topBox}&amp;gt;
          &amp;lt;h2&amp;gt;최근 게시글&amp;lt;/h2&amp;gt;
          &amp;lt;Link href=&quot;/board/write&quot; className={styles.goWrite}&amp;gt;
            게시글 작성 &amp;amp;gt;
          &amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.newPostTable1}&amp;gt;
          &amp;lt;span className={`${styles.th} ${styles.th1}`}&amp;gt;No&amp;lt;/span&amp;gt;
          &amp;lt;span className={`${styles.th} ${styles.th2}`}&amp;gt;제목&amp;lt;/span&amp;gt;
          &amp;lt;span className={`${styles.th} ${styles.th3}`}&amp;gt;작성자&amp;lt;/span&amp;gt;
          &amp;lt;span className={`${styles.th} ${styles.th4}`}&amp;gt;날짜&amp;lt;/span&amp;gt;
          &amp;lt;span className={`${styles.th} ${styles.th5}`}&amp;gt;추천수&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.newPostTable2}&amp;gt;
          {posts.map((post, index) =&amp;gt; {
            return (
              &amp;lt;BoardNewItem
                num={index + 1}
                key={post.id}
                id={post.id}
                title={post.title}
                content={post.content}
                authorName={post.authorName}
                createdAt={post.createdAt}
              /&amp;gt;
            );
          })}
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.01.15.png&quot; data-origin-width=&quot;2978&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK2zCB/btsFTfcrtMN/6uRJHPKEDOWLacMrzZUZPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK2zCB/btsFTfcrtMN/6uRJHPKEDOWLacMrzZUZPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK2zCB/btsFTfcrtMN/6uRJHPKEDOWLacMrzZUZPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK2zCB%2FbtsFTfcrtMN%2F6uRJHPKEDOWLacMrzZUZPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2978&quot; height=&quot;634&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.01.15.png&quot; data-origin-width=&quot;2978&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BoardNewItem 컴포넌트를 만들어서 데이터를 보내도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 grid로 스타일링 하여 한 줄에 4개씩 보이게 만들었는데 아무래도 게시판은 기본 게시판 스타일로 보여주는 게 좋을 것 같아서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 개별 게시글 페이지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 개별 게시글 페이지 데이터 가져오는 api 작성 (api/post/[id]/route.ts)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710835533229&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import prisma from &quot;@/app/lib/prisma&quot;;
import { NextResponse } from &quot;next/server&quot;;

export async function GET(request: Request, { params }: any) {
  const id = params.id;

  const post = await prisma.post.findUnique({
    where: { id },
    include: {
      author: true,
    },
  });

  const authorName = post?.author?.name || &quot;Unknown&quot;;

  // 필요한 데이터만 추출하여 반환
  const postData = {
    id: post?.id,
    title: post?.title,
    content: post?.content,
    published: post?.published,
    authorId: post?.authorId,
    createdAt: post?.createdAt,
    updatedAt: post?.updatedAt,
    authorName: authorName, // 작성자 이름 추가
  };

  return NextResponse.json(postData);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 상세페이지 features에 적용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710835425660&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

const BoardDetailPage = ({ postId }: any) =&amp;gt; {
  // 로그인한 유저 정보
  const { data: session } = useSession();
  const userId = session?.user?.id;

  // 좋아요
  const [like, setLike] = useState(false);

  const OnHeartClick = () =&amp;gt; {
    setLike(!like);
  };

  // 게시글
  const [postState, setPostState] = useState&amp;lt;{
    title: string;
    authorName: string;
    createdAt: string;
    updatedAt: string | null;
    content: string | null;
    authorId: number | null;
  }&amp;gt;({
    title: &quot;&quot;,
    authorName: &quot;&quot;,
    createdAt: &quot;&quot;,
    updatedAt: null,
    content: null,
    authorId: null,
  });

  // 게시글 데이터 가져오기
  useEffect(() =&amp;gt; {
    async function fetchPost() {
      try {
        const response = await fetch(`/api/post/${postId}`, {
          method: &quot;GET&quot;,
        });
        const postData = await response.json();
        setPostState(postData);
      } catch (error) {
        console.error(&quot;Error fetching post:&quot;, error);
      }
    }

    fetchPost();
  }, [postId]);

  const newFormat = dayjs(postState?.createdAt).format(&quot;YYYY-MM-DD H:mm:ss&quot;);

  return (
    &amp;lt;div className={styles.root}&amp;gt;
      &amp;lt;div className={styles.titleBox}&amp;gt;
        &amp;lt;div className={styles.titleTop}&amp;gt;
          &amp;lt;p className={styles.title}&amp;gt;{postState?.title}&amp;lt;/p&amp;gt;

          {/* 로그인한 유저와 글쓴 유저가 같을 때만 나타남 */}
          {userId === postState?.authorId &amp;amp;&amp;amp; (
            &amp;lt;div className={styles.subTxt}&amp;gt;
              &amp;lt;UpdatePostButton postId={postId} /&amp;gt;
              &amp;lt;span className={styles.bar}&amp;gt;|&amp;lt;/span&amp;gt;
              &amp;lt;DeletePostButton postId={postId} /&amp;gt;
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.info}&amp;gt;
          &amp;lt;p&amp;gt;작성자: {postState?.authorName}&amp;lt;/p&amp;gt;
          &amp;lt;span&amp;gt;{newFormat}&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div className={styles.top}&amp;gt;
        {/* &amp;lt;span className={styles.mainImgBox}&amp;gt;
          &amp;lt;Image src={example1} alt=&quot;예시사진&quot; className={styles.mainImg} /&amp;gt;
        &amp;lt;/span&amp;gt; */}
        &amp;lt;span className={styles.heartBox}&amp;gt;
          &amp;lt;Image
            src={like ? heart_fill : heart}
            alt=&quot;빈 하트&quot;
            className={styles.heartImg}
            onClick={OnHeartClick}
          /&amp;gt;
        &amp;lt;/span&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;div className={styles.bottom}&amp;gt;
        &amp;lt;div className={styles.nameWrap}&amp;gt;{postState?.content}&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default BoardDetailPage;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch 함수를 통해서 만들어놓은 api를 불러오고&lt;/li&gt;
&lt;li&gt;로그인 한 유저의 아이디와 작성자 아이디가 일치하면 수정, 삭제 버튼이 보이도록 하였다.&lt;/li&gt;
&lt;li&gt;하트를 누르면 검은색 하트로 바뀌면서 그 정보가 db에 저장되도록 할 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.08.30.png&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LOSRP/btsFVVqkLht/jsKEh8Z5ZSY7PeTJdpUkzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LOSRP/btsFVVqkLht/jsKEh8Z5ZSY7PeTJdpUkzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LOSRP/btsFVVqkLht/jsKEh8Z5ZSY7PeTJdpUkzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLOSRP%2FbtsFVVqkLht%2FjsKEh8Z5ZSY7PeTJdpUkzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2934&quot; height=&quot;632&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.08.30.png&quot; data-origin-width=&quot;2934&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인한 유저와 작성자가 같지 않을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.08.46.png&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWN8Qr/btsFWztx7O5/Y2HEPb3SMopghkANuxgRi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWN8Qr/btsFWztx7O5/Y2HEPb3SMopghkANuxgRi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWN8Qr/btsFWztx7O5/Y2HEPb3SMopghkANuxgRi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWN8Qr%2FbtsFWztx7O5%2FY2HEPb3SMopghkANuxgRi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2920&quot; height=&quot;606&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.08.46.png&quot; data-origin-width=&quot;2920&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인한 유저와 작성자가 같을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3️⃣ 게시글 수정하기 (UPDATE)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. update api 작성하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710853025718&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export async function POST(request: Request, { params }: any) {
  const id = params.id;
  const { title, content, authorId } = await request.json(); // JSON 형식으로 파싱

  // 글 수정 로직 구현
  const updatedPost = await prisma.post.update({
    where: { id },
    data: {
      title,
      content,
      authorId,
    },
  });

  return NextResponse.json(updatedPost);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. postForm 컴포넌트 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710853064778&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

const PostForm = ({ postId }: any) =&amp;gt; {
 ...

  useEffect(() =&amp;gt; {
    // postId가 존재하면 해당 글 데이터를 불러옴
    if (postId) {
      // postId를 사용하여 글 데이터를 불러오는 API 호출
      fetch(`/api/post/${postId}`)
        .then((response) =&amp;gt; response.json())
        .then((postData) =&amp;gt; {
          setTitle(postData.title);
          setContent(postData.content);
          // 작성자 식별자를 설정
          setAuthorId(postData.authorId);
        })
        .catch((error) =&amp;gt; console.error(&quot;Error fetching post:&quot;, error));
    }
  }, [postId]);

  // 새 글 작성 및 수정 로직
  const handleSubmit = async (e: SyntheticEvent) =&amp;gt; {
    e.preventDefault();

    // authorId가 null인 경우 처리
    if (authorId === null) {
      console.error(&quot;사용자 식별자가 없습니다. 사용자를 다시 로그인하세요.&quot;);
      return;
    }

    try {
      // postId가 있으면 글 수정, 없으면 새 글 작성
      const url = postId ? `/api/post/${postId}` : &quot;/api/addpost&quot;;
      await fetch(url, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({ title, content, authorId }),
      });
      alert(&quot;저장하시겠습니까?&quot;);
      router.push(&quot;/board&quot;);
      router.refresh();
    } catch (error) {
      console.log(error);
    }

    setTitle(&quot;&quot;);
    setContent(&quot;&quot;);
  };

  return (
    &amp;lt;div className={styles.formBox}&amp;gt;
      &amp;lt;h1 className={styles.formTitle}&amp;gt;
        {title ? &quot;글 수정하기&quot; : &quot;글 작성하기&quot;}
      &amp;lt;/h1&amp;gt;

      &amp;lt;form onSubmit={handleSubmit} className={styles.form}&amp;gt;
        &amp;lt;div className={styles.inputBox}&amp;gt;
          &amp;lt;label htmlFor=&quot;title&quot; className={styles.label}&amp;gt;
            제목
          &amp;lt;/label&amp;gt;
          &amp;lt;input
            type=&quot;text&quot;
            id=&quot;title&quot;
            name=&quot;title&quot;
            value={title}
            onChange={handleTitleChange}
            required
            className={styles.input}
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.textAreaBox}&amp;gt;
          &amp;lt;label htmlFor=&quot;content&quot; className={styles.label}&amp;gt;
            내용
          &amp;lt;/label&amp;gt;
          &amp;lt;textarea
            name=&quot;content&quot;
            id=&quot;content&quot;
            value={content}
            onChange={handleContentChange}
            required
            className={styles.textArea}
          /&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.buttonBox}&amp;gt;
          &amp;lt;button type=&quot;submit&quot; className={styles.saveBtn}&amp;gt;
            저장하기
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div className={styles.buttonBox}&amp;gt;
          &amp;lt;p
            onClick={() =&amp;gt; {
              alert(
                &quot;작성 중인 내용은 저장되지 않습니다. 그래도 취소하시겠습니까?&quot;
              );
              router.push(&quot;/board&quot;);
            }}
            className={styles.cancelTxt}
          &amp;gt;
            취소하기
          &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default PostForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 9.56.14.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0VKXO/btsFV9bdRup/wTRnfhWWZR81WlHEnMkSsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0VKXO/btsFV9bdRup/wTRnfhWWZR81WlHEnMkSsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0VKXO/btsFV9bdRup/wTRnfhWWZR81WlHEnMkSsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0VKXO%2FbtsFV9bdRup%2FwTRnfhWWZR81WlHEnMkSsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1892&quot; height=&quot;906&quot; data-filename=&quot;스크린샷 2024-03-19 오후 9.56.14.png&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 9.56.27.png&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8HNn3/btsFWtUPw6l/uthbbAVXKFE3iar0IFKFMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8HNn3/btsFWtUPw6l/uthbbAVXKFE3iar0IFKFMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8HNn3/btsFWtUPw6l/uthbbAVXKFE3iar0IFKFMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8HNn3%2FbtsFWtUPw6l%2FuthbbAVXKFE3iar0IFKFMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1810&quot; height=&quot;569&quot; data-filename=&quot;스크린샷 2024-03-19 오후 9.56.27.png&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수정 잘 되는 것 확인!&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4️⃣ 게시글 삭제하기 (DELETE)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. delete api 작성하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710835862716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import prisma from &quot;@/app/lib/prisma&quot;;
import { NextResponse } from &quot;next/server&quot;;

export async function DELETE(request: Request, { params }: any) {
  const id = params.id;

  const post = await prisma.post.delete({
    where: { id },
  });

  return NextResponse.json(post);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 삭제 버튼 컴포넌트 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1710835889616&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import { useRouter } from &quot;next/navigation&quot;;
import * as styles from &quot;./index.css&quot;;

const DeletePostButton = ({ postId }: any) =&amp;gt; {
  const router = useRouter();

  async function handleClick() {
    try {
      await fetch(`/api/post/${postId}`, {
        method: &quot;DELETE&quot;,
      });

      router.refresh;
    } catch (e) {
      console.error(e);
    }
  }

  return (
    &amp;lt;span onClick={handleClick} className={styles.deleteBtn}&amp;gt;
      삭제
    &amp;lt;/span&amp;gt;
  );
};

export default DeletePostButton;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.12.45.png&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKM2Wy/btsFWDvXstw/LikwI2tkYNbwQopnk7dGoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKM2Wy/btsFWDvXstw/LikwI2tkYNbwQopnk7dGoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKM2Wy/btsFWDvXstw/LikwI2tkYNbwQopnk7dGoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKM2Wy%2FbtsFWDvXstw%2FLikwI2tkYNbwQopnk7dGoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2982&quot; height=&quot;528&quot; data-filename=&quot;스크린샷 2024-03-19 오후 5.12.45.png&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;asdfasdf 게시글이 삭제된 것을 볼 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 기본적인 CRUD도 끝!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 api까지 만들어 db 연결하고,, 처음이라 많이 헤맸지만 (typescript 오류까지 겹쳐서 더더,,) 풀로 다 만들어보니 전체적인 과정도 알 수 있어서 좋은 경험이었다!&lt;/p&gt;</description>
      <category>NEXT.JS/Cock! 칵테일 프로젝트</category>
      <author>예글</author>
      <guid isPermaLink="true">https://01exsilver.tistory.com/67</guid>
      <comments>https://01exsilver.tistory.com/67#entry67comment</comments>
      <pubDate>Sat, 16 Mar 2024 22:27:36 +0900</pubDate>
    </item>
  </channel>
</rss>