Vue.js 스타일 가이드
린팅
우리는 plugin:vue/recommended
와 함께 eslint-vue-plugin를 기본으로 설정합니다. 더 많은 문서는 여기를 참조하세요.
기본 규칙
- 서비스는 자체 파일을 갖습니다.
- 스토어는 자체 파일을 갖습니다.
-
번들 파일에서 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
-
Props는 객체로 선언되어야 합니다.
// 나쁨 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>
닫힌 태그
-
자체 닫히는 컴포넌트 태그를 선호합니다.
// bad <component></component> // good <component />
템플릿 내에서 컴포넌트 사용
-
템플릿에서 사용할 때는 케밥 표기법을 사용하는 것이 다른 스타일보다 선호됩니다.
// bad <MyComponent /> // good <my-component />
정렬
-
.vue
파일의 태그 순서<script> // ... </script> <template> // ... </template> // 우리는 지역화된 스타일을 사용하지 않지만 몇 가지 경우가 있습니다. <style> // ... </style>
-
Vue 컴포넌트의 속성: 컴포넌트 내 속성 순서 규칙을 확인하세요.
:key
v-for
를 사용할 때 각 항목에 고유한 :key
속성을 제공해야 합니다.
-
순회되는 배열의 요소가 고유한
id
를 가지고 있는 경우 해당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
변수에 재할당합니다.
전역으로 변경 가능한 wrapper를 생성하면 컴포넌트/DOM 요소를 찾기 위한 일반 함수를 정의할 수 있습니다:
import MyComponent from '~/path/to/my_component.vue';
describe('MyComponent', () => {
let wrapper;
// 이것은 테스트 전체에 걸쳐 재사용할 수 있습니다
const findMyComponent = wrapper.findComponent(MyComponent);
// ...
})
컴포넌트를 마운트하기 위해 beforeEach
블록을 사용하여 컴포넌트를 자동으로 파기하도록 설정할 수 있습니다. (자세한 정보는
createComponent factory를 참조하세요.)
createComponent
팩토리
마운팅 로직을 중복해서 사용하지 않으려면 createComponent
팩토리 함수를 정의하는 것이 유용합니다. 이는 각 테스트 블록에서 재사용할 수 있는 클로저로, mount
및 shallowMount
의 결과를 wrapper
변수에 재할당해야 합니다:
import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';
describe('MyComponent', () => {
// "전역" 래퍼 변수를 초기화합니다. 이것은 테스트 전체에서 사용됩니다:
let wrapper;
// createComponent 팩토리를 정의합니다:
function createComponent() {
// 컴포넌트를 마운트하고 `wrapper`를 재할당합니다:
wrapper = shallowMount(MyComponent);
}
it('마운트됩니다', () => {
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', () => {
// "전역" 래퍼 변수를 초기화합니다. 이것은 테스트 전체에서 사용됩니다:
let wrapper;
// createComponent 팩토리를 정의합니다
function createComponent() {
// 컴포넌트를 마운트하고 `wrapper`를 재할당합니다
wrapper = shallowMount(MyComponent);
}
beforeEach(() => {
createComponent();
});
it('마운트됩니다', () => {
expect(wrapper.exists()).toBe(true);
});
it('`isLoading` prop이 기본적으로 `false`입니다', () => {
expect(wrapper.props('isLoading')).toBe(false);
});
})
createComponent
최상의 방법
-
많은 매개변수 대신에 단일(또는 제한된 수의) 객체 매개변수를 사용하는 것을 고려하세요.
props
와 같은 일반 데이터를 위한 단일 매개변수를 정의하는 것은 괜찮지만, JavaScript 스타일 가이드를 염두에 두고 매개변수 수 제한 내에 유지하세요:// 나쁨 function createComponent(data, props, methods, isLoading, mountFn) { } // 좋음 function createComponent({ data, props, methods, stubs, isLoading } = {}) { } // 좋음 function createComponent(props = {}, { data, methods, stubs, isLoading } = {}) { }
-
동일한 테스트 세트 내에서
mount
및shallowMount
둘 다 필요한 경우,createComponent
팩토리에 사용될 탑재 함수(mount
또는shallowMount
)를 받는mountFn
매개변수를 정의하는 것이 유용할 수 있습니다: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');
컴포넌트 상태 설정
-
가능한 경우 컴포넌트 상태를 설정할 때
setProps
사용을 피하십시오. 대신 컴포넌트를 탑재할 때propsData
를 설정하세요:// 나쁨 wrapper = shallowMount(MyComponent); wrapper.setProps({ myProp: 'my cool prop' }); // 좋음 wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
여기서의 예외는 특정 방식으로 컴포넌트의 반응성을 테스트하고자 하는 경우입니다. 예를 들어, 특정 watcher가 실행된 후 컴포넌트의 출력을 테스트하고싶은 경우 등입니다. 이러한 동작을 테스트하기 위해
setProps
를 사용하는 것은 괜찮습니다.
컴포넌트 상태 접근
-
속성이나 어트리뷰트에 접근할 때,
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에서 데이터를 가져와야 하는 경우, 애플리케이션을 부트스트래핑하는 동안
- 애플리케이션 부트스트래핑 중에 애플리케이션별 데이터(예:
scrollTo
는 언제든지 액세스 가능)를 위해window
객체를 한 번 쿼리할 수 있습니다. - 기술적 부채를 나중에 재구성하기 위해 우리의 표준을 따르지 않는 코드를 작성하는 일시적이지만 즉각적인 필요성이 있을 수 있습니다. 관리자는 기술 부채에 동의해야 합니다. 이 기술 부채에 대한 문제가 생성되어야 하며 추가 평가 및 토론을 위해 이 기술 부채를 몇 달 안에 수정해야 합니다.
- 기술적 부채를 만들 때는 그 코드에 대한 테스트를 시작하기 전에 작성해야 하며 해당 테스트는 다시 작성해서는 안 됩니다. 예를 들어, jQuery 테스트를 Vue 테스트로 다시 작성하지 말아야 합니다.
- 중앙 집중식 상태 관리로 VueX를 선택할 수 있습니다. VueX를 사용하지 않기로 선택하는 경우 Vue.js 문서에서 찾을 수 있는 store pattern을 사용해야 합니다.
- 한 번 중앙 집중식 상태 관리 솔루션을 선택하면 전체 애플리케이션에 대해 사용해야 합니다. 상태 관리 솔루션을 혼합해서 사용해서는 안 됩니다.