Как инициализировать объект массива с дополнительными свойствами в TypeScript?

#typescript

#typescript

Вопрос:

Мне нужно создать объект следующего типа:

 type ArrayWithA = [number, number, number] amp; { a: string };
 

Я делаю это следующим образом:

 const obj : any = [1, 2, 3];
obj.a = "foo";
const arrayWithA : ArrayWithA = obj as ArrayWithA;
 

Вопрос: Каков лучший способ добиться этого (т.е. Без использования any )?

Бонусный вопрос: Каков хороший способ инициализации объекта типа: type FuncWithA = ((string)=>void) amp; { a: string } ?

Ответ №1:

Я бы рекомендовал использовать Object.assign() , который стандартная библиотека TypeScript представляет как возвращающий тип пересечения нужной вам формы. Есть небольшая проблема в том, что нелегко заставить компилятор просто сделать вывод, что литерал массива будет кортежем точного типа [number, number, number] . Если вы согласны с readonly [number, number, number] этим, вы можете использовать const утверждение:

 type ArrayWithA = readonly [number, number, number] amp; { a: string };
const arrayWithA: ArrayWithA = Object.assign([1, 2, 3] as const, { a: "foo" });
 

В противном случае вы можете использовать различные приемы:

 type ArrayWithA = [number, number, number] amp; { a: string };

const arr: [number, number, number] = [1, 2, 3]; // annotate extra variable
let arrayWithA: ArrayWithA = Object.assign(arr, { a: "foo" });

// type assertion
arrayWithA = Object.assign([1, 2, 3] as [number, number, number], { a: "foo" });

// helper function
const asTuple = <T extends any[]>(arr: [...T]) => arr;
arrayWithA = Object.assign(asTuple([1, 2, 3]), { a: "foo" });
 

Для функций вы можете сделать то же самое с Object.assign() :

 type FuncWithA = ((x: string) => void) amp; { a: string }
let funcWithA: FuncWithA = Object.assign(
  (x: string) => console.log(x.toUpperCase()), 
  { a: "foo" }
);
 

Но вы также можете просто использовать оператор функции и добавить свойство позже, поскольку в TypeScript 3.1 представлены функции расширения:

 function func(x: string) {
    console.log(x);
}
func.a = "foo"; // no error
funcWithA = func; // that works
 

Игровая площадка ссылка на код

Ответ №2:

Используйте Object.assign !

Возвращаемый тип Object.assign — это просто пересечение типов его аргументов, поэтому вы можете создавать эти гибридные объекты, перечисляя их части и комбинируя сразу, а не добавляя дополнительные свойства постфактум (что часто требует приведения типов, как вы заметили).

Итак, для некоторых ваших примеров вы можете сделать это следующим образом:

 type ArrayWithA = [number, number, number] amp; { a: string };

// Try like so:
// The cast is needed as otherwise it will be inferred as number[].
const obj2 = Object.assign([1, 2, 3] as [number, number, number], {a: "foo"}); // obj2 has type: [number, number, number] amp; { a: string } and is assignable to ArrayWithA.

// Same for functions!
type FuncWithA = ((arg: string) => void) amp; { a: string };

const f1 = Object.assign((s: string) => {}, {a: "foo"}) // f1 has type: ((s: string) => void) amp; { a: string } and is assignable to FuncWithA.
 

Ссылка на игровую площадку.

Ответ №3:

Я бы выбрал что-то вроде:

 type ArrayWithA = [number, number, number] amp; { a: string };
namespace ArrayWithA {
  export function of(a: string, ...rest: [number, number, number]): ArrayWithA {
    const o = rest as ArrayWithA;
    o.a = a;
    return o;
  }
}

const arrayWithA = ArrayWithA.of('a', 1, 2, 3);