Vue 문법 정리 3 / 컴포넌트 정의 방법 및 사용법
Vue 컴포넌트 정의 방법
문자열 템플릿 방식
// HTML 내부 script에 컴포넌트 정의 시
const App = {
template: `
// HTML 코드 작성
`
}
// js 파일로 컴포넌트 정의 시
export default {
template: `
// HTML 코드 작성
`
}
HTML에서 Vue 컴포넌트를 정의하고, script에서 벡틱(`) 등으로 감싼 문자열로 템플릿을 작성하는 방식입니다.
코드가 길어지면 가독성이 떨어지고 관리가 어려워서 실무에서 잘 사용되지는 않습니다.
Single File Component (SFC) 방식
// vue 파일로 컴포넌트 정의 시
<template>
// HTML 코드 작성
</template>
<script>
export default {
// Javascript 코드 작성
};
</script>
<style>
// CSS 작성
// CSS import 가능
@import './assets/CSS명.css';
</style>
.vue 파일에 template, script, style 태그를 구분해 Vue 컴포넌트를 정의하는 가독성 좋은 방식입니다.
실무에서 컴포넌트 정의 시 일반적으로 사용하는 방법입니다.
Vue SFC 특수 속성
script setup |
setup() 함수 작성 및 data변수 return 없이 바로 템플릿에서 사용 가능합니다. 자동으로 컴포넌트 setup 역할을 수행합니다. |
style scoped |
현재 컴포넌트에만 CSS가 적용되어 다른 컴포넌트와 스타일 충돌을 방지합니다. 외부 컴포넌트에는 해당 CSS가 적용되지 않아서 사용할 수 없습니다. CSS 사용 시 클래스명이 data-v-7c185b5e 등 고유한 선택자로 변환됩니다. |
style module |
CSS가 모듈 형태로 컴파일됩니다. 컴포넌트 내부에서는 $style 객체로 CSS 클래스명을 바인딩할 수 있습니다. style module="이름" 이렇게 선언하면 $style 객체 이름을 변경할 수 있습니다. CSS 사용 시 클래스명이 _red_1extt_2 등 고유한 이름으로 변환되어 충돌을 방지합니다. |
Vue 컴포넌트 생성 및 등록 방법
Vue 컴포넌트 import
// js 파일 import 시
import App from "./App.js";
// vue 파일 import 시
import App from "./App.vue";
파일에 작성된 컴포넌트를 const 변수에 import 해서 사용할 수 있습니다.
Vue 파일은 Vite 또는 Vue CLI, Webpack 같은 빌드 도구를 이용해야 import 가능합니다.
Vite는 Node.js 기반 도구이므로 npm으로 프로젝트를 관리해야 사용 가능합니다.
ES 모듈 사용 시 script 설정
<script type="module" src="./src/main.js"></script>
export 한 js 파일을 import 하는 js 파일을 HTML에 추가하면 오류가 날 수 있습니다.
ES 모듈 문법 (import, export) 사용 시 script 태그에 type=”module” 속성을 지정해야 합니다.
Vue 루트 컴포넌트 인스턴스 생성
import App from "./App.vue";
// CDN으로 Vue 설치한 경우
const app = Vue.createApp(App);
// npm으로 Vue 설치한 경우
import { createApp } from 'vue';
const app = createApp(App);
App 루트 컴포넌트는 주로 main.js에 정의합니다.
컴포넌트 전역 등록
import 컴포넌트명 from './components/컴포넌트명.vue';
// 컴포넌트 전역 등록
app.component('컴포넌트명', 컴포넌트명);
app.mount('#app');
app.component 함수로 컴포넌트를 전역 등록하면, Vue 애플리케이션 전체에서 사용 가능합니다.
컴포넌트 전역 등록은 main.js에서 하는 것이 관리하기 편합니다.
컴포넌트 지역 등록
<template>
<div>
<자식컴포넌트명></자식컴포넌트명>
<div>
</template>
<script>
import 자식컴포넌트명 from './자식컴포넌트명.vue'
export default {
components: { // 컴포넌트 지역 등록
자식컴포넌트명
},
setup() {
return { };
}
}
</script>
컴포넌트 지역 등록 시 현재 컴포넌트 영역에서만 사용할 수 있습니다.
자식 컴포넌트 script는 1회, setup 함수는 각 인수턴스 수만큼 실행됩니다.
Vue 컴포넌트 사용법
Props 속성
부모 컴포넌트에서 자식 컴포넌트로 단방향 데이터 전달하는 속성입니다.
props 선언 시 카멜케이스, 자식 컴포넌트로 전달 시 케밥케이스 사용이 권장됩니다.
props 전달하는 부모 컴포넌트 예시
<template>
<자식컴포넌트명 props명1="텍스트" :props명2="data변수명"></자식컴포넌트명>
<div v-for="post in data객체배열명" :key="post.id">
<자식컴포넌트명 v-bind="post"></자식컴포넌트명>
</div>
<자식컴포넌트명 v-model="data변수명"></자식컴포넌트명>
또는
<자식컴포넌트명 :model-value="data변수명" @update:model-value="함수명(자식컴포넌트emits파라미터명)"></자식컴포넌트명>
// 자식컴포넌트에서 props는 'modelValue', emits 이벤트명은 'update:modelValue'로 받을 수 있습니다.
<자식컴포넌트명 v-model:props명="data변수명"></자식컴포넌트명>
// v-model에 전달인자로 props명 지정 시,
// 자식컴포넌트에서 props는 'props명', emits 이벤트명은 'update:props명'으로 받을 수 있습니다.
</template>
<script>
import 자식컴포넌트명 from '@/components/자식컴포넌트명.vue';
import { reactive, ref } from 'vue';
export default {
components: {
자식컴포넌트명
},
setup() {
const data변수명 = ref(숫자);
const data객체배열명 = reactive([
{ id: 1, props명1: "텍스트1", props명2: 숫자1 },
{ id: 2, props명1: "텍스트2", props명2: 숫자2 },
{ id: 3, props명1: "텍스트3", props명2: 숫자3 },
]);
return { data변수명, data객체배열명 }
}
}
</script>
부모 컴포넌트 템플릿에 자식 컴포넌트 선언 시 props 전달하는 예시입니다.
자식 컴포넌트에서는 props (readonly) 값을 변경할 수 없습니다.
props 전달받은 자식 컴포넌트 예시
<template>
{{ props명1 }} {{ props명2 }} {{ isSmallNum }}
</template>
<script>
import { computed } from 'vue';
export default {
// 배열 형태로 정의
props: [ 'props명1', 'props명2' ]
또는
// 객체 형태로 정의 (권장)
props: {
props명1: String,
props명2: {
type: Number,
required: true,
default: 100,
validator: (value) => {
return 0 < value && value < 1000;
}
},
props명3: {
type: Object,
default: () => {
return { 기본값키 : '기본값' };
}
}
}
setup(props) {
// props 가공
const isSmallNum = computed(() => {
return props.props명2 < 50 ? '작다' : '크다'
});
return { isSmallNum };
}
}
</script>
자식 컴포넌트 스크립트에서 props 받아서 사용하는 예시입니다.
emit 기능
자식 컴포넌트에서 부모 컴포넌트로 props 변경 요청 등 이벤트를 발생시키는 기능입니다.
emit 이벤트 발생시키는 자식 컴포넌트 예시
<template>
<button @click="함수명">버튼</button>
또는
<button @click="$emit('이벤트명', 파라미터변수)">버튼</button>
</template>
<script setup>
// 이벤트명 배열 선언
const emit = defineEmits(['이벤트명']);
또는
// 이벤트명: 이벤트 유효성검사 객체 선언
const emit = defineEmits({
'이벤트명': (파라미터변수1, 파라미터변수2) => {
if (!파라미터변수1) {
return false;
} else if (!파라미터변수2) {
return false;
}
return true;
}
});
// 이벤트 발생시키는 함수 정의
function 함수명() {
const 파라미터변수1 = 파라미터값;
const 파라미터변수2 = 파라미터값;
emit('이벤트명', 파라미터변수1, 파라미터변수2);
}
</script>
Options API의 this.$emit, Composition API의 context.emit 방식보다 defineEmits 사용이 권장됩니다.
이벤트 선언 없이도 emit 이벤트 호출은 정상 작동합니다. 이벤트 선언은 선택사항입니다.
emit 이벤트 처리하는 부모 컴포넌트 예시
<template>
<Child @이벤트명="함수명" />
</template>
<script setup>
import Child from './자식컴포넌트명.vue';
function 함수명(파라미터명1, 파라미터명2) {
console.log('자식 컴포넌트가 보낸 파라미터1:', 파라미터명1);
console.log('자식 컴포넌트가 보낸 파라미터2:', 파라미터명2);
}
</script>
자식 컴포넌트 emit 이벤트명은 부모 컴포넌트에서 케밥 케이스로 변환해서 받는 것을 권장합니다.
Non-Props 속성 (fallthrough 속성)
부모 컴포넌트에서 자식 컴포넌트에 전달하였으나, 자식 컴포넌트가 선언하지 않은 속성 또는 이벤트입니다.
Non-Props 속성들은 자식 template의 최상위 요소에 자동 상속됩니다.
Non-Props 속성 예시
class | 이미 최상위 요소에 class 속성이 있는 경우에는 병합됩니디. |
style | 이미 최상위 요소에 style 속성이 있는 경우에는 병합됩니디. |
id | 이미 최상위 요소에 id 속성이 있는 경우에는 부모에서 보낸 id로 덮어쓰기 됩니다. |
이벤트 리스너 (@click 등) |
크롬 F12 개발자 도구 > Elements 탭 > 최상위 요소 선택 > Event Listeners 탭 > Ancestors All 체크 해제 현재 요소에 직접 연결된 click 이벤트를 확인할 수 있습니다. remove 버튼 클릭 시 이벤트 삭제 테스트도 가능합니다. |
Non-Props 속성 자동상속 비활성화 처리
<template>
<div>
// attrs 객체로 다중 속성 바인딩
<button type="button" v-bind="$attrs">버튼</button>
</div>
</template>
<script>
export default {
// 최상위 요소 자동상속 비활성화
inheritAttrs: false,
setup(props, context) {
console.log(context.attrs);
return {};
}
}
<script>
자식 컴포넌트에서 inheritAttrs: false 옵션을 설정하면,
Non-Props 속성 최상위 요소 자동상속을 비활성화 할 수 있습니다.
context.attrs 객체를 사용해서 원하는 DOM 요소에 수동 상속하면 됩니다.
Vue 2에서 속성은 &attrs 객체, 이벤트는 listeners 객체에 포함하였으나
Vew 3는 listeners 객체가 삭제되어 모두 &attrs 객체에 포함합니다.
Provide, Inject
Props를 깊은 자식 컴포넌트까지 계속 전달하는 Prop Drilling 문제를 보완하는 기능입니다.
Provide, Inject를 사용하면 부모 컴포넌트가 깊은 자식 컴포넌트에 직접 데이터를 전달할 수 있습니다.
Provide, Inject 사용 예시
// 부모 컴포넌트 예시
<template>
<자식컴포넌트명></자식컴포넌트명>
</template>
<script>
import { ref, provide, readonly } from 'vue';
import 자식컴포넌트명 from './자식컴포넌트명.vue';
export default {
components: {
자식컴포넌트명,
},
setup() {
const count = ref(0);
const increment = () => count.value++;
provide('키1', 값1);
provide('키2', 값2);
// 변수 변경용 함수 함께 제공
provide('키3', {
count: readonly(count),
increment
});
return {};
},
}
</script>
// 깊은 자식 컴포넌트 예시
<template>
<div>{{ data변수명1 }}, {{ data변수명2 }}, {{ count }}</div>
</template>
<script>
import { inject } from 'vue';
export default {
setup() {
const data변수명1 = inject('키1');
const data변수명2 = inject('키2', '기본값');
const { count, increment } = inject('키3');
// 키3 상태 변경 시 사용
increment();
return { data변수명1, data변수명2, count };
}
}
</script>
부모 컴포넌트에서 provide 함수로 값을 제공하고, 깊은 자식 컴포넌트에서 Inject 함수로 가져올 수 있습니다.
Inject로 받은 변수의 상태는 부모가 Provid 한 함수로 변경하는 것이 유지보수에 용이합니다.
App-level Provide 예시
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.provide('키', '전달 데이터');
app.mount('#app');
최상위 App 컴포넌트를 생성하는 main.js 스크립트에서 모든 하위 컴포넌트에 데이터를 제공할 수 있습니다.
Vue 3의 app.provide 함수는 Vue2의 globalProperties 또는 Vue.prototype 단점을 개선한 방식입니다.
script setup 속성
<script setup>
import { useSlots, useAttrs } from 'vue';
import 자식컴포넌트명 from '@/components/자식컴포넌트명.vue';
const props = defineProps({
키: {
type: String,
default: '기본값' // 필수X
},
});
const emit = defineEmits(['이벤트명1', '이벤트명2']);
const count = ref(0);
function increment() {
count.value++;
}
const slots = useSlots();
const attrs = useAttrs();
</script>
위 간결한 script setup 코드는 아래 코드와 동일한 기능을 합니다.
defineProps, defineEmits는 script setup 전용 컴파일러 매크로 함수입니다.
<script>
import { ref } from 'vue';
import 자식컴포넌트명 from '@/components/자식컴포넌트명.vue';
export default {
components: {
자식컴포넌트명,
},
props: {
키: {
type: String,
default: '기본값' // 필수X
},
},
emits: ['이벤트명1', '이벤트명2'],
setup(props, { emit, attrs, slots }) {
const count = ref(0);
function increment() {
count.value++;
}
return { count, increment, attrs, slots };
},
};
</script>
script setup 특징
script setup으로 정의된 부모 컴포넌트는 자식 컴포넌트에서 $parent로 접근할 수 없습니다.
script setup으로 정의된 자식 컴포넌트는 부모 컴포넌트에서 template ref로 접근할 수 없습니다.
그러나, defineExpose로 노출한 변수는 참조하는 컴포넌트에서도 접근이 가능합니다.
// 자식 컴포넌트 예시
<script setup>
const secret = '값'; // 부모에서 접근 불가능
const sayHi = () => console.log('안녕!');
defineExpose({
sayHi, // 부모에서 접근 가능
});
</script>
// 부모 컴포넌트 예시
<template>
<자식컴포넌트명 ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue';
import 자식컴포넌트명 from './자식컴포넌트명.vue';
const childRef = ref(null);
onMounted(() => {
console.log(childRef.value.secret) // // 접근 불가능
childRef.value.sayHi() // 접근 가능
});
</script>
위 코드는 script setup으로 정의한 자식 컴포넌트에서 defineExpose 함수를 사용하여 부모 컴포넌트에 변수를 노출하는 예시입니다.
script setup, script 혼용 방법
<script>
// 컴포넌트 첫 import 시 한 번만 실행 (모듈 스코프)
함수명();
export default {
inheritAttrs: false, // template에서 $attrs 수동 바인딩 필요
customOptions: { 옵션명: '값' }
}
</script>
<script setup>
// setup() 내부 로직 : 컴포넌트 인스턴스 생성 시마다 실행
</script>
setup() 밖에서 한 번만 실행되어야 하는 코드, inheritAttrs 옵션, 플러그인으로 활성화된 커스텀 옵션 등 사용 시 script를 함께 선언할 수 있습니다.
비동기 API 호출 방법
npm i axios
프로젝트 경로에서 위 명령어로 axios 통신 모듈 설치 후, script setup 안에서 API 호출이 가능합니다.
<script setup>
import axios from 'axios';
import { onMounted } from 'vue';
const response = await axios('https://서버도메인/URI');
또는
onMounted(() => {
async function callApi() {
const response = await axios.get('https://서버도메인/URI');
}
callApi();
});
</script>