Vue 초보 개발자 위한 Vue.js 핵심 문법 15개

안녕하세요. Vue 사용하시죠?

저는 거의 매일 vue.js 사용하고 있는데, 인터넷에는 올바른 사용법이라는 내용의 글이 생각보다 많지 않다는 생각을 합니다. 그래서 꼭 필요한 Vue 개발 최선의 문법 사용 15개를 정리해 보았습니다.


여러분의 개발에 도움이 되었으면 좋겠고요. 잘못된 내용이나 더 나은 의견이 있으면 최대한 부드럽게 지적해 주시면 감사하겠습니다.

1. v-for 안에서는 반드시 :key 사용

v-for 지시문(디렉티브 directive)에선 key 속성을 사용하여 데이터를 제어합니다. Vue 가 구성 요소 (컴포넌트 component) 상태를 추적하고 각 요소(엘리먼트 element)를 지속적으로 참조할 수 있습니다. 특히 애니메이션이나 Vue 트랜지션(transition)에서 key 가 중요하므로 반드시 사용하시기 바랍니다.


key가 없으면 Vue는 DOM을 효율적으로 생성하는 것에 그칩니다. 또한, v-for 엘리먼트가 순서대로 표시되지 않는 등의 문제점이 발생합니다.


이런 이유로 v-for를 사용할 땐 반드시 key 도 설정합시다. 각각의 엘리먼트에 특정 키를 할당함으로써 Vue 앱이 DOM 조작을 어떻게 하는지 알아낼 수 있습니다.


<!-- 안 좋은 방법 -->
<div v-for='animal in animals' />

<!-- 좋은 방법 -->
<div v-for='animal in animals' :key='animal.id' />


2. 이벤트 이름은 케밥 케이스 kebab-case

사용자 정의 이벤트(커스텀 custom event)를 emit 할 때 이벤트 이름은 케밥 케이스를 사용하세요. 부모 요소가 똑같은 구문의 이벤트를 실행하기 때문입니다.


이벤트 이름은 Vue가 자동으로 소문자로 변환해 줍니다만, 컴포넌트(component) 간의 일관성을 유지하여, 읽기 쉬운 코드를 만들기 위해 케밥 케이스를 사용하는 것이 좋습니다.


// 자식 컴포넌트
this.$emit('close-window')


<!-- 부모 컴포넌트 -->
<popup-window v-on:close-window='handleEvent()' />


3. Props 선언에는 카멜 케이스 , 템플릿에는 케밥 케이스 를 사용합니다.

JavaScript 에서는 카멜 케이스 (camelCase)가 기본이지만, HTML 에서는 케밥 케이스 (Kebab case)를 사용합니다. Vue 도 이 규칙을 따릅니다.


Vue 는 케밥 케이스와 카멜 케이스를 자동으로 변환해 주므로 선언할 때 이외엔 별로 신경 쓸 필요는 없습니다.


<!--  좋은  -->
<PopupWindow greetingText='good morning!' /> 
props: { 'greeting-text'String }

<!-- 좋은  -->
<PopupWindow greeting-text='good morning!' /> 
props: { greetingText: String }

4. 컴포넌트 옵션 순서는 스타일 가이드를 따름

컴포넌트 옵션은 순서대로 정의하세요. 찾는 시간을 줄일 수 있고, 새로운 컴포넌트 를 만들 때 유용합니다.


예를 들어, 이벤트 순서는 다음과 같이 정의하면 좋다고 생각합니다.


  • watch
  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed


공식 문서 ( 컴포넌트 / 인스턴스 옵션 순서) 에 자세히 실려 있으니 꼭 정독해보세요.


5. 데이터 프로퍼티(속성)에선 반드시 Function 반환

컴포넌트 에서 데이터를 선언할 때, 데이터 속성에는 반드시 function을 return하세요. return 하지 않으면 오브젝트를 반환하게 되고, 데이터가 모든 컴포넌트 인스턴스에서 공유됩니다.


재사용 가능한 고유의 오브젝트를 반환하도록 컴포넌트를 구성한다고 생각하는데요. 데이터 객체를 function 에서 return 하도록 구현하는 게 좋습니다.


// w안 좋은 예
data: {
  name: 'Momoko Toyota',
  hobbies: []
}

// 좋은 예
data () {
  return {
    name'Momoko Toyota',
    hobbies: []
  }
}


6. v-for 와 v-if 는 같은 요소로 사용하지 않는다

