Vue 스타일 가이드

오민영·2022년 2월 16일
1

Vue.js

목록 보기
10/10
post-thumbnail

필수

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

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 정의 🌟

Props은 가능한 상세하게 정의되어야 한다.

커밋 된 코드에서 prop 정의는 적어도 type은 명시되도록 가능한 상세하게 정의되는게 좋다.

자세한 prop 정의는 두 가지 이점을 갖는다.

  • API는 컴포넌트 API를 문서화하므로 컴포넌트의 사용 방법을 쉽게 알 수 있다.
  • 개발 중에 Vue는 컴포넌트 타입이 잘못 지정된 props를 전달하면 경고 메시지를 표시하기 때문에, 잠재적 원인을 파악할 수 있도록 도와준다.
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와 항상 함께 사용한다.

서브트리 내부 컴포넌트 상태를 유지하기 위해, 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-if 와 v-for 를 동시에 사용하지 않는다. 🌟

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>

Private 속성 이름

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

싱글 파일 컴포넌트 이름 규칙 지정(casing)

싱글 파일 컴포넌트의 파일명은 항상 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>

템플릿에서 컴포넌트 이름 규칙 지정(casing)

보편적으로, 단일 파일 컴포넌트 / 단순 문자열 템플릿 에서는 PascalCase로 작명하고, DOM 템플릿에서는 kebab-case로 작명한다.

파스칼 케이스로 작명했을 때 몇 가지 장점이 있다.

  • PascalCase는 자바스크립트에서도 사용되기 때문에 개발시 템플릿에서 UI 컴포넌트 이름을 자동으로 완성할 수 있다.
  • 하나의 단어인 HTML 엘리먼트 요소와 시각적으로 더 구별된다.
<!-- In single-file components and string templates -->
<MyComponent/>

<!-- In DOM templates -->
<my-component></my-component>
OR

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

🌟 JS/JSX 에서 컴포넌트 이름(name) 규칙 지정 🌟

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

🌟 Prop 이름 규칙 지정(casing) 🌟

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>

추천

Reference

vue style guide

profile
이것저것 정리하는 공간

0개의 댓글