Копирование массива с добавлением новой проперти

Делаю копию массива объектов, через функцию map. Почему не возникает ошибки при на новую проперть xxx, которая не объявлена в интерфейсе?

interface A{
  a:number;
  b:string;
};

let a:A[] = [{a:1, b:"a"}, {a:2, b:"b"}];

let b:A[] = a.map((x)=>({...x, xxx:"abc"}));

Ответы (1 шт):

Автор решения: Gray

У ответа 2 части.

Часть Первая:

Это пример ковариантности. TypeScript вывел тип:

{
    xxx: string;
    a: number;
    b: string;
}

Который является подтипом интерфейса A. То что можно сделать с супертипом, можно сделать и с подтипом. Ошибки тут нет, это правильное и ожидаемое поведение, как в других языках. За подробностями можно обратиться к термину "Вариантность" в программировании. К сожалению не могу дать одного источника со всеми разъяснениями, можно начать копать с книги "Профессиональный TypeScript" за авторством Черный Б.

Часть Вторая:

А дальше интереснее. Можно сделать так, чтобы нам выдавалась ошибка при использовании литерала объекта благодаря этой фиче.

Проще говоря, в случае прямого присваивания TypeScript проверяет состав полей и говорит нам о потенциальной ошибке. Тоже относится к прямому возврату литерала объекта через return или пишем тот же литерал при вызове функции.

Тут мы немного подсказываем компилятору что у нас тип является интерфейсом А:

// Ошибка!
let b: A[] = a.map((x): A =>({ ...x, xxx: "abc" }));

// Тоже ошибка!
const doIt = (s: A) => null;
doIt({ ...a[0], xxx: "abc" });

// Ещё одна ошибка!
let e: A = { ...a[0], xxx: "abc" };

Но это работает только на одном уровне присваивания, дальше включается ковариантность:

// Нет ошибки
let c: A[] = a.map((x): A => {
    const some = { ...x, xxx: "abc" };
    const other: A = some;
    return other;
});

Ссылка на песочницу

→ Ссылка