JavaScript Tips | 基本・共通ユーティリティ:安全処理 – デフォルト値補完

JavaScript JavaScript
スポンサーリンク

なぜ「デフォルト値補完」が業務でめちゃくちゃ大事なのか

業務コードでは、「外から来る値」がとにかく信用できません。
API レスポンス、フォーム入力、設定ファイル、環境変数、ローカルストレージなど、どれも「欠けている」「型が違う」「null が入っている」ことが普通にあります。

そこで毎回こう書いていると、コードがすぐにぐちゃぐちゃになります。

const pageSize = config.pageSize != null ? config.pageSize : 20;
const theme = config.theme ?? "light";
const lang = config.lang || "ja";
JavaScript

一つ二つならまだいいのですが、設定項目が 10 個、20 個と増えてくると、
「値がなければデフォルトを入れる」という同じパターンが延々と続きます。

ここで登場するのが「デフォルト値補完ユーティリティ」です。
ざっくり言うと、

「“足りないところをデフォルトで埋めた安全なオブジェクト”を作る小さな関数」

です。
これを一つ用意しておくと、「入力がどれだけ雑でも、内部では“ちゃんとした形”で扱える」という状態を作れます。


まずは一番シンプルな「値レベル」のデフォルト補完

null / undefined のときだけデフォルトを使う

単純に「値が null または undefined ならデフォルトを使いたい」という場面は多いです。
そのための小さなユーティリティを作ってみます。

function withDefault(value, defaultValue) {
  return value ?? defaultValue;
}
JavaScript

??(null 合体演算子)は、「左側が null か undefined のときだけ右側を使う」という演算子です。

使い方はとても素直です。

const pageSize = withDefault(config.pageSize, 20);
const theme = withDefault(config.theme, "light");
JavaScript

ここでのポイントは、「0 や空文字は“有効な値”として扱う」ということです。

withDefault(0, 10);        // 0
withDefault("", "default"); // ""
withDefault(null, "x");     // "x"
withDefault(undefined, "x");// "x"
JavaScript

業務では、「0 も空文字も意味がある値」として扱いたいことが多いので、
|| ではなく ?? ベースのユーティリティを持っておくと安全です。


オブジェクト全体に対する「デフォルト値補完」

「設定オブジェクトに足りない項目を埋める」ユーティリティ

本番でよく出てくるのは、「設定オブジェクトにデフォルトをマージする」というパターンです。

例えば、こんなデフォルト設定があるとします。

const defaultOptions = {
  pageSize: 20,
  theme: "light",
  lang: "ja",
};
JavaScript

ユーザーが一部だけ上書きしてくるケース。

const userOptions = {
  pageSize: 50,
};
JavaScript

これを「デフォルトを補完した完成形」にしたいわけです。

// 期待する結果
{
  pageSize: 50,   // ユーザー指定
  theme: "light", // デフォルト
  lang: "ja",     // デフォルト
}
JavaScript

これを行うユーティリティを作ってみます。

function withDefaultOptions(options, defaultOptions) {
  return {
    ...defaultOptions,
    ...(options ?? {}),
  };
}
JavaScript

使い方はこうです。

const finalOptions = withDefaultOptions(userOptions, defaultOptions);
console.log(finalOptions);
JavaScript

ここでの重要ポイントは二つです。

一つ目は、「スプレッド構文で“デフォルト → ユーザー指定”の順にマージしている」ことです。
後ろに書いた方が優先されるので、「ユーザー指定があればそれを使い、なければデフォルト」が自然に実現できます。

二つ目は、「options が null / undefined のときでも安全に動くようにしている」ことです。
options ?? {} としているので、「そもそも設定が渡ってこなかった」場合でも、デフォルトだけでオブジェクトが作られます。


ネストしたオブジェクトのデフォルト補完(浅い版)

設定が少し複雑になると、ネストしたオブジェクトが出てきます。

const defaultConfig = {
  ui: {
    theme: "light",
    density: "normal",
  },
  paging: {
    pageSize: 20,
  },
};
JavaScript

ユーザーがこう指定してきたとします。

const userConfig = {
  ui: {
    theme: "dark",
  },
};
JavaScript

理想の結果はこうです。

{
  ui: {
    theme: "dark",     // 上書き
    density: "normal", // デフォルト
  },
  paging: {
    pageSize: 20,      // デフォルト
  },
}
JavaScript

