Vue.js 스타일 가이드

린팅

우리는 plugin:vue/recommended를 사용하여 eslint-vue-plugin를 기본값으로 설정합니다. 더 많은 문서는 rules를 확인하세요.

기본 규칙

  1. 서비스는 자체 파일을 가집니다.
  2. 스토어는 자체 파일을 가집니다.
  3. 번들 파일에서 Vue 컴포넌트를 인스턴스화하는 함수를 사용하세요:

    // bad
    class {
      init() {
        new Component({})
      }
    }
    
    // good
    document.addEventListener('DOMContentLoaded', () => new Vue({
      el: '#element',
      components: {
        componentName
      },
      render: createElement => createElement('component-name'),
    }));
    
  4. 서비스나 스토어에 대해 싱글톤을 사용하지 마세요

    // bad
    class Store {
      constructor() {
        if (!this.prototype.singleton) {
          // do something
        }
      }
    }
    
    // good
    class Store {
      constructor() {
        // do something
      }
    }
    
  5. Vue 템플릿에는 .vue를 사용하세요. HAML에서 %template는 사용하지 마세요.

  6. Vue 앱으로 전달되는 데이터를 명시적으로 정의하세요.

    // bad
    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      provide: {
        ...someDataset
      },
      props: {
        ...anotherDataset
      },
      render: createElement => createElement('component-name'),
    }));
    
    // good
    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'),
    });
    

    우리는 코드베이스를 명시적이고 검색 가능하도록 유지하기 위해 이 특별한 경우에서 spread operator의 사용을 자제합니다. 이는 Vuex 상태를 초기화할 때와 같이 해당 상황이 더 유익한 경우에도 적용됩니다. 또한 위의 패턴은 인스턴스화 중에 비스칼라 값들을 쉽게 파싱할 수 있도록 합니다.

    return new Vue({
      el: '#element',
      name: 'ComponentNameRoot',
      components: {
        componentName
      },
      props: {
        foo,
        bar: parseBoolean(bar)
      },
      render: createElement => createElement('component-name'),
    });
    

