Issue
In TypeScript code, I often will see code wrapped inside of Angle Brackets, just like HTML. I know that they are not HTML Elements, and I know that the code inside of the angle brackets are types, however; I see types written without angle-brackets all the time. It seems that there is a very specific & fundamental purpose for wrapping types inside of the angle brackets, and I feel that much of what I fail to understand can be deduced from the answer to this question.
I would like to know why the angle brackets are part of the TypeScript language, what the angle brackets do programmatically, and what effect do the angle brackets have on the code inside of them.
For example: What is the purpose of adding the angle brackets here? How should I interpret them?
getContent<K extends keyof ContentMap>(content: K, conf?: ContentMap[K]["conf"]): Promise<Readonly<ContentMap[K]["content"]>>;
Solution
When you learn Typescript, you actually learn not one language, but two. The first language is the Typescript proper, which is Javascript with type annotations and some extensions, like "enum" or "public/private" class members. The second language is the language of types. It has no official name, let's call it Anders after the inventor, Anders Hejlsberg.
The purpose of Anders is to generate dynamic types for your program. While Typescript manipulates values that are strings, numbers, objects etc, Anders only deals with a single kind of data: the type itself. Anders' values are types. A function in Anders accepts one or multiple type arguments and returns another type.
Every time you use <>
in your program, you actually write Anders code, not Typescript code. This code can be called either explicitly (when you write something like MyType<T>
), or under the hood, via type inference.
For example, here's a Typescript function, which accepts two values and returns another value, based on them:
function pair (x, y) {
return [x, y]
}
This is an Anders function, which accepts two types and returns another type, based on them:
type Pair<U, V> = [U, V]
In Typescript, if you give pair
two values, you'll get an array of these two values.
In Anders, if you give Pair
number
(not any number, the "number" type), and string
, you'll get back [number, string]
, which is the type of all possible number,string
arrays, like [1, "hi"]
or [3.14, "hey"]
. If you give it string
and boolean
, you'll get the type of all arrays like ["hi", true]
, ["blah", false]
.
Like other languages, Anders provides basic programming constructs (that, to recap, all are types or act on types, not values):
built-in types, like
number
,string
,any
,{}
. These are similar to Typescript built-in objects like "Number" or "String".literals, like
"foo"
. These are similar to literals in Typescript, but while in TS"foo"
means a specific string, e.g. a sequence of charactersf, o, o
, in Anders it means a type, namely, "the type of all strings that are foo", which, obviously, has only one possible member,"foo"
.unions, similar to arrays in TS:
A|B|C
.structures, similar to objects in TS. In TS, an object maps strings to values. In Anders, a structure (aka "mapped type"), maps types to other types. The index operator
S[B]
returns the type to which the structureS
mapsB
{foo: string; bar:number}["foo"]` ====> string
operators, e.g. the unary
keyof
operator takes a typeA
and returns the type of all possible keys ofA
, that is, a union (array)TypeOfKey1 | TypeOfKey2 | ...
keyof {foo:string, bar:number} =====> "foo"|"bar"
comparisons, like
a > b
in TS. Anders only has one form of comparison,A extends B
, which means thatA
is a subset ofB
, that is, all possible values of the typeA
are also values ofB
, but not necessarily the other way around."foo" extends string =====> ok "foo" extends "foo"|"bar" =====> ok "blag" extends "foo"|"bar" =====> not ok
conditionals:
comparison ? Type1 : Type2
loops, like
{[A in SomeUnion]: T}
. This creates a structure, whose keys are the union members and values are of type T{[A in "foo"|"bar"]: number} =====> {foo:number, bar:number}
function calls, which are
SomeOtherTypeDeclaration<Type1, Type2, ...>
finally, Anders also have type checks for input parameters, similar to
function foo(x:number)
in Typescript. In Anders, a type check is a comparison, that is,A extends B
Now, back to your example (simplified for clarity).
interface A {}
interface B {}
interface C {}
interface D {}
type ContentMap = {
foo: {
conf: A
content: B
},
bar: {
conf: C
content: D
}
}
function getContent<K extends keyof ContentMap>
( content: K,
conf?: ContentMap[K]["conf"]
): Readonly<ContentMap[K]["content"]> {
...
}
getContent
is the Anders function, which accepts a type K and returns another type (X, Y) => Z
, which is a type of all functions that have two arguments of types X
and Y
and the return value is of type Z
.
Let's "call" this function manually with different types and see what happens.
getContent<number>
. First off, Anders checks the type for the argument. Our type check isextends keyof ContentMap
. As we recall,keyof ContentMap
returns an array of keys ofContentMap
, that is"foo"|"bar"
where, again,"foo"
and"bar"
are types and not just strings. Then, our argument,number
, is checked against"foo"|"bar"
. Obviously,number
is not a subset of this type, so the type check fails and we get an error.getContent<"foo">
. The type check succeeds (since"foo"
is a subset of"foo"|"bar"
) and we can proceed. Our task is to construct the function type based on"foo"
. The first param has the typeK
, the same as the argument, so it becomes just"foo"
. The second param applies the index operator twice: first, we evaluateContentMap["foo"]
, which gives us{conf: A, content: B}
and then we apply["conf"]
, which gives usA
. In the similar way, we obtainB
for the return type. Finally, we call the built-in Anders functionReadonly
and get back another type, let's call itReadonlyB
, So, what we've got is the function type(content: "foo", conf: A) => ReadonlyB
, and this is what our Anders function returns.getContent<"bar">
... left as an exercise.
Now, what happens when you write this?
let something = getContent('foo', {...})
The compiler sees that you have some Anders code, related to getContent
and evaluates that code, passing "foo"
as an argument. As seen above, the return type will be ("foo", A) => ReadonlyB
. Then, the above line is checked against this type, and fails if it doesn't match, which is basically what the whole thing is all about.
Hope this helps...
Answered By - georg Answer Checked By - Senaida (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.