単純なスプレッドだけだと、ネストの中までは補完されません。

const merged = {
  ...defaultConfig,
  ...userConfig,
};

console.log(merged.ui); // { theme: "dark" } density が消える
JavaScript

そこで、「浅いネストだけ対応する簡易版」を作ってみます。

function mergeConfig(userConfig, defaultConfig) {
  const result = { ...defaultConfig };

  if (userConfig == null) {
    return result;
  }

  for (const key of Object.keys(userConfig)) {
    const userValue = userConfig[key];
    const defaultValue = defaultConfig[key];

    if (
      userValue &&
      typeof userValue === "object" &&
      !Array.isArray(userValue) &&
      defaultValue &&
      typeof defaultValue === "object" &&
      !Array.isArray(defaultValue)
    ) {
      result[key] = { ...defaultValue, ...userValue };
    } else {
      result[key] = userValue;
    }
  }

  return result;
}
JavaScript

使い方です。

const finalConfig = mergeConfig(userConfig, defaultConfig);
console.log(finalConfig);
JavaScript

ここでの深掘りポイントは、「オブジェクト同士なら一段だけ中身をマージする」というルールにしていることです。
完全な「深いマージ(deep merge)」はライブラリの世界ですが、
業務では「一段だけネストしている設定」が多いので、このくらいの簡易版でも十分役に立ちます。


「未定義だけデフォルトで埋める」か「null も埋める」か

デフォルト値補完で必ず悩むのが、

「null は“値がない”なのか、“あえて null にしている”のか」

という問題です。

例えば、こういうケース。

const defaultOptions = {
  title: "デフォルトタイトル",
};

const userOptions = {
  title: null,
};
JavaScript

ここで、「最終的な title をどうするか」はプロジェクトの方針次第です。

null も「値がない」とみなしてデフォルトで埋める
null は「ユーザーが意図的に空にした」とみなしてそのまま残す

これをコントロールするために、「undefined のときだけデフォルトを使う」版と、
「null / undefined のときにデフォルトを使う」版を分けておくと便利です。

function withDefaultIfUndefined(value, defaultValue) {
  return value === undefined ? defaultValue : value;
}

function withDefaultIfNullish(value, defaultValue) {
  return value ?? defaultValue;
}
JavaScript

どちらを使うかは、「null をどう扱うか」というチームのルールに合わせて決めるとよいです。
ここを曖昧にすると、「画面によって null の意味が違う」というカオスになります。


実務での具体的な利用イメージ

コンポーネントの props にデフォルトを補完する

UI コンポーネントでよくあるのが、「props の一部だけ渡される」ケースです。

const defaultProps = {
  size: "md",
  variant: "primary",
  disabled: false,
};

function normalizeProps(props) {
  return withDefaultOptions(props, defaultProps);
}
JavaScript

これを使えば、コンポーネント内部では「必ず size / variant / disabled が存在する」という前提で書けます。

const props = normalizeProps(userProps);
// ここから先は props.size などを安心して使える
JavaScript

API パラメータのデフォルト補完

API クライアント側で、「指定されなかったパラメータにデフォルトを入れる」パターンも定番です。

const defaultSearchParams = {
  page: 1,
  pageSize: 20,
  sort: "createdAt",
};

function buildSearchParams(params) {
  return withDefaultOptions(params, defaultSearchParams);
}
JavaScript

これで、「呼び出し側は必要なものだけ指定すればよい」「サーバーには常にフルセットのパラメータが飛ぶ」という状態を作れます。


小さな練習で感覚をつかむ

次のようなデフォルトと入力パターンを自分で用意して、
withDefault, withDefaultOptions, mergeConfig を実装して試してみてください。

const defaultConfig = {
  ui: { theme: "light", density: "normal" },
  paging: { pageSize: 20 },
  lang: "ja",
};

const patterns = [
  {},
  { ui: { theme: "dark" } },
  { paging: { pageSize: 50 } },
  { lang: "en" },
  null,
  undefined,
];
JavaScript

それぞれに対して、「最終的な設定オブジェクトがどうなるか」をコンソールに出してみると、
「どこまでをデフォルトで埋めるか」「null をどう扱うか」「ネストをどうマージするか」が、かなりクリアに見えてきます。

タイトルとURLをコピーしました