Vue 3๊ฐ ์ ์ํ๊ฐ ๋์์ต๋๋ค.
์ด ํฌ์คํ ์ Vue 3์์ ์ ์์ผ๋ก ๋์ค๊ฒ ๋ Composition API ๋ฅผ ๋ค๋ฃจ๊ธฐ ๋๋ฌธ์ ์ดํด๊ฐ ์ด๋ ค์ฐ์ ๋ถ๋ค์ ์ด์ ํฌ์คํ ๐ 23.02.10 - Vue 3..! ์ด์ ๋ฒ์ ๊ณผ๋ ๋ฌด์์ด ๋ฌ๋ผ์ก์๊น ์ ๋ณด๊ณ ์์ฃผ์ธ์.
Vue 2์์๋ plugin์ ํ์ฉํ์ฌ ๊ณ ๋ฅผ ์ ์์๋ 2๊ฐ์ง ์ฝ๋ ์์ฑ ๋ฐฉ์ Composition API์ Option API.
์ด๋ฒ์ Vue 3๊ฐ ์ ์ํ ๋๋ฉด์ ๊ธฐ์กด ์ผ๋ฆฌ์ผ ๋จ๊ณ์์์ ๋ด์ฉ๋ณด๋ค ๋ ๋ง์ ๋ถ๋ถ์ด Composition API ๋ฌธ๋ฒ์ผ๋ก ์์ฑ์ด ๋ ๊ฒ์ ํ์ธํ ์๊ฐ ์๋ค.
์ด๋ฒ ํฌ์คํธ๋ Compostion API์ Option API๊ฐ ์ด๋ ํ ํ์์ ๊ตฌ์กฐ๋ก ์ด๋ฃจ์ด์ ธ์๋์ง ์ ๋ฆฌํ๊ณ ์๋ก์ ์ด์ ๋ฐ ์ด๋ ํ ๊ฒฝ์ฐ์ ์ด๋ค ๊ฑธ๋ก ์์ฑํด์ผ ํ๋์ง๋ฅผ ๋ค๋ฃจ๋ ค๊ณ ํ๋ค.
Vue๋ผ๋ ํ๋ ์์ํฌ๋ Single-File Component์ ํ์์ผ๋ก HTML ๊ธฐ๋ฅ์ <template>
, CSS ๊ธฐ๋ฅ์ <style>
๊ทธ๋ฆฌ๊ณ JS์ <script>
ํ๊ทธ์์ ์์ฑํ ์ฝ๋๋ค์ ์ถํ์ ์ปดํ์ผ ์, ๊ฐ๊ฐ์ ํ์ผ๋ก ํ์ฑํ์ฌ ๋ ๋๋งํ๊ฒ ๋๋ ๊ตฌ์กฐ์ด๋ค.
์ฌ๊ธฐ์ <script>
ํ๊ทธ์์ ์ด๋ ํ ํ์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋์ง์ ๋ฐ๋ผ์ Composition API
์ Option API
๋ฐฉ์์ผ๋ก ๋๋๋ค.
ํ๋จ์ ๊ทธ๋ฆผ์ ๋ ๋ฐฉ์์ผ๋ก ์์ฑํ์ ๋์ ์ฐจ์ด๋ฅผ ๋งน๋ชฉ์ ์ผ๋ก ๋ณด์ฌ์ค๋ค.
๋จผ์ ์์ ๊ทธ๋ฆผ์ ๋ณด๋ฉด ๋๊ฐ์ง ์ฝ๋ ์ค๋ฅธ์ชฝ์ ์ฝ๋๋ค์ด ๋ฌด์ธ๊ฐ ์ ๊ฐํ๊ฒ ์ญํ ๊ตฐ์ผ๋ก ์ ๋ฆฌ๋์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. Composition API ๊ฐ ๋์ค๊ฒ ๋ ํฐ ์ด์ ์ด ๋ฐ๋ก ์ด ๋ถ๋ถ์ด๋ค.
Composition API๋ ๊ธฐ์กด Option API ์ ๋ก์ง ์ฌ์ฌ์ฉ ๊ธฐ์ ์ธ mixins
์ ๋จ์ ์ ํด๊ฒฐํ๊ณ ๋ฐ์์ฑ ์์คํ
์ ์ฝ๊ฒ ํตํฉํ๊ธฐ ์ํ ๊ตฌ์กฐ๋ก ์ง์ฌ์ ธ ์์ด ๊นจ๋ํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ํ๋ ์์ํฌ์ ์ญํ ์ ํ๋ค.
Vue 3์์๋ ์ด์ ๋ณด๋ค Typescript์ ํธํ์ฑ์ด ๋์์ก๋ค๊ณ ํ๋ค.
<script setup>
ํ๊ทธ๋ฅผ ํตํ์ฌ ๊ตฌ์ฑ ์์์ ํ
ํ๋ฆฟ์ด ์ฝ๋์ ๋์ผํ ๋ฒ์์ ์ธ๋ผ์ธ๋ ํจ์๋ก ์ปดํ์ผ ๋๊ธฐ์, ์ด์ Option API ์์ this
๋ก ์ ๊ทผํ๋ ๊ณผ์ ์ ์ค์ฌ์ฃผ๊ธฐ์ ๋ณ์ ์ด๋ฆ์ ์์ ํ๊ฒ ๋จ์ถ๋ ์ ์๊ธฐ ๋๋ฌธ์ ๋ ๋์ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๊ฒ ๋๋ค.
์ด Option API
๋ ์ต์
์ ๊ฐ์ฒด๋ฅผ ํ์ฉํ์ฌ ์ฝ๋๋ฅผ ์ค๊ณํ๊ฒ ๋๋๋ฐ data
, methods
, mounted
๋ฑ์ด ์๋ค. ๋ ์์ธํ ์ค๋ช
์ ์ํด ๋ด๊ฐ ๊ทธ๋์ ์์ฑํ๋ ์ฝ๋๋ฅผ ํ๋จ์ ์ฒจ๋ถํ๋ ค๊ณ ํ๋ค. (vue3 ๋ฒ ํ ๋ฒ์ ๋ ์์ฑํ ์ฝ๋๋ผ ์ผ๋ถ ๋ถํธํ ๋ถ๋ถ์ด ์์ ์๋ ์์ต๋๋ค.)
<template>
<!-- inactive - active ํ ํ ์ปจํ
์ด๋ -->
<div class="selectContainer__total">
<!-- ํ์ฑํ ๋๊ธฐ ์ ์ ์ปจํ
์ด๋ -->
<div
class="selectContainer__inactive"
@click="Active()"
:style="{
transition: $store.state.isActive ? '4s' : '4s',
opacity: $store.state.isActive ? 0 : 1,
display: $store.state.isActive ? 'none' : 'inline',
}">
<!-- ์ด๊ธฐ๊ฐ false -->
<div class="flightContainer">
<span>
<h1>{{ shortDep }}</h1>
{{ getAirport_dep }}
</span>
<div>
<img
src="./airplane2.png"
alt="๋นํ๊ธฐ"
:style="airplane_img_inactive" />
</div>
<span>
<h1>{{ shortArr }}</h1>
{{ getAirport_arr }}
</span>
</div>
...
</div>
</div>
</template>
<script>
import Button from "../UI/UI/neumorphism/button/Button.vue";
import ToggleButton from "../UI/UI/neumorphism/toggle-button/ToggleButton.vue";
import Datepicker from "vue3-datepicker";
import { mapState, mapGetters, mapActions } from "vuex";
import { defineComponent, ref } from "vue";
// eslint-disable-next-line no-unused-vars
const picked = ref(new Date());
// eslint-disable-next-line no-unused-vars
import { enUS } from "date-fns/locale";
// eslint-disable-next-line no-unused-vars
import { isSameDay } from "date-fns";
export default defineComponent({
// ๋ฐ์ดํฐ ์ ์ฅํ๋ ๊ณณ {{๋ฐ์ดํฐ๋ฐ์ธ๋ฉ}}
name: "Search-component",
components: {
ToggleButton: ToggleButton,
Button: Button,
Datepicker: Datepicker,
},
data() {
return {
inactiveHeight: "15rem",
picked_from: new Date(),
picked_to: "",
toggle: false,
person: [1, 0, 0],
selectClass: "",
openModal: false,
btnActive: false,
DepartureDate: "September 17th",
ArrivalDate: "October 15th",
airplane_img_inactive: {
width: "30px",
height: "30px",
marginTop: "20px",
marginLeft: "auto",
marginRight: "auto",
},
},
methods: {
Active() {
this.$store.state.isActive = true;
},
unActive() {
this.$store.state.isActive = false;
},
reverse() {
/* shortDep <-> shortArr */
this.$store.state.tem_short = this.$store.state.arrival;
this.$store.state.arrival = this.$store.state.departure;
this.$store.state.departure = this.$store.state.tem_short;
/* getAirport_dep <-> getAirport_arr */
this.$store.state.tem_airport = this.$store.state.airport_arr;
this.$store.state.airport_arr = this.$store.state.airport_dep;
this.$store.state.airport_dep = this.$store.state.tem_airport;
/*.depAirportId <-> arrAirportId */
this.$store.state.tem_code = this.$store.state.arrAirportId;
this.$store.state.arrAirportId = this.$store.state.depAirportId;
this.$store.state.depAirportId = this.$store.state.tem_code;
/* tem_depNm _<-> tem_arrNm */
this.$store.state.tem_DepArrNm = this.$store.state.tem_depNm;
this.$store.state.tem_depNm = this.$store.state.tem_arrNm;
this.$store.state.tem_arrNm = this.$store.state.tem_DepArrNm;
},
// ํ๊ณ์น๋ฅผ ๋์ผ๋ฉด ๋ฒํผ ์ ๊ทธ๊ธฐ
btnIncrease() {
if (document.querySelector("input[id=adult]:checked")) this.person[0]++;
else if (document.querySelector("input[id=kid]:checked"))
this.person[1]++;
else this.person[2]++;
/* Prevent from over 9 */
...
},
computed: {
...mapState(["arrival", "departure", "tem_short", "selectClass"]),
...mapGetters({
shortDep: "shortDep",
shortArr: "shortArr",
getAirport_dep: "getAirport_dep",
getAirport_arr: "getAirport_arr",
}),
...mapActions({
searchInfo: "searchInfo",
}),
/* phoneHeight() {
return window.matchMedia('screen and (max-width: 440px)').matches ? '60vh' : '35rem'
}, */
activeHeight() {
return document.getElementsByClassName("selectContainer__active").height;
},
},
watch: {},
});
</script>
<style lang="scss" scoped>
@import "../UI/scss/main.scss";
...
* {
perspective: 700px;
user-select: none; // prevent block
}
html {
font-size: 16px;
}
.selectContainer {
margin: {
top: 15px;
</style>
degineComponent
๋ผ๋ ๊ฐ์ฒด ์์์ ์ค์ง์ ์ธ data๋ฅผ ๋ค๋ฃจ๋ data()
, ํจ์๋ฅผ ๋ค๋ฃจ๋ methods
, ์บ์ฑ ์ฒ๋ฆฌ๋ computed
๋ฑ์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ ๋ก์ง์ ์ ์ํ๊ฒ๋๋ค. ๋ํ vue์ ๋ผ์ดํ์ฌ์ดํด
์ ํํํ๋ mounted()
, created()
๋ฑ์ผ๋ก ์ง์ ์ ์ธ ํํ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค.
๋ํ Option API์์์ ์ต์
์ผ๋ก ์ ์๋ ์์ฑ๋ค์ Component Instance ๋ฅผ ๊ฐ๋ฅดํค๋ ํจ์ ๋ด๋ถ์ this
๋ก ๋
ธ์ถ์ด ๋๋ค๊ณ ํ๋ค.
์์ methods
์์ data ๊ฐ์ฒด์ ๊ฐ์ ์ฌ์ฉํ ๋๋ this๋ฅผ ์ ๋์ฌ๋ก ์์ผ๋ก, ๊บผ๋ด์ ์ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
Composotion API๋ ์ต์ ์ ์ ์ธํ๋ ๋์ ๊ฐ์ ธ์จ ํจ์๋ฅผ ์ฌ์ฉํ์ฌ Vue๋ฅผ ์์ฑํ๊ฒ ํด์ฃผ๋ API ์์ฑ ๊ธฐ๋ฒ์ด๋ผ๊ณ ํ๋๋ฐ ์๊ธด ๊ฒ์ ๋ณด๋ฉด React์์ ์์ฑํ๋ ๋ฐฉ์๊ณผ ์ ์ฌํ ๊ฒ์ ๋ณผ ์ ์๋ค.
์๋์ ์ฝ๋๋ vue ๊ณต์ ํํ์ด์ง์์ ์ธ์ฉ์ ํด์๋ค.
<script setup>
import { ref, onMounted } from 'vue'
// reactive state
const count = ref(0)
// functions that mutate state and trigger updates
function increment() {
count.value++
}
// lifecycle hooks
onMounted(() => {
console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
์ก์์ผ๋ก ๋ดค์ ๋ ๋ฌ๋ผ์ง ๋ถ๋ถ์ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด ๋ณด์์ ๋..
<script>
ํ๊ทธ๊ฐ <script setup>
ํ๊ทธ๋ก ๋ณ๊ฒฝํ์ง๋ง ์ ํํ๊ฒ ๋์ด๊ฐ๋ ๊ฒ์ด ์ข์ผ๋ ๊ฐ๋ฐ์๋ค์ ์น๊ตฌ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ Composition API ์ ์ ์๋ฅผ ์ดํด๋ณด๋ ค๊ณ ํ๋ค.
์ต์
์ ์ธ์ ํ๋ ๋ฐฉ์์์ ํจ์ ๊ตฌ์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ API ์คํ์ผ๋ก ์์ฑํ๋ ๋ฐฉ์์ ์๋ฏธํ๋ค.
(๊ทธ๋ ์ง๋ง Composition API๋ Vue์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ๊ณ ์ธ๋ถํ๋ ๋ฐ์์ฑ ํจ๋ฌ๋ค์์ ๊ธฐ๋ฐ์ผ๋ก ์์ฑ๋๊ธฐ์ ๋ถ๋ณ์ฑ์ ๊ฐ์กฐํ๋ ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ด ์๋๋ค)
๋ฐ์์ฑ API ๊ฐ ์ถ๊ฐ๋์๋ค.ref()
, reactive()
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์์ฑ ์ํ, ๊ณ์ฐ๋ ์ํ ๋ฐ ๊ฐ์์๋ฅผ ์ง์ ์์ฑํ ์ ์๋ค.
๋ผ์ดํ์ฌ์ดํด ํ
์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค. ex ) onMounted()
, onCreated()
๋ฐ์์ฑ API๋ฅผ ์ฌ์ฉํ๋ ๋์ Vue์ ์์กด์ฑ ์ฃผ์
์์คํ
์ ํ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ provide()
, inject()
์์ฑ
์๋ ์ด Composition API๋ Vue 3 ์์์ ๋ชจ๋ธ ์ด๊ธฐ์ Vue 2 ์ ์ ๋ค์ Vue 2.7๋ก ์
๊ทธ๋ ์ด๋, ํน์ @vue/composition-api
ํ๋ฌ๊ทธ์ธ์ ์ค์นํ์ฌ ์ฌ์ฉํ๋ฉด ๋๋ค๊ณ ํ๋ค.
๊ผญ ๊ทธ๋ ๋ค๋ ๊ฒ์ ์๋๋ผ๊ณ ํ๋ค. Option API์ ํฐ ์ฅ์ ์ ์ค, ์๊ท๋ชจ์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค ๋๋ ์๊ฐ์ ๋ ํ ์ ์๊ฒ๋์ด ํฐ ์ฌ๋์ ๋ฐ๋๋ค๋ ์ ์ด๋ค. ํ์ง๋ง, ๋๊ท๋ชจ ํ๋ก์ ํธ์์๋ ์ฌ๋ฌ ์ฌ์ฌ์ฉ๋๋ ๋ก์ง๋ค์ ๊ฐ์ง์๋ค์ด ๋์ด๋๊ธฐ์ Composition API ๋ฅผ ์ถ์ฒํ๋ค๊ณ ํ๋ค.
๋ฐ๋ผ์ ์ฐ๋ฆฌ์ ํ๋ก์ ํธ์ ๊ท๋ชจ๊ฐ ์ด๋ป๊ฒ ๋ ๊ฒ์ธ์ง ๊ฐ์ ํ๊ณ ์ค๊ณ๋ฅผ ํ๋ ๊ฒ์ด ์ข๋ค
Reference : https://vuejs.org/guide/extras/composition-api-faq.html