なぜ「デフォルト値補完」が業務でめちゃくちゃ大事なのか
業務コードでは、「外から来る値」がとにかく信用できません。
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 などを安心して使える
JavaScriptAPI パラメータのデフォルト補完
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 をどう扱うか」「ネストをどうマージするか」が、かなりクリアに見えてきます。
