JavaScript開発者必見!ESLint「prefer-const」でコードを改善

2025-06-01

ESLintの「prefer-const」ルールは、JavaScriptの変数宣言において、再代入されない変数をletではなくconstで宣言することを推奨するルールです。

JavaScriptでは、変数を宣言する方法として主にvarletconstの3つがあります。

  • const: ES6で導入されたキーワード。ブロックスコープを持ち、宣言時に値を初期化する必要があり、一度値を代入すると再代入はできません。 (ただし、オブジェクトや配列の場合、その中身のプロパティや要素は変更可能です。constが制限するのは変数自体への再代入です。)
  • let: ES6で導入されたキーワード。ブロックスコープを持ち、再代入は可能ですが、再宣言はできません。変数の値を後で変更する可能性がある場合に使用します。
  • var: 2015年のES6以前から存在したキーワード。関数スコープを持ち、巻き上げ(hoisting)の挙動が複雑で、再宣言や再代入が自由にできるため、意図しないバグの原因になることがありました。現代ではほとんど使われません。

なぜ「prefer-const」を使うのか?

「prefer-const」ルールが推奨される主な理由は以下の通りです。

  1. 意図の明確化
    変数がconstで宣言されている場合、その変数の値が後で変更されることはないという意図がコードを読む人に明確に伝わります。これにより、コードの理解が容易になります。
  2. バグの防止
    変数に誤って再代入してしまうという種類のバグを防ぐことができます。例えば、ループ内でカウンター変数とは別に、意図せず再代入されてはいけない値が上書きされてしまうようなケースです。
  3. 可読性の向上
    コードのどこで値が変更される可能性があるのか、どこは変更されないのかが視覚的に区別しやすくなります。
  4. 不変性の促進
    関数型プログラミングのパラダイムでは不変性(immutability)が重視されます。「prefer-const」はその原則に沿ったコーディングを促します。

「prefer-const」の適用例

例えば、以下のようなコードがあるとします。

// prefer-const ルールが有効な場合、警告/エラーになる例
let name = "Alice"; // 'name' is never re-assigned. Use 'const' instead.
console.log(name);

let age = 30; // 'age' is never re-assigned. Use 'const' instead.
// ... age を変更する処理がない場合
console.log(age);

// prefer-const ルールが適用されない例
let counter = 0;
counter++; // counter が再代入されているので OK
console.log(counter);

const PI = 3.14; // 最初から const で宣言しているので OK

上記の例で、「name」と「age」はletで宣言されていますが、その後どこでも再代入されていません。この場合、「prefer-const」ルールが有効になっていると、ESLintは「letではなくconstを使いましょう」という警告またはエラーを出力します。

設定方法

ESLintの設定ファイル(通常は.eslintrc.js.eslintrc.jsonなど)に、以下のようにルールを追加します。

{
  "rules": {
    "prefer-const": "error" // または "warn"
  }
}
  • "warn": ルールに違反した場合、ESLintは警告として報告しますが、通常はビルドを停止しません。
  • "error": ルールに違反した場合、ESLintはエラーとして報告し、多くのLinterはビルドを停止させます。


エラーメッセージ: 'variableName' is never reassigned. Use 'const' instead. prefer-const

説明
これは prefer-const ルールが最も典型的に出すエラーメッセージです。let で宣言された変数が、そのスコープ内で一度も再代入されていない場合に発生します。「この変数は変更されないのだから、const を使うべきですよ」というESLintからの指摘です。

よくある原因

  • グローバル変数や外部からの変更
    宣言されたスコープ内では再代入されていないが、実はコールバック関数や外部のスコープから間接的に変更されているケース。しかし、prefer-const は静的解析(コードを読み込むだけで判断する)であるため、このような動的な変更を検知できません。
  • 単純な見落とし
    変数をletで宣言したものの、後で値を変更する予定がなくなったり、そもそも変更する必要がない変数だったりする。

