Skip to content
← Back to rules

oxc/no-map-spread Perf

🛠️💡 An auto-fix and a suggestion are available for this rule for some violations.

何を行うか

Array.prototype.map および Array.prototype.flatMap において、オブジェクトまたは配列のスプレッドを使用して配列要素にプロパティや要素を追加することを禁止します。

このルールは、スプレッド演算子がオブジェクトや配列をマージする場合にのみ報告対象とし、コピーする目的での使用には適用しません。

なぜ問題なのか

スプレッドは、配列内のオブジェクトにプロパティを追加したり、複数のオブジェクトを結合するためによく使われます。しかし、スプレッドは新しいオブジェクトの再割り当てと O(n) のメモリコピーを伴うため、非効率です。

ts
// scores の各オブジェクトは浅くコピーされます。また、scores は以降再利用されないため、スプレッドは非効率です。
function getDisplayData() {
  const scores: Array<{ username: string; score: number }> = getScores();
  const displayData = scores.map((score) => ({ ...score, rank: getRank(score) }));
  return displayData;
}

マッピングされた配列内のオブジェクトが後に変更されることを想定しない限り、[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) を使うほうが良いです。

ts
// score がインプレースで変更され、よりパフォーマンスが向上します。
function getDisplayData() {
  const scores: Array<{ username: string; score: number }> = getScores();
  const displayData = scores.map((score) => Object.assign(score, { rank: getRank(score) }));
  return displayData;
}

変更の防止

map 呼び出しでのオブジェクトのスプレッドには正当なユースケースがあります。特に、返される配列の消費者が元データに影響を与えることなく変更できるようにしたい場合です。このルールは、これらのケースについて報告を回避する努力を行います。

クラスインスタンスのプロパティに対するスプレッドは完全に無視されます:

ts
class AuthorsDb {
  #authors = [];
  public getAuthorsWithBooks() {
    return this.#authors.map((author) => ({
      // 変更を防止し、呼び出し側に独自の深め(若干)のコピーを提供します。
      ...author,
      books: getBooks(author),
    }));
  }
}

map 呼び出し後に再読み込みされる配列に対するスプレッドも、デフォルトでは無視されます。この動作は ignoreRereads オプションで設定できます。

/* "oxc/no-map-spread": ["error", { "ignoreRereads": true }] */
const scores = getScores();
const displayData = scores.map(score => ({ ...score, rank: getRank(score) }));
console.log(scores); // map 呼び出し後に scores が再読み込みされています

配列

配列スプレッドの場合、可能な限り Array.prototype.concat または Array.prototype.push を使用すべきです。スプレッドはイテラブルに対して動作するのに対し、concatpush は配列のみに動作するため、意味が多少異なります。

ts
let arr = [1, 2, 3];
let set = new Set([4]);

let a = [...arr, ...set]; // [1, 2, 3, 4]
let b = arr.concat(set); // [1, 2, 3, Set(1)]

// スプレッドよりもパフォーマンスが良く、同じ意味を持ちますが、冗長です。
let c = arr.concat(Array.from(set)); // [1, 2, 3, 4]

// また、`Symbol.isConcatSpreadable` を使用することもできます。
set[Symbol.isConcatSpreadable] = true;
let d = arr.concat(set); // [1, 2, 3, 4]

自動修正

このルールはオブジェクトスプレッドによる違反を自動的に修正できますが、配列については修正しません。配列の修正は将来追加される可能性があります。

単一の要素(スプレッドのみ)を持つオブジェクト式は修正されません。

js
arr.map((x) => ({ ...x })); // 修正されません

--fix オプションを使用することで、スプレッドの前に関数的要素がある場合の修正が可能です。Object.assign は最初の引数を変更するため、これらの要素で新しいオブジェクトが作成され、スプレッド識別子は変更されません。結果として、スプレッドの意味論が保持されます。

js
// 修正前
arr.map(({ x, y }) => ({ x, ...y }));

// 修正後
arr.map(({ x, y }) => Object.assign({ x }, y));

スプレッドがオブジェクトの最初のプロパティである場合、--fix-suggestions を使用して提案が提供されます。この修正はスプレッド識別子を変更するため、予期しない副作用を引き起こす可能性があります。

js
// 修正前
arr.map(({ x, y }) => ({ ...x, y }));
arr.map(({ x, y }) => ({ ...x, y }));

// 修正後
arr.map(({ x, y }) => Object.assign(x, { y }));
arr.map(({ x, y }) => Object.assign(x, y));

このルールにおける誤ったコードの例:

js
const arr = [{ a: 1 }, { a: 2 }, { a: 3 }];
const arr2 = arr.map((obj) => ({ ...obj, b: obj.a * 2 }));

このルールにおける正しいコードの例:

ts
const arr = [{ a: 1 }, { a: 2 }, { a: 3 }];
arr.map((obj) => Object.assign(obj, { b: obj.a * 2 }));

// インスタンスプロパティは無視されます
class UsersDb {
  #users = [];
  public get users() {
    // users をクローンし、呼び出し側に独自の深め(若干)のコピーを提供します。
    return this.#users.map((user) => ({ ...user }));
  }
}
tsx
function UsersTable({ users }) {
  const usersWithRoles = users.map((user) => ({ ...user, role: getRole(user) }));

  return (
    <table>
      {usersWithRoles.map((user) => (
        <tr>
          <td>{user.name}</td>
          <td>{user.role}</td>
        </tr>
      ))}
      <tfoot>
        <tr>
          {/* users の再読み込み */}
          <td>ユーザー数合計: {users.length}</td>
        </tr>
      </tfoot>
    </table>
  );
}

参照

設定

このルールは以下のプロパティを持つ設定オブジェクトを受け取ります:

ignoreArgs

type: boolean

default: true

関数の引数として渡された配列に対する map を無視します。

このオプションはデフォルトで有効になっており、誤検出を減らすために役立ちます。ただし、非効率なスプレッドを見逃す可能性があります。.oxlintrc.json ファイルでは、これを無効にすることを推奨します。

ignoreArgstrue 時にこのルールにおいて誤ったコードの例:

ts
/* "oxc/no-map-spread": ["error", { "ignoreArgs": true }] */
function foo(arr) {
  let arr2 = arr.filter((x) => x.a > 0);
  return arr2.map((x) => ({ ...x }));
}

ignoreArgstrue 時にこのルールにおいて正しいコードの例:

ts
/* "oxc/no-map-spread": ["error", { "ignoreArgs": true }] */
function foo(arr) {
  return arr.map((x) => ({ ...x }));
}

ignoreRereads

type: boolean

default: true

map 呼び出し後に再読み込みされるマップ済み配列を無視します。

再利用される配列は、変更を避けるために浅いコピーの挙動に依存することがあります。このような場合、Object.assign はスプレッドよりも実際にはパフォーマンスが向上しないのです。

使い方

このルールを有効化するには、設定ファイルまたは CLI を使用できます:

json
{
  "rules": {
    "oxc/no-map-spread": "error"
  }
}
bash
oxlint --deny oxc/no-map-spread

参照