JavaScript | ES6+ 文法:オブジェクト拡張 – オブジェクト操作設計

JavaScript JavaScript
スポンサーリンク

オブジェクト操作設計とは何か

「オブジェクト操作設計」というのは、
単に user.name = "Alice" といった「その場しのぎの代入」を書くのではなく、

  • どんな形のオブジェクトにするか(構造・型)
  • どこで作って、どこで更新して、どこで読むのか(責務の分け方)
  • どうやって「元を壊さず」に扱うか(不変データ)

をあらかじめ決めておくことです。

ES6+ のオブジェクト拡張(スプレッド構文、Object.assignObject.entries / Object.fromEntries、プロパティ短縮記法、計算されたプロパティ名、メソッド定義省略など)は、この「設計」をきれいに実現するための武器です。

ここが重要です。
文法そのものを知るだけでは不十分で、「どう組み合わせて、安全で読みやすいオブジェクト操作にするか」を意識すると、コードの質が一気に変わります。


データ構造を先に決める(設計のスタート地点)

形を決めるだけで、迷いがかなり減る

まず、「このオブジェクトはどういう形をしているべきか」をハッキリさせます。

例えばユーザー情報なら:

// ユーザー1件の形を決める
// id: number
// name: string
// roles: string[]
// meta: { createdAt: number, updatedAt: number }
const user = {
  id: 1,
  name: "Alice",
  roles: ["user"],
  meta: {
    createdAt: Date.now(),
    updatedAt: Date.now()
  }
};
JavaScript

「とりあえず突っ込む」ではなく、「このオブジェクトのプロパティはこれとこれ」と決めてからコードを書くと、そのあとスプレッドや Object.assign を使った更新パターンも迷わず書けます。

重要なのは、ネストを含めた「一貫した形」を決めておくことです。
同じ user なのに、ファイルによって形が違う、というのが一番の地獄です。


変更の方針を決める(不変データをベースにする)

原則:「元のオブジェクトは変更せず、新しいオブジェクトを作る」

ES6+ 的な設計では、「不変データ」をベースに考えるのがおすすめです。

例えば設定オブジェクト:

const config = {
  theme: "light",
  lang: "ja",
  debug: false
};
JavaScript

「debug を true にしたい」ときに、次の2つの書き方があります。

元を書き換える(可変):

config.debug = true; // 元を直接変更
JavaScript

新しいオブジェクトを作る(不変):

const newConfig = { ...config, debug: true };
JavaScript

ここが重要です。
設計としては、「基本は後者(不変)」を採用するかどうかをチームで決める

不変スタイルのメリットは:

  • 「いつの間にかどこかで書き換わっている」バグが減る
  • 関数が「入力 → 出力」の形になりやすい(再利用しやすい)
  • React などのライブラリと相性が良い

初心者のうちは、「外部から渡されたオブジェクトや状態は、原則として直接いじらず、新しいものを返す」と決めてしまうと、安全側に倒せます。


生成・更新・マージのパターンを揃える

生成:プロパティ短縮記法で意図を見せる

「変数からオブジェクトを作る」場面では、プロパティ短縮記法を使うと設計が見やすくなります。

const id = 1;
const name = "Alice";
const roles = ["user"];

const user = { id, name, roles };
JavaScript

{ id, name, roles } と書かれていれば、「このオブジェクトはこの3つを持つ」という意図が一目でわかります。
ここで「どんなプロパティを持つべきか」を揃えておくことが、設計の土台になります。

更新:スプレッド構文で「元+差分」の形にする

更新関数は、「元オブジェクトと変更点を受け取り、新しいオブジェクトを返す」形にすると、設計がきれいになります。

function updateUser(user, updates) {
  return { 
    ...user, 
    ...updates, 
    meta: {
      ...user.meta,
      updatedAt: Date.now()
    }
  };
}

const user1 = {
  id: 1,
  name: "Alice",
  roles: ["user"],
  meta: { createdAt: 0, updatedAt: 0 }
};

const user2 = updateUser(user1, { name: "Bob" });

console.log(user1.name); // "Alice"
console.log(user2.name); // "Bob"
JavaScript

ここが重要です。
更新の設計として、「元+差分 → 新」というパターンを統一すると、どこから見ても同じリズムでコードが読めます。

マージ:Object.assign / スプレッドの方向と優先度

設定やオプションのマージでは、「どちらを優先するか」を設計で決めておく必要があります。

const defaultOptions = {
  method: "GET",
  timeout: 3000,
  cache: "no-cache"
};

function createOptions(userOptions = {}) {
  return {
    ...defaultOptions,  // デフォルト
    ...userOptions      // ユーザーが上書き
  };
}
JavaScript

{ ...defaultOptions, ...userOptions } という順番にすることで、
「デフォルト → 上書き」という設計がそのままコードに出ます。

この「順番で優先度を表現する」ルールを、一貫して守ることが大事です。


読み出し・安全なアクセスをどう設計するか

分割代入で「必要なものだけ」を明示的に取り出す

関数にオブジェクトを渡すとき、その中から何が必要かを明確にしておくと設計がスッキリします。

function printUser({ id, name, roles }) {
  console.log(`(${id}) ${name} [${roles.join(", ")}]`);
}

const user = {
  id: 1,
  name: "Alice",
  roles: ["user", "admin"],
  meta: { createdAt: 0 }
};

printUser(user);
JavaScript