トラブルシューティング

  • 意図的に let を使う場合(稀なケース)

    • 本当に将来的に再代入の可能性がある場合
      現時点では再代入されていなくても、将来的にコードが変更されて再代入される可能性がある場合(例えば、開発中の機能でまだ実装されていない部分など)は、let のままで構いません。ESLintの警告を一時的に無効にするコメント(// eslint-disable-next-line prefer-const)を追加することもできますが、乱用は避けるべきです。
    • 複雑な初期化ロジック
      変数の初期化が複数ステップに分かれていて、宣言と同時に初期化できない場合など。
      let timer;
      function initialize() {
        if (condition) {
          clearInterval(timer);
        }
      }
      timer = setInterval(initialize, 100); // 宣言後に代入されているので let でOK
      
      このようなケースでは、ESLintの prefer-const ルールには ignoreReadBeforeAssign オプションがあります。これを true に設定すると、宣言と最初の代入の間に変数が参照されている場合、その変数を無視します。
      {
        "rules": {
          "prefer-const": ["error", { "ignoreReadBeforeAssign": true }]
        }
      }
      
  • デストラクチャリングの場合

    • 再代入される変数とされない変数を分ける:
      // 修正例
      let { a } = someObject;
      const { b } = someObject; // b は const に
      a = 10;
      console.log(b);
      
    • prefer-const ルールのオプション destructuring を調整する。デフォルトは "any" で、デストラクチャリングされた変数の一部でもconstにできるものがあれば警告します。これを "all" に設定すると、デストラクチャリングされた変数すべてがconstにできる場合にのみ警告します。
      // .eslintrc.js または .eslintrc.json
      {
        "rules": {
          "prefer-const": ["error", { "destructuring": "all" }]
        }
      }
      
      ただし、これはすべてのケースで望ましいとは限らないため、注意が必要です。
  • const への変更
    メッセージの通り、その変数が本当に再代入されないのであれば、letconst に変更します。これが最も推奨される解決策です。

    // 修正前
    let userName = "Taro";
    console.log(userName);
    
    // 修正後
    const userName = "Taro";
    console.log(userName);
    

オブジェクトや配列のプロパティの変更に関する誤解

説明
const で宣言された変数にオブジェクトや配列が代入されている場合、その変数自体に別のオブジェクトや配列を再代入することはできません。しかし、そのオブジェクトや配列の中身のプロパティや要素を変更することは可能です。

const myArray = [1, 2, 3];
myArray.push(4); // OK: 配列の中身を変更
console.log(myArray); // [1, 2, 3, 4]

// myArray = [5, 6]; // エラー: const 変数への再代入

この挙動を誤解していると、「const を使ったのに中身が変わるじゃないか!」と混乱することがあります。prefer-const ルールは、変数そのものへの再代入が行われていないかを見ているため、上記の myArray.push(4) のような操作では警告を出しません。

トラブルシューティング

  • 不変性を強制したい場合
    もしオブジェクトや配列の中身も不変にしたいのであれば、Object.freeze() を使用するなどの追加の対応が必要です。
    const myImmutableArray = Object.freeze([1, 2, 3]);
    // myImmutableArray.push(4); // エラー: Cannot add property 3, object is not extensible
    
  • const の意味の再確認
    const は「値が不変(immutable)」であるというよりは、「変数のバインディング(参照先)が不変」であると理解することが重要です。参照先のオブジェクト自体が可変(mutable)であるかどうかは、const とは別の概念です。

設定ファイル(.eslintrc.*)の問題

説明
ESLintが正しく設定されていない場合、prefer-const ルールが期待通りに動作しないことがあります。

よくある原因

  • ESLintのキャッシュ
    ESLintはパフォーマンス向上のためにキャッシュを使用します。設定を変更しても、キャッシュが原因で古い情報が使われてしまうことがあります。
  • パーサーの設定ミス
    特にTypeScriptを使用している場合、適切なパーサー(@typescript-eslint/parserなど)が設定されていないと、ESLintがコードを正しく解析できず、ルールの適用に失敗することがあります。
  • ルールが有効になっていない
    設定ファイルに prefer-const が追加されていない、または "off" に設定されている。

トラブルシューティング

  • キャッシュのクリア
    ESLintのキャッシュをクリアするために、node_modules/.cache/eslint ディレクトリを削除したり、eslint --fix --no-cache のように --no-cache オプションを付けて実行してみます。
  • 設定の確認
    • .eslintrc.js または .eslintrc.json を開き、rules セクションに "prefer-const": "error" または "prefer-const": "warn" が含まれていることを確認します。
    • TypeScriptを使っている場合は、parser@typescript-eslint/parser が設定されていることを確認します。
      {
        "parser": "@typescript-eslint/parser",
        "parserOptions": {
          "ecmaVersion": "latest", // または適切なESバージョン
          "sourceType": "module"
        },
        "plugins": [
          "@typescript-eslint"
        ],
        "rules": {
          "prefer-const": "error"
          // ... 他のルール
        }
      }
      

実行環境やエディタの統合の問題

説明
ESLintは正しく設定されているのに、エディタ(VS Codeなど)でリアルタイムにエラーが表示されなかったり、CI/CDパイプラインでだけエラーが出たりすることがあります。

よくある原因

  • CI/CD環境との差異
    開発環境とCI/CD環境でNode.jsのバージョンや依存関係のインストール状況が異なる。
  • パスの問題
    ESLintがプロジェクトのルートディレクトリから実行されていない、または間違った設定ファイルを参照している。
  • エディタ拡張機能の不具合
    エディタのESLint拡張機能が正しく動作していない、または設定が同期されていない。

トラブルシューティング

  • 依存関係の再インストール
    node_modules を削除し、npm install または yarn install を再実行して、依存関係が正しくインストールされていることを確認します。
  • ターミナルでの直接実行
    エディタではなく、ターミナルで直接 npx eslint your-file.jsnpm run lint を実行して、ESLintが正しくエラーを報告するか確認します。ターミナルでエラーが出て、エディタで出ない場合は、エディタ側の問題です。
  • エディタ拡張機能の設定確認
    VS Codeの場合、ESLint拡張機能の設定(ESLint: EnableESLint: Validate など)を確認します。
  • エディタの再起動
    エディタを再起動すると、ESLint拡張機能がリロードされ、問題が解決することがよくあります。


prefer-const ルールは、JavaScriptの変数宣言において、再代入されない変数をletではなくconstで宣言することを推奨します。これにより、コードの意図が明確になり、バグを減らすことができます。

ESLintが有効な環境で以下のコードを記述すると、prefer-constルールによってどのような警告やエラーが出るか、そしてどのように修正すべきかを見ていきましょう。

基本的な例(letからconstへの変更)

この例では、letで宣言された変数が一度も再代入されないため、ESLintが警告を出します。

問題のあるコード

// Example 1: Basic re-assignment check
let userName = "Alice";
console.log(userName);
// userName = "Bob"; // この行がコメントアウトされているため、userName は再代入されていない

ESLintの警告/エラー

/path/to/your/file.js
  2:5  error  'userName' is never reassigned. Use 'const' instead.  prefer-const

修正後のコード

// Example 1: Fixed
const userName = "Alice"; // let から const に変更
console.log(userName);

解説
userName は初期化後に変更されていないため、const で宣言するのが適切です。これにより、この変数が以降のコードで変更されないことが保証され、コードの意図が明確になります。

再代入される場合はletでOK

この例では、letで宣言された変数が実際に再代入されるため、prefer-constルールは警告を出しません。

問題ないコード

// Example 2: Variable is re-assigned (prefer-const does not warn)
let counter = 0;
console.log("Initial counter:", counter);

counter = 1; // counter は再代入されている
console.log("Updated counter:", counter);

if (true) {
  counter++; // さらに再代入
}
console.log("Final counter:", counter);

ESLintの出力
何も警告やエラーは出ません。

解説
counter変数は 0 で初期化された後、1 に再代入され、さらにインクリメントされています。このように再代入が伴う場合は、let を使用するのが正解であり、prefer-const ルールはこれを正しく判断します。

デストラクチャリングの例

オブジェクトや配列のデストラクチャリングを使用した場合のprefer-constの挙動です。

問題のあるコード

// Example 3: Destructuring (some variables are not reassigned)
const person = { name: "Bob", age: 25, city: "New York" };
let { name, age, city } = person; // name と city は再代入されない場合

console.log(`${name} is ${age} years old and lives in ${city}.`);

age = 26; // age は再代入される
console.log(`Updated age: ${age}`);

ESLintの警告/エラー

/path/to/your/file.js
  3:9  error  'name' is never reassigned. Use 'const' instead.  prefer-const
  3:24 error  'city' is never reassigned. Use 'const' instead.  prefer-const

namecityletで宣言されていますが、再代入されていないため警告が出ます。ageは再代入されているため警告は出ません。

修正後のコード

// Example 3: Fixed Destructuring
const person = { name: "Bob", age: 25, city: "New York" };

// 再代入される age は let に、されない name と city は const に分ける
const { name, city } = person;
let { age } = person;

console.log(`${name} is ${age} years old and lives in ${city}.`);

age = 26;
console.log(`Updated age: ${age}`);

解説
デストラクチャリングの場合でも、個々の変数の再代入の有無に応じてconstletを使い分けるべきです。これにより、コードの意図がより明確になります。

応用: destructuring オプション prefer-const ルールには destructuring オプションがあり、デフォルトは "any" です。これは、デストラクチャリングされた変数の一部でもconstにできるものがあれば警告するという意味です。 これを "all" に設定すると、デストラクチャリングされたすべての変数がconstにできる場合にのみ警告します。

例えば、以下のように設定した場合:

{
  "rules": {
    "prefer-const": ["error", { "destructuring": "all" }]
  }
}

そして、先ほどの問題のあるコードを実行すると、ageが再代入されるため、namecityに対しても警告は出なくなります(すべての変数がconstにできないため)。このオプションは、一括でletにするかconstにするかを選びたい場合に便利ですが、個々の変数の不変性を強調したい場合はデフォルトの"any"の方が適しています。

ループ内の変数宣言

ループ内で変数を宣言し、ループの各イテレーションで新しい値が割り当てられる場合でも、prefer-constは正しく動作します。

問題のあるコード

// Example 4: Loop variable (sometimes can be const)
const numbers = [1, 2, 3];
for (let i = 0; i < numbers.length; i++) {
  // i は各イテレーションで再代入されるので let で正しい
  let num = numbers[i]; // num はこのスコープ内で再代入されていない
  console.log(num);
}

ESLintの警告/エラー

/path/to/your/file.js
  4:7  error  'num' is never reassigned. Use 'const' instead.  prefer-const

i はループ変数なので let で適切ですが、num はループの各イテレーションで新しい値が代入されるだけで、そのイテレーション内では再代入されていないため、警告が出ます。

修正後のコード

// Example 4: Fixed Loop variable
const numbers = [1, 2, 3];
for (let i = 0; i < numbers.length; i++) {
  const num = numbers[i]; // num を const に変更
  console.log(num);
}
// または for...of ループを使用する
for (const num of numbers) { // for...of の場合は自然と const になることが多い
  console.log(num);
}

解説
ループ内で宣言された変数が、そのループの1回の実行中に再代入されないのであれば、const を使用すべきです。for...of ループは、このようなケースでconstを自然に適用できるため、推奨されることが多いです。

ignoreReadBeforeAssign オプション

このオプションは、変数が宣言された後、初期値が代入される前に参照されている場合に、prefer-constの警告を無視するものです。これは、複雑な初期化ロジックや特定のデザインパターンで役立ちます。

問題のあるコード(ignoreReadBeforeAssign が false またはデフォルトの場合)

// Example 5: Initializing a variable in multiple steps
let myValue; // ESLintは 'myValue' が再代入されていないと判断する
if (someCondition) {
  myValue = "Condition met";
} else {
  myValue = "Condition not met";
}
console.log(myValue);

ESLintの警告/エラー

/path/to/your/file.js
  2:5  error  'myValue' is never reassigned. Use 'const' instead.  prefer-const

myValue は宣言後に値を割り当てられていますが、ESLintは静的解析で「宣言後の再代入」を検知できず、const にできると判断します。

設定変更

{
  "rules": {
    "prefer-const": ["error", { "ignoreReadBeforeAssign": true }]
  }
}

修正後のコード(設定変更後、このコードは警告を出しません)

// Example 5: Initializing a variable in multiple steps (with ignoreReadBeforeAssign: true)
let myValue; // ignoreReadBeforeAssign: true の場合、警告が出ない
if (someCondition) {
  myValue = "Condition met";
} else {
  myValue = "Condition not met";
}
console.log(myValue);

解説
ignoreReadBeforeAssign: true を設定することで、変数が宣言された直後ではなく、その後の処理で初めて値が割り当てられるパターンを許容します。ただし、このようなケースでも可能であれば三項演算子や即時実行関数などを使ってconstで初期化することを検討すべきです。

// より良い書き方 (const を使用)
const myValue = someCondition ? "Condition met" : "Condition not met";
console.log(myValue);


はい、ESLintのprefer-constルールに関連する「代替手段」というよりは、prefer-constルールがあることによって推奨されるプログラミングの代替手段(書き換え方)、またはprefer-constルールを適用できない・適用したくない場合の代替策としてご説明します。

prefer-const ルールは、「再代入されない変数はconstで宣言すべき」という原則を強制します。この原則に沿うために、コードをよりconstを使いやすく書き換える「代替手段」と、どうしてもletを使いたい場合の「代替策」があります。

let を const に書き換えるための代替手段

これがprefer-constルールの最も直接的な目的であり、推奨される「代替手段」です。

a. 変数を宣言時に初期化する

多くの場合は、変数の宣言と初期化を同時に行うことで、letではなくconstを使用できます。

prefer-constの警告が出る例

// let で宣言したが、後で値を代入し、それ以降変更されない
let message;
if (condition) {
  message = "Hello";
} else {
  message = "Goodbye";
}
console.log(message);

代替手段(constを使用する書き換え)
三項演算子や即時実行関数(IIFE: Immediately Invoked Function Expression)を使って、宣言と同時に初期化します。

// 三項演算子で書き換え
const message = condition ? "Hello" : "Goodbye";
console.log(message);

// IIFE で書き換え
const anotherMessage = (() => {
  if (someOtherCondition) {
    return "Alpha";
  } else {
    return "Beta";
  }
})();
console.log(anotherMessage);

メリット

  • prefer-constルールに準拠します。
  • コードの可読性が向上し、意図しない再代入によるバグを防ぎます。
  • 変数の値が一度設定されたら変更されないことが明確になります。
b. デストラクチャリングの分割

オブジェクトや配列のデストラクチャリングで、一部の変数だけが再代入される場合、それらを分割してconstletを使い分けます。

prefer-constの警告が出る例

let { id, name, status } = someData; // id と name は再代入されない
status = "active"; // status だけ再代入される
console.log(id, name, status);

代替手段(constとletを使い分ける)

const { id, name } = someData; // 再代入されないものは const
let { status } = someData;   // 再代入されるものだけ let

status = "active";
console.log(id, name, status);

メリット

  • prefer-constルールに準拠します。
  • 変数の不変性が明確になり、それぞれの変数の役割がはっきりします。
c. for...of ループの使用

forループでインデックス変数と要素変数をletで宣言し、要素変数がループ内で再代入されない場合、for...ofループに書き換えることでconstを自然に適用できます。

prefer-constの警告が出る例

const numbers = [1, 2, 3];
for (let i = 0; i < numbers.length; i++) {
  let num = numbers[i]; // num はこのスコープ内で再代入されない
  console.log(num);
}

代替手段(for...ofループに書き換え)

const numbers = [1, 2, 3];
for (const num of numbers) { // num は各イテレーションで新しい定数として扱われる
  console.log(num);
}

メリット

  • prefer-constルールに準拠します。
  • 各イテレーションでnumconstとして扱われるため、意図しない変更を防ぎます。
  • より簡潔で可読性の高いコードになります。

prefer-constルールを適用できない・適用したくない場合の代替策

prefer-constルールは非常に強力ですが、すべてのケースで完璧にフィットするわけではありません。以下は、やむを得ずletを使用する場合の代替策です。

a. ESLintのコメントでルールを無効化する

一時的、または特定の理由でprefer-constルールを適用したくない場合に、コード内にESLintのコメントを追加してルールを無効化できます。

使用例

let value; // 初期化が複雑で、宣言時に値を決定できない場合など
// ... 複雑なロジック
if (condition1) {
  value = "Option A";
} else if (condition2) {
  value = "Option B";
} else {
  // eslint-disable-next-line prefer-const
  value = "Default Option"; // ここで prefer-const の警告を無視
}
console.log(value);

メリット

  • コードベース全体の設定を変更せずに対応できます。
  • 特定の行やブロックでルールを一時的に無効にできます。

デメリット

  • コメントによる無効化は、なぜletであるべきなのかの理由を明確にするコメントと一緒に使うべきです。
  • 乱用すると、Linterの本来の目的(コード品質の維持)を損なう可能性があります。
b. prefer-constルールのオプションを調整する

ESLintの設定ファイルで、prefer-constルールの振る舞いを調整するオプションがあります。

  • ignoreReadBeforeAssign オプション

    • true: 変数が宣言された後、初期値が代入される前に参照されている場合、prefer-constの警告を無視します。
    • 設定例
      {
        "rules": {
          "prefer-const": ["error", { "ignoreReadBeforeAssign": true }]
        }
      }
      
    • 用途
      宣言と最初の代入の間に複雑なロジックを挟む必要がある場合など、constに書き換えにくいパターンで警告を抑制したい場合に利用します。
    • "any" (デフォルト): デストラクチャリングされた変数の一部でもconstにできるものがあれば警告。
    • "all": デストラクチャリングされたすべての変数がconstにできる場合にのみ警告。
    • 設定例
      {
        "rules": {
          "prefer-const": ["error", { "destructuring": "all" }]
        }
      }
      
    • 用途
      デストラクチャリングで一部だけletにする場合でも、残りのconstにできる変数に対して警告を出したくない場合に利用します。

メリット

  • プロジェクト全体でルールの挙動を微調整できます。

デメリット

  • ルールの意図を完全に無効化する可能性があるので、慎重に検討する必要があります。
  • プロジェクトのすべての開発者に影響を与えます。
c. ルールの重要度を下げる、または無効化する(非推奨)

最終手段として、prefer-constルールの重要度を"warn"に下げるか、"off"にして完全に無効化する方法もあります。

設定例

{
  "rules": {
    "prefer-const": "warn" // エラーではなく警告にする
    // "prefer-const": "off" // ルールを完全に無効にする
  }
}

メリット

  • 一時的に問題を回避できます。

デメリット

  • 強く非推奨です。 コード品質を低下させ、将来的なバグの原因となる可能性があります。特別な理由がない限り、この方法は避けるべきです。