Vue.jsスタイルガイド
リンティング
デフォルトはeslint-vue-pluginで、plugin:vue/recommended
。 詳しいドキュメントはルールを確認してください。
基本ルール
- サービスは独自のファイル
- ストア独自のファイル
-
バンドルファイル内の関数を使用して、Vueコンポーネントをインスタンス化します:
// bad class { init() { new Component({}) } } // good document.addEventListener('DOMContentLoaded', () => new Vue({ el: '#element', components: { componentName }, render: createElement => createElement('component-name'), }));
-
サービスやストアにシングルトンを使用しないでください。
// bad class Store { constructor() { if (!this.prototype.singleton) { // do something } } } // good class Store { constructor() { // do something } }
-
Vue テンプレートには
.vue
を使用してください。HAML では%template
を使用しないでください。 -
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'), }));
命名
-
拡張子:Vue コンポーネントの拡張子は
.vue
を使用します。ファイル拡張子として.js
を使用しないでください(#34371)。 -
リファレンスの命名:デフォルトのインポートには PascalCase を使用します:
// bad import cardBoard from 'cardBoard.vue' components: { cardBoard, }; // good import CardBoard from 'cardBoard.vue' components: { CardBoard, };
- プロップの命名: DOM コンポーネントの prop 名の使用は避けてください。
-
プロップの命名:テンプレートで小道具を指定するには、キャメルケースの代わりにケバブケースを使用してください。
// bad <component class="btn"> // good <component css-class="btn"> // bad <component myProp="prop" /> // good <component my-prop="prop" />
整列
-
テンプレートメソッドでは、以下のアライメントスタイルに従ってください:
-
複数の属性がある場合、すべての属性は改行されるべきです:
// 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>
-
属性が1つだけの場合、タグはインラインにすることができます:
// good <component bar="bar" /> // good <component bar="bar" /> // bad <component bar="bar" />
-
引用
-
テンプレート内部では常に二重引用符
"
を使用し、その他のJSでは一重引用符'
を使用します。// bad template: ` <button :class='style'>Button</button> ` // good template: ` <button :class="style">Button</button> `
プロップス
-
小道具はオブジェクトとして宣言する必要があります。
// bad props: ['foo'] // good props: { foo: { type: String, required: false, default: 'bar' } }
-
プロップを宣言するとき、必須キーは常に提供されるべきです。
// bad props: { foo: { type: String, } } // good props: { foo: { type: String, required: false, default: 'bar' } }
-
プロパテ ィが必須でない場合はデフォルトキーを指定すべきです。プロパティの存在をチェックする必要があるいくつかのシナリオがあります。これらの場合、デフォルトキーは提供されるべきではありません。
// good props: { foo: { type: String, required: false, } } // good props: { foo: { type: String, required: false, default: 'bar' } } // good props: { foo: { type: String, required: true } }
データ
-
data
メソッドは常に関数// bad data: { foo: 'foo' } // good data() { return { foo: 'foo' }; }
ディレクティブ
-
速記法
@
はv-on
// bad <component v-on:click="eventHandler"/> // good <component @click="eventHandler"/>
-
速記法
:
はv-bind
// bad <component v-bind:class="btn"/> // good <component :class="btn"/>
-
速記法
#
はv-slot
// bad <template v-slot:header></template> // good <template #header></template>
終了タグ
-
自動閉鎖コンポーネントタグを優先
// bad <component></component> // good <component />
テンプレート内でのコンポーネントの使用
-
テンプレート内でコンポーネントを使用する場合、他のスタイルよりもケバブケースの名前を優先します。
// bad <MyComponent /> // good <my-component />
順序付け
-
.vue
ファイルのタグの順序<script> // ... </script> <template> // ... </template> // We don't use scoped styles but there are few instances of this <style> // ... </style>
-
Vue コンポーネントのプロパティ:コンポーネントルールのプロパティの順序を確認します。
:key
v-for
を使用する場合は、各項目に一意の :key
属性を指定する必要があります。
-
反復処理される配列の要素が一意な
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>
-
v-for
をtemplate
とともに使用し、子要素が複数ある場合、: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
変数に再割り当てします。
グローバルで変更可能なラッパーを作成すると、以下のような利点があります:
-
コンポーネント/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.js
にenableAutoDestroy
を設定し、テスト実行後にコンポーネントを自動的に破棄します。
createComponent
ファクトリ
実装ロジックの重複を避けるために、各テストブロックで再利用できるcreateComponent
ファクトリー関数を定義しておくと便利です。これは、wrapper
変数をmount
とshallowMount
の結果に再割り当てするクロージャです:
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
ベストプラクティス
-
多くの引数ではなく、単一の(または限られた数の)オブジェクト引数を使用することを検討してください。
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 } = {}) { }
-
同じテストセット内で
mount
とshallowMount
の両方が必要な場合は、createComponent
ファクトリ用のmountFn
パラメータを定義しておくと便利です。 このパラメータは、コンポーネントのマウントに使用するマウント関数 (mount
あるいはshallowMount
) を受け取ります: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
を設定します:// bad wrapper = shallowMount(MyComponent); wrapper.setProps({ myProp: 'my cool prop' }); // good wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
ただし、何らかの方法でコンポーネントの反応性をテストしたい場合は例外です。例えば、特定のウォッチャーが実行された後のコンポーネントの出力をテストしたい場合です。このような動作のテストに
setProps
を使用しても問題ありません。
コンポーネントの状態へのアクセス
-
プロップやアトリビュートにアクセスする場合は、
wrapper.props().myProp
やwrapper.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);
-
複数のプロップをアサートする場合、
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', });
-
一部の小道具にしか興味がない場合は、
toMatchObject
を使用できます。expect.objectContaining
よりもtoMatchObject
を優先してください:// good expect(wrapper.props()).toEqual(expect.objectContaining({ propA: 'valueA', propB: 'valueB', })); // better expect(wrapper.props()).toMatchObject({ propA: 'valueA', propB: 'valueB', });
小道具のバリデーションのテスト
- コンポーネントの小道具をチェックする際には
assertProps
ヘルパーを使います。小道具のバリデーションの失敗はエラーとして投げられます。
import { assertProps } from 'helpers/assert_props'
// ...
expect(() => assertProps(SomeComponent, { invalidPropValue: '1', someOtherProp: 2 })).toThrow()
JavaScriptとVueの合意
この合意の目的は、私たち全員が同じページにいることを確認することです。
- Vueを記述する場合、アプリケーションでjQueryを使用することはできません。
- DOMからデータを取得する必要がある場合は、アプリケーションの起動中に1回だけDOMにクエリを発行し、
dataset
。これはjQueryを使わなくてもできます。 - Vue.jsでjQueryの依存関係を使用するには、ドキュメントにあるこの例に従ってください。
- 外部のjQueryイベントをVueアプリケーション内でリッスンする必要がある場合は、jQueryイベントリスナーを使用できます。
- jQueryイベントを新たに追加する必要はありません。新しいjQueryイベントを追加する代わりに、同じタスクを実行する別の方法を見てみましょう。
- DOMからデータを取得する必要がある場合は、アプリケーションの起動中に1回だけDOMにクエリを発行し、
- アプリケーションの起動中に、
window
オブジェクトに一度だけ、アプリケーション固有のデータをクエリすることができます (たとえば、scrollTo
はいつでもアクセスできます)。このアクセスは、アプリケーションの起動中に行ってください。 - 後でリファクタリングするために、私たちの標準に従わないコードを書くことで、一時的ではありますが、すぐに技術的負債を作る必要があるかもしれません。メンテナーは、そもそも技術的負債を受け入れる必要があります。その技術的負債をさらに評価し、議論するために、イシューを作成すべきです。その優先順位はメンテナーが決定します。
- 技術的負債を作るときは、そのコードのテストを事前に書いておかなければなりません。例えば、jQueryのテストをVueのテストに書き換えるなどです。
- 状態の一元管理としてVueXを使用することもできます。VueXを使用しない場合は、Vue.jsのドキュメントに記載されているストアパターンを使用する必要があります。
- 一元的な状態管理ソリューションを選択したら、アプリケーション全体でそれを使用する必要があります。状態管理ソリューションを混ぜて使用しないでください。