[{"data":1,"prerenderedAt":2626},["ShallowReactive",2],{"note:code\u002Fvue\u002F04-components":3},{"id":4,"title":5,"body":6,"description":16,"extension":2620,"meta":2621,"navigation":87,"path":2622,"seo":2623,"stem":2624,"__hash__":2625},"notes\u002Fsource\u002Fcode\u002Fvue\u002F04-components.md","04 组件、Props、事件和插槽",{"type":7,"value":8,"toc":2598},"minimark",[9,13,17,20,25,28,164,167,183,186,297,303,307,310,335,338,349,352,356,359,386,389,517,522,525,560,563,567,570,573,649,652,655,658,666,669,673,676,678,832,834,877,880,969,972,1008,1012,1015,1018,1058,1061,1112,1115,1119,1125,1343,1345,1440,1443,1578,1581,1585,1588,1594,1653,1656,1688,1695,1698,1704,1708,1711,1864,1866,1930,1940,1947,1950,1976,1979,2002,2008,2094,2096,2118,2121,2133,2136,2140,2143,2149,2159,2204,2247,2250,2253,2267,2271,2337,2341,2346,2349,2352,2356,2358,2384,2386,2405,2417,2420,2444,2448,2450,2465,2467,2494,2503,2505,2530,2534,2537,2548,2551,2594],[10,11,5],"h1",{"id":12},"_04-组件props事件和插槽",[14,15,16],"p",{},"组件是 Vue 项目最重要的组织方式。你可以把组件理解成“一个有名字、有输入、有输出的界面单元”。",[14,18,19],{},"本章目标：你能判断什么时候该拆组件，能正确使用 props 和 emit，能理解插槽为什么存在，能避免子组件乱改父组件数据。",[21,22,24],"h2",{"id":23},"_1-为什么需要组件","1. 为什么需要组件",[14,26,27],{},"假设一个页面里有三张课程卡片：",[29,30,35],"pre",{"className":31,"code":32,"language":33,"meta":34,"style":34},"language-vue shiki shiki-themes github-light github-dark","\u003Carticle>\n  \u003Ch2>JavaScript 基础\u003C\u002Fh2>\n  \u003Cp>80 分钟\u003C\u002Fp>\n  \u003Cbutton>标记完成\u003C\u002Fbutton>\n\u003C\u002Farticle>\n\n\u003Carticle>\n  \u003Ch2>Vue 核心语法\u003C\u002Fh2>\n  \u003Cp>90 分钟\u003C\u002Fp>\n  \u003Cbutton>标记完成\u003C\u002Fbutton>\n\u003C\u002Farticle>\n\n\u003Carticle>\n  \u003Ch2>Pinia 状态管理\u003C\u002Fh2>\n  \u003Cp>70 分钟\u003C\u002Fp>\n  \u003Cbutton>标记完成\u003C\u002Fbutton>\n\u003C\u002Farticle>\n","vue","",[36,37,38,54,60,66,72,82,89,98,104,110,115,124,129,138,144,150,155],"code",{"__ignoreMap":34},[39,40,43,47,51],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sVt8B","\u003C",[39,48,50],{"class":49},"s9eBZ","article",[39,52,53],{"class":45},">\n",[39,55,57],{"class":41,"line":56},2,[39,58,59],{"class":45},"  \u003Ch2>JavaScript 基础\u003C\u002Fh2>\n",[39,61,63],{"class":41,"line":62},3,[39,64,65],{"class":45},"  \u003Cp>80 分钟\u003C\u002Fp>\n",[39,67,69],{"class":41,"line":68},4,[39,70,71],{"class":45},"  \u003Cbutton>标记完成\u003C\u002Fbutton>\n",[39,73,75,78,80],{"class":41,"line":74},5,[39,76,77],{"class":45},"\u003C\u002F",[39,79,50],{"class":49},[39,81,53],{"class":45},[39,83,85],{"class":41,"line":84},6,[39,86,88],{"emptyLinePlaceholder":87},true,"\n",[39,90,92,94,96],{"class":41,"line":91},7,[39,93,46],{"class":45},[39,95,50],{"class":49},[39,97,53],{"class":45},[39,99,101],{"class":41,"line":100},8,[39,102,103],{"class":45},"  \u003Ch2>Vue 核心语法\u003C\u002Fh2>\n",[39,105,107],{"class":41,"line":106},9,[39,108,109],{"class":45},"  \u003Cp>90 分钟\u003C\u002Fp>\n",[39,111,113],{"class":41,"line":112},10,[39,114,71],{"class":45},[39,116,118,120,122],{"class":41,"line":117},11,[39,119,77],{"class":45},[39,121,50],{"class":49},[39,123,53],{"class":45},[39,125,127],{"class":41,"line":126},12,[39,128,88],{"emptyLinePlaceholder":87},[39,130,132,134,136],{"class":41,"line":131},13,[39,133,46],{"class":45},[39,135,50],{"class":49},[39,137,53],{"class":45},[39,139,141],{"class":41,"line":140},14,[39,142,143],{"class":45},"  \u003Ch2>Pinia 状态管理\u003C\u002Fh2>\n",[39,145,147],{"class":41,"line":146},15,[39,148,149],{"class":45},"  \u003Cp>70 分钟\u003C\u002Fp>\n",[39,151,153],{"class":41,"line":152},16,[39,154,71],{"class":45},[39,156,158,160,162],{"class":41,"line":157},17,[39,159,77],{"class":45},[39,161,50],{"class":49},[39,163,53],{"class":45},[14,165,166],{},"问题：",[168,169,170,174,177,180],"ul",{},[171,172,173],"li",{},"结构重复。",[171,175,176],{},"样式重复。",[171,178,179],{},"按钮逻辑重复。",[171,181,182],{},"以后改卡片结构要改很多处。",[14,184,185],{},"组件化后：",[29,187,189],{"className":31,"code":188,"language":33,"meta":34,"style":34},"\u003CLessonCard\n  v-for=\"lesson in lessons\"\n  :key=\"lesson.id\"\n  :lesson=\"lesson\"\n  :completed=\"isCompleted(lesson.id)\"\n  @toggle=\"toggleLesson\"\n\u002F>\n",[36,190,191,198,223,241,256,275,292],{"__ignoreMap":34},[39,192,193,195],{"class":41,"line":42},[39,194,46],{"class":45},[39,196,197],{"class":49},"LessonCard\n",[39,199,200,204,207,211,214,217,220],{"class":41,"line":56},[39,201,203],{"class":202},"szBVR","  v-for",[39,205,206],{"class":45},"=",[39,208,210],{"class":209},"sZZnC","\"",[39,212,213],{"class":45},"lesson ",[39,215,216],{"class":202},"in",[39,218,219],{"class":45}," lessons",[39,221,222],{"class":209},"\"\n",[39,224,225,228,232,234,236,239],{"class":41,"line":62},[39,226,227],{"class":45},"  :",[39,229,231],{"class":230},"sScJk","key",[39,233,206],{"class":45},[39,235,210],{"class":209},[39,237,238],{"class":45},"lesson.id",[39,240,222],{"class":209},[39,242,243,245,248,250,252,254],{"class":41,"line":68},[39,244,227],{"class":45},[39,246,247],{"class":230},"lesson",[39,249,206],{"class":45},[39,251,210],{"class":209},[39,253,247],{"class":45},[39,255,222],{"class":209},[39,257,258,260,263,265,267,270,273],{"class":41,"line":74},[39,259,227],{"class":45},[39,261,262],{"class":230},"completed",[39,264,206],{"class":45},[39,266,210],{"class":209},[39,268,269],{"class":230},"isCompleted",[39,271,272],{"class":45},"(lesson.id)",[39,274,222],{"class":209},[39,276,277,280,283,285,287,290],{"class":41,"line":84},[39,278,279],{"class":45},"  @",[39,281,282],{"class":230},"toggle",[39,284,206],{"class":45},[39,286,210],{"class":209},[39,288,289],{"class":45},"toggleLesson",[39,291,222],{"class":209},[39,293,294],{"class":41,"line":91},[39,295,296],{"class":45},"\u002F>\n",[14,298,299,302],{},[36,300,301],{},"LessonCard"," 负责显示一张课程卡片。父组件只负责准备数据和处理事件。",[21,304,306],{"id":305},"_2-什么时候应该拆组件","2. 什么时候应该拆组件",[14,308,309],{},"适合拆：",[168,311,312,315,329,332],{},[171,313,314],{},"一块 UI 重复出现。",[171,316,317,318,321,322,321,325,328],{},"一块 UI 有清晰含义，例如 ",[36,319,320],{},"TodoItem","、",[36,323,324],{},"SearchBox",[36,326,327],{},"UserMenu","。",[171,330,331],{},"一个文件太长，逻辑混在一起。",[171,333,334],{},"你能清楚说出组件的输入和输出。",[14,336,337],{},"不急着拆：",[168,339,340,343,346],{},[171,341,342],{},"只出现一次且很短。",[171,344,345],{},"拆出去后要传很多零碎 props。",[171,347,348],{},"组件职责说不清。",[14,350,351],{},"判断标准不是“越拆越高级”，而是“拆完后边界更清楚”。",[21,353,355],{"id":354},"_3-父组件向子组件传数据props","3. 父组件向子组件传数据：Props",[14,357,358],{},"父组件：",[29,360,362],{"className":31,"code":361,"language":33,"meta":34,"style":34},"\u003CLessonCard :lesson=\"lesson\" \u002F>\n",[36,363,364],{"__ignoreMap":34},[39,365,366,368,370,373,375,377,379,381,383],{"class":41,"line":42},[39,367,46],{"class":45},[39,369,301],{"class":49},[39,371,372],{"class":45}," :",[39,374,247],{"class":230},[39,376,206],{"class":45},[39,378,210],{"class":209},[39,380,247],{"class":45},[39,382,210],{"class":209},[39,384,385],{"class":45}," \u002F>\n",[14,387,388],{},"子组件：",[29,390,392],{"className":31,"code":391,"language":33,"meta":34,"style":34},"\u003Cscript setup>\ndefineProps({\n  lesson: {\n    type: Object,\n    required: true\n  }\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Carticle>\n    \u003Ch2>{{ lesson.title }}\u003C\u002Fh2>\n    \u003Cp>{{ lesson.minutes }} 分钟\u003C\u002Fp>\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n",[36,393,394,406,414,419,424,433,438,443,451,455,464,473,487,500,509],{"__ignoreMap":34},[39,395,396,398,401,404],{"class":41,"line":42},[39,397,46],{"class":45},[39,399,400],{"class":49},"script",[39,402,403],{"class":230}," setup",[39,405,53],{"class":45},[39,407,408,411],{"class":41,"line":56},[39,409,410],{"class":230},"defineProps",[39,412,413],{"class":45},"({\n",[39,415,416],{"class":41,"line":62},[39,417,418],{"class":45},"  lesson: {\n",[39,420,421],{"class":41,"line":68},[39,422,423],{"class":45},"    type: Object,\n",[39,425,426,429],{"class":41,"line":74},[39,427,428],{"class":45},"    required: ",[39,430,432],{"class":431},"sj4cs","true\n",[39,434,435],{"class":41,"line":84},[39,436,437],{"class":45},"  }\n",[39,439,440],{"class":41,"line":91},[39,441,442],{"class":45},"})\n",[39,444,445,447,449],{"class":41,"line":100},[39,446,77],{"class":45},[39,448,400],{"class":49},[39,450,53],{"class":45},[39,452,453],{"class":41,"line":106},[39,454,88],{"emptyLinePlaceholder":87},[39,456,457,459,462],{"class":41,"line":112},[39,458,46],{"class":45},[39,460,461],{"class":49},"template",[39,463,53],{"class":45},[39,465,466,469,471],{"class":41,"line":117},[39,467,468],{"class":45},"  \u003C",[39,470,50],{"class":49},[39,472,53],{"class":45},[39,474,475,478,480,483,485],{"class":41,"line":126},[39,476,477],{"class":45},"    \u003C",[39,479,21],{"class":49},[39,481,482],{"class":45},">{{ lesson.title }}\u003C\u002F",[39,484,21],{"class":49},[39,486,53],{"class":45},[39,488,489,491,493,496,498],{"class":41,"line":131},[39,490,477],{"class":45},[39,492,14],{"class":49},[39,494,495],{"class":45},">{{ lesson.minutes }} 分钟\u003C\u002F",[39,497,14],{"class":49},[39,499,53],{"class":45},[39,501,502,505,507],{"class":41,"line":140},[39,503,504],{"class":45},"  \u003C\u002F",[39,506,50],{"class":49},[39,508,53],{"class":45},[39,510,511,513,515],{"class":41,"line":146},[39,512,77],{"class":45},[39,514,461],{"class":49},[39,516,53],{"class":45},[14,518,519,521],{},[36,520,410],{}," 声明这个组件需要外部传什么数据。",[14,523,524],{},"这就像函数参数：",[29,526,530],{"className":527,"code":528,"language":529,"meta":34,"style":34},"language-js shiki shiki-themes github-light github-dark","function LessonCard(lesson) {\n  \u002F\u002F 使用 lesson\n}\n","js",[36,531,532,549,555],{"__ignoreMap":34},[39,533,534,537,540,543,546],{"class":41,"line":42},[39,535,536],{"class":202},"function",[39,538,539],{"class":230}," LessonCard",[39,541,542],{"class":45},"(",[39,544,247],{"class":545},"s4XuR",[39,547,548],{"class":45},") {\n",[39,550,551],{"class":41,"line":56},[39,552,554],{"class":553},"sJ8bj","  \u002F\u002F 使用 lesson\n",[39,556,557],{"class":41,"line":62},[39,558,559],{"class":45},"}\n",[14,561,562],{},"组件的 props 是组件的输入。",[21,564,566],{"id":565},"_4-为什么-props-应该只读","4. 为什么 props 应该只读",[14,568,569],{},"子组件不应该直接修改 props。",[14,571,572],{},"不推荐：",[29,574,576],{"className":31,"code":575,"language":33,"meta":34,"style":34},"\u003Cscript setup>\nconst props = defineProps({\n  lesson: Object\n})\n\nfunction complete() {\n  props.lesson.completed = true\n}\n\u003C\u002Fscript>\n",[36,577,578,588,604,609,613,617,627,637,641],{"__ignoreMap":34},[39,579,580,582,584,586],{"class":41,"line":42},[39,581,46],{"class":45},[39,583,400],{"class":49},[39,585,403],{"class":230},[39,587,53],{"class":45},[39,589,590,593,596,599,602],{"class":41,"line":56},[39,591,592],{"class":202},"const",[39,594,595],{"class":431}," props",[39,597,598],{"class":202}," =",[39,600,601],{"class":230}," defineProps",[39,603,413],{"class":45},[39,605,606],{"class":41,"line":62},[39,607,608],{"class":45},"  lesson: Object\n",[39,610,611],{"class":41,"line":68},[39,612,442],{"class":45},[39,614,615],{"class":41,"line":74},[39,616,88],{"emptyLinePlaceholder":87},[39,618,619,621,624],{"class":41,"line":84},[39,620,536],{"class":202},[39,622,623],{"class":230}," complete",[39,625,626],{"class":45},"() {\n",[39,628,629,632,634],{"class":41,"line":91},[39,630,631],{"class":45},"  props.lesson.completed ",[39,633,206],{"class":202},[39,635,636],{"class":431}," true\n",[39,638,639],{"class":41,"line":100},[39,640,559],{"class":45},[39,642,643,645,647],{"class":41,"line":106},[39,644,77],{"class":45},[39,646,400],{"class":49},[39,648,53],{"class":45},[14,650,651],{},"为什么？",[14,653,654],{},"因为数据是父组件传下来的。如果子组件直接改，父组件会很难追踪数据到底是谁改的。",[14,656,657],{},"更推荐的模式：",[29,659,664],{"className":660,"code":662,"language":663,"meta":34},[661],"language-text","父组件拥有数据\n  ↓ props\n子组件显示数据\n  ↓ emit\n父组件收到事件后修改数据\n","text",[36,665,662],{"__ignoreMap":34},[14,667,668],{},"这叫单向数据流。",[21,670,672],{"id":671},"_5-子组件通知父组件emit","5. 子组件通知父组件：emit",[14,674,675],{},"子组件不能直接决定父组件的数据怎么改，但可以通知父组件“用户做了什么”。",[14,677,388],{},[29,679,681],{"className":31,"code":680,"language":33,"meta":34,"style":34},"\u003Cscript setup>\nconst props = defineProps({\n  lesson: {\n    type: Object,\n    required: true\n  }\n})\n\nconst emit = defineEmits(['toggle'])\n\nfunction handleClick() {\n  emit('toggle', props.lesson.id)\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton @click=\"handleClick\">切换完成\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[36,682,683,693,705,709,713,719,723,727,731,752,756,765,777,781,789,793,801,823],{"__ignoreMap":34},[39,684,685,687,689,691],{"class":41,"line":42},[39,686,46],{"class":45},[39,688,400],{"class":49},[39,690,403],{"class":230},[39,692,53],{"class":45},[39,694,695,697,699,701,703],{"class":41,"line":56},[39,696,592],{"class":202},[39,698,595],{"class":431},[39,700,598],{"class":202},[39,702,601],{"class":230},[39,704,413],{"class":45},[39,706,707],{"class":41,"line":62},[39,708,418],{"class":45},[39,710,711],{"class":41,"line":68},[39,712,423],{"class":45},[39,714,715,717],{"class":41,"line":74},[39,716,428],{"class":45},[39,718,432],{"class":431},[39,720,721],{"class":41,"line":84},[39,722,437],{"class":45},[39,724,725],{"class":41,"line":91},[39,726,442],{"class":45},[39,728,729],{"class":41,"line":100},[39,730,88],{"emptyLinePlaceholder":87},[39,732,733,735,738,740,743,746,749],{"class":41,"line":106},[39,734,592],{"class":202},[39,736,737],{"class":431}," emit",[39,739,598],{"class":202},[39,741,742],{"class":230}," defineEmits",[39,744,745],{"class":45},"([",[39,747,748],{"class":209},"'toggle'",[39,750,751],{"class":45},"])\n",[39,753,754],{"class":41,"line":112},[39,755,88],{"emptyLinePlaceholder":87},[39,757,758,760,763],{"class":41,"line":117},[39,759,536],{"class":202},[39,761,762],{"class":230}," handleClick",[39,764,626],{"class":45},[39,766,767,770,772,774],{"class":41,"line":126},[39,768,769],{"class":230},"  emit",[39,771,542],{"class":45},[39,773,748],{"class":209},[39,775,776],{"class":45},", props.lesson.id)\n",[39,778,779],{"class":41,"line":131},[39,780,559],{"class":45},[39,782,783,785,787],{"class":41,"line":140},[39,784,77],{"class":45},[39,786,400],{"class":49},[39,788,53],{"class":45},[39,790,791],{"class":41,"line":146},[39,792,88],{"emptyLinePlaceholder":87},[39,794,795,797,799],{"class":41,"line":152},[39,796,46],{"class":45},[39,798,461],{"class":49},[39,800,53],{"class":45},[39,802,803,805,808,811,813,816,819,821],{"class":41,"line":157},[39,804,468],{"class":45},[39,806,807],{"class":49},"button",[39,809,810],{"class":230}," @click",[39,812,206],{"class":45},[39,814,815],{"class":209},"\"handleClick\"",[39,817,818],{"class":45},">切换完成\u003C\u002F",[39,820,807],{"class":49},[39,822,53],{"class":45},[39,824,826,828,830],{"class":41,"line":825},18,[39,827,77],{"class":45},[39,829,461],{"class":49},[39,831,53],{"class":45},[14,833,358],{},[29,835,837],{"className":31,"code":836,"language":33,"meta":34,"style":34},"\u003CLessonCard\n  :lesson=\"lesson\"\n  @toggle=\"toggleLesson\"\n\u002F>\n",[36,838,839,845,859,873],{"__ignoreMap":34},[39,840,841,843],{"class":41,"line":42},[39,842,46],{"class":45},[39,844,197],{"class":49},[39,846,847,849,851,853,855,857],{"class":41,"line":56},[39,848,227],{"class":45},[39,850,247],{"class":230},[39,852,206],{"class":45},[39,854,210],{"class":209},[39,856,247],{"class":45},[39,858,222],{"class":209},[39,860,861,863,865,867,869,871],{"class":41,"line":62},[39,862,279],{"class":45},[39,864,282],{"class":230},[39,866,206],{"class":45},[39,868,210],{"class":209},[39,870,289],{"class":45},[39,872,222],{"class":209},[39,874,875],{"class":41,"line":68},[39,876,296],{"class":45},[14,878,879],{},"父组件函数：",[29,881,883],{"className":527,"code":882,"language":529,"meta":34,"style":34},"function toggleLesson(id) {\n  const lesson = lessons.value.find((item) => item.id === id)\n\n  if (lesson) {\n    lesson.completed = !lesson.completed\n  }\n}\n",[36,884,885,899,936,940,948,961,965],{"__ignoreMap":34},[39,886,887,889,892,894,897],{"class":41,"line":42},[39,888,536],{"class":202},[39,890,891],{"class":230}," toggleLesson",[39,893,542],{"class":45},[39,895,896],{"class":545},"id",[39,898,548],{"class":45},[39,900,901,904,907,909,912,915,918,921,924,927,930,933],{"class":41,"line":56},[39,902,903],{"class":202},"  const",[39,905,906],{"class":431}," lesson",[39,908,598],{"class":202},[39,910,911],{"class":45}," lessons.value.",[39,913,914],{"class":230},"find",[39,916,917],{"class":45},"((",[39,919,920],{"class":545},"item",[39,922,923],{"class":45},") ",[39,925,926],{"class":202},"=>",[39,928,929],{"class":45}," item.id ",[39,931,932],{"class":202},"===",[39,934,935],{"class":45}," id)\n",[39,937,938],{"class":41,"line":62},[39,939,88],{"emptyLinePlaceholder":87},[39,941,942,945],{"class":41,"line":68},[39,943,944],{"class":202},"  if",[39,946,947],{"class":45}," (lesson) {\n",[39,949,950,953,955,958],{"class":41,"line":74},[39,951,952],{"class":45},"    lesson.completed ",[39,954,206],{"class":202},[39,956,957],{"class":202}," !",[39,959,960],{"class":45},"lesson.completed\n",[39,962,963],{"class":41,"line":84},[39,964,437],{"class":45},[39,966,967],{"class":41,"line":91},[39,968,559],{"class":45},[14,970,971],{},"流程：",[973,974,975,981,984,987,993,1002,1005],"ol",{},[171,976,977,978,980],{},"父组件把 ",[36,979,247],{}," 传给子组件。",[171,982,983],{},"子组件显示课程。",[171,985,986],{},"用户点击子组件按钮。",[171,988,989,990,328],{},"子组件 ",[36,991,992],{},"emit('toggle', id)",[171,994,995,996,999,1000,328],{},"父组件监听 ",[36,997,998],{},"@toggle","，调用 ",[36,1001,289],{},[171,1003,1004],{},"父组件修改自己的数据。",[171,1006,1007],{},"数据变化后，新的 props 再传给子组件。",[21,1009,1011],{"id":1010},"_6-事件名怎么设计","6. 事件名怎么设计",[14,1013,1014],{},"事件名应该表达“发生了什么”，不要太模糊。",[14,1016,1017],{},"不清晰：",[29,1019,1021],{"className":527,"code":1020,"language":529,"meta":34,"style":34},"emit('change')\nemit('click')\nemit('update')\n",[36,1022,1023,1036,1047],{"__ignoreMap":34},[39,1024,1025,1028,1030,1033],{"class":41,"line":42},[39,1026,1027],{"class":230},"emit",[39,1029,542],{"class":45},[39,1031,1032],{"class":209},"'change'",[39,1034,1035],{"class":45},")\n",[39,1037,1038,1040,1042,1045],{"class":41,"line":56},[39,1039,1027],{"class":230},[39,1041,542],{"class":45},[39,1043,1044],{"class":209},"'click'",[39,1046,1035],{"class":45},[39,1048,1049,1051,1053,1056],{"class":41,"line":62},[39,1050,1027],{"class":230},[39,1052,542],{"class":45},[39,1054,1055],{"class":209},"'update'",[39,1057,1035],{"class":45},[14,1059,1060],{},"更清晰：",[29,1062,1064],{"className":527,"code":1063,"language":529,"meta":34,"style":34},"emit('toggle', id)\nemit('remove', id)\nemit('submit', form)\nemit('select', option)\n",[36,1065,1066,1077,1088,1100],{"__ignoreMap":34},[39,1067,1068,1070,1072,1074],{"class":41,"line":42},[39,1069,1027],{"class":230},[39,1071,542],{"class":45},[39,1073,748],{"class":209},[39,1075,1076],{"class":45},", id)\n",[39,1078,1079,1081,1083,1086],{"class":41,"line":56},[39,1080,1027],{"class":230},[39,1082,542],{"class":45},[39,1084,1085],{"class":209},"'remove'",[39,1087,1076],{"class":45},[39,1089,1090,1092,1094,1097],{"class":41,"line":62},[39,1091,1027],{"class":230},[39,1093,542],{"class":45},[39,1095,1096],{"class":209},"'submit'",[39,1098,1099],{"class":45},", form)\n",[39,1101,1102,1104,1106,1109],{"class":41,"line":68},[39,1103,1027],{"class":230},[39,1105,542],{"class":45},[39,1107,1108],{"class":209},"'select'",[39,1110,1111],{"class":45},", option)\n",[14,1113,1114],{},"组件事件不是 DOM 事件，它是你定义的业务事件。",[21,1116,1118],{"id":1117},"_7-完整例子todoitem","7. 完整例子：TodoItem",[14,1120,989,1121,1124],{},[36,1122,1123],{},"TodoItem.vue","：",[29,1126,1128],{"className":31,"code":1127,"language":33,"meta":34,"style":34},"\u003Cscript setup>\ndefineProps({\n  todo: {\n    type: Object,\n    required: true\n  }\n})\n\nconst emit = defineEmits(['toggle', 'remove'])\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cli>\n    \u003Clabel>\n      \u003Cinput\n        type=\"checkbox\"\n        :checked=\"todo.done\"\n        @change=\"emit('toggle', todo.id)\"\n      >\n      \u003Cspan>{{ todo.title }}\u003C\u002Fspan>\n    \u003C\u002Flabel>\n\n    \u003Cbutton @click=\"emit('remove', todo.id)\">删除\u003C\u002Fbutton>\n  \u003C\u002Fli>\n\u003C\u002Ftemplate>\n",[36,1129,1130,1140,1146,1151,1155,1161,1165,1169,1173,1194,1202,1206,1214,1222,1231,1239,1249,1259,1269,1275,1289,1299,1304,1325,1334],{"__ignoreMap":34},[39,1131,1132,1134,1136,1138],{"class":41,"line":42},[39,1133,46],{"class":45},[39,1135,400],{"class":49},[39,1137,403],{"class":230},[39,1139,53],{"class":45},[39,1141,1142,1144],{"class":41,"line":56},[39,1143,410],{"class":230},[39,1145,413],{"class":45},[39,1147,1148],{"class":41,"line":62},[39,1149,1150],{"class":45},"  todo: {\n",[39,1152,1153],{"class":41,"line":68},[39,1154,423],{"class":45},[39,1156,1157,1159],{"class":41,"line":74},[39,1158,428],{"class":45},[39,1160,432],{"class":431},[39,1162,1163],{"class":41,"line":84},[39,1164,437],{"class":45},[39,1166,1167],{"class":41,"line":91},[39,1168,442],{"class":45},[39,1170,1171],{"class":41,"line":100},[39,1172,88],{"emptyLinePlaceholder":87},[39,1174,1175,1177,1179,1181,1183,1185,1187,1190,1192],{"class":41,"line":106},[39,1176,592],{"class":202},[39,1178,737],{"class":431},[39,1180,598],{"class":202},[39,1182,742],{"class":230},[39,1184,745],{"class":45},[39,1186,748],{"class":209},[39,1188,1189],{"class":45},", ",[39,1191,1085],{"class":209},[39,1193,751],{"class":45},[39,1195,1196,1198,1200],{"class":41,"line":112},[39,1197,77],{"class":45},[39,1199,400],{"class":49},[39,1201,53],{"class":45},[39,1203,1204],{"class":41,"line":117},[39,1205,88],{"emptyLinePlaceholder":87},[39,1207,1208,1210,1212],{"class":41,"line":126},[39,1209,46],{"class":45},[39,1211,461],{"class":49},[39,1213,53],{"class":45},[39,1215,1216,1218,1220],{"class":41,"line":131},[39,1217,468],{"class":45},[39,1219,171],{"class":49},[39,1221,53],{"class":45},[39,1223,1224,1226,1229],{"class":41,"line":140},[39,1225,477],{"class":45},[39,1227,1228],{"class":49},"label",[39,1230,53],{"class":45},[39,1232,1233,1236],{"class":41,"line":146},[39,1234,1235],{"class":45},"      \u003C",[39,1237,1238],{"class":49},"input\n",[39,1240,1241,1244,1246],{"class":41,"line":152},[39,1242,1243],{"class":230},"        type",[39,1245,206],{"class":45},[39,1247,1248],{"class":209},"\"checkbox\"\n",[39,1250,1251,1254,1256],{"class":41,"line":157},[39,1252,1253],{"class":230},"        :checked",[39,1255,206],{"class":45},[39,1257,1258],{"class":209},"\"todo.done\"\n",[39,1260,1261,1264,1266],{"class":41,"line":825},[39,1262,1263],{"class":230},"        @change",[39,1265,206],{"class":45},[39,1267,1268],{"class":209},"\"emit('toggle', todo.id)\"\n",[39,1270,1272],{"class":41,"line":1271},19,[39,1273,1274],{"class":45},"      >\n",[39,1276,1278,1280,1282,1285,1287],{"class":41,"line":1277},20,[39,1279,1235],{"class":45},[39,1281,39],{"class":49},[39,1283,1284],{"class":45},">{{ todo.title }}\u003C\u002F",[39,1286,39],{"class":49},[39,1288,53],{"class":45},[39,1290,1292,1295,1297],{"class":41,"line":1291},21,[39,1293,1294],{"class":45},"    \u003C\u002F",[39,1296,1228],{"class":49},[39,1298,53],{"class":45},[39,1300,1302],{"class":41,"line":1301},22,[39,1303,88],{"emptyLinePlaceholder":87},[39,1305,1307,1309,1311,1313,1315,1318,1321,1323],{"class":41,"line":1306},23,[39,1308,477],{"class":45},[39,1310,807],{"class":49},[39,1312,810],{"class":230},[39,1314,206],{"class":45},[39,1316,1317],{"class":209},"\"emit('remove', todo.id)\"",[39,1319,1320],{"class":45},">删除\u003C\u002F",[39,1322,807],{"class":49},[39,1324,53],{"class":45},[39,1326,1328,1330,1332],{"class":41,"line":1327},24,[39,1329,504],{"class":45},[39,1331,171],{"class":49},[39,1333,53],{"class":45},[39,1335,1337,1339,1341],{"class":41,"line":1336},25,[39,1338,77],{"class":45},[39,1340,461],{"class":49},[39,1342,53],{"class":45},[14,1344,358],{},[29,1346,1348],{"className":31,"code":1347,"language":33,"meta":34,"style":34},"\u003CTodoItem\n  v-for=\"todo in todos\"\n  :key=\"todo.id\"\n  :todo=\"todo\"\n  @toggle=\"toggleTodo\"\n  @remove=\"removeTodo\"\n\u002F>\n",[36,1349,1350,1357,1375,1390,1405,1420,1436],{"__ignoreMap":34},[39,1351,1352,1354],{"class":41,"line":42},[39,1353,46],{"class":45},[39,1355,1356],{"class":49},"TodoItem\n",[39,1358,1359,1361,1363,1365,1368,1370,1373],{"class":41,"line":56},[39,1360,203],{"class":202},[39,1362,206],{"class":45},[39,1364,210],{"class":209},[39,1366,1367],{"class":45},"todo ",[39,1369,216],{"class":202},[39,1371,1372],{"class":45}," todos",[39,1374,222],{"class":209},[39,1376,1377,1379,1381,1383,1385,1388],{"class":41,"line":62},[39,1378,227],{"class":45},[39,1380,231],{"class":230},[39,1382,206],{"class":45},[39,1384,210],{"class":209},[39,1386,1387],{"class":45},"todo.id",[39,1389,222],{"class":209},[39,1391,1392,1394,1397,1399,1401,1403],{"class":41,"line":68},[39,1393,227],{"class":45},[39,1395,1396],{"class":230},"todo",[39,1398,206],{"class":45},[39,1400,210],{"class":209},[39,1402,1396],{"class":45},[39,1404,222],{"class":209},[39,1406,1407,1409,1411,1413,1415,1418],{"class":41,"line":74},[39,1408,279],{"class":45},[39,1410,282],{"class":230},[39,1412,206],{"class":45},[39,1414,210],{"class":209},[39,1416,1417],{"class":45},"toggleTodo",[39,1419,222],{"class":209},[39,1421,1422,1424,1427,1429,1431,1434],{"class":41,"line":84},[39,1423,279],{"class":45},[39,1425,1426],{"class":230},"remove",[39,1428,206],{"class":45},[39,1430,210],{"class":209},[39,1432,1433],{"class":45},"removeTodo",[39,1435,222],{"class":209},[39,1437,1438],{"class":41,"line":91},[39,1439,296],{"class":45},[14,1441,1442],{},"父组件逻辑：",[29,1444,1446],{"className":527,"code":1445,"language":529,"meta":34,"style":34},"function toggleTodo(id) {\n  todos.value = todos.value.map((todo) => {\n    return todo.id === id ? { ...todo, done: !todo.done } : todo\n  })\n}\n\nfunction removeTodo(id) {\n  todos.value = todos.value.filter((todo) => todo.id !== id)\n}\n",[36,1447,1448,1461,1485,1522,1527,1531,1535,1548,1574],{"__ignoreMap":34},[39,1449,1450,1452,1455,1457,1459],{"class":41,"line":42},[39,1451,536],{"class":202},[39,1453,1454],{"class":230}," toggleTodo",[39,1456,542],{"class":45},[39,1458,896],{"class":545},[39,1460,548],{"class":45},[39,1462,1463,1466,1468,1471,1474,1476,1478,1480,1482],{"class":41,"line":56},[39,1464,1465],{"class":45},"  todos.value ",[39,1467,206],{"class":202},[39,1469,1470],{"class":45}," todos.value.",[39,1472,1473],{"class":230},"map",[39,1475,917],{"class":45},[39,1477,1396],{"class":545},[39,1479,923],{"class":45},[39,1481,926],{"class":202},[39,1483,1484],{"class":45}," {\n",[39,1486,1487,1490,1493,1495,1498,1501,1504,1507,1510,1513,1516,1519],{"class":41,"line":62},[39,1488,1489],{"class":202},"    return",[39,1491,1492],{"class":45}," todo.id ",[39,1494,932],{"class":202},[39,1496,1497],{"class":45}," id ",[39,1499,1500],{"class":202},"?",[39,1502,1503],{"class":45}," { ",[39,1505,1506],{"class":202},"...",[39,1508,1509],{"class":45},"todo, done: ",[39,1511,1512],{"class":202},"!",[39,1514,1515],{"class":45},"todo.done } ",[39,1517,1518],{"class":202},":",[39,1520,1521],{"class":45}," todo\n",[39,1523,1524],{"class":41,"line":68},[39,1525,1526],{"class":45},"  })\n",[39,1528,1529],{"class":41,"line":74},[39,1530,559],{"class":45},[39,1532,1533],{"class":41,"line":84},[39,1534,88],{"emptyLinePlaceholder":87},[39,1536,1537,1539,1542,1544,1546],{"class":41,"line":91},[39,1538,536],{"class":202},[39,1540,1541],{"class":230}," removeTodo",[39,1543,542],{"class":45},[39,1545,896],{"class":545},[39,1547,548],{"class":45},[39,1549,1550,1552,1554,1556,1559,1561,1563,1565,1567,1569,1572],{"class":41,"line":100},[39,1551,1465],{"class":45},[39,1553,206],{"class":202},[39,1555,1470],{"class":45},[39,1557,1558],{"class":230},"filter",[39,1560,917],{"class":45},[39,1562,1396],{"class":545},[39,1564,923],{"class":45},[39,1566,926],{"class":202},[39,1568,1492],{"class":45},[39,1570,1571],{"class":202},"!==",[39,1573,935],{"class":45},[39,1575,1576],{"class":41,"line":106},[39,1577,559],{"class":45},[14,1579,1580],{},"注意：子组件只负责“告诉父组件用户点击了哪个任务”，真正修改数组的是父组件。",[21,1582,1584],{"id":1583},"_8-插槽让父组件决定子组件内部内容","8. 插槽：让父组件决定子组件内部内容",[14,1586,1587],{},"有些组件不应该关心具体内容，只负责外壳。",[14,1589,1590,1591,1124],{},"例如 ",[36,1592,1593],{},"BasePanel.vue",[29,1595,1597],{"className":31,"code":1596,"language":33,"meta":34,"style":34},"\u003Ctemplate>\n  \u003Csection class=\"panel\">\n    \u003Cslot \u002F>\n  \u003C\u002Fsection>\n\u003C\u002Ftemplate>\n",[36,1598,1599,1607,1624,1637,1645],{"__ignoreMap":34},[39,1600,1601,1603,1605],{"class":41,"line":42},[39,1602,46],{"class":45},[39,1604,461],{"class":49},[39,1606,53],{"class":45},[39,1608,1609,1611,1614,1617,1619,1622],{"class":41,"line":56},[39,1610,468],{"class":45},[39,1612,1613],{"class":49},"section",[39,1615,1616],{"class":230}," class",[39,1618,206],{"class":45},[39,1620,1621],{"class":209},"\"panel\"",[39,1623,53],{"class":45},[39,1625,1626,1628,1631,1635],{"class":41,"line":62},[39,1627,477],{"class":45},[39,1629,1630],{"class":49},"slot",[39,1632,1634],{"class":1633},"s7hpK"," \u002F",[39,1636,53],{"class":45},[39,1638,1639,1641,1643],{"class":41,"line":68},[39,1640,504],{"class":45},[39,1642,1613],{"class":49},[39,1644,53],{"class":45},[39,1646,1647,1649,1651],{"class":41,"line":74},[39,1648,77],{"class":45},[39,1650,461],{"class":49},[39,1652,53],{"class":45},[14,1654,1655],{},"使用：",[29,1657,1659],{"className":31,"code":1658,"language":33,"meta":34,"style":34},"\u003CBasePanel>\n  \u003Ch2>学习进度\u003C\u002Fh2>\n  \u003Cp>你已经完成 3 章。\u003C\u002Fp>\n\u003C\u002FBasePanel>\n",[36,1660,1661,1670,1675,1680],{"__ignoreMap":34},[39,1662,1663,1665,1668],{"class":41,"line":42},[39,1664,46],{"class":45},[39,1666,1667],{"class":49},"BasePanel",[39,1669,53],{"class":45},[39,1671,1672],{"class":41,"line":56},[39,1673,1674],{"class":45},"  \u003Ch2>学习进度\u003C\u002Fh2>\n",[39,1676,1677],{"class":41,"line":62},[39,1678,1679],{"class":45},"  \u003Cp>你已经完成 3 章。\u003C\u002Fp>\n",[39,1681,1682,1684,1686],{"class":41,"line":68},[39,1683,77],{"class":45},[39,1685,1667],{"class":49},[39,1687,53],{"class":45},[14,1689,1690,1691,1694],{},"渲染时，",[36,1692,1693],{},"\u003Cslot \u002F>"," 会被父组件传进来的内容替换。",[14,1696,1697],{},"这就像：",[29,1699,1702],{"className":1700,"code":1701,"language":663,"meta":34},[661],"BasePanel 提供盒子\n父组件提供盒子里面放什么\n",[36,1703,1701],{"__ignoreMap":34},[21,1705,1707],{"id":1706},"_9-具名插槽","9. 具名插槽",[14,1709,1710],{},"如果一个组件有多个可填区域，用具名插槽。",[29,1712,1714],{"className":31,"code":1713,"language":33,"meta":34,"style":34},"\u003C!-- CourseCard.vue -->\n\u003Ctemplate>\n  \u003Carticle class=\"card\">\n    \u003Cheader>\n      \u003Cslot name=\"title\" \u002F>\n    \u003C\u002Fheader>\n\n    \u003Cmain>\n      \u003Cslot \u002F>\n    \u003C\u002Fmain>\n\n    \u003Cfooter>\n      \u003Cslot name=\"actions\" \u002F>\n    \u003C\u002Ffooter>\n  \u003C\u002Farticle>\n\u003C\u002Ftemplate>\n",[36,1715,1716,1721,1729,1744,1753,1771,1779,1783,1792,1802,1810,1814,1823,1840,1848,1856],{"__ignoreMap":34},[39,1717,1718],{"class":41,"line":42},[39,1719,1720],{"class":553},"\u003C!-- CourseCard.vue -->\n",[39,1722,1723,1725,1727],{"class":41,"line":56},[39,1724,46],{"class":45},[39,1726,461],{"class":49},[39,1728,53],{"class":45},[39,1730,1731,1733,1735,1737,1739,1742],{"class":41,"line":62},[39,1732,468],{"class":45},[39,1734,50],{"class":49},[39,1736,1616],{"class":230},[39,1738,206],{"class":45},[39,1740,1741],{"class":209},"\"card\"",[39,1743,53],{"class":45},[39,1745,1746,1748,1751],{"class":41,"line":68},[39,1747,477],{"class":45},[39,1749,1750],{"class":49},"header",[39,1752,53],{"class":45},[39,1754,1755,1757,1759,1762,1764,1767,1769],{"class":41,"line":74},[39,1756,1235],{"class":45},[39,1758,1630],{"class":49},[39,1760,1761],{"class":230}," name",[39,1763,206],{"class":45},[39,1765,1766],{"class":209},"\"title\"",[39,1768,1634],{"class":1633},[39,1770,53],{"class":45},[39,1772,1773,1775,1777],{"class":41,"line":84},[39,1774,1294],{"class":45},[39,1776,1750],{"class":49},[39,1778,53],{"class":45},[39,1780,1781],{"class":41,"line":91},[39,1782,88],{"emptyLinePlaceholder":87},[39,1784,1785,1787,1790],{"class":41,"line":100},[39,1786,477],{"class":45},[39,1788,1789],{"class":49},"main",[39,1791,53],{"class":45},[39,1793,1794,1796,1798,1800],{"class":41,"line":106},[39,1795,1235],{"class":45},[39,1797,1630],{"class":49},[39,1799,1634],{"class":1633},[39,1801,53],{"class":45},[39,1803,1804,1806,1808],{"class":41,"line":112},[39,1805,1294],{"class":45},[39,1807,1789],{"class":49},[39,1809,53],{"class":45},[39,1811,1812],{"class":41,"line":117},[39,1813,88],{"emptyLinePlaceholder":87},[39,1815,1816,1818,1821],{"class":41,"line":126},[39,1817,477],{"class":45},[39,1819,1820],{"class":49},"footer",[39,1822,53],{"class":45},[39,1824,1825,1827,1829,1831,1833,1836,1838],{"class":41,"line":131},[39,1826,1235],{"class":45},[39,1828,1630],{"class":49},[39,1830,1761],{"class":230},[39,1832,206],{"class":45},[39,1834,1835],{"class":209},"\"actions\"",[39,1837,1634],{"class":1633},[39,1839,53],{"class":45},[39,1841,1842,1844,1846],{"class":41,"line":140},[39,1843,1294],{"class":45},[39,1845,1820],{"class":49},[39,1847,53],{"class":45},[39,1849,1850,1852,1854],{"class":41,"line":146},[39,1851,504],{"class":45},[39,1853,50],{"class":49},[39,1855,53],{"class":45},[39,1857,1858,1860,1862],{"class":41,"line":152},[39,1859,77],{"class":45},[39,1861,461],{"class":49},[39,1863,53],{"class":45},[14,1865,1655],{},[29,1867,1869],{"className":31,"code":1868,"language":33,"meta":34,"style":34},"\u003CCourseCard>\n  \u003Ctemplate #title>\n    \u003Ch2>Vue Router\u003C\u002Fh2>\n  \u003C\u002Ftemplate>\n\n  \u003Cp>学习页面跳转和动态路由。\u003C\u002Fp>\n\n  \u003Ctemplate #actions>\n    \u003Cbutton>开始学习\u003C\u002Fbutton>\n  \u003C\u002Ftemplate>\n\u003C\u002FCourseCard>\n",[36,1870,1871,1880,1885,1890,1895,1899,1904,1908,1913,1918,1922],{"__ignoreMap":34},[39,1872,1873,1875,1878],{"class":41,"line":42},[39,1874,46],{"class":45},[39,1876,1877],{"class":49},"CourseCard",[39,1879,53],{"class":45},[39,1881,1882],{"class":41,"line":56},[39,1883,1884],{"class":45},"  \u003Ctemplate #title>\n",[39,1886,1887],{"class":41,"line":62},[39,1888,1889],{"class":45},"    \u003Ch2>Vue Router\u003C\u002Fh2>\n",[39,1891,1892],{"class":41,"line":68},[39,1893,1894],{"class":45},"  \u003C\u002Ftemplate>\n",[39,1896,1897],{"class":41,"line":74},[39,1898,88],{"emptyLinePlaceholder":87},[39,1900,1901],{"class":41,"line":84},[39,1902,1903],{"class":45},"  \u003Cp>学习页面跳转和动态路由。\u003C\u002Fp>\n",[39,1905,1906],{"class":41,"line":91},[39,1907,88],{"emptyLinePlaceholder":87},[39,1909,1910],{"class":41,"line":100},[39,1911,1912],{"class":45},"  \u003Ctemplate #actions>\n",[39,1914,1915],{"class":41,"line":106},[39,1916,1917],{"class":45},"    \u003Cbutton>开始学习\u003C\u002Fbutton>\n",[39,1919,1920],{"class":41,"line":112},[39,1921,1894],{"class":45},[39,1923,1924,1926,1928],{"class":41,"line":117},[39,1925,77],{"class":45},[39,1927,1877],{"class":49},[39,1929,53],{"class":45},[14,1931,1932,1935,1936,1939],{},[36,1933,1934],{},"#title"," 是 ",[36,1937,1938],{},"v-slot:title"," 的简写。",[21,1941,1943,1944],{"id":1942},"_10-组件-v-model","10. 组件 ",[36,1945,1946],{},"v-model",[14,1948,1949],{},"普通输入框：",[29,1951,1953],{"className":31,"code":1952,"language":33,"meta":34,"style":34},"\u003Cinput v-model=\"keyword\">\n",[36,1954,1955],{"__ignoreMap":34},[39,1956,1957,1959,1962,1965,1967,1969,1972,1974],{"class":41,"line":42},[39,1958,46],{"class":45},[39,1960,1961],{"class":49},"input",[39,1963,1964],{"class":230}," v-model",[39,1966,206],{"class":45},[39,1968,210],{"class":209},[39,1970,1971],{"class":45},"keyword",[39,1973,210],{"class":209},[39,1975,53],{"class":45},[14,1977,1978],{},"如果你自己写一个搜索框组件，也希望这样用：",[29,1980,1982],{"className":31,"code":1981,"language":33,"meta":34,"style":34},"\u003CSearchBox v-model=\"keyword\" \u002F>\n",[36,1983,1984],{"__ignoreMap":34},[39,1985,1986,1988,1990,1992,1994,1996,1998,2000],{"class":41,"line":42},[39,1987,46],{"class":45},[39,1989,324],{"class":49},[39,1991,1964],{"class":230},[39,1993,206],{"class":45},[39,1995,210],{"class":209},[39,1997,1971],{"class":45},[39,1999,210],{"class":209},[39,2001,385],{"class":45},[14,2003,2004,2005,1124],{},"可以在子组件中使用 ",[36,2006,2007],{},"defineModel",[29,2009,2011],{"className":31,"code":2010,"language":33,"meta":34,"style":34},"\u003C!-- SearchBox.vue -->\n\u003Cscript setup>\nconst model = defineModel()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cinput v-model=\"model\" placeholder=\"搜索课程\">\n\u003C\u002Ftemplate>\n",[36,2012,2013,2018,2028,2043,2051,2055,2063,2086],{"__ignoreMap":34},[39,2014,2015],{"class":41,"line":42},[39,2016,2017],{"class":553},"\u003C!-- SearchBox.vue -->\n",[39,2019,2020,2022,2024,2026],{"class":41,"line":56},[39,2021,46],{"class":45},[39,2023,400],{"class":49},[39,2025,403],{"class":230},[39,2027,53],{"class":45},[39,2029,2030,2032,2035,2037,2040],{"class":41,"line":62},[39,2031,592],{"class":202},[39,2033,2034],{"class":431}," model",[39,2036,598],{"class":202},[39,2038,2039],{"class":230}," defineModel",[39,2041,2042],{"class":45},"()\n",[39,2044,2045,2047,2049],{"class":41,"line":68},[39,2046,77],{"class":45},[39,2048,400],{"class":49},[39,2050,53],{"class":45},[39,2052,2053],{"class":41,"line":74},[39,2054,88],{"emptyLinePlaceholder":87},[39,2056,2057,2059,2061],{"class":41,"line":84},[39,2058,46],{"class":45},[39,2060,461],{"class":49},[39,2062,53],{"class":45},[39,2064,2065,2067,2069,2071,2073,2076,2079,2081,2084],{"class":41,"line":91},[39,2066,468],{"class":45},[39,2068,1961],{"class":49},[39,2070,1964],{"class":230},[39,2072,206],{"class":45},[39,2074,2075],{"class":209},"\"model\"",[39,2077,2078],{"class":230}," placeholder",[39,2080,206],{"class":45},[39,2082,2083],{"class":209},"\"搜索课程\"",[39,2085,53],{"class":45},[39,2087,2088,2090,2092],{"class":41,"line":100},[39,2089,77],{"class":45},[39,2091,461],{"class":49},[39,2093,53],{"class":45},[14,2095,358],{},[29,2097,2098],{"className":31,"code":1981,"language":33,"meta":34,"style":34},[36,2099,2100],{"__ignoreMap":34},[39,2101,2102,2104,2106,2108,2110,2112,2114,2116],{"class":41,"line":42},[39,2103,46],{"class":45},[39,2105,324],{"class":49},[39,2107,1964],{"class":230},[39,2109,206],{"class":45},[39,2111,210],{"class":209},[39,2113,1971],{"class":45},[39,2115,210],{"class":209},[39,2117,385],{"class":45},[14,2119,2120],{},"含义：",[168,2122,2123,2128],{},[171,2124,2125,2126,980],{},"父组件的 ",[36,2127,1971],{},[171,2129,2130,2131,328],{},"子组件输入变化时，通知父组件更新 ",[36,2132,1971],{},[14,2134,2135],{},"本质上仍然是 props + emit，只是 Vue 提供了简洁语法。",[21,2137,2139],{"id":2138},"_11-provide-inject","11. Provide \u002F Inject",[14,2141,2142],{},"如果数据要跨很多层组件传递，层层 props 会很麻烦。",[29,2144,2147],{"className":2145,"code":2146,"language":663,"meta":34},[661],"App\n  Layout\n    Sidebar\n      UserMenu\n",[36,2148,2146],{"__ignoreMap":34},[14,2150,2151,2152,2155,2156,2158],{},"如果 ",[36,2153,2154],{},"App"," 里的主题要给 ",[36,2157,327],{}," 用，可以：",[29,2160,2162],{"className":527,"code":2161,"language":529,"meta":34,"style":34},"\u002F\u002F 上层组件\nimport { provide } from 'vue'\n\nprovide('theme', 'dark')\n",[36,2163,2164,2169,2183,2187],{"__ignoreMap":34},[39,2165,2166],{"class":41,"line":42},[39,2167,2168],{"class":553},"\u002F\u002F 上层组件\n",[39,2170,2171,2174,2177,2180],{"class":41,"line":56},[39,2172,2173],{"class":202},"import",[39,2175,2176],{"class":45}," { provide } ",[39,2178,2179],{"class":202},"from",[39,2181,2182],{"class":209}," 'vue'\n",[39,2184,2185],{"class":41,"line":62},[39,2186,88],{"emptyLinePlaceholder":87},[39,2188,2189,2192,2194,2197,2199,2202],{"class":41,"line":68},[39,2190,2191],{"class":230},"provide",[39,2193,542],{"class":45},[39,2195,2196],{"class":209},"'theme'",[39,2198,1189],{"class":45},[39,2200,2201],{"class":209},"'dark'",[39,2203,1035],{"class":45},[29,2205,2207],{"className":527,"code":2206,"language":529,"meta":34,"style":34},"\u002F\u002F 下层组件\nimport { inject } from 'vue'\n\nconst theme = inject('theme')\n",[36,2208,2209,2214,2225,2229],{"__ignoreMap":34},[39,2210,2211],{"class":41,"line":42},[39,2212,2213],{"class":553},"\u002F\u002F 下层组件\n",[39,2215,2216,2218,2221,2223],{"class":41,"line":56},[39,2217,2173],{"class":202},[39,2219,2220],{"class":45}," { inject } ",[39,2222,2179],{"class":202},[39,2224,2182],{"class":209},[39,2226,2227],{"class":41,"line":62},[39,2228,88],{"emptyLinePlaceholder":87},[39,2230,2231,2233,2236,2238,2241,2243,2245],{"class":41,"line":68},[39,2232,592],{"class":202},[39,2234,2235],{"class":431}," theme",[39,2237,598],{"class":202},[39,2239,2240],{"class":230}," inject",[39,2242,542],{"class":45},[39,2244,2196],{"class":209},[39,2246,1035],{"class":45},[14,2248,2249],{},"但不要滥用。普通业务共享状态通常更适合 Pinia。",[14,2251,2252],{},"Provide\u002FInject 更常用于：",[168,2254,2255,2258,2261,2264],{},[171,2256,2257],{},"主题。",[171,2259,2260],{},"表单上下文。",[171,2262,2263],{},"UI 组件库内部。",[171,2265,2266],{},"深层组件共享少量上下文。",[21,2268,2270],{"id":2269},"_12-组件通信选择表","12. 组件通信选择表",[2272,2273,2274,2287],"table",{},[2275,2276,2277],"thead",{},[2278,2279,2280,2284],"tr",{},[2281,2282,2283],"th",{},"场景",[2281,2285,2286],{},"推荐方式",[2288,2289,2290,2299,2306,2313,2321,2329],"tbody",{},[2278,2291,2292,2296],{},[2293,2294,2295],"td",{},"父组件给子组件展示数据",[2293,2297,2298],{},"props",[2278,2300,2301,2304],{},[2293,2302,2303],{},"子组件告诉父组件用户操作",[2293,2305,1027],{},[2278,2307,2308,2311],{},[2293,2309,2310],{},"父组件决定子组件内部某块内容",[2293,2312,1630],{},[2278,2314,2315,2318],{},[2293,2316,2317],{},"表单组件双向绑定",[2293,2319,2320],{},"组件 v-model",[2278,2322,2323,2326],{},[2293,2324,2325],{},"跨很多层传少量上下文",[2293,2327,2328],{},"provide\u002Finject",[2278,2330,2331,2334],{},[2293,2332,2333],{},"多页面共享业务状态",[2293,2335,2336],{},"Pinia",[21,2338,2340],{"id":2339},"_13-常见错误","13. 常见错误",[2342,2343,2345],"h3",{"id":2344},"错误一子组件直接改-props","错误一：子组件直接改 props",[14,2347,2348],{},"现象：数据流混乱，父组件难以追踪。",[14,2350,2351],{},"修复：子组件 emit，父组件修改。",[2342,2353,2355],{"id":2354},"错误二父组件传错-prop-名","错误二：父组件传错 prop 名",[14,2357,358],{},[29,2359,2361],{"className":31,"code":2360,"language":33,"meta":34,"style":34},"\u003CLessonCard :course=\"lesson\" \u002F>\n",[36,2362,2363],{"__ignoreMap":34},[39,2364,2365,2367,2369,2371,2374,2376,2378,2380,2382],{"class":41,"line":42},[39,2366,46],{"class":45},[39,2368,301],{"class":49},[39,2370,372],{"class":45},[39,2372,2373],{"class":230},"course",[39,2375,206],{"class":45},[39,2377,210],{"class":209},[39,2379,247],{"class":45},[39,2381,210],{"class":209},[39,2383,385],{"class":45},[14,2385,388],{},[29,2387,2389],{"className":527,"code":2388,"language":529,"meta":34,"style":34},"defineProps({\n  lesson: Object\n})\n",[36,2390,2391,2397,2401],{"__ignoreMap":34},[39,2392,2393,2395],{"class":41,"line":42},[39,2394,410],{"class":230},[39,2396,413],{"class":45},[39,2398,2399],{"class":41,"line":56},[39,2400,608],{"class":45},[39,2402,2403],{"class":41,"line":62},[39,2404,442],{"class":45},[14,2406,2407,2408,2410,2411,2413,2414,2416],{},"子组件要的是 ",[36,2409,247],{},"，父组件传的是 ",[36,2412,2373],{},"，所以 ",[36,2415,247],{}," 为空。",[14,2418,2419],{},"修复：",[29,2421,2422],{"className":31,"code":361,"language":33,"meta":34,"style":34},[36,2423,2424],{"__ignoreMap":34},[39,2425,2426,2428,2430,2432,2434,2436,2438,2440,2442],{"class":41,"line":42},[39,2427,46],{"class":45},[39,2429,301],{"class":49},[39,2431,372],{"class":45},[39,2433,247],{"class":230},[39,2435,206],{"class":45},[39,2437,210],{"class":209},[39,2439,247],{"class":45},[39,2441,210],{"class":209},[39,2443,385],{"class":45},[2342,2445,2447],{"id":2446},"错误三emit-名和监听名不一致","错误三：emit 名和监听名不一致",[14,2449,388],{},[29,2451,2453],{"className":527,"code":2452,"language":529,"meta":34,"style":34},"emit('remove')\n",[36,2454,2455],{"__ignoreMap":34},[39,2456,2457,2459,2461,2463],{"class":41,"line":42},[39,2458,1027],{"class":230},[39,2460,542],{"class":45},[39,2462,1085],{"class":209},[39,2464,1035],{"class":45},[14,2466,358],{},[29,2468,2470],{"className":31,"code":2469,"language":33,"meta":34,"style":34},"\u003CTodoItem @delete=\"removeTodo\" \u002F>\n",[36,2471,2472],{"__ignoreMap":34},[39,2473,2474,2476,2478,2481,2484,2486,2488,2490,2492],{"class":41,"line":42},[39,2475,46],{"class":45},[39,2477,320],{"class":49},[39,2479,2480],{"class":45}," @",[39,2482,2483],{"class":230},"delete",[39,2485,206],{"class":45},[39,2487,210],{"class":209},[39,2489,1433],{"class":45},[39,2491,210],{"class":209},[39,2493,385],{"class":45},[14,2495,2496,2497,2499,2500,2502],{},"父组件监听的是 ",[36,2498,2483],{},"，子组件发的是 ",[36,2501,1426],{},"，不会触发。",[14,2504,2419],{},[29,2506,2508],{"className":31,"code":2507,"language":33,"meta":34,"style":34},"\u003CTodoItem @remove=\"removeTodo\" \u002F>\n",[36,2509,2510],{"__ignoreMap":34},[39,2511,2512,2514,2516,2518,2520,2522,2524,2526,2528],{"class":41,"line":42},[39,2513,46],{"class":45},[39,2515,320],{"class":49},[39,2517,2480],{"class":45},[39,2519,1426],{"class":230},[39,2521,206],{"class":45},[39,2523,210],{"class":209},[39,2525,1433],{"class":45},[39,2527,210],{"class":209},[39,2529,385],{"class":45},[2342,2531,2533],{"id":2532},"错误四为了拆而拆","错误四：为了拆而拆",[14,2535,2536],{},"如果一个组件需要 15 个 props 才能工作，可能说明它边界不清。可以考虑：",[168,2538,2539,2542,2545],{},[171,2540,2541],{},"是否拆得太细。",[171,2543,2544],{},"是否应该把相关数据组合成对象。",[171,2546,2547],{},"是否应该让某些逻辑留在父组件。",[21,2549,2550],{"id":2550},"本章练习",[973,2552,2553,2564,2572,2579,2584,2591],{},[171,2554,2555,2556,2558,2559,2561,2562,328],{},"写一个 ",[36,2557,301],{}," 组件，props 接收 ",[36,2560,247],{}," 和 ",[36,2563,262],{},[171,2565,2566,2568,2569,2571],{},[36,2567,301],{}," 点击按钮后 emit ",[36,2570,282],{},"，参数是课程 id。",[171,2573,2574,2575,2578],{},"父组件维护 ",[36,2576,2577],{},"completedLessonIds","，收到事件后修改数组。",[171,2580,2555,2581,2583],{},[36,2582,1667],{},"，通过默认插槽显示内容。",[171,2585,2555,2586,2588,2589,328],{},[36,2587,324],{},"，支持 ",[36,2590,1946],{},[171,2592,2593],{},"回答：为什么子组件不应该直接改 props？",[2595,2596,2597],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":34,"searchDepth":56,"depth":56,"links":2599},[2600,2601,2602,2603,2604,2605,2606,2607,2608,2609,2611,2612,2613,2619],{"id":23,"depth":56,"text":24},{"id":305,"depth":56,"text":306},{"id":354,"depth":56,"text":355},{"id":565,"depth":56,"text":566},{"id":671,"depth":56,"text":672},{"id":1010,"depth":56,"text":1011},{"id":1117,"depth":56,"text":1118},{"id":1583,"depth":56,"text":1584},{"id":1706,"depth":56,"text":1707},{"id":1942,"depth":56,"text":2610},"10. 组件 v-model",{"id":2138,"depth":56,"text":2139},{"id":2269,"depth":56,"text":2270},{"id":2339,"depth":56,"text":2340,"children":2614},[2615,2616,2617,2618],{"id":2344,"depth":62,"text":2345},{"id":2354,"depth":62,"text":2355},{"id":2446,"depth":62,"text":2447},{"id":2532,"depth":62,"text":2533},{"id":2550,"depth":56,"text":2550},"md",{"order":68},"\u002Fsource\u002Fcode\u002Fvue\u002F04-components",{"title":5,"description":16},"source\u002Fcode\u002Fvue\u002F04-components","7P7ESU--_Jm1AoGRs3XmVG8MdZfk5Vz3h6y-3fklpOM",1784032889391]