Vue.jsスタイルガイド

リンティング

デフォルトはeslint-vue-pluginでplugin:vue/recommended 。 詳しいドキュメントはルールを確認してください。

基本ルール

  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演算子の使用を推奨しません。これは、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. プロップの命名: DOM コンポーネントの prop 名の使用は避けてください。
  4. プロップの命名:テンプレートで小道具を指定するには、キャメルケースの代わりにケバブケースを使用してください。

    // 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. 属性が1つだけの場合、タグはインラインにすることができます:

      // 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>
    `
    

プロップス

  1. 小道具はオブジェクトとして宣言する必要があります。

    // bad
    props: ['foo']
       
    // good
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  2. プロップを宣言するとき、必須キーは常に提供されるべきです。

    // bad
    props: {
      foo: {
        type: String,
      }
    }
       
    // good
    props: {
      foo: {
        type: String,
        required: false,
        default: 'bar'
      }
    }
    
  3. プロパテ ィが必須でない場合はデフォルトキーを指定すべきです。プロパティの存在をチェックする必要があるいくつかのシナリオがあります。これらの場合、デフォルトキーは提供されるべきではありません。

    // 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 メソッドは常に関数

    // bad
    data: {
      foo: 'foo'
    }
       
    // good
    data() {
      return {
        foo: 'foo'
      };
    }
    

ディレクティブ

  1. 速記法@v-on

    // bad
    <component v-on:click="eventHandler"/>
       
    // good
    <component @click="eventHandler"/>
    
  2. 速記法:v-bind

    // bad
    <component v-bind:class="btn"/>
       
    // good
    <component :class="btn"/>
    
  3. 速記法#v-slot

    // bad
    <template v-slot:header></template>
       
    // good
    <template #header></template>
    

終了タグ

  1. 自動閉鎖コンポーネントタグを優先

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

テンプレート内でのコンポーネントの使用

  1. テンプレート内でコンポーネントを使用する場合、他のスタイルよりもケバブケースの名前を優先します。

    // bad
    <MyComponent />
       
    // good
    <my-component />
    

順序付け

  1. .vue ファイルのタグの順序

    <script>
      // ...
    </script>
       
    <template>
      // ...
    </template>
       
    // We don't use scoped styles but there are few instances of this
    <style>
      // ...
    </style>
    
  2. Vue コンポーネントのプロパティ:コンポーネントルールのプロパティの順序を確認します。

:key

v-for を使用する場合は、各項目に一意の :key 属性を指定する必要があります。

  1. 反復処理される配列の要素が一意な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. v-fortemplate とともに使用し、子要素が複数ある場合、: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 変数に再割り当てします。

グローバルで変更可能なラッパーを作成すると、以下のような利点があります:

  • コンポーネント/DOM要素を見つけるための共通関数の定義:

     import MyComponent from '~/path/to/my_component.vue';
     describe('MyComponent', () => {
       let wrapper;
       
       // this can now be reused across tests
       const findMyComponent = wrapper.findComponent(MyComponent);
       // ...
     })
    
  • beforeEach ブロックを使用してコンポーネントをマウントします(詳しくは createComponent factory を参照してください)。
  • shared_test_setup.jsenableAutoDestroy を設定し、テスト実行後にコンポーネントを自動的に破棄します。

createComponent ファクトリ

実装ロジックの重複を避けるために、各テストブロックで再利用できるcreateComponent ファクトリー関数を定義しておくと便利です。これは、wrapper 変数をmountshallowMountの結果に再割り当てするクロージャです:

import MyComponent from '~/path/to/my_component.vue';
import { shallowMount } from '@vue/test-utils';

describe('MyComponent', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test:
  let wrapper;

  // Define our `createComponent` factory:
  function createComponent() {
    // Mount component and reassign `wrapper`:
    wrapper = shallowMount(MyComponent);
  }

  it('mounts', () => {
    createComponent();

    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `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', () => {
  // Initiate the "global" wrapper variable. This will be used throughout our test
  let wrapper;

  // define our `createComponent` factory
  function createComponent() {
    // mount component and reassign `wrapper`
    wrapper = shallowMount(MyComponent);
  }

  beforeEach(() => {
    createComponent();
  });

  it('mounts', () => {
    expect(wrapper.exists()).toBe(true);
  });

  it('`isLoading` prop defaults to `false`', () => {
    expect(wrapper.props('isLoading')).toBe(false);
  });
})

