JavaScriptスタイルガイド

私たちはAirbnb JavaScriptスタイルガイドとそれに付随するリンターを使用して、JavaScriptスタイルガイドラインのほとんどを管理しています。

Airbnbが定めたスタイルガイドラインに加え、私たちは以下のような特別なルールを設けています。

note
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);
note
文字列が非整数を表す可能性がある場合(つまり、小数を含む場合)は、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を返した場合の内部サーバーエラーについては、一般的なエラーメッセージを返すべきです。

バックエンドがエラーを返す場合、そのエラーはユーザーに表示するのに適したものでなければなりません。

何らかの理由でそれが難しい場合は、最後の手段として、特定のエラーメッセージをプレフィックス付きで選択することができます:

  1. バックエンドがエラーメッセージの先頭にプレフィックスをつけて表示するようにします:

    Gitlab::Utils::ErrorMessage.to_user_facing('Example user-facing error-message')
    
  2. 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 APIGraphQL のガイドに従ってください。