简体   繁体   中英

How to initialize an array object with extra properties in TypeScript?

I need to create an object of the following type:

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

I do this as follows:

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

Question: What is a better way to accomplish this (ie without using any )?

Bonus Question: What is a good way to initialize an object of type: type FuncWithA = ((string)=>void) & { a: string } ?

Use Object.assign !

The return type of Object.assign is just the intersection of the types of its arguments, so you can compose these hybrid objects by listing out their parts and combining at once, rather than adding additional properties after the fact (which often requires type casts, as you note).

So for some of your examples, you might do so like this:

type ArrayWithA = [number, number, number] & { 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] & { a: string } and is assignable to ArrayWithA.

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

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

Playground Link.

I would recommend using Object.assign() , which TypeScript's standard library represents as returning an intersection type of the form you want. There's a little bit of a wrinkle in that it's not easy to get the compiler to just infer that an array literal will be a tuple of the exact type [number, number, number] . If you are okay with readonly [number, number, number] then you can use a const assertion :

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

Otherwise there are various tricks you can use:

type ArrayWithA = [number, number, number] & { 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" });

For functions, you can do the same thing with Object.assign() :

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

But you could also just use a function statement and add the property later, since TypeScript 3.1 introduced expando functions :

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

Playground link to code

I would go with something like:

type ArrayWithA = [number, number, number] & { 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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM