Style Guide

카탈로그
  1. 1. 스타일 가이드
    1. 1.1. 규칙 분류
      1. 1.1.1. 우선순위 A: 필수
      2. 1.1.2. 우선순위 B: 매우 추천함
      3. 1.1.3. 우선순위 C: 추천함
      4. 1.1.4. 우선순위 D: 주의요함
    2. 1.2. 우선순위 A 규칙: 필수 (에러 방지)
      1. 1.2.1. 컴포넌트 이름에 합성어 사용 필수
        1. 1.2.1.1. 나쁨
        2. 1.2.1.2. 좋음
      2. 1.2.2. 컴포넌트 데이터 필수
        1. 1.2.2.1. 자세한 설명
        2. 1.2.2.2. 나쁨
        3. 1.2.2.3. 좋음
      3. 1.2.3. Props 정의 필수
        1. 1.2.3.1. 자세한 설명
        2. 1.2.3.2. 나쁨
        3. 1.2.3.3. 좋음
      4. 1.2.4. v-for 에 key 지정 필수
        1. 1.2.4.1. 자세한 설명
        2. 1.2.4.2. 나쁨
        3. 1.2.4.3. 좋음
      5. 1.2.5. v-if와 v-for를 동시에 사용하지 마세요 필수
        1. 1.2.5.1. 자세한 설명
        2. 1.2.5.2. 나쁨
        3. 1.2.5.3. 좋음
      6. 1.2.6. 컴포넌트 스타일 스코프 필수
        1. 1.2.6.1. 자세한 설명
        2. 1.2.6.2. 나쁨
        3. 1.2.6.3. 좋음
      7. 1.2.7. Private 속성 이름 필수
        1. 1.2.7.1. 자세한 설명
        2. 1.2.7.2. 나쁨
        3. 1.2.7.3. 좋음
    3. 1.3. 우선순위 B 규칙: 매우 추천함 (가독성 향상을 위함)
      1. 1.3.1. 컴포넌트 파일 매우 추천함
        1. 1.3.1.1. 나쁨
        2. 1.3.1.2. 좋음
      2. 1.3.2. 싱글 파일 컴포넌트 이름 규칙 지정(casing) 매우 추천함
        1. 1.3.2.1. 나쁨
        2. 1.3.2.2. 좋음
      3. 1.3.3. 베이스 컴포넌트 이름 매우 추천함
        1. 1.3.3.1. 자세한 설명
        2. 1.3.3.2. 나쁨
        3. 1.3.3.3. 좋음
      4. 1.3.4. 싱글 인스턴스 컴포넌트 이름 매우 추천함
        1. 1.3.4.1. 나쁨
        2. 1.3.4.2. 좋음
      5. 1.3.5. 강한 연관성을 가진 컴포넌트 이름 매우 추천함
        1. 1.3.5.1. 자세한 설명
        2. 1.3.5.2. 나쁨
        3. 1.3.5.3. 좋음
      6. 1.3.6. 컴포넌트 이름의 단어 순서 정렬 매우 추천함
        1. 1.3.6.1. 자세한 설명
        2. 1.3.6.2. 나쁨
        3. 1.3.6.3. 좋음
      7. 1.3.7. 셀프 클로징 컴포넌트 매우 추천함
        1. 1.3.7.1. 나쁨
        2. 1.3.7.2. 좋음
      8. 1.3.8. 템플릿에서 컴포넌트 이름 규칙 지정(casing) 매우 추천함
        1. 1.3.8.1. 나쁨
        2. 1.3.8.2. 좋음
      9. 1.3.9. JS/JSX에서 컴포넌트 이름 규칙 지정(casing) 매우 추천함
        1. 1.3.9.1. 자세한 설명
        2. 1.3.9.2. 나쁨
        3. 1.3.9.3. 좋음
      10. 1.3.10. 전체 이름 컴포넌트 이름 매우 추천함
        1. 1.3.10.1. 나쁨
        2. 1.3.10.2. 좋음
      11. 1.3.11. Prop 이름 규칙 지정(casing) 매우 추천함
        1. 1.3.11.1. 나쁨
        2. 1.3.11.2. 좋음
      12. 1.3.12. 다중 속성 엘리먼트 매우 추천함
        1. 1.3.12.1. 나쁨
        2. 1.3.12.2. 좋음
      13. 1.3.13. 템플릿에서 단순한 표현식 매우 추천함
        1. 1.3.13.1. 나쁨
        2. 1.3.13.2. 좋음
      14. 1.3.14. 단순한 계산된 속성 매우 추천함
        1. 1.3.14.1. 자세한 설명
        2. 1.3.14.2. 나쁨
        3. 1.3.14.3. 좋음
      15. 1.3.15. 속성 값에 따옴표 매우 추천함
        1. 1.3.15.1. 나쁨
        2. 1.3.15.2. 좋음
      16. 1.3.16. 축약형 디렉티브 매우 추천함
        1. 1.3.16.1. 나쁨
        2. 1.3.16.2. 좋음
    4. 1.4. 우선순위 C 규칙: 추천함 (선택의 혼란 또는 판단 오버헤드 최소화)
      1. 1.4.1. 컴포넌트/인스턴스 옵션 순서 추천함
      2. 1.4.2. 엘리먼트 속성 순서 추천함
      3. 1.4.3. 컴포넌트/인스턴스 옵션간 빈 줄 추천함
        1. 1.4.3.1. 좋음
      4. 1.4.4. 싱글 파일 컴포넌트 최상위 엘리먼트 순서 추천함
        1. 1.4.4.1. 나쁨
        2. 1.4.4.2. 좋음
    5. 1.5. 우선순위 D 규칙: 주의요함 (잠재적인 위험을 내포한 패턴)
      1. 1.5.1. key가 없는 v-if/v-if-else/v-else 주의요함
        1. 1.5.1.1. 나쁨
        2. 1.5.1.2. 좋음
      2. 1.5.2. scoped에서 엘리먼트 셀렉터 사용 주의요함
        1. 1.5.2.1. 자세한 설명
        2. 1.5.2.2. 나쁨
        3. 1.5.2.3. 좋음
      3. 1.5.3. 부모-자식간 의사소통 주의요함
        1. 1.5.3.1. 나쁨
        2. 1.5.3.2. 좋음
      4. 1.5.4. 전역 상태 관리 주의요함
        1. 1.5.4.1. 나쁨
        2. 1.5.4.2. 좋음

