[TIL 14] 단일 파일 컴포넌트를 이용한 Vue 애플리케이션 개발

nini·2025년 3월 25일

KB IT's Your Life

목록 보기
14/40

단일 파일 컴포넌트를 이용한 Vue 애플리케이션 개발

단일 파일 컴포넌트

Single File Component

  • 컴포넌트 하나를 .vue 파일 하나에 작성
  • 한 파일에 컴포넌트 구성을 위해 템플릿, 스크립트, 스타일을 모두 포함

프로젝트 설정 도구

번들링(Bundling)

  • 여러 모듈을 묶어서 하나 또는 몇개의 모듈 파일로 만드는 과정
  • webpack, vite 등

Vite 기반의 도구

  • npm init vue
    • vite 기반으로 프로젝트 구성
  • .으로 시작하는 파일명은 hidden 파일명(리눅스나 맥에서, 윈도우에서는 X)
  • src 작업 폴더
  • src/main.js 진입점(entry point)
  • App.vue 컴포넌트

  • "type": "module" ES 모듈
  • "build": "vite build" (펙킹, 번들링) 배포본 제작
  • "dev": "vite" 평상시에는 develop으로
  • "dependencies" 실제 배포용에서 필요
  • “devDependencies” 배포할때는 필요없다(개발 과정에서만 필요)

`npm run dev`

