Vue.js 스타일 가이드
린트
우리는 plugin:vue/recommended
와 함께 eslint-vue-plugin를 기본으로 설정합니다. 자세한 설명은 rules에서 확인하세요.
기본 규칙
- 서비스는 자체 파일을 갖습니다.
- 스토어는 자체 파일을 갖습니다.
-
번들 파일에서 Vue 컴포넌트를 인스턴스화하기 위해 함수를 사용하세요:
// 안 좋은 예 class { init() { new Component({}) } } // 좋은 예 document.addEventListener('DOMContentLoaded', () => new Vue({ el: '#element', components: { componentName }, render: createElement => createElement('component-name'), }));
-
서비스나 스토어에 대해 싱글톤을 사용하지 마십시오.
// 안 좋은 예 class Store { constructor() { if (!this.prototype.singleton) { // 무언가 수행 } } } // 좋은 예 class Store { constructor() { // 무언가 수행 } }
-
Vue 템플릿에는
.vue
를 사용하세요. HAML에서%template
를 사용하지 마세요. -
Vue 앱으로 전달되는 데이터를 명시적으로 정의하세요.
// 안 좋은 예 return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, provide: { ...someDataset }, props: { ...anotherDataset }, render: createElement => createElement('component-name'), }); // 좋은 예 const { foobar, barfoo } = someDataset; const { foo, bar } = anotherDataset; return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, provide: { foobar, barfoo }, props: { foo, bar }, render: createElement => createElement('component-name'), });
위와 같은 특정 경우에는 스프레드 연산자의 사용을 권장하지 않습니다. 코드베이스를 명확하고 검색 가능하게 유지하기 위해 해당 패턴을 사용하세요. 또한, 위의 예시와 같은 경우에는 스프레드 연산자 대신에 이러한 패턴을 사용하면 비스칼라 값을 쉽게 파싱할 수 있게 됩니다.
return new Vue({ el: '#element', name: 'ComponentNameRoot', components: { componentName }, props: { foo, bar: parseBoolean(bar) }, render: createElement => createElement('component-name'), });
네이밍
-
확장자: Vue 컴포넌트에는
.vue
확장자를 사용하세요..js
확장자를 사용하지 마세요 (#34371). -
참조 네이밍: 기본 가져오기에는 PascalCase를 사용하세요.
// 안 좋은 예 import cardBoard from 'cardBoard.vue' components: { cardBoard, }; // 좋은 예 import CardBoard from 'cardBoard.vue' components: { CardBoard, };
- 프롭스 네이밍: DOM 컴포넌트 프롭 이름을 사용하지 마세요.
-
프롭스 네이밍: 템플릿에서 프롭을 제공할 때 camelCase 대신 kebab-case를 사용하세요.
// 안 좋은 예 <component class="btn"> // 좋은 예 <component css-class="btn"> // 안 좋은 예 <component myProp="prop" /> // 좋은 예 <component my-prop="prop" />
정렬
-
템플릿 메서드에 대해 다음과 같은 정렬 스타일을 따르세요:
-
둘 이상의 속성이 있는 경우 모든 속성은 새 줄에 있어야 합니다:
// 안 좋은 예 <component v-if="bar" param="baz" /> <button class="btn">Click me</button> // 좋은 예 <component v-if="bar" param="baz" /> <button class="btn"> Click me </button>
-
한 개의 속성이 있는 경우 태그는 인라인이어야 합니다:
// 좋은 예 <component bar="bar" /> // 좋은 예 <component bar="bar" /> // 안 좋은 예 <component bar="bar" />
-
따옴표
-
템플릿 내에서는 항상 이중 따옴표
"
를 사용하고, 다른 JavaScript 코드에는 작은 따옴표'
를 사용하세요.// 안 좋은 예 template: ` <button :class='style'>Button</button> ` // 좋은 예 template: ` <button :class="style">Button</button>
프롭스
-
프롭스는 객체로 선언되어야 합니다.
// 안 좋은 예 props: ['foo'] // 좋은 예 props: { foo: { type: String, required: false, default: 'bar' } }
-
프롭 선언 시 항상 required 키를 제공해야 합니다.
// 안 좋은 예 props: { foo: { type: String, } } // 좋은 예 props: { foo: { type: String, required: false, default: 'bar' } }
-
프롭이 필요하지 않은 경우 default 키를 제공해야 합니다. 프로퍼티의 존재 여부를 확인해야 하는 시나리오도 있습니다. 이러한 경우에는 default 키를 제공해서는 안 됩니다.
// 좋은 예 props: { foo: { type: String, required: false, } } // 좋은 예 props: { foo: { type: String, required: false, default: 'bar' } } // 좋은 예 props: { foo: { type: String, required: true } }
데이터
-
data
메서드는 항상 함수여야 합니다.// 안 좋은 예 data: { foo: 'foo' } // 좋은 예 data() { return { foo: 'foo' }; }
디렉티브
-
Shorthand
@
는v-on
보다 선호됩니다.// 안 좋은 예 <component v-on:click="eventHandler"/> // 좋은 예 <component @click="eventHandler"/>
-
Shorthand
:
는v-bind
보다 선호됩니다.// 안 좋은 예 <component v-bind:class="btn"/> // 좋은 예 <component :class="btn"/>
-
Shorthand
#
는v-slot
보다 선호됩니다.// 안 좋은 예 <template v-slot:header></template> // 좋은 예 <template #header></template>
닫는 태그
-
가능한 경우 self-closing 컴포넌트 태그를 사용하세요.
// 안 좋은 예 <component></component> // 좋은 예 <component />
템플릿 내에서 컴포넌트 사용
-
템플릿에서 컴포넌트의 kebab-case 이름을 다른 스타일보다 선호하세요.
// 안 좋은 예 <MyComponent /> // 좋은 예 <my-component />
순서
-
.vue
파일의 태그 순서<script> // ... </script> <template> // ... </template> // 우리는 scoped 스타일을 사용하지 않지만 이와 같은 경우가 몇 군데 있습니다 <style> // ... </style>
-
Vue 컴포넌트의 속성 순서: 컴포넌트 속성의 순서에 대해 order of properties in components rule를 확인하세요.
:key
v-for
를 사용할 때 매 항목마다 고유한 :key
속성을 제공해야 합니다.
-
배열의 요소에 고유한
id
가 있는 경우 해당id
를 사용하는 것이 좋습니다:<div v-for="item in items" :key="item.id" > <!-- 내용 --> </div>
-
반복되는 요소에 고유한 ID가 없는 경우 배열 인덱스를
:key
속성으로 사용할 수 있습니다.<div v-for="(item, index) in items" :key="index" > <!-- 내용 --> </div>
-
v-for
를template
과 함께 사용하고 여러 자식 요소가 있는 경우,:key
값은 필수적으로 고유해야 합니다.kebab-case
네임스페이스를 사용하는 것이 좋습니다.<template v-for="(item, index) in items"> <span :key="`span-${index}`"></span> <button :key="`button-${index}`"></button> </template>
-
중첩된
v-for
를 다룰 때에도 위와 같은 지침을 사용하세요.<div v-for="item in items" :key="item.id" > <span v-for="element in array" :key="element.id" > <!-- 내용 --> </span> </div>
유용한 링크:
Vue 테스팅
시간이 지남에 따라 Vue 컴포넌트를 효과적으로 테스트하기 위해 몇 가지 프로그래밍 패턴과 스타일 기호가 발전해왔습니다. 다음 가이드에서는 이러한 패턴 중 일부를 설명합니다. 이것들은 엄격한 지침이 아니라 Vue 테스트를 GitLab에서 어떻게 작성하는지에 대한 통찰을 제공하기 위한 제안과 좋은 관행의 모음입니다.
컴포넌트 마운팅
일반적으로 Vue 컴포넌트를 테스트할 때마다 컴포넌트를 “다시 마운트”해야 합니다.
이를 달성하기 위해:
- 최상위
describe
블록 내부에 변경 가능한wrapper
변수를 생성합니다. -
mount
또는shallowMount
를 사용하여 컴포넌트를 마운트합니다. - 결과로 나온
Wrapper
인스턴스를wrapper
변수에 재할당합니다.
글로벌하고 변경 가능한 wrapper
를 만드는 것은 컴포넌트/DOM 요소를 찾기 위한 공통 함수를 정의하는 등 여러 이점을 제공합니다.
import MyComponent from '~/path/to/my_component.vue';
describe('MyComponent', () => {
let wrapper;
// 이제 이것은 테스트 전체에서 재사용할 수 있습니다
const findMyComponent = wrapper.findComponent(MyComponent);
// ...
})
- 컴포넌트를 마운트하기 위해
beforeEach
블록을 사용할 수 있습니다(createComponent
팩터리에서 더 많은 정보를 확인하세요). - 컴포넌트를 자동으로 파괴시키기 위해
shared_test_setup.js
에 설정된enableAutoDestroy
를 사용할 수 있습니다.
createComponent
팩터리
마운팅 로직을 중복해서 작성하는 것을 피하기 위해 createComponent
팩터리 함수를 정의하는 것이 유용합니다. 이는 각 테스트 블록에서 재사용할 수 있는 클로저이며, mount
와 shallowMount
의 결과를 wrapper
변수에 재할당해야 합니다.
import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';
describe('MyComponent', () => {
// "글로벌" wrapper 변수를 초기화합니다. 이것은 테스트 전체에서 사용됩니다:
let wrapper;
// `createComponent` 팩터리를 정의합니다:
function createComponent() {
// 컴포넌트를 마운트하고 `wrapper`를 재할당합니다:
wrapper = shallowMount(MyComponent);
}
it('마운트됩니다', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` 프롭이 기본적으로 `false`입니다', () => {
createComponent();
expect(wrapper.props('isLoading')).toBe(false);
});
})
마찬가지로, beforeEach
블록에서 createComponent
를 호출하여 테스트의 중복을 제거할 수 있습니다:
import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';
describe('MyComponent', () => {
// "글로벌" wrapper 변수를 초기화합니다. 이것은 테스트 전체에서 사용됩니다:
let wrapper;
// `createComponent` 팩터리를 정의합니다:
function createComponent() {
// 컴포넌트를 마운트하고 `wrapper`를 재할당합니다:
wrapper = shallowMount(MyComponent);
}
beforeEach(() => {
createComponent();
});
it('마운트됩니다', () => {
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` 프롭이 기본적으로 `false`입니다', () => {
expect(wrapper.props('isLoading')).toBe(false);
});
})
createComponent
의 최선의 방법
-
많은 매개변수 대신 단일(또는 제한된 수의) 객체 인수를 사용하는 것이 좋습니다.
props
와 같은 공통 데이터에 대한 단일 매개변수 정의는 괜찮지만, JavaScript 스타일 가이드를 참고하고 매개변수 수 제한 내에 유지하세요:// bad function createComponent(props, stubs, mountFn, foo) { } // good function createComponent({ props, stubs, mountFn, foo } = {}) { } // good function createComponent(props = {}, { stubs, mountFn, foo } = {}) { }
-
동일한 테스트 세트 내에서
mount
그리고shallowMount
둘 다 필요한 경우, 컴포넌트를 마운트하는 데 사용할 마운트 함수(mount
또는shallowMount
)를 사용할 수 있는mountFn
매개변수를createComponent
팩터리로 정의하는 것이 유용합니다:import { shallowMount } from '@vue/test-utils'; function createComponent({ mountFn = shallowMount } = {}) { }
-
wrapper.findByTestId()
를 노출하는mountExtended
와shallowMountExtended
도우미를 사용하세요:import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { SomeComponent } from 'components/some_component.vue'; let wrapper; const createWrapper = () => { wrapper = shallowMountExtended(SomeComponent); }; const someButton = () => wrapper.findByTestId('someButtonTestId');
-
data
,methods
또는 컴포넌트 내부를 확장하는 기타 마운팅 옵션을 사용하는 것을 피하세요.
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { SomeComponent } from 'components/some_component.vue';
let wrapper;
// bad :( - 이는 실제 사용자 상호작용을 우회하고 테스트를 컴포넌트 내부에 결합시킵니다.
const createWrapper = ({ data }) => {
wrapper = shallowMountExtended(SomeComponent, {
data
});
};
// good :) - `clickShowButton`과 같은 도우미는 실제 I/O와 상호작용합니다.
const createWrapper = () => {
wrapper = shallowMountExtended(SomeComponent);
};
const clickShowButton = () => {
wrapper.findByTestId('show').trigger('click');
}
컴포넌트 상태 설정
-
가능한 경우 컴포넌트 상태를 설정하기 위해
setProps
를 사용하지 마십시오. 대신 컴포넌트를 마운트할 때propsData
를 설정하십시오.// 안 좋은 예 wrapper = shallowMount(MyComponent); wrapper.setProps({ myProp: 'my cool prop' }); // 좋은 예 wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
여기서의 예외는 특정 방식으로 컴포넌트의 반응성을 테스트하고자 하는 경우입니다. 예를 들어, 특정 워쳐가 실행된 후에 컴포넌트의 출력을 테스트하고자 하는 경우
setProps
를 사용하여 해당 동작을 테스트하는 것은 괜찮습니다. -
컴포넌트의 내부 상태를 설정하는
setData
를 사용하지 마십시오. 대신, 컴포넌트의 자식요소에서 이벤트를 트리거하거나 다른 부수효과를 일으켜 상태 변경을 강제하십시오.
컴포넌트 상태 접근
-
속성 또는 어트리뷰트에 접근할 때,
wrapper.props('myProp')
구문을 사용하여wrapper.props().myProp
이나wrapper.vm.myProp
보다 우선하십시오.// 우수한 예 expect(wrapper.props().myProp).toBe(true); expect(wrapper.attributes().myAttr).toBe(true); // 더 나은 예 expect(wrapper.props('myProp').toBe(true); expect(wrapper.attributes('myAttr')).toBe(true);
-
여러 속성을 단언할 때는
props()
객체의 깊은 동등성을toEqual
로 확인하십시오.// 우수한 예 expect(wrapper.props('propA')).toBe('valueA'); expect(wrapper.props('propB')).toBe('valueB'); expect(wrapper.props('propC')).toBe('valueC'); // 더 나은 예 expect(wrapper.props()).toEqual({ propA: 'valueA', propB: 'valueB', propC: 'valueC', });
-
일부 속성에만 관심이 있다면,
toMatchObject
를 사용하십시오.expect.objectContaining
보다toMatchObject
를 선호하십시오.// 우수한 예 expect(wrapper.props()).toEqual(expect.objectContaining({ propA: 'valueA', propB: 'valueB', })); // 더 나은 예 expect(wrapper.props()).toMatchObject({ propA: 'valueA', propB: 'valueB', });
속성 유효성 검사
컴포넌트 속성을 확인할 때 assertProps
도우미를 사용하십시오. 속성 유효성 검사 오류는 오류로 throw됩니다.
import { assertProps } from 'helpers/assert_props'
// ...
expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()
JavaScript/Vue 어코드
이 어코드의 목표는 우리 모두가 동일한 페이지에 있음을 확인하는 것입니다.
- Vue를 작성할 때, 애플리케이션에서 jQuery를 사용해서는 안 됩니다.
- DOM에서 데이터를 가져와야 하는 경우, 애플리케이션을 부트스트래핑하는 동안
dataset
을 사용하여 데이터 속성을 가져올 수 있습니다. jQuery 없이도 가능합니다. - Vue.js에서 jQuery 의존성을 사용할 수 있습니다. 예시는 문서의 이 예시를 따릅니다.
- Vue 애플리케이션 내부에서 외부의 jQuery 이벤트를 청취해야 하는 경우 jQuery 이벤트 청취기를 사용할 수 있습니다.
- 새로운 jQuery 이벤트를 추가할 필요가 없는 경우, 동일한 작업을 수행하는 다른 방법을 찾아보십시오.
- DOM에서 데이터를 가져와야 하는 경우, 애플리케이션을 부트스트래핑하는 동안
- 애플리케이션 별 데이터를 위해 애플리케이션을 부트스트래핑하는 동안
window
객체에 1회 접근할 수 있습니다(예:scrollTo
는 언제든지 접근 가능합니다). 애플리케이션을 부트스트래핑하는 동안에만 이러한 접근을 하십시오. - 기술적인 빚을 만들어야 하는 경우, 그 기술적 빚을 나중에 리팩터링하기 위한 코드를 작성하십시오. 유지보수자들이 처음부터 기술적 빚에 동의해야 합니다. 해당 기술적 빚에 대한 문제를 평가하기 위해 이슈를 생성하고 더 자세히 논의해야 합니다. 앞으로 몇 개월 내에 해당 기술적 빚을 해결해야 하며, 우선순위는 유지보수자에 의해 결정되어야 합니다.
- 기술적 빚을 만들 때, 해당 코드에 대한 테스트를 미리 작성해야 하며, 해당 테스트를 다시 작성해서는 안 됩니다. 예를 들어, jQuery 테스트를 Vue 테스트로 변경해서는 안 됩니다.
- 중앙 집중화된 상태 관리로 VueX를 사용할 수 있습니다. VueX를 사용하지 않기로 선택하는 경우 Vue.js 문서에서 찾을 수 있는 store pattern을 사용해야 합니다.
- 중앙 집중화된 상태 관리 솔루션을 선택한 후 전체 애플리케이션에 대해 해당 솔루션을 사용해야 합니다. 상태 관리 솔루션을 혼합하여 사용해서는 안 됩니다.