Vue.js 에서 권장하는 스타일 가이드. 코딩 컨벤션 같은 거랄까..



가이드 하는 내용 중 필수매우추천함 항목에 대해 설명한다.


1. 컴포넌트 이름에 합성어 사용

root 컴포넌트인 App 컴포넌트를 제외하고는 컴포넌트의 이름은 항상 "합성-어"를 사용해야 한다.

모든 HTML 엘리먼트의 이름은 한 단어로 표현되기 때문에, 충돌을 방지하려면 컴포넌트를 잘 설명할 수 있는 하이픈으로 구분된 합성어를 사용하는것이 좋다.

Bad

Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}

Good

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

2. 컴포넌트 데이터

컴포넌트 데이터는 함수여야 한다.
한 컴포넌트에서 data 프로퍼티를 사용할 때, 그 값은 무조건 한 객체를 리턴하는 함수여야 한다.

함수가 아닌 그냥 일반 객체일 경우, 컴포넌트를 두 영역에 렌더링 시 같은 data 객체를 참조하게 되어, 두 컴포넌트가 같은 값을 공유하게 되는 문제가 발생한다. (의도적인 거라면 할말없지만, mvvm 패턴을 따르지 않는 잘못된 방식이다.)

따라서 각 사용처마다 독립적인 data 객체를 가져야 하므로 꼭 함수로 정의하여 리터럴 객체를 리턴하도록 구현해야 한다.

Bad

Vue.component('some-comp', {
data: {
foo: 'bar'
}
})
export default {
data: {
foo: 'bar'
}
}

Good

Vue.component('some-comp', {
data: function () {
return {
foo: 'bar'
}
}
})
// In a .vue file
export default {
data () {
return {
foo: 'bar'
}
}
}
// 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'
}
})


3. Props 정의

Prop 정의는 가능한 가장 자세히 기술해야 한다.
최소 type 이라도 기술하자.

Bad

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

Good

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

4. v-for에 key 지정

v-for 사용시 항상 key를 지정해라.
내부 컴포넌트의 하위 트리의 상태를 유지하기 위해 key 가 필요하다. 

Vue.js 는 가상 돔 렌더링을 한다.
v-for 와 같은 디렉티브는 바인딩 된 배열에 따라 내용이 그려진다.
배열에 아이템이 하나라도 추가되면 전체 내용을 다시 그려야 하는 비싼 연산이 될 수 있다.
이련 경우를 방지하고자, 각 아이템의 key를 알려주면 정확한 비교가 가능하므로, 가상돔 업데이트 연산 과정이 명확하고 빠르게 수행되어 불필요한 업데이트를 방지 할 수 있다.

Bad

<ul>
<li v-for="todo in todos">
{{ todo.text }}
</li>
</ul>

Good

<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

5. v-if 와 v-for 를 동시에 사용하지 마세요

Vue가 디렉티브를 처리하는 과정에서, v-for 가 v-if 보다 우선순위가 더 높다.
따라서 아래와 같은 템플릿이
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
<li>
</ul>
이렇게 해석 될 수 있다.
this.users.map(function (user) {
if (user.isActive) {
return user.name
}
})

따라서 users 의 일부분만 렌더링 하려는 의도임에도, active user 가 변경되었는지 여부에 관계없이, 리렌더링 할때마다 전체 users 목록을 항상 순회해야 한다.
그 대신 아래와 같이 계산된 프로퍼티를 순회해라.
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
<ul>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
<li>
</ul>
이렇게 구현하면 아래와 같은 이점을 얻는다.
  • 필터링 된 리스트는 users 배열에 active 변경사항이 있는 경우만 다시 계산되므로 필터링이 훨씬 효과적이다.
  • v-for="users in activeUsers" 를 사용하면, 렌더링 동안에 active users 만 순회하므로 렌더링이 훨씬 효과적이다.
  • 로직과 화면 영역이 분리되어, 유지관리가 훨씬 수월 해 진다.

Bad

<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>

Good

<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>

6. 컴포넌트 스타일 스코프

최상단 App 컴포넌트와 layout 관련 컴포넌트의 스타일은 전역이 될 수도 있지만, 다른 모든 컴포넌트듸 스타일은 항상 scoped 여야 한다.

이거는 단순히 싱글 파일 컴포넌트에 대한 얘기이다. 항상 scoped 속성을 요구하는것이 아니다. 스코핑은 CSS module 을 통해 정의될 수도있고, BEM 과 같은 클래스-기반 방법도 있다.

컴포넌트 라이브러리들은  scoped 속성을 사용하는 대신 클래스-기반 전략을 취해야 한다.
클래스-기반 전략을 취하면 내부 스타일을 재정의 하는것이 더 쉬우며, 사람이 읽을 수 있는 클래스 이름을 사용하면(높은 특정도를 가지지는 않지만) 충돌을 일으키지는 않는다.

만약 다른 개발자들과, 외부 다른 서드파티 HTML/CSS 를 포함한 큰 프로젝트를 개발한다고 한다면, 일관된 스코핑은 스타일이 의도된 컴포넌트에만 적용되도록 확신할 수 있다.

scpoed 속성 외에도, 유니크한 클래스이름 사용은 서드파티와 충돌을 일으키지 않는데 도움을 받을 수 있다. 

예를들면 많은 프로젝트가 button, btn, icon 같은 클래스 이름을 사용하는데, 
굳이 BEM 스타일을 사용하지 않아도, 프로젝트나 및 컴포넌트 별로 ButtonClose-icon 같은 접두사를 추가하면 충돌을 방지할 수 있는 보호장치를 마련할 수 있다.

Bad

<template>
<button class="btn btn-close">X</button>
</template>

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

Good

<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>


7. Private 속성 이름

plugin, mixin 등 을 정의할 때 private 속성은 항상 $_ 접두어를 사용하여 정의해라.

_ 접두어는 Vue 에서 private 속성들을 정의하는데 사용하므로, _ 접두어 사용은 vue 인스턴스의 속성을 오버라이딩 할 수 있는 위험성이 있다. 만약 그 속성명이 사용되지 않음을 확인했다 치더라도, 다음 버전에서 충돌이 일어나지 않을것이라는 보장이 없다.

$ 접두어는 Vue 에코시스템 안에서 사용자에게 노출시키려는 의미를 가진 특별한 속성이다. 따라서 $ 접두어 사용도 적절치 않다.

대신 두 접두어를 혼합한 $_ 접두어를 추천한다. 컨벤션으로 $_ 접두어는 사용자 정의 private 속성을 보장하여 Vue 와 충돌이 없다.


Bad

var myGreatMixin = {
// ...
methods: {
update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
_update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$update: function () {
// ...
}
}
}
var myGreatMixin = {
// ...
methods: {
$_update: function () {
// ...
}
}
}

Good

var myGreatMixin = {
// ...
methods: {
$_myGreatMixin_update: function () {
// ...
}
}
}






+ Recent posts