스타일 가이드

이 문서는 Vue 코드에 대한 공식 스타일 가이드입니다. 만약 현재 Vue를 사용하여 프로젝트를 진행중이라면 이 문서는 에러와 바이크쉐딩(bikeshedding), 안티패턴을 피하는 좋은 참조가 될것 입니다. 그러나 무조건 이 문서에서 제시하는 스타일 가이드가 당신의 프로젝트에 적합한 것은 아닙니다. 그러므로 당신의 경험과 기술스택, 개인적 통찰력을 바탕으로 이 스타일 가이드가 적용되는 것을 권장해드립니다.

대부분의 경우 우리는 HTML과 자바스크립트에 대한 제안은 일반적으로 피합니다. 우리는 당신이 세미콜론이나 쉼표(trailing commas)에 대한 사용여부는 신경쓰지 않습니다. 우리는 당신이 HTML의 속성값을 위해 작음따옴표를 사용하지는 큰따옴표를 사용하는지 신경쓰지 않습니다. 그러나 특정 패턴이 뷰 컨텍스트에서 유용하다고 발견된 경우 예외가 존재합니다.

Soon, we’ll also provide tips for enforcement. Sometimes you’ll simply have to be disciplined, but wherever possible, we’ll try to show you how to use ESLint and other automated processes to make enforcement simpler.

마지막으로, 우리는 규칙을 4가지 범주로 분류하였습니다.

규칙 분류

우선순위 A: 필수

이 규칙은 오류를 예방하는데 도움을 주기 때문에 모든 비용을 통해서 학습하고 준수하여야 합니다. 예외상황이 존재하겠지만 매우 드물며 자바스크립트와 뷰에 대한 전문 지식이 있어야 만들 수 있습니다.

우선순위 B: 매우 추천함

이 규칙은 대부분의 프로젝트에서 가독성 그리고 개발자경험을 향상시키는 것으로 발견되었습니다. 해당 규칙을 위반해도 코드는 여전히 실행되지만 위반은 드물고 정당합니다.

우선순위 C: 추천함

동일하게 좋은 여러가지 옵션이 존재하는 경우, 일관성을 보장하기 위해 임의의 선택을 할 수 있습니다. 이 규칙은 각각의 수용가능한 옵션을 설명하고 기본 선택을 제안합니다. 즉, 일관성 있고 좋은 이유가 있으면 당신의 코드베이스에서 자유롭게 다른 선택을 할 수 있습니다. 좋은 이유가 있어야 합니다! 커뮤니티 표준에 적응되기 위해서 당신은 다음과 같이 해야합니다.

  1. 당신이 마주하는 대부분의 커뮤니티 코드를 더 쉽게 분석할 수 있도록 훈련하세요
  2. 커뮤니티 코드 예제를 수정하기 한고 복사 그리고 붙혀넣기 할 수 있어야 합니다
  3. 적어도 뷰에 있어서는 당신이 선호하는 코딩 스타일을 수용하는 새로운 직원을 자주 찾을 것입니다

우선순위 D: 주의요함

뷰의 몇 가지 은 특성은 드문 엣지 케이스 또는 레거시 코드로의 부터 마이크레이션을 위해 존재합니다. 그러나 그것들을 남용하면 당신의 코드를 유지보수하기 어렵게만들거나 버그를 발생시키는 원인이 될 수 있습니다. 이 규칙은 잠재적 위험요소를 인식시켜주고 언제 그리고 왜 피해야되는지 설명해 줍니다.

우선순위 A 규칙: 필수 (에러 방지)

컴포넌트 이름에 합성어 사용 필수

root 컴포넌트인 App<transition>, <component>등 Vue에서 제공되는 빌트인 컴포넌트를 제외하고 컴포넌트의 이름은 항상 합성어를 사용해야한다.

모든 HTML 엘리먼트의 이름은 한 단어이기 때문에 합성어를 사용하는 것은 기존 그리고 향후 HTML엘리먼트와의 충돌을 방지해줍니다.

나쁨

Vue.component(‘todo’, {
// …
})

1
2
3
4
5
6

