목차
- Options Api 방식
- Composition Api 방식
- v-model 선언 방식
- Computed 선언 방식
- Watch / Prop 선언 방식
- Emit 선언 방식
- Route / Router 선언 방식
- Store 선언 방식
최근에 Vue 2 버전이 2.7 버전을 끝으로 업데이트가 종료된다는 소식을 듣게 되어 Vue 3 버전과 어떤 차이점이 있는지 제가 알고 있는 선에서 공유 드리고자 이 주제로 글을 작성하였습니다.
3버전으로 올라가면서 라이프사이클에도 약간의 변화가 생기긴 했지만 이번 포스팅에서는 사용하는 문법 위주로 소개해보려고 합니다.
Vue 2 에서의 스크립트 작성 방식은 Options API 방식과 Class Component 방식 2 가지가 있습니다.
Options API 가 OOP 를 배경을 두고 만들어졌기 때문에 Class 기반으로 잘 맞는다고 합니다.
그래서 TypeScript를 사용하는 저희는 Class Component 기반으로 프로젝트를 구성하고 있습니다.
1. Options API 방식
Options API 방식은 data
, methods
, mounted
와 같은 속성을 Vue 객체 내에서 사용하여 컴포넌트의 로직을 정의하는 방법을 뜻 합니다.
// Options API
export default {
name: "SampleComponent",
data: () => { return { sample: ''} },
methods: {},
mounted: {},
}
// Class 형 Component
export default class SampleComponent extends Vue {
sample = '';
mounted() {}
}
Vue 객체를 extends 받아서 새로운 Component 객체를 생성하는 방식
2. Composition API 방식 with setup()
Vue 3로 버전이 올라가면서 새롭게 추가된 방식으로 Composition API 방식과 setup() 가 추가되었습니다.
Composition API 는 컴포넌트 내에서 사용하는 특정 기능을 갖는 코드를 유연하게 구성하여 사용할 수 있도록 Vue3 에서 추가된 함수 기반의 API를 의미압니다.
(Vue2에서 사용했던 data(), methods, computed 와 같은 객체 형태에 얽매이지 않음)
Composition API 를 사용하면 setup() 라는 메서드 안에서 자유롭게 코드를 작성할 수 있습니다.
script 내에 setup 지시어를 추가하거나 setup() { } 메서드를 사용하여 코드를 작성하는 방법으로 두 가지가 있습니다.
기존 Vue2에서 사용했던 문법에 setup() 함수만 추가해주면 됩니다.
다만, 이렇게 사용할 경우 반드시 사용된 변수 또는 함수에 대해서 return 으로 내보내줘야 template 에서 사용할 수 있습니다.
<template>
<div>
<p></p>
<button type="button" @click="onClickHandler">Button</button>
</div>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
components: {},
setup() {
// 변수 선언
const sample = ref('sample');
// 함수 선언
const onClickHandler = () => {
console.log(`click`);
}
return { // return 필수!!!!!!!!
sample, onClickHandler
}
}
})
</script>
이 방법을 보완하고자 나온게 script setup 문법입니다.
<template>
<div>
<p></p>
<button type="button" @click="onClickHandler">Button</button>
</div>
</template>
<script lang='ts' setup>
import {ref, Ref} from "vue";
// 필요한 컴포넌트는 단순 import 만 해주면 사용할 수 있다.
const sample: Ref<string> = ref('');
const onClickHandler = () => {
console.log(`click`);
}
// return 이 필요없다.
</script>
script 안에 setup 만 넣어주면 return 문 없이 바로 template 에서 사용할 수 있습니다.
setup 함수 내에서 React 처럼 render 함수
와 JSX
를 지원하고있어 template
없이 컴포넌트를 구성할 수 있다고 하는데 이 방법은 아직 안해봤습니다..ㅎㅎ
컴포지션 API가 추가 로직 재사용 및 코드 구성 이점과 함께 뛰어난 TypeScript 통합을 제공한다는 점을 감안할 때 Vue 3에서 Class API를 더 이상 사용하지 않는 것이 좋다. 라고 합니다.
v-model 선언 방식 차이
Vue2 에서의 방법
Vue 2에서 Data 변수를 선언할 때 data 함수 내부에 선언해주거나 Class 내부에 선언만 해주면 변수 선언이 끝이나게 됩니다.
export default {
data() {
return {
sampleBoolean: true,
sampleId: '',
sampleLists: [],
}
}
}
@Component({})
export default class SampleComponent extends Vue {
sampleBoolean = true;
sampleId = '';
sampleLists = [];
}
Vue3 에서의 방법
1. Ref 를 사용한 방법
Vue3 이전의 Ref는 뷰 템플릿의 DOM 또는 컴포넌트를 가리키는 속성이었지만, Vue3에서는 reactive reference
를 의미합니다.
<script setup lang='ts'>
import { ref, Ref } from "vue";
interface Sample {
id: string;
password: string;
}
const sampleBoolean: Ref<boolean> = ref(true);
const sampleId: Ref<string> = ref('');
const sampleLists: Ref<Sample[]> | Sample[] = ref([]);
</script>
원시형 데이터는 ref
로 이용하여 반응형 데이터를 관리 (Object 와 같은 타입도 선언 가능합니다. <타입의 영향을="" 받지="" 않음="">)타입의>
데이터 접근 방식 : isLoading.value
, recipeId.value
와 같이 .value를 통하여 접근할 수 있습니다.
value 값을 변경하거나 가져올 때도 동일하게 .value로 접근하면 됩니다.
💡 원시형 데이터
boolean, number, string, undefined, null, symbol
2. Reactive를 사용한 방법
<script setup lang='ts'>
import { reactive } from "vue";
interface Sample {
id: string;
password: string;
}
interface State {
sampleBoolean: boolean;
sampleId: string;
sampleLists: Sample[];
}
const state: State = reactive({
sampleBoolean: true,
sampleId: '',
sampleLists: [],
});
// state 객체에서 꺼내서 사용 가능
const { sampleBoolean, sampleId, sampleLists } = state;
// ref 와 다르게 .value로 값을 꺼낼 필요가 없음
const onClickHandler = () => {
alert(`${sampleBoolean} - ${sampleId} - ${sampleLists}`);
}
</script>
참조형 데이터는 Reactive
를 사용
Vue2에서 사용하는 data() 내에 선언하는 방식과 비슷합니다.
💡 참조형 데이터
Array, Object, Map, Set 등
Computed 선언 방식 차이
Vue2 에서의 방법
<script lang="ts">
export default {
computed: {
isLogin: function() {
return this.$store.getters["utilModule/isLogin"];
}
}
}
</script>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
@Component({})
export default class SampleClass extends Vue {
get isLogin() {
return this.$store.getters["utilModule/isLogin"];
}
}
</script>
Vue3 에서의 방법
<script lang="ts" setup>
import { computed, ComputedRef } from "vue";
import store from "@coponents/store";
const isLogin: ComputedRef<boolean> = computed(() => store.getters["utilModule/isLogin"]);
</script>
vue 에서 computed
를 import 받아 사용합니다.
또한 ComputedRef Type을 지원하고 있어 타입을 지정해 줄 수 있습니다.
Watch / Props 선언 방식 차이
Vue2 에서의 방법
<script lang="ts">
import { Prop, Watch, Vue, Components } from 'vue-property-decorator';
@Components({})
export default class ClassComponent extends Vue {
@Prop({ default: false }) readonly isOpen: boolean;
sampleText = '';
@Watch('route')
watchProps() {
this.sampleText = '';
}
}
</script>
Vue3 에서의 방법
<script lang="ts" setup>
import { ref, Ref, defineProps, withDefaults, watch } from "vue";
import {useRoute} from 'vue-router';
const route = useRoute();
const sampleText: Ref<string> = ref('');
const props1 = defineProps({
isOpen: {
type: boolean,
required: true,
}
}) // default 값 없음
const propsDefault = withDefaults(defineProps<{ isOpen: boolean }>(), { isOpen: false })
watch(route, () => sampleText.value = '', { deep: true, immediate: false })
</script>
vue에서 필요한 함수를 import 하여 사용합니다.
defineProps
함수를 사용하여 Props를 선언하고,
withDefaults
함수를 사용하여 Prop의 기본 값을 설정할 수 있습니다.
💡 **deep이나 immediate이란?**
deep: 지정한 속성안에 있으면, 그 속성 또한 감시 대상으로 지정
immediate: 페이지를 읽어들일 때에도 watch를 실행
기본적으로 watch는 지정한 속성만 감시하는데 deep : true
를 줬을 경우 지정한 값의 내부안의 값 또한 감시 대상으로 올립니다.
페이지가 실행되는 시점에는 watch가 동작하지 않습니다.
Emit 선언 방식 차이
Vue2 에서의 방법
<script lang="ts">
import { Vue, Emit, Component } from 'vue-property-decorator';
@Component({})
export default class SampleComponent extends Vue {
isOpen = false;
// 1. Decorator 사용
@Emit('closeModal')
closeMenu() {
return !this.isOpen;
}
// 2. Vue 객체 이용
closeMenu() {
return this.$emit('closeModal', !this.isOpen);
}
}
</script>
Vue3 에서의 방법
<script lang="ts" setup>
import { defineEmits } from "vue";
// emit Event 정의
const emit = defineEmits(['closeModal']);
// 정의한 이벤트 호출
const sendDataToParentComponent = (isOpen: boolean) => {
emit('closeModal', !isOpen);
};
</script>
Router / Route 선언 방식 차이
Vue2 에서의 방법
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({})
export default class SampleComponent {
sampleRouterFunction() {
//router
this.$router.push('/');
// route
const { title } = this.$route.query as { title: string };
}
}
</script>
Vue3 에서의 방법
<script lang="ts" setup>
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const redirect = (): void => {
router.push('/');
}
const { title } = route.query as { title: string };
</script>
useRouter()
useRoute()
훅을 호출하여 라우터 기능을 사용할 수 있습니다.
Store 선언 방식 차이
Vue2 에서의 방법
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({})
export default class SampleComponent extends Vue {
sampleText = '';
mounted() {
this.$store.dispatch('sampleStore/saveText', this.sampleText)
}
}
</script>
Vue3 에서의 방법
<script lang="ts" setup>
import {onMounted, ref, Ref} from "vue";
import store from '@/store';
const sampleText: Ref<string> = ref('');
//mount hook import
onMounted(() => {
store.dispatch("sampleStore/saveText", sampleText.value);
});
</script>
Store import 시 주의할 점
<script lang="ts" setup>
import { useStore } from "vuex";
const store = useStore();
store.dispatch("sampleStore/saveText", 0);
</script>
setup 함수 내에 useStore() 함수를 호출할 경우 콘솔에서 아래와 같은 경고 문구를 볼 수 있습니다.
❗ vue warn inject() can only be used inside setup() or functional components
Vuex 에서 store 객체를 바로 호출하기 보다는 createStore() 이용하여 만들어진 store를 호출하는 방식으로 변경하면 경고 문구는 사라지게 됩니다.
// src > stroe > index.ts
import { createStore } from "vuex";
import createPersistedState from "vuex-persistedstate";
export default createStore({
plugins: [
createPersistedState({
paths: [],
}),
],
modules: {},
});