요소 (엘리먼트)를 어떤 조건으로 필터링 할 때, v-if 와 v-for 를 함께 사용하고 싶죠?


<div v-for='book in books' v-if='book.price < 1500'>


v-for 지시문 (디렉티브)은 v-if 보다 우선 순위가 높습니다. 다시 말해서, 내부 요소를 루프에 태운 다음 v-if 로 조건을 확인하는 것입니다. 예를 들어 다음 코드와 같은 느낌입니다.


this.books.map(function (book) {
  if (books.price < 1500) {
    return book
  }
})


이 뜻은, books 몇 개의 요소를 렌더링하고 싶을 때, books 배열 전체를 반복해서 처리한다는 겁니다. 그다지 좋은 방법 아닙니다.


해결책은 computed 속성 (프로퍼티)을 사용하는 것입니다. 아래처럼요.


<div v-for='book in reasonableBooks'>


computed: {
  reasonableBooks: () => {
    return this.books.filter(function (book) {
      return books.price < 1500
    })
  }
}


이 방법이 좋은 이유는 다음과 같습니다.


반복 처리 대상은 모든 요소가 아닙니다. 따라서 더 효율적입니다. 의존 관계 (종속성)가 변경되면서 일부만 연산 대상이 되므로, 템플릿 구성 요소가 논리적으로 분할됩니다. 결과적으로 읽기 쉬운 컴포넌트가 됩니다.

7. Props Validate (유효성)는 확실히 정의

앱이 커질수록, props 에서 사용되는 데이터 타입, 데이터 유형, 기타 정보들을 잊어버리고 쉽다고 생각합니다. 특히 대규모 개발팀일수록 개개인이 만든 컴포넌트를 팀원들이 한번에 이해하기 어렵습니다.


팀원들이 내가 만든 컴포넌트 props 포맷 등을 확인하고 이해해야 하는데, 이 시간을 조금이라도 줄이기 위해선 props Validate 는 제대로! 확실히! 정의하세요.


자세한 내용은 공식 사이트를 참고하시는 게 좋습니다.


props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value!== -1
    }
  }
}


8. 컴포넌트 이름에는 파스칼 케이스 또는 케밥 케이스 사용

컴포넌트 이름에는 파스칼 케이스 (PascalCase) 또는 케밥 케이스 (kebab-case)를 사용한다는 규약이 있습니다. 어느 쪽을 사용해도 문제 없습니다.


대부분의 IDE의 자동 완성 기능을 지원하고 있기 때문에, 파스칼 케이스가 좋습니다.


# 안 좋은 방법
mycomponent.vue
myComponent.vue
Mycomponent.vue

# 좋은 방법
MyComponent.vue
my-component.vue


9. 기본 컴포넌트 이름에는 접두사 (prefix) 붙이기

버튼이나 아이콘 등 앱의 여러 컴포넌트에서 사용하는 컴포넌트들을 기본 컴포넌트 (Base components 기본 요소) 라고 합니다.


Vue 스타일 가이드에 따르면, 다음의 세 가지 컴포넌트만 포함하는 것이 기본 컴포넌트입니다.


  • HTML 요소
  • 다른 기본 컴포넌트
  • 제 3자 UI 컴포넌트


이러한 컴포넌트 명명 규칙에 따라 Base, V, App 등 특정 접두사(prefix)를 붙이는 것입니다.


components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue

components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue


이런 규약을 따르면 파일 시스템에서 기본 컴포넌트를 알파벳 순으로 그룹화 합니다. 그리고 webpack Import 함수를 사용하여 이 명명 규칙의 패턴과 일치하는 component 를 찾아 Vue 프로젝트의 글로벌에 자동으로 Import 합니다.


10. destroy()의 $off 이용해 이벤트 리스너 제거

$on 을 사용하여 이벤트를 감시한다면 destroyed()에 $off 를 사용해 리스너를 꼭 제거합니다. 메모리 누수를 방지할 수 있습니다.



11. 한 번만 선언하고 사용되는 컴포넌트 이름에 접두사 Prefix 'The' 를 붙인다

기본 컴포넌트 와 비슷한 것으로 단일 컴포넌트 가 있습니다. 모든 페이지에서 한 번만 사용되며, props 를 사용하지 않는 컴포넌트입니다. 예를 들어, 헤더, 사이드 바, 푸터, 바닥 글 등입니다.


