import { createApp, h } from "vue";
const app = createApp({
render() {
return h("div", h("h1", "Hello vue3"));
},
});
app.mount("#app");
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
<script>
export default {};
</script>
export default {
name: "App",
};
export default {
data: function(){
return {
message : "Hello, World!"
}
}
};
export default {
data(){
return {
message : "Hello, World!"
}
}
};
<template>
<h1>{{ message }}</h1>
</template>
<template>
<h1>{{ message }}</h1>
<h2>{{ 1 + 2 }}</h2>
<h3>{{ message.toUpperCase() }}</h3>
</template>
<template>
<h1>{{ message }}</h1> // Hello, World!
<div v-html="message"></div> // Hello, World!
</template>
<template>
<h1>{{ message }}</h1> // Hello, World!
<div v-html="message"></div> // Hello, World!
<div v-text="message"></div> // Hello, World!
</template>
<div v-html="message">ddd</div> // 에러
<div v-text="message">ddd</div> // 에러
2. v-pre
<div v-pre>{{ name }}</div> // {{ name }} 그대로 출력
<div v-pre>
<p>{{ name }}</p>
<p>{{ name }}</p>
</div> // {{ name }} {{ name }}
3. v-bind , :
export default {
data: function () {
return {
id: "main",
};
},
};
</script>
<template>
<main v-bind:id="id"></main> // id가 data에서 지정한 값이 바인딩된다.
</template>
</script>
<template>
<main :id="id"></main> // 콜론만 입력해서 id값으로써 데이터바인딩이 가능하게 된다.(class, style, href 등등 다 가능)
</template>
export default {
data: function () {
return {
id: "main",
class : "sub"
};
},
};
</script>
<template>
<main :id></main> // data의 속성과 동일하면 속성값을 사용할 필요 없이 바인딩 가능하다.
<div :class></div>
</template>
export default {
data: function () {
return {
isActive : false
};
},
};
</script>
<template>
<div :class="isActive ? "show" : "hide">등장</div>
<div :class="isActive && "show">등장2</div>
</template>
export default {
name: "App",
data() {
return {
imageSrc:
"https://cdn.pixabay.com/photo/2021/11/26/20/45/lantern-6826697_1280.jpg",
};
},
};
</script>
<template>
<img :src="imageSrc" alt="altText" />
</template>
<script>
export default {
name: "App",
data() {
return {
styledObject: {
color: "red",
fontSize: "30px",
},
};
},
};
</script>
<template>
<p :style="styledObject">asd</p>
</template>
<script>
export default {
name: "App",
data() {
return {
binding: {
id: "main",
style: { color: "red", fontWeight: "bold" },
},
};
},
};
</script>
<template>
<p v-bind="binding">가나다</p>
</template>
5. v-if(v-else-if, v-else), v-show
<template>
<p v-if="false">가나다</p> // 요소의 렌더링을 막음
<p v-if="true">마바사</p>
<p v-show="false">가나다</p> // 요소는 존재하고 display:none 처리
<p v-show="true">마바사</p>
</template>
<script>
export default {
name: "App",
data() {
return {
isActive: true
};
},
};
</script>
<template>
<p v-if="isActive">가나다</p>
<p v-if="1 >= 0">가나다</p>
</template>
<template>
<p v-if="false">가나다</p>
<p v-else-if="false">가나다</p>
<p v-else-if="true">가나다</p> // 이 요소만 렌더링
<p v-else-if="true">가나다</p>
<p v-else>가나다</p> // 만약 전부 false였다면 해당 요소 렌더링
</template>
6. v-cloak
7. v-for
<script>
import Success from "./2-1/Success.vue";
import Loading from "./2-1/Loading.vue";
import Idle from "./2-1/Idle.vue";
import Error from "./2-1/Error.vue";
export default {
data() {
return {
arr: [1, 2, 3],
};
},
};
</script>
<ul>
<li v-for="(value, index) in arr" key="index">{{ value }}</li>
</ul>
첫번째엔 대상값, 두번째엔 인덱스값으로 파라미터를 받아 반복문을 사용할 수 있다.
v-for 반복문에서 추가로 filter, map 등의 함수를 사용해 반복문 돌릴 대상을 특정하는 로직을 추가하는 등의 방법을 사용해도 된다.
<script>
export default {
data() {
return {
itemsObj: [
{ id: 1, name: "james" },
{ id: 2, name: "smith" },
],
};
},
};
</script>
<ul>
<li
v-for="(value, index) in itemsObj.filter((e) => e.name === 'james')"
:key="index"
>
{{ value.id }}-{{ value.name }}
</li>
</ul>
<script>
export default {
data() {
return {
obj: { id: 2, name: "smith" },
};
},
};
</script>
<ul>
<li v-for="(value, key) in obj" :key="key">{{ value }}-{{ key }}</li>
</ul>
<script>
export default {
data() {
return {
categorys: [
{ name: "Fruits", items: ["Apple", "Banana", "Cherry"] },
{ name: "Vegetables", items: ["Carrot", "Tomato", "Lettuce"] },
],
};
},
};
</script>
<template>
<ul>
<li v-for="(category, index) in categorys" :key="index">
<h3>{{ category.name }}</h3>
<ul>
<li v-for="item in category.items">
{{ item }}
</li>
</ul>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
categorys: [
{ name: "Fruits", items: ["Apple", "Banana", "Cherry"] },
{ name: "Vegetables", items: ["Carrot", "Tomato", "Lettuce"] },
],
};
},
};
</script>
<template>
<ul>
<li v-for="(category, index) in categorys" :key="index">
<h3>{{ category.name }}</h3>
<ul>
<template v-for="item in category.items">
<li v-if="item.includes('e')">
{{ item }}
</li>
</template>
</ul>
</li>
</ul>
</template>
복잡한 배열 예시
<script>
export default {
data() {
return {
categorys: [
{
id: 1,
name: "전자제품",
products: [
{
id: 1,
name: "스마트폰",
isAvailable: true,
options: [
{ id: 1, name: "블랙", inStock: true },
{ id: 2, name: "화이트", inStock: false },
],
},
],
},
],
};
},
};
</script>
<template>
<div>
<template v-for="(category, index) in categorys" :key="category.id">
<h2>{{ category.name }}</h2>
<template v-for="(product, index) in category.products" :key="product.id">
<h3>{{ product.name }}</h3>
<template v-for="(option, index) in product.options" :key="option.id">
<h4 v-if="option.inStock">{{ option.name }}</h4>
</template>
</template>
</template>
</div>
</template>
8. v-model
<script>
export default {
data() {
return {
input: "",
};
},
};
</script>
<template>
<input type="text" v-model="input" />
<h1>{{ input }}</h1>
</template>
<script>
export default {
data() {
return {
uid: "",
upw: "",
desc: "",
chk: false,
fruits: ["apple"],
gender: "male",
};
},
};
</script>
<template>
<form action="">
<label for="uid">
<span>아이디</span>
<input type="text" id="uid" v-model="uid" />
</label>
<label for="upw">
<span>비밀번호</span>
<input type="password" id="upw" v-model="upw" />
</label>
<label for="desc">
<span>상세내용</span>
<textarea id="desc" v-model="desc" />
</label>
<label for="remember">
<span>check</span>
<input type="checkbox" id="remember" v-model="chk" />
</label>
<div>
<label>
<span>apple</span>
<input type="checkbox" v-model="fruits" value="apple" />
</label>
<label>
<span>banana</span>
<input type="checkbox" v-model="fruits" value="banana" />
</label>
<label>
<span>orange</span>
<input type="checkbox" v-model="fruits" value="orange" />
</label>
</div>
<label
><input
type="radio"
name="gender"
value="male"
v-model="gender"
/>male</label
>
<label
><input
type="radio"
name="gender"
value="female"
v-model="gender"
/>female</label
>
</form>
</template>
vue에서 스타일을 부여하는 방법으로 sfc구조의 style태그를 사용하는 방법이 가장 일반적이다.
사용하는 방법은 기존 css와 동일하게 하면 되는듯 하다
그런데 여기서 style에 scoped속성을 부여하냐, 그냥 사용하냐에 따라 달라진다.
scoped속성을 추가하면 해당 컴포넌트 내에서만 스타일의 적용을 받을 수 있게 된다.
전역 스타일이 되냐 지역 스타일이 되냐를 분류할 수 있다.
뷰는 실무에서 style태그를 활용해서 css를 부여한다.
css로 스타일을 만드는 방법도 있다.
scc파일을 만든 후에 style태그에 @import로 불러오면 된다.
vue파일이 길어지는게 보기 싫으면 이렇게 css 따로 모듈화 해서 사용하면 된다.
vue에서 css가 담긴 style폴더는 보통 assets폴더에 보관한다.
그 이유는 viteConfig의 경로 alias 기능을 사용할 수 있기 때문이다(?)
assets에 있는 폴더는 viteConfig에서 import할 경로의 맨 앞을 ~나 @등 원하는 기호로 정할 수 있다(절대경로 커스텀 가능한게 좋다는건가?)
해외의 대부분 개발자는 ~를 주로 사용한다고 한다.
자바스크립트의 import를 사용하는 방법도 있다
main.js에서 css파일을 import해서 전역으로 사용하는 방법이 있다.
태그에 인라인으로 넣는 방법도 있다.(이거 써도 돼?)
어짜피 컴포넌트 안에 스타일 스크립트 다 들어가서 상관없는가?
data에 style들을 만들어서 인라인에 넣어 한번에 적용할 수도 있다.
style들을 여러개 만들어서 style="[]" 로 안에 style들을 배열처럼 집어넣으면 한번에 적용이 된다.
npm install bootstrap
import "bootstrap";
import "bootstrap/dist/css/bootstrap.css";
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,vue}"],
theme: {
extend: {},
},
plugins: [],
};
<style lang="scss">
div {
h2 {
color : orange
}
}
</style>
npm i @vue-styled-components/core
<script setup lang="ts">
import { styled } from '@vue-styled-components/core'
const StyledDiv = styled.div`
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
border-radius: 9999px;
background-color: #4c5a6d;
font-size: 20px;
font-weight: bold;
color: #fff;
`
</script>
<template>
<StyledDiv>Hello World!</StyledDiv>
</template>
<script>
export default {
name: "App",
data() {
return {
count: 0,
};
},
methods: {
decrement: function () {
console.log("decrement");
},
increment: function () {
console.log("increment");
},
reset: function () {
console.log("reset");
},
},
};
</script>
<script>
export default {
name: "App",
data() {
return {
count: 0,
};
},
methods: {
decrement: function () {
this.count -= 1;
},
increment: function () {
this.count += 1;
},
reset: function () {
this.count = 0;
},
},
};
</script>
increment: function () {
this.increment()
this.count += 1;
},
methods: {
decrement() {
this.count -= 1;
},
increment() {
this.count += 1;
},
reset() {
if (this.count === 0) return;
this.count = 0;
},
},
<template>
<h1>{{ count }}</h1>
<button v-on:click="increment">증가</button>
<button v-on:click="reset">리셋</button>
<button v-on:click="decrement">감소</button>
</template>
<template>
<h1>{{ count }}</h1>
<button @:click="increment">증가</button>
<button @:click="reset">리셋</button>
<button @:click="decrement">감소</button>
</template>
<template>
<h1>{{ count }}</h1>
<button @:click="increment">증가</button>
<button @:click="reset">리셋</button>
<button @:click="decrement">감소</button>
<button @:click="count -= 2">2감소</button>
</template>
methods: {
decrement(event) {
console.log(event)
this.count -= 1;
},
increment() {
this.count += 1;
},
reset() {
if (this.count === 0) return;
this.count = 0;
},
},
<button @:click="decrement($event)">감소</button>
<button @:click.once="increment">증가</button>
<script>
export default {
name: "App",
data() {
return {
count: 0,
email: "",
};
},
methods: {
decrement(event) {
console.log(event);
this.count -= 1;
},
increment() {
this.count += 1;
},
reset() {
if (this.count === 0) return;
this.count = 0;
},
consoleEmail() {
console.log(this.email);
},
},
};
</script>
<template>
<a href="https://www.naver.com" @click.prevent></a>
<form @click.prevent="consoleEmail">
<input type="text" v-model="email" />
<button type="submit">제출</button>
</form>
</template>
<input type="text" @keyup.enter="consoleEmail" v-model="email" />
<input type="text" @keyup.ctrl.enter="consoleEmail" v-model="email" />
// crtl + enter를 입력해야 발동
<input type="text" @keyup.enter.exact="consoleEmail" v-model="email" /> // 반드시 enter를 입력할때만 이벤트 발동
<script>
export default {
name: "App",
data() {
return {
query: "",
};
},
methods: {
handleInput($event) {
this.query = $event.target.value;
},
},
};
</script>
<input type="text" :value="query" @input="handleInput($event)" />
{{ query }}
<script>
export default {
name: "App",
data() {
return {
name: "철수",
age: 20,
gender: "male",
};
},
methods: {},
};
</script>
<template>
<div v-memo="[name]">
<h2>{{ name }}</h2>
<h2>{{ age }}</h2>
<h2>{{ gender }}</h2>
</div>
<button @click="name = 'james'">이름 변경</button>
<button @click="age = 50">나이 변경</button>
<button @click="gender = 'female'">성별 변경</button>
</template>
<script>
export default {
data() {
return {
firstName: "kisu",
lastName: "kim",
};
},
methods: {},
computed: {
fullName() {
console.log("ㅎㅇ");
return `${this.lastName}${this.firstName}`;
},
},
};
</script>
<template>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
<h2>{{ fullName }}</h2>
</template>
import { createApp } from "vue";
import App from "./App.vue";
import First from "./components/First.vue";
const app = createApp(App);
// 전역 컴포넌트 등록방법
app.component("First", First);
app.mount("#app");
import { createApp } from "vue";
import App from "./App.vue";
import First from "./components/First.vue";
import Second from "./components/Second.vue";
const app = createApp(App);
// 전역 컴포넌트 등록방법
app //
.component("First", First)
.component("Second", Second);
app.mount("#app");
<script>
import First from "./components/First.vue";
import Second from "./components/Second.vue";
export default {
name: "App",
components: {
First,
Second,
},
};
</script>
<template>
<article>스타또</article>
<First />
<Second />
</template>
컴포넌트가 연결된 지역엔 그 지역의 style이 적용된다.
vue에서 컴포넌트를 렌더링하는 방식의 특징 때문에 발생하는 현상이다.
import받는 컴포넌트의 루트요소를 같은 범위로 인식하기 때문에 import되는 컴포넌트의 스타일도 루트컴포넌트의 스타일을 공유하게 된다.
vue의 컴포넌트엔 특징이 하나 있는데 컴포넌트요소에 id, class등 속성을 넣으면 해당 컴포넌트 파일의 루트요소에 그 속성이 전달된다.
속성이 전달되는것 뿐 만 아니라 props의 역할까지 한다 = 2가지의 역할을 수행함
<First id="title" class="title" /> // First 컴포넌트의 루트요소로 들어감
// 부모 컴포넌트
<script>
import First from "./components/First.vue";
export default {
name: "App",
components: {
First,
},
};
</script>
<template>
<article>스타또</article>
<First id="title" class="title" hello="hello" />
</template>
// 자식 컴포넌트
<script>
export default {
name: "First",
data() {
return {};
},
props: ["hello"], // hello만 props로 사용 가능 + id랑 class는 속성값으로 들어감
};
</script>
<template>
<div>첫번째 {{ hello }}</div>
</template>
<First id="title" class="title" hello="hello" v-bind:age="20" />
짱많네...
총 9개의 라이프사이클을 가지고 있다.
사용법은 다음과 같다.
옵션스 API에 data(){} 처럼 추가해서 사용하면 된다.
export default {
name: "First",
data() {
return {};
},
props: [],
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeUnmount() {
console.log("beforeUnmount");
},
unmounted() {
console.log("unmounted");
},
};
<script>
export default {
name: "First",
data() {
return {
count: 0,
};
},
props: [],
beforeCreate() {
console.log("beforeCreate", this.count);
},
created() {
console.log("created", this.count);
},
beforeMount() {
console.log("beforeMount", this.count);
},
mounted() {
console.log("mounted", this.count);
},
beforeUpdate() {
console.log("beforeUpdate", this.count);
},
updated() {
console.log("updated", this.count);
},
beforeUnmount() {
console.log("beforeUnmount", this.count);
},
unmounted() {
console.log("unmounted", this.count);
},
};
</script>
<template>
<div>첫번째</div>
<p>{{ count }}</p>
<button @click="count += 1">+</button>
</template>
// 컴포넌트 생성
beforeCreate undefined
First.vue:14 created 0
First.vue:17 beforeMount 0
First.vue:20 mounted 0
// 컴포넌트 업데이트(count + 1 눌렀을때)
First.vue:23 beforeUpdate 1 // 화면 업데이트 전 (렌더링 되기 이전부터 숫자는 증가해있다.)
First.vue:26 updated 1 // 화면 업데이트 후
// 컴포넌트 제거
First.vue:29 beforeUnmount 1
First.vue:32 unmounted 1
watch: {
count(cur, prev) {
console.log("현재 값", cur);
console.log("이전 값", prev);
},
},
watch: {
count: {
handler(cur, prev) {
console.log("현재 값", cur);
console.log("이전 값", prev);
},
deep: true,
},
},
export default {
name: "FirstChild",
props: {
age: { type: Number }, // age props의 타입을 number로 지정
},
data() {
return {};
},
};
</script>
<template>
<h1>FirstChild</h1>
<p>{{ age }} / {{ typeof age }}</p>
</template>
export default {
name: "FirstChild",
props: {
age: { type: [Number, String] }, // age props의 타입을 number 또는 string으로 지정
},
data() {
return {};
},
};
</script>
<template>
<h1>FirstChild</h1>
<p>{{ age }} / {{ typeof age }}</p>
</template>
props: {
age: {
type: [Number, String],
default() {
return 10;
},
},
},
props: {
age: {
type: [Number, String],
require: true,
},
},
props: {
age: {
type: [Number, String],
require: true,
default() {
return 10;
},
validator(value) {
return value > 0; // 0보다 더 큰 값만 취급해줌
},
},
props: {
disabled: boolean // true로 값이 저장됨
},
props: {
age: { type: [Number, String] },
name: String,
},
<FirstChild :name="name" :print-hello="printHello"/> // 전달할땐 케밥케이스로 쓰는데 받을땐 카멜케이스로 받는다.
<FirstChild :name="name" :print-hello="printHello" @greet="greet" />
export default {
name: "FirstChild",
components: { SecondChild },
data() {
return {};
},
props: {
name: String,
printHello: Function,
},
emits: ["greet"],
};
</script>
<template>
<h2>FirstChild</h2>
<button @click="$emit('greet')">greet</button>
<SecondChild :name="name" :print-hello="printHello" @greet="$emit('greet')" />
</template>
emits: ["greet"],
methods: {
greet() {
return this.$emit("greet");
},
},
<script>
import FirstChild from "./components/FirstChild.vue";
export default {
name: "App",
components: {
FirstChild,
},
provide: { // provide로 전역상태 설정
message: "안녕!",
},
};
</script>
<template>
<FirstChild />
</template>
<script>
export default {
name: "FirstChild",
data() {
return {};
},
inject: ["message"], // inject로 전역상태 받기
created() {
console.log(this.message);
},
};
</script>
<template>
<div>{{ message }}</div>
</template>
이는 전역으로 공유하는 상태이기 때문에 props드릴링이 발생하지 않는다.
자신보다 하위의 컴포넌트들에게 inject로 데이터를 전달해줄 수 있다.
자식에서 부모는 provide를 할 수 없다.(렌더링 시점부터 다름)
물론 함수도 가능하다
<script>
import FirstChild from "./components/FirstChild.vue";
export default {
name: "App",
components: {
FirstChild,
},
provide: {
message: "안녕!",
print(e) {
console.log(e);
},
},
};
</script>
<template>
<FirstChild />
</template>
<script>
import SecondChild from "./SecondChild.vue";
export default {
name: "FirstChild",
data() {
return {};
},
inject: ["message", "print"],
created() {
console.log(this.message);
},
components: {
SecondChild,
},
};
</script>
<template>
<div>{{ message }}</div>
<SecondChild />
<button @click="print('a')">a</button>
</template>
단, provide로 전송해주는 컴포넌트에서 만든 methods나 data를 this.으로 담아서 전송하는건 불가능하다.
하지만 이것도 provide를 함수처럼 활용하면 this참조로도 사용 가능하다.
함수처럼 활용한다는 말은 provide를 함수형태로 만들고 함수 안에 return{} 을 넣어서 그 안에서 작성하면 된다.
<script>
import FirstChild from "./components/FirstChild.vue";
export default {
name: "App",
components: {
FirstChild,
},
data(){
return{
ex: "해위이이"
}
},
provide() { // 함수형태로 작성
return { // return문으로 객체형식 공급
message: "안녕!",
print(e) {
console.log(e);
},
ex: this.ex, // this키워드로 컴포넌트 내의 상태 불러오기 가능
};
},
};
</script>
<template>
<FirstChild />
</template>
따라서 provide는 this도 사용할 수 있는 상위호환인 함수형태 provide를 사용하면 된다.
그런데 inject로 받은 값은 그 값을 변경하려해도 화면에 반영되지 않는다.
provider로 공급받은 상태는 반응성을 가지지 않기 때문이다.
하지만 받을 수 있게 하는 방법은 computed 함수를 사용하면 된다(옵션스 API 그거 아님)
provide를 넘길때 이 computed함수로 래핑한 후에 전송하면 해당 상태가 반응성을 가지게 된다.
반응성을 주고 싶으면 이 computed를 사용해서 바꾸면 된다.
그리고 computed를 받은 상태는 readonly가 걸리기 때문에 루트 컴포넌트에서 해당 상태를 수정하는 함수를 inject로 가져와 사용하지 않는 이상 값의 직접적인 변경은 할 수 없다.
inject를 사용할때 배열[] 말고 객체{}로도 가져올 수 있다.
객체로 가져오면 상태의 이름을 변경할 수 있고, 기본값을 정할 수 있다.
inject: {
message : {
from: "message2",
default(){
return "방가워"
}
}
}
// keys.js
export const count = Symbol();
provide() {
return {
[count]: computed(() => this.count),
increment: this.increment,
};
},
import Button from "./components/Button.vue";
export default {
name: "App",
data() {
return {};
},
components: {
Button,
},
};
</script>
<template>
<div>asd</div>
<Button>버튼</Button> // Button 컴포넌트에 컨텐츠로 "버튼" 이라는 텍스트 전송
</template>
<script>
export default {
name: "button",
data() {
return {};
},
};
</script>
<template>
<button>
<slot></slot> // 이곳에 "버튼" 텍스트 렌더링
</button>
</template>
이 을 활용해서 컨텐츠의 내용을 렌더링 하는걸 이름 없는 슬롯 방식 이라고 부른다.
이름이 있는 슬롯 방식이란 것도 있다.
이름이 있는 슬롯 방식은 컨텐츠를 여러개 만들어야 할 때 v-slot이라는 디렉티브를 활용해 이름을 지정해주면 여러개의 slot을 사용할 수 있게 된다.
v-slot을 활용하여 이름을 정해줄땐 각각의 슬롯마다 template태그로 감싸고 거기에 v-slot을 넣어줘야 한다.
<script>
import Button from "./components/Button.vue";
import DefaultLayout from "./layouts/DefaultLayout.vue";
export default {
name: "App",
data() {
return {};
},
components: {
Button,
DefaultLayout,
},
};
</script>
<template>
<div>asd</div>
<Button>버튼</Button>
<DefaultLayout>
<template v-slot:header>
<header>
<h1>header</h1>
</header>
</template>
<template v-slot:main>
<main>
<h2>main</h2>
</main>
</template>
<template v-slot:footer>
<footer>
<h3>footer</h3>
</footer>
</template>
</DefaultLayout>
</template>
<script>
export default {
name: "DefaultLayout"
};
</script>
<template>
<slot name="header"></slot>
<slot name="main"></slot>
<slot name="footer"></slot>
</template>
<template>
<div>asd</div>
<Button>버튼</Button>
<DefaultLayout>
<template v-slot:header>
<header>
<h1>header</h1>
</header>
</template>
<template v-slot:default> // default로 이름 없는 슬롯 지정(또는 template를 없애는 것도 방법이다.)
<main>
<h2>main</h2>
</main>
</template>
<template v-slot:footer>
<footer>
<h3>footer</h3>
</footer>
</template>
</DefaultLayout>
</template>
<script>
export default {
name: "DefaultLayout",
};
</script>
<template>
<slot name="header"></slot>
<slot></slot> // 이름없는 슬롯으로 사용 가능
<slot name="footer"></slot>
</template>
<slot>
<span>안녕</span> // 기본값이 됨
</slot>
<script>
import Button from "./components/Button.vue";
import DefaultLayout from "./layouts/DefaultLayout.vue";
export default {
name: "App",
data() {
return {
slotName: "header", // 동적으로 추가할 슬롯명을 변수로 저장
};
},
components: {
DefaultLayout,
},
};
</script>
<template>
<DefaultLayout>
<template v-slot:[slotName]> // [] 안에 변수를 넣어 동적으로 슬롯명 지정
<header>
<h1>header</h1>
</header>
</template>
</DefaultLayout>
</template>
<template #footer>
<footer>
<h3>footer</h3>
</footer>
</template>
slot으로 생성된 컨텐츠는 부모 컴포넌트의 data와 스타일 영향을 받는다. 이를 슬롯의 범위라고 한다.
slot으로 css가 꾸며진 컨텐츠를 전송하고 싶다면 부모 컴포넌트에서 작업한 뒤 전송해야 한다.
slot에 전할 컨텐츠가 data(){} 안에 있는 값이 바인딩 되어 있을때 그 바인당 키값이 부모, 자식 컴포넌트 둘 다 존재한다면 부모 컴포넌트의 키값을 사용한다.
슬롯의 범위의 영향을 받는 슬롯을 범위가 지정된 슬롯이라고 한다.
만약 data의 영향을 자식 컴포넌트로 부터 사용하고 싶으면 v-bind 형태로 slot에 집어넣으면 된다.
<slot :buttonText="buttonText">{{ buttonText }}</slot>
<Button #="{ buttonText, count }">
<span>{{ buttonText }} {{ count }}</span>
</Button>
<script>
export default {
name: "button",
data() {
return {
buttonText: "헬로우",
count: 10,
};
},
};
</script>
<template>
<button>
<slot :buttonText="buttonText" :count="count"></slot>
</button>
</template>
// 옵션스 API
<script>
export default {
name: "Option",
data() {
return {
count: 0,
};
},
computed: {
doubleCount() {
return this.count * 2;
},
},
methods: {
increment() {
this.count += 1;
},
decrement() {
this.count -= 1;
},
reset() {
this.count = 0;
},
},
};
</script>
<template>
<div>{{ count }}</div>
<div>doubleCount {{ doubleCount }}</div>
<button @click="decrement">감소</button>
<button @click="reset">리셋</button>
<button @click="increment">증가</button>
</template>
// 컴포지션 API
import { computed, ref } from "vue";
export default {
name: "Composition",
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const decrement = () => count.value--;
const increment = () => count.value++;
const reset = () => (count.value = 0);
return { count, doubleCount, decrement, increment, reset };
},
};
</script>
<template>
<div>{{ count }}</div>
<div>doubleCount {{ doubleCount }}</div>
<button @click="decrement">감소</button>
<button @click="reset">리셋</button>
<button @click="increment">증가</button>
</template>
간단한 로직이라도 옵션스 API는 길이를 차지하는 속성을 많이 사용해 코드가 길어지는 반면에 컴포지션 API는 필요한 API를 함수 호출 처럼 부르는 형식으로 더욱 간편하고 간략하게 로직을 만들 수 있다.
앞선 setup 함수는 vue의 이전버전인 옵션스API에 대한 호환성을 위해 옵션스API와 비슷하게 만들어 놨다.
그러다 보니 딱히 옵션스 API와 큰 변화가 없어보여 잘 안쓰게 되었다.
그래서 새로 컴포지션 API 작성문법을 추가하게 되었는데 그게 바로 script setup이다
이 방법은 구버전의 코드가 아닌 이상 무조건 이 방법을 사용한다.
이 코드가 너무나도 쉽고 명확하기 때문이다.
// 컴포지션 API
import { computed, ref } from "vue";
export default {
name: "Composition",
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const decrement = () => count.value--;
const increment = () => count.value++;
const reset = () => (count.value = 0);
return { count, doubleCount, decrement, increment, reset };
},
};
</script>
<template>
<div>{{ count }}</div>
<div>doubleCount {{ doubleCount }}</div>
<button @click="decrement">감소</button>
<button @click="reset">리셋</button>
<button @click="increment">증가</button>
</template>
// script setup
<script setup>
import { computed, ref } from "vue";
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const decrement = () => count.value--;
const increment = () => count.value++;
const reset = () => (count.value = 0);
</script>
<template>
<div>{{ count }}</div>
<div>doubleCount {{ doubleCount }}</div>
<button @click="decrement">감소</button>
<button @click="reset">리셋</button>
<button @click="increment">증가</button>
</template>
<script setup>
import Composition from "./components/Composition.vue";
import Option from "./components/Option.vue";
import Setup from "./components/Setup.vue";
</script>
<template>
<Option />
<Composition />
<Setup />
</template>
<script setup>
import { ref } from "vue";
const count = ref(0);
const state = ref({
count: 0,
});
const numArr = ref([1, 2, 3]);
</script>
<template>
<h2>{{ count }}</h2>
<h2>{{ state.count }}</h2>
<h2>{{ numArr }}</h2>
<button @click="count++">증가(count)</button>
<button @click="state.count++">증가(state.count)</button>
<button @click="numArr.push(4)">4 추가</button>
</template>
<script setup>
import { reactive, ref } from "vue";
const state = ref({
count: 0,
});
console.log(state.value.count);
const state2 = reactive({
count: 0,
});
console.log(state2.count);
</script>
<script setup>
const count = ref(0);
watch(count, (cur, prev) => { // 1번째 파라미터에 변경되는 상태, 2번째 파라미터엔 상태변경시 실행하는 코드
console.log("count changed", cur, prev);
});
</script>
<template>
<h2>{{ count }}</h2>
<button @click="count++">증가(count)</button>
</template>
<script setup>
import { reactive } from "vue";
const obj = ref({ count: 0 });
watch(
obj,
(cur, prev) => {
console.log("count changed", cur, prev);
},
{ deep: true } // 3번째 파라미터에 {deep:true}를 주면 깊은 감시자 옵션이 적용됨
);
</script>
<template>
<h2>{{ obj.count }}</h2>
<button @click="obj.count++">증가(count)</button>
</template>
<script setup>
import { reactive } from "vue";
const obj = reactive({ count: 0 });
watch(obj, (cur, prev) => {
console.log("count changed", cur, prev); // reactive는 깊은 감시자 필요없음
});
</script>
<template>
<h2>{{ obj.count }}</h2>
<button @click="obj.count++">증가(count)</button>
</template>
watch(
obj,
(cur, prev) => {
console.log("count changed", cur, prev);
},
{ immediate: true } // immediate를 사용하면 최초 1번 console.log("count changed", cur, prev);가 실행된다.
);
const obj = reactive({ count: 0 });
watch(
obj,
(cur, prev) => {
console.log("2 changed", cur, prev);
},
{ once: true } // 상태 변경을 한 번만 감지해 다음 상태변경은 watch의 코드가 실행되지 않는다.
);
npm install unplugin-auto-import
import AutoImport from "unplugin-auto-import/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), vueDevTools(), AutoImport({ imports: ["vue"] })],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
<script setup>
import {
onBeforeMount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
} from "vue";
const count = ref(0);
console.log("component setup!");
onBeforeMount(() => {
console.log("onBeforeMount");
});
onMounted(() => {
console.log("onMounted");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate");
});
onUpdated(() => {
console.log("onUpdated");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onUnmounted(() => {
console.log("onUnmounted");
});
</script>
<template>
<h1>LifeCycle {{ count }}</h1>
<button @click="count++">+</button>
</template>
<script setup>
const props = defineProps({
count: {
type: Number,
rejected: true,
default() {
return 10;
},
validator(value) {
return value > 0;
},
},
});
</script>
<template>
<div>{{ props.count }}</div>
</template>
<script>
const emite = defineEmits(["increment"]);
const increment = () => {
emite("increment", 120);
};
</script>
<template>
<button @click="increment">클릭</button>
</template>
<script setup>
import Count from "./components/Count.vue";
const count = ref(0);
provide("provideCount", count);
</script>
<script setup>
import { inject } from "vue";
const provideCount = inject("provideCount");
</script>
<template>
<h1>provideCount: {{ provideCount }}</h1>
</template>
npm install vue-router@4
// main.js
const router = createRouter({})
const router = createRouter({
history: createWebHistory(), // /user, /user/1 등의 url을 사용하기 위해 사용
// history: createWebHashHistory(), 해쉬는 잘 안씀
});
import Home from "./pages/Home.vue";
import About from "./pages/About.vue";
const router = createRouter({
routes: [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/about",
name: "About",
component: About
},
], // 라우트 정의
});
// router -> index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "./pages/Home.vue";
import About from "./pages/About.vue";
const router = createRouter({
history: createWebHistory(), // /user, /user/1 등의 url을 사용하기 위해 사용
// history: createWebHashHistory(), 해쉬는 잘 안씀
routes: [
{
path: "/",
name: "Home",
component: Home
},
{
path: "/about",
name: "About",
component: About
},
], // 라우트 정의
});
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
<script setup></script>
<template>
<RouterView></RouterView>
</template>
<script setup></script>
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<RouterView></RouterView>
</template>
routes: [
{
path: "/",
name: "Home",
component: Home, // 정적 임포트 방식
},
{
path: "/about",
name: "About",
component: About, // 정적 임포트 방식
},
], // 라우트 정의
이 정적 임포트 방식엔 단점이 있는데 바로 필요없는 페이지에 대한 컴포넌트도 모두 불러온다는 것이다.
내가 home 페이지에 접속했지만 사실 about 컴포넌트 까지 네트워크 요청에 요청이 가있다. === 쓰지 않는 페이지 까지 요청해서 불러와 리소스 낭비를 하고 있다.
하지만 리소스를 미리 불러와 해당 페이지로 이동이 빠르기 때문에 상황에 따라선 이런 정적 임포트 방식이 더 좋을 수 있다.
동적임포트로 적용하려면 콜백함수와 import함수를 사용하면된다.
routes: [
{
path: "/",
name: "Home",
component: Home, // 정적 임포트 방식
},
{
path: "/about",
name: "About",
component: () => import("@/pages/About.vue"), // 동적 임포트 방식
},
], // 라우트 정의
이렇게 하면 필요한 페이지의 컴포넌트만 불러올 수 있다.
단점으론 페이지 이동 시 로딩 시간이 필요하다는 점이 있다.
정적 임포트는 home에, 동적 그 외엔 전부 동적 임포트를 하는게 좋다.
{
path: "/about/:id",
name: "About",
component: () => import("@/pages/About.vue"),
},
<RouterLink to="/about/1">About</RouterLink>
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
const { id, user } = route.params;
</script>
<template>
<div>{{ id }}</div>
</template>
<script setup>
import { useRoute } from "vue-router";
const route = useRoute();
const { id, user } = route.params;
const { lang } = route.query;
</script>
<template>
<div>{{ id }}</div>
<div>{{ lang }}</div>
</template>
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: () => import("@/pages/NotFound.vue"),
}
이제 이상한 페이지로 이동하면 NotFound 컴포넌트를 보여준다(물론 NotFound 컴포넌트 만들어야 함)
/user-:after 이렇게 :를 /user- 가 입력된 뒤에 들어가게 되면 user- 은 고정 path가 되고 그 뒤에 값부터 쿼리 파라미터가 된다. - 이를 잘 활용하면 좋타
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<template>
<div>NotFound</div>
<button @click="router.push('/')">홈으로 가기</button>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
</script>
<template>
<div>NotFound</div>
<button @click="router.replace('/')">홈으로 가기</button> // 404페이지에 접속한 기록이 남지 않는다.
</template>
routes: [
{
path: "/",
name: "Home",
component: Home, // 정적 임포트 방식
},
{
path: "/about/:id",
name: "About",
component: () => import("@/pages/About.vue"), // 동적 임포트 방식(해당 url로 접근할때 컴포넌트를 받아옴)
},
{
path: "/user/:id",
name: "User",
component: () => import("@/pages/User.vue"),
},
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: () => import("@/pages/NotFound.vue"),
},
], // 라우트 정의
<RouterLink :to="{ name: 'About' }">About</RouterLink>
<RouterLink :to="{ name: 'About', params: { id: 1 } }">About</RouterLink>
<RouterLink
:to="{ name: 'About', params: { id: 1 }, query: { lang: 'ko' } }"
>About</RouterLink
>
{
path: "/product",
name: "Product",
component: () => import("@/pages/Product.vue"),
children: [
{
path: "info",
name: "ProductInfo",
component: () => import("@/pages/ProductItem.vue"),
},
{
path: ":item", // path를 :item으로 설정하면 params까지 사용 가능하다.
name: "ProductItem",
component: () => import("@/pages/ProductItem.vue"),
},
],
},
// product.vue
<script setup></script>
<template>
<div>product</div>
<RouterView />
</template>
{
path: "/dashboard",
name: "Dashboard",
components: {
header: () => import("@/components/DashboardHeader.vue"),
default: () => import("@/pages/Dashboard.vue"),
footer: () => import("@/components/DashboardFooter.vue"),
},
},
<script setup></script>
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink
:to="{
name: 'About',
params: { id: 1, more: 'hi' },
query: { lang: 'ko' },
}"
>About</RouterLink
>
<RouterLink
:to="{
name: 'Product',
params: { item: '아이템' },
}"
>ProductItem</RouterLink
>
</nav>
<RouterView name="header"></RouterView>
<RouterView></RouterView>
<RouterView name="footer"></RouterView>
</template>
{
path: "/user/:id",
name: "User",
redirect: ()=> "/about-new" // 리다이렉트
},
{
path: "/dashboard",
name: "Dashboard",
components: {
header: () => import("@/components/DashboardHeader.vue"),
default: () => import("@/pages/Dashboard.vue"),
footer: () => import("@/components/DashboardFooter.vue"),
},
alias: "/dash", // dash 입력시에도 페이지 이동 가능
},
{
path: "/dashboard",
name: "Dashboard",
components: {
header: () => import("@/components/DashboardHeader.vue"),
default: () => import("@/pages/Dashboard.vue"),
footer: () => import("@/components/DashboardFooter.vue"),
},
alias: ["/dash/:id", "/ddaasshh/:iidd"],
},
vue를 create로 설치할때 3번째 항목에서 router설치하겠냐는 문구에 yes라고 답하면 라우터를 설치해주고 라우팅 초기세팅(index.js 생성, main.js 초기설정)을 다 해준다.
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/AboutView.vue"),
},
],
});
router.beforeEach((to, from, next) => {
//매개변수에 현재값(to), 이전값(from), 다음값(next)이 담긴다.
const goToNext = false;
if(goToNext){
next()
} else {
next(false) // 다음 페이지로 전환할 수 없게 막음
}
});
router.beforeResolve((to, from, next) => {
//매개변수에 현재값(to), 이전값(from), 다음값(next)이 담긴다.
console.log(to);
console.log(from);
});
router.afterEach((to, from, failer) => {
//매개변수에 현재값(to), 이전값(from), 다음값(next)이 담긴다.
console.log(to);
console.log(from)
console.log(failer) // undefined면 성공, 에러에 대한 정보객체가 담겨있으면 실패
});
{
path: "/about",
name: "about",
component: () => import("../views/AboutView.vue"),
beforeEnter: (_,__,next) => { // 한 가지 라우트에만 넣어서 사용 가능
const isAuthenticated = true
if(!isAuthenticated){
next("/")
}
next()
}
},
<script setup>
import { onBeforeRouteLeave } from "vue-router";
const text = ref("");
onBeforeRouteLeave((to, from) => {
console.log("라우트를 이동하기 전");
console.log(to);
console.log(from);
if (text.value) {
const answer = window.confirm("정말 떠나시겠습니까?");
if (answer) {
return true;
} else {
return false;
}
}
});
onBeforeRouteUpdate((to, from) => {
console.log("라우트가 업데이트 될 때");
console.log(to);
console.log(from);
});
</script>
<template>
<div>
<h1>{{ text }}</h1>
<textarea v-model="text"></textarea>
</div>
</template>
{
path: "/",
name: "Home",
component: HomeView,
meta: {
title: "Home"
}
},
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Home",
component: HomeView,
meta: {
title: "Home",
},
},
],
scrollBehavior(to, from, savedPosition) {
return { top: 0 }; // 위치값을 페이지 넘어갈 때 마다 0으로 설정
},
});
scrollBehavior(to, from, savedPosition) {
console.log(to.hash);
if (to.hash) return { el: to.hash };
return { top: 0 };
},
scrollBehavior(to, from, savedPosition) {
console.log(to.hash);
if (to.hash) return { el: to.hash, behavier: "smooth"};
return { top: 0 };
},
npm install pinia
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount("#app");
import { defineStore } from "pinia";
import { computed, ref } from "vue";
export const useCountStore = defineStore("countStore", () => {
const count = ref(0);
const increment = () => count.value++;
const doubleCount = computed(() => {
return count.value * 2;
});
return {
count,
increment,
doubleCount,
};
});
이건 좋네 많이
이제 사용할 컴포넌트로 와서 export한 useCountStore를 풀어주면 끝이다.
<script setup>
import { useCountStore } from "@/stores/countStore";
const countStore = useCountStore();
</script>
<template>
<h2>About view: {{ countStore.count }}</h2>
<h2>About view: {{ countStore.doubleCount }}</h2>
<button @click="countStore.increment">증가</button>
</template>
<script setup>
import { useCountStore } from "@/stores/countStore";
import { storeToRefs } from "pinia";
const countStore = useCountStore();
const { count, doubleCount } = storeToRefs(countStore);
const { increment } = countStore;
</script>
<template>
<h2>About view: {{ count }}</h2>
<h2>About view: {{ doubleCount }}</h2>
<button @click="increment">증가</button>
</template>
과제 = props드릴링 방식으로 todo 구현 + pinia 사용으로 todo구현 (둘 다 컴포지션 API 사용)
npm i pinia-plugin-persistedstate
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore(
'main',
() => {
const someState = ref('hello pinia')
return { someState }
},
{
persist: true,
},
)
npm install json-server@0.17.0 --save-dev
"scripts": {
"db": "json-server --watch src/db.json",
"db:delay": "json-server --watch src/db.json --delay 2000"
},
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "typicode"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "typicode"
}
}
npm run db
-url에 도메인/post로 접근시 db.json의 json 파일이 보이면 성공
<script setup>
import { onBeforeMount, ref } from "vue";
// GET, POST, PUT/PATCH, DELETE
const posts = ref(null);
onBeforeMount(async () => {
const res = await fetch(`http://localhost:3000/posts`);
const data = await res.json();
posts.value = data;
});
const handlePost = async () => {
try {
const res = await fetch(`http://localhost:3000/posts`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "new Data",
author: "sucoding",
}),
});
if (!res.ok) throw new Error("데이터 전송에 실패했습니다.");
if (res.ok) {
const data = await res.json();
posts.value.push(data);
}
} catch (error) {
console.log(error);
}
};
const handlePut = async () => {
try {
const res = await fetch(`http://localhost:3000/posts/2`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "new Data -PUT",
author: "sucoding -PUT",
}),
});
if (!res.ok) throw new Error("데이터 변경에 실패했습니다.");
if (res.ok) {
const data = await res.json();
posts.value = posts.value.map((post) =>
post.id === data.id ? { ...data } : post
);
}
} catch (error) {
console.log(error);
}
};
const handlePatch = async () => {
try {
const res = await fetch(`http://localhost:3000/posts/3`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "new Data -PATCHa",
author: "sucoding -PATCH",
}),
});
if (!res.ok) throw new Error("데이터 변경에 실패했습니다.");
if (res.ok) {
const data = await res.json();
posts.value = posts.value.map((post) =>
post.id === data.id ? { ...data } : post
);
}
} catch (error) {
console.log(error);
}
};
const handleDelete = async (id) => {
try {
const res = await fetch(`http://localhost:3000/posts/${id}`, {
method: "DELETE",
});
if (!res.ok) throw new Error("데이터 삭제에 실패했습니다.");
if (res.ok) {
posts.value = posts.value.filter((post) => post.id !== id);
}
} catch (error) {
console.log(error);
}
};
</script>
<template>
<div>Fetch Basic</div>
<button @click="handlePost()">POST</button>
<button @click="handlePut()">PUT</button>
<button @click="handlePatch()">PATCH</button>
<button @click="handleDelete(4)">DELETE</button>
<pre>{{ posts }}</pre>
</template>
가끔씩 백이 res에 응답값을 담지 않고 보내는 경우가 있는데 그거 없으면 불필요한 api콜을 2번 해야 할 수 있으므로 백엔드에게 요청을 해야한다.
axios의 경우 status로 전송되는 데이터의 값(200, 400, 401 등)을 백엔드가 설정해서 보낸다.
백엔드와 status의 숫자값을 상호의논한 후에 사용해야한다.
tanstackQuery를 쓰면 여러가지 서버를 관리할 수 있는 기능들이 있어서 대규모 프로젝트에서 axios와 fetch등과 더불어? 쓰는데 워낙 방대한 내용이라 axios와 fetch부터 잘 사용해보자
api 사용시 확인해야할 오류
서버에 접근조차 못했을때
서버에 접근했지만 적절한 응답을 못 받을때
suspense기능
우선 vue에선 await을 setup script 내에서 자유롭게 사용 가능하다.
const data = await fetch("http://localhost:3000/posts")
const SuspenseWithComp = defineAsyncComponent(() => {
import("@/components/SuspenseBasic.vue");
});
리프레시 토큰은 자바스크립트로 조작 못하게 네트워크에서 관리한다?
로그인 시에 백엔드 개발자가 리프레시토큰이 바로 토큰값에 들어갈 수 있도록 조치해줘야 한다.
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import { useAuthStore } from "@/stores/auth";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/login",
name: "login",
component: () => import("../views/Login.vue"),
meta: {
requireAuth: false,
},
},
{
path: "/register",
name: "register",
component: () => import("../views/RegisterView.vue"),
meta: {
requireAuth: false,
},
},
{
path: "/user",
name: "user",
component: () => import("../views/UserView.vue"),
meta: {
requireAuth: true,
},
},
],
});
// 라우터 가드
router.beforeEach((to, _, next) => {
const authStore = useAuthStore();
if (to.meta?.requireAuth && !authStore.isLoggedIn) {
// 너가 접근할 페이지의 requireAuth가 true이고 로그인 되어있지 않다면 로그인 페이지로 이동해라.
next("/login");
} else if (to.meta?.requireAuth === false && authStore.isLoggedIn) {
// 너가 접근할 페이지의 requireAuth가 false이고 로그인 되어있다면 홈홈 페이지로 이동해라.
next("/");
}
next();
});