createComponent ベストプラクティス

  1. 多くの引数ではなく、単一の(または限られた数の)オブジェクト引数を使用することを検討してください。props のような一般的なデータに対して単一のパラメータを定義することは問題ありませんが、JavaScript スタイルガイドを念頭に置き、パラメータ数の制限を守りましょう:

    // bad
    function createComponent(data, props, methods, isLoading, mountFn) { }
       
    // good
    function createComponent({ data, props, methods, stubs, isLoading } = {}) { }
       
    // good
    function createComponent(props = {}, { data, methods, stubs, isLoading } = {}) { }
    
  2. 同じテストセット内でmount shallowMount の両方が必要な場合は、createComponent ファクトリ用のmountFn パラメータを定義しておくと便利です。 このパラメータは、コンポーネントのマウントに使用するマウント関数 (mount あるいはshallowMount) を受け取ります:

    import { shallowMount } from '@vue/test-utils';
       
    function createComponent({ mountFn = shallowMount } = {}) { }
    
  3. 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');
    

コンポーネントの状態設定

  1. setProps を使ってコンポーネントの状態を設定することは、可能な限り避けてください。代わりに、コンポーネントをマウントするときにコンポーネントのpropsData を設定します:

    // bad
    wrapper = shallowMount(MyComponent);
    wrapper.setProps({
      myProp: 'my cool prop'
    });
       
    // good
    wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
    

    ただし、何らかの方法でコンポーネントの反応性をテストしたい場合は例外です。例えば、特定のウォッチャーが実行された後のコンポーネントの出力をテストしたい場合です。このような動作のテストにsetProps を使用しても問題ありません。

コンポーネントの状態へのアクセス

  1. プロップやアトリビュートにアクセスする場合は、wrapper.props().myPropwrapper.vm.myProp よりも、wrapper.props('myProp') の構文のほうが便利です:

    // good
    expect(wrapper.props().myProp).toBe(true);
    expect(wrapper.attributes().myAttr).toBe(true);
       
    // better
    expect(wrapper.props('myProp').toBe(true);
    expect(wrapper.attributes('myAttr')).toBe(true);
    
  2. 複数のプロップをアサートする場合、props() オブジェクトとtoEqual の深い等価性をチェックします:

    // good
    expect(wrapper.props('propA')).toBe('valueA');
    expect(wrapper.props('propB')).toBe('valueB');
    expect(wrapper.props('propC')).toBe('valueC');
       
    // better
    expect(wrapper.props()).toEqual({
      propA: 'valueA',
      propB: 'valueB',
      propC: 'valueC',
    });
    
  3. 一部の小道具にしか興味がない場合は、toMatchObject を使用できます。expect.objectContaining よりもtoMatchObject を優先してください:

    // good
    expect(wrapper.props()).toEqual(expect.objectContaining({
      propA: 'valueA',
      propB: 'valueB',
    }));
       
    // better
    expect(wrapper.props()).toMatchObject({
      propA: 'valueA',
      propB: 'valueB',
    });
    

小道具のバリデーションのテスト

  1. コンポーネントの小道具をチェックする際にはassertProps ヘルパーを使います。小道具のバリデーションの失敗はエラーとして投げられます。
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. 外部のjQueryイベントをVueアプリケーション内でリッスンする必要がある場合は、jQueryイベントリスナーを使用できます。
    4. jQueryイベントを新たに追加する必要はありません。新しいjQueryイベントを追加する代わりに、同じタスクを実行する別の方法を見てみましょう。
  2. アプリケーションの起動中に、window オブジェクトに一度だけ、アプリケーション固有のデータをクエリすることができます (たとえば、scrollTo はいつでもアクセスできます)。このアクセスは、アプリケーションの起動中に行ってください。
  3. 後でリファクタリングするために、私たちの標準に従わないコードを書くことで、一時的ではありますが、すぐに技術的負債を作る必要があるかもしれません。メンテナーは、そもそも技術的負債を受け入れる必要があります。その技術的負債をさらに評価し、議論するために、イシューを作成すべきです。その優先順位はメンテナーが決定します。
  4. 技術的負債を作るときは、そのコードのテストを事前に書いておかなければなりません。例えば、jQueryのテストをVueのテストに書き換えるなどです。
  5. 状態の一元管理としてVueXを使用することもできます。VueXを使用しない場合は、Vue.jsのドキュメントに記載されているストアパターンを使用する必要があります。
  6. 一元的な状態管理ソリューションを選択したら、アプリケーション全体でそれを使用する必要があります。状態管理ソリューションを混ぜて使用しないでください。