[{"data":1,"prerenderedAt":1752},["ShallowReactive",2],{"note:code\u002Fvue\u002F11-typescript-production":3},{"id":4,"title":5,"body":6,"description":16,"extension":1746,"meta":1747,"navigation":88,"path":1748,"seo":1749,"stem":1750,"__hash__":1751},"notes\u002Fsource\u002Fcode\u002Fvue\u002F11-typescript-production.md","11 TypeScript 和生产级代码",{"type":7,"value":8,"toc":1726},"minimark",[9,13,17,20,25,28,117,120,178,182,239,242,306,309,370,373,435,439,442,489,495,498,526,529,553,557,685,688,695,725,728,749,755,759,819,822,840,843,859,862,865,885,888,892,895,942,945,1012,1019,1028,1031,1089,1093,1254,1260,1263,1267,1270,1292,1295,1303,1355,1358,1379,1385,1389,1392,1395,1426,1430,1436,1439,1477,1481,1484,1508,1511,1515,1520,1568,1571,1578,1599,1604,1608,1654,1657,1684,1687,1722],[10,11,5],"h1",{"id":12},"_11-typescript-和生产级代码",[14,15,16],"p",{},"你可以先用 JavaScript 学 Vue，再逐步引入 TypeScript。TypeScript 的价值不是“让代码更复杂”，而是让错误更早暴露，尤其适合多人协作和长期维护的项目。",[14,18,19],{},"本章目标：你理解 TypeScript 能解决什么问题，能给 Vue props、emits、ref、API 数据加基础类型，并知道生产级代码应该具备哪些习惯。",[21,22,24],"h2",{"id":23},"_1-javascript-中的问题","1. JavaScript 中的问题",[14,26,27],{},"JavaScript 很灵活：",[29,30,35],"pre",{"className":31,"code":32,"language":33,"meta":34,"style":34},"language-js shiki shiki-themes github-light github-dark","function formatMinutes(minutes) {\n  return `${minutes} 分钟`\n}\n\nformatMinutes(30)\nformatMinutes('abc')\n","js","",[36,37,38,62,77,83,90,105],"code",{"__ignoreMap":34},[39,40,43,47,51,55,59],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"szBVR","function",[39,48,50],{"class":49},"sScJk"," formatMinutes",[39,52,54],{"class":53},"sVt8B","(",[39,56,58],{"class":57},"s4XuR","minutes",[39,60,61],{"class":53},") {\n",[39,63,65,68,72,74],{"class":41,"line":64},2,[39,66,67],{"class":45},"  return",[39,69,71],{"class":70},"sZZnC"," `${",[39,73,58],{"class":53},[39,75,76],{"class":70},"} 分钟`\n",[39,78,80],{"class":41,"line":79},3,[39,81,82],{"class":53},"}\n",[39,84,86],{"class":41,"line":85},4,[39,87,89],{"emptyLinePlaceholder":88},true,"\n",[39,91,93,96,98,102],{"class":41,"line":92},5,[39,94,95],{"class":49},"formatMinutes",[39,97,54],{"class":53},[39,99,101],{"class":100},"sj4cs","30",[39,103,104],{"class":53},")\n",[39,106,108,110,112,115],{"class":41,"line":107},6,[39,109,95],{"class":49},[39,111,54],{"class":53},[39,113,114],{"class":70},"'abc'",[39,116,104],{"class":53},[14,118,119],{},"第二次调用语法上没问题，但业务上可能错了。TypeScript 可以提前提醒：这里应该传数字。",[29,121,125],{"className":122,"code":123,"language":124,"meta":34,"style":34},"language-ts shiki shiki-themes github-light github-dark","function formatMinutes(minutes: number) {\n  return `${minutes} 分钟`\n}\n\nformatMinutes('abc') \u002F\u002F 类型错误\n","ts",[36,126,127,145,155,159,163],{"__ignoreMap":34},[39,128,129,131,133,135,137,140,143],{"class":41,"line":42},[39,130,46],{"class":45},[39,132,50],{"class":49},[39,134,54],{"class":53},[39,136,58],{"class":57},[39,138,139],{"class":45},":",[39,141,142],{"class":100}," number",[39,144,61],{"class":53},[39,146,147,149,151,153],{"class":41,"line":64},[39,148,67],{"class":45},[39,150,71],{"class":70},[39,152,58],{"class":53},[39,154,76],{"class":70},[39,156,157],{"class":41,"line":79},[39,158,82],{"class":53},[39,160,161],{"class":41,"line":85},[39,162,89],{"emptyLinePlaceholder":88},[39,164,165,167,169,171,174],{"class":41,"line":92},[39,166,95],{"class":49},[39,168,54],{"class":53},[39,170,114],{"class":70},[39,172,173],{"class":53},") ",[39,175,177],{"class":176},"sJ8bj","\u002F\u002F 类型错误\n",[21,179,181],{"id":180},"_2-基础类型","2. 基础类型",[29,183,185],{"className":122,"code":184,"language":124,"meta":34,"style":34},"const title: string = 'Vue'\nconst minutes: number = 45\nconst done: boolean = false\n",[36,186,187,206,222],{"__ignoreMap":34},[39,188,189,192,195,197,200,203],{"class":41,"line":42},[39,190,191],{"class":45},"const",[39,193,194],{"class":100}," title",[39,196,139],{"class":45},[39,198,199],{"class":100}," string",[39,201,202],{"class":45}," =",[39,204,205],{"class":70}," 'Vue'\n",[39,207,208,210,213,215,217,219],{"class":41,"line":64},[39,209,191],{"class":45},[39,211,212],{"class":100}," minutes",[39,214,139],{"class":45},[39,216,142],{"class":100},[39,218,202],{"class":45},[39,220,221],{"class":100}," 45\n",[39,223,224,226,229,231,234,236],{"class":41,"line":79},[39,225,191],{"class":45},[39,227,228],{"class":100}," done",[39,230,139],{"class":45},[39,232,233],{"class":100}," boolean",[39,235,202],{"class":45},[39,237,238],{"class":100}," false\n",[14,240,241],{},"数组：",[29,243,245],{"className":122,"code":244,"language":124,"meta":34,"style":34},"const tags: string[] = ['vue', 'router']\nconst scores: number[] = [80, 90]\n",[36,246,247,279],{"__ignoreMap":34},[39,248,249,251,254,256,258,261,264,267,270,273,276],{"class":41,"line":42},[39,250,191],{"class":45},[39,252,253],{"class":100}," tags",[39,255,139],{"class":45},[39,257,199],{"class":100},[39,259,260],{"class":53},"[] ",[39,262,263],{"class":45},"=",[39,265,266],{"class":53}," [",[39,268,269],{"class":70},"'vue'",[39,271,272],{"class":53},", ",[39,274,275],{"class":70},"'router'",[39,277,278],{"class":53},"]\n",[39,280,281,283,286,288,290,292,294,296,299,301,304],{"class":41,"line":64},[39,282,191],{"class":45},[39,284,285],{"class":100}," scores",[39,287,139],{"class":45},[39,289,142],{"class":100},[39,291,260],{"class":53},[39,293,263],{"class":45},[39,295,266],{"class":53},[39,297,298],{"class":100},"80",[39,300,272],{"class":53},[39,302,303],{"class":100},"90",[39,305,278],{"class":53},[14,307,308],{},"对象类型：",[29,310,312],{"className":122,"code":311,"language":124,"meta":34,"style":34},"type Lesson = {\n  id: string\n  title: string\n  minutes: number\n  done: boolean\n}\n",[36,313,314,327,337,346,356,366],{"__ignoreMap":34},[39,315,316,319,322,324],{"class":41,"line":42},[39,317,318],{"class":45},"type",[39,320,321],{"class":49}," Lesson",[39,323,202],{"class":45},[39,325,326],{"class":53}," {\n",[39,328,329,332,334],{"class":41,"line":64},[39,330,331],{"class":57},"  id",[39,333,139],{"class":45},[39,335,336],{"class":100}," string\n",[39,338,339,342,344],{"class":41,"line":79},[39,340,341],{"class":57},"  title",[39,343,139],{"class":45},[39,345,336],{"class":100},[39,347,348,351,353],{"class":41,"line":85},[39,349,350],{"class":57},"  minutes",[39,352,139],{"class":45},[39,354,355],{"class":100}," number\n",[39,357,358,361,363],{"class":41,"line":92},[39,359,360],{"class":57},"  done",[39,362,139],{"class":45},[39,364,365],{"class":100}," boolean\n",[39,367,368],{"class":41,"line":107},[39,369,82],{"class":53},[14,371,372],{},"使用：",[29,374,376],{"className":122,"code":375,"language":124,"meta":34,"style":34},"const lesson: Lesson = {\n  id: 'core',\n  title: 'Vue 核心语法',\n  minutes: 90,\n  done: false\n}\n",[36,377,378,393,404,414,423,431],{"__ignoreMap":34},[39,379,380,382,385,387,389,391],{"class":41,"line":42},[39,381,191],{"class":45},[39,383,384],{"class":100}," lesson",[39,386,139],{"class":45},[39,388,321],{"class":49},[39,390,202],{"class":45},[39,392,326],{"class":53},[39,394,395,398,401],{"class":41,"line":64},[39,396,397],{"class":53},"  id: ",[39,399,400],{"class":70},"'core'",[39,402,403],{"class":53},",\n",[39,405,406,409,412],{"class":41,"line":79},[39,407,408],{"class":53},"  title: ",[39,410,411],{"class":70},"'Vue 核心语法'",[39,413,403],{"class":53},[39,415,416,419,421],{"class":41,"line":85},[39,417,418],{"class":53},"  minutes: ",[39,420,303],{"class":100},[39,422,403],{"class":53},[39,424,425,428],{"class":41,"line":92},[39,426,427],{"class":53},"  done: ",[39,429,430],{"class":100},"false\n",[39,432,433],{"class":41,"line":107},[39,434,82],{"class":53},[21,436,438],{"id":437},"_3-可选属性和联合类型","3. 可选属性和联合类型",[14,440,441],{},"可选属性：",[29,443,445],{"className":122,"code":444,"language":124,"meta":34,"style":34},"type User = {\n  id: string\n  name: string\n  avatarUrl?: string\n}\n",[36,446,447,458,466,475,485],{"__ignoreMap":34},[39,448,449,451,454,456],{"class":41,"line":42},[39,450,318],{"class":45},[39,452,453],{"class":49}," User",[39,455,202],{"class":45},[39,457,326],{"class":53},[39,459,460,462,464],{"class":41,"line":64},[39,461,331],{"class":57},[39,463,139],{"class":45},[39,465,336],{"class":100},[39,467,468,471,473],{"class":41,"line":79},[39,469,470],{"class":57},"  name",[39,472,139],{"class":45},[39,474,336],{"class":100},[39,476,477,480,483],{"class":41,"line":85},[39,478,479],{"class":57},"  avatarUrl",[39,481,482],{"class":45},"?:",[39,484,336],{"class":100},[39,486,487],{"class":41,"line":92},[39,488,82],{"class":53},[14,490,491,494],{},[36,492,493],{},"avatarUrl?"," 表示可能存在，也可能没有。",[14,496,497],{},"联合类型：",[29,499,501],{"className":122,"code":500,"language":124,"meta":34,"style":34},"type Priority = 'low' | 'normal' | 'high'\n",[36,502,503],{"__ignoreMap":34},[39,504,505,507,510,512,515,518,521,523],{"class":41,"line":42},[39,506,318],{"class":45},[39,508,509],{"class":49}," Priority",[39,511,202],{"class":45},[39,513,514],{"class":70}," 'low'",[39,516,517],{"class":45}," |",[39,519,520],{"class":70}," 'normal'",[39,522,517],{"class":45},[39,524,525],{"class":70}," 'high'\n",[14,527,528],{},"这样可以限制优先级只能是这三个值。",[29,530,532],{"className":122,"code":531,"language":124,"meta":34,"style":34},"const priority: Priority = 'urgent' \u002F\u002F 报错\n",[36,533,534],{"__ignoreMap":34},[39,535,536,538,541,543,545,547,550],{"class":41,"line":42},[39,537,191],{"class":45},[39,539,540],{"class":100}," priority",[39,542,139],{"class":45},[39,544,509],{"class":49},[39,546,202],{"class":45},[39,548,549],{"class":70}," 'urgent'",[39,551,552],{"class":176}," \u002F\u002F 报错\n",[21,554,556],{"id":555},"_4-vue-中给-props-加类型","4. Vue 中给 props 加类型",[29,558,562],{"className":559,"code":560,"language":561,"meta":34,"style":34},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\ntype Lesson = {\n  id: string\n  title: string\n  minutes: number\n  done: boolean\n}\n\ndefineProps\u003C{\n  lesson: Lesson\n  completed: boolean\n}>()\n\u003C\u002Fscript>\n","vue",[36,563,564,587,597,605,613,621,629,634,639,648,659,669,675],{"__ignoreMap":34},[39,565,566,569,573,576,579,581,584],{"class":41,"line":42},[39,567,568],{"class":53},"\u003C",[39,570,572],{"class":571},"s9eBZ","script",[39,574,575],{"class":49}," setup",[39,577,578],{"class":49}," lang",[39,580,263],{"class":53},[39,582,583],{"class":70},"\"ts\"",[39,585,586],{"class":53},">\n",[39,588,589,591,593,595],{"class":41,"line":64},[39,590,318],{"class":45},[39,592,321],{"class":49},[39,594,202],{"class":45},[39,596,326],{"class":53},[39,598,599,601,603],{"class":41,"line":79},[39,600,331],{"class":57},[39,602,139],{"class":45},[39,604,336],{"class":100},[39,606,607,609,611],{"class":41,"line":85},[39,608,341],{"class":57},[39,610,139],{"class":45},[39,612,336],{"class":100},[39,614,615,617,619],{"class":41,"line":92},[39,616,350],{"class":57},[39,618,139],{"class":45},[39,620,355],{"class":100},[39,622,623,625,627],{"class":41,"line":107},[39,624,360],{"class":57},[39,626,139],{"class":45},[39,628,365],{"class":100},[39,630,632],{"class":41,"line":631},7,[39,633,82],{"class":53},[39,635,637],{"class":41,"line":636},8,[39,638,89],{"emptyLinePlaceholder":88},[39,640,642,645],{"class":41,"line":641},9,[39,643,644],{"class":49},"defineProps",[39,646,647],{"class":53},"\u003C{\n",[39,649,651,654,656],{"class":41,"line":650},10,[39,652,653],{"class":57},"  lesson",[39,655,139],{"class":45},[39,657,658],{"class":49}," Lesson\n",[39,660,662,665,667],{"class":41,"line":661},11,[39,663,664],{"class":57},"  completed",[39,666,139],{"class":45},[39,668,365],{"class":100},[39,670,672],{"class":41,"line":671},12,[39,673,674],{"class":53},"}>()\n",[39,676,678,681,683],{"class":41,"line":677},13,[39,679,680],{"class":53},"\u003C\u002F",[39,682,572],{"class":571},[39,684,586],{"class":53},[14,686,687],{},"这样父组件传错类型时，编辑器会提示。",[14,689,690,691,694],{},"例如 ",[36,692,693],{},"completed"," 应该是布尔值：",[29,696,698],{"className":559,"code":697,"language":561,"meta":34,"style":34},"\u003CLessonCard :completed=\"true\" \u002F>\n",[36,699,700],{"__ignoreMap":34},[39,701,702,704,707,710,712,714,717,720,722],{"class":41,"line":42},[39,703,568],{"class":53},[39,705,706],{"class":571},"LessonCard",[39,708,709],{"class":53}," :",[39,711,693],{"class":49},[39,713,263],{"class":53},[39,715,716],{"class":70},"\"",[39,718,719],{"class":100},"true",[39,721,716],{"class":70},[39,723,724],{"class":53}," \u002F>\n",[14,726,727],{},"如果写成：",[29,729,731],{"className":559,"code":730,"language":561,"meta":34,"style":34},"\u003CLessonCard completed=\"true\" \u002F>\n",[36,732,733],{"__ignoreMap":34},[39,734,735,737,739,742,744,747],{"class":41,"line":42},[39,736,568],{"class":53},[39,738,706],{"class":571},[39,740,741],{"class":49}," completed",[39,743,263],{"class":53},[39,745,746],{"class":70},"\"true\"",[39,748,724],{"class":53},[14,750,751,752,754],{},"这里传的是字符串 ",[36,753,746],{},"，TypeScript 和 Vue 工具会更容易发现问题。",[21,756,758],{"id":757},"_5-给-emits-加类型","5. 给 emits 加类型",[29,760,762],{"className":122,"code":761,"language":124,"meta":34,"style":34},"const emit = defineEmits\u003C{\n  toggle: [id: string]\n  remove: [id: string]\n}>()\n",[36,763,764,778,798,815],{"__ignoreMap":34},[39,765,766,768,771,773,776],{"class":41,"line":42},[39,767,191],{"class":45},[39,769,770],{"class":100}," emit",[39,772,202],{"class":45},[39,774,775],{"class":49}," defineEmits",[39,777,647],{"class":53},[39,779,780,783,785,787,790,793,796],{"class":41,"line":64},[39,781,782],{"class":57},"  toggle",[39,784,139],{"class":45},[39,786,266],{"class":53},[39,788,789],{"class":49},"id",[39,791,792],{"class":53},": ",[39,794,795],{"class":100},"string",[39,797,278],{"class":53},[39,799,800,803,805,807,809,811,813],{"class":41,"line":79},[39,801,802],{"class":57},"  remove",[39,804,139],{"class":45},[39,806,266],{"class":53},[39,808,789],{"class":49},[39,810,792],{"class":53},[39,812,795],{"class":100},[39,814,278],{"class":53},[39,816,817],{"class":41,"line":85},[39,818,674],{"class":53},[14,820,821],{},"调用：",[29,823,825],{"className":122,"code":824,"language":124,"meta":34,"style":34},"emit('toggle', props.lesson.id)\n",[36,826,827],{"__ignoreMap":34},[39,828,829,832,834,837],{"class":41,"line":42},[39,830,831],{"class":49},"emit",[39,833,54],{"class":53},[39,835,836],{"class":70},"'toggle'",[39,838,839],{"class":53},", props.lesson.id)\n",[14,841,842],{},"如果写错事件名：",[29,844,846],{"className":122,"code":845,"language":124,"meta":34,"style":34},"emit('toggel', props.lesson.id)\n",[36,847,848],{"__ignoreMap":34},[39,849,850,852,854,857],{"class":41,"line":42},[39,851,831],{"class":49},[39,853,54],{"class":53},[39,855,856],{"class":70},"'toggel'",[39,858,839],{"class":53},[14,860,861],{},"会提示错误。",[14,863,864],{},"如果参数类型错：",[29,866,868],{"className":122,"code":867,"language":124,"meta":34,"style":34},"emit('toggle', 123)\n",[36,869,870],{"__ignoreMap":34},[39,871,872,874,876,878,880,883],{"class":41,"line":42},[39,873,831],{"class":49},[39,875,54],{"class":53},[39,877,836],{"class":70},[39,879,272],{"class":53},[39,881,882],{"class":100},"123",[39,884,104],{"class":53},[14,886,887],{},"也会提示错误。",[21,889,891],{"id":890},"_6-给-ref-加类型","6. 给 ref 加类型",[14,893,894],{},"多数时候 TypeScript 能推断：",[29,896,898],{"className":122,"code":897,"language":124,"meta":34,"style":34},"const count = ref(0) \u002F\u002F Ref\u003Cnumber>\nconst title = ref('') \u002F\u002F Ref\u003Cstring>\n",[36,899,900,922],{"__ignoreMap":34},[39,901,902,904,907,909,912,914,917,919],{"class":41,"line":42},[39,903,191],{"class":45},[39,905,906],{"class":100}," count",[39,908,202],{"class":45},[39,910,911],{"class":49}," ref",[39,913,54],{"class":53},[39,915,916],{"class":100},"0",[39,918,173],{"class":53},[39,920,921],{"class":176},"\u002F\u002F Ref\u003Cnumber>\n",[39,923,924,926,928,930,932,934,937,939],{"class":41,"line":64},[39,925,191],{"class":45},[39,927,194],{"class":100},[39,929,202],{"class":45},[39,931,911],{"class":49},[39,933,54],{"class":53},[39,935,936],{"class":70},"''",[39,938,173],{"class":53},[39,940,941],{"class":176},"\u002F\u002F Ref\u003Cstring>\n",[14,943,944],{},"需要手动写类型的情况：",[29,946,948],{"className":122,"code":947,"language":124,"meta":34,"style":34},"type Lesson = {\n  id: string\n  title: string\n}\n\nconst lesson = ref\u003CLesson | null>(null)\n",[36,949,950,960,968,976,980,984],{"__ignoreMap":34},[39,951,952,954,956,958],{"class":41,"line":42},[39,953,318],{"class":45},[39,955,321],{"class":49},[39,957,202],{"class":45},[39,959,326],{"class":53},[39,961,962,964,966],{"class":41,"line":64},[39,963,331],{"class":57},[39,965,139],{"class":45},[39,967,336],{"class":100},[39,969,970,972,974],{"class":41,"line":79},[39,971,341],{"class":57},[39,973,139],{"class":45},[39,975,336],{"class":100},[39,977,978],{"class":41,"line":85},[39,979,82],{"class":53},[39,981,982],{"class":41,"line":92},[39,983,89],{"emptyLinePlaceholder":88},[39,985,986,988,990,992,994,996,999,1001,1004,1007,1010],{"class":41,"line":107},[39,987,191],{"class":45},[39,989,384],{"class":100},[39,991,202],{"class":45},[39,993,911],{"class":49},[39,995,568],{"class":53},[39,997,998],{"class":49},"Lesson",[39,1000,517],{"class":45},[39,1002,1003],{"class":100}," null",[39,1005,1006],{"class":53},">(",[39,1008,1009],{"class":100},"null",[39,1011,104],{"class":53},[14,1013,1014,1015,1018],{},"为什么要 ",[36,1016,1017],{},"Lesson | null","？",[14,1020,1021,1022,1024,1025,1027],{},"因为一开始可能还没请求到课程详情，所以是 ",[36,1023,1009],{},"。请求成功后才是 ",[36,1026,998],{},"。",[14,1029,1030],{},"模板中要处理：",[29,1032,1034],{"className":559,"code":1033,"language":561,"meta":34,"style":34},"\u003Cp v-if=\"!lesson\">加载中...\u003C\u002Fp>\n\u003Carticle v-else>\n  \u003Ch1>{{ lesson.title }}\u003C\u002Fh1>\n\u003C\u002Farticle>\n",[36,1035,1036,1064,1076,1081],{"__ignoreMap":34},[39,1037,1038,1040,1042,1045,1047,1049,1052,1055,1057,1060,1062],{"class":41,"line":42},[39,1039,568],{"class":53},[39,1041,14],{"class":571},[39,1043,1044],{"class":45}," v-if",[39,1046,263],{"class":53},[39,1048,716],{"class":70},[39,1050,1051],{"class":45},"!",[39,1053,1054],{"class":53},"lesson",[39,1056,716],{"class":70},[39,1058,1059],{"class":53},">加载中...\u003C\u002F",[39,1061,14],{"class":571},[39,1063,586],{"class":53},[39,1065,1066,1068,1071,1074],{"class":41,"line":64},[39,1067,568],{"class":53},[39,1069,1070],{"class":571},"article",[39,1072,1073],{"class":45}," v-else",[39,1075,586],{"class":53},[39,1077,1078],{"class":41,"line":79},[39,1079,1080],{"class":53},"  \u003Ch1>{{ lesson.title }}\u003C\u002Fh1>\n",[39,1082,1083,1085,1087],{"class":41,"line":85},[39,1084,680],{"class":53},[39,1086,1070],{"class":571},[39,1088,586],{"class":53},[21,1090,1092],{"id":1091},"_7-api-数据类型","7. API 数据类型",[29,1094,1096],{"className":122,"code":1095,"language":124,"meta":34,"style":34},"type ApiLesson = {\n  id: string\n  title: string\n  completed: boolean\n}\n\nasync function getLessons(): Promise\u003CApiLesson[]> {\n  const response = await fetch('\u002Fapi\u002Flessons')\n\n  if (!response.ok) {\n    throw new Error('课程加载失败')\n  }\n\n  return response.json()\n}\n",[36,1097,1098,1109,1117,1125,1133,1137,1141,1168,1191,1195,1208,1226,1231,1235,1249],{"__ignoreMap":34},[39,1099,1100,1102,1105,1107],{"class":41,"line":42},[39,1101,318],{"class":45},[39,1103,1104],{"class":49}," ApiLesson",[39,1106,202],{"class":45},[39,1108,326],{"class":53},[39,1110,1111,1113,1115],{"class":41,"line":64},[39,1112,331],{"class":57},[39,1114,139],{"class":45},[39,1116,336],{"class":100},[39,1118,1119,1121,1123],{"class":41,"line":79},[39,1120,341],{"class":57},[39,1122,139],{"class":45},[39,1124,336],{"class":100},[39,1126,1127,1129,1131],{"class":41,"line":85},[39,1128,664],{"class":57},[39,1130,139],{"class":45},[39,1132,365],{"class":100},[39,1134,1135],{"class":41,"line":92},[39,1136,82],{"class":53},[39,1138,1139],{"class":41,"line":107},[39,1140,89],{"emptyLinePlaceholder":88},[39,1142,1143,1146,1149,1152,1155,1157,1160,1162,1165],{"class":41,"line":631},[39,1144,1145],{"class":45},"async",[39,1147,1148],{"class":45}," function",[39,1150,1151],{"class":49}," getLessons",[39,1153,1154],{"class":53},"()",[39,1156,139],{"class":45},[39,1158,1159],{"class":49}," Promise",[39,1161,568],{"class":53},[39,1163,1164],{"class":49},"ApiLesson",[39,1166,1167],{"class":53},"[]> {\n",[39,1169,1170,1173,1176,1178,1181,1184,1186,1189],{"class":41,"line":636},[39,1171,1172],{"class":45},"  const",[39,1174,1175],{"class":100}," response",[39,1177,202],{"class":45},[39,1179,1180],{"class":45}," await",[39,1182,1183],{"class":49}," fetch",[39,1185,54],{"class":53},[39,1187,1188],{"class":70},"'\u002Fapi\u002Flessons'",[39,1190,104],{"class":53},[39,1192,1193],{"class":41,"line":641},[39,1194,89],{"emptyLinePlaceholder":88},[39,1196,1197,1200,1203,1205],{"class":41,"line":650},[39,1198,1199],{"class":45},"  if",[39,1201,1202],{"class":53}," (",[39,1204,1051],{"class":45},[39,1206,1207],{"class":53},"response.ok) {\n",[39,1209,1210,1213,1216,1219,1221,1224],{"class":41,"line":661},[39,1211,1212],{"class":45},"    throw",[39,1214,1215],{"class":45}," new",[39,1217,1218],{"class":49}," Error",[39,1220,54],{"class":53},[39,1222,1223],{"class":70},"'课程加载失败'",[39,1225,104],{"class":53},[39,1227,1228],{"class":41,"line":671},[39,1229,1230],{"class":53},"  }\n",[39,1232,1233],{"class":41,"line":677},[39,1234,89],{"emptyLinePlaceholder":88},[39,1236,1238,1240,1243,1246],{"class":41,"line":1237},14,[39,1239,67],{"class":45},[39,1241,1242],{"class":53}," response.",[39,1244,1245],{"class":49},"json",[39,1247,1248],{"class":53},"()\n",[39,1250,1252],{"class":41,"line":1251},15,[39,1253,82],{"class":53},[14,1255,1256,1259],{},[36,1257,1258],{},"Promise\u003CApiLesson[]>"," 表示这个异步函数最终返回课程数组。",[14,1261,1262],{},"注意：TypeScript 只能检查你写的代码，不能保证后端真实返回一定符合类型。后端返回脏数据时，TypeScript 不会自动拦截。真实项目如果需要运行时校验，可以使用 zod 等库。",[21,1264,1266],{"id":1265},"_8-类型放在哪里","8. 类型放在哪里",[14,1268,1269],{},"小项目可以写在组件里：",[29,1271,1273],{"className":122,"code":1272,"language":124,"meta":34,"style":34},"type Lesson = { ... }\n",[36,1274,1275],{"__ignoreMap":34},[39,1276,1277,1279,1281,1283,1286,1289],{"class":41,"line":42},[39,1278,318],{"class":45},[39,1280,321],{"class":49},[39,1282,202],{"class":45},[39,1284,1285],{"class":53}," { ",[39,1287,1288],{"class":45},"...",[39,1290,1291],{"class":53}," }\n",[14,1293,1294],{},"多个文件共用时，可以放：",[29,1296,1301],{"className":1297,"code":1299,"language":1300,"meta":34},[1298],"language-text","src\u002Ftypes\u002Flesson.ts\n","text",[36,1302,1299],{"__ignoreMap":34},[29,1304,1306],{"className":122,"code":1305,"language":124,"meta":34,"style":34},"\u002F\u002F src\u002Ftypes\u002Flesson.ts\nexport type Lesson = {\n  id: string\n  title: string\n  minutes: number\n}\n",[36,1307,1308,1313,1327,1335,1343,1351],{"__ignoreMap":34},[39,1309,1310],{"class":41,"line":42},[39,1311,1312],{"class":176},"\u002F\u002F src\u002Ftypes\u002Flesson.ts\n",[39,1314,1315,1318,1321,1323,1325],{"class":41,"line":64},[39,1316,1317],{"class":45},"export",[39,1319,1320],{"class":45}," type",[39,1322,321],{"class":49},[39,1324,202],{"class":45},[39,1326,326],{"class":53},[39,1328,1329,1331,1333],{"class":41,"line":79},[39,1330,331],{"class":57},[39,1332,139],{"class":45},[39,1334,336],{"class":100},[39,1336,1337,1339,1341],{"class":41,"line":85},[39,1338,341],{"class":57},[39,1340,139],{"class":45},[39,1342,336],{"class":100},[39,1344,1345,1347,1349],{"class":41,"line":92},[39,1346,350],{"class":57},[39,1348,139],{"class":45},[39,1350,355],{"class":100},[39,1352,1353],{"class":41,"line":107},[39,1354,82],{"class":53},[14,1356,1357],{},"导入：",[29,1359,1361],{"className":122,"code":1360,"language":124,"meta":34,"style":34},"import type { Lesson } from '..\u002Ftypes\u002Flesson'\n",[36,1362,1363],{"__ignoreMap":34},[39,1364,1365,1368,1370,1373,1376],{"class":41,"line":42},[39,1366,1367],{"class":45},"import",[39,1369,1320],{"class":45},[39,1371,1372],{"class":53}," { Lesson } ",[39,1374,1375],{"class":45},"from",[39,1377,1378],{"class":70}," '..\u002Ftypes\u002Flesson'\n",[14,1380,1381,1384],{},[36,1382,1383],{},"import type"," 表示只导入类型，不导入运行时代码。",[21,1386,1388],{"id":1387},"_9-生产级代码习惯","9. 生产级代码习惯",[14,1390,1391],{},"生产级不是指代码很复杂，而是指它能被维护。",[14,1393,1394],{},"好习惯：",[1396,1397,1398,1402,1405,1408,1411,1414,1417,1420,1423],"ul",{},[1399,1400,1401],"li",{},"文件名和组件名清晰。",[1399,1403,1404],{},"组件职责单一。",[1399,1406,1407],{},"状态放在合适位置。",[1399,1409,1410],{},"API 请求有错误处理。",[1399,1412,1413],{},"表单有校验。",[1399,1415,1416],{},"列表有空状态。",[1399,1418,1419],{},"关键逻辑有测试。",[1399,1421,1422],{},"不把所有逻辑塞进一个巨大组件。",[1399,1424,1425],{},"不依赖“我记得这里不会出错”。",[21,1427,1429],{"id":1428},"_10-目录建议","10. 目录建议",[29,1431,1434],{"className":1432,"code":1433,"language":1300,"meta":34},[1298],"src\u002F\n  assets\u002F\n  components\u002F\n  composables\u002F\n  data\u002F\n  router\u002F\n  services\u002F\n  stores\u002F\n  types\u002F\n  views\u002F\n",[36,1435,1433],{"__ignoreMap":34},[14,1437,1438],{},"职责：",[1396,1440,1441,1447,1453,1459,1465,1471],{},[1399,1442,1443,1446],{},[36,1444,1445],{},"components","：可复用组件。",[1399,1448,1449,1452],{},[36,1450,1451],{},"views","：页面组件。",[1399,1454,1455,1458],{},[36,1456,1457],{},"composables","：组合式函数。",[1399,1460,1461,1464],{},[36,1462,1463],{},"stores","：Pinia。",[1399,1466,1467,1470],{},[36,1468,1469],{},"services","：请求封装。",[1399,1472,1473,1476],{},[36,1474,1475],{},"types","：共享类型。",[21,1478,1480],{"id":1479},"_11-初学者引入-typescript-的节奏","11. 初学者引入 TypeScript 的节奏",[14,1482,1483],{},"不要一开始就被类型吓住。推荐顺序：",[1485,1486,1487,1490,1499,1502,1505],"ol",{},[1399,1488,1489],{},"先用 JavaScript 学会 Vue 核心。",[1399,1491,1492,1493,1495,1496,1027],{},"给数据模型写类型，例如 ",[36,1494,998],{},"、",[36,1497,1498],{},"Todo",[1399,1500,1501],{},"给 props 和 emits 加类型。",[1399,1503,1504],{},"给 API 函数返回值加类型。",[1399,1506,1507],{},"给复杂组合式函数加类型。",[14,1509,1510],{},"TypeScript 是工具，不是学习 Vue 的前置障碍。",[21,1512,1514],{"id":1513},"_12-常见错误","12. 常见错误",[1516,1517,1519],"h3",{"id":1518},"错误一把类型当运行时变量用","错误一：把类型当运行时变量用",[29,1521,1523],{"className":122,"code":1522,"language":124,"meta":34,"style":34},"type Lesson = { title: string }\n\nconsole.log(Lesson) \u002F\u002F 错误，Lesson 是类型，不是值\n",[36,1524,1525,1544,1548],{"__ignoreMap":34},[39,1526,1527,1529,1531,1533,1535,1538,1540,1542],{"class":41,"line":42},[39,1528,318],{"class":45},[39,1530,321],{"class":49},[39,1532,202],{"class":45},[39,1534,1285],{"class":53},[39,1536,1537],{"class":57},"title",[39,1539,139],{"class":45},[39,1541,199],{"class":100},[39,1543,1291],{"class":53},[39,1545,1546],{"class":41,"line":64},[39,1547,89],{"emptyLinePlaceholder":88},[39,1549,1550,1553,1556,1559,1561,1563,1565],{"class":41,"line":79},[39,1551,1552],{"class":49},"console",[39,1554,1555],{"class":53},".",[39,1557,1558],{"class":49},"log",[39,1560,54],{"class":53},[39,1562,998],{"class":49},[39,1564,173],{"class":53},[39,1566,1567],{"class":176},"\u002F\u002F 错误，Lesson 是类型，不是值\n",[14,1569,1570],{},"类型只在开发和编译时存在，运行时没有。",[1516,1572,1574,1575],{"id":1573},"错误二过度使用-any","错误二：过度使用 ",[36,1576,1577],{},"any",[29,1579,1581],{"className":122,"code":1580,"language":124,"meta":34,"style":34},"const lesson: any = {}\n",[36,1582,1583],{"__ignoreMap":34},[39,1584,1585,1587,1589,1591,1594,1596],{"class":41,"line":42},[39,1586,191],{"class":45},[39,1588,384],{"class":100},[39,1590,139],{"class":45},[39,1592,1593],{"class":100}," any",[39,1595,202],{"class":45},[39,1597,1598],{"class":53}," {}\n",[14,1600,1601,1603],{},[36,1602,1577],{}," 会关闭类型检查。初学时可以偶尔救急，但不要大量使用。",[1516,1605,1607],{"id":1606},"错误三忽略-null","错误三：忽略 null",[29,1609,1611],{"className":122,"code":1610,"language":124,"meta":34,"style":34},"const lesson = ref\u003CLesson | null>(null)\n\nconsole.log(lesson.value.title) \u002F\u002F 可能报错，因为 lesson.value 可能是 null\n",[36,1612,1613,1637,1641],{"__ignoreMap":34},[39,1614,1615,1617,1619,1621,1623,1625,1627,1629,1631,1633,1635],{"class":41,"line":42},[39,1616,191],{"class":45},[39,1618,384],{"class":100},[39,1620,202],{"class":45},[39,1622,911],{"class":49},[39,1624,568],{"class":53},[39,1626,998],{"class":49},[39,1628,517],{"class":45},[39,1630,1003],{"class":100},[39,1632,1006],{"class":53},[39,1634,1009],{"class":100},[39,1636,104],{"class":53},[39,1638,1639],{"class":41,"line":64},[39,1640,89],{"emptyLinePlaceholder":88},[39,1642,1643,1646,1648,1651],{"class":41,"line":79},[39,1644,1645],{"class":53},"console.",[39,1647,1558],{"class":49},[39,1649,1650],{"class":53},"(lesson.value.title) ",[39,1652,1653],{"class":176},"\u002F\u002F 可能报错，因为 lesson.value 可能是 null\n",[14,1655,1656],{},"修复：",[29,1658,1660],{"className":122,"code":1659,"language":124,"meta":34,"style":34},"if (lesson.value) {\n  console.log(lesson.value.title)\n}\n",[36,1661,1662,1670,1680],{"__ignoreMap":34},[39,1663,1664,1667],{"class":41,"line":42},[39,1665,1666],{"class":45},"if",[39,1668,1669],{"class":53}," (lesson.value) {\n",[39,1671,1672,1675,1677],{"class":41,"line":64},[39,1673,1674],{"class":53},"  console.",[39,1676,1558],{"class":49},[39,1678,1679],{"class":53},"(lesson.value.title)\n",[39,1681,1682],{"class":41,"line":79},[39,1683,82],{"class":53},[21,1685,1686],{"id":1686},"本章练习",[1485,1688,1689,1698,1704,1713,1719],{},[1399,1690,1691,1692,1694,1695,1697],{},"为 ",[36,1693,998],{}," 和 ",[36,1696,1498],{}," 写类型。",[1399,1699,1700,1701,1703],{},"把 ",[36,1702,706],{}," 的 props 改成 TypeScript 写法。",[1399,1705,1691,1706,1694,1709,1712],{},[36,1707,1708],{},"toggle",[36,1710,1711],{},"remove"," emits 加类型。",[1399,1714,1715,1716,1027],{},"写 ",[36,1717,1718],{},"getLessons(): Promise\u003CLesson[]>",[1399,1720,1721],{},"回答：TypeScript 能防止哪些错误？不能防止哪些错误？",[1723,1724,1725],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":34,"searchDepth":64,"depth":64,"links":1727},[1728,1729,1730,1731,1732,1733,1734,1735,1736,1737,1738,1739,1745],{"id":23,"depth":64,"text":24},{"id":180,"depth":64,"text":181},{"id":437,"depth":64,"text":438},{"id":555,"depth":64,"text":556},{"id":757,"depth":64,"text":758},{"id":890,"depth":64,"text":891},{"id":1091,"depth":64,"text":1092},{"id":1265,"depth":64,"text":1266},{"id":1387,"depth":64,"text":1388},{"id":1428,"depth":64,"text":1429},{"id":1479,"depth":64,"text":1480},{"id":1513,"depth":64,"text":1514,"children":1740},[1741,1742,1744],{"id":1518,"depth":79,"text":1519},{"id":1573,"depth":79,"text":1743},"错误二：过度使用 any",{"id":1606,"depth":79,"text":1607},{"id":1686,"depth":64,"text":1686},"md",{"order":661},"\u002Fsource\u002Fcode\u002Fvue\u002F11-typescript-production",{"title":5,"description":16},"source\u002Fcode\u002Fvue\u002F11-typescript-production","85woldebFyvlgo5LVjJm4ySPNvUt5y-AnLMdgWsqBOI",1784032889391]