​``` js
export default {
name: 'Todo',
// ...
}

좋음

1
2
3
Vue.component('todo-item', {
// ...
})
1
2
3
4
export default {
name: 'TodoItem',
// ...
}

컴포넌트 데이터 필수

컴포넌트의 data 는 반드시 함수여야 합니다.

컴포넌트(i.e. new Vue를 제외한 모든곳)의 data 프로퍼티의 값은 반드시 객체(object)를 반환하는 함수여야 한다.

자세한 설명

data 의 값이 오브젝트일 경우, 컴포넌트의 모든 인스턴스가 공유한다. 예를 들어, 다음 data 를 가진 TodoList 컴포넌트를 상상해보자.

1
2
3
4
data: {
listTitle: '',
todos: []
}

이 컴포넌트는 재사용하여 사용자가 여러 목록(e.g. 쇼핑, 소원, 오늘 할일 등)을 유지할 수 있도록 해야 할 수 있다. 컴포넌트의 모든 인스턴스가 동일한 data 객체를 참조하므로, 하나의 목록의 타이틀을 변경할 때 다른 모든 리스트의 타이틀도 변경될 것이다. Todo를 추가/수정/삭제하는 경우에도 마찬가지다.

대신 우리는 각 컴포넌트의 인스턴스 자체 data만을 관리하기를 원한다. 이렇게 하려면 각 인스턴스는 고유한 data 객체를 생성해야 한다. JavaScript에서는 함수안에서 객체를 반환하는 방법으로 해결할 수 있다:

1
2
3
4
5
6
data: function () {
return {
listTitle: '',
todos: []
}
}

나쁨

1
2
3
4
5
Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
1
2
3
4
5
export default {
data: {
foo: 'bar'
}
}

좋음

1
2
3
4
5
6
7
Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
1
2
3
4
5
6
7
8
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
1
2
3
4
5
6
7
8
// It's OK to use an object directly in a root
// Vue instance, since only a single instance
// will ever exist.
new Vue({
data: {
foo: 'bar'
}
})

Props 정의 필수

Prop은 가능한 상세하게 정의되어야 합니다.

커밋 된 코드에서, prop 정의는 적어도 타입은 명시되도록 가능한 상세하게 정의되어야 합니다.

자세한 설명

자세한 prop definitions 두 가지 이점을 갖는다:

  • 이 API는 컴포넌트의 API를 문서화하므로 컴포넌트의 사용 방법을 쉽게 알 수 있다.
  • 개발 중에, Vue는 컴포넌트의 타입이 잘못 지정된 props를 전달하면 경고 메시지를 표시하여 오류의 잠재적 원인을 파악할 수 있도록 도와준다.

나쁨

1
2
// This is only OK when prototyping
props: ['status']

좋음

1
2
3
props: {
status: String
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Even better!
props: {
status: {
type: String,
required: true,
validator: function (value) {
return [
'syncing',
'synced',
'version-conflict',
'error'
].indexOf(value) !== -1
}
}
}

v-forkey 지정 필수

v-forkey와 항상 함께 사용합니다.

서브트리의 내부 컴포넌트 상태를 유지하기 위해 v-for항상 key와 함께 요구됩니다. 비록 엘리먼트이긴 하지만 에니메이션의 객체 불변성과 같이 예측 가능한 행동을 유지하는것은 좋은 습관입니다.

자세한 설명

할일 목록이 있다고 가정 해보자:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data: function () {
return {
todos: [
{
id: 1,
text: 'Learn to use v-for'
},
{
id: 2,
text: 'Learn to use key'
}
]
}
}

그 다음 알파벳순으로 정렬한다. DOM 이 업데이트될 때, Vue는 가능한 적은 DOM 전이(mutations)를 수행하기 위해 렌더링을 최적화한다. 즉, 첫번째 할일 엘리먼트를 지우고, 리스트의 마지막에 다시 추가한다.

The problem is, there are cases where it’s important not to delete elements that will remain in the DOM. For example, you may want to use <transition-group> to animate list sorting, or maintain focus if the rendered element is an <input>. In these cases, adding a unique key for each item (e.g. :key="todo.id") will tell Vue how to behave more predictably.

In our experience, it’s better to always add a unique key, so that you and your team simply never have to worry about these edge cases. Then in the rare, performance-critical scenarios where object constancy isn’t necessary, you can make a conscious exception.

나쁨

1
2
3
4
5
<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>

좋음

1
2
3
4
5
6
7
8
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

v-ifv-for를 동시에 사용하지 마세요 필수

v-for가 사용된 엘리먼트에 절대 v-if를 사용하지 마세요.

사용 가능해 보이는 두 가지 일반적인 경우가 있습니다:

  • 리스트 목록을 필터링하기 위해서 입니다 (e.g. v-for="user in users" v-if="user.isActive"). 이 경우 users을 새로운 computed 속성으로 필더링된 목록으로 대체하십시오(e.g. activeUsers).

  • 숨기기 위해 리스트의 랜더링을 피할 때 입니다 (e.g. v-for="user in users" v-if="shouldShowUsers"). 이 경우 v-if를 컨테이너 엘리먼트로 옮기세요 (e.g. ul, ol).

자세한 설명

When Vue processes directives, v-for has a higher priority than v-if, so that this template:

1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

Will be evaluated similar to:

1
2
3
4
5
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})

So even if we only render elements for a small fraction of users, we have to iterate over the entire list every time we re-render, whether or not the set of active users has changed.

By iterating over a computed property instead, like this:

1
2
3
4
5
6
7
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

다음과 같은 이점을 얻을 수 있다:

  • The filtered list will only be re-evaluated if there are relevant changes to the users array, making filtering much more efficient.
  • Using v-for="user in activeUsers", we only iterate over active users during render, making rendering much more efficient.
  • Logic is now decoupled from the presentation layer, making maintenance (change/extension of logic) much easier.

We get similar benefits from updating:

1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

to:

1
2
3
4
5
6
7
8
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

By moving the v-if to a container element, we’re no longer checking shouldShowUsers for every user in the list. Instead, we check it once and don’t even evaluate the v-for if shouldShowUsers is false.

나쁨

1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
1
2
3
4
5
6
7
8
9
<ul>
<li
v-for="user in users"
v-if="shouldShowUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

좋음

1
2
3
4
5
6
7
8
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
1
2
3
4
5
6
7
8
<ul v-if="shouldShowUsers">
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>

컴포넌트 스타일 스코프 필수

For applications, styles in a top-level App component and in layout components may be global, but all other components should always be scoped.

This is only relevant for single-file components. It does not require that the scoped attribute be used. Scoping could be through CSS modules, a class-based strategy such as BEM, or another library/convention.

Component libraries, however, should prefer a class-based strategy instead of using the scoped attribute.

This makes overriding internal styles easier, with human-readable class names that don’t have too high specificity, but are still very unlikely to result in a conflict.

자세한 설명

If you are developing a large project, working with other developers, or sometimes include 3rd-party HTML/CSS (e.g. from Auth0), consistent scoping will ensure that your styles only apply to the components they are meant for.

Beyond the scoped attribute, using unique class names can help ensure that 3rd-party CSS does not apply to your own HTML. For example, many projects use the button, btn, or icon class names, so even if not using a strategy such as BEM, adding an app-specific and/or component-specific prefix (e.g. ButtonClose-icon) can provide some protection.

나쁨

1
2
3
4
5
6
7
8
9
<template>
<button class="btn btn-close">X</button>
</template>

<style>
.btn-close {
background-color: red;
}
</style>

좋음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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>

Private 속성 이름 필수

플러그인, mixin 등에서 커스텀 사용자 private 프로터피에는 항상 접두사 $_를 사용하라. 그 다음 다른 사람의 코드와 충돌을 피하려면 named scope를 포함하라. (e.g. $_yourPluginName_).

자세한 설명

Vue uses the _ prefix to define its own private properties, so using the same prefix (e.g. _update) risks overwriting an instance property. Even if you check and Vue is not currently using a particular property name, there is no guarantee a conflict won’t arise in a later version.

As for the $ prefix, its purpose within the Vue ecosystem is special instance properties that are exposed to the user, so using it for private properties would not be appropriate.

Instead, we recommend combining the two prefixes into $_, as a convention for user-defined private properties that guarantee no conflicts with Vue.

나쁨

1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}
1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
_update: function () {
// ...
}
}
}
1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}

좋음

1
2
3
4
5
6
7
8
var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Even better!
var myGreatMixin = {
// ...
methods: {
publicMethod() {
// ...
myPrivateFunction()
}
}
}

function myPrivateFunction() {
// ...
}

export default myGreatMixin

우선순위 B 규칙: 매우 추천함 (가독성 향상을 위함)

컴포넌트 파일 매우 추천함

Whenever a build system is available to concatenate files, each component should be in its own file.

This helps you to more quickly find a component when you need to edit it or review how to use it.

나쁨

1
2
3
4
5
6
7
Vue.component('TodoList', {
// ...
})

Vue.component('TodoItem', {
// ...
})

좋음

1
2
3
components/
|- TodoList.js
|- TodoItem.js
1
2
3
components/
|- TodoList.vue
|- TodoItem.vue

싱글 파일 컴포넌트 이름 규칙 지정(casing) 매우 추천함

Filenames of single-file components should either be always PascalCase or always kebab-case.

PascalCase works best with autocompletion in code editors, as it’s consistent with how we reference components in JS(X) and templates, wherever possible. However, mixed case filenames can sometimes create issues on case-insensitive file systems, which is why kebab-case is also perfectly acceptable.

나쁨

1
2
components/
|- mycomponent.vue
1
2
components/
|- myComponent.vue

좋음

1
2
components/
|- MyComponent.vue
1
2
components/
|- my-component.vue

베이스 컴포넌트 이름 매우 추천함

Base components (a.k.a. presentational, dumb, or pure components) that apply app-specific styling and conventions should all begin with a specific prefix, such as Base, App, or V.

자세한 설명

These components lay the foundation for consistent styling and behavior in your application. They may only contain:

  • HTML elements,
  • other base components, and
  • 3rd-party UI components.

But they’ll never contain global state (e.g. from a Vuex store).

Their names often include the name of an element they wrap (e.g. BaseButton, BaseTable), unless no element exists for their specific purpose (e.g. BaseIcon). If you build similar components for a more specific context, they will almost always consume these components (e.g. BaseButton may be used in ButtonSubmit).

Some advantages of this convention:

  • When organized alphabetically in editors, your app’s base components are all listed together, making them easier to identify.

  • Since component names should always be multi-word, this convention prevents you from having to choose an arbitrary prefix for simple component wrappers (e.g. MyButton, VueButton).

  • Since these components are so frequently used, you may want to simply make them global instead of importing them everywhere. A prefix makes this possible with Webpack:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var requireComponent = require.context("./src", true, /^Base[A-Z]/)
    requireComponent.keys().forEach(function (fileName) {
    var baseComponentConfig = requireComponent(fileName)
    baseComponentConfig = baseComponentConfig.default || baseComponentConfig
    var baseComponentName = baseComponentConfig.name || (
    fileName
    .replace(/^.+\//, '')
    .replace(/\.\w+$/, '')
    )
    Vue.component(baseComponentName, baseComponentConfig)
    })

나쁨

1
2
3
4
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

좋음

1
2
3
4
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
1
2
3
4
components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue
1
2
3
4
components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

싱글 인스턴스 컴포넌트 이름 매우 추천함

Components that should only ever have a single active instance should begin with the The prefix, to denote that there can be only one.

This does not mean the component is only used in a single page, but it will only be used once per page. These components never accept any props, since they are specific to your app, not their context within your app. If you find the need to add props, it’s a good indication that this is actually a reusable component that is only used once per page for now.

나쁨

1
2
3
components/
|- Heading.vue
|- MySidebar.vue

좋음

1
2
3
components/
|- TheHeading.vue
|- TheSidebar.vue

강한 연관성을 가진 컴포넌트 이름 매우 추천함

Child components that are tightly coupled with their parent should include the parent component name as a prefix.

If a component only makes sense in the context of a single parent component, that relationship should be evident in its name. Since editors typically organize files alphabetically, this also keeps these related files next to each other.

자세한 설명

You might be tempted to solve this problem by nesting child components in directories named after their parent. For example:

1
2
3
4
5
6
components/
|- TodoList/
|- Item/
|- index.vue
|- Button.vue
|- index.vue

or:

1
2
3
4
5
6
components/
|- TodoList/
|- Item/
|- Button.vue
|- Item.vue
|- TodoList.vue

This isn’t recommended, as it results in:

  • Many files with similar names, making rapid file switching in code editors more difficult.
  • Many nested sub-directories, which increases the time it takes to browse components in an editor’s sidebar.

나쁨

1
2
3
4
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
1
2
3
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

좋음

1
2
3
4
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
1
2
3
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

컴포넌트 이름의 단어 순서 정렬 매우 추천함

Component names should start with the highest-level (often most general) words and end with descriptive modifying words.

자세한 설명

You may be wondering:

“Why would we force component names to use less natural language?”

In natural English, adjectives and other descriptors do typically appear before the nouns, while exceptions require connector words. For example:

  • Coffee with milk
  • Soup of the day
  • Visitor to the museum

You can definitely include these connector words in component names if you’d like, but the order is still important.

Also note that what’s considered “highest-level” will be contextual to your app. For example, imagine an app with a search form. It may include components like this one:

1
2
3
4
5
6
7
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

As you might notice, it’s quite difficult to see which components are specific to the search. Now let’s rename the components according to the rule:

1
2
3
4
5
6
7
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputExcludeGlob.vue
|- SearchInputQuery.vue
|- SettingsCheckboxLaunchOnStartup.vue
|- SettingsCheckboxTerms.vue

Since editors typically organize files alphabetically, all the important relationships between components are now evident at a glance.

You might be tempted to solve this problem differently, nesting all the search components under a “search” directory, then all the settings components under a “settings” directory. We only recommend considering this approach in very large apps (e.g. 100+ components), for these reasons:

  • It generally takes more time to navigate through nested sub-directories, than scrolling through a single components directory.
  • Name conflicts (e.g. multiple ButtonDelete.vue components) make it more difficult to quickly navigate to a specific component in a code editor.
  • Refactoring becomes more difficult, because find-and-replace often isn’t sufficient to update relative references to a moved component.

나쁨

1
2
3
4
5
6
7
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue

좋음

1
2
3
4
5
6
7
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

셀프 클로징 컴포넌트 매우 추천함

Components with no content should be self-closing in single-file components, string templates, and JSX - but never in DOM templates.

Components that self-close communicate that they not only have no content, but are meant to have no content. It’s the difference between a blank page in a book and one labeled “This page intentionally left blank.” Your code is also cleaner without the unnecessary closing tag.

Unfortunately, HTML doesn’t allow custom elements to be self-closing - only official “void” elements. That’s why the strategy is only possible when Vue’s template compiler can reach the template before the DOM, then serve the DOM spec-compliant HTML.

나쁨

1
2
<!-- In single-file components, string templates, and JSX -->
<MyComponent></MyComponent>
1
2
<!-- In DOM templates -->
<my-component/>

좋음

1
2
<!-- In single-file components, string templates, and JSX -->
<MyComponent/>
1
2
<!-- In DOM templates -->
<my-component></my-component>

템플릿에서 컴포넌트 이름 규칙 지정(casing) 매우 추천함

In most projects, component names should always be PascalCase in single-file components and string templates - but kebab-case in DOM templates.

PascalCase has a few advantages over kebab-case:

  • Editors can autocomplete component names in templates, because PascalCase is also used in JavaScript.
  • <MyComponent> is more visually distinct from a single-word HTML element than <my-component>, because there are two character differences (the two capitals), rather than just one (a hyphen).
  • If you use any non-Vue custom elements in your templates, such as a web component, PascalCase ensures that your Vue components remain distinctly visible.

Unfortunately, due to HTML’s case insensitivity, DOM templates must still use kebab-case.

Also note that if you’ve already invested heavily in kebab-case, consistency with HTML conventions and being able to use the same casing across all your projects may be more important than the advantages listed above. In those cases, using kebab-case everywhere is also acceptable.

나쁨

1
2
<!-- In single-file components and string templates -->
<mycomponent/>
1
2
<!-- In single-file components and string templates -->
<myComponent/>
1
2
<!-- In DOM templates -->
<MyComponent></MyComponent>

좋음

1
2
<!-- In single-file components and string templates -->
<MyComponent/>
1
2
<!-- In DOM templates -->
<my-component></my-component>

OR

1
2
<!-- Everywhere -->
<my-component></my-component>

JS/JSX에서 컴포넌트 이름 규칙 지정(casing) 매우 추천함

Component names in JS/JSX should always be PascalCase, though they may be kebab-case inside strings for simpler applications that only use global component registration through Vue.component.

자세한 설명

In JavaScript, PascalCase is the convention for classes and prototype constructors - essentially, anything that can have distinct instances. Vue components also have instances, so it makes sense to also use PascalCase. As an added benefit, using PascalCase within JSX (and templates) allows readers of the code to more easily distinguish between components and HTML elements.

However, for applications that use only global component definitions via Vue.component, we recommend kebab-case instead. The reasons are:

  • It’s rare that global components are ever referenced in JavaScript, so following a convention for JavaScript makes less sense.
  • These applications always include many in-DOM templates, where kebab-case must be used.

나쁨

1
2
3
Vue.component('myComponent', {
// ...
})
1
import myComponent from './MyComponent.vue'
1
2
3
4
export default {
name: 'myComponent',
// ...
}
1
2
3
4
export default {
name: 'my-component',
// ...
}

좋음

1
2
3
Vue.component('MyComponent', {
// ...
})
1
2
3
Vue.component('my-component', {
// ...
})
1
import MyComponent from './MyComponent.vue'
1
2
3
4
export default {
name: 'MyComponent',
// ...
}

전체 이름 컴포넌트 이름 매우 추천함

Component names should prefer full words over abbreviations.

The autocompletion in editors make the cost of writing longer names very low, while the clarity they provide is invaluable. Uncommon abbreviations, in particular, should always be avoided.

나쁨

1
2
3
components/
|- SdSettings.vue
|- UProfOpts.vue

좋음

1
2
3
components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

Prop 이름 규칙 지정(casing) 매우 추천함

Prop names should always use camelCase during declaration, but kebab-case in templates and JSX.

We’re simply following the conventions of each language. Within JavaScript, camelCase is more natural. Within HTML, kebab-case is.

나쁨

1
2
3
props: {
'greeting-text': String
}
1
<WelcomeMessage greetingText="hi"/>

좋음

1
2
3
props: {
greetingText: String
}
1
<WelcomeMessage greeting-text="hi"/>

다중 속성 엘리먼트 매우 추천함

Elements with multiple attributes should span multiple lines, with one attribute per line.

In JavaScript, splitting objects with multiple properties over multiple lines is widely considered a good convention, because it’s much easier to read. Our templates and JSX deserve the same consideration.

나쁨

1
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo">
1
<MyComponent foo="a" bar="b" baz="c"/>

좋음

1
2
3
4
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo"
>
1
2
3
4
5
<MyComponent
foo="a"
bar="b"
baz="c"
/>

템플릿에서 단순한 표현식 매우 추천함

Component templates should only include simple expressions, with more complex expressions refactored into computed properties or methods.

Complex expressions in your templates make them less declarative. We should strive to describe what should appear, not how we’re computing that value. Computed properties and methods also allow the code to be reused.

나쁨

1
2
3
4
5
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}

좋음

1
2
<!-- In a template -->
{{ normalizedFullName }}
1
2
3
4
5
6
7
8
// 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(' ')
}
}

단순한 계산된 속성 매우 추천함

Complex computed properties should be split into as many simpler properties as possible.

자세한 설명

Simpler, well-named computed properties are:

  • Easier to test

    When each computed property contains only a very simple expression, with very few dependencies, it’s much easier to write tests confirming that it works correctly.

  • Easier to read

    Simplifying computed properties forces you to give each value a descriptive name, even if it’s not reused. This makes it much easier for other developers (and future you) to focus in on the code they care about and figure out what’s going on.

  • More adaptable to changing requirements

    Any value that can be named might be useful to the view. For example, we might decide to display a message telling the user how much money they saved. We might also decide to calculate sales tax, but perhaps display it separately, rather than as part of the final price.

    Small, focused computed properties make fewer assumptions about how information will be used, so require less refactoring as requirements change.

나쁨

1
2
3
4
5
6
7
8
9
computed: {
price: function () {
var basePrice = this.manufactureCost / (1 - this.profitMargin)
return (
basePrice -
basePrice * (this.discountPercent || 0)
)
}
}

좋음

1
2
3
4
5
6
7
8
9
10
11
computed: {
basePrice: function () {
return this.manufactureCost / (1 - this.profitMargin)
},
discount: function () {
return this.basePrice * (this.discountPercent || 0)
},
finalPrice: function () {
return this.basePrice - this.discount
}
}

속성 값에 따옴표 매우 추천함

Non-empty HTML attribute values should always be inside quotes (single or double, whichever is not used in JS).

While attribute values without any spaces are not required to have quotes in HTML, this practice often leads to avoiding spaces, making attribute values less readable.

나쁨

1
<input type=text>
1
<AppSidebar :style={width:sidebarWidth+'px'}>

좋음

1
<input type="text">
1
<AppSidebar :style="{ width: sidebarWidth + 'px' }">

축약형 디렉티브 매우 추천함

Directive shorthands (: for v-bind:, @ for v-on: and # for v-slot) should be used always or never.

나쁨

1
2
3
4
<input
v-bind:value="newTodoText"
:placeholder="newTodoInstructions"
>
1
2
3
4
<input
v-on:input="onInput"
@focus="onFocus"
>
1
2
3
4
5
6
7
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>

좋음

1
2
3
4
<input
:value="newTodoText"
:placeholder="newTodoInstructions"
>
1
2
3
4
<input
v-bind:value="newTodoText"
v-bind:placeholder="newTodoInstructions"
>
1
2
3
4
<input
@input="onInput"
@focus="onFocus"
>
1
2
3
4
<input
v-on:input="onInput"
v-on:focus="onFocus"
>
1
2
3
4
5
6
7
<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>
1
2
3
4
5
6
7
<template #header>
<h1>Here might be a page title</h1>
</template>

<template #footer>
<p>Here's some contact info</p>
</template>

우선순위 C 규칙: 추천함 (선택의 혼란 또는 판단 오버헤드 최소화)

컴포넌트/인스턴스 옵션 순서 추천함

컴포넌트/인스턴스 옵션의 순서는 언제나 일정한 순서로 정렬되어야 합니다.

아래는 권장하는 컴포넌트 옵션 순서입니다. 유형별로 나누어 놓았으므로, 플러그인으로 추가되는 새로운 옵션들 역시 이에 맞추어 정렬하면 됩니다.

  1. 사이드 이펙트(Side Effects) (컴포넌트 외부에 효과가 미치는 옵션)

    • el
  2. 전역 인지(Global Awareness) (컴포넌트 바깥의 지식을 필요로 하는 옵션)

    • name
    • parent
  3. 컴포넌트 유형(Component Type) (컴포넌트의 유형을 바꾸는 옵션)

    • functional
  4. 템플릿 변경자(Template Modifiers) (템플릿이 컴파일되는 방식을 바꾸는 옵션)

    • delimiters
    • comments
  5. 템플릿 의존성(Template Dependencies) (템플릿에 이용되는 요소들을 지정하는 옵션)

    • components
    • directives
    • filters
  6. 컴포지션(Composition) (다른 컴포넌트의 속성을 가져와 합치는 옵션)

    • extends
    • mixins
  7. 인터페이스(Interface) (컴포넌트의 인터페이스를 지정하는 옵션)

    • inheritAttrs
    • model
    • props/propsData
  8. 지역 상태(Local State) (반응적인 지역 속성들을 설정하는 옵션)

    • data
    • computed
  9. 이벤트(Events) (반응적인 이벤트에 의해 실행되는 콜백을 지정하는 옵션)

    • watch
    • 라이프사이클 이벤트 (호출 순서대로 정렬)
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeUpdate
      • updated
      • activated
      • deactivated
      • beforeDestroy
      • destroyed
  10. 비반응적 속성(Non-Reactive Properties) (시스템의 반응성과 관계 없는 인스턴스 속성을 지정하는 옵션)

    • methods
  11. 렌더링(Rendering) (컴포넌트 출력을 선언적으로 지정하는 옵션)

    • template/render
    • renderError

엘리먼트 속성 순서 추천함

엘리먼트 및 컴포넌트의 속성은 언제나 일정한 순서로 정렬되어야 합니다.

아래는 권장하는 엘리먼트 속성 순서입니다. 유형별로 나누어 놓았으므로, 커스텀 속성이나 directive 역시 이에 맞추어 정렬하면 됩니다.

  1. 정의(Definition) (컴포넌트 옵션을 제공하는 속성)

    • is
  2. 리스트 렌더링(List Rendering) (같은 엘리먼트의 변형을 여러 개 생성하는 속성)

    • v-for
  3. 조건부(Conditionals) (엘리먼트가 렌더링되는지 혹은 보여지는지 여부를 결정하는 속성)

    • v-if
    • v-else-if
    • v-else
    • v-show
    • v-cloak
  4. 렌더 변경자(Render Modifiers) (엘리먼트의 렌더링 방식을 변경하는 속성)

    • v-pre
    • v-once
  5. 전역 인지(Global Awareness) (컴포넌트 바깥의 지식을 요구하는 속성)

    • id
  6. 유일한 속성(Unique Attributes) (유일한 값을 가질 것을 요구하는 속성)

    • ref
    • key
    • slot
  7. 양방향 바인딩(Two-Way Binding) (바인딩과 이벤트를 결합하는 속성)

    • v-model
  8. 기타 속성 (따로 언급하지 않은 속성들)

  9. 이벤트(Events) (컴포넌트 이벤트 리스너를 지정하는 속성)

    • v-on
  10. 내용(Content) (엘리먼트의 내용을 덮어쓰는 속성)

    • v-html
    • v-text

컴포넌트/인스턴스 옵션간 빈 줄 추천함

You may want to add one empty line between multi-line properties, particularly if the options can no longer fit on your screen without scrolling.

When components begin to feel cramped or difficult to read, adding spaces between multi-line properties can make them easier to skim again. In some editors, such as Vim, formatting options like this can also make them easier to navigate with the keyboard.

좋음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
props: {
value: {
type: String,
required: true
},

focused: {
type: Boolean,
default: false
},

label: String,
icon: String
},

computed: {
formattedValue: function () {
// ...
},

inputClasses: function () {
// ...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// No spaces are also fine, as long as the component
// is still easy to read and navigate.
props: {
value: {
type: String,
required: true
},
focused: {
type: Boolean,
default: false
},
label: String,
icon: String
},
computed: {
formattedValue: function () {
// ...
},
inputClasses: function () {
// ...
}
}

싱글 파일 컴포넌트 최상위 엘리먼트 순서 추천함

Single-file components should always order <script>, <template>, and <style> tags consistently, with <style> last, because at least one of the other two is always necessary.

나쁨

1
2
3
<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>
1
2
3
4
5
6
7
8
9
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

좋음

1
2
3
4
5
6
7
8
9
<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>
1
2
3
4
5
6
7
8
9
<!-- ComponentA.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

우선순위 D 규칙: 주의요함 (잠재적인 위험을 내포한 패턴)

key가 없는 v-if/v-if-else/v-else 주의요함

It’s usually best to use key with v-if + v-else, if they are the same element type (e.g. both <div> elements).

By default, Vue updates the DOM as efficiently as possible. That means when switching between elements of the same type, it simply patches the existing element, rather than removing it and adding a new one in its place. This can have unintended consequences if these elements should not actually be considered the same.

나쁨

1
2
3
4
5
6
<div v-if="error">
Error: {{ error }}
</div>
<div v-else>
{{ results }}
</div>

좋음

1
2
3
4
5
6
7
8
9
10
11
12
<div
v-if="error"
key="search-status"
>
Error: {{ error }}
</div>
<div
v-else
key="search-results"
>
{{ results }}
</div>

scoped에서 엘리먼트 셀렉터 사용 주의요함

Element selectors should be avoided with scoped.

Prefer class selectors over element selectors in scoped styles, because large numbers of element selectors are slow.

자세한 설명

To scope styles, Vue adds a unique attribute to component elements, such as data-v-f3f3eg9. Then selectors are modified so that only matching elements with this attribute are selected (e.g. button[data-v-f3f3eg9]).

The problem is that large numbers of element-attribute selectors (e.g. button[data-v-f3f3eg9]) will be considerably slower than class-attribute selectors (e.g. .btn-close[data-v-f3f3eg9]), so class selectors should be preferred whenever possible.

나쁨

1
2
3
4
5
6
7
8
9
<template>
<button>X</button>
</template>

<style scoped>
button {
background-color: red;
}
</style>

좋음

1
2
3
4
5
6
7
8
9
<template>
<button class="btn btn-close">X</button>
</template>

<style scoped>
.btn-close {
background-color: red;
}
</style>

부모-자식간 의사소통 주의요함

Props and events should be preferred for parent-child component communication, instead of this.$parent or mutating props.

An ideal Vue application is props down, events up. Sticking to this convention makes your components much easier to understand. However, there are edge cases where prop mutation or this.$parent can simplify two components that are already deeply coupled.

The problem is, there are also many simple cases where these patterns may offer convenience. Beware: do not be seduced into trading simplicity (being able to understand the flow of your state) for short-term convenience (writing less code).

나쁨

1
2
3
4
5
6
7
8
9
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: '<input v-model="todo.text">'
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
methods: {
removeTodo () {
var vm = this
vm.$parent.todos = vm.$parent.todos.filter(function (todo) {
return todo.id !== vm.todo.id
})
}
},
template: `
<span>
{{ todo.text }}
<button @click="removeTodo">
X
</button>
</span>
`
})

좋음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<input
:value="todo.text"
@input="$emit('input', $event.target.value)"
>
`
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('TodoItem', {
props: {
todo: {
type: Object,
required: true
}
},
template: `
<span>
{{ todo.text }}
<button @click="$emit('delete')">
X
</button>
</span>
`
})

전역 상태 관리 주의요함

전역 상태 관리에는 this.$root 나 글로벌 이벤트 버스(global event bus)보다는 Vuex를 이용하는 것이 좋습니다.

전역 상태 관리에 this.$root글로벌 이벤트 버스를 이용하는 것은 아주 단순한 경우에는 편리할 수 있지만 대부분의 Application에는 부적절합니다. Vuex는 상태를 관리하는 중앙 저장소를 제공하는 것만이 아니라 상태 변화를 체계화하고, 추적하며, 디버깅을 용이하게 하는 도구들을 제공합니다.

나쁨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.js
new Vue({
data: {
todos: []
},
created: function () {
this.$on('remove-todo', this.removeTodo)
},
methods: {
removeTodo: function (todo) {
var todoIdToRemove = todo.id
this.todos = this.todos.filter(function (todo) {
return todo.id !== todoIdToRemove
})
}
}
})

좋음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// store/modules/todos.js
export default {
state: {
list: []
},
mutations: {
REMOVE_TODO (state, todoId) {
state.list = state.list.filter(todo => todo.id !== todoId)
}
},
actions: {
removeTodo ({ commit, state }, todo) {
commit('REMOVE_TODO', todo.id)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- TodoItem.vue -->
<template>
<span>
{{ todo.text }}
<button @click="removeTodo(todo)">
X
</button>
</span>
</template>

<script>
import { mapActions } from 'vuex'

export default {
props: {
todo: {
type: Object,
required: true
}
},
methods: mapActions(['removeTodo'])
}
</script>