JavaScript(TypeScript)で役立つコーディングTips集

2023/04/12に公開

普段フロントエンドエンジニアとしてコーディングをしている中で、溜まった JavaScript および TypeScript におけるコーディングの Tips を残したいと思います。
対象としては、ある程度 JavaScript の知識がある人向けとなっています。

分割代入

React を主に使用している方は、例えば以下のように props を渡すときに分割代入を使用しているかと思います。
分割代入をすることで、展開するときにいちいちprops.をつけなくてよくなります。

type taskProps = {
  id: string;
  name: string;
  description: string;
};

export const Task: FC<taskProps> = ({ id, name, description }) => {
  return (
    <>
      {id}:{name}:{description}
    </>
  );
};

これを配列のメソッドを使うときにも使ってみることをおすすめします。
例えば、以下のようにfilterメソッドを使う場合、普通ならitem.をつけています。

const data = [
  { name: 'Tom', height: 180 },
  { name: 'Lisa', height: 168 },
  { name: 'jack', height: 180 },
];
const filteredData = data.filter(
  (item) => item.name === 'Tom' && item.height === 180
);

これを分割代入で記述すると以下のようになります。

const data = [
  { name: 'Tom', height: 180 },
  { name: 'Lisa', height: 168 },
  { name: 'jack', height: 180 },
];
const filteredData = data.filter(
  ({ name, height }) => name === 'Tom' && height === 180
);

props の時と同様に、いちいちitem.をつける必要がなくなりましたね。
この例だと、記述量はあまり変わりませんが、オブジェクトの中身が多いときなんかは少し記述が楽になります。
また、TypeScript で分割代入を使用していないときは、itemの型を設定するよう言われるかもしれませんが、分割代入だと型の指摘がありません。
逆に言えば、明確に型を指定したいときは分割代入を使わず、型が明確でわざわざ指定する必要がないときは分割代入を使ってみてもいいかなと思います。

if 文を使うときの変数管理

if 文の条件によって変数の値を変える場合、以下のようにletで変数を宣言することがあると思います。

let gender;
if (isWoman) {
  gender = 1;
} else {
  gender = 2;
}

ただ、letだとこちらの記事にあるように、再代入が何度も起こると何の値が入っているかわかりづらくなるなどのデメリットがあります。
そこで、letを使わずにconstを用いる方法も知っておくと便利でしょう。

三項演算子

if 文の分岐が複雑でない場合は三項演算子を用いると、constでより簡単に記述できます。

const gender = isWoman ? 1 : 2;

即時関数

if 文の条件や if 文の中の処理が多い場合は、即時関数で記述することも検討してみてください。
ただ、見慣れない人にとってはわかりづらい記述になるかもしれないので、必ずしも使うことが良いとは限りません。

const content = (() => {
  if (num === 1 && description === 'パターン1') {
    return <p>パターン1</p>;
  } else if (num === 2 && description === 'パターン2') {
    return <p>パターン2</p>;
  } else {
    return <p>その他</p>;
  }
})();

早期リターン(ガード節)

if 文のネストが深くなるときは早期リターン(ガード節)を用いることをおすすめします。
例えば、以下の例のように、ネストが深くなると可動性が低下します。

if (user) {
  if (user.isHuman) {
    if (user.name === 'Mike') {
      return 'Mike';
    } else {
      return 'Other';
    }
  }
}

これを早期リターンを用いて記述すると以下のようになります。

if (!user) return;
if (!user.isHuman) return;
if (user.name === 'Mike') return 'Mike';
return 'Other';

このように記述することで、可読性の向上、処理の軽量化、行数の削減など、さまざまなメリットがあります。
こちらの記事がより深く書かれているので、非常に参考になります。

Null 合体演算子

三項演算子の派生系みたいなもので、左辺がnullまたはundefinedのときに右の値を返し、それ以外の場合には左の値を返します。
例えば、API から返ってくるデータがnullまたはundefinedのときに空配列を返したい場合、三項演算子で書くと以下のようになります。

//dataはAPIから返ってくるデータ
const response = data ? data : [];

上記を Null 合体演算子で記述すると、以下のようになります。

const response = data ?? [];

こちらの方がよりシンプルに書けますね。
一方、Null 合体演算子と似たものに OR 演算子(||)もあります。
OR 演算子の特徴としては、左辺が falsy のときに右の値を返します。
つまり、左辺がnullundefinedのときだけでなく、0 や空文字のときも右の値を返すということです。
以下が Null 合体演算子と OR 演算子の具体的な比較です。

const num = 0;
console.log(num ?? 'No'); // 0
console.log(num || 'No'); // 'No'

Null 合体演算子の方がより厳密な判定になるので、個人的には Null 合体演算子を使うことが多いです。
MDNこちらの記事も参考になります。

スプレッド構文

配列やオブジェクトを展開したいときに使える構文です。
もとの配列やオブジェクトをコピーするので、イミュータブル(非破壊的)に操作をすることができます。
例えば、新しい要素を追加した配列を作りたい場合、以下のようになります。

const countryList = ['America', 'China', 'Korea'];
const newCountryList = [...countryList, 'Japan'];
console.log(newCountryList); // ['America', 'China', 'Korea', 'Japan']

オブジェクトの場合も同様です。

