Home > Blockchain >  TS - Cannot pass argument from const - how to use "as const" with arrays?
TS - Cannot pass argument from const - how to use "as const" with arrays?

Time:03-04

I need to pass nested object as method argument. Inline everything works, but passing variable will result in TS error because for example { a: 'x' } will become { a: string } instead. Solution for that is to use as const on that variable. But this does not work on objects with arrays etc. I'm forced to use 'as const' multiple times INSIDE that object, instead just doing 'as const` at the end. Why?

type A = 'xxx' | 'yyy' | 'zzz'

type Demo = {
    a: A,
    b: number,
}[]

const demoMethod = (demo: Demo) => {
    console.log(demo)
}

/* Example #1 - OK */
demoMethod([
    { a: 'xxx', b: 111 },
    { a: 'yyy', b: 222 },
    { a: 'xxx', b: 333 },
])

/* Example #2 - ERROR */
const demo2 = [
    { a: 'xxx', b: 111 },
    { a: 'yyy', b: 222 },
    { a: 'xxx', b: 333 },
]
demoMethod(demo2)

/* Example #3 - OK */
const demo3 = [
    { a: 'xxx', b: 111 } as const,
    { a: 'yyy', b: 222 } as const,
    { a: 'xxx', b: 333 } as const,
]
demoMethod(demo3)


/* Example #4 - ERROR */
const demo4 = [
    { a: 'xxx', b: 111 },
    { a: 'yyy', b: 222 },
    { a: 'xxx', b: 333 },
] as const
demoMethod(demo4)

TS playground

It does not make any sense for me that even that example #1 and #2 are 100% the same, #2 does not work and I'm forced to change my data using multiple 'as const' instead passing it at it is. Is there any better solution to force TS to treat my const argument same way as inline argument?

Edit: Note that I have defined type and method only in my example purposes. In real life this method comes from external package, and without exported type.

CodePudding user response:

Since, you don't have access to the type, you can try to retrieve it using Parameters.

type a = Parameters<typeof demoMethod>[0];
// Which gives
// type a = {
//    a: A;
//    b: number;
//}[]

Then use type annotation for example!

Previous response

Instead of using as const, you could do const demo5: Demo = ....

It will then check that the value you give to demo5 corresponds to the type Demo.

const demo5: Demo = [
    { a: 'xxx', b: 111 },
    { a: 'abc', b: 222 }, // TypeError: Type '"abc"' is not assignable to type 'A'.
]

Playground example

In general, it is better to avoid const a = ... as Demo; because Typescript won't do any check and just assume that a is of type Demo.

const a = 'Hello world!' as number; // Won't raise an error
const a: number = 'Hello world!'; // Will raise a type error

CodePudding user response:

Example #2 does not work because demo is inferred as { a: string; b: number; }[]. This is clearly not assignable to Demo, which is { a: A; b: number; }[]. You have to either explicitly tell TypeScript that demo is of type Demo with a type annotation like @Cr4zySheep said, or use as:

// type annotation
const demo2: Demo = [
    { a: 'xxx', b: 111 },
    { a: 'yyy', b: 222 },
    { a: 'xxx', b: 333 },
] as Demo; // or use 'as'

demoMethod(demo2);

Below fixes example #4

Readonly arrays are not assignable to regular arrays.

This is because regular arrays are mutable. If you provide an immutable array to something that expects a mutable array, that clearly is a type mismatch. However, regular arrays can be assigned to immutable arrays, because regular arrays don't have to be mutated.

type Demo = ReadonlyArray<{
    a: A,
    b: number,
}>;

So here would be the proposed solution; just use a readonly array.

  • Related