- 避ける
forEach
- パラメータ数の制限
- DOM イベントを処理するクラスは避けましょう。
- コンストラクタに要素コンテナを渡します。
- 文字列から整数への変換
- CSS セレクタ - 接頭辞
js-
を使用してください。 - ES モジュールの構文
- CommonJSモジュールの構文
- モジュールの絶対パスと相対パス
- ページ以外のモジュールでは
DOMContentLoaded
を使用しないでください。 - XSSを避ける
- ESLint
- IIFE
- グローバル名前空間
- 副作用
- 純粋関数とデータの変異
- 定数をプリミティブとしてエクスポート
- エラーハンドリング
JavaScriptスタイルガイド
私たちはAirbnb JavaScriptスタイルガイドとそれに付随するリンターを使用して、JavaScriptスタイルガイドラインのほとんどを管理しています。
Airbnbが定めたスタイルガイドラインに加え、私たちは以下のような特別なルールを設けています。
yarn run lint:eslint:all
またはyarn run lint:eslint $PATH_TO_FILE
を実行することで、ESLint を内部で実行できます。避けるforEach
データを変更する場合は、forEach
を避けてください。forEach
の代わりにmap
,reduce
,filter
を使用してください。これはAirbnbのスタイルガイドに沿ったものです。
// bad
users.forEach((user, index) => {
user.id = index;
});
// good
const usersWithId = users.map((user, index) => {
return Object.assign({}, user, { id: index });
});
パラメータ数の制限
関数やメソッドに3つ以上のパラメータがある場合は、代わりにオブジェクトをパラメータとして使用します。
// bad
function a(p1, p2, p3, p4) {
// ...
};
// good
function a({ p1, p2, p3, p4 }) {
// ...
};
DOM イベントを処理するクラスは避けましょう。
クラスの目的が DOM イベントをバインドしてコールバックを処理することだけであれば、関数を使用することを推奨します。
// bad
class myClass {
constructor(config) {
this.config = config;
}
init() {
document.addEventListener('click', () => {});
}
}
// good
const myFunction = () => {
document.addEventListener('click', () => {
// handle callback here
});
}
コンストラクタに要素コンテナを渡します。
クラスが DOM を操作する際に、要素コンテナをパラメータとして受け取ります。このほうがメンテナーでパフォーマンスもよくなります。
// bad
class a {
constructor() {
document.querySelector('.b');
}
}
// good
class a {
constructor(options) {
options.container.querySelector('.b');
}
}
文字列から整数への変換
文字列を整数に変換する場合、Number
セマンティックで読み Number
やすくなります。Number
どちらも可能ですが、 Number
若干メンテナーの方が有利です。
警告: parseInt
には基数の引数を含める必要があります。
// bad (missing radix argument)
parseInt('10');
// good
parseInt("106", 10);
// good
Number("106");
// bad (missing radix argument)
things.map(parseInt);
// good
things.map(Number);
parseInt
を使用しないでください。代わりにNumber
またはparseInt
を検討してください。CSS セレクタ - 接頭辞js-
を使用してください。
CSS クラスが JavaScript で要素への参照としてのみ使用される場合、クラス名の前にjs-
を付けます。
// bad
<button class="add-user"></button>
// good
<button class="js-add-user"></button>
ES モジュールの構文
ほとんどの JavaScript ファイルでは、ES モジュール構文を使用してモジュールをインポートまたはエクスポートします。名前付きエクスポートの方が、名前の一貫性が保たれます。
// bad (with exceptions, see below)
export default SomeClass;
import SomeClass from 'file';
// good
export { SomeClass };
import { SomeClass } from 'file';
デフォルトのエクスポートを使用することは、いくつかの特別な状況では許容されます:
- Vue シングルファイルコンポーネント(SFC)
- Vuex 変異ファイル
詳細については、RFC 20を参照してください。
CommonJSモジュールの構文
Nodeの設定にはCommonJSモジュール構文が必要です。名前付きエクスポートを優先してください。
// bad
module.exports = SomeClass;
const SomeClass = require('./some_class');
// good
module.exports = { SomeClass };
const { SomeClass } = require('./some_class');
モジュールの絶対パスと相対パス
インポートするモジュールが2階層以下の場合は相対パスを使います。
// bad
import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
// good
import GitLabStyleGuide from '../GitLabStyleGuide';
インポートするモジュールが2つ以上上の階層にある場合は、代わりに絶対パスを使用してください:
// bad
import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
// good
import GitLabStyleGuide from '~/GitLabStyleGuide';
また、グローバル・ネームスペースに追加しないでください。
ページ以外のモジュールではDOMContentLoaded
を使用しないでください。
インポートされたモジュールは、ロードされるたびに同じ動作をする必要があります。DOMContentLoaded
イベントは、/pages/*
ディレクトリにロードされたモジュールに対してのみ許可されています。これらは webpack で動的にロードされるためです。
XSSを避ける
innerHTML
,append()
,html()
を使ってコンテンツを設定しないでください。脆弱性を開きすぎてしまいます。
ESLint
ESLintの動作はツールガイドに記載されています。
IIFE
IIFE (Immediately-Invoked Function Expressions)の使用は避けてください。ファイルの中身をIIFEでラップする例はたくさんありますが、Sprocketsからwebpackへの移行後、これはもう必要ありません。レガシーコードをリファクタリングする際には、IIFEを使用せず、自由に削除してください。
グローバル名前空間
グローバル名前空間への追加は避けてください。
// bad
window.MyClass = class { /* ... */ };
// good
export default class MyClass { /* ... */ }
副作用
トップレベルの副作用
export
を含むスクリプトでは、トップレベルの副作用は禁止されています:
// bad
export default class MyClass { /* ... */ }
document.addEventListener("DOMContentLoaded", function(event) {
new MyClass();
}
コンストラクタでの副作用の回避
constructor
内で非同期コールや API リクエスト、DOM 操作を行わないようにしましょう。 代わりに別の関数に移しましょう。こうすることで、テストが書きやすくなり、単一責任の原則に反することもなくなります。
// bad
class myClass {
constructor(config) {
this.config = config;
axios.get(this.config.endpoint)
}
}
// good
class myClass {
constructor(config) {
this.config = config;
}
makeRequest() {
axios.get(this.config.endpoint)
}
}
const instance = new myClass();
instance.makeRequest();
純粋関数とデータの変異
小さな純粋関数をたくさん書き、突然変異が起こる場所を最小限にするよう努力します。
// bad
const values = {foo: 1};
function impureFunction(items) {
const bar = 1;
items.foo = items.a * bar + 2;
return items.a;
}
const c = impureFunction(values);
// good
var values = {foo: 1};
function pureFunction (foo) {
var bar = 1;
foo = foo * bar + 2;
return foo;
}
var c = pureFunction(values.foo);
定数をプリミティブとしてエクスポート
オブジェクトをエクスポートするよりも、共通の名前空間を持つ定数プリミティブをエクスポートすることを推奨します。こうすることで、コンパイル時の参照チェックがうまくいき、実行時に誤ってundefined
を使ってしまうのを防ぐことができます。さらに、バンドルサイズを小さくするのにも役立ちます。
定数をコレクション (配列やオブジェクト) としてエクスポートするのは、たとえばプロップバリデータのように定数を繰り返し処理する必要があるときだけにしてください。
// bad
export const VARIANT = {
WARNING: 'warning',
ERROR: 'error',
};
// good
export const VARIANT_WARNING = 'warning';
export const VARIANT_ERROR = 'error';
// good, if the constants need to be iterated over
export const VARIANTS = [VARIANT_WARNING, VARIANT_ERROR];
エラーハンドリング
サーバーが500
を返した場合の内部サーバーエラーについては、一般的なエラーメッセージを返すべきです。
バックエンドがエラーを返す場合、そのエラーはユーザーに表示するのに適したものでなければなりません。
何らかの理由でそれが難しい場合は、最後の手段として、特定のエラーメッセージをプレフィックス付きで選択することができます:
-
バックエンドがエラーメッセージの先頭にプレフィックスをつけて表示するようにします:
Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message')
-
app/assets/javascripts/lib/utils/error_message.js
に含まれるエラーメッセージユーティリティ関数を使用してください。
このユーティリティは、サーバ応答から受け取ったエラーオブジェクトとデフォルトのエラーメッセージの2つのパラメータを受け付けます。このユーティリティは、エラーオブジェクトのメッセージを調べ、そのメッセージがユーザー向けであるかどうかを示す接頭辞を調べます。メッセージがユーザー向けである場合、ユーティリティはそれをそのまま返します。そうでない場合は、パラメータとして渡されたデフォルトのエラーメッセージを返します。
import { parseErrorMessage } from '~/lib/utils/error_message';
onError(error) {
const errorMessage = parseErrorMessage(error, genericErrorText);
}
この接頭辞はAPIレスポンスに使用してはならないことに注意してください。エラーオブジェクトを使用する方法については、REST APIやGraphQL のガイドに従ってください。