Vue.js 스타일 가이드

린트

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

기본 규칙

  1. 서비스는 자체 파일을 가지고 있습니다.
  2. 스토어는 자체 파일을 가지고 있습니다.
  3. 번들 파일에서 Vue 구성 요소를 인스턴스화하기 위해 함수를 사용하세요:

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

    // 나쁨
    class Store {
      constructor() {
        if (!this.prototype.singleton) {
          // 무언가를 수행합니다
        }
      }
    }
    
    // 좋음
    class Store {
      constructor() {
        // 무언가를 수행합니다
      }
    }
    
  5. Vue 템플릿에는 .vue를 사용하세요. HAML에서는 %template을 사용하지 마세요.

  6. 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'),
    });
    

네이밍

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

    // 나쁨
    import cardBoard from 'cardBoard.vue'
    
    components: {
      cardBoard,
    };
    
    // 좋음
    import CardBoard from 'cardBoard.vue'
    
    components: {
      CardBoard,
    };
    
  3. Props 네이밍: DOM 컴포넌트 prop 이름을 사용하지 마세요.
  4. Props 네이밍: 템플릿에서 프롭을 제공할 때 camelCase 대신 kebab-case를 사용하세요.

    // 나쁨
    <component class="btn">
    
    // 좋음
    <component css-class="btn">
    
    // 나쁨
    <component myProp="prop" />
    
    // 좋음
    <component my-prop="prop" />
    

정렬

  1. 템플릿 메서드의 정렬 스타일을 다음과 같이 따르세요:

    1. 여러 속성을 가질 때는 모든 속성을 새 줄에 배치하세요:

      // 나쁨
      <component v-if="bar"
          param="baz" />
      
      <button class="btn">Click me</button>
      
      // 좋음
      <component
        v-if="bar"
        param="baz"
      />
      
      <button class="btn">
        Click me
      </button>
      
    2. 하나의 속성만 있는 경우 태그를 인라인으로 배치하세요:

      // 좋음
        <component bar="bar" />
      
      // 좋음
        <component
          bar="bar"
          />
      
      // 나쁨
       <component
          bar="bar" />
      

따옴표

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

    // 나쁨
    template: `
      <button :class='style'>Button</button>
    `
    
    // 좋음
    template: `
      <button :class="style">Button</button>
    `
    

프롭스

  1. 프롭은 객체로 선언해야 합니다.

    // 나쁨
    props: ['foo']
    
    // 좋음
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  2. 프롭을 선언할 때는 반드시 required 키를 제공해야 합니다.

    // 나쁨
    props: {
      foo: {
        type: String,
      }
    }
    
    // 좋음
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  3. 프롭이 필수가 아닌 경우 default 키를 제공해야 합니다. 프로퍼티의 존재 여부를 확인해야 하는 시나리오가 몇 가지 있습니다. 그런 경우에는 default 키를 제공하지 않아야 합니다.

    // 좋음
    props: {
      foo: {
        type: String,
        required: false,
      }
    }
    
    // 좋음
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
    // 좋음
    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. 가능하면 self-closing 컴포넌트 태그를 선호하세요.

    // 나쁨
    <component></component>
    
    // 좋음
    <component />
    

템플릿 내에서 컴포넌트 사용

  1. 템플릿에서 사용할 때 다른 스타일보다 kebab-cased 이름을 사용하세요.

    // 나쁨
    <MyComponent />
    
    // 좋음
    <my-component />
    

<style> 태그

우리는 Vue 컴포넌트에서 <style> 태그를 사용하지 않습니다. 그 이유는 몇 가지가 있습니다:

  1. SCSS 변수 및 믹스인 또는 Tailwind CSS @apply 지시문을 사용할 수 없습니다.
  2. 이러한 스타일은 실행 시에 삽입됩니다.
  3. CSS를 정의하는 몇 가지 다른 방법이 이미 있습니다.

<style> 태그 대신에 Tailwind CSS 유틸리티 클래스 또는 페이지별 CSS를 사용해야 합니다.

순서

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

    <script>
      // ...
    </script>
    
    <template>
      // ...
    </template>
    
    // `<style>` 태그를 사용하지 않지만,  가지 인스턴스가 있습니다.
    <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 변수에 재할당하세요.

전역 가변적인 wrapper를 생성하는 것은 여러 이점을 제공합니다. 이는 다음과 같은 기능을 정의하는 데 사용할 수 있습니다:

  import MyComponent from '~/path/to/my_component.vue';
  describe('MyComponent', () => {
    let wrapper;

    // 이것은 이제 테스트 전체에서 재사용할 수 있습니다.
    const findMyComponent = wrapper.findComponent(MyComponent);
    // ...
  })
  • 컴포넌트/DOM 요소를 찾기 위한 일반적인 함수를 정의합니다.
  • 컴포넌트를 마운트하기 위해 beforeEach 블록을 사용하세요(자세한 내용은 createComponent 팩토리를 참조하세요).
  • shared_test_setup.js에서 설정된 enableAutoDestroy를 사용하여 테스트 이후에 컴포넌트를 자동으로 파괴하세요.

비동기 하위 컴포넌트

shallowMount비동기 하위 컴포넌트를위한 컴포넌트 스터브를 생성하지 않습니다. 비동기 하위 컴포넌트를 올바르게 스텁하기 위해서는 stubs 옵션을 사용하세요. 비동기 하위 컴포넌트에 name 옵션이 정의되어 있어야 하며, 그렇지 않으면 (wrapperfindComponent 방법이 올바르게 작동하지 않을 수 있으므로) 제대로 작동하지 않을 수 있습니다.

createComponent 팩토리

마운팅 로직을 중복으로 작성하는 것을 방지하기 위해 createComponent 팩토리 함수를 정의하는 것이 유용합니다. 이것은 mountshallowMount의 결과를 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` 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('마운트됩니다', () => {
    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop은 `false`로 기본 설정됩니다', () => {
    expect(wrapper.props('isLoading')).toBe(false);
  });
})

createComponent 최선의 방법

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

    // 안 좋은 예
    function createComponent(props, stubs, mountFn, foo) { }
    
    // 좋은 예
    function createComponent({ props, stubs, mountFn, foo } = {}) { }
    
    // 좋은 예
    function createComponent(props = {}, { stubs, mountFn, foo } = {}) { }
    
  2. 동일한 테스트 세트 내에서 mountshallowMount 모두 필요한 경우, createComponent 팩토리에 마운트 함수(mount 또는 shallowMount)를 받는 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');
    
  4. 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');
    }
    

구성 요소 상태 설정

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

    // 안 좋은 예
    wrapper = shallowMount(MyComponent);
    wrapper.setProps({
      myProp: 'my cool prop'
    });
    
    // 좋은 예
    wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
    

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

  2. 컴포넌트의 내부 상태를 설정하는 setData를 사용하지 마세요. 대신, 컴포넌트의 자식 요소에서 이벤트를 트리거하거나 기타 부수 효과를 사용하여 상태 변경을 강제하세요.

구성 요소 상태 접근

  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. 여러 속성을 확인할 때 toEqualprops() 객체의 깊은 동일성을 확인하세요:

    // 좋은 예
    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 헬퍼를 사용하세요. 속성 유효성 검사 실패는 에러로 발생합니다:

import { assertProps } from 'helpers/assert_props'

// ...

expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()