コピペで使える JavaScript / TypeScript 実践 Tips 集【中級者向け】

知っているようで知らない JavaScript / TypeScript の便利テクニックを、コピペして即使えるコード付きでまとめました。「もっと早く知りたかった」と思えるものを中心に厳選しています。

配列操作をスマートに書く

重複削除は Set で一発

const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)];
// [1, 2, 3]

オブジェクトの配列で特定キーを軸に重複除去したい場合は Map を使います。

type User = { id: number; name: string };
const users: User[] = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 1, name: "Alice (dup)" },
];

const uniqueById = [...new Map(users.map((u) => [u.id, u])).values()];
// id: 1 は最後のエントリで上書きされる

groupBy でグルーピング(Object.groupBy)

ES2024 で標準化された Object.groupBy が便利です。polyfill なしで使えるランタイムが増えています。

const items = [
  { category: "fruit", name: "apple" },
  { category: "veggie", name: "carrot" },
  { category: "fruit", name: "banana" },
];

const grouped = Object.groupBy(items, (item) => item.category);
// { fruit: [...], veggie: [...] }

flatMap で map + flatten を一回で

const sentences = ["hello world", "foo bar baz"];
const words = sentences.flatMap((s) => s.split(" "));
// ["hello", "world", "foo", "bar", "baz"]

空要素を除外するフィルタリングにも使えます。

const maybeNumbers = ["1", "two", "3", "four"];
const numbers = maybeNumbers.flatMap((s) => {
  const n = Number(s);
  return Number.isNaN(n) ? [] : [n];
});
// [1, 3]

TypeScript の型テクニック

satisfies 演算子で型推論を維持したまま型チェック

const config = {
  port: 3000,
  host: "localhost",
  debug: true,
} satisfies Record<string, string | number | boolean>;

// config.port の型は number(string | number | boolean ではない)
const doubled = config.port * 2; // OK

as キャストと違い、型チェックが効いたまま元の型推論が残ります。

Awaited で Promise の中身の型を取り出す

async function fetchUser() {
  return { id: 1, name: "Alice" };
}

type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string }

外部ライブラリの非同期関数の戻り値型を使い回すときに重宝します。

discriminated union でフェイルセーフな状態管理

type AsyncState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };

function render(state: AsyncState<string>) {
  switch (state.status) {
    case "success":
      return state.data; // data にアクセスできる
    case "error":
      return state.error.message; // error にアクセスできる
    default:
      return null;
  }
}

infer で型の一部を抽出する

type UnwrapArray<T> = T extends Array<infer Item> ? Item : T;

type A = UnwrapArray<string[]>; // string
type B = UnwrapArray<number>;   // number(配列でなければそのまま)

非同期処理のパターン

Promise.allSettled で全件処理 + エラーを握りつぶさない

const results = await Promise.allSettled([
  fetch("/api/users"),
  fetch("/api/posts"),
  fetch("/api/comments"),
]);

for (const result of results) {
  if (result.status === "fulfilled") {
    console.log("OK", result.value);
  } else {
    console.error("NG", result.reason);
  }
}

Promise.all と違い、1 件失敗しても残りの結果を捨てません。

AbortController でリクエストをキャンセル

function fetchWithTimeout(url: string, ms: number) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);

  return fetch(url, { signal: controller.signal }).finally(() =>
    clearTimeout(timer)
  );
}

React の useEffect クリーンアップや、古いリクエストのキャンセルにも同じパターンが使えます。

async/await のエラーを Result 型でラップする

毎回 try/catch を書くのが辛い場合に便利なユーティリティです。

type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function tryCatch<T>(
  promise: Promise<T>
): Promise<Result<T>> {
  try {
    return { ok: true, value: await promise };
  } catch (e) {
    return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
  }
}

// 使い方
const result = await tryCatch(fetch("/api/data"));
if (!result.ok) {
  console.error(result.error.message);
  return;
}
const data = result.value; // Response

オブジェクト操作

Object.fromEntries でオブジェクトを変換する

Object.entries と組み合わせると、オブジェクトを変換する処理が簡潔に書けます。

const prices = { apple: 100, banana: 200, cherry: 300 };

// 全値を2倍にする
const doubled = Object.fromEntries(
  Object.entries(prices).map(([k, v]) => [k, v * 2])
);
// { apple: 200, banana: 400, cherry: 600 }

// 特定キーだけ除外する
const { apple: _, ...withoutApple } = prices;
// { banana: 200, cherry: 300 }

構造化代入のデフォルト値と別名

function setup({ host = "localhost", port = 3000, debug: isDebug = false } = {}) {
  console.log(host, port, isDebug);
}

setup({ port: 8080 });
// "localhost" 8080 false

小さいけど効く小技

?? と ?. を組み合わせてネストを安全に掘る

const user = null;
const city = user?.address?.city ?? "不明";
// "不明"

ラベル付き break でネストしたループを抜ける

outer: for (const row of matrix) {
  for (const cell of row) {
    if (cell === target) {
      console.log("見つかった");
      break outer;
    }
  }
}

structuredClone でディープコピー

const original = { a: { b: { c: 42 } } };
const clone = structuredClone(original);

clone.a.b.c = 99;
console.log(original.a.b.c); // 42(影響なし)

Date・Map・Set・ArrayBuffer なども正しくコピーされます(関数・DOM ノードは不可)。

crypto.randomUUID で UUID 生成

const id = crypto.randomUUID();
// "550e8400-e29b-41d4-a716-446655440000" のような文字列

ブラウザ・Node.js 18 以降・Deno で動作します。ライブラリ不要です。

まとめ

今回紹介した Tips をカテゴリ別に振り返ります。

カテゴリTips
配列操作Set で重複除去、Map でオブジェクト重複除去、flatMap で map+filter
型テクニックsatisfiesAwaited、discriminated union、infer
非同期allSettledAbortController、Result 型ラッパー
オブジェクトObject.fromEntries、構造化代入のデフォルト値
小技?? / ?.、ラベル付き break、structuredClonecrypto.randomUUID

「知ってた!」という Tips も、実際にコードで見ると使い方のイメージが広がるはずです。ぜひ手元のコードに取り入れてみてください。