引数で分割代入をすることで、「この関数は user のうち id, name, roles にだけ関心がある」という設計が、コードに現れます。

ネストしたプロパティへのアクセスと不変更新

ネストが深くなったときの更新も、設計しておかないとすぐカオスになります。

例えば、状態オブジェクト:

const state = {
  user: {
    id: 1,
    name: "Alice"
  },
  ui: {
    theme: "light"
  }
};
JavaScript

user.name を変えたい場合の「不変更新パターン」はこうです。

const newState = {
  ...state,
  user: {
    ...state.user,
    name: "Bob"
  }
};
JavaScript

ここが重要です。
「外側をスプレッド → 変えたい内側もスプレッド+上書き」というパターンを、
「ネスト1段ごとに繰り返す」と決めておくと、ネストが深くなってもブレません。


Object.entries / fromEntries を使った「変換」の設計

オブジェクトを「配列として変換」→「またオブジェクトに戻す」

設計の中で、「オブジェクトの一部だけを加工したい」という場面も多く出てきます。
このとき Object.entriesObject.fromEntries を使うと、「オブジェクト操作」を「配列操作」として設計できます。

例えば、値を2倍する:

const prices = { apple: 100, orange: 80 };

const doubled = Object.fromEntries(
  Object.entries(prices).map(([key, value]) => [key, value * 2])
);

console.log(doubled); // { apple: 200, orange: 160 }
JavaScript

重要なのは、「設計としてこの流れを採用するか」です。

  1. Object.entries でオブジェクト → [key, value] 配列
  2. 配列として map / filter などで柔軟に加工
  3. Object.fromEntries でオブジェクトに戻す

「オブジェクトの変換処理はこの3ステップで書く」と決めておくと、
自分のコードも他人のコードも読み解きやすくなります。


小さな「操作関数」に分ける設計

「データの形」ごとに操作関数を用意する

例えばユーザーオブジェクトに対して、こんな操作があるとします。

  • 名前を変更する
  • ロールを追加する
  • アクティブフラグを変える(active: true/false など)

これを全部その場で直接いじるのではなく、
「ユーザー操作関数」として分けておくと設計が綺麗になります。

function renameUser(user, newName) {
  return { ...user, name: newName };
}

function addRole(user, role) {
  return { ...user, roles: [...user.roles, role] };
}

function setActive(user, active) {
  return { ...user, active };
}
JavaScript

こうしておけば、使う側は「設計どおりの操作」しかできません。

const user1 = { id: 1, name: "Alice", roles: ["user"], active: false };

const user2 = renameUser(user1, "Bob");
const user3 = addRole(user2, "admin");
const user4 = setActive(user3, true);
JavaScript

ここが重要です。
「オブジェクトの形」と「そのオブジェクトに許される操作」をセットで設計しておく
これができていると、オブジェクト操作が「バラバラの代入の寄せ集め」ではなく、「意味のあるAPI」になります。


具体例で総仕上げ:ユーザー管理オブジェクトの設計

データ形の定義

// ユーザー1件の形
// id: number
// name: string
// roles: string[]
// active: boolean
// meta: { createdAt: number, updatedAt: number }
function createUser(id, name) {
  const now = Date.now();
  return {
    id,
    name,
    roles: ["user"],
    active: true,
    meta: {
      createdAt: now,
      updatedAt: now
    }
  };
}
JavaScript

操作関数の定義(不変更新)

function renameUser(user, newName) {
  return {
    ...user,
    name: newName,
    meta: {
      ...user.meta,
      updatedAt: Date.now()
    }
  };
}

function addRole(user, role) {
  if (user.roles.includes(role)) return user;
  return {
    ...user,
    roles: [...user.roles, role],
    meta: {
      ...user.meta,
      updatedAt: Date.now()
    }
  };
}

function deactivateUser(user) {
  if (!user.active) return user;
  return {
    ...user,
    active: false,
    meta: {
      ...user.meta,
      updatedAt: Date.now()
    }
  };
}
JavaScript

利用側のコード(読みやすさの確認)

let user = createUser(1, "Alice");

user = renameUser(user, "Bob");
user = addRole(user, "admin");
user = deactivateUser(user);

console.log(user);
JavaScript

ここまでくると、「オブジェクト操作設計」が見えてきます。

  • 形は createUser で統一
  • 更新は「元+差分 → 新」で不変
  • 更新タイミングで meta.updatedAt を必ず更新(設計ルール)
  • 利用側は「意味のある操作」のみを呼ぶ

ES6+ のオブジェクト拡張(スプレッド構文・短縮記法など)が、この設計を支えていることがわかると思います。


まとめ

オブジェクト操作設計のカギは、文法そのものよりも、

  • オブジェクトの「形」を先に決める
  • 変更方針を「不変」をベースに揃える
  • 生成・更新・マージのパターンを統一する
  • entries / fromEntries で「変換処理」を設計する
  • 「形」と「許される操作」をセットで小さな関数として定義する

という「考え方」にあります。

ES6+ のオブジェクト拡張機能は、この考え方をコードに落とし込むための道具です。
スプレッド、プロパティ短縮、計算プロパティ名、Object.assignObject.entries / Object.fromEntries などを、「設計を支えるための文法」として意識すると、書くコードのレベルが一段上がります。

最初から完璧な設計を目指す必要はありません。
まずはひとつ、「このオブジェクトだけは不変スタイルで操作関数を用意してみる」ところから始めてみてください。そこから「設計する楽しさ」が見えてきます。

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