const profile = { name: 'Mike', age: 19, height: 178, weight: 60 };
const newProfile = { ...profile, gender: 'male' };
console.log(newProfile); // { name: 'Mike', age: 19, height: 178, weight: 60, gender: 'male' }

また、結合した配列も作成することができます。

const countryList1 = ['America', 'China', 'Korea'];
const countryList2 = ['Japan', 'France'];
const newCountryList = [...countryList1, ...countryList2];
console.log(newCountryList); // ['America', 'China', 'Korea', 'Japan', 'France']

オブジェクトの場合も同様です。

const profile1 = { name: 'Mike', age: 19, height: 178, weight: 60 };
const profile2 = { gender: 'male', country: 'America' };
const newProfile = { ...profile1, ...profile2 };
console.log(newProfile); // { name: 'Mike', age: 19, height: 178, weight: 60, gender: 'male', country: 'America' }

さらに、オブジェクトの場合は、要素を変更した新しいオブジェクトを作成することもできます。

const profile = { name: 'Mike', age: 19, height: 178, weight: 60 };
const newProfile = { ...profile, name: 'Jack' };
console.log(newProfile); // { name: 'Jack', age: 19, height: 178, weight: 60 }

スプレッド構文はかなり便利なので、JavaScript を使うならば絶対に知っておくべき知識ですね。

残余引数

残余引数という言葉はあまり聞き慣れないかもしれませんが、挙動はスプレッド構文とは逆で複数の要素を集約して 1 つのオブジェクトにします。
私が残余引数を使う場面は、オブジェクトから特定のプロパティをイミュータブル(非破壊的)に削除したいときです。
言い換えると、特定のプロパティを除いた新しいオブジェクトを作りたいときに、残余引数を用いると簡単に実現することができます。

例えば、以下のようなオブジェクトが存在するとします。

const profile = {
  name: 'Mike',
  age: 19,
  height: 178,
  weight: 60,
};

このオブジェクトから age を除いた新しいオブジェクトを作りたいとします。
パッと思いつくのは delete 演算子を用いることですが、ミュータブル(破壊的)な処理になってしまいます。
オブジェクトをコピーしてから delete 演算子を使ったり、配列に変換してフィルタリングしてから再度オブジェクトに戻すなどの方法もありますが、以下のように残余引数を用いるともっと楽になります。

const { age, ...newProfile } = profile;
console.log(newProfile); // { name: 'Mike', height; 178, weight: 60 }

これだと 1 行で済みます。
挙動としては age を除いたプロパティがnewProfileに集約されるイメージですね。
詳しい構文の説明はMDNこちらの記事を参考にしていただければと思います。

Set オブジェクト

Set オブジェクトは一意の値のコレクションで重複を許しません。
例えば、配列から重複した値を排除したい場合は、Set オブジェクトを使えば以下のように簡単に実現できます。

const array1 = ['apple', 'orange', 'apple', 'grape', 'banana'];
const array2 = [...new Set(array1)]; //スプレッド構文またはArray.from()でSet型から配列に変換可能
console.log(array2); // ['apple', 'orange', 'grape', 'banana']

特に、配列同士を比較する際に Set オブジェクトは効果を発揮します。
例えば、以下のような 2 つの配列があるとします。

const employees = ['Mike', 'John', 'Micheal', 'Anna'];
const departments = [
  {
    id: 1,
    name: '営業部',
    manager: 'Mike',
  },
  {
    id: 2,
    name: '開発部',
    manager: 'Jack',
  },
  {
    id: 3,
    name: '経理部',
    manager: 'Anna',
  },
];

上記配列のdepartmentsの中から、managerの値がemployeesの要素と一致するものだけを抽出しようとしたとき、配列のメソッドを用いると以下のような実装になるかと思います。

const filteredDepartments = departments.filter(({ manager }) =>
  employees.includes(manager)
);
console.log(filteredDepartments); // [{ id: 1, name: '営業部', manager: 'Mike' }, { id: 3, name: '経理部', manager: 'Anna' }]

しかし、上記だとパフォーマンスの面で良いコードとは言えません。
例えば、departmentsの要素が 1000 個、employeesの要素が 1000 個あったとき、計算量は最大で 1000×1000=100 万回になります。
departmentsの計算量を O(M)、employeesの計算量を O(N)とすると、O(M * N)になる)
データ量が増えれば増えるほど二次関数的に計算量が増えて処理が重たくなります。

そこで、Set オブジェクトの登場です。
Set オブジェクトを用いて上記と同じ処理を書くと以下のようになります。

const employeesSet = new Set(employees);
const filteredDepartments = departments.filter(({ manager }) =>
  employeesSet.has(manager)
);
console.log(filteredDepartments); // [{ id: 1, name: '営業部', manager: 'Mike' }, { id: 3, name: '経理部', manager: 'Anna' }]

このように実装すると、計算量は O(M)となり、departmentsの要素が 1000 個、employeesの要素が 1000 個あったときでも計算量は最大 1000 回となります。
ちなみに、hasはSetオブジェクトのインスタンスメソッドであり、他にもSetオブジェクト特有のメソッドがあるので、詳しくはMDNを参考にしていただければと思います。
また、計算量についてはこちらの記事も参考になるかと思います。

Discussion