하나의 활성 인스턴스 만 존재할 수 있다는 의미로 단일 컴포넌트 이름에는 접두사 'The'를 붙입시다.


단일 인스턴스의 컴포넌트 이름은 여기 참조


components/
|- TheHeading.vue
|- TheSidebar.vue
|- TheFooter.vue


12. 믹스인 속성에는 $_ 사용

믹스인 (mixin) 은 반복 사용되는 코드를 한 블록에 넣어 여러번 재사용하는 방법입니다. 그러나 몇 가지 문제가 있습니다. 그 중 하나가 속성 중복 (오버랩)입니다.


컴포넌트 믹스인을 import 할 때 컴포넌트 믹스인 코드가 결합됩니다. 만약 같은 이름의 속성이 있으면 어떻게 될까요?


이 경우 컴포넌트 속성이 우선입니다. 컴포넌트 속성은 믹스인 속성보다 우선 순위가 높기 때문입니다. 믹스인에 높은 우선 순위를 부여하고 싶다면, 명명 규칙에 기초한 "이름"을 믹스인에 붙여 오버래핑을 방지할 수 있습니다.


컴포넌트 속성과 믹스인 속성을 구분하기 위해 $_를 사용합니다. $_를 사용하는 이유는,


  • VueJS 명명 규칙으로 정의되어 있음
  • _는 Vue private 속성을 정의하려고 이미 사용 중이라 사용할 수 없음
  • $는 Vue Eco 시스템에서 특별한 인스턴스 속성에 사용할 수 없음


$_createUser 처럼, $_를 붙이면 쉽게 읽을 수 있고, 컴포넌트와 믹스인의 구별이 쉬워집니다.


13. 지시문 작성에 일관성을 갖기

v-on 등의 지시어에는 생략 기법(축약, 숏핸딩)이 있습니다.


  • v-on → @
  • v-bind → :
  • v-slot → #


축약하여 사용하는 것엔 문제가 없지만, 한쪽만 채택하여 프로젝트 일관성을 갖게 합시다. 결과적으로 읽기 쉬운 코드가 됩니다.

14. created와 watch 함수를 호출하지 않는다

초보자가 흔히하는 실수는 created 와 watch 모두를 호출하는 것입니다. 컴포넌트가 초기화될 때 watch hook 을 제어하고 싶기 때문입니다.


// 안 좋은 예
created: () {
  this.changeName()
},
methods: {
  changeName() {
    console.log('this is my name!');
  }
},
watch () {
  property() {
    this.changeName()
  }
}


언뜻 보기엔 문제가 없는 것처럼 보이지만, created()와 watch() 코드가 중복되었습니다.


해결 방법은 함수를 watch() 로 이동하고 immediate와 handler 속성을 선언함으로써, 컴포넌트가 초기화될 때 함수가 실행되게 하는 것입니다.


  1. handler (newVal, oldVal) → watcher 함수를 말합니다.
  2. immediate: true → 인스턴스가 만들어질 때 핸들러가 움직이게 됩니다


// 좋은 예
watch: {
  myProperty: {
    immediate: true,
    handler() {
      this.changeName();
    }
  }
},
methods: {
  changeName() {
     console.log('this is my name!');
  }
}


// 더 좋은 예
watch: {
  myProperty: {
    immediate: true,
    handler() {
      // 메소드에 메소드를 정의하지 않는다
      console.log('this is my name!');
    }
  }
}


15. 템플릿 구문에는 기본적인 JavaScript 구문만 포함

템플릿 안에 로직을 구현하고 싶은 기분은 이해합니다만, 그럴 경우 템플릿 코드가 복잡해지고 읽기 어렵습니다.


<!-- 안 좋은 예 -->
<p>
 {{
   function () {
    return this.fullName.split(" ").map(function (word) {
      return word[0][0].toUpperCase()
    }).join('')
  }
 }}
</p>


기본적으로 템플릿 안에서 무엇이 표시되는지 직관적으로 알고 싶겠죠. 그렇다면, 위와 같은 복잡한 구문은 이름을 붙이고 적절한 컴포넌트 옵션에 넣어야 합니다.


<p>{{ initialName }}<p>


computed: {
  initialName: function () {
    return this.fullName.split(" ").map(function (word) {
      return word[0][0].toUpperCase()
    }).join('')
  }
}


복잡한 루틴을 분할하면 재사용성 또한 높아집니다.


댓글(2)

Designed by JB FACTORY