このガイドはあなたのVue.js のコードを統一する方法を提供します。
- 開発者/チームメンバがより問題を理解し、見つけやすくする。
- IDEがよりコードを解釈し、支援を提供しやすくする。
- すでに使用しているビルドツールを(再)利用しやすくする。
- 別々のコードの塊を蓄え、供給しやすくする。
このガイドはDe VoorhoedeによるRiotJS Style Guideに刺激を受け作られました。
- コンポーネントの記述をシンプルに保つ
- コンポーネントのpropsをプリミティブに保つ
- コンポーネントのpropsの利用
this
をcomponent
に割り当てる- コンポーネント構成
- コンポーネントイベント名
this.$parent
を避けるthis.$refs
は注意して使用する- スタイルスコープとしてコンポーネント名を使用する
- コンポーネントAPIをドキュメント化する
- コンポーネントデモの追加
- コンポーネントファイルをLintする
- 必要に応じてコンポーネントを作成する
常に単一の機能を持つ小さなモジュールからアプリケーションを構築しましょう。
モジュールはアプリケーションの自己完結型の小さな部品です。特にVue.jsライブラリはあなたがview-logicなモジュールを作れるように設計されています。
小さなモジュールにすることで、あなたと他の開発者両方にとって、学びやすく、理解しやすく、維持しやすく、再利用しやすく、そして、 デバッグしやすくなります。
各Vueコンポーネント(モジュールのようなもの)は FIRST: ひとつのことに集中し (Focused (単一責任))、独立していて(Independent)、 再利用可能で(Reusable)、小さく(Small) そしてテスト可能 (Testable)でなければなりません。
もしあなたのコンポーネントが多くのことをしていて大きすぎる場合、ひとつのことだけをする、より小さなコンポーネントに分けましょう。 経験則から言うと、各コンポーネントは100行以下のコードになるようにするのがいいでしょう。また、例えばスタンドアローンのデモを追加することによって、 Vueコンポーネントが独立して動作することを確認しましょう。
各コンポーネントの名前は、
- 意味のある名前で: 具体的過ぎず、抽象的過ぎず。
- 短く: 2または3語。
- 発音可能: それらについて話せるようにしたい。
であるべきです。
さらにVueコンポーネントの名前は、
- カスタム要素仕様に準拠: ハイフンを含み、 予約語は使用しない。
app-
ネームスペース: 非常に汎用的、あるいは1語であれば、他のプロジェクトでも容易に再利用できる。
であるべきです。
- 名前は、コンポーネントについて会話するときに使用されます。したがって、それは短く、意味があり、発音可能でなければなりません。
<!-- 推奨 -->
<app-header></app-header>
<user-list></user-list>
<range-slider></range-slider>
<!-- 非推奨 -->
<btn-group></btn-group> <!-- 短いですが, 発音が困難です。代わりに`button-group`を使いましょう -->
<ui-slider></ui-slider> <!-- 全てコンポーネントはuiなので、中身を表していません -->
<slider></slider> <!-- カスタム要素仕様に準拠していません -->
Vue.jsのインラインの記述は100%JavaScriptです。これは非常に強力なことですが、複雑になる可能性もあるということです。 ですので、シンプルな記述を保つようにしましょう。.
- 複雑なインラインの記述は判読困難です。
- インラインの記述は他の場所で再利用できません。これはコードの重複と腐敗につながります。
- IDEは基本的に式の構文サポート機能を持っていないため、自動補完や検証を行うことができません。
もしあまりにも複雑だったり、判読困難な場合はメソッド、またはcomputedプロパティに移動させましょう!
<!-- 推奨 -->
<template>
<h1>
{{ `${year}-${month}` }}
</h1>
</template>
<script type="text/javascript">
export default {
computed: {
month() {
return this.twoDigits((new Date()).getUTCMonth() + 1);
},
year() {
return (new Date()).getUTCFullYear();
}
},
methods: {
twoDigits(num) {
return ('0' + num).slice(-2);
}
},
};
</script>
<!-- 非推奨 -->
<template>
<h1>
{{ `${(new Date()).getUTCFullYear()}-${('0' + ((new Date()).getUTCMonth()+1)).slice(-2)}` }}
</h1>
</template>
Vue.jsは複雑なJavaScriptオブジェクトを渡せるようになっていますが, コンポーネントのpropsはできるだけプリミティブに保つ ようにするべきです。複雑なオブジェクトの使用を避け、JavaScriptプリミティブと関数のみを使うようにしましょう。
- 各propの属性を別々に使用することにより、コンポーネントは明確で表現力豊かなAPIを持つことになります。
- propsの値としてプリミティブとファンクションのみを使用することで、コンポーネントのAPIをネイティブHTML(5)のAPIに似たものにできます。
- 各propの属性を使用することで、他の開発者がコンポーネントインスタンスに何が渡されるかを理解しやすくなります。
- 複雑なオブジェクトが渡されると、そのオブジェクトのどのプロパティとメソッドが実際にカスタムコンポーネントで使われるかがわかりにくくなります。これによりコードのリファクタリングが難しくなり、腐敗を招くことになります。
プリミティブまたは関数を値としたpropsごとのコンポーネント属性を使用します。
<!-- 推奨 -->
<range-slider
:values="[10, 20]"
min="0"
max="100"
step="5"
:on-slide="updateInputs"
:on-end="updateResults">
</range-slider>
<!-- 非推奨 -->
<range-slider :config="complexConfigObject"></range-slider>
Vue.jsでは、あなたのコンポーネントのpropsはあなたのAPIです。頑丈で予測しやすいAPIは、他の開発者があなたのコンポーネントを使用するのを簡単にします。
コンポーネントのpropsはカスタムHTML属性を介して渡されます。 これらの属性の値はVue.jsプレーンストリング (:attr="値"
または v-bind:attr="値"
)か、または無いこともあります。 あなたは コンポーネントのpropsを利用 して、それらの異なるケースに対応できるようにしましょう。
コンポーネントのpropsを利用することで、あなたのコンポーネントを常に機能するようになります(防御的プログラミング)。それは後で他の開発者が、あなたが想定していない方法でコンポーネントを使用する場合でもです。
- propsのデフォルト値を使用します。
- 期待するタイプの値の検証のために、
type
オプションを使用します。[1*] - 使用される前にpropsが存在するかチェックします。
<template>
<input type="range" v-model="value" :max="max" :min="min">
</template>
<script type="text/javascript">
export default {
props: {
max: {
type: Number, // [1*] これは'max'propが数値であることを検証します。
default() { return 10; },
},
min: {
type: Number,
default() { return 0; },
},
value: {
type: Number,
default() { return 4; },
},
},
};
</script>
Vue.jsコンポーネントのコンテキスト内では、 this
はコンポーネントインスタンスにバインドされています。
したがって、別のコンテキストで参照する必要がある場合は、 this
がcomponent
として使用できることを確認してください。
言い換えれば: const self = this;
のようなコーディングはもう しないで ください。 Vueコンポーネントの使用は安全です。
- ES6を使っている場合、
this
を変数に保存しておく必要はありません。 - 通常、アロー関数を使用すれば静的スコープは保持されます。
- ES6を使用していないために、
アロー関数
を使用できない場合は、this
を変数に格納する必要があります。それが唯一の例外です。
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
console.log(this.hello());
},
},
};
</script>
<!-- 非推奨 -->
<script type="text/javascript">
export default {
methods: {
hello() {
return 'hello';
},
printHello() {
const self = this; // 不要
console.log(self.hello());
},
},
};
</script>
論理的に考えやすく、思考の流れに従いやすいようにしましょう. 方法を見てください。
- コンポーネントを明確かつグループ化されたオブジェクトとすることで、コードを読みやすくし、開発者はコードの基準を簡単に持てるようになります。
- props、data、computed、watches、そしてmethodsをアルファベット順に並べることで、見つけやすくなります。
- 繰り返しになりますが, コンポーネントをグループ化することで読みやすくなります (name、extends、props、dataそしてcomputed、components、 watch、methods、lifecycle methods、など);
name
属性を使いましょう. vue devtoolsとname属性を使うと、開発/テストが容易になります。- BEM、または rscssのようなCSSの命名方法論を使いましょう。 - 詳細?;
- Vue.jsの製作者Evan Youが推奨するように、テンプレートスクリプト形式の.vueファイル構成を使用しましょう。
コンポーネンのト構成:
<template lang="html">
<div class="Ranger__Wrapper">
<!-- ... -->
</div>
</template>
<script type="text/javascript">
export default {
// このちいさな要素を忘れないでください
name: 'RangeSlider',
// 新しいコンポーネントを合成します
extends: {},
// コンポーネントのプロパティ/値
props: {
bar: {}, // アルファベット順
foo: {},
fooBar: {},
},
// 変数
data() {},
computed: {},
// 他のコンポーネントを使用
components: {},
// メソッド
watch: {},
methods: {},
// コンポーネントライフサイクルフック
beforeCreate() {},
mounted() {},
};
</script>
<style scoped>
.Ranger__Wrapper { /* ... */ }
</style>
Vue.jsはすべてのVueハンドラ関数を提供し、式はViewModelに厳密にバインドされています。各コンポーネントのイベントは、開発中の問題を回避するような良い名前付けのスタイルを適用しましょう。 以下の 理由 を参照してください。
- 開発者は好きなイベント名を自由に使うことができるため、混乱を招く可能性があります。
- イベントに名前をつける自由は、 DOMテンプレートの非互換性につながる可能性があります。
- イベント名前はケバブケースにするべきです。
- あなたのコンポーネントで外部で関心を持つ独自のアクションのため、upload-success、upload-error、さらにはdropzone-upload-success、 dropzone-upload-errorのような固有の名前をつける必要があります。(スコープ付きプレフィックスを使用する必要がある場合は)
- イベント名は末尾が不定形の動詞(例 client-api-load)、または名詞(例 drive-upload-success)で終わるべきです。(出典);
Vue.jsは、親コンテキストにアクセスできるネストされたコンポーネントをサポートしています。 あなたのVueコンポーネントの外部のコンテキストにアクセスすることは、 モジュールベースの開発のFIRSTのルールに違反するため、 ** this.$parent
の使用を避ける ** べきです。
- vueコンポーネントは、他のコンポーネントと同様に、独立して動作する必要があります。 コンポーネントがその親にアクセスする必要がある場合、このルールは壊されます。
- コンポーネントがその親にアクセスする必要がある場合、別のコンテキストで再利用することはできません。
- 属性/プロパティを使用して、親から子コンポーネントに値を渡します。
- 属性の式のコールバックを使用して、親コンポーネントで定義されたメソッドを子コンポーネントに渡します。
- 子コンポーネントからイベントを発行し、それを親コンポーネントでキャッチします。
Vue.jsはref
属性を介して他のコンポーネントや、基本的なHTML要素のコンテキストにアクセスできるコンポーネントをサポートしています。この属性はthis.$refs
を介して、コンポーネントまたDOM要素のコンテキストにアクセス可能な方法を提供します。ほとんどの場合、 this.$refs
を介して 他のコンポーネント のコンテキストにアクセスする必要性は避けることができます。このため、間違ったコンポーネントAPIを避けるためにこれを使用するときは注意が必要です。
- vueコンポーネントは、他のコンポーネントと同様に、独立して動作する必要があります。 コンポーネントが必要なすべてのアクセスをサポートしていない場合、それは悪い設計/実装です。
- ほとんどのコンポーネントでは、プロパティとイベントで十分です。
- 良いコンポーネントAPIを作りましょう。
- 独創的なコンポーネントの目的に常に注意してください。
- 特殊なコードを書かないでください。ジェネリックコンポーネント内に特殊なコードを記述する必要がある場合は、APIが十分に一般的でないか、他のケースを管理するために新しいコンポーネントが必要になるということです。
- すべてのpropsをチェックして、欠けているものがあるかどうか確認します。 もしあれば、課題を作成するかあなたのコンポーネントを強化しましょう。
- すべてのイベントをチェックしましょう。ほとんどのケースで、開発者は子-親のコミュニケーション(イベント)が必要であることを忘れてしまいます。 そのため、(propsを使用した)親-子のコミュニケーションだけを覚えているのです。
- Propsは下へ、eventsは上へ! ゴールとして良いAPIと分離を要求された場合、コンポーネントをアップグレードしましょう。
- propsやeventsが消耗し、コンポーネントでの
this.$refs
の利用が利にかなっているときは、それを使用するべきです。 (下の例を参照してください). - データバインディングやディレクティブで要素を操作できない場合、 (
jQuery
,document.getElement*
,document.queryElement
の代わりに)this.$refs
を使用してDOM要素にアクセスすることは良い方法です。
<!-- 良いです。refは必要ありません。 -->
<range :max="max"
:min="min"
@current-value="currentValue"
:step="1"></range>
<!-- this.$refsを使用する場合の良い例です。 -->
<modal ref="basicModal">
<h4>Basic Modal</h4>
<button class="primary" @click="$refs.basicModal.hide()">Close</button>
</modal>
<button @click="$refs.basicModal.open()">Open modal</button>
<!-- モーダルコンポーネント -->
<template>
<div v-show="active">
<!-- ... -->
</div>
</template>
<script>
export default {
// ...
data() {
return {
active: false,
};
},
methods: {
open() {
this.active = true;
},
hide() {
this.active = false;
},
},
// ...
};
</script>
<!-- 発行される可能性のあるものへのアクセスは避けましょう -->
<template>
<range :max="max"
:min="min"
ref="range"
:step="1"></range>
</template>
<script>
export default {
// ...
methods: {
getRangeCurrentValue() {
return this.$refs.range.currentValue;
},
},
// ...
};
</script>
Vue.jsのコンポーネント要素は、スタイルスコープのルートとして非常によく使用されるカスタム要素です。 あるいは、コンポーネント名はCSSクラスネームスペースとして、使用できます。
-
コンポーネント要素のスタイルをスコープすると、コンポーネントの外にスタイルが漏れるのを防ぐため、予測可能性が向上します。
-
モジュールディレクトリに同じ名前を使用すると、Vue.jsコンポーネントとスタイルルートが同じところに属していることを開発者が理解しやすくなります。
BEMとOOCSSに基づくネームスペースプレフィックスとしてコンポーネント名を使いましょう。 そして スタイルクラスでscoped
属性を使いましょう。
scoped
を使用すると、<style>
にあるすべてのクラスにシグネチャを追加するようVueコンパイラに指示します。このシグネチャは、コンポーネントを構成するすべてのタグにコンポーネントCSSを適用するようブラウザに(サポートしている場合)強制し、CSSのスタイリングが漏れないようにします。
<style scoped>
/* 推奨 */
.MyExample { }
.MyExample li { }
.MyExample__item { }
/* 非推奨 */
.My-Example { } /* コンポーネント名またはモジュール名にスコープされておらず、BEM準拠でもありません */
</style>
Vue.jsコンポーネントのインスタンスは、アプリケーション内のコンポーネント要素を使用して作成されます。インスタンスはそのカスタム属性によって構成されます。他の開発者がコンポーネントを使用するときのため、これらのカスタム属性(コンポーネントのAPI)をREADME.md
ファイルに記述しましょう。
- ドキュメンテーションは、開発者に、そのコードのすべてを伝えることなく、コンポーネントの概要を高レベルで表示します。これにより、コンポーネントのアクセスが容易になり、使いやすくなります。
- コンポーネントのAPIは、それを構成するカスタム属性のセットです。したがって、これらは、コンポーネントを使用する(開発しない)開発者にとって特に重要です。
- ドキュメンテーションはAPIを形式化し、コンポーネントのコードを変更するときに下位互換性を維持する機能を開発者に教えます。
README.md
は最初に読み込まれるドキュメントのデファクトスタンダードファイル名です。 コードリポジトリホスティングサービス(Github、Bitbucket、Gitlabなど)は、ソースディレクトリを参照するときに、READMEファイルの内容を直接表示します。これは私たちのモジュールディレクトリにも当てはまります。
コンポーネントのモジュールディレクトリにREADME.md
ファイルを追加しましょう。
range-slider/
├── range-slider.vue
├── range-slider.less
└── README.md
READMEファイルに、モジュールの機能と使用方法を記述しましょう。 vueコンポーネントの場合、APIとしてサポートされているカスタム属性を記述するのが最も役立ちます。
range sliderは、スライダーレールのハンドルを開始値と終了値の両方でドラッグして数値範囲を設定できます。
このモジュールはクロスブラウザとタッチサポートのため、noUiSliderを使用します。
<range-slider>
次のカスタムコンポーネント属性をサポートしています:
属性 | 型 | 説明 |
---|---|---|
min |
数値 | レンジの始まりの数値 (下限)。 |
max |
数値 | レンジの終わりの数値 (上限). |
values |
数値[] 任意 | 開始値と終了値を含む配列。 例. values="[10, 20]" . デフォルトは[opts.min, opts.max] . |
step |
数値 任意 | インクリメント/デクリメントの数値。デフォルトは1。 |
on-slide |
関数 任意 | ユーザーが開始(HANDLE == 0 )または終了(HANDLE == 1 )ハンドルをドラッグしているときに、(values, HANDLE) で呼び出される関数。例. on-slide={ updateInputs } component.updateInputs = (values, HANDLE) => { const value = values[HANDLE]; } . |
on-end |
関数 任意 | ユーザーがハンドルのドラッグを停止したとき、(values, HANDLE) で呼び出される関数。 |
スライダの外観をカスタマイズするにはnoUiSliderドキュメントのStylingセクションを参照してください。
構成の異なるコンポーネントのデモを含むindex.htmlファイルを追加し、コンポーネントの使用方法を示しましょう。
- コンポーネントのデモは、コンポーネントが単独で動作することを示します。
- コンポーネントのデモは、ドキュメンテーションやコードを掘り起こす前に、開発者にプレビューを提供します。
- デモでは、コンポーネントを使用できるすべての可能な構成とバリエーションを示すことができます。
Lintersはコードの一貫性を高め、構文エラーの追跡に役立ちます。.vueファイルはプロジェクトにeslint-plugin-html
を追加してlintすることができます。vue-cliを使用すれば、ESLintをデフォルトで有効にしてプロジェクトを開始できます。
- コンポーネントファイルのLintは、すべての開発者が同じコードスタイルを使用するようにします。
- コンポーネントファイルのLintは、遅くなる前に構文エラーの追跡をするのに役立ちます。
lintersがあなたの*.vue
ファイルからスクリプトを抽出するためには, スクリプトを<script>
コンポーネントの中に置き、 コンポーネントの記述をシンプルに保つようにしてください (lintersはそれらを理解できないので)。 グローバル変数vue
とコンポーネントのprops
を許可するようにリンターを設定します。
ESLintには、コンポーネントファイルからスクリプトを抽出するための追加のESLint HTML pluginが必要です。
ESLintを.eslintrc
ファイルに設定します。(IDEもそれを解釈することができます):
{
"extends": "eslint:recommended",
"plugins": ["html"],
"env": {
"browser": true
},
"globals": {
"opts": true,
"vue": true
}
}
ESLint実行
eslint src/**/*.vue
JSHintは (--extra-ext
を使用して)HTMLをパースでき、(--extract=auto
を使用して)スクリプトを抽出できます。
JSHintを.jshintrc
ファイルに設定します。 (IDEもそれを解釈することができます):
{
"browser": true,
"predef": ["opts", "vue"]
}
JSHint実行
jshint --config modules/.jshintrc --extra-ext=html --extract=auto modules/
注意: JSHintはvue
を拡張子として受け入れず, htmlだけを受け入れます。
Vue.jsはコンポーネントフレームワークに基づいています。コンポーネントの作成タイミングを知らないと、次のような問題が発生する可能性があります。
- コンポーネントが大きすぎる場合は、おそらく(再)使用し、維持することは困難です。
- コンポーネントが小さすぎると、プロジェクトが浸水し、コンポーネント間の通信が困難になります。
- あなたのプロジェクトのニーズに合わせてコンポーネントを構築することを常に忘れないでください。しかし、それらのコンポーネントが取り出せると考えるようにしてください。ライブラリのようにプロジェクトから取り出せれば、より堅牢で一貫性のあるものになります。
- 既存のコンポーネントや安定したコンポーネントにコミュニケーション(propsやevents)を組み込むことができるため、できるだけ早くコンポーネントを構築する方が良いでしょう。
- まず、できるだけ早くモーダル、ポップオーバー、ツールバー、メニュー、ヘッダーなどの明白なコンポーネントを作成してみてください。あなたの現在のページ、または全体的に、あなたが後で必要となることをあなたが知っているコンポーネントを。
- 第2に、新しい開発ごとに、ページ全体またはその一部分について、急いで開発する前に考えてみてください。その一部がコンポーネントであることが分かっている場合は、作成してください。
- 最後に、もし確信がなければ、コンポーネントを作らないでください!"後で役立つかもしれない"コンポーネントであなたのプロジェクトを汚染するのを避けましょう。それらは空っぽで、永遠にそこにあるだけかもしれません。プロジェクトの残りの部分との互換性の複雑さを避けるために、その状態になっていたことに気づいたらすぐにそれを分解するよう注意してください。
フォークしてプルリクエストを送るか、Issueを作ってください。
- ブラジルポルトガル語: Pablo Henrique Silva github:pablohpsilva, twitter: @PabloHPSilva
- ロシア語: Mikhail Kuznetcov github:shershen08, twitter: @legkoletat