我知道What does enclosing a class in angle brackets “<>” mean in TypeScript?中有一个几乎相似的问题
但是当我发现它以复杂的方式(对于新手)在接口内声明函数时,仍然感到困惑。
getContent<K extends keyof ContentMap>(content: K, conf?: ContentMap[K]["conf"]): Promise<Readonly<ContentMap[K]["content"]>>;
尖括号使用了很多次(甚至递归)。我怎么读?
答案 0 :(得分:3)
学习打字稿时,实际上不是学习一种语言,而是学习两种语言。第一语言是Typescript专有语言,它是带有类型注释和一些扩展名的Javascript,例如“枚举”或“公共/私有”类成员。第二种语言是类型的语言。它没有正式名称,我们以发明人Anders Hejlsberg的名字命名为Anders。
Anders的目的是为程序生成动态类型。虽然Typescript可以处理字符串,数字,对象等值,但是Anders仅处理一种数据:类型本身。安德斯的价值观是类型。 Anders中的函数接受一个或多个类型参数,然后返回另一种类型。
每次在程序中使用<>
时,实际上是在编写Anders代码,而不是Typescript代码。可以通过类型推断来显式调用此代码(在编写MyType<T>
之类的代码时),也可以在后台进行调用。
例如,这是一个Typescript函数,该函数接受两个值并基于它们返回另一个值:
function pair (x, y) {
return [x, y]
}
这是一个Anders函数,它接受两种类型并基于它们返回另一种类型:
type Pair<U, V> = [U, V]
在Typescript中,如果给pair
两个值,则将得到这两个值的数组。
在安德斯(Anders)中,如果您输入Pair
number
(不是任何数字,即“数字”类型)和string
,您将得到[number, string]
,该数字是所有可能的number,string
数组的类型,例如[1, "hi"]
或[3.14, "hey"]
。如果您给它string
和boolean
,则将获得所有数组的类型,例如["hi", true]
,["blah", false]
。
与其他语言一样,安德斯(Anders)提供了基本的编程构造(概括地说,它们都是类型或作用于类型,而不是值):
内置类型,例如number
,string
,any
,{}
。这些类似于Typescript内置对象,例如“ Number”或“ String”。
文字,例如"foo"
。这些类似于Typescript中的文字,但是在TS "foo"
中表示特定的字符串,例如在Anders中,字符序列f, o, o
的意思是一种类型,即“所有为foo的字符串的类型”,很显然,它只有一个可能的成员"foo"
。
联合,类似于TS中的数组:A|B|C
。
结构,类似于TS中的对象。在TS中,对象将字符串映射到值。在Anders中,结构(也称为“映射类型”)将类型映射为其他类型。索引运算符S[B]
返回结构S
映射到的类型B
{foo: string; bar:number}["foo"]` ====> string
个运算符,例如一元keyof
运算符采用类型A
并返回A
的所有可能键的类型,即,联合(数组)TypeOfKey1 | TypeOfKey2 | ...
keyof {foo:string, bar:number} =====> "foo"|"bar"
比较,例如TS中的a > b
。 Anders仅具有一种比较形式,即A extends B
,这意味着A
是B
的子集,也就是说,类型A
的所有可能值也是B
,但不一定相反。
"foo" extends string =====> ok
"foo" extends "foo"|"bar" =====> ok
"blag" extends "foo"|"bar" =====> not ok
条件词:comparison ? Type1 : Type2
循环,如{[A in SomeUnion]: T}
。这将创建一个结构,其键为联合成员,值的类型为T
{[A in "foo"|"bar"]: number} =====> {foo:number, bar:number}
函数调用,即SomeOtherTypeDeclaration<Type1, Type2, ...>
最后,Anders还对输入参数进行类型检查,类似于Typescript中的function foo(x:number)
。在Anders中,类型检查是一种比较,即A extends B
现在,回到您的示例(为清楚起见,对其进行了简化)。
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
是Anders函数,它接受类型K,并返回另一个类型(X, Y) => Z
,它是所有具有两个参数类型X
和{{1 }},返回值的类型为Y
。
让我们以不同类型手动“调用”此功能,然后看看会发生什么。
Z
。首先,Anders检查参数的类型。我们的类型检查是getContent<number>
。我们记得,extends keyof ContentMap
返回了keyof ContentMap
的键数组,也就是ContentMap
,其中"foo"|"bar"
和"foo"
都是类型,而不仅仅是字符串。然后,将我们的参数"bar"
与number
进行比较。显然,"foo"|"bar"
不是该类型的子集,因此类型检查失败,并且会出现错误。
number
。类型检查成功(由于getContent<"foo">
是"foo"
的子集),我们可以继续进行。我们的任务是基于"foo"|"bar"
构造函数类型。第一个参数的类型为"foo"
,与参数相同,因此它只是K
。第二个参数两次应用了索引运算符:首先,我们评估"foo"
,这给了我们ContentMap["foo"]
,然后我们应用了{conf: A, content: B}
,这给了我们["conf"]
。以类似的方式,我们获得A
作为返回类型。最后,我们调用内置的Anders函数B
并返回另一种类型,我们将其称为Readonly
,因此,我们得到的是函数类型ReadonlyB
,这是我们的Anders函数返回什么。
(content: "foo", conf: A) => ReadonlyB
...作为练习。
现在,当你写这篇文章时会发生什么?
getContent<"bar">
编译器会发现您有一些与let something = getContent('foo', {...})
相关的Anders代码,并通过传递getContent
作为参数来评估该代码。如上所示,返回类型将为"foo"
。然后,对上述类型的行进行检查,如果不匹配则失败,这基本上就是全部内容。
希望这对您有帮助...
答案 1 :(得分:1)
如@axiac所述,这与泛型有关。
阅读方法是思考类型。
示例:
// generic class that deals with type T
class List<T> {}
// usage
const list1 = new List<string>() // list of type string
const list2 = new List<number>() // list of type number
const list3 = new List<any>() // list of type any