カルキチブログ

JavaScriptのletを使うときに意識していること

コードをレビューをするときにletのスコープが分かりづらいコードに対して、letのスコープが分かりやすいような処理に書き換えて欲しいと言った旨の指摘をさせていただいたのですが、letのスコープが分かりづらいコードって何なのか?逆にletのスコープが分かりやすいコードって何なのかを言語化したくなったので、筆者がletを使う時に意識していることを紹介いたします。

良くないletの使い方

では早速筆者が思う良くないletは以下のような使い方です。
良くない使い方の例のサンプルコードはReactのカスタムフックを題材にしています。

フロントエンドやReact詳しくない人はイメージつきにくいかもしれませんが、以下のような処理を想定しています。

  • APIから取得したデータを元にプルダウンを表示したい。
  • 同じAPIカラ取得したデータを参照するが、プルダウンを表示する画面によって表示する項目、表示しない項目を出し分けたい。
import { useState }from 'react';

const useHogeFunc = () => {
  let hoge = [{label: '選択してください', value: '' }];

  // - 3〜40行くらいの処理が続く
  // - 3〜40行くらいの処理の中で、条件によってhogeの値が変化する
   
  // 条件によって変化するhogeの値がstateに入る
   const [hogeOptions, setHogeOptions] = useState(hoge);

   return {
     hogeOptions,
     setHogeOptions
   }
}

詳細な処理は端折りましたが、サンプルコードとして紹介したhogeFuncには以下のような問題点があります。

  • 関数の内部でhogeの値がどのように変化しているのか分かりにくい。
  • hogeを関数内の処理で参照している場合、関数内のどの部分で値が変化するのか追うのが大変。

上記のような問題が発生していると、コードを読む時にletの値がどのように変化するのか常に目を光らせながら読む必要がありますし、値がどこで変化するのかを把握するのも大変です。

改修や新たな機能の追加を行うときに、予期せずに値の上書きを行なってしまえば、バグの発生原因にもなるでしょう。

スコープが分かりやすいletの使い方

では、スコープが分かりやすいletの使い方とはどのような使い方でしょうか?
筆者がletを使うときは、目視で理解できるスコープを持った関数の中でletを定義して使うように意識しています。

const useHogeFunc = () => {
   const hogeChild = () => {
     let hoge = [{label: '選択してください', value: '' }];
     // 処理
     return hoge;
   }

   // 条件によって変化するhogeの値がstateに入る
   const [hogeOptions, setHogeOptions] = useState(hoge());

   return {
     hogeOptions,
     setHogeOptions
   }
}

目視で理解できるスコープを持った関数の定義が曖昧な表現にはなってしまっていますが、個人的には15〜20行くらいが目安かなとは考えています。

超えてしまいそうな場合は、そもそも関数の責務が大きすぎるから2つの関数に分割できないかを考えるか、それが難しい場合は値の上書きを実行している場所でどのような条件の時にどのように値が変化するのかコメントを残すようにはしています。

const hogeChild = (type: 'a' | 'b') => {
  let hoge = [{label: '選択してください', value: '' }];

  // コメント
  if (type === 'a') {

  // コメント
  } else if (type === 'b') {

  } else {

  }

  return hoge;
}

まとめ

  • letを使うときは、目視で理解できるスコープを持った関数の中でletを定義して使うようにする。
  • letを定義した関数のスコープは15〜20行くらいに絞る

当たり前のことをと思う方もいらっしゃるかもしれませんが、JavaScriptのletは再宣言はできませんが、再代入はできます。
何が言いたいかというと、letを使うという事は値の上書きが発生しますよという意思表示であると考えています。

値の上書きが発生しないのであれば、letではなくconstを使うべきなので、基本はconstを使うようにして、処理によって、どうしても値の上書きをした方が都合がいい時にletを使うといいのかなと思っています。

おまけ

ついでなので、let以外にconstやvarに関しても筆者の意見をまとめておきます。

  • まずはconstでなんとかできないかを考える。
  • 再代入に加えて、再宣言も可能なvarはそもそも使うべきではない。

基本はconstでなんとかできないかを考えて、varに関しては、ESLintのno-varprefer-const等のルールを適用して、使えないようにするべきであると考えています。

実務のプロジェクトでは、ESLintに加えて、huskylint-stagedを組み合わせて、コミットするときにリントのチェックを実行して、リントのルールに則っていないコードはコミットできないようにしています。