모듈 경로 표기법 설정

  • @기호를 src 폴더로 인식시켜 절대 경로 사용

    • vite.config.js에서 설정
    • ‘@’~
  • vscode에서 @기호 인식시키기

    • jsconfig.json에서 실행
    • “@/*” ~

생성된 프로젝트 구조 살펴보기

핵심 파일들-진입점

  • ‘./App.vue’ 컴포넌트에 분리

App.vue 파일 열었더니 설치하라고 나오는 팝업

설치전

설치후

App.vue

import 할 때에는 Helloworld로

<HelloWorld> & <hello-world> 같은 의미(둘다 사용 가능)


단일 파일 컴포넌트

App.vue → 컴포넌트 진입

<template /> 다양한 디렉티브와 보간법을 사용해 컴포넌트가 렌더링할 템플릿 작성

<script /> 컴포넌트 내부에서 사용할 Vue 컴포넌트 내부의 옵션 정의

<style /> 컴포넌트가 사용할 스타일 지정


간단한 단일 컴포넌트 작성과 사용

CheckboxItem 컴포넌트 만들기

  • src/components 하위 파일/디렉토리 삭제
  • src/components/CheckboxItem.vue 파일 추가
<template>
  <li><input type="checkbox" v-model="checked" />옵션1</li>
</template>

<script>
export default {
  name: 'CheckboxItem',
  data() {
    return {
      checked: false,
    };
  },
};
</script>
  • src/main.js 수정
    • 맨 위 import~ 지우기

컴포넌트를 사용하기 위해선 먼저 등록을!

  • 전역 방법
    • 한번만 등록하면 어디에서든 바로 사용 가능
    • 쓰지도 않는게 import 돼서 파일이 커질 수 있음(사용하지 않는 컴포넌트에서도 포함되므로 패키징시 파일이 커질 수 있음)
      • src/main.js에서 만든 루트 Vue 인스턴스에 등록

        import CheckboxItem from './components/CheckboxItem.vue';
        
        createApp(App)
        .component('CheckboxItem', CheckboxItem)
        .mount('#app');
  • 지역 방법(권장)
    • 사용하는 컴포넌트내에서 매번 컴포넌트 등록
      • 사용할 컴포넌트 import

      • components 옵션에 해당 컴포넌트 지정

        <script>
        import CheckboxItem from './components/CheckboxItem.vue';
        
        export default {
          name: 'App',
          components: { CheckboxItem }, // 지역컴포넌트
        };
        </script>
      • src/App.vue

        <CheckboxItem /> 사용자 정의 태그로 사용

        <template>
          <div>
            <h2>App 컴포넌트</h2>
            <hr />
            <ul>
              <CheckboxItem />
              <CheckboxItem />
              <CheckboxItem />
              <CheckboxItem />
            </ul>
          </div>
        </template>

❗️http://localhost:XXXX 링크 이동했을 때 아래와 같은 에러가?

Vue의 Single File Component(SFC)에서는 <template> 요소가 하나만 포함되어야 한다. 그렇지 않으면 "Single file component can contain only one <template> element" 오류가 발생


컴포넌트의 조합

  • 복잡한 화면의 Vue 애플리케이션
    • 여러 컴포넌트들을 조합하여 개발
    • 컴포넌트들은 부모-자식 관계로 트리 구조를 형성
    • 부모 컴포넌트가 자식 컴포넌트를 포함하는 형태

여러 컴포넌트들을 조합하여 애플리케이션을 개발하면 컴포넌트 간의 정보의 전달이 필요하다. 이러한 정보의 전달은 속성(props)과 이벤트(event)를 이용한다.
컴포넌트들은 속성(Props)을 통해서 자식 컴포넌트로 정보를 전달할 수 있다. 전달 방향은 부모에서 자식으로만 향한다.
자식 컴포넌트에서 부모 컴포넌트로 정보를 전달할 때는 이벤트를 이용한다. 자식 컴포넌트는 부모 컴포넌트로 이벤트를 발신(emit)할 수 있다. 자식 컴포넌트에서 사용자 정의 이벤트를 정의하고 이벤트를 발생시키면 부모 컴포넌트에서 이벤트 핸들러 메서드를 호출하도록 작성한다. 속성 전달과 이벤트 발신이 부모-자식 컴포넌트 간의 정보를 전달하는 기본적인 방법이다.


속성

속성을 이용한 정보 전달

  • 자식 컴포넌트는 props 옵션으로 속성 정의
  • 부모 컴포넌트는 v-bind를 이용해 자식 컴포넌트의 속성에 정보를 전달
  • 속성으로 전달받은 데이터는 변경 불가!! → 읽기 전용(read only)
  • 부모에서 속성 값을 변경 시 → 자식은 자동으로 다시 렌더링
<template>
  <li><input type="checkbox" :checked="checked" />{{ name }}</li>
</template>

<!-- 속성을 이용한 정보 전달 -->
<script>
export default {
  name: 'CheckboxItem',
  props: ['name', 'checked'], // 속성명을 배열로 정의
  // => props 옵션으로 전달받을 속성을 정의
  // 부모 컴포넌트로부터 속성을 전달받도록! (속성을 이용한 정보 전달)
};
</scrip
<CheckboxItem
	v-for="idol in idols"
	:key="idol.id"
	:name="idol.name"
	:checked="idol.checked"
/>

:name="idol.name"
:checked="idol.checked"
<!-- 개별적인 속성 값 하나하나를 v-bind 디렉티브로 전달-->
<template>
  <li><input type="checkbox" :checked="idol.checked" />{{ idol.name }}</li>
</template>

<!-- 속성을 이용해 객체 전달하기(기본 타입의 값이 아닌 객체나 배열을 전달할 수 있다!) -->
<script>
export default {
  name: 'CheckboxItem2',
  props: ['idol'],
};
</script>
 
<!-- name, checked 속성 대신에 idol 이라는 속성을 전달받도록 변경 -> 속성으로 객체를 전달받을 것 -->
<CheckboxItem v-for="idol in idols" :key="idol.id" :idol="idol" />

속성 변경

  • 기본값 속성

    • 자식 컴포넌트에서 수정 시 에러 발생
    • 숫자, boolean, 문자열
  • 참조 타입 속성

    • 참조 자체를 수정 시 에러 발생
    • 참조 객체의 내부 속성값 변경은 허용 → 비권장

속성(props)으로 전달받은 값은 읽기 전용의 값
→ 기본 타입(primitive type)인 경우 자식 컴포넌트에서 변경할 수 없으며, 객체나 배열과 같은 참조 타입인 경우라도 변경할 수는 있지만 변경하지 않을 것을 권장
→ 속성의 목적은 자식 컴포넌트가 필요한 데이터를 속성으로 전달받아 UI를 렌더링하기 위함

객체를 속성으로 전달하는 경우는 속성을 변경할 수 있다.
→ 객체나 배열은 참조형이기 때문에 객체, 배열의 메모리 주소가 바뀌지 않고 내부 속성만 변경이 되는 경우라면 속성으로 전달한 값은 바뀌지 않은 것으로 간주되기 때문에 변경할 수 있다. 하지만 속성의 본래 목적에 위배되는 것이므로 권장하지 않는다.


속성의 유효성 검증

  • 속성에 대한 엄격한 유효성 검증 필요시 → 배열이 아닌 객체 형태로 속성을 정의
<!-- 속성의 유효성 검증 -->
<template>
  <li><input type="checkbox" :checked="checked" />{{ id }} - {{ name }}</li>
</template>

<script>
export default {
  name: 'CheckboxItem',
  props: {
    id: [Number, String], // Number 또는 String 타입
    name: String, // String 타입
    checked: {
      type: Boolean, // Boolean 타입
      required: false, // 옵션
      default: false, // 생략시, 기본값은 false
    },
  },
};
</script>
<template>
  <div>
    <h2>관심있는 K-POP 가수?</h2>
    <hr />
    <ul>
      <CheckboxItem
        v-for="idol in idols"
        :key="idol.id"
        :id="idol.id"
        :name="idol.name"
        :checked="idol.checked"
      />
    </ul>
  </div>
</template>

<script>
import CheckboxItem from './components/CheckboxItem3.vue';
export default {
  name: 'App',
  components: { CheckboxItem },
  data() {
    return {
      idols: [
        // { id: 1, name: 'BTS', checked: true },
        // { id: 2, name: 'Black Pink' },
        // { id: 3, name: 'EXO' },
        // { id: 4, name: 'ITZY' },
        // checked 값이 없는 아이템 존재 => checked 속성이 전달되지 않으면 기본값인 false를 부여하도록 CheckboxItem3 컴포넌트 예제에서 지정
				
				// 유효성에 어긋나는 값을 속성으로 전달
        { id: 1, name: '이름1', checked: true },
        { id: 2, name: '이름2', checked: 1 },
        { id: 3, name: '이름3' },
        { id: 4, name: { special: '이름4' } },
      ],
    };
  },
};
</script>

에러 분석: Invalid prop: type check failed

이 에러는 부모 컴포넌트에서 자식 컴포넌트(CheckboxItem.vue)로 전달하는 props 타입이 올바르지 않아서 발생한 것이다.


사용자 정의 이벤트

사용자 정의 이벤트를 이용한 정보 전달

  • 이벤트 - 자식 컴포넌트가 부모 컴포넌트에게 정보를 전달하는 방법
  • 자식 컴포넌트가 이벤트를 발신(emit events)
    • 컴포넌트 인스턴스의 $emit() 메서드 이용

      $emit('이벤트명', [값])

      이벤트명 → 사용자 정의

      값 → 부모에게 전달

  • 부모 컴포넌트는 v-on 디렉티브로 이벤트 수신
  • 자식 컴포넌트에서
    • this.$emit('event-name', eventArgs1, eventArgs2, …)
      • eventArgs1~ 정보
  • 부모 컴포넌트에서
    <child-component @event-name="handlerMethod" />
    
    methods: {
    	handlerMethod(eventArgs1, eventArgs2, …) {
    	// 전달받은 아규먼트로 처리할 코드 작성
    	}
    }
  • 이벤트명은 자식이 결정 @nameChanged

이벤트의 발신을 위한 $emit() 내장 메서드는 직계 부모 컴포넌트로만 이벤트 정보를 전송한다. 컴포넌트 계층 구조가 복잡할 때는 중간에 거쳐가는 컴포넌트에서 이벤트 정보를 받아서 다시 부모로 전달해야 한다.


이벤트 유효성 검증

  • emits 옵션 등록
    • 발신하는 이벤트에 대한 유효성 검사를 수행
      • 이벤트명2: null → 이벤트명만 검사
      • 이벤트명1:(e) ~ → 상황도 검사

작성 안 하면 부모의 이벤트명과 다른 걸 적어서 작동이 안 될 때 에러가 안 떠서 왜인지 모르게 됨

  • emits: [ 'nameChanged1'],
    // nameChanged1 이외의 이벤트 발생시 경고 출력
    (이벤트명이라도 작성해 주는 게 좋음)
emits: {
	namedChanged: (e) => { // 이벤트명
		return e.name && typeof e.name === 'string' && e.name.trim().length >= 3
		// e.name -> 입력 여부 체크
		// type ~ 'string' -> 타입 체크
		// e.name.trim()~ -> 길이 체크
		? true
		: false;
	},
},

이벤트 에미터 사용

이벤트 에미터

  • 부모-자식-손자와 같은 많은 계층에서 이벤트 전달은 매우 힘듦

→ 하나의 공유 이벤트 에미터(Event Emitter)를 만들어 특정 컴포넌트에게 이벤트를 전송

→ mitt 패키지 라이브러리 사용(잘 사용 안 함 , 존재 정도만 알아두기)

→ 상태 관리 툴 사용


1. 프로젝트 만드는 법(골격 이해)
2. 부모-자식 관계가 생기는데 부모가 자식한테 정보를 어떻게 전달? 프로퍼티 property(만드는 방법 알기)
3. 자식이 부모에게 정보 전달은? 이벤트로!

profile
사용자를 고려한 디자인과 UX에 관심있는 개발자

0개의 댓글