[{"data":1,"prerenderedAt":3952},["ShallowReactive",2],{"\u002Fwriting\u002Ftype-gymnastics-1":3},{"id":4,"title":5,"body":6,"date":3941,"description":3942,"extension":3943,"intro":3944,"meta":3945,"navigation":449,"path":3946,"seo":3947,"stem":3948,"tags":3949,"__hash__":3951},"writing\u002Fwriting\u002Ftype-gymnastics-1.md","TS Gymnastics 1 - Upfront Basics",{"type":7,"value":8,"toc":3922},"minimark",[9,21,24,36,41,152,171,313,320,325,369,378,457,463,521,524,687,698,701,712,781,784,791,797,841,1035,1042,1057,1073,1252,1258,1418,1421,1427,1430,1570,1580,1800,1803,1810,1897,1912,1919,2014,2021,2027,2039,2221,2228,2307,2310,2526,2529,2532,2675,2681,2837,2840,2843,2846,3013,3016,3019,3066,3073,3291,3294,3394,3420,3423,3430,3439,3570,3576,3671,3679,3691,3831,3834,3866,3918],[10,11,12,13,20],"p",{},"TS 的类型系统十分完备，本质上可以看做是一门",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTuring_completeness",[18],"nofollow","图灵完备","的编译期语言。而「类型体操 Type Gymnastics」指利用 TS 中的类型系统进行各类复杂操作甚至实现部分算法的技巧。",[10,22,23],{},"在基础篇中，将回顾 TS 类型系统的一些基本类型和行为。",[25,26,28,32,33],"h2",{"id":27},"never与",[29,30,31],"code",{},"never","与",[29,34,35],{},"{}",[10,37,38,40],{},[29,39,31],{},"表示不存在的类型，是所有类型的子类型。利用 never 在类型层面的“空”特性，可以实现类型的筛选和排除。",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-ts shiki shiki-themes houston","function foo(x: string | number) {\n  if (typeof x === 'string') return\n  if (typeof x === 'number') return\n  x \u002F\u002F never（不可达分支）\n}\n","ts","",[29,49,50,87,116,136,146],{"__ignoreMap":47},[51,52,55,59,63,67,71,74,78,81,84],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"sqF6k","function",[51,60,62],{"class":61},"stHQG"," foo",[51,64,66],{"class":65},"sLo_3","(",[51,68,70],{"class":69},"smONe","x",[51,72,73],{"class":65},": ",[51,75,77],{"class":76},"s8HWQ","string",[51,79,80],{"class":65}," | ",[51,82,83],{"class":76},"number",[51,85,86],{"class":65},") {\n",[51,88,90,93,96,99,103,106,110,113],{"class":53,"line":89},2,[51,91,92],{"class":57},"  if",[51,94,95],{"class":65}," (",[51,97,98],{"class":57},"typeof",[51,100,102],{"class":101},"sb16X"," x",[51,104,105],{"class":65}," === ",[51,107,109],{"class":108},"se2J0","'string'",[51,111,112],{"class":65},") ",[51,114,115],{"class":57},"return\n",[51,117,119,121,123,125,127,129,132,134],{"class":53,"line":118},3,[51,120,92],{"class":57},[51,122,95],{"class":65},[51,124,98],{"class":57},[51,126,102],{"class":101},[51,128,105],{"class":65},[51,130,131],{"class":108},"'number'",[51,133,112],{"class":65},[51,135,115],{"class":57},[51,137,139,142],{"class":53,"line":138},4,[51,140,141],{"class":101},"  x",[51,143,145],{"class":144},"sy89t"," \u002F\u002F never（不可达分支）\n",[51,147,149],{"class":53,"line":148},5,[51,150,151],{"class":65},"}\n",[10,153,154,155,157,158,170],{},"但是，",[29,156,35],{},"并非表示空类型，而是",[159,160,161,162,165,166,169],"strong",{},"所有非",[29,163,164],{},"null","\u002F",[29,167,168],{},"undefined","的类型","。换言之，有下列类型结果：",[42,172,174],{"className":44,"code":173,"language":46,"meta":47,"style":47},"type A = string extends {} ? true : false      \u002F\u002F true\ntype B = number extends {} ? true : false      \u002F\u002F true\ntype C = {} extends {} ? true : false          \u002F\u002F true\ntype D = null extends {} ? true : false        \u002F\u002F false\ntype E = undefined extends {} ? true : false   \u002F\u002F false\n",[29,175,176,210,235,261,287],{"__ignoreMap":47},[51,177,178,181,184,187,189,192,195,198,201,204,207],{"class":53,"line":54},[51,179,180],{"class":57},"type",[51,182,183],{"class":76}," A",[51,185,186],{"class":65}," = ",[51,188,77],{"class":76},[51,190,191],{"class":57}," extends",[51,193,194],{"class":65}," {} ",[51,196,197],{"class":57},"?",[51,199,200],{"class":76}," true",[51,202,203],{"class":57}," :",[51,205,206],{"class":76}," false",[51,208,209],{"class":144},"      \u002F\u002F true\n",[51,211,212,214,217,219,221,223,225,227,229,231,233],{"class":53,"line":89},[51,213,180],{"class":57},[51,215,216],{"class":76}," B",[51,218,186],{"class":65},[51,220,83],{"class":76},[51,222,191],{"class":57},[51,224,194],{"class":65},[51,226,197],{"class":57},[51,228,200],{"class":76},[51,230,203],{"class":57},[51,232,206],{"class":76},[51,234,209],{"class":144},[51,236,237,239,242,245,248,250,252,254,256,258],{"class":53,"line":118},[51,238,180],{"class":57},[51,240,241],{"class":76}," C",[51,243,244],{"class":65}," = {} ",[51,246,247],{"class":57},"extends",[51,249,194],{"class":65},[51,251,197],{"class":57},[51,253,200],{"class":76},[51,255,203],{"class":57},[51,257,206],{"class":76},[51,259,260],{"class":144},"          \u002F\u002F true\n",[51,262,263,265,268,270,272,274,276,278,280,282,284],{"class":53,"line":138},[51,264,180],{"class":57},[51,266,267],{"class":76}," D",[51,269,186],{"class":65},[51,271,164],{"class":76},[51,273,191],{"class":57},[51,275,194],{"class":65},[51,277,197],{"class":57},[51,279,200],{"class":76},[51,281,203],{"class":57},[51,283,206],{"class":76},[51,285,286],{"class":144},"        \u002F\u002F false\n",[51,288,289,291,294,296,298,300,302,304,306,308,310],{"class":53,"line":148},[51,290,180],{"class":57},[51,292,293],{"class":76}," E",[51,295,186],{"class":65},[51,297,168],{"class":76},[51,299,191],{"class":57},[51,301,194],{"class":65},[51,303,197],{"class":57},[51,305,200],{"class":76},[51,307,203],{"class":57},[51,309,206],{"class":76},[51,311,312],{"class":144},"   \u002F\u002F false\n",[25,314,316,319],{"id":315},"keyof类型系统的反射",[29,317,318],{},"keyof","：类型系统的「反射」",[10,321,322,324],{},[29,323,318],{},"经常被用于获取对象类中键的字面量联合类型，例如：",[42,326,328],{"className":44,"code":327,"language":46,"meta":47,"style":47},"type T = {\n  name: string\n  age: number\n}\n\u002F\u002F keyof T: 'name' | 'age'\n",[29,329,330,340,350,360,364],{"__ignoreMap":47},[51,331,332,334,337],{"class":53,"line":54},[51,333,180],{"class":57},[51,335,336],{"class":76}," T",[51,338,339],{"class":65}," = {\n",[51,341,342,345,347],{"class":53,"line":89},[51,343,344],{"class":101},"  name",[51,346,73],{"class":65},[51,348,349],{"class":76},"string\n",[51,351,352,355,357],{"class":53,"line":118},[51,353,354],{"class":101},"  age",[51,356,73],{"class":65},[51,358,359],{"class":76},"number\n",[51,361,362],{"class":53,"line":138},[51,363,151],{"class":65},[51,365,366],{"class":53,"line":148},[51,367,368],{"class":144},"\u002F\u002F keyof T: 'name' | 'age'\n",[10,370,371,373,374,377],{},[29,372,318],{},"也可以被使用在类上，且",[159,375,376],{},"只会返回公有属性的键的类型","。",[42,379,381],{"className":44,"code":380,"language":46,"meta":47,"style":47},"class User {\n  readonly name: string\n  private age: number\n  protected address: string\n  bio: string\n}\n\n\u002F\u002F keyof User: \"name\" | \"bio\"\n",[29,382,383,394,406,418,430,439,444,451],{"__ignoreMap":47},[51,384,385,388,391],{"class":53,"line":54},[51,386,387],{"class":57},"class",[51,389,390],{"class":76}," User",[51,392,393],{"class":65}," {\n",[51,395,396,399,402,404],{"class":53,"line":89},[51,397,398],{"class":57},"  readonly",[51,400,401],{"class":101}," name",[51,403,73],{"class":65},[51,405,349],{"class":76},[51,407,408,411,414,416],{"class":53,"line":118},[51,409,410],{"class":57},"  private",[51,412,413],{"class":101}," age",[51,415,73],{"class":65},[51,417,359],{"class":76},[51,419,420,423,426,428],{"class":53,"line":138},[51,421,422],{"class":57},"  protected",[51,424,425],{"class":101}," address",[51,427,73],{"class":65},[51,429,349],{"class":76},[51,431,432,435,437],{"class":53,"line":148},[51,433,434],{"class":101},"  bio",[51,436,73],{"class":65},[51,438,349],{"class":76},[51,440,442],{"class":53,"line":441},6,[51,443,151],{"class":65},[51,445,447],{"class":53,"line":446},7,[51,448,450],{"emptyLinePlaceholder":449},true,"\n",[51,452,454],{"class":53,"line":453},8,[51,455,456],{"class":144},"\u002F\u002F keyof User: \"name\" | \"bio\"\n",[10,458,459,460,462],{},"在其他类型上使用",[29,461,318],{},"，通常返回该类型索引签名和原型方法与属性的字面量。",[42,464,466],{"className":44,"code":465,"language":46,"meta":47,"style":47},"keyof any[] \u002F\u002F number | 'length' | 'toString' | 'push' | 'pop' | ...\nkeyof [string, number] \u002F\u002F '0' | '1' | 'length' | 'push' | ...\nkeyof string \u002F\u002F number | 'length' | 'charAt' | 'substring' | ...\nkeyof '字符串字面量' \u002F\u002F 同 keyof string\n",[29,467,468,481,501,511],{"__ignoreMap":47},[51,469,470,472,475,478],{"class":53,"line":54},[51,471,318],{"class":101},[51,473,474],{"class":101}," any",[51,476,477],{"class":65},"[] ",[51,479,480],{"class":144},"\u002F\u002F number | 'length' | 'toString' | 'push' | 'pop' | ...\n",[51,482,483,485,488,490,493,495,498],{"class":53,"line":89},[51,484,318],{"class":101},[51,486,487],{"class":65}," [",[51,489,77],{"class":101},[51,491,492],{"class":65},", ",[51,494,83],{"class":101},[51,496,497],{"class":65},"] ",[51,499,500],{"class":144},"\u002F\u002F '0' | '1' | 'length' | 'push' | ...\n",[51,502,503,505,508],{"class":53,"line":118},[51,504,318],{"class":101},[51,506,507],{"class":101}," string",[51,509,510],{"class":144}," \u002F\u002F number | 'length' | 'charAt' | 'substring' | ...\n",[51,512,513,515,518],{"class":53,"line":138},[51,514,318],{"class":101},[51,516,517],{"class":108}," '字符串字面量'",[51,519,520],{"class":144}," \u002F\u002F 同 keyof string\n",[10,522,523],{},"这是因为，对于数组、字符串等类型，在 TS 里有如下的类型签名：",[42,525,527],{"className":44,"code":526,"language":46,"meta":47,"style":47},"interface Array\u003CT> {\n  [index: number]: T\n  length: number\n  push(...items: T[]): number\n  ...\n}\n\ninterface String {\n  readonly length: number\n  charAt(pos: number): string\n  substring(start: number, end?: number): string\n  ...\n}\n",[29,528,529,546,564,573,593,598,602,606,615,627,647,677,682],{"__ignoreMap":47},[51,530,531,534,537,540,543],{"class":53,"line":54},[51,532,533],{"class":57},"interface",[51,535,536],{"class":76}," Array",[51,538,539],{"class":65},"\u003C",[51,541,542],{"class":76},"T",[51,544,545],{"class":65},"> {\n",[51,547,548,551,554,556,558,561],{"class":53,"line":89},[51,549,550],{"class":65},"  [",[51,552,553],{"class":69},"index",[51,555,73],{"class":65},[51,557,83],{"class":76},[51,559,560],{"class":65},"]: ",[51,562,563],{"class":76},"T\n",[51,565,566,569,571],{"class":53,"line":118},[51,567,568],{"class":101},"  length",[51,570,73],{"class":65},[51,572,359],{"class":76},[51,574,575,578,581,584,586,588,591],{"class":53,"line":138},[51,576,577],{"class":61},"  push",[51,579,580],{"class":65},"(...",[51,582,583],{"class":69},"items",[51,585,73],{"class":65},[51,587,542],{"class":76},[51,589,590],{"class":65},"[]): ",[51,592,359],{"class":76},[51,594,595],{"class":53,"line":148},[51,596,597],{"class":65},"  ...\n",[51,599,600],{"class":53,"line":441},[51,601,151],{"class":65},[51,603,604],{"class":53,"line":446},[51,605,450],{"emptyLinePlaceholder":449},[51,607,608,610,613],{"class":53,"line":453},[51,609,533],{"class":57},[51,611,612],{"class":76}," String",[51,614,393],{"class":65},[51,616,618,620,623,625],{"class":53,"line":617},9,[51,619,398],{"class":57},[51,621,622],{"class":101}," length",[51,624,73],{"class":65},[51,626,359],{"class":76},[51,628,630,633,635,638,640,642,645],{"class":53,"line":629},10,[51,631,632],{"class":61},"  charAt",[51,634,66],{"class":65},[51,636,637],{"class":69},"pos",[51,639,73],{"class":65},[51,641,83],{"class":76},[51,643,644],{"class":65},"): ",[51,646,349],{"class":76},[51,648,650,653,655,658,660,662,664,667,669,671,673,675],{"class":53,"line":649},11,[51,651,652],{"class":61},"  substring",[51,654,66],{"class":65},[51,656,657],{"class":69},"start",[51,659,73],{"class":65},[51,661,83],{"class":76},[51,663,492],{"class":65},[51,665,666],{"class":69},"end",[51,668,197],{"class":57},[51,670,73],{"class":65},[51,672,83],{"class":76},[51,674,644],{"class":65},[51,676,349],{"class":76},[51,678,680],{"class":53,"line":679},12,[51,681,597],{"class":65},[51,683,685],{"class":53,"line":684},13,[51,686,151],{"class":65},[10,688,689,690,693,694,697],{},"遵循同样的规则，",[29,691,692],{},"keyof any","为",[29,695,696],{},"string | number | symbol","，因为任意键都可以是字符串、数字或 symbol 类型。",[25,699,700],{"id":700},"联合类型与交叉类型",[10,702,703,704,707,708,711],{},"联合类型",[29,705,706],{},"|","表示值可以是多种类型中的一种；交叉类型",[29,709,710],{},"&","表示值必须同时满足多个类型。这两个操作符是组合类型的基本方式。",[42,713,715],{"className":44,"code":714,"language":46,"meta":47,"style":47},"type A = { key: string }\ntype B = { key: number }\n\nA | B \u002F\u002F { key: string } | { key: number }\nA & B \u002F\u002F never\n",[29,716,717,736,752,756,769],{"__ignoreMap":47},[51,718,719,721,723,726,729,731,733],{"class":53,"line":54},[51,720,180],{"class":57},[51,722,183],{"class":76},[51,724,725],{"class":65}," = { ",[51,727,728],{"class":101},"key",[51,730,73],{"class":65},[51,732,77],{"class":76},[51,734,735],{"class":65}," }\n",[51,737,738,740,742,744,746,748,750],{"class":53,"line":89},[51,739,180],{"class":57},[51,741,216],{"class":76},[51,743,725],{"class":65},[51,745,728],{"class":101},[51,747,73],{"class":65},[51,749,83],{"class":76},[51,751,735],{"class":65},[51,753,754],{"class":53,"line":118},[51,755,450],{"emptyLinePlaceholder":449},[51,757,758,761,763,766],{"class":53,"line":138},[51,759,760],{"class":76},"A",[51,762,80],{"class":65},[51,764,765],{"class":76},"B",[51,767,768],{"class":144}," \u002F\u002F { key: string } | { key: number }\n",[51,770,771,773,776,778],{"class":53,"line":148},[51,772,760],{"class":76},[51,774,775],{"class":65}," & ",[51,777,765],{"class":76},[51,779,780],{"class":144}," \u002F\u002F never\n",[25,782,783],{"id":783},"条件类型",[10,785,786,787,790],{},"条件类型以类似三目表达式的形式，根据类型关系进行分支选择以决定最终的类型，能够实现类型系统中的",[29,788,789],{},"if-else","逻辑。",[10,792,793,794,796],{},"使用",[29,795,247],{},"来实现条件类型，常用于类型过滤和类型分发。",[798,799,801,825],"callout",{"title":800},"分发与防止分发",[10,802,803,804,806,807,810,811,814,815,817,818,821,822,824],{},"当",[29,805,542],{},"是",[159,808,809],{},"裸类型参数","时，",[29,812,813],{},"T extends U","会判断构成",[29,816,542],{},"的每一个子类型是否满足",[29,819,820],{},"extends U","子句。需要防止这种分发时，可以将",[29,823,542],{},"包裹在元组中。",[10,826,827,829,830,833,834,833,837,840],{},[159,828,809],{},"：指在泛型中未被包裹（即没有被如",[29,831,832],{},"Array\u003CT>","、",[29,835,836],{},"[T]",[29,838,839],{},"Promise\u003CT>","等类型修饰）的裸露的类型参数。",[42,842,844],{"className":44,"code":843,"language":46,"meta":47,"style":47},"\u002F\u002F 类型过滤，从联合类型中提取字符串类型\ntype ExtractString\u003CT> = T extends string ? T : never\ntype A = ExtractString\u003Cstring | number> \u002F\u002F string\n\n\u002F\u002F 类型分发，当 T 是联合类型时，条件类型会分配给每个子类型\ntype ToArray\u003CT> = T extends any ? T[] : never\ntype B = ToArray\u003Cstring | number> \u002F\u002F string[] | number[]\n\u002F\u002F 防止分发，将 T 包裹在元组中，避免条件类型分配给每个子类型\ntype ToArrayNonDist\u003CT> = [T] extends [any] ? T[] : never\ntype C = ToArrayNonDist\u003Cstring | number> \u002F\u002F (string | number)[]\n",[29,845,846,851,881,906,910,915,945,969,974,1011],{"__ignoreMap":47},[51,847,848],{"class":53,"line":54},[51,849,850],{"class":144},"\u002F\u002F 类型过滤，从联合类型中提取字符串类型\n",[51,852,853,855,858,860,862,865,867,869,871,874,876,878],{"class":53,"line":89},[51,854,180],{"class":57},[51,856,857],{"class":76}," ExtractString",[51,859,539],{"class":65},[51,861,542],{"class":76},[51,863,864],{"class":65},"> = ",[51,866,542],{"class":76},[51,868,191],{"class":57},[51,870,507],{"class":76},[51,872,873],{"class":57}," ?",[51,875,336],{"class":76},[51,877,203],{"class":57},[51,879,880],{"class":76}," never\n",[51,882,883,885,887,889,892,894,896,898,900,903],{"class":53,"line":118},[51,884,180],{"class":57},[51,886,183],{"class":76},[51,888,186],{"class":65},[51,890,891],{"class":76},"ExtractString",[51,893,539],{"class":65},[51,895,77],{"class":76},[51,897,80],{"class":65},[51,899,83],{"class":76},[51,901,902],{"class":65},"> ",[51,904,905],{"class":144},"\u002F\u002F string\n",[51,907,908],{"class":53,"line":138},[51,909,450],{"emptyLinePlaceholder":449},[51,911,912],{"class":53,"line":148},[51,913,914],{"class":144},"\u002F\u002F 类型分发，当 T 是联合类型时，条件类型会分配给每个子类型\n",[51,916,917,919,922,924,926,928,930,932,934,936,938,940,943],{"class":53,"line":441},[51,918,180],{"class":57},[51,920,921],{"class":76}," ToArray",[51,923,539],{"class":65},[51,925,542],{"class":76},[51,927,864],{"class":65},[51,929,542],{"class":76},[51,931,191],{"class":57},[51,933,474],{"class":76},[51,935,873],{"class":57},[51,937,336],{"class":76},[51,939,477],{"class":65},[51,941,942],{"class":57},":",[51,944,880],{"class":76},[51,946,947,949,951,953,956,958,960,962,964,966],{"class":53,"line":446},[51,948,180],{"class":57},[51,950,216],{"class":76},[51,952,186],{"class":65},[51,954,955],{"class":76},"ToArray",[51,957,539],{"class":65},[51,959,77],{"class":76},[51,961,80],{"class":65},[51,963,83],{"class":76},[51,965,902],{"class":65},[51,967,968],{"class":144},"\u002F\u002F string[] | number[]\n",[51,970,971],{"class":53,"line":453},[51,972,973],{"class":144},"\u002F\u002F 防止分发，将 T 包裹在元组中，避免条件类型分配给每个子类型\n",[51,975,976,978,981,983,985,988,990,992,994,996,999,1001,1003,1005,1007,1009],{"class":53,"line":617},[51,977,180],{"class":57},[51,979,980],{"class":76}," ToArrayNonDist",[51,982,539],{"class":65},[51,984,542],{"class":76},[51,986,987],{"class":65},"> = [",[51,989,542],{"class":76},[51,991,497],{"class":65},[51,993,247],{"class":57},[51,995,487],{"class":65},[51,997,998],{"class":76},"any",[51,1000,497],{"class":65},[51,1002,197],{"class":57},[51,1004,336],{"class":76},[51,1006,477],{"class":65},[51,1008,942],{"class":57},[51,1010,880],{"class":76},[51,1012,1013,1015,1017,1019,1022,1024,1026,1028,1030,1032],{"class":53,"line":629},[51,1014,180],{"class":57},[51,1016,241],{"class":76},[51,1018,186],{"class":65},[51,1020,1021],{"class":76},"ToArrayNonDist",[51,1023,539],{"class":65},[51,1025,77],{"class":76},[51,1027,80],{"class":65},[51,1029,83],{"class":76},[51,1031,902],{"class":65},[51,1033,1034],{"class":144},"\u002F\u002F (string | number)[]\n",[25,1036,1038,1039],{"id":1037},"类型推断infer","类型推断",[29,1040,1041],{},"infer",[10,1043,1044,1045,1047,1048,1050,1051,1053,1054,1056],{},"如果说",[29,1046,247],{},"实现了类型系统中的条件分支，那么",[29,1049,1041],{},"就相当于结构赋值。",[29,1052,1041],{},"只能在",[29,1055,247],{},"的字句中使用，它声明一个待推断的变量类型，并让类型系统自动推导出具体的类型赋给它。",[10,1058,1059,1061,1062,1065,1066,1069,1070,377],{},[29,1060,1041],{},"常用于",[159,1063,1064],{},"提取函数、数组、Promise 等复杂类型的内部组成部分","，",[159,1067,1068],{},"实现模式匹配","（如取得函数返回值类型或取得元组的首个元素的类型）和实现类型系统中的",[159,1071,1072],{},"递归解构",[42,1074,1076],{"className":44,"code":1075,"language":46,"meta":47,"style":47},"\u002F\u002F 推断函数 T 返回值的类型是 R，从而提取 R 的类型\ntype ReturnType\u003CT> = T extends (...args: any[]) => infer R ? R : never\n\u002F\u002F 推断数组 T 中的第一个元素的类型是 R，从而提取 R 的类型\ntype First\u003CT> = T extends (infer R)[] ? R : never\n\u002F\u002F 推断 T 的类型是返回 R 的 Promise，从而提取 R 的类型\ntype UnwrapPromise\u003CT> = T extends Promise\u003Cinfer R> ? R : never\n\u002F\u002F 推断数组 T 的类型是 R 的数组，如果是则递归进行展开\ntype DeepFlatten\u003CT> = T extends (infer R)[] ? DeepFlatten\u003CR> : T\n",[29,1077,1078,1083,1131,1136,1169,1174,1209,1214],{"__ignoreMap":47},[51,1079,1080],{"class":53,"line":54},[51,1081,1082],{"class":144},"\u002F\u002F 推断函数 T 返回值的类型是 R，从而提取 R 的类型\n",[51,1084,1085,1087,1090,1092,1094,1096,1098,1100,1103,1106,1108,1110,1113,1116,1119,1122,1124,1127,1129],{"class":53,"line":89},[51,1086,180],{"class":57},[51,1088,1089],{"class":76}," ReturnType",[51,1091,539],{"class":65},[51,1093,542],{"class":76},[51,1095,864],{"class":65},[51,1097,542],{"class":76},[51,1099,191],{"class":57},[51,1101,1102],{"class":65}," (...",[51,1104,1105],{"class":69},"args",[51,1107,73],{"class":65},[51,1109,998],{"class":76},[51,1111,1112],{"class":65},"[]) ",[51,1114,1115],{"class":57},"=>",[51,1117,1118],{"class":65}," infer ",[51,1120,1121],{"class":76},"R",[51,1123,873],{"class":57},[51,1125,1126],{"class":76}," R",[51,1128,203],{"class":57},[51,1130,880],{"class":76},[51,1132,1133],{"class":53,"line":118},[51,1134,1135],{"class":144},"\u002F\u002F 推断数组 T 中的第一个元素的类型是 R，从而提取 R 的类型\n",[51,1137,1138,1140,1143,1145,1147,1149,1151,1153,1156,1158,1161,1163,1165,1167],{"class":53,"line":138},[51,1139,180],{"class":57},[51,1141,1142],{"class":76}," First",[51,1144,539],{"class":65},[51,1146,542],{"class":76},[51,1148,864],{"class":65},[51,1150,542],{"class":76},[51,1152,191],{"class":57},[51,1154,1155],{"class":65}," (infer ",[51,1157,1121],{"class":76},[51,1159,1160],{"class":65},")[] ",[51,1162,197],{"class":57},[51,1164,1126],{"class":76},[51,1166,203],{"class":57},[51,1168,880],{"class":76},[51,1170,1171],{"class":53,"line":148},[51,1172,1173],{"class":144},"\u002F\u002F 推断 T 的类型是返回 R 的 Promise，从而提取 R 的类型\n",[51,1175,1176,1178,1181,1183,1185,1187,1189,1191,1194,1197,1199,1201,1203,1205,1207],{"class":53,"line":441},[51,1177,180],{"class":57},[51,1179,1180],{"class":76}," UnwrapPromise",[51,1182,539],{"class":65},[51,1184,542],{"class":76},[51,1186,864],{"class":65},[51,1188,542],{"class":76},[51,1190,191],{"class":57},[51,1192,1193],{"class":76}," Promise",[51,1195,1196],{"class":65},"\u003Cinfer ",[51,1198,1121],{"class":76},[51,1200,902],{"class":65},[51,1202,197],{"class":57},[51,1204,1126],{"class":76},[51,1206,203],{"class":57},[51,1208,880],{"class":76},[51,1210,1211],{"class":53,"line":446},[51,1212,1213],{"class":144},"\u002F\u002F 推断数组 T 的类型是 R 的数组，如果是则递归进行展开\n",[51,1215,1216,1218,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":53,"line":453},[51,1217,180],{"class":57},[51,1219,1220],{"class":76}," DeepFlatten",[51,1222,539],{"class":65},[51,1224,542],{"class":76},[51,1226,864],{"class":65},[51,1228,542],{"class":76},[51,1230,191],{"class":57},[51,1232,1155],{"class":65},[51,1234,1121],{"class":76},[51,1236,1160],{"class":65},[51,1238,197],{"class":57},[51,1240,1220],{"class":76},[51,1242,539],{"class":65},[51,1244,1121],{"class":76},[51,1246,902],{"class":65},[51,1248,942],{"class":57},[51,1250,1251],{"class":76}," T\n",[10,1253,1254,1255,1257],{},"此外，一个语句中可以包含多次",[29,1256,1041],{},"，用于分别提取多个类型。",[42,1259,1261],{"className":44,"code":1260,"language":46,"meta":47,"style":47},"\u002F\u002F 推断函数 T 的参数类型是 R，返回值类型是 U，从而提取 R 和 U 的类型到一个元组中\ntype ExtractToTuple\u003CT> = T extends (arg: infer R) => infer U ? [R, U] : never\n\ntype FuncParseInt = (arg: string) => number\ntype Result = ExtractToTuple\u003CFuncParseInt>  \u002F\u002F [string, number]\ntype FuncToString = (arg: boolean | number) => string\ntype Result2 = ExtractToTuple\u003CFuncToString>  \u002F\u002F [boolean | number, string]\n",[29,1262,1263,1268,1320,1324,1347,1370,1397],{"__ignoreMap":47},[51,1264,1265],{"class":53,"line":54},[51,1266,1267],{"class":144},"\u002F\u002F 推断函数 T 的参数类型是 R，返回值类型是 U，从而提取 R 和 U 的类型到一个元组中\n",[51,1269,1270,1272,1275,1277,1279,1281,1283,1285,1287,1290,1293,1295,1297,1299,1301,1304,1306,1308,1310,1312,1314,1316,1318],{"class":53,"line":89},[51,1271,180],{"class":57},[51,1273,1274],{"class":76}," ExtractToTuple",[51,1276,539],{"class":65},[51,1278,542],{"class":76},[51,1280,864],{"class":65},[51,1282,542],{"class":76},[51,1284,191],{"class":57},[51,1286,95],{"class":65},[51,1288,1289],{"class":69},"arg",[51,1291,1292],{"class":65},": infer ",[51,1294,1121],{"class":76},[51,1296,112],{"class":65},[51,1298,1115],{"class":57},[51,1300,1118],{"class":65},[51,1302,1303],{"class":76},"U",[51,1305,873],{"class":57},[51,1307,487],{"class":65},[51,1309,1121],{"class":76},[51,1311,492],{"class":65},[51,1313,1303],{"class":76},[51,1315,497],{"class":65},[51,1317,942],{"class":57},[51,1319,880],{"class":76},[51,1321,1322],{"class":53,"line":118},[51,1323,450],{"emptyLinePlaceholder":449},[51,1325,1326,1328,1331,1334,1336,1338,1340,1342,1344],{"class":53,"line":138},[51,1327,180],{"class":57},[51,1329,1330],{"class":76}," FuncParseInt",[51,1332,1333],{"class":65}," = (",[51,1335,1289],{"class":69},[51,1337,73],{"class":65},[51,1339,77],{"class":76},[51,1341,112],{"class":65},[51,1343,1115],{"class":57},[51,1345,1346],{"class":76}," number\n",[51,1348,1349,1351,1354,1356,1359,1361,1364,1367],{"class":53,"line":148},[51,1350,180],{"class":57},[51,1352,1353],{"class":76}," Result",[51,1355,186],{"class":65},[51,1357,1358],{"class":76},"ExtractToTuple",[51,1360,539],{"class":65},[51,1362,1363],{"class":76},"FuncParseInt",[51,1365,1366],{"class":65},">  ",[51,1368,1369],{"class":144},"\u002F\u002F [string, number]\n",[51,1371,1372,1374,1377,1379,1381,1383,1386,1388,1390,1392,1394],{"class":53,"line":441},[51,1373,180],{"class":57},[51,1375,1376],{"class":76}," FuncToString",[51,1378,1333],{"class":65},[51,1380,1289],{"class":69},[51,1382,73],{"class":65},[51,1384,1385],{"class":76},"boolean",[51,1387,80],{"class":65},[51,1389,83],{"class":76},[51,1391,112],{"class":65},[51,1393,1115],{"class":57},[51,1395,1396],{"class":76}," string\n",[51,1398,1399,1401,1404,1406,1408,1410,1413,1415],{"class":53,"line":446},[51,1400,180],{"class":57},[51,1402,1403],{"class":76}," Result2",[51,1405,186],{"class":65},[51,1407,1358],{"class":76},[51,1409,539],{"class":65},[51,1411,1412],{"class":76},"FuncToString",[51,1414,1366],{"class":65},[51,1416,1417],{"class":144},"\u002F\u002F [boolean | number, string]\n",[25,1419,1420],{"id":1420},"映射类型",[10,1422,1423,1424,377],{},"映射类型通过遍历键的联合类型，来生成新的类型，常用于",[159,1425,1426],{},"对于已有的属性进行批量转换",[10,1428,1429],{},"例如，批量修改属性：",[42,1431,1433],{"className":44,"code":1432,"language":46,"meta":47,"style":47},"type Readonly\u003CT> = {\n  readonly [P in keyof T]: T[P]\n}\ntype Partial\u003CT> = {\n  [P in keyof T]?: T[P]\n}\ntype Required\u003CT> = {\n  [P in keyof T]-?: T[P]\n}\n",[29,1434,1435,1449,1478,1482,1495,1522,1526,1539,1566],{"__ignoreMap":47},[51,1436,1437,1439,1442,1444,1446],{"class":53,"line":54},[51,1438,180],{"class":57},[51,1440,1441],{"class":76}," Readonly",[51,1443,539],{"class":65},[51,1445,542],{"class":76},[51,1447,1448],{"class":65},"> = {\n",[51,1450,1451,1453,1455,1458,1461,1464,1466,1468,1470,1473,1475],{"class":53,"line":89},[51,1452,398],{"class":57},[51,1454,487],{"class":65},[51,1456,1457],{"class":76},"P",[51,1459,1460],{"class":57}," in",[51,1462,1463],{"class":57}," keyof",[51,1465,336],{"class":76},[51,1467,560],{"class":65},[51,1469,542],{"class":76},[51,1471,1472],{"class":65},"[",[51,1474,1457],{"class":76},[51,1476,1477],{"class":65},"]\n",[51,1479,1480],{"class":53,"line":118},[51,1481,151],{"class":65},[51,1483,1484,1486,1489,1491,1493],{"class":53,"line":138},[51,1485,180],{"class":57},[51,1487,1488],{"class":76}," Partial",[51,1490,539],{"class":65},[51,1492,542],{"class":76},[51,1494,1448],{"class":65},[51,1496,1497,1499,1501,1503,1505,1507,1510,1512,1514,1516,1518,1520],{"class":53,"line":148},[51,1498,550],{"class":65},[51,1500,1457],{"class":76},[51,1502,1460],{"class":57},[51,1504,1463],{"class":57},[51,1506,336],{"class":76},[51,1508,1509],{"class":65},"]",[51,1511,197],{"class":57},[51,1513,73],{"class":65},[51,1515,542],{"class":76},[51,1517,1472],{"class":65},[51,1519,1457],{"class":76},[51,1521,1477],{"class":65},[51,1523,1524],{"class":53,"line":441},[51,1525,151],{"class":65},[51,1527,1528,1530,1533,1535,1537],{"class":53,"line":446},[51,1529,180],{"class":57},[51,1531,1532],{"class":76}," Required",[51,1534,539],{"class":65},[51,1536,542],{"class":76},[51,1538,1448],{"class":65},[51,1540,1541,1543,1545,1547,1549,1551,1554,1556,1558,1560,1562,1564],{"class":53,"line":453},[51,1542,550],{"class":65},[51,1544,1457],{"class":76},[51,1546,1460],{"class":57},[51,1548,1463],{"class":57},[51,1550,336],{"class":76},[51,1552,1553],{"class":65},"]-",[51,1555,197],{"class":57},[51,1557,73],{"class":65},[51,1559,542],{"class":76},[51,1561,1472],{"class":65},[51,1563,1457],{"class":76},[51,1565,1477],{"class":65},[51,1567,1568],{"class":53,"line":617},[51,1569,151],{"class":65},[10,1571,1572,1573,1576,1577,1579],{},"或者，结合",[29,1574,1575],{},"as","子句，可以根据原有键名和属性值决定新键名，甚至返回",[29,1578,31],{},"来删除属性。",[42,1581,1583],{"className":44,"code":1582,"language":46,"meta":47,"style":47},"type Getters\u003CT> = {\n  [P in keyof T as `get${Capitalize\u003Cstring & P>}`]: () => T[P]\n}\ntype Person = { name: string; age: number }\ntype PersonGetters = Getters\u003CPerson>\n\u002F\u002F { getName: () => string; getAge: () => number }\n\ntype Filter\u003CT> = {\n  [K in keyof T as T[K] extends string ? K : never]: T[K]\n}\ntype Filtered = Filter\u003CPerson>\n\u002F\u002F { name: string }\n",[29,1584,1585,1598,1652,1656,1684,1704,1709,1713,1726,1773,1777,1795],{"__ignoreMap":47},[51,1586,1587,1589,1592,1594,1596],{"class":53,"line":54},[51,1588,180],{"class":57},[51,1590,1591],{"class":76}," Getters",[51,1593,539],{"class":65},[51,1595,542],{"class":76},[51,1597,1448],{"class":65},[51,1599,1600,1602,1604,1606,1608,1610,1613,1616,1619,1622,1624,1626,1628,1630,1633,1636,1639,1642,1644,1646,1648,1650],{"class":53,"line":89},[51,1601,550],{"class":65},[51,1603,1457],{"class":76},[51,1605,1460],{"class":57},[51,1607,1463],{"class":57},[51,1609,336],{"class":76},[51,1611,1612],{"class":57}," as",[51,1614,1615],{"class":108}," `get",[51,1617,1618],{"class":57},"${",[51,1620,1621],{"class":76},"Capitalize",[51,1623,539],{"class":65},[51,1625,77],{"class":76},[51,1627,775],{"class":65},[51,1629,1457],{"class":76},[51,1631,1632],{"class":65},">",[51,1634,1635],{"class":57},"}",[51,1637,1638],{"class":108},"`",[51,1640,1641],{"class":65},"]: () ",[51,1643,1115],{"class":57},[51,1645,336],{"class":76},[51,1647,1472],{"class":65},[51,1649,1457],{"class":76},[51,1651,1477],{"class":65},[51,1653,1654],{"class":53,"line":118},[51,1655,151],{"class":65},[51,1657,1658,1660,1663,1665,1668,1670,1672,1675,1678,1680,1682],{"class":53,"line":138},[51,1659,180],{"class":57},[51,1661,1662],{"class":76}," Person",[51,1664,725],{"class":65},[51,1666,1667],{"class":101},"name",[51,1669,73],{"class":65},[51,1671,77],{"class":76},[51,1673,1674],{"class":65},"; ",[51,1676,1677],{"class":101},"age",[51,1679,73],{"class":65},[51,1681,83],{"class":76},[51,1683,735],{"class":65},[51,1685,1686,1688,1691,1693,1696,1698,1701],{"class":53,"line":148},[51,1687,180],{"class":57},[51,1689,1690],{"class":76}," PersonGetters",[51,1692,186],{"class":65},[51,1694,1695],{"class":76},"Getters",[51,1697,539],{"class":65},[51,1699,1700],{"class":76},"Person",[51,1702,1703],{"class":65},">\n",[51,1705,1706],{"class":53,"line":441},[51,1707,1708],{"class":144},"\u002F\u002F { getName: () => string; getAge: () => number }\n",[51,1710,1711],{"class":53,"line":446},[51,1712,450],{"emptyLinePlaceholder":449},[51,1714,1715,1717,1720,1722,1724],{"class":53,"line":453},[51,1716,180],{"class":57},[51,1718,1719],{"class":76}," Filter",[51,1721,539],{"class":65},[51,1723,542],{"class":76},[51,1725,1448],{"class":65},[51,1727,1728,1730,1733,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753,1755,1758,1760,1763,1765,1767,1769,1771],{"class":53,"line":617},[51,1729,550],{"class":65},[51,1731,1732],{"class":76},"K",[51,1734,1460],{"class":57},[51,1736,1463],{"class":57},[51,1738,336],{"class":76},[51,1740,1612],{"class":57},[51,1742,336],{"class":76},[51,1744,1472],{"class":65},[51,1746,1732],{"class":76},[51,1748,497],{"class":65},[51,1750,247],{"class":57},[51,1752,507],{"class":76},[51,1754,873],{"class":57},[51,1756,1757],{"class":76}," K",[51,1759,203],{"class":57},[51,1761,1762],{"class":76}," never",[51,1764,560],{"class":65},[51,1766,542],{"class":76},[51,1768,1472],{"class":65},[51,1770,1732],{"class":76},[51,1772,1477],{"class":65},[51,1774,1775],{"class":53,"line":629},[51,1776,151],{"class":65},[51,1778,1779,1781,1784,1786,1789,1791,1793],{"class":53,"line":649},[51,1780,180],{"class":57},[51,1782,1783],{"class":76}," Filtered",[51,1785,186],{"class":65},[51,1787,1788],{"class":76},"Filter",[51,1790,539],{"class":65},[51,1792,1700],{"class":76},[51,1794,1703],{"class":65},[51,1796,1797],{"class":53,"line":679},[51,1798,1799],{"class":144},"\u002F\u002F { name: string }\n",[25,1801,1802],{"id":1802},"元组类型",[10,1804,1805,1806,1809],{},"元组类型（Tuple）是数组类型的一种特化形式，它表示一个固定长度且每个位置元素类型已知的数组。与普通数组",[29,1807,1808],{},"T[]","不同，元组类型明确规定了每一索引处的类型，以及整体的长度。",[42,1811,1813],{"className":44,"code":1812,"language":46,"meta":47,"style":47},"\u002F\u002F 普通数组：长度可变，所有元素类型相同\nconst arr: number[] = [1, 2, 3]\n\n\u002F\u002F 元组：长度固定，每个位置类型可不同\nconst tuple: [string, number, boolean] = ['hello', 42, true]\n",[29,1814,1815,1820,1850,1854,1859],{"__ignoreMap":47},[51,1816,1817],{"class":53,"line":54},[51,1818,1819],{"class":144},"\u002F\u002F 普通数组：长度可变，所有元素类型相同\n",[51,1821,1822,1825,1828,1830,1832,1835,1838,1840,1843,1845,1848],{"class":53,"line":89},[51,1823,1824],{"class":57},"const",[51,1826,1827],{"class":76}," arr",[51,1829,73],{"class":65},[51,1831,83],{"class":76},[51,1833,1834],{"class":65},"[] = [",[51,1836,1837],{"class":108},"1",[51,1839,492],{"class":65},[51,1841,1842],{"class":108},"2",[51,1844,492],{"class":65},[51,1846,1847],{"class":108},"3",[51,1849,1477],{"class":65},[51,1851,1852],{"class":53,"line":118},[51,1853,450],{"emptyLinePlaceholder":449},[51,1855,1856],{"class":53,"line":138},[51,1857,1858],{"class":144},"\u002F\u002F 元组：长度固定，每个位置类型可不同\n",[51,1860,1861,1863,1866,1869,1871,1873,1875,1877,1879,1882,1885,1887,1890,1892,1895],{"class":53,"line":148},[51,1862,1824],{"class":57},[51,1864,1865],{"class":76}," tuple",[51,1867,1868],{"class":65},": [",[51,1870,77],{"class":76},[51,1872,492],{"class":65},[51,1874,83],{"class":76},[51,1876,492],{"class":65},[51,1878,1385],{"class":76},[51,1880,1881],{"class":65},"] = [",[51,1883,1884],{"class":108},"'hello'",[51,1886,492],{"class":65},[51,1888,1889],{"class":108},"42",[51,1891,492],{"class":65},[51,1893,1894],{"class":108},"true",[51,1896,1477],{"class":65},[10,1898,1899,1900,1903,1904,1907,1908,1911],{},"元组在 TypeScript 类型系统中扮演着“定长列表”的角色，是许多高级类型操作的基石。元组常用来表示坐标",[29,1901,1902],{},"[x, y]","、颜色",[29,1905,1906],{},"[r, g, b]","等结构固定的数据，且可以与下面的",[29,1909,1910],{},"as const","结合获得精确字面量类型。",[10,1913,1914,1915,1918],{},"与数组类型一样，通过",[29,1916,1917],{},"T[number]","可以获取元组中每个位置的类型，获取到的往往是一个联合类型。",[42,1920,1922],{"className":44,"code":1921,"language":46,"meta":47,"style":47},"const tuple = ['hello', 42, true]\ntype TupleElement = (typeof tuple)[number] \u002F\u002F Type: string | number | boolean\nconst tuple = ['hello', 42, true] as const\ntype TupleElement = (typeof tuple)[number] \u002F\u002F Type: \"hello\" | 42 | true\n",[29,1923,1924,1945,1968,1993],{"__ignoreMap":47},[51,1925,1926,1928,1930,1933,1935,1937,1939,1941,1943],{"class":53,"line":54},[51,1927,1824],{"class":57},[51,1929,1865],{"class":76},[51,1931,1932],{"class":65}," = [",[51,1934,1884],{"class":108},[51,1936,492],{"class":65},[51,1938,1889],{"class":108},[51,1940,492],{"class":65},[51,1942,1894],{"class":108},[51,1944,1477],{"class":65},[51,1946,1947,1949,1952,1954,1956,1958,1961,1963,1965],{"class":53,"line":89},[51,1948,180],{"class":57},[51,1950,1951],{"class":76}," TupleElement",[51,1953,1333],{"class":65},[51,1955,98],{"class":57},[51,1957,1865],{"class":101},[51,1959,1960],{"class":65},")[",[51,1962,83],{"class":76},[51,1964,497],{"class":65},[51,1966,1967],{"class":144},"\u002F\u002F Type: string | number | boolean\n",[51,1969,1970,1972,1974,1976,1978,1980,1982,1984,1986,1988,1990],{"class":53,"line":118},[51,1971,1824],{"class":57},[51,1973,1865],{"class":76},[51,1975,1932],{"class":65},[51,1977,1884],{"class":108},[51,1979,492],{"class":65},[51,1981,1889],{"class":108},[51,1983,492],{"class":65},[51,1985,1894],{"class":108},[51,1987,497],{"class":65},[51,1989,1575],{"class":57},[51,1991,1992],{"class":57}," const\n",[51,1994,1995,1997,1999,2001,2003,2005,2007,2009,2011],{"class":53,"line":138},[51,1996,180],{"class":57},[51,1998,1951],{"class":76},[51,2000,1333],{"class":65},[51,2002,98],{"class":57},[51,2004,1865],{"class":101},[51,2006,1960],{"class":65},[51,2008,83],{"class":76},[51,2010,497],{"class":65},[51,2012,2013],{"class":144},"\u002F\u002F Type: \"hello\" | 42 | true\n",[25,2015,2017,32,2019],{"id":2016},"as与as-const",[29,2018,1575],{},[29,2020,1910],{},[10,2022,2023,2024,2026],{},"前文使用到",[29,2025,1575],{},"子句。除了常见的断言类型之外，还能用于改变映射类型中的键。",[10,2028,2029,2031,2032,2035,2036,377],{},[29,2030,1910],{},"是一个特别的语句，可以用来将类型推断",[159,2033,2034],{},"缩窄到最明确的类型","；当用在对象或数组上时，可以",[159,2037,2038],{},"防止对象的属性或数组被修改",[42,2040,2042],{"className":44,"code":2041,"language":46,"meta":47,"style":47},"const status = \"success\" \u002F\u002F Type: string\nconst literalStatus = \"success\" as const \u002F\u002F Type: \"success\"\n\nconst routes = ['home', 'about', 'contact'] as const\ntype Routes = (typeof routes)[number] \u002F\u002F Type: \"home\" | \"about\" | \"contact\"\nroutes.push('blog') \u002F\u002F Error\n\nconst person = { name: 'Owen', age: 30 } as const\ntype Person = typeof person \u002F\u002F Type: { name: \"Owen\"; age: 30 }\nperson.name = 'John' \u002F\u002F Error\n",[29,2043,2044,2059,2078,2082,2110,2132,2153,2157,2189,2204],{"__ignoreMap":47},[51,2045,2046,2048,2051,2053,2056],{"class":53,"line":54},[51,2047,1824],{"class":57},[51,2049,2050],{"class":76}," status",[51,2052,186],{"class":65},[51,2054,2055],{"class":108},"\"success\"",[51,2057,2058],{"class":144}," \u002F\u002F Type: string\n",[51,2060,2061,2063,2066,2068,2070,2072,2075],{"class":53,"line":89},[51,2062,1824],{"class":57},[51,2064,2065],{"class":76}," literalStatus",[51,2067,186],{"class":65},[51,2069,2055],{"class":108},[51,2071,1612],{"class":57},[51,2073,2074],{"class":76}," const",[51,2076,2077],{"class":144}," \u002F\u002F Type: \"success\"\n",[51,2079,2080],{"class":53,"line":118},[51,2081,450],{"emptyLinePlaceholder":449},[51,2083,2084,2086,2089,2091,2094,2096,2099,2101,2104,2106,2108],{"class":53,"line":138},[51,2085,1824],{"class":57},[51,2087,2088],{"class":76}," routes",[51,2090,1932],{"class":65},[51,2092,2093],{"class":108},"'home'",[51,2095,492],{"class":65},[51,2097,2098],{"class":108},"'about'",[51,2100,492],{"class":65},[51,2102,2103],{"class":108},"'contact'",[51,2105,497],{"class":65},[51,2107,1575],{"class":57},[51,2109,1992],{"class":57},[51,2111,2112,2114,2117,2119,2121,2123,2125,2127,2129],{"class":53,"line":148},[51,2113,180],{"class":57},[51,2115,2116],{"class":76}," Routes",[51,2118,1333],{"class":65},[51,2120,98],{"class":57},[51,2122,2088],{"class":101},[51,2124,1960],{"class":65},[51,2126,83],{"class":76},[51,2128,497],{"class":65},[51,2130,2131],{"class":144},"\u002F\u002F Type: \"home\" | \"about\" | \"contact\"\n",[51,2133,2134,2137,2140,2143,2145,2148,2150],{"class":53,"line":441},[51,2135,2136],{"class":108},"routes",[51,2138,2139],{"class":65},".",[51,2141,2142],{"class":76},"push",[51,2144,66],{"class":65},[51,2146,2147],{"class":108},"'blog'",[51,2149,112],{"class":65},[51,2151,2152],{"class":144},"\u002F\u002F Error\n",[51,2154,2155],{"class":53,"line":446},[51,2156,450],{"emptyLinePlaceholder":449},[51,2158,2159,2161,2164,2166,2168,2170,2173,2175,2177,2179,2182,2185,2187],{"class":53,"line":453},[51,2160,1824],{"class":57},[51,2162,2163],{"class":76}," person",[51,2165,725],{"class":65},[51,2167,1667],{"class":101},[51,2169,73],{"class":65},[51,2171,2172],{"class":108},"'Owen'",[51,2174,492],{"class":65},[51,2176,1677],{"class":101},[51,2178,73],{"class":65},[51,2180,2181],{"class":108},"30",[51,2183,2184],{"class":65}," } ",[51,2186,1575],{"class":57},[51,2188,1992],{"class":57},[51,2190,2191,2193,2195,2197,2199,2201],{"class":53,"line":617},[51,2192,180],{"class":57},[51,2194,1662],{"class":76},[51,2196,186],{"class":65},[51,2198,98],{"class":57},[51,2200,2163],{"class":101},[51,2202,2203],{"class":144}," \u002F\u002F Type: { name: \"Owen\"; age: 30 }\n",[51,2205,2206,2209,2211,2213,2215,2218],{"class":53,"line":629},[51,2207,2208],{"class":108},"person",[51,2210,2139],{"class":65},[51,2212,1667],{"class":76},[51,2214,186],{"class":65},[51,2216,2217],{"class":108},"'John'",[51,2219,2220],{"class":144}," \u002F\u002F Error\n",[10,2222,2223,2224,2227],{},"常见的使用方式是在项目中替代",[29,2225,2226],{},"enum","，例如：",[42,2229,2231],{"className":44,"code":2230,"language":46,"meta":47,"style":47},"const status = {\n  SUCCESS: 'success',\n  FAIL: 'fail',\n} as const\n\ntype Status = typeof status[keyof typeof status]  \u002F\u002F Type: \"success\" | \"fail\"\n",[29,2232,2233,2241,2254,2266,2275,2279],{"__ignoreMap":47},[51,2234,2235,2237,2239],{"class":53,"line":54},[51,2236,1824],{"class":57},[51,2238,2050],{"class":76},[51,2240,339],{"class":65},[51,2242,2243,2246,2248,2251],{"class":53,"line":89},[51,2244,2245],{"class":101},"  SUCCESS",[51,2247,73],{"class":65},[51,2249,2250],{"class":108},"'success'",[51,2252,2253],{"class":65},",\n",[51,2255,2256,2259,2261,2264],{"class":53,"line":118},[51,2257,2258],{"class":101},"  FAIL",[51,2260,73],{"class":65},[51,2262,2263],{"class":108},"'fail'",[51,2265,2253],{"class":65},[51,2267,2268,2271,2273],{"class":53,"line":138},[51,2269,2270],{"class":65},"} ",[51,2272,1575],{"class":57},[51,2274,1992],{"class":57},[51,2276,2277],{"class":53,"line":148},[51,2278,450],{"emptyLinePlaceholder":449},[51,2280,2281,2283,2286,2288,2290,2292,2294,2296,2299,2301,2304],{"class":53,"line":441},[51,2282,180],{"class":57},[51,2284,2285],{"class":76}," Status",[51,2287,186],{"class":65},[51,2289,98],{"class":57},[51,2291,2050],{"class":101},[51,2293,1472],{"class":65},[51,2295,318],{"class":101},[51,2297,2298],{"class":57}," typeof",[51,2300,2050],{"class":101},[51,2302,2303],{"class":65},"]  ",[51,2305,2306],{"class":144},"\u002F\u002F Type: \"success\" | \"fail\"\n",[10,2308,2309],{},"另一个常见的应用场景是在需要从值中提取类型，例如：",[42,2311,2313],{"className":44,"code":2312,"language":46,"meta":47,"style":47},"const routes = [\n  { path: '\u002Fuser\u002F:id' },\n  { path: '\u002Fbiz\u002F:app' }\n] as const\n\ntype Route = typeof routes[number]\ntype Path = Route['path']\n\ntype ExtractParam\u003CT> =\n  T extends `${string}:${infer P}` ? P : never\n\ntype Param = ExtractParam\u003CPath> \u002F\u002F Type: \"id\" | \"app\"\n\n\u002F\u002F 在函数中，相对于使用`string`，使用更窄的`Param`类型\nfunction parseParam(param: Param) {\n  \u002F\u002F ...\n}\n",[29,2314,2315,2324,2340,2353,2361,2365,2384,2403,2407,2421,2459,2463,2485,2489,2495,2515,2521],{"__ignoreMap":47},[51,2316,2317,2319,2321],{"class":53,"line":54},[51,2318,1824],{"class":57},[51,2320,2088],{"class":76},[51,2322,2323],{"class":65}," = [\n",[51,2325,2326,2329,2332,2334,2337],{"class":53,"line":89},[51,2327,2328],{"class":65},"  { ",[51,2330,2331],{"class":101},"path",[51,2333,73],{"class":65},[51,2335,2336],{"class":108},"'\u002Fuser\u002F:id'",[51,2338,2339],{"class":65}," },\n",[51,2341,2342,2344,2346,2348,2351],{"class":53,"line":118},[51,2343,2328],{"class":65},[51,2345,2331],{"class":101},[51,2347,73],{"class":65},[51,2349,2350],{"class":108},"'\u002Fbiz\u002F:app'",[51,2352,735],{"class":65},[51,2354,2355,2357,2359],{"class":53,"line":138},[51,2356,497],{"class":65},[51,2358,1575],{"class":57},[51,2360,1992],{"class":57},[51,2362,2363],{"class":53,"line":148},[51,2364,450],{"emptyLinePlaceholder":449},[51,2366,2367,2369,2372,2374,2376,2378,2380,2382],{"class":53,"line":441},[51,2368,180],{"class":57},[51,2370,2371],{"class":76}," Route",[51,2373,186],{"class":65},[51,2375,98],{"class":57},[51,2377,2088],{"class":101},[51,2379,1472],{"class":65},[51,2381,83],{"class":101},[51,2383,1477],{"class":65},[51,2385,2386,2388,2391,2393,2396,2398,2401],{"class":53,"line":446},[51,2387,180],{"class":57},[51,2389,2390],{"class":76}," Path",[51,2392,186],{"class":65},[51,2394,2395],{"class":76},"Route",[51,2397,1472],{"class":65},[51,2399,2400],{"class":108},"'path'",[51,2402,1477],{"class":65},[51,2404,2405],{"class":53,"line":453},[51,2406,450],{"emptyLinePlaceholder":449},[51,2408,2409,2411,2414,2416,2418],{"class":53,"line":617},[51,2410,180],{"class":57},[51,2412,2413],{"class":76}," ExtractParam",[51,2415,539],{"class":65},[51,2417,542],{"class":76},[51,2419,2420],{"class":65},"> =\n",[51,2422,2423,2426,2428,2431,2433,2435,2437,2439,2441,2444,2446,2448,2450,2452,2455,2457],{"class":53,"line":629},[51,2424,2425],{"class":76},"  T",[51,2427,191],{"class":57},[51,2429,2430],{"class":108}," `",[51,2432,1618],{"class":57},[51,2434,77],{"class":76},[51,2436,1635],{"class":57},[51,2438,942],{"class":108},[51,2440,1618],{"class":57},[51,2442,2443],{"class":65},"infer ",[51,2445,1457],{"class":76},[51,2447,1635],{"class":57},[51,2449,1638],{"class":108},[51,2451,873],{"class":57},[51,2453,2454],{"class":76}," P",[51,2456,203],{"class":57},[51,2458,880],{"class":76},[51,2460,2461],{"class":53,"line":649},[51,2462,450],{"emptyLinePlaceholder":449},[51,2464,2465,2467,2470,2472,2475,2477,2480,2482],{"class":53,"line":679},[51,2466,180],{"class":57},[51,2468,2469],{"class":76}," Param",[51,2471,186],{"class":65},[51,2473,2474],{"class":76},"ExtractParam",[51,2476,539],{"class":65},[51,2478,2479],{"class":76},"Path",[51,2481,902],{"class":65},[51,2483,2484],{"class":144},"\u002F\u002F Type: \"id\" | \"app\"\n",[51,2486,2487],{"class":53,"line":684},[51,2488,450],{"emptyLinePlaceholder":449},[51,2490,2492],{"class":53,"line":2491},14,[51,2493,2494],{"class":144},"\u002F\u002F 在函数中，相对于使用`string`，使用更窄的`Param`类型\n",[51,2496,2498,2500,2503,2505,2508,2510,2513],{"class":53,"line":2497},15,[51,2499,58],{"class":57},[51,2501,2502],{"class":61}," parseParam",[51,2504,66],{"class":65},[51,2506,2507],{"class":69},"param",[51,2509,73],{"class":65},[51,2511,2512],{"class":76},"Param",[51,2514,86],{"class":65},[51,2516,2518],{"class":53,"line":2517},16,[51,2519,2520],{"class":144},"  \u002F\u002F ...\n",[51,2522,2524],{"class":53,"line":2523},17,[51,2525,151],{"class":65},[25,2527,2528],{"id":2528},"模板字面量类型",[10,2530,2531],{},"模板字面量类型（Template Literal Types）允许在类型层面拼接字符串。可以通过拼接的方式实现类型分发或映射。",[42,2533,2535],{"className":44,"code":2534,"language":46,"meta":47,"style":47},"type A = `${'a' | 'b'}_${1 | 2}`\n\u002F\u002F \"a_1\" | \"a_2\" | \"b_1\" | \"b_2\"\n\ntype Person = {\n  name: string\n  age: number\n}\ntype PersonGetter = {\n  [P in keyof Person as `get${Capitalize\u003Cstring & P>}`]: () => Person[P]\n}\n\u002F\u002F { getName: () => string; getAge: () => number }\n",[29,2536,2537,2575,2580,2584,2592,2600,2608,2612,2621,2667,2671],{"__ignoreMap":47},[51,2538,2539,2541,2543,2545,2547,2549,2552,2554,2557,2559,2562,2564,2566,2568,2570,2572],{"class":53,"line":54},[51,2540,180],{"class":57},[51,2542,183],{"class":76},[51,2544,186],{"class":65},[51,2546,1638],{"class":108},[51,2548,1618],{"class":57},[51,2550,2551],{"class":108},"'a'",[51,2553,80],{"class":65},[51,2555,2556],{"class":108},"'b'",[51,2558,1635],{"class":57},[51,2560,2561],{"class":108},"_",[51,2563,1618],{"class":57},[51,2565,1837],{"class":108},[51,2567,80],{"class":65},[51,2569,1842],{"class":108},[51,2571,1635],{"class":57},[51,2573,2574],{"class":108},"`\n",[51,2576,2577],{"class":53,"line":89},[51,2578,2579],{"class":144},"\u002F\u002F \"a_1\" | \"a_2\" | \"b_1\" | \"b_2\"\n",[51,2581,2582],{"class":53,"line":118},[51,2583,450],{"emptyLinePlaceholder":449},[51,2585,2586,2588,2590],{"class":53,"line":138},[51,2587,180],{"class":57},[51,2589,1662],{"class":76},[51,2591,339],{"class":65},[51,2593,2594,2596,2598],{"class":53,"line":148},[51,2595,344],{"class":101},[51,2597,73],{"class":65},[51,2599,349],{"class":76},[51,2601,2602,2604,2606],{"class":53,"line":441},[51,2603,354],{"class":101},[51,2605,73],{"class":65},[51,2607,359],{"class":76},[51,2609,2610],{"class":53,"line":446},[51,2611,151],{"class":65},[51,2613,2614,2616,2619],{"class":53,"line":453},[51,2615,180],{"class":57},[51,2617,2618],{"class":76}," PersonGetter",[51,2620,339],{"class":65},[51,2622,2623,2625,2627,2629,2631,2633,2635,2637,2639,2641,2643,2645,2647,2649,2651,2653,2655,2657,2659,2661,2663,2665],{"class":53,"line":617},[51,2624,550],{"class":65},[51,2626,1457],{"class":76},[51,2628,1460],{"class":57},[51,2630,1463],{"class":57},[51,2632,1662],{"class":76},[51,2634,1612],{"class":57},[51,2636,1615],{"class":108},[51,2638,1618],{"class":57},[51,2640,1621],{"class":76},[51,2642,539],{"class":65},[51,2644,77],{"class":76},[51,2646,775],{"class":65},[51,2648,1457],{"class":76},[51,2650,1632],{"class":65},[51,2652,1635],{"class":57},[51,2654,1638],{"class":108},[51,2656,1641],{"class":65},[51,2658,1115],{"class":57},[51,2660,1662],{"class":76},[51,2662,1472],{"class":65},[51,2664,1457],{"class":76},[51,2666,1477],{"class":65},[51,2668,2669],{"class":53,"line":629},[51,2670,151],{"class":65},[51,2672,2673],{"class":53,"line":649},[51,2674,1708],{"class":144},[10,2676,2677,2678,2680],{},"结合内置类型和",[29,2679,1041],{},"又可分别实现字符串的转和子串的提取。",[42,2682,2684],{"className":44,"code":2683,"language":46,"meta":47,"style":47},"\u002F\u002F 结合`infer`，实现路由参数的提取\ntype ParseRoute\u003CRoute extends string> = \n  Route extends `${infer _Start}:${infer Param}\u002F${infer Rest}`\n    ? Param | ParseRoute\u003CRest>\n    : Route extends `${infer _Start}:${infer Param}`\n    ? Param\n    : never\n\ntype RouteParams = ParseRoute\u003C\"\u002Fuser\u002F:id\u002Fpost\u002F:postId\"> \u002F\u002F \"id\" | \"postId\"\n",[29,2685,2686,2691,2709,2750,2768,2799,2806,2812,2816],{"__ignoreMap":47},[51,2687,2688],{"class":53,"line":54},[51,2689,2690],{"class":144},"\u002F\u002F 结合`infer`，实现路由参数的提取\n",[51,2692,2693,2695,2698,2700,2702,2704,2706],{"class":53,"line":89},[51,2694,180],{"class":57},[51,2696,2697],{"class":76}," ParseRoute",[51,2699,539],{"class":65},[51,2701,2395],{"class":76},[51,2703,191],{"class":57},[51,2705,507],{"class":76},[51,2707,2708],{"class":65},"> = \n",[51,2710,2711,2714,2716,2718,2720,2722,2725,2727,2729,2731,2733,2735,2737,2739,2741,2743,2746,2748],{"class":53,"line":118},[51,2712,2713],{"class":76},"  Route",[51,2715,191],{"class":57},[51,2717,2430],{"class":108},[51,2719,1618],{"class":57},[51,2721,2443],{"class":65},[51,2723,2724],{"class":76},"_Start",[51,2726,1635],{"class":57},[51,2728,942],{"class":108},[51,2730,1618],{"class":57},[51,2732,2443],{"class":65},[51,2734,2512],{"class":76},[51,2736,1635],{"class":57},[51,2738,165],{"class":108},[51,2740,1618],{"class":57},[51,2742,2443],{"class":65},[51,2744,2745],{"class":76},"Rest",[51,2747,1635],{"class":57},[51,2749,2574],{"class":108},[51,2751,2752,2755,2757,2759,2762,2764,2766],{"class":53,"line":138},[51,2753,2754],{"class":57},"    ?",[51,2756,2469],{"class":76},[51,2758,80],{"class":65},[51,2760,2761],{"class":76},"ParseRoute",[51,2763,539],{"class":65},[51,2765,2745],{"class":76},[51,2767,1703],{"class":65},[51,2769,2770,2773,2775,2777,2779,2781,2783,2785,2787,2789,2791,2793,2795,2797],{"class":53,"line":148},[51,2771,2772],{"class":57},"    :",[51,2774,2371],{"class":76},[51,2776,191],{"class":57},[51,2778,2430],{"class":108},[51,2780,1618],{"class":57},[51,2782,2443],{"class":65},[51,2784,2724],{"class":76},[51,2786,1635],{"class":57},[51,2788,942],{"class":108},[51,2790,1618],{"class":57},[51,2792,2443],{"class":65},[51,2794,2512],{"class":76},[51,2796,1635],{"class":57},[51,2798,2574],{"class":108},[51,2800,2801,2803],{"class":53,"line":441},[51,2802,2754],{"class":57},[51,2804,2805],{"class":76}," Param\n",[51,2807,2808,2810],{"class":53,"line":446},[51,2809,2772],{"class":57},[51,2811,880],{"class":76},[51,2813,2814],{"class":53,"line":453},[51,2815,450],{"emptyLinePlaceholder":449},[51,2817,2818,2820,2823,2825,2827,2829,2832,2834],{"class":53,"line":617},[51,2819,180],{"class":57},[51,2821,2822],{"class":76}," RouteParams",[51,2824,186],{"class":65},[51,2826,2761],{"class":76},[51,2828,539],{"class":65},[51,2830,2831],{"class":108},"\"\u002Fuser\u002F:id\u002Fpost\u002F:postId\"",[51,2833,902],{"class":65},[51,2835,2836],{"class":144},"\u002F\u002F \"id\" | \"postId\"\n",[25,2838,2839],{"id":2839},"递归类型",[10,2841,2842],{},"在类型定义中引用自身，称为递归类型。TypeScript 的类型系统具有图灵完备性，通过递归类型可以处理嵌套结构并模拟循环和递归算法。",[10,2844,2845],{},"常见的使用递归类型的场景是需要深度操作对象的时候，例如将深层对象中的每个属性进行修改。利用递归类型也能构建复杂的条件类型。",[42,2847,2849],{"className":44,"code":2848,"language":46,"meta":47,"style":47},"\u002F\u002F 尾递归版本的字符串替换\ntype ReplaceAll\u003C\n  S extends string,\n  From extends string,\n  To extends string,\n  Result extends string = ''\n> = S extends `${infer Head}${From}${infer Tail}`\n  ? ReplaceAll\u003CTail, From, To, `${Result}${Head}${To}`>\n  : `${Result}${S}`;\n",[29,2850,2851,2856,2866,2877,2888,2899,2913,2948,2991],{"__ignoreMap":47},[51,2852,2853],{"class":53,"line":54},[51,2854,2855],{"class":144},"\u002F\u002F 尾递归版本的字符串替换\n",[51,2857,2858,2860,2863],{"class":53,"line":89},[51,2859,180],{"class":57},[51,2861,2862],{"class":76}," ReplaceAll",[51,2864,2865],{"class":65},"\u003C\n",[51,2867,2868,2871,2873,2875],{"class":53,"line":118},[51,2869,2870],{"class":76},"  S",[51,2872,191],{"class":57},[51,2874,507],{"class":76},[51,2876,2253],{"class":65},[51,2878,2879,2882,2884,2886],{"class":53,"line":138},[51,2880,2881],{"class":76},"  From",[51,2883,191],{"class":57},[51,2885,507],{"class":76},[51,2887,2253],{"class":65},[51,2889,2890,2893,2895,2897],{"class":53,"line":148},[51,2891,2892],{"class":76},"  To",[51,2894,191],{"class":57},[51,2896,507],{"class":76},[51,2898,2253],{"class":65},[51,2900,2901,2904,2906,2908,2910],{"class":53,"line":441},[51,2902,2903],{"class":76},"  Result",[51,2905,191],{"class":57},[51,2907,507],{"class":76},[51,2909,186],{"class":65},[51,2911,2912],{"class":108},"''\n",[51,2914,2915,2917,2920,2922,2924,2926,2928,2931,2934,2937,2939,2941,2944,2946],{"class":53,"line":446},[51,2916,864],{"class":65},[51,2918,2919],{"class":76},"S",[51,2921,191],{"class":57},[51,2923,2430],{"class":108},[51,2925,1618],{"class":57},[51,2927,2443],{"class":65},[51,2929,2930],{"class":76},"Head",[51,2932,2933],{"class":57},"}${",[51,2935,2936],{"class":76},"From",[51,2938,2933],{"class":57},[51,2940,2443],{"class":65},[51,2942,2943],{"class":76},"Tail",[51,2945,1635],{"class":57},[51,2947,2574],{"class":108},[51,2949,2950,2953,2955,2957,2959,2961,2963,2965,2968,2970,2972,2974,2977,2979,2981,2983,2985,2987,2989],{"class":53,"line":453},[51,2951,2952],{"class":57},"  ?",[51,2954,2862],{"class":76},[51,2956,539],{"class":65},[51,2958,2943],{"class":76},[51,2960,492],{"class":65},[51,2962,2936],{"class":76},[51,2964,492],{"class":65},[51,2966,2967],{"class":76},"To",[51,2969,492],{"class":65},[51,2971,1638],{"class":108},[51,2973,1618],{"class":57},[51,2975,2976],{"class":76},"Result",[51,2978,2933],{"class":57},[51,2980,2930],{"class":76},[51,2982,2933],{"class":57},[51,2984,2967],{"class":76},[51,2986,1635],{"class":57},[51,2988,1638],{"class":108},[51,2990,1703],{"class":65},[51,2992,2993,2996,2998,3000,3002,3004,3006,3008,3010],{"class":53,"line":617},[51,2994,2995],{"class":57},"  :",[51,2997,2430],{"class":108},[51,2999,1618],{"class":57},[51,3001,2976],{"class":76},[51,3003,2933],{"class":57},[51,3005,2919],{"class":76},[51,3007,1635],{"class":57},[51,3009,1638],{"class":108},[51,3011,3012],{"class":65},";\n",[25,3014,3015],{"id":3015},"逆变与协变",[10,3017,3018],{},"协变与逆变是类型系统中的概念和性质，并非实际的语法。逆变与协变用来描述复杂类型（如函数、数组）在子类型关系中的变化方向。简单来说：",[3020,3021,3022,3041,3054],"ul",{},[3023,3024,3025,3028,3029,806,3031,3033,3034,806,3037,3040],"li",{},[159,3026,3027],{},"协变（Covariant）","：如果",[29,3030,760],{},[29,3032,765],{},"的子类型，那么",[29,3035,3036],{},"Complex\u003CA>",[29,3038,3039],{},"Complex\u003CB>","的子类型。",[3023,3042,3043,3028,3046,806,3048,3033,3050,806,3052,3040],{},[159,3044,3045],{},"逆变（Contravariant）",[29,3047,760],{},[29,3049,765],{},[29,3051,3039],{},[29,3053,3036],{},[3023,3055,3056,3059,3060,3062,3063,3065],{},[159,3057,3058],{},"不变（Invariant）","：",[29,3061,3036],{},"和",[29,3064,3039],{},"之间没有子类型关系。",[10,3067,3068,3069,3072],{},"在 TypeScript 中，对象属性、数组、返回类型是协变的，函数参数类型是逆变的（严格模式下，",[29,3070,3071],{},"--strictFunctionTypes","启用）。",[42,3074,3076],{"className":44,"code":3075,"language":46,"meta":47,"style":47},"\u002F\u002F 协变示例：返回值类型\ntype GetNumber = () => number;\ntype GetAny = () => any;\nlet getNumber: GetNumber = () => 1;\nlet getAny: GetAny = getNumber; \u002F\u002F 安全，因为 number 是 any 的子类型\n\u002F\u002F GetNumber extends GetAny 是 true\n\n\u002F\u002F 逆变示例：参数类型\ntype HandleString = (x: string) => void;\ntype HandleUnion = (x: string | number) => void;\nlet handleUnion: HandleUnion = (x) => console.log(x);\nlet handleString: HandleString = handleUnion; \u002F\u002F 安全？在严格函数类型下是允许的\n\u002F\u002F 因为参数是逆变：string | number 是 string 的超类型，所以 HandleUnion 是 HandleString 的子类型\n\u002F\u002F HandleUnion extends HandleString 是 true\n",[29,3077,3078,3083,3100,3115,3137,3159,3164,3168,3173,3197,3224,3259,3281,3286],{"__ignoreMap":47},[51,3079,3080],{"class":53,"line":54},[51,3081,3082],{"class":144},"\u002F\u002F 协变示例：返回值类型\n",[51,3084,3085,3087,3090,3093,3095,3098],{"class":53,"line":89},[51,3086,180],{"class":57},[51,3088,3089],{"class":76}," GetNumber",[51,3091,3092],{"class":65}," = () ",[51,3094,1115],{"class":57},[51,3096,3097],{"class":76}," number",[51,3099,3012],{"class":65},[51,3101,3102,3104,3107,3109,3111,3113],{"class":53,"line":118},[51,3103,180],{"class":57},[51,3105,3106],{"class":76}," GetAny",[51,3108,3092],{"class":65},[51,3110,1115],{"class":57},[51,3112,474],{"class":76},[51,3114,3012],{"class":65},[51,3116,3117,3120,3123,3125,3128,3130,3132,3135],{"class":53,"line":138},[51,3118,3119],{"class":57},"let",[51,3121,3122],{"class":61}," getNumber",[51,3124,73],{"class":65},[51,3126,3127],{"class":76},"GetNumber",[51,3129,3092],{"class":65},[51,3131,1115],{"class":57},[51,3133,3134],{"class":108}," 1",[51,3136,3012],{"class":65},[51,3138,3139,3141,3144,3146,3149,3151,3154,3156],{"class":53,"line":148},[51,3140,3119],{"class":57},[51,3142,3143],{"class":101}," getAny",[51,3145,73],{"class":65},[51,3147,3148],{"class":76},"GetAny",[51,3150,186],{"class":65},[51,3152,3153],{"class":101},"getNumber",[51,3155,1674],{"class":65},[51,3157,3158],{"class":144},"\u002F\u002F 安全，因为 number 是 any 的子类型\n",[51,3160,3161],{"class":53,"line":441},[51,3162,3163],{"class":144},"\u002F\u002F GetNumber extends GetAny 是 true\n",[51,3165,3166],{"class":53,"line":446},[51,3167,450],{"emptyLinePlaceholder":449},[51,3169,3170],{"class":53,"line":453},[51,3171,3172],{"class":144},"\u002F\u002F 逆变示例：参数类型\n",[51,3174,3175,3177,3180,3182,3184,3186,3188,3190,3192,3195],{"class":53,"line":617},[51,3176,180],{"class":57},[51,3178,3179],{"class":76}," HandleString",[51,3181,1333],{"class":65},[51,3183,70],{"class":69},[51,3185,73],{"class":65},[51,3187,77],{"class":76},[51,3189,112],{"class":65},[51,3191,1115],{"class":57},[51,3193,3194],{"class":76}," void",[51,3196,3012],{"class":65},[51,3198,3199,3201,3204,3206,3208,3210,3212,3214,3216,3218,3220,3222],{"class":53,"line":629},[51,3200,180],{"class":57},[51,3202,3203],{"class":76}," HandleUnion",[51,3205,1333],{"class":65},[51,3207,70],{"class":69},[51,3209,73],{"class":65},[51,3211,77],{"class":76},[51,3213,80],{"class":65},[51,3215,83],{"class":76},[51,3217,112],{"class":65},[51,3219,1115],{"class":57},[51,3221,3194],{"class":76},[51,3223,3012],{"class":65},[51,3225,3226,3228,3231,3233,3236,3238,3240,3242,3244,3247,3249,3252,3254,3256],{"class":53,"line":649},[51,3227,3119],{"class":57},[51,3229,3230],{"class":61}," handleUnion",[51,3232,73],{"class":65},[51,3234,3235],{"class":76},"HandleUnion",[51,3237,1333],{"class":65},[51,3239,70],{"class":69},[51,3241,112],{"class":65},[51,3243,1115],{"class":57},[51,3245,3246],{"class":101}," console",[51,3248,2139],{"class":65},[51,3250,3251],{"class":61},"log",[51,3253,66],{"class":65},[51,3255,70],{"class":101},[51,3257,3258],{"class":65},");\n",[51,3260,3261,3263,3266,3268,3271,3273,3276,3278],{"class":53,"line":679},[51,3262,3119],{"class":57},[51,3264,3265],{"class":101}," handleString",[51,3267,73],{"class":65},[51,3269,3270],{"class":76},"HandleString",[51,3272,186],{"class":65},[51,3274,3275],{"class":101},"handleUnion",[51,3277,1674],{"class":65},[51,3279,3280],{"class":144},"\u002F\u002F 安全？在严格函数类型下是允许的\n",[51,3282,3283],{"class":53,"line":684},[51,3284,3285],{"class":144},"\u002F\u002F 因为参数是逆变：string | number 是 string 的超类型，所以 HandleUnion 是 HandleString 的子类型\n",[51,3287,3288],{"class":53,"line":2491},[51,3289,3290],{"class":144},"\u002F\u002F HandleUnion extends HandleString 是 true\n",[10,3292,3293],{},"以上是具体的函数类型。下面假设函数类型带有参数，因而无法直接判断的情况。",[42,3295,3297],{"className":44,"code":3296,"language":46,"meta":47,"style":47},"type A = \u003CT>() => T[]\ntype B = \u003CT>() => Array\u003CT>\nlet a: A = () => []\nlet b: B = () => []\na = b \u002F\u002F ? 是否可以安全赋值\n\u002F\u002F B extends A 吗？\n",[29,3298,3299,3320,3342,3360,3377,3389],{"__ignoreMap":47},[51,3300,3301,3303,3305,3308,3310,3313,3315,3317],{"class":53,"line":54},[51,3302,180],{"class":57},[51,3304,183],{"class":76},[51,3306,3307],{"class":65}," = \u003C",[51,3309,542],{"class":76},[51,3311,3312],{"class":65},">() ",[51,3314,1115],{"class":57},[51,3316,336],{"class":76},[51,3318,3319],{"class":65},"[]\n",[51,3321,3322,3324,3326,3328,3330,3332,3334,3336,3338,3340],{"class":53,"line":89},[51,3323,180],{"class":57},[51,3325,216],{"class":76},[51,3327,3307],{"class":65},[51,3329,542],{"class":76},[51,3331,3312],{"class":65},[51,3333,1115],{"class":57},[51,3335,536],{"class":76},[51,3337,539],{"class":65},[51,3339,542],{"class":76},[51,3341,1703],{"class":65},[51,3343,3344,3346,3349,3351,3353,3355,3357],{"class":53,"line":118},[51,3345,3119],{"class":57},[51,3347,3348],{"class":61}," a",[51,3350,73],{"class":65},[51,3352,760],{"class":76},[51,3354,3092],{"class":65},[51,3356,1115],{"class":57},[51,3358,3359],{"class":65}," []\n",[51,3361,3362,3364,3367,3369,3371,3373,3375],{"class":53,"line":138},[51,3363,3119],{"class":57},[51,3365,3366],{"class":61}," b",[51,3368,73],{"class":65},[51,3370,765],{"class":76},[51,3372,3092],{"class":65},[51,3374,1115],{"class":57},[51,3376,3359],{"class":65},[51,3378,3379,3381,3383,3386],{"class":53,"line":148},[51,3380,14],{"class":101},[51,3382,186],{"class":65},[51,3384,3385],{"class":101},"b",[51,3387,3388],{"class":144}," \u002F\u002F ? 是否可以安全赋值\n",[51,3390,3391],{"class":53,"line":441},[51,3392,3393],{"class":144},"\u002F\u002F B extends A 吗？\n",[10,3395,3396,3397,3400,3401,3403,3404,3413,3414,3417,3418,377],{},"在存在泛型参数的情况下，TS 会进行",[159,3398,3399],{},"全称量化","，即将",[29,3402,542],{},"假设为任意类型，即对于",[159,3405,3406,3407],{},"所有的类型 T 均必须满足 B",[3408,3409,3410,3411],"t",{}," 可以被赋给 A",[3408,3412],{},"。在这个例子中，显然将任意类型带入 T 均可满足条件，所以赋值的安全的，",[29,3415,3416],{},"B extends A","的结果为",[29,3419,1894],{},[25,3421,3422],{"id":3422},"扩展运算符",[10,3424,3425,3426,3429],{},"TS 的类型系统中也可以使用扩展运算符",[29,3427,3428],{},"...","，帮助分发类型到具体的泛型参数。可以作用于数组中的任意位置。",[10,3431,3432,3433,833,3436,3438],{},"借助于扩展运算符，可以很方便地获取数组的第一个或最后一个元素，并在类型层面实现诸如",[29,3434,3435],{},"concat",[29,3437,2142],{},"等方法。",[42,3440,3442],{"className":44,"code":3441,"language":46,"meta":47,"style":47},"\u002F\u002F 返回第一个元素\ntype FirstEl\u003CT> = T extends [infer S, ...any] ? S : never\n\u002F\u002F 合并数组\ntype Concat\u003CT1 extends any[], T2 extends any> = [...T1, ...T2]\n\u002F\u002F 从数组中移除最后一个元素\ntype Pop\u003CT> = T extends [...infer S, any] ? S : []\n",[29,3443,3444,3449,3487,3492,3529,3534],{"__ignoreMap":47},[51,3445,3446],{"class":53,"line":54},[51,3447,3448],{"class":144},"\u002F\u002F 返回第一个元素\n",[51,3450,3451,3453,3456,3458,3460,3462,3464,3466,3469,3471,3474,3476,3478,3480,3483,3485],{"class":53,"line":89},[51,3452,180],{"class":57},[51,3454,3455],{"class":76}," FirstEl",[51,3457,539],{"class":65},[51,3459,542],{"class":76},[51,3461,864],{"class":65},[51,3463,542],{"class":76},[51,3465,191],{"class":57},[51,3467,3468],{"class":65}," [infer ",[51,3470,2919],{"class":76},[51,3472,3473],{"class":65},", ...",[51,3475,998],{"class":76},[51,3477,497],{"class":65},[51,3479,197],{"class":57},[51,3481,3482],{"class":76}," S",[51,3484,203],{"class":57},[51,3486,880],{"class":76},[51,3488,3489],{"class":53,"line":118},[51,3490,3491],{"class":144},"\u002F\u002F 合并数组\n",[51,3493,3494,3496,3499,3501,3504,3506,3508,3511,3514,3516,3518,3521,3523,3525,3527],{"class":53,"line":138},[51,3495,180],{"class":57},[51,3497,3498],{"class":76}," Concat",[51,3500,539],{"class":65},[51,3502,3503],{"class":76},"T1",[51,3505,191],{"class":57},[51,3507,474],{"class":76},[51,3509,3510],{"class":65},"[], ",[51,3512,3513],{"class":76},"T2",[51,3515,191],{"class":57},[51,3517,474],{"class":76},[51,3519,3520],{"class":65},"> = [...",[51,3522,3503],{"class":76},[51,3524,3473],{"class":65},[51,3526,3513],{"class":76},[51,3528,1477],{"class":65},[51,3530,3531],{"class":53,"line":148},[51,3532,3533],{"class":144},"\u002F\u002F 从数组中移除最后一个元素\n",[51,3535,3536,3538,3541,3543,3545,3547,3549,3551,3554,3556,3558,3560,3562,3564,3566,3568],{"class":53,"line":441},[51,3537,180],{"class":57},[51,3539,3540],{"class":76}," Pop",[51,3542,539],{"class":65},[51,3544,542],{"class":76},[51,3546,864],{"class":65},[51,3548,542],{"class":76},[51,3550,191],{"class":57},[51,3552,3553],{"class":65}," [...infer ",[51,3555,2919],{"class":76},[51,3557,492],{"class":65},[51,3559,998],{"class":76},[51,3561,497],{"class":65},[51,3563,197],{"class":57},[51,3565,3482],{"class":76},[51,3567,203],{"class":57},[51,3569,3359],{"class":65},[10,3571,3572,3573,3059],{},"从只读数组中提取元素时使用扩展运算符进行分发，可以用于去除数组的",[29,3574,3575],{},"readonly",[42,3577,3579],{"className":44,"code":3578,"language":46,"meta":47,"style":47},"const arr = [1, 2, 'hello'] as const\ntype Writable\u003CT extends readonly any[]> = T extends readonly [...infer U] ? [...U] : T\n\nWritable\u003Ctypeof arr> \u002F\u002F [1, 2, 'hello']\n",[29,3580,3581,3605,3651,3655],{"__ignoreMap":47},[51,3582,3583,3585,3587,3589,3591,3593,3595,3597,3599,3601,3603],{"class":53,"line":54},[51,3584,1824],{"class":57},[51,3586,1827],{"class":76},[51,3588,1932],{"class":65},[51,3590,1837],{"class":108},[51,3592,492],{"class":65},[51,3594,1842],{"class":108},[51,3596,492],{"class":65},[51,3598,1884],{"class":108},[51,3600,497],{"class":65},[51,3602,1575],{"class":57},[51,3604,1992],{"class":57},[51,3606,3607,3609,3612,3614,3616,3618,3621,3623,3626,3628,3630,3632,3634,3636,3638,3640,3643,3645,3647,3649],{"class":53,"line":89},[51,3608,180],{"class":57},[51,3610,3611],{"class":76}," Writable",[51,3613,539],{"class":65},[51,3615,542],{"class":76},[51,3617,191],{"class":57},[51,3619,3620],{"class":57}," readonly",[51,3622,474],{"class":76},[51,3624,3625],{"class":65},"[]> = ",[51,3627,542],{"class":76},[51,3629,191],{"class":57},[51,3631,3620],{"class":57},[51,3633,3553],{"class":65},[51,3635,1303],{"class":76},[51,3637,497],{"class":65},[51,3639,197],{"class":57},[51,3641,3642],{"class":65}," [...",[51,3644,1303],{"class":76},[51,3646,497],{"class":65},[51,3648,942],{"class":57},[51,3650,1251],{"class":76},[51,3652,3653],{"class":53,"line":118},[51,3654,450],{"emptyLinePlaceholder":449},[51,3656,3657,3660,3662,3664,3666,3668],{"class":53,"line":138},[51,3658,3659],{"class":76},"Writable",[51,3661,539],{"class":65},[51,3663,98],{"class":57},[51,3665,1827],{"class":101},[51,3667,902],{"class":65},[51,3669,3670],{"class":144},"\u002F\u002F [1, 2, 'hello']\n",[25,3672,3674,3675,3678],{"id":3673},"warm-up实现pick类型","Warm Up：实现",[29,3676,3677],{},"Pick","类型",[10,3680,3681,3684,3685,3687,3688,3690],{},[29,3682,3683],{},"Pick\u003CT, K>","类型用于从对象类型",[29,3686,542],{},"中剔除",[29,3689,1732],{},"以外的属性，返回一个新的对象类型。",[42,3692,3694],{"className":44,"code":3693,"language":46,"meta":47,"style":47},"interface Todo {\n  title: string\n  description: string\n  completed: boolean\n}\n\ntype TodoTitle = Pick\u003CTodo, 'title'>\n\u002F\u002F { title: string }\ntype TodoLite = Pick\u003CTodo, 'title' | 'completed'>\n\u002F\u002F { title: string; completed: boolean }\ntype ErrorCase = Pick\u003CTodo, 'title' | 'invalid'>\n\u002F\u002F 错误\n",[29,3695,3696,3705,3714,3723,3733,3737,3741,3764,3769,3795,3800,3826],{"__ignoreMap":47},[51,3697,3698,3700,3703],{"class":53,"line":54},[51,3699,533],{"class":57},[51,3701,3702],{"class":76}," Todo",[51,3704,393],{"class":65},[51,3706,3707,3710,3712],{"class":53,"line":89},[51,3708,3709],{"class":101},"  title",[51,3711,73],{"class":65},[51,3713,349],{"class":76},[51,3715,3716,3719,3721],{"class":53,"line":118},[51,3717,3718],{"class":101},"  description",[51,3720,73],{"class":65},[51,3722,349],{"class":76},[51,3724,3725,3728,3730],{"class":53,"line":138},[51,3726,3727],{"class":101},"  completed",[51,3729,73],{"class":65},[51,3731,3732],{"class":76},"boolean\n",[51,3734,3735],{"class":53,"line":148},[51,3736,151],{"class":65},[51,3738,3739],{"class":53,"line":441},[51,3740,450],{"emptyLinePlaceholder":449},[51,3742,3743,3745,3748,3750,3752,3754,3757,3759,3762],{"class":53,"line":446},[51,3744,180],{"class":57},[51,3746,3747],{"class":76}," TodoTitle",[51,3749,186],{"class":65},[51,3751,3677],{"class":76},[51,3753,539],{"class":65},[51,3755,3756],{"class":76},"Todo",[51,3758,492],{"class":65},[51,3760,3761],{"class":108},"'title'",[51,3763,1703],{"class":65},[51,3765,3766],{"class":53,"line":453},[51,3767,3768],{"class":144},"\u002F\u002F { title: string }\n",[51,3770,3771,3773,3776,3778,3780,3782,3784,3786,3788,3790,3793],{"class":53,"line":617},[51,3772,180],{"class":57},[51,3774,3775],{"class":76}," TodoLite",[51,3777,186],{"class":65},[51,3779,3677],{"class":76},[51,3781,539],{"class":65},[51,3783,3756],{"class":76},[51,3785,492],{"class":65},[51,3787,3761],{"class":108},[51,3789,80],{"class":65},[51,3791,3792],{"class":108},"'completed'",[51,3794,1703],{"class":65},[51,3796,3797],{"class":53,"line":629},[51,3798,3799],{"class":144},"\u002F\u002F { title: string; completed: boolean }\n",[51,3801,3802,3804,3807,3809,3811,3813,3815,3817,3819,3821,3824],{"class":53,"line":649},[51,3803,180],{"class":57},[51,3805,3806],{"class":76}," ErrorCase",[51,3808,186],{"class":65},[51,3810,3677],{"class":76},[51,3812,539],{"class":65},[51,3814,3756],{"class":76},[51,3816,492],{"class":65},[51,3818,3761],{"class":108},[51,3820,80],{"class":65},[51,3822,3823],{"class":108},"'invalid'",[51,3825,1703],{"class":65},[51,3827,3828],{"class":53,"line":679},[51,3829,3830],{"class":144},"\u002F\u002F 错误\n",[10,3832,3833],{},"可以看到：",[3020,3835,3836,3847,3860],{},[3023,3837,3838,3840,3841,3843,3844,3846],{},[29,3839,1732],{},"类型应当是",[29,3842,542],{},"中的属性名，通过为泛型参数",[29,3845,1732],{},"增加约束实现；",[3023,3848,3849,3850,3852,3853,3856,3857,3859],{},"返回的类型中，只有",[29,3851,1732],{},"中指定的属性，因此需要使用",[29,3854,3855],{},"[P in K]","来映射",[29,3858,1732],{},"中的属性名；",[3023,3861,3862,3863,3865],{},"最终需要将",[29,3864,542],{},"中对应属性的类型映射到新的对象类型中。",[42,3867,3869],{"className":44,"code":3868,"language":46,"meta":47,"style":47},"type Pick\u003CT, K extends keyof T> = {\n  [P in K]: T[P]\n}\n",[29,3870,3871,3894,3914],{"__ignoreMap":47},[51,3872,3873,3875,3878,3880,3882,3884,3886,3888,3890,3892],{"class":53,"line":54},[51,3874,180],{"class":57},[51,3876,3877],{"class":76}," Pick",[51,3879,539],{"class":65},[51,3881,542],{"class":76},[51,3883,492],{"class":65},[51,3885,1732],{"class":76},[51,3887,191],{"class":57},[51,3889,1463],{"class":57},[51,3891,336],{"class":76},[51,3893,1448],{"class":65},[51,3895,3896,3898,3900,3902,3904,3906,3908,3910,3912],{"class":53,"line":89},[51,3897,550],{"class":65},[51,3899,1457],{"class":76},[51,3901,1460],{"class":57},[51,3903,1757],{"class":76},[51,3905,560],{"class":65},[51,3907,542],{"class":76},[51,3909,1472],{"class":65},[51,3911,1457],{"class":76},[51,3913,1477],{"class":65},[51,3915,3916],{"class":53,"line":118},[51,3917,151],{"class":65},[3919,3920,3921],"style",{},"html pre.shiki code .sqF6k, html code.shiki .sqF6k{--shiki-default:#54B9FF}html pre.shiki code .stHQG, html code.shiki .stHQG{--shiki-default:#00DAEF}html pre.shiki code .sLo_3, html code.shiki .sLo_3{--shiki-default:#EEF0F9}html pre.shiki code .smONe, html code.shiki .smONe{--shiki-default:#4BF3C8;--shiki-default-font-style:italic}html pre.shiki code .s8HWQ, html code.shiki .s8HWQ{--shiki-default:#ACAFFF}html pre.shiki code .sb16X, html code.shiki .sb16X{--shiki-default:#4BF3C8}html pre.shiki code .se2J0, html code.shiki .se2J0{--shiki-default:#FFD493}html pre.shiki code .sy89t, html code.shiki .sy89t{--shiki-default:#EEF0F98F;--shiki-default-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":47,"searchDepth":89,"depth":89,"links":3923},[3924,3926,3928,3929,3930,3931,3932,3933,3935,3936,3937,3938,3939],{"id":27,"depth":89,"text":3925},"never与{}",{"id":315,"depth":89,"text":3927},"keyof：类型系统的「反射」",{"id":700,"depth":89,"text":700},{"id":783,"depth":89,"text":783},{"id":1037,"depth":89,"text":1037},{"id":1420,"depth":89,"text":1420},{"id":1802,"depth":89,"text":1802},{"id":2016,"depth":89,"text":3934},"as与as const",{"id":2528,"depth":89,"text":2528},{"id":2839,"depth":89,"text":2839},{"id":3015,"depth":89,"text":3015},{"id":3422,"depth":89,"text":3422},{"id":3673,"depth":89,"text":3940},"Warm Up：实现Pick类型","2022.02.12","TS 的类型系统十分完备，本质上可以看做是一门图灵完备的编译期语言。而「类型体操 Type Gymnastics」指利用 TS 中的类型系统进行各类复杂操作甚至实现部分算法的技巧。","md","TS 的类型系统本质上可以被看做一门图灵完备的编译期语言",{},"\u002Fwriting\u002Ftype-gymnastics-1",{"title":5,"description":3942},"writing\u002Ftype-gymnastics-1",[3950],"TypeScript","OHUnWKe_M8Wf6m_837Sw7MEX3dZml2K8b9xOZFhLR6w",1774950409696]