root 컴포넌트인 App 과 <transition>
, <component>
등 Vue에서 제공되는 빌트인 컴포넌트를 제외하고 컴포넌트의 이름은 항상 합성어를 사용해야한다.
모든 HTML 엘리먼트의 이름은 한 단어이기 때문에 합성어를 사용하는 것은 기존, 향후 HTML 엘리먼트와의 충돌을 방지해준다.
// 좋은 예
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
// 나쁜 예
Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}
컴포넌트의 data
는 반드시 함수여야 한다.
컴포넌트 (new Vue
를 제외한 모든 곳)의 data
프로퍼티의 값은 반드시 객체(object)를 반환하는 함수여야 한다.
Vue.component('some-comp', {
data: function(){
return {
foo: 'bar'
}
}
})
export default {
data () {
foo: 'bar'
}
}
new Vue({
data: {
foo: 'bar'
}
})
Props은 가능한 상세하게 정의되어야 한다.
커밋 된 코드에서 prop 정의는 적어도 type은 명시되도록 가능한 상세하게 정의되는게 좋다.
자세한 prop 정의는 두 가지 이점을 갖는다.
props: {
status: String,
type: String
}
// 더 좋은 예
props: {
state: {
type: String,
required: true,
validator: function(value) {
return [
'syncing',
'synced',
'version-confilict',
'error'
].indexOf(value) !== -1
}
}
}
// 나쁜 예
props: ['status']
v-for는 key와 항상 함께 사용한다.
서브트리 내부 컴포넌트 상태를 유지하기 위해, v-for
는 항상 key
와 함께 사용한다. 비록 element이긴 하지만 에니메이션의 객체 불변성과 같이 예측이 가능한 행동을 유지하는 것은 좋은 습관이다.
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
<!-- 나쁜 예 -->
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>
v-for가 사용된 엘리먼트에 v-if를 사용하지 않는다.
사용을 해야하는 2가지 케이스
<li v-for=”user in users” v-if=”user.isActive”>
(해결) users
를 새로운 computed
속성으로, 필터링 된 목록으로 새로 생성(activeUsers
)해서 대체한다<li v-for=”user in users” v-if=”shouldShowUsers”>
(해결) v-if
를 상위 컨테이너 엘리먼트로 옮긴다 (ul / ol)<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<!-- 나쁨 -->
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
App 컴포넌트 스타일이나 글로벌 레이아웃 컴포넌트 스타일은 전역에 위치할 수 있으나, 단일 파일의 경우 각각의 UI 컴포넌트 스타일은 적용 범위가 반드시 지정되어야 한다.
<template>
<button class="button button-close">X</button>
</template>
<!-- Using the `scoped` attribute -->
<style scoped>
.button {
border: none;
border-radius: 2px;
}
.button-close {
background-color: red;
}
</style>
<template>
<button :class="[$style.button, $style.buttonClose]">X</button>
</template>
<!-- Using CSS modules -->
<style module>
.button {
border: none;
border-radius: 2px;
}
.buttonClose {
background-color: red;
}
</style>
<template>
<button class="c-Button c-Button--close">X</button>
</template>
<!-- Using the BEM convention -->
<style>
.c-Button {
border: none;
border-radius: 2px;
}
.c-Button--close {
background-color: red;
}
</style>
Plugin, Mixin 등에서 사용자 커스텀 Private 프로퍼티에는 항상 접두사 $_
를 사용한다.
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
// Even better! (모듈화 적용)
var myGreatMixin = {
// ...
methods: {
publicMethod() {
// ...
myPrivateFunction()
}
}
}
function myPrivateFunction() {
// ...
}
export default myGreatMixin
각각의 컴포넌트 별로 자체 파일이 있는게 좋다. 이는 보다 빠르게 컴포넌트 파일을 찾을 수 있다.
components/
|- TodoList.js
|- TodoItem.js
components/
|- TodoList.vue
|- TodoItem.vue
싱글 파일 컴포넌트의 파일명은 항상 PacalCase나 kebab-case로 명명한다!
components/
|- MyComponent.vue
components/
|- my-component.vue
// 나쁜 예
components/
|- mycomponent.vue
components/
|- myComponent.vue
App 별 스타일 지정 및 규칙을 적용할 때 순수 UI 컴포넌트의 경우, Base
, App
, V
와 같은 특정 접두사로 시작해야 한다.
Button / Table / Icon / Select / Input 등 순수 UI 컴포넌트
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue
// 나쁨
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
한가지 활성 인스턴스를 갖고 있는 컴포넌트는 The
특정 접두사로 시작해야 한다.
이 싱글 인스턴스 컴포넌트는 한 페이지에서만 사용되는 것이 아니라, 페이지당 한 번만 사용된다.
Header / Gnb / Sidebar 와 같은 조합의 UI 컴포넌트
components/
|- TheHeading.vue
|- TheSidebar.vue
// 나쁨
components/
|- Heading.vue
|- MySidebar.vue
상위 구성 요소와 연결되는 하위 구성 요소에는 상위 구성 요소 이름이 접두사로 포함되어야 한다.
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
// 나쁜 예
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
구성 요소 이름은 high level 단어로 시작하고, 설명적인 수정 단어로 끝나는게 좋다.
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
// 나쁨
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
내용이 없는 단일 파일 컴포넌트, 단순 문자열 템플릿 등의 경우에는 셀프 클로징이 가능하고, DOM 템플릿에서는 셀프 클로징을 사용하지 않는다.
셀프 클로징은 콘텐츠가 없을 뿐만 아니라, 콘텐츠가 없어야 한다는 의미를 전달하고 있기 때문에 지켜주는 것이 좋다.
<!-- In single-file components, string templates, and JSX -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
보편적으로, 단일 파일 컴포넌트 / 단순 문자열 템플릿 에서는 PascalCase로 작명하고, DOM 템플릿에서는 kebab-case로 작명한다.
파스칼 케이스로 작명했을 때 몇 가지 장점이 있다.
<!-- In single-file components and string templates -->
<MyComponent/>
<!-- In DOM templates -->
<my-component></my-component>
OR
<!-- Everywhere -->
<my-component></my-component>
JS/JSX (<sctript></script>
)에서 컴포넌트 name
은 무조건 PascalCase로 작명한다.
다만, Vue.component를 통해서만 컴포넌트를 등록하는 단순한 애플리케이션의 경우, kebab-case로 작성할 수도 있다.
Vue.component('MyComponent', {
// ...
})
Vue.component('my-component', {
// ...
})
import MyComponent from './MyComponent.vue'
export default {
name: 'MyComponent',
// ...
}
//나쁜 예
Vue.component('myComponent', {
// ...
})
import myComponent from './MyComponent.vue'
export default {
name: 'myComponent',
// ...
}
export default {
name: 'my-component',
// ...
}
컴포넌트 이름은 명확한 의미를 전달할 수 있도록 작명한다. 즉, 약어를 사용하지 않는게 좋다.
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
// 나쁜 예
components/
|- SdSettings.vue
|- UProfOpts.vue
props 이름은 <script>
내에서는 camelCase로 작성되어야 하고, template / JSX (HTML) 내부에서는 kebab-case로 작성되는 것이 좋다.
// JS
props: {
greetingText: String
}
// template
<WelcomeMessage greeting-text="hi"/>
// 나쁜 예
props: {
'greeting-text': String
}
<WelcomeMessage greetingText="hi"/>
어트리뷰트가 다중으로 선언되어 있는 경우, 멀티 라인으로 작성하는게 좋다.
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>
<MyComponent
foo="a"
bar="b"
baz="c"
/>
<template>
내부에는 간결하게 작성하는 것이 좋다. 복잡한 식이 들어가야 하는 경우, computed 에 작성하는 것이 좋다.
<!-- In a template -->
{{ normalizedFullName }}
// The complex expression has been moved to a computed property
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
// 나쁜 예
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
복잡한 computed 속성은 가능한 한, 기능 별로 최소 단위로 분할해서 작성해주는게 좋다.
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}
// 나쁜 예
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}
비어 있지 않은 HTML 속성 값은 항상 따옴표 안에 있어야 한다. (’’, “” 중 JS에서 사용되지 않는 것)
<input type="text">
<AppSidebar :style="{ width: sidebarWidth + 'px' }">
축약형 디렉티브는 항상 사용하거나, 아예 사용하지 않도록 한다.
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
<input
@input="onInput"
@focus="onFocus"
>
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>