Issue
The EventBridgeEvent
interface found in "@types/aws-lambda"
is defined as:
export interface EventBridgeEvent<TDetailType extends string, TDetail>
Notice that TDetailType
extends the string
type. What's interesting to me is that I can define a variable as:
event: EventBridgeEvent<'stringLiteralFoo', Bar>
In order to prevent copying/pasting the string literal I tried to define it as a variable but then I am no longer able to use it as a type.
const stringLiteralFooVar = 'stringLiteralFoo'
event: EventBridgeEvent<stringLiteralFooVar, Bar>
The error I get is:
'stringLiteralFoo' refers to a value, but is being used as a type here. Did you mean 'typeof stringLiteralFoo'?
The full definition of the Event Bridge Event is
export interface EventBridgeEvent<TDetailType extends string, TDetail> {
id: string;
version: string;
account: string;
time: string;
region: string;
resources: string[];
source: string;
'detail-type': TDetailType;
detail: TDetail;
'replay-name'?: string;
}
So to find the detail-type
of a specific event I'm doing the following:
event: EventBridgeEvent<'stringLiteralFoo', Bar>
if (event['detail-type'] == 'stringLiteralFoo') {
// logic here
}
But I'd like to prevent copying/pasting the 'stringLiteralFoo'
literal.
Here is the minimal reproducible example:
export interface EventBridgeEvent<TDetailType extends string, TDetail> {
'detail-type': TDetailType;
detail: TDetail;
}
const event: EventBridgeEvent<'Foo1' | 'Foo2', Bar> = {
'detail-type': 'Foo1',
detail: {}, // of type Bar
}
if (event['detail-type'] == 'Foo1') {
// logic here
}
interface Bar {}
So my final question is, in the example above, how can I prevent copying and pasting the literal string; 'stringLiteralFoo'
?
Solution
If you want stop yourself from having to mention the same quoted strings multiple times both as string literal values and string literal types, you could do something like this. First, create a const
-asserted array of the relevant string literals:
const detailTypes = ["Foo1", "Foo2"] as const;
That const
assertion is telling the compiler that you don't want it to simply infer the type of detailTypes
as string[]
, a mutable and unordered array of an arbitrary number of unknown strings. Instead, you want it to infer an ordered, fixed-length, readonly tuple type consisting of the string literal types of the contents. So the compiler infers this for detailTypes
:
// const detailTypes: readonly ["Foo1", "Foo2"]
Armed with this, we can use various value and type operators to get the types you care about without having to write out the quoted string values and types again. For example:
const event: EventBridgeEvent<typeof detailTypes[number], Bar> = {
'detail-type': detailTypes[0],
detail: {}, // of type Bar
}
if (event['detail-type'] == detailTypes[0]) {
}
The type typeof detailTypes
uses the TypeScript typeof
type query operator to resolve to readonly ["Foo1", "Foo2"]
. In order to get the union type "Foo1" |"Foo2"
from this, we can index into it with the number
index: typeof detailTypes[number]
. You can think of this as: what type do you get if you index into detailTypes
with a valid key of type number
? The answer is "either "Foo1"
or "Foo2"
", and hence the union.
And you can write detailTypes[0]
or detailTypes[1]
to grab the individual element values. If you need their types, you can use the typeof
type operator again, either directly
type ElemOne = typeof detailTypes[1];
// type ElemOne = "Foo2"
or indirectly
const elemOne = detailTypes[1];
type ElemOneAlso = typeof elemOne;
// type ElemOneAlso = "Foo2"
So there you go.
Note that in type ElemOne = typeof detailTypes[1]
, the 1
is a numeric literal type and the [1]
is an indexed access type, whereas in const elemOne = detailTypes[1]
, the 1
is a numeric literal value, and the [1]
is an actual indexing operation in JavaScript. They look the same, and in some sense they talk about comparable operations, but they are not the same. Explaining the difference can be tricky. The type ElemOne = ...
line is purely acting at the type level, with TypeScript type operators, while the const elemOne = ...
line is acting at the value level, with JavaScript operators. The type system is completely erased from the emitted JavaScript. See this answer to a different question about how types and values exist in separate levels and how they might look the same but sometimes refer to very different things.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.