Vue.js 스타일 가이드
린팅
기본적으로 eslint-vue-plugin와 plugin:vue/recommended
를 사용합니다.
규칙을 확인하여 더 많은 문서를 참고하세요.
기본 규칙
-
서비스는 자체 파일을 가져야 합니다.
-
스토어는 자체 파일을 가져야 합니다.
-
번들 파일에서 함수를 사용하여 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'), }));
우리는 이 특정 경우에서 스프레드 연산자의 사용을 권장하지 않습니다.
이는 우리의 코드베이스를 명확히 하고, 탐색 가능하며, 검색 가능한 상태로 유지하기 위함입니다.
이는 Vuex 상태 초기화와 같이 위의 이점을 누릴 수 있는 모든 곳에 적용됩니다.
위 패턴은 또한 인스턴스화 중 스칼라가 아닌 값을 쉽게 파싱할 수 있도록 합니다.
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, };
-
Props 네이밍: DOM 컴포넌트 prop 이름을 사용하지 마세요.
-
Props 네이밍: 템플릿에서 props를 제공할 때 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" />
-
인용부호
-
템플릿 내에서는 항상 이중 인용부호
"
를 사용하고, 다른 모든 JS에는 단일 인용부호'
를 사용하십시오.// 나쁨 template: ` <button :class='style'>Button</button> ` // 좋음 template: ` <button :class="style">Button</button> `
소품
-
소품은 객체로 선언해야 합니다.
// 나쁨 props: ['foo'] // 좋음 props: { foo: { type: String, required: false, default: 'bar' } }
-
소품을 선언할 때 필수 키는 항상 제공되어야 합니다.
// 나쁨 props: { foo: { type: String, } } // 좋음 props: { foo: { type: String, required: false, default: 'bar' } }
-
소품이 필수가 아닐 경우 기본 키가 제공되어야 합니다.
특정 시나리오에서는 속성의 존재 여부를 확인해야 합니다. 이 경우 기본 키는 제공되지 않아야 합니다.
// 좋음 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' }; }
지시어
-
단축키
@
가v-on
보다 선호됩니다.// 나쁨 <component v-on:click="eventHandler"/> // 좋음 <component @click="eventHandler"/>
-
단축키
:
가v-bind
보다 선호됩니다.// 나쁨 <component v-bind:class="btn"/> // 좋음 <component :class="btn"/>
-
단축키
#
가v-slot
보다 선호됩니다.// 나쁨 <template v-slot:header></template> // 좋음 <template #header></template>
닫는 태그
-
자기 닫힘 컴포넌트 태그를 선호하십시오.
// 나쁨 <component></component> // 좋음 <component />
템플릿 내에서의 컴포넌트 사용
-
템플릿에서 사용할 때 컴포넌트의 케밥 표기법 이름을 다른 스타일보다 선호하십시오.
// 나쁨 <MyComponent /> // 좋음 <my-component />
<style>
태그
Vue 컴포넌트에서 <style>
태그를 사용하지 않는 몇 가지 이유가 있습니다:
-
SCSS 변수와 믹스인 또는 Tailwind CSS
@apply
지시어를 사용할 수 없습니다. -
이러한 스타일은 런타임에 삽입됩니다.
-
우리는 이미 CSS를 정의하는 다른 방법이 몇 가지 있습니다.
<style>
태그 대신 Tailwind CSS 유틸리티 클래스 또는 페이지 특정 CSS를 사용해야 합니다.
정렬
-
.vue
파일의 태그 순서<script> // ... </script> <template> // ... </template> // 우리는 `<style>` 태그를 사용하지 않지만 이 경우가 몇 가지 있습니다. <style> // ... </style>
-
Vue 컴포넌트의 속성: 컴포넌트의 속성 순서 규칙을 확인하세요.
:key
v-for
를 사용할 때 각 항목에 대해 고유한 :key
속성을 제공해야 합니다.
-
반복되는 배열의 요소에 고유한
id
가 있다면, 이를 사용하는 것이 좋습니다:<div v-for="item in items" :key="item.id" > <!-- content --> </div>
-
반복되는 요소에 고유한 ID가 없을 경우, 배열 인덱스를
:key
속성으로 사용할 수 있습니다.<div v-for="(item, index) in items" :key="index" > <!-- content --> </div>
-
template
와 함께v-for
를 사용하고 자식 요소가 둘 이상인 경우,: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" > <!-- content --> </span> </div>
유용한 링크:
Vue 테스트
시간이 지나면서 Vue 컴포넌트를 효과적으로 테스트하기 위한 여러 프로그래밍 패턴과 스타일 선호가 나타났습니다. 다음 가이드는 이러한 것들 중 일부를 설명합니다.
이들은 엄격한 지침이 아닙니다, 오히려 GitLab에서 Vue 테스트를 작성하는 방법에 대한 통찰력을 제공하기 위한 제안과 좋은 관행의 모음입니다.
컴포넌트 마운트
일반적으로 Vue 컴포넌트를 테스트할 때, 각 테스트 블록에서 컴포넌트를 “다시 마운트”해야 합니다.
이를 달성하기 위해:
-
최상위
describe
블록 내에 가변wrapper
변수를 만듭니다. -
mount
또는shallowMount
를 사용하여 컴포넌트를 마운트합니다. -
결과
Wrapper
인스턴스를wrapper
변수에 재할당합니다.
전역 가변 래퍼 생성은 다음과 같은 여러 가지 이점을 제공합니다:
-
컴포넌트/DOM 요소를 찾기 위한 공통 함수를 정의할 수 있습니다:
import MyComponent from '~/path/to/my_component.vue'; describe('MyComponent', () => { let wrapper; // 이제 이를 테스트 간에 재사용할 수 있습니다. const findMyComponent = wrapper.findComponent(MyComponent); // ... })
-
컴포넌트를 마운트하기 위한
beforeEach
블록을 사용할 수 있습니다 (자세한 내용은 thecreateComponent
팩토리를 참조하십시오). -
enableAutoDestroy
를shared_test_setup.js
에 설정하여 테스트가 실행된 후 자동으로 컴포넌트를 파괴할 수 있습니다.
비동기 자식 컴포넌트
shallowMount
는 비동기 자식 컴포넌트에 대한 컴포넌트 스텁을 생성하지 않습니다. 비동기 자식 컴포넌트를 올바르게 스텁하기 위해서는 stubs
옵션을 사용하세요. 비동기 자식 컴포넌트에 name
옵션이 정의되어 있는지 확인하세요. 그렇지 않으면 wrapper
의 findComponent
메소드가 제대로 작동하지 않을 수 있습니다.
createComponent
팩토리
우리의 마운트 로직을 중복하지 않도록, 각 테스트 블록에서 재사용할 수 있는 createComponent
팩토리 함수를 정의하는 것이 유용합니다. 이는 우리의 wrapper
변수를 mount
및 shallowMount
의 결과로 재할당해야 하는 클로저입니다:
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('mounts', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` prop 기본값은 `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('mounts', () => {
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` prop 기본값은 `false`입니다', () => {
expect(wrapper.props('isLoading')).toBe(false);
});
})
createComponent
모범 사례
-
많은 인수보다 하나(또는 제한된 수)의 객체 인수를 사용하는 것을 고려하세요. 일반 데이터에 대해 단일 매개변수를 정의하는 것은 괜찮지만, JavaScript 스타일 가이드를 염두에 두고 매개변수 수 제한 내에 있도록 하세요:
// 나쁨 function createComponent(props, stubs, mountFn, foo) { } // 좋음 function createComponent({ props, stubs, mountFn, foo } = {}) { } // 좋음 function createComponent(props = {}, { stubs, mountFn, foo } = {}) { }
-
동일한 테스트 세트 내에서
mount
와shallowMount
두 가지를 모두 필요로 하는 경우, 컴포넌트를 마운트하는 데 사용할 수 있는 마운트 함수를 받아들이는mountFn
매개변수를createComponent
팩토리에 정의하는 것이 유용할 수 있습니다:import { shallowMount } from '@vue/test-utils'; function createComponent({ mountFn = shallowMount } = {}) { }
-
mountExtended
및shallowMountExtended
도우미를 사용하여wrapper.findByTestId()
를 노출하세요: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; // 나쁨 :( - 이는 실제 사용자 상호 작용을 우회하고 테스트를 컴포넌트 내부와 결합시킵니다. const createWrapper = ({ data }) => { wrapper = shallowMountExtended(SomeComponent, { data }); }; // 좋음 :) - `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
를 사용하는 것은 괜찮습니다. -
컴포넌트의 내부 상태를 설정하고 실제 I/O 테스트를 우회하는
setData
의 사용을 피하세요. 대신, 상태 변화를 강제하기 위해 컴포넌트의 자식 요소나 다른 부작용에서 이벤트를 트리거하세요.
컴포넌트 상태 접근
-
props나 속성에 접근할 때,
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
를 사용하여props()
객체의 깊은 동등성을 확인하세요:// 좋음 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', });
-
특정 props에만 관심이 있다면
toMatchObject
를 사용할 수 있습니다.expect.objectContaining
보다toMatchObject
를 선호하세요:// 좋음 expect(wrapper.props()).toEqual(expect.objectContaining({ propA: 'valueA', propB: 'valueB', })); // 더 좋음 expect(wrapper.props()).toMatchObject({ propA: 'valueA', propB: 'valueB', });
props 유효성 검사 테스트
컴포넌트 props를 확인할 때는 assertProps
헬퍼를 사용하세요. props 유효성 검사 실패는 오류로 발생합니다:
import { assertProps } from 'helpers/assert_props'
// ...
expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()