네이밍

  1. 확장자: Vue 컴포넌트에는 .vue 확장자를 사용하세요. 파일 확장자로 .js를 사용하지 마세요 (#34371).
  2. 참조 네이밍: 기본 가져오기에는 PascalCase를 사용하세요:

    // bad
    import cardBoard from 'cardBoard.vue'
    
    components: {
      cardBoard,
    };
    
    // good
    import CardBoard from 'cardBoard.vue'
    
    components: {
      CardBoard,
    };
    
  3. Props 네이밍: DOM 구성 프롭 이름을 사용하지 마세요.
  4. Props 네이밍: 템플릿에서 props를 제공할 때 camelCase 대신 kebab-case를 사용하세요.

    // bad
    <component class="btn">
    
    // good
    <component css-class="btn">
    
    // bad
    <component myProp="prop" />
    
    // good
    <component my-prop="prop" />
    

정렬

  1. 템플릿 메소드에 대해 다음 정렬 스타일을 따르세요:

    1. 여러 속성이 있는 경우 모든 속성을 새 줄에 작성하세요:

      // bad
      <component v-if="bar"
          param="baz" />
      
      <button class="btn">Click me</button>
      
      // good
      <component
        v-if="bar"
        param="baz"
      />
      
      <button class="btn">
        Click me
      </button>
      
    2. 하나의 속성이 있는 경우 태그는 한 줄에 작성할 수 있습니다:

      // good
        <component bar="bar" />
      
      // good
        <component
          bar="bar"
          />
      
      // bad
       <component
          bar="bar" />
      

따옴표

  1. 템플릿 내부에서는 항상 이중 따옴표 "를 사용하고 다른 JS에는 작은 따옴표 '를 사용하세요.

    // bad
    template: `
      <button :class='style'>Button</button>
    `
    
    // good
    template: `
      <button :class="style">Button</button>
    

    Props

  2. Props는 객체로 선언되어야 합니다.

    // bad
    props: ['foo']
    
    // good
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  3. 필수 키는 프롭을 선언할 때 항상 제공되어야 합니다.

    // bad
    props: {
      foo: {
        type: String,
      }
    }
    
    // good
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  4. 프롭이 필수가 아닌 경우 기본 키를 제공해야 합니다. 프로퍼티의 존재 여부를 확인해야 하는 시나리오가 있습니다. 이러한 경우 기본 키를 제공해서는 안 됩니다.

    // good
    props: {
      foo: {
        type: String,
        required: false,
      }
    }
    
    // good
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
    // good
    props: {
      foo: {
        type: String,
        required: true
      }
    }
    

데이터

  1. data 메소드는 항상 함수여야 합니다.

    // 나쁜 예
    data: {
      foo: 'foo'
    }
    
    // 좋은 예
    data() {
      return {
        foo: 'foo'
      };
    }
    

디렉티브

  1. 단축 표기법 @v-on보다 선호합니다.

    // 나쁜 예
    <component v-on:click="eventHandler"/>
    
    // 좋은 예
    <component @click="eventHandler"/>
    
  2. 단축 표기법 :v-bind보다 선호합니다.

    // 나쁜 예
    <component v-bind:class="btn"/>
    
    // 좋은 예
    <component :class="btn"/>
    
  3. 단축 표기법 #v-slot보다 선호합니다.

    // 나쁜 예
    <template v-slot:header></template>
    
    // 좋은 예
    <template #header></template>
    

종료 태그

  1. 자체 종료 컴포넌트 태그를 선호합니다.

    // 나쁜 예
    <component></component>
    
    // 좋은 예
    <component />
    

템플릿 내 구성 요소 사용

  1. 템플릿에서 컴포넌트의 kebab-case 명칭을 사용할 때 다른 스타일보다 우선합니다.

    // 나쁜 예
    <MyComponent />
    
    // 좋은 예
    <my-component />
    

순서

  1. .vue 파일에서 태그 순서

    <script>
      // ...
    </script>
    
    <template>
      // ...
    </template>
    
    // 스코프 스타일을 사용하지 않지만 일부 인스턴스가 있습니다.
    <style>
      // ...
    </style>
    
  2. Vue 컴포넌트의 속성: 구성 요소 속성 순서 규칙을 확인하세요.

:key

v-for를 사용할 때 각 항목에 고유한 :key 속성을 제공해야 합니다.

  1. 반복되는 배열 요소가 고유한 id를 가지고 있는 경우 해당 id를 사용하는 것이 좋습니다.

    <div
      v-for="item in items"
      :key="item.id"
    >
      <!-- 내용 -->
    </div>
    
  2. 반복되는 요소에 고유한 ID가 없는 경우, :key 속성으로 배열 인덱스를 사용할 수 있습니다.

    <div
      v-for="(item, index) in items"
      :key="index"
    >
      <!-- 내용 -->
    </div>
    
  3. templatev-for를 함께 사용하고 하나 이상의 하위 요소가있는 경우, :key 값은 고유해야 합니다. kebab-case 네임스페이스를 사용하는 것이 좋습니다.

    <template v-for="(item, index) in items">
      <span :key="`span-${index}`"></span>
      <button :key="`button-${index}`"></button>
    </template>
    
  4. 중첩 된 v-for를 다룰 때는 위의 지침과 동일한 지침을 사용하세요.

    <div
      v-for="item in items"
      :key="item.id"
    >
      <span
        v-for="element in array"
        :key="element.id"
      >
        <!-- 내용 -->
      </span>
    </div>
    

유용한 링크:

  1. 상태 유지
  2. Vue 스타일 가이드: Keyed v-for

Vue 테스트

시간이 지남에 따라 Vue 구성 요소를 효과적으로 테스트하기 위해 일부 프로그래밍 패턴과 스타일 기본 설정이 나타났습니다. 다음 가이드는 이러한 내용을 설명합니다. 이것은 엄격한 지침이 아니라 GitLab에서 Vue 테스트를 작성하는 방식에 대한 통찰력을 제공하고자 하는 제안 및 좋은 실천 방법의 모음입니다.

컴포넌트 마운트

일반적으로 Vue 컴포넌트를 테스트 할 때마다 컴포넌트를 “다시 마운트”해야합니다.

이를 달성하기 위해:

  1. 최상위 describe 블록 내에서 변경 가능한 wrapper 변수를 생성합니다.
  2. mount 또는 shallowMount을 사용하여 컴포넌트를 마운트합니다.
  3. 결과로 나온 Wrapper 인스턴스를 wrapper 변수에 다시 할당합니다.

전역적이고 변경 가능한 래퍼를 만드는 것은 컴포넌트/ DOM 요소를 찾는 데 일반적인 기능을 정의하는 것과 같은 여러 가지 장점을 제공합니다.

createComponent 팩토리

마운트 로직을 중복으로 작성하지 않으려면 각 테스트 블록에서 재사용할 수 있는 createComponent 팩토리 함수를 정의하는 것이 유용합니다. 이는 wrapper 변수를 mountshallowMount의 결과물로 재할당해야 하는 클로저입니다.

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 최상의 관행

  1. 여러 매개변수 대신 단일(또는 제한된 수의) 객체 매개변수를 사용하는 것을 고려하세요. props와 같은 공통 데이터에 대해 단일 매개변수를 정의하는 것은 괜찮지만, JavaScript 스타일 가이드를 염두에 두고 매개변수 수를 제한하세요.

    // 나쁨
    function createComponent(data, props, methods, isLoading, mountFn) { }
    
    // 좋음
    function createComponent({ data, props, methods, stubs, isLoading } = {}) { }
    
    // 좋음
    function createComponent(props = {}, { data, methods, stubs, isLoading } = {}) { }
    
  2. 동일한 테스트 세트 내에서 mountshallowMount 모두를 필요로 하는 경우, 마운트 함수(mount 또는 shallowMount)를 받아들이는 createComponent 팩토리에 대한 mountFn 매개변수를 정의하는 것이 유용합니다.

    import { shallowMount } from '@vue/test-utils';
    
    function createComponent({ mountFn = shallowMount } = {}) { }
    
  3. mountExtendedshallowMountExtended 헬퍼를 사용하여 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');
    

컴포넌트 상태 설정

  1. 가능한 경우 컴포넌트 상태를 설정하는 데 setProps를 사용하지 마세요. 대신 컴포넌트를 마운트할 때 컴포넌트의 propsData를 설정하세요.

    // 나쁨
    wrapper = shallowMount(MyComponent);
    wrapper.setProps({
      myProp: 'my cool prop'
    });
    
    // 좋음
    wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
    

    여기서의 예외는 특정 방식으로 컴포넌트 반응성을 테스트하려는 경우입니다. 예를 들어, 특정 감시자가 실행된 후 컴포넌트의 출력을 테스트하고자 할 수 있습니다. 이러한 동작을 테스트하기 위해 setProps를 사용하는 것은 괜찮습니다.

컴포넌트 상태 접근

  1. 프롭이나 속성에 액세스할 때, 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);
    
  2. 여러 프롭을 단언하는 경우 toEqual을 사용하여 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',
    });
    
  3. 일부 프롭에만 관심이 있는 경우 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 어코드

이 어코드의 목표는 우리 모두가 동일한 페이지에 있는지 확인하는 것입니다.

  1. Vue를 작성할 때, 응용 프로그램에서 jQuery를 사용할 수 없습니다.
    1. DOM에서 데이터를 가져와야 하는 경우, 응용 프로그램을 부트스트랩하는 동안 1회 DOM을 쿼리하여 dataset을 사용하여 데이터 속성을 가져올 수 있습니다. jQuery를 사용하지 않고 이 작업을 수행할 수 있습니다.
    2. Vue.js에서 jQuery 종속성을 사용할 수 있습니다. 문서의 이 예시를 참고하세요.
    3. Vue 애플리케이션 내부에서 외부 jQuery 이벤트를 수신해야 하는 경우 jQuery 이벤트 리스너를 사용할 수 있습니다.
    4. 불필요한 경우 새로운 jQuery 이벤트를 추가하지 않습니다. 대신 동일한 작업을 수행하는 다른 방법을 살펴보세요.
  2. 응용 프로그램 부트스트랩 중에는 응용 프로그램에 대한 특정 데이터(예: scrollTo)에 액세스하는 것은 한 번만 허용됩니다.
  3. 기술적 부채를 생성하는 것이 즉각적으로 필요할 수 있지만, 나중에 리팩터링될 코드를 작성하여 우리의 표준에 부합하지 않는 것이 허용됩니다. 유지 보수자는 기술 부채에 대해 최초에 동의해야 합니다. 해당 기술 부채에 대한 문제가 작성되어 더 평가하고 토의해야 합니다. 다가오는 몇 달 안에 그 기술 부채를 해소해야 하며, 그 우선순위는 유지 보수자에 의해 결정되어야 합니다.
  4. 기술 부채를 생성할 때는 해당 코드에 대한 테스트를 미리 작성해야 하며 해당 테스트는 다시 작성해서는 안 됩니다. 예를 들어, jQuery 테스트를 Vue 테스트로 다시 작성해서는 안 됩니다.
  5. 중앙 집중식 상태 관리로 VueX를 사용할 수 있습니다. VueX를 사용하지 않기로 선택하는 경우 Vue.js 문서에서 store pattern을 사용해야 합니다.
  6. 중앙 집중식 상태 관리 솔루션을 선택한 후에는 응용 프로그램 전체에 걸쳐 해당 솔루션을 사용해야 합니다. 상태 관리 솔루션을 혼합하여 사용해서는 안 됩니다.