Vue.js 스타일 가이드

린팅

우리는 plugin:vue/recommended와 함께 eslint-vue-plugin를 기본으로 설정합니다. 더 많은 문서는 여기를 참조하세요.

기본 규칙

  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 네이밍: 템플릿에서 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>
    `
    

Props

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

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

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

    // 좋음
    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. 자체 닫히는 컴포넌트 태그를 선호합니다.

    // bad
    <component></component>
       
    // good
    <component />
    

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

  1. 템플릿에서 사용할 때는 케밥 표기법을 사용하는 것이 다른 스타일보다 선호됩니다.

    // bad
    <MyComponent />
       
    // good
    <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"
    >
      <!-- content -->
    </div>
    
  2. 순회되는 요소가 고유한 ID를 가지고 있지 않은 경우 배열 인덱스를 :key 속성으로 사용할 수 있습니다.

    <div
      v-for="(item, index) in items"
      :key="index"
    >
      <!-- content -->
    </div>
    
  3. template과 함께 v-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"
      >
        <!-- content -->
      </span>
    </div>
    

유용한 링크:

  1. 상태 유지하기
  2. Vue 스타일 가이드: 키가 있는 v-for

Vue 테스팅

시간이 흐름에 따라 Vue 컴포넌트를 효과적으로 테스트하기 위해 다양한 프로그래밍 패턴과 스타일 기호가 나왔습니다. 다음 가이드에서는 이러한 사항 중 일부를 설명합니다. 이것들은 엄격한 지침이 아니라 GitLab에서 Vue 테스트를 작성하는 방법에 대한 통찰을 제공하고 몇 가지 제안과 좋은 관행들의 모음입니다.

컴포넌트 마운팅

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

이를 위해:

  1. 최상위 describe 블록 내에서 변경 가능한 wrapper 변수를 생성합니다.
  2. mount 또는 shallowMount를 사용하여 컴포넌트를 마운트합니다.
  3. 결과로 나온 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 팩토리 함수를 정의하는 것이 유용합니다. 이는 각 테스트 블록에서 재사용할 수 있는 클로저로, mountshallowMount의 결과를 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 최상의 방법

  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 둘 다 필요한 경우, createComponent 팩토리에 사용될 탑재 함수(mount 또는 shallowMount)를 받는 mountFn 매개변수를 정의하는 것이 유용할 수 있습니다:

    import { shallowMount } from '@vue/test-utils';
       
    function createComponent({ mountFn = shallowMount } = {}) { }
    
  3. wrapper.findByTestId()를 노출하는 데 도움이 되는 mountExtendedshallowMountExtended 헬퍼를 사용하세요:

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

    여기서의 예외는 특정 방식으로 컴포넌트의 반응성을 테스트하고자 하는 경우입니다. 예를 들어, 특정 watcher가 실행된 후 컴포넌트의 출력을 테스트하고싶은 경우 등입니다. 이러한 동작을 테스트하기 위해 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. 여러 속성을 단언할 때, 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에서 데이터를 가져와야 하는 경우, 애플리케이션을 부트스트래핑하는 동안 dataset을 사용하여 데이터 어트리뷰트를 가져올 수 있습니다. 여기에는 jQuery를 사용할 필요가 없습니다.
    2. 문서의 이 예시를 따르면 Vue.js에서 jQuery 의존성을 사용할 수 있습니다.
    3. Vue 애플리케이션 내부에서 외부 jQuery 이벤트를 청취해야 하는 경우 jQuery 이벤트 리스너를 사용할 수 있습니다.
    4. 불필요한 새로운 jQuery 이벤트를 추가하지 않습니다. 대신 동일한 작업을 수행하는 다른 방법을 살펴보세요.
  2. 애플리케이션 부트스트래핑 중에 애플리케이션별 데이터(예: scrollTo는 언제든지 액세스 가능)를 위해 window 객체를 한 번 쿼리할 수 있습니다.
  3. 기술적 부채를 나중에 재구성하기 위해 우리의 표준을 따르지 않는 코드를 작성하는 일시적이지만 즉각적인 필요성이 있을 수 있습니다. 관리자는 기술 부채에 동의해야 합니다. 이 기술 부채에 대한 문제가 생성되어야 하며 추가 평가 및 토론을 위해 이 기술 부채를 몇 달 안에 수정해야 합니다.
  4. 기술적 부채를 만들 때는 그 코드에 대한 테스트를 시작하기 전에 작성해야 하며 해당 테스트는 다시 작성해서는 안 됩니다. 예를 들어, jQuery 테스트를 Vue 테스트로 다시 작성하지 말아야 합니다.
  5. 중앙 집중식 상태 관리로 VueX를 선택할 수 있습니다. VueX를 사용하지 않기로 선택하는 경우 Vue.js 문서에서 찾을 수 있는 store pattern을 사용해야 합니다.
  6. 한 번 중앙 집중식 상태 관리 솔루션을 선택하면 전체 애플리케이션에 대해 사용해야 합니다. 상태 관리 솔루션을 혼합해서 사용해서는 안 됩니다.