npm init vue@latest
vue 설치 시 아래의 옵션을 Yes / No
로 선택하면 설치가 완료됩니다. Yes 선택시 해당하는 모듈이 함께 설치됩니다.
√ Project name: ... vue-project
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / Yes
√ Add Prettier for code formatting? ... No / Yes
설치 완료 후 아래의 명령어를 통해 vue 프로젝트의 모듈을 설치할 수 있습니다.
cd vue-project
npm install
src
├─ main.ts
├─ App.vue (root component)
├─ views
│ └─ AboutView.vue
│ └─ HomeView.vue
└─ router // 라우터 설치시
└─ index.ts
설치가 완료되면 src 디렉토리 내부에 App.vue
와 main.ts
가 존재합니다. App.vue는 루트 컴포넌트이고 main.ts는 뷰 어플리케이션을 시작하기 위한 설정이 포함되어 있습니다.
// main.ts
const app = createApp(App); // App은 루트 컴포넌트
app.mount('#app');
createApp
: 루트 컴포넌트를 등록할 수 있습니다.app.mount('#app')
: createApp
을 호출 후 반환된 객체의 mount
메서드를 호출할때 화면에 렌더링 되기 시작합니다(호출하지 않으면 렌더링 X).Vue 컴포넌트는 template
, script
, style
총 3개의 영역으로 구성되어 있습니다.
<template>
</template>
<script>
</script>
<style>
</style>
템플릿 영역에서는 HTML 태그와 유사한 형태로 코드를 작성할 수 있습니다.
<template>
<div>Hello World</div>
</template>
자바스크립트에서 선언된 데이터를 템플릿 영역에서 바인딩하여 화면에 출력할때는 {{}}
를 사용합니다.
<template>
<div>{{ message }}</div>
</template>
단순히 텍스트를 바인딩할 수 있을 뿐 아니라 표현식 및 함수도 호출할 수 있습니다.
<template>
<div>{{ getMessage() }}</div>
</template>
💡 이 경우 리렌더링 될때마다 함수를 호출하기 때문에 성능상 이점을 가지지 않습니다.
텍스트 바인딩의 경우 최종 결과가 문자열이기 때문에 HTML 코드를 파싱한 결과를 화면에 보여주고 싶은 경우에는 v-html
디렉티브를 사용해야 합니다.
<template>
<div v-html="html"></div>
</template>
HTML 속성에 데이터를 바인딩할 때는 v-bind:속성명
으로 사용할 수 있습니다. 이때 v-bind는 생략할 수 있습니다. 리액트와는 달리 변수를 사용할때도 “”
를 사용합니다.
<template>
<div v-bind:id="hello" :class="className"></div>
</template>
여러개의 속성을 한방에 바인딩 하고 싶은 경우에는 객체를 사용하면 됩니다.
const data = {
id: 'hello',
class: 'hi'
}
<template>
<div v-bind="data"></div>
</template>
💡 이 방법을 통하여 부모에게서 전달받은 Props를 자식 컴포넌트에 전부 전달해야 하는 경우 사용할때 용이합니다.
조건에 따라 렌더링 될지 여부를 결정할 떄 사용됩니다. 값으로는 불리언 타입의 값을 사용할 수 있습니다. 자바스크립트의 if문과 마찬가지로 v-if / v-else-if / v-else
세 가지 디렉티브가 존재합니다.
<template>
<div v-if="loginedIn">로그아웃</div>
<div v-else>로그인</div>
</template>
조건에 따라 화면에 보여질지 여부를 결정할때 사용됩니다. v-if와의 차이점은 v-if는 렌더링 자체가 되지 않지만 v-show는 렌더링은 되지만 display: none
인 상태입니다.
<template>
<div v-show="loginedIn">아이디</div>
</template>
💡 빈번하게 토글 되어야 하는 경우네는 v-show를 사용하는게 좋습니다.
다수의 데이터를 비슷한 템플릿 블록으로 렌더링해야 할떄 사용합니다. 보통 리스트를 렌더링할때 자주 사용됩니다.
대상 타입으로는 Array | Object | number | string | Iterable 가 있습니다.
<template>
<ul>
<li v-for="item in items">{{ item.id }}</li>
</ul>
</template>
<script setup>
을 사용한 방식으로 설명합니다. script 태그에 setup 속성을 추가하면 됩니다.
<script setup>
</script>
setup 속성을 사용하면 스크립트 태그에서 작성된 변수, 함수, import를 별다른 설정없이 템플릿 영역 내부에서 사용이 가능하게 됩니다.
<script setup>
const message = 'Hello World!';
</script>
<template>
<div>{{ message }}</div>
</template>
script 영역안에서 변수를 선언하고 해당 변수의 값을 변경하더라도 리렌더링이 되지 않습니다.
아래의 예제는 div를 클릭 했을 때 메시지 변수의 값을 변경하고 있습니다. 콘솔에서는 변경된 값으로 출력되지만 화면에서는 변경되지 않습니다. 이는 message가 반응형이 아니기 때문입니다.
<script setup>
let message = "Hello World!";
const onClick = () => {
message = "Hi World";
console.log(message);
};
</script>
<template>
<div @click="onClick">{{ message }}</div>
</template>
reactive
는 객체만 반응형 데이터로 만드는데 사용이 가능합니다. 반환값으로 인자로 받은 객체와 동일한 프록시 객체를 반환해줍니다. 그래서 일반적인 객체를 사용하는 것처럼 사용이 가능합니다.
그리고 깊은 감지를 사용하기 때문에 중첩된 상황에서도 감지가 가능합니다.
이제 클릭 시 정상적으로 화면에 반영이 됩니다.
<script setup>
import { reactive } from "vue";
const obj = reactive({ message: "Hello World" });
const onClick = () => {
obj.message = "Hi World";
};
</script>
<template>
<div @click="onClick">{{ obj.message }}</div>
</template>
만약 깊은 감지가 아닌 얕은 감지를 사용하고 싶다면
shallowReactive
를 사용하면 됩니다.
초기화시에 존재하지 않던 속성을 후에 추가하더라도 감지가 됩니다.
<script setup>
import { reactive } from 'vue';
const obj = reactive({});
obj.count = 0;
const onClick = () => {
obj.count++;
};
</script>
<tempate>
<div>
{{ obj.count }}
<button @click="onClick">증가</button>
</div>
</template>
ref
는 객체뿐 아니라 기본 타입에도 반응형을 추가할 수 있습니다. reactive
와 마찬가지로 객체에 반응형을 추가할 시에 깊은 감지가 가능합니다. ref
호출 후 값에 접근하기 위해서는 반환된 객체의 value
를 통해서 가능합니다.
<script setup>
import { ref } from "vue";
const message = ref("Hello World");
const onClick = () => {
message.value = "Hi World";
};
</script>
<template>
<div @click="onClick">{{ message }}</div>
</template>
💡 template 부분에서는 value를 사용하지 않아도 됩니다.
깊은 감지는 피하기 위해서는 shallowRef
를 사용하면 됩니다.
ref
와 reactive
의 차이점은 reactive는 객체만 가능하다는 점입니다. 추가적으로 reactive는 객체 내부 속성의 값을 변경하는 것은 반응형이 유지가 되나, 객체 자체에 값을 재할당 하는 경우에는 반응형으로 사용이 되지 않습니다.
그래서 증가버튼을 누를때까지는 화면에 반영이 되나, 변경을 누른 이후 증가를 눌러도 아무런 반응을 하지 않게 됩니다.
<template>
<div>
{{ obj.count }}
<button @click="onClick">증가</button>
<button @click="onChange">변경</button>
</div>
</template>
<script setup>
import { reactive } from 'vue';
let obj = reactive({ count: 0 });
const onClick = () => {
obj.count++;
};
const onChange = () => {
obj = {
count: 100
};
}
값을 재할당 시 reactive로 감싸주어도 감지가 되지 않는 것을 확인할 수 있습니다.
// ...
const onChange = () => {
obj = reactive({
count: 100
});
}
하지만 ref
를 사용하는 경우에는 위와 같은 경우에도 반응형을 유지하면서 값을 재할당 할 수 있습니다.
<template>
<div>
{{ obj.count }}
<button @click="onClick">증가</button>
<button @click="onChange">변경</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
let obj = ref({ count: 0 });
const onClick = () => {
obj.value.count++;
};
const onChange = () => {
obj.value = {
count: 100
};
}
</script>
결론
getter 접근자처럼 읽기 전용의 ref 객체를 반환합니다. 첫번째 인수로 콜백함수를 전달하며 콜백함수에서 반환 된 값이 ref 객체의 값이 됩니다.
<script setup>
import { ref, computed } from "vue";
let obj = ref({ count: 0 });
const multiple = computed(() => obj.value.count * obj.value.count);
const onClick = () => {
obj.value.count++;
};
</script>
<template>
<div>
{{ multiple }}
<button @click="onClick">증가</button>
</div>
</template>
set
프로퍼티를 사용하여 수정할 수 있는 ref 객체를 생성할 수 있습니다.
<script setup>
import { ref, computed } from "vue";
let obj = ref({ count: 0 });
const multiple = computed({
get: () => obj.value.count * obj.value.count,
set: (value) => {
obj.value.count = value;
},
});
const onClick = () => {
obj.value.count++;
};
const onChange = () => {
multiple.value = obj.value.count - 1;
};
</script>
<template>
<div>
{{ multiple }}
<button @click="onClick">증가</button>
<button @click="onChange">변경</button>
</div>
</template>
computed의 getter는 캐싱기능을 지원합니다. getter에서 사용하고 있는 반응형 값이 변경되지 않는다면 리렌더링이 되더라도 호출이 되지 않습니다. 복잡한 계산을 해야 하는 경우 computed를 사용하면 좋습니다.
아래의 코드를 실행하면 증가 버튼을 눌렀을 때는 콘솔이 출력되지 않고 변경을 눌렀을 때만 콘솔이 출력됩니다.
<script setup>
import { ref, computed } from "vue";
const message = ref("Hello World");
let obj = ref({ count: 0 });
const multiple = computed(() => {
console.log("출력");
return `Kim ${message.value}`;
});
const onClick = () => {
obj.value.count++;
};
const onChnage = () => {
message.value += 1;
};
</script>
<template>
<div>
<p>{{ multiple }}</p>
<p>{{ obj.count }}</p>
<button @click="onClick">증가</button>
<button @click="onChnage">변경</button>
</div>
</template>
style 영역에서는 CSS를 추가할 수 있습니다. 속성으로 scoped를 추가하는 경우 해당 컴포넌트 내에서만 적용이 됩니다. scoped를 사용하지 않는 경우 글로벌하게 적용 됩니다. 사용방법은 기존 css 파일에서 작성하던 방식과 동일합니다.