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>