Skip to content
← Back to rules

oxc/no-map-spread 성능

An auto-fix is available for this rule.

작동 방식

Array.prototype.mapArray.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을 사용하는 것이 더 좋습니다.

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

기본값: true

함수 매개변수로 전달된 배열에 대한 맵을 무시합니다.

이 옵션은 잘못된 경고를 피하기 위해 기본적으로 활성화되어 있습니다. 하지만 비효율적인 전개를 놓칠 수 있다는 단점이 있습니다. .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

기본값: true

map 호출 후 다시 읽혀지는 맵된 배열을 무시합니다.

재사용되는 배열은 변형을 피하기 위해 얕은 복사 동작에 의존할 수 있습니다. 이러한 경우 Object.assign은 전개보다 실제로 더 효율적이지 않습니다.

사용 방법

이 규칙을 구성 파일이나 명령줄에서 활성화하려면 다음을 사용할 수 있습니다:

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

참고 자료