[{"data":1,"prerenderedAt":4373},["ShallowReactive",2],{"notes-flutter-to-vue-comparison":3},{"id":4,"title":5,"body":6,"date":4362,"description":4363,"extension":4364,"meta":4365,"navigation":205,"path":4366,"seo":4367,"seoDescription":4368,"seoTitle":4369,"slug":4370,"stem":4371,"__hash__":4372},"notes\u002Fnotes\u002F2026-05-03-flutter-to-vue-comparison.md","Flutter 工程师的 Vue 对比学习指南",{"type":7,"value":8,"toc":4324},"minimark",[9,16,19,24,140,142,146,156,158,162,167,173,237,242,343,348,379,383,388,490,495,639,643,672,674,678,790,794,917,921,925,940,944,1077,1081,1085,1114,1118,1222,1224,1228,1329,1460,1462,1466,1470,1474,1499,1503,1568,1572,1576,1615,1619,1694,1698,1777,1781,1909,1911,1915,1991,1995,1999,2046,2050,2207,2211,2215,2239,2243,2329,2331,2335,2340,2393,2398,2519,2694,2698,2765,2767,2771,2775,2780,2842,2847,2956,3073,3075,3079,3084,3127,3132,3312,3316,3485,3487,3491,3496,3516,3521,3720,3722,3726,3729,3906,4006,4008,4012,4156,4158,4162,4228,4230,4234,4321],[10,11,12],"blockquote",{},[13,14,15],"p",{},"以 Flutter\u002FDart 的概念为锚点，快速建立 Vue 3 (Composition API) 的心智模型。",[17,18],"hr",{},[20,21,23],"h2",{"id":22},"_1-整体架构对比","1. 整体架构对比",[25,26,27,43],"table",{},[28,29,30],"thead",{},[31,32,33,37,40],"tr",{},[34,35,36],"th",{},"维度",[34,38,39],{},"Flutter",[34,41,42],{},"Vue",[44,45,46,58,69,85,96,107,118,129],"tbody",{},[31,47,48,52,55],{},[49,50,51],"td",{},"语言",[49,53,54],{},"Dart",[49,56,57],{},"JavaScript \u002F TypeScript",[31,59,60,63,66],{},[49,61,62],{},"渲染",[49,64,65],{},"自绘引擎 (Skia\u002FImpeller)",[49,67,68],{},"基于 DOM",[31,70,71,74,77],{},[49,72,73],{},"构建单元",[49,75,76],{},"Widget",[49,78,79,80,84],{},"Component (",[81,82,83],"code",{},".vue"," 单文件组件)",[31,86,87,90,93],{},[49,88,89],{},"状态管理",[49,91,92],{},"setState \u002F Provider \u002F Riverpod \u002F Bloc",[49,94,95],{},"ref \u002F reactive \u002F Pinia",[31,97,98,101,104],{},[49,99,100],{},"路由",[49,102,103],{},"Navigator \u002F GoRouter",[49,105,106],{},"Vue Router",[31,108,109,112,115],{},[49,110,111],{},"样式",[49,113,114],{},"Widget 属性内联",[49,116,117],{},"CSS \u002F Scoped CSS \u002F Tailwind",[31,119,120,123,126],{},[49,121,122],{},"包管理",[49,124,125],{},"pub (pubspec.yaml)",[49,127,128],{},"npm \u002F pnpm (package.json)",[31,130,131,134,137],{},[49,132,133],{},"构建工具",[49,135,136],{},"Flutter CLI",[49,138,139],{},"Vite",[17,141],{},[20,143,145],{"id":144},"_2-项目结构对比","2. 项目结构对比",[147,148,153],"pre",{"className":149,"code":151,"language":152},[150],"language-text","# Flutter                          # Vue (Vite 脚手架)\nlib\u002F                               src\u002F\n├── main.dart                      ├── main.ts          # 入口\n├── app.dart                       ├── App.vue          # 根组件\n├── models\u002F                        ├── types\u002F           # 类型定义\n├── screens\u002F                       ├── views\u002F           # 页面组件\n├── widgets\u002F                       ├── components\u002F      # 可复用组件\n├── providers\u002F                     ├── stores\u002F          # Pinia 状态\n├── services\u002F                      ├── api\u002F             # 网络请求\n└── utils\u002F                         ├── utils\u002F\npubspec.yaml                       ├── router\u002F          # 路由配置\n                                   package.json\n","text",[81,154,151],{"__ignoreMap":155},"",[17,157],{},[20,159,161],{"id":160},"_3-组件-widget","3. 组件 = Widget",[163,164,166],"h3",{"id":165},"_31-基本组件结构","3.1 基本组件结构",[13,168,169],{},[170,171,172],"strong",{},"Flutter — StatelessWidget",[147,174,178],{"className":175,"code":176,"language":177,"meta":155,"style":155},"language-dart shiki shiki-themes github-light github-dark","class Greeting extends StatelessWidget {\n  final String name;\n  const Greeting({required this.name});\n\n  @override\n  Widget build(BuildContext context) {\n    return Text('Hello, $name');\n  }\n}\n","dart",[81,179,180,188,194,200,207,213,219,225,231],{"__ignoreMap":155},[181,182,185],"span",{"class":183,"line":184},"line",1,[181,186,187],{},"class Greeting extends StatelessWidget {\n",[181,189,191],{"class":183,"line":190},2,[181,192,193],{},"  final String name;\n",[181,195,197],{"class":183,"line":196},3,[181,198,199],{},"  const Greeting({required this.name});\n",[181,201,203],{"class":183,"line":202},4,[181,204,206],{"emptyLinePlaceholder":205},true,"\n",[181,208,210],{"class":183,"line":209},5,[181,211,212],{},"  @override\n",[181,214,216],{"class":183,"line":215},6,[181,217,218],{},"  Widget build(BuildContext context) {\n",[181,220,222],{"class":183,"line":221},7,[181,223,224],{},"    return Text('Hello, $name');\n",[181,226,228],{"class":183,"line":227},8,[181,229,230],{},"  }\n",[181,232,234],{"class":183,"line":233},9,[181,235,236],{},"}\n",[13,238,239],{},[170,240,241],{},"Vue — 单文件组件 (SFC)",[147,243,247],{"className":244,"code":245,"language":246,"meta":155,"style":155},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cp>Hello, {{ name }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{ name: string }>()\n\u003C\u002Fscript>\n","vue",[81,248,249,262,276,285,289,312,335],{"__ignoreMap":155},[181,250,251,255,259],{"class":183,"line":184},[181,252,254],{"class":253},"sVt8B","\u003C",[181,256,258],{"class":257},"s9eBZ","template",[181,260,261],{"class":253},">\n",[181,263,264,267,269,272,274],{"class":183,"line":190},[181,265,266],{"class":253},"  \u003C",[181,268,13],{"class":257},[181,270,271],{"class":253},">Hello, {{ name }}\u003C\u002F",[181,273,13],{"class":257},[181,275,261],{"class":253},[181,277,278,281,283],{"class":183,"line":196},[181,279,280],{"class":253},"\u003C\u002F",[181,282,258],{"class":257},[181,284,261],{"class":253},[181,286,287],{"class":183,"line":202},[181,288,206],{"emptyLinePlaceholder":205},[181,290,291,293,296,300,303,306,310],{"class":183,"line":209},[181,292,254],{"class":253},[181,294,295],{"class":257},"script",[181,297,299],{"class":298},"sScJk"," setup",[181,301,302],{"class":298}," lang",[181,304,305],{"class":253},"=",[181,307,309],{"class":308},"sZZnC","\"ts\"",[181,311,261],{"class":253},[181,313,314,317,320,324,328,332],{"class":183,"line":215},[181,315,316],{"class":298},"defineProps",[181,318,319],{"class":253},"\u003C{ ",[181,321,323],{"class":322},"s4XuR","name",[181,325,327],{"class":326},"szBVR",":",[181,329,331],{"class":330},"sj4cs"," string",[181,333,334],{"class":253}," }>()\n",[181,336,337,339,341],{"class":183,"line":221},[181,338,280],{"class":253},[181,340,295],{"class":257},[181,342,261],{"class":253},[13,344,345],{},[170,346,347],{},"对比要点：",[349,350,351,362,368],"ul",{},[352,353,354,355,358,359],"li",{},"Flutter 的 ",[81,356,357],{},"build()"," 方法 ≈ Vue 的 ",[81,360,361],{},"\u003Ctemplate>",[352,363,364,365],{},"Flutter 的构造函数参数 ≈ Vue 的 ",[81,366,367],{},"props",[352,369,370,371,374,375,378],{},"Vue 用 ",[81,372,373],{},"{{ }}"," 做插值，Flutter 用 ",[81,376,377],{},"${}"," 在 Dart 字符串里插值",[163,380,382],{"id":381},"_32-有状态组件","3.2 有状态组件",[13,384,385],{},[170,386,387],{},"Flutter — StatefulWidget",[147,389,391],{"className":175,"code":390,"language":177,"meta":155,"style":155},"class Counter extends StatefulWidget {\n  @override\n  State\u003CCounter> createState() => _CounterState();\n}\n\nclass _CounterState extends State\u003CCounter> {\n  int count = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(children: [\n      Text('$count'),\n      ElevatedButton(\n        onPressed: () => setState(() => count++),\n        child: Text('Add'),\n      ),\n    ]);\n  }\n}\n",[81,392,393,398,402,407,411,415,420,425,429,433,438,444,450,456,462,468,474,480,485],{"__ignoreMap":155},[181,394,395],{"class":183,"line":184},[181,396,397],{},"class Counter extends StatefulWidget {\n",[181,399,400],{"class":183,"line":190},[181,401,212],{},[181,403,404],{"class":183,"line":196},[181,405,406],{},"  State\u003CCounter> createState() => _CounterState();\n",[181,408,409],{"class":183,"line":202},[181,410,236],{},[181,412,413],{"class":183,"line":209},[181,414,206],{"emptyLinePlaceholder":205},[181,416,417],{"class":183,"line":215},[181,418,419],{},"class _CounterState extends State\u003CCounter> {\n",[181,421,422],{"class":183,"line":221},[181,423,424],{},"  int count = 0;\n",[181,426,427],{"class":183,"line":227},[181,428,206],{"emptyLinePlaceholder":205},[181,430,431],{"class":183,"line":233},[181,432,212],{},[181,434,436],{"class":183,"line":435},10,[181,437,218],{},[181,439,441],{"class":183,"line":440},11,[181,442,443],{},"    return Column(children: [\n",[181,445,447],{"class":183,"line":446},12,[181,448,449],{},"      Text('$count'),\n",[181,451,453],{"class":183,"line":452},13,[181,454,455],{},"      ElevatedButton(\n",[181,457,459],{"class":183,"line":458},14,[181,460,461],{},"        onPressed: () => setState(() => count++),\n",[181,463,465],{"class":183,"line":464},15,[181,466,467],{},"        child: Text('Add'),\n",[181,469,471],{"class":183,"line":470},16,[181,472,473],{},"      ),\n",[181,475,477],{"class":183,"line":476},17,[181,478,479],{},"    ]);\n",[181,481,483],{"class":183,"line":482},18,[181,484,230],{},[181,486,488],{"class":183,"line":487},19,[181,489,236],{},[13,491,492],{},[170,493,494],{},"Vue — Composition API",[147,496,498],{"className":244,"code":497,"language":246,"meta":155,"style":155},"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cp>{{ count }}\u003C\u002Fp>\n    \u003Cbutton @click=\"count++\">Add\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\n\nconst count = ref(0)\n\u003C\u002Fscript>\n",[81,499,500,508,517,531,553,562,570,574,590,604,608,631],{"__ignoreMap":155},[181,501,502,504,506],{"class":183,"line":184},[181,503,254],{"class":253},[181,505,258],{"class":257},[181,507,261],{"class":253},[181,509,510,512,515],{"class":183,"line":190},[181,511,266],{"class":253},[181,513,514],{"class":257},"div",[181,516,261],{"class":253},[181,518,519,522,524,527,529],{"class":183,"line":196},[181,520,521],{"class":253},"    \u003C",[181,523,13],{"class":257},[181,525,526],{"class":253},">{{ count }}\u003C\u002F",[181,528,13],{"class":257},[181,530,261],{"class":253},[181,532,533,535,538,541,543,546,549,551],{"class":183,"line":202},[181,534,521],{"class":253},[181,536,537],{"class":257},"button",[181,539,540],{"class":298}," @click",[181,542,305],{"class":253},[181,544,545],{"class":308},"\"count++\"",[181,547,548],{"class":253},">Add\u003C\u002F",[181,550,537],{"class":257},[181,552,261],{"class":253},[181,554,555,558,560],{"class":183,"line":209},[181,556,557],{"class":253},"  \u003C\u002F",[181,559,514],{"class":257},[181,561,261],{"class":253},[181,563,564,566,568],{"class":183,"line":215},[181,565,280],{"class":253},[181,567,258],{"class":257},[181,569,261],{"class":253},[181,571,572],{"class":183,"line":221},[181,573,206],{"emptyLinePlaceholder":205},[181,575,576,578,580,582,584,586,588],{"class":183,"line":227},[181,577,254],{"class":253},[181,579,295],{"class":257},[181,581,299],{"class":298},[181,583,302],{"class":298},[181,585,305],{"class":253},[181,587,309],{"class":308},[181,589,261],{"class":253},[181,591,592,595,598,601],{"class":183,"line":233},[181,593,594],{"class":326},"import",[181,596,597],{"class":253}," { ref } ",[181,599,600],{"class":326},"from",[181,602,603],{"class":308}," 'vue'\n",[181,605,606],{"class":183,"line":435},[181,607,206],{"emptyLinePlaceholder":205},[181,609,610,613,616,619,622,625,628],{"class":183,"line":440},[181,611,612],{"class":326},"const",[181,614,615],{"class":330}," count",[181,617,618],{"class":326}," =",[181,620,621],{"class":298}," ref",[181,623,624],{"class":253},"(",[181,626,627],{"class":330},"0",[181,629,630],{"class":253},")\n",[181,632,633,635,637],{"class":183,"line":446},[181,634,280],{"class":253},[181,636,295],{"class":257},[181,638,261],{"class":253},[13,640,641],{},[170,642,347],{},[349,644,645,662,669],{},[352,646,647,650,651,654,655,658,659,661],{},[81,648,649],{},"setState()"," ≈ 直接修改 ",[81,652,653],{},"ref"," 的 ",[81,656,657],{},".value","（模板中自动解包，不用写 ",[81,660,657],{},"）",[352,663,664,665,668],{},"Flutter 需要 StatefulWidget + State 两个类，Vue 只需 ",[81,666,667],{},"ref()"," 一行",[352,670,671],{},"Vue 的响应式是自动追踪依赖的，不需要手动调用 setState",[17,673],{},[20,675,677],{"id":676},"_4-响应式系统对比","4. 响应式系统对比",[25,679,680,691],{},[28,681,682],{},[31,683,684,686,688],{},[34,685,39],{},[34,687,42],{},[34,689,690],{},"说明",[44,692,693,706,721,736,752,771],{},[31,694,695,700,703],{},[49,696,697],{},[81,698,699],{},"setState(() { })",[49,701,702],{},"自动（修改 ref\u002Freactive 即触发）",[49,704,705],{},"Vue 无需手动通知",[31,707,708,713,718],{},[49,709,710],{},[81,711,712],{},"ValueNotifier\u003CT>",[49,714,715],{},[81,716,717],{},"ref\u003CT>()",[49,719,720],{},"单值响应式",[31,722,723,728,733],{},[49,724,725],{},[81,726,727],{},"ChangeNotifier",[49,729,730],{},[81,731,732],{},"reactive({})",[49,734,735],{},"对象级响应式",[31,737,738,743,749],{},[49,739,740],{},[81,741,742],{},"Provider.of\u003CT>(context)",[49,744,745,748],{},[81,746,747],{},"inject()"," \u002F Pinia store",[49,750,751],{},"跨组件共享状态",[31,753,754,759,768],{},[49,755,756],{},[81,757,758],{},"StreamBuilder",[49,760,761,764,765],{},[81,762,763],{},"watch()"," \u002F ",[81,766,767],{},"watchEffect()",[49,769,770],{},"监听变化并执行副作用",[31,772,773,778,787],{},[49,774,775],{},[81,776,777],{},"FutureBuilder",[49,779,780,783,784],{},[81,781,782],{},"onMounted"," + async 或 ",[81,785,786],{},"Suspense",[49,788,789],{},"异步数据加载",[163,791,793],{"id":792},"ref-vs-reactive","ref vs reactive",[147,795,797],{"className":244,"code":796,"language":246,"meta":155,"style":155},"\u003Cscript setup lang=\"ts\">\nimport { ref, reactive } from 'vue'\n\n\u002F\u002F ref — 用于基本类型（类似 ValueNotifier）\nconst count = ref(0)\ncount.value++  \u002F\u002F 脚本中需要 .value\n\n\u002F\u002F reactive — 用于对象（类似 ChangeNotifier）\nconst user = reactive({ name: 'Alice', age: 25 })\nuser.age++     \u002F\u002F 直接修改属性，不需要 .value\n\u003C\u002Fscript>\n",[81,798,799,815,826,830,836,852,863,867,872,899,909],{"__ignoreMap":155},[181,800,801,803,805,807,809,811,813],{"class":183,"line":184},[181,802,254],{"class":253},[181,804,295],{"class":257},[181,806,299],{"class":298},[181,808,302],{"class":298},[181,810,305],{"class":253},[181,812,309],{"class":308},[181,814,261],{"class":253},[181,816,817,819,822,824],{"class":183,"line":190},[181,818,594],{"class":326},[181,820,821],{"class":253}," { ref, reactive } ",[181,823,600],{"class":326},[181,825,603],{"class":308},[181,827,828],{"class":183,"line":196},[181,829,206],{"emptyLinePlaceholder":205},[181,831,832],{"class":183,"line":202},[181,833,835],{"class":834},"sJ8bj","\u002F\u002F ref — 用于基本类型（类似 ValueNotifier）\n",[181,837,838,840,842,844,846,848,850],{"class":183,"line":209},[181,839,612],{"class":326},[181,841,615],{"class":330},[181,843,618],{"class":326},[181,845,621],{"class":298},[181,847,624],{"class":253},[181,849,627],{"class":330},[181,851,630],{"class":253},[181,853,854,857,860],{"class":183,"line":215},[181,855,856],{"class":253},"count.value",[181,858,859],{"class":326},"++",[181,861,862],{"class":834},"  \u002F\u002F 脚本中需要 .value\n",[181,864,865],{"class":183,"line":221},[181,866,206],{"emptyLinePlaceholder":205},[181,868,869],{"class":183,"line":227},[181,870,871],{"class":834},"\u002F\u002F reactive — 用于对象（类似 ChangeNotifier）\n",[181,873,874,876,879,881,884,887,890,893,896],{"class":183,"line":233},[181,875,612],{"class":326},[181,877,878],{"class":330}," user",[181,880,618],{"class":326},[181,882,883],{"class":298}," reactive",[181,885,886],{"class":253},"({ name: ",[181,888,889],{"class":308},"'Alice'",[181,891,892],{"class":253},", age: ",[181,894,895],{"class":330},"25",[181,897,898],{"class":253}," })\n",[181,900,901,904,906],{"class":183,"line":435},[181,902,903],{"class":253},"user.age",[181,905,859],{"class":326},[181,907,908],{"class":834},"     \u002F\u002F 直接修改属性，不需要 .value\n",[181,910,911,913,915],{"class":183,"line":440},[181,912,280],{"class":253},[181,914,295],{"class":257},[181,916,261],{"class":253},[163,918,920],{"id":919},"计算属性-派生状态","计算属性 = 派生状态",[13,922,923],{},[170,924,39],{},[147,926,928],{"className":175,"code":927,"language":177,"meta":155,"style":155},"\u002F\u002F 每次 build 都重新计算\nString get fullName => '${firstName} ${lastName}';\n",[81,929,930,935],{"__ignoreMap":155},[181,931,932],{"class":183,"line":184},[181,933,934],{},"\u002F\u002F 每次 build 都重新计算\n",[181,936,937],{"class":183,"line":190},[181,938,939],{},"String get fullName => '${firstName} ${lastName}';\n",[13,941,942],{},[170,943,42],{},[147,945,947],{"className":244,"code":946,"language":246,"meta":155,"style":155},"\u003Cscript setup lang=\"ts\">\nimport { ref, computed } from 'vue'\n\nconst firstName = ref('Alice')\nconst lastName = ref('Smith')\n\n\u002F\u002F 自动缓存，只在依赖变化时重新计算\nconst fullName = computed(() => `${firstName.value} ${lastName.value}`)\n\u003C\u002Fscript>\n",[81,948,949,965,976,980,997,1015,1019,1024,1069],{"__ignoreMap":155},[181,950,951,953,955,957,959,961,963],{"class":183,"line":184},[181,952,254],{"class":253},[181,954,295],{"class":257},[181,956,299],{"class":298},[181,958,302],{"class":298},[181,960,305],{"class":253},[181,962,309],{"class":308},[181,964,261],{"class":253},[181,966,967,969,972,974],{"class":183,"line":190},[181,968,594],{"class":326},[181,970,971],{"class":253}," { ref, computed } ",[181,973,600],{"class":326},[181,975,603],{"class":308},[181,977,978],{"class":183,"line":196},[181,979,206],{"emptyLinePlaceholder":205},[181,981,982,984,987,989,991,993,995],{"class":183,"line":202},[181,983,612],{"class":326},[181,985,986],{"class":330}," firstName",[181,988,618],{"class":326},[181,990,621],{"class":298},[181,992,624],{"class":253},[181,994,889],{"class":308},[181,996,630],{"class":253},[181,998,999,1001,1004,1006,1008,1010,1013],{"class":183,"line":209},[181,1000,612],{"class":326},[181,1002,1003],{"class":330}," lastName",[181,1005,618],{"class":326},[181,1007,621],{"class":298},[181,1009,624],{"class":253},[181,1011,1012],{"class":308},"'Smith'",[181,1014,630],{"class":253},[181,1016,1017],{"class":183,"line":215},[181,1018,206],{"emptyLinePlaceholder":205},[181,1020,1021],{"class":183,"line":221},[181,1022,1023],{"class":834},"\u002F\u002F 自动缓存，只在依赖变化时重新计算\n",[181,1025,1026,1028,1031,1033,1036,1039,1042,1045,1048,1051,1054,1057,1060,1062,1064,1067],{"class":183,"line":227},[181,1027,612],{"class":326},[181,1029,1030],{"class":330}," fullName",[181,1032,618],{"class":326},[181,1034,1035],{"class":298}," computed",[181,1037,1038],{"class":253},"(() ",[181,1040,1041],{"class":326},"=>",[181,1043,1044],{"class":308}," `${",[181,1046,1047],{"class":253},"firstName",[181,1049,1050],{"class":308},".",[181,1052,1053],{"class":253},"value",[181,1055,1056],{"class":308},"} ${",[181,1058,1059],{"class":253},"lastName",[181,1061,1050],{"class":308},[181,1063,1053],{"class":253},[181,1065,1066],{"class":308},"}`",[181,1068,630],{"class":253},[181,1070,1071,1073,1075],{"class":183,"line":233},[181,1072,280],{"class":253},[181,1074,295],{"class":257},[181,1076,261],{"class":253},[163,1078,1080],{"id":1079},"侦听器-监听变化","侦听器 = 监听变化",[13,1082,1083],{},[170,1084,39],{},[147,1086,1088],{"className":175,"code":1087,"language":177,"meta":155,"style":155},"\u002F\u002F 用 didUpdateWidget 或 addListener\n@override\nvoid didUpdateWidget(oldWidget) {\n  if (widget.id != oldWidget.id) fetchData(widget.id);\n}\n",[81,1089,1090,1095,1100,1105,1110],{"__ignoreMap":155},[181,1091,1092],{"class":183,"line":184},[181,1093,1094],{},"\u002F\u002F 用 didUpdateWidget 或 addListener\n",[181,1096,1097],{"class":183,"line":190},[181,1098,1099],{},"@override\n",[181,1101,1102],{"class":183,"line":196},[181,1103,1104],{},"void didUpdateWidget(oldWidget) {\n",[181,1106,1107],{"class":183,"line":202},[181,1108,1109],{},"  if (widget.id != oldWidget.id) fetchData(widget.id);\n",[181,1111,1112],{"class":183,"line":209},[181,1113,236],{},[13,1115,1116],{},[170,1117,42],{},[147,1119,1121],{"className":244,"code":1120,"language":246,"meta":155,"style":155},"\u003Cscript setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\nconst id = ref(1)\n\nwatch(id, (newVal, oldVal) => {\n  fetchData(newVal)\n})\n\u003C\u002Fscript>\n",[81,1122,1123,1139,1150,1154,1172,1176,1201,1209,1214],{"__ignoreMap":155},[181,1124,1125,1127,1129,1131,1133,1135,1137],{"class":183,"line":184},[181,1126,254],{"class":253},[181,1128,295],{"class":257},[181,1130,299],{"class":298},[181,1132,302],{"class":298},[181,1134,305],{"class":253},[181,1136,309],{"class":308},[181,1138,261],{"class":253},[181,1140,1141,1143,1146,1148],{"class":183,"line":190},[181,1142,594],{"class":326},[181,1144,1145],{"class":253}," { ref, watch } ",[181,1147,600],{"class":326},[181,1149,603],{"class":308},[181,1151,1152],{"class":183,"line":196},[181,1153,206],{"emptyLinePlaceholder":205},[181,1155,1156,1158,1161,1163,1165,1167,1170],{"class":183,"line":202},[181,1157,612],{"class":326},[181,1159,1160],{"class":330}," id",[181,1162,618],{"class":326},[181,1164,621],{"class":298},[181,1166,624],{"class":253},[181,1168,1169],{"class":330},"1",[181,1171,630],{"class":253},[181,1173,1174],{"class":183,"line":209},[181,1175,206],{"emptyLinePlaceholder":205},[181,1177,1178,1181,1184,1187,1190,1193,1196,1198],{"class":183,"line":215},[181,1179,1180],{"class":298},"watch",[181,1182,1183],{"class":253},"(id, (",[181,1185,1186],{"class":322},"newVal",[181,1188,1189],{"class":253},", ",[181,1191,1192],{"class":322},"oldVal",[181,1194,1195],{"class":253},") ",[181,1197,1041],{"class":326},[181,1199,1200],{"class":253}," {\n",[181,1202,1203,1206],{"class":183,"line":221},[181,1204,1205],{"class":298},"  fetchData",[181,1207,1208],{"class":253},"(newVal)\n",[181,1210,1211],{"class":183,"line":227},[181,1212,1213],{"class":253},"})\n",[181,1215,1216,1218,1220],{"class":183,"line":233},[181,1217,280],{"class":253},[181,1219,295],{"class":257},[181,1221,261],{"class":253},[17,1223],{},[20,1225,1227],{"id":1226},"_5-生命周期对比","5. 生命周期对比",[25,1229,1230,1243],{},[28,1231,1232],{},[31,1233,1234,1237,1240],{},[34,1235,1236],{},"Flutter (State)",[34,1238,1239],{},"Vue 3 (Composition API)",[34,1241,1242],{},"时机",[44,1244,1245,1260,1275,1290,1304,1317],{},[31,1246,1247,1252,1257],{},[49,1248,1249],{},[81,1250,1251],{},"initState()",[49,1253,1254],{},[81,1255,1256],{},"onMounted()",[49,1258,1259],{},"组件挂载\u002F初始化",[31,1261,1262,1267,1272],{},[49,1263,1264],{},[81,1265,1266],{},"didUpdateWidget()",[49,1268,1269],{},[81,1270,1271],{},"onUpdated()",[49,1273,1274],{},"更新后",[31,1276,1277,1282,1287],{},[49,1278,1279],{},[81,1280,1281],{},"dispose()",[49,1283,1284],{},[81,1285,1286],{},"onUnmounted()",[49,1288,1289],{},"销毁\u002F卸载",[31,1291,1292,1297,1301],{},[49,1293,1294],{},[81,1295,1296],{},"didChangeDependencies()",[49,1298,1299],{},[81,1300,763],{},[49,1302,1303],{},"依赖变化",[31,1305,1306,1309,1314],{},[49,1307,1308],{},"—",[49,1310,1311],{},[81,1312,1313],{},"onBeforeMount()",[49,1315,1316],{},"挂载前",[31,1318,1319,1321,1326],{},[49,1320,1308],{},[49,1322,1323],{},[81,1324,1325],{},"onBeforeUpdate()",[49,1327,1328],{},"更新前",[147,1330,1332],{"className":244,"code":1331,"language":246,"meta":155,"style":155},"\u003Cscript setup lang=\"ts\">\nimport { onMounted, onUnmounted } from 'vue'\n\n\u002F\u002F ≈ initState\nonMounted(() => {\n  console.log('组件已挂载')\n  window.addEventListener('resize', onResize)\n})\n\n\u002F\u002F ≈ dispose\nonUnmounted(() => {\n  window.removeEventListener('resize', onResize)\n})\n\u003C\u002Fscript>\n",[81,1333,1334,1350,1361,1365,1370,1380,1395,1411,1415,1419,1424,1435,1448,1452],{"__ignoreMap":155},[181,1335,1336,1338,1340,1342,1344,1346,1348],{"class":183,"line":184},[181,1337,254],{"class":253},[181,1339,295],{"class":257},[181,1341,299],{"class":298},[181,1343,302],{"class":298},[181,1345,305],{"class":253},[181,1347,309],{"class":308},[181,1349,261],{"class":253},[181,1351,1352,1354,1357,1359],{"class":183,"line":190},[181,1353,594],{"class":326},[181,1355,1356],{"class":253}," { onMounted, onUnmounted } ",[181,1358,600],{"class":326},[181,1360,603],{"class":308},[181,1362,1363],{"class":183,"line":196},[181,1364,206],{"emptyLinePlaceholder":205},[181,1366,1367],{"class":183,"line":202},[181,1368,1369],{"class":834},"\u002F\u002F ≈ initState\n",[181,1371,1372,1374,1376,1378],{"class":183,"line":209},[181,1373,782],{"class":298},[181,1375,1038],{"class":253},[181,1377,1041],{"class":326},[181,1379,1200],{"class":253},[181,1381,1382,1385,1388,1390,1393],{"class":183,"line":215},[181,1383,1384],{"class":253},"  console.",[181,1386,1387],{"class":298},"log",[181,1389,624],{"class":253},[181,1391,1392],{"class":308},"'组件已挂载'",[181,1394,630],{"class":253},[181,1396,1397,1400,1403,1405,1408],{"class":183,"line":221},[181,1398,1399],{"class":253},"  window.",[181,1401,1402],{"class":298},"addEventListener",[181,1404,624],{"class":253},[181,1406,1407],{"class":308},"'resize'",[181,1409,1410],{"class":253},", onResize)\n",[181,1412,1413],{"class":183,"line":227},[181,1414,1213],{"class":253},[181,1416,1417],{"class":183,"line":233},[181,1418,206],{"emptyLinePlaceholder":205},[181,1420,1421],{"class":183,"line":435},[181,1422,1423],{"class":834},"\u002F\u002F ≈ dispose\n",[181,1425,1426,1429,1431,1433],{"class":183,"line":440},[181,1427,1428],{"class":298},"onUnmounted",[181,1430,1038],{"class":253},[181,1432,1041],{"class":326},[181,1434,1200],{"class":253},[181,1436,1437,1439,1442,1444,1446],{"class":183,"line":446},[181,1438,1399],{"class":253},[181,1440,1441],{"class":298},"removeEventListener",[181,1443,624],{"class":253},[181,1445,1407],{"class":308},[181,1447,1410],{"class":253},[181,1449,1450],{"class":183,"line":452},[181,1451,1213],{"class":253},[181,1453,1454,1456,1458],{"class":183,"line":458},[181,1455,280],{"class":253},[181,1457,295],{"class":257},[181,1459,261],{"class":253},[17,1461],{},[20,1463,1465],{"id":1464},"_6-模板语法-widget-树","6. 模板语法 = Widget 树",[163,1467,1469],{"id":1468},"_61-条件渲染","6.1 条件渲染",[13,1471,1472],{},[170,1473,39],{},[147,1475,1477],{"className":175,"code":1476,"language":177,"meta":155,"style":155},"Column(children: [\n  if (isLoggedIn) Text('Welcome'),\n  if (!isLoggedIn) TextButton(onPressed: login, child: Text('Login')),\n])\n",[81,1478,1479,1484,1489,1494],{"__ignoreMap":155},[181,1480,1481],{"class":183,"line":184},[181,1482,1483],{},"Column(children: [\n",[181,1485,1486],{"class":183,"line":190},[181,1487,1488],{},"  if (isLoggedIn) Text('Welcome'),\n",[181,1490,1491],{"class":183,"line":196},[181,1492,1493],{},"  if (!isLoggedIn) TextButton(onPressed: login, child: Text('Login')),\n",[181,1495,1496],{"class":183,"line":202},[181,1497,1498],{},"])\n",[13,1500,1501],{},[170,1502,42],{},[147,1504,1506],{"className":244,"code":1505,"language":246,"meta":155,"style":155},"\u003Ctemplate>\n  \u003Cp v-if=\"isLoggedIn\">Welcome\u003C\u002Fp>\n  \u003Cbutton v-else @click=\"login\">Login\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[81,1507,1508,1516,1537,1560],{"__ignoreMap":155},[181,1509,1510,1512,1514],{"class":183,"line":184},[181,1511,254],{"class":253},[181,1513,258],{"class":257},[181,1515,261],{"class":253},[181,1517,1518,1520,1522,1525,1527,1530,1533,1535],{"class":183,"line":190},[181,1519,266],{"class":253},[181,1521,13],{"class":257},[181,1523,1524],{"class":298}," v-if",[181,1526,305],{"class":253},[181,1528,1529],{"class":308},"\"isLoggedIn\"",[181,1531,1532],{"class":253},">Welcome\u003C\u002F",[181,1534,13],{"class":257},[181,1536,261],{"class":253},[181,1538,1539,1541,1543,1546,1548,1550,1553,1556,1558],{"class":183,"line":196},[181,1540,266],{"class":253},[181,1542,537],{"class":257},[181,1544,1545],{"class":298}," v-else",[181,1547,540],{"class":298},[181,1549,305],{"class":253},[181,1551,1552],{"class":308},"\"login\"",[181,1554,1555],{"class":253},">Login\u003C\u002F",[181,1557,537],{"class":257},[181,1559,261],{"class":253},[181,1561,1562,1564,1566],{"class":183,"line":202},[181,1563,280],{"class":253},[181,1565,258],{"class":257},[181,1567,261],{"class":253},[163,1569,1571],{"id":1570},"_62-列表渲染","6.2 列表渲染",[13,1573,1574],{},[170,1575,39],{},[147,1577,1579],{"className":175,"code":1578,"language":177,"meta":155,"style":155},"ListView.builder(\n  itemCount: items.length,\n  itemBuilder: (ctx, i) => ListTile(\n    key: ValueKey(items[i].id),\n    title: Text(items[i].name),\n  ),\n)\n",[81,1580,1581,1586,1591,1596,1601,1606,1611],{"__ignoreMap":155},[181,1582,1583],{"class":183,"line":184},[181,1584,1585],{},"ListView.builder(\n",[181,1587,1588],{"class":183,"line":190},[181,1589,1590],{},"  itemCount: items.length,\n",[181,1592,1593],{"class":183,"line":196},[181,1594,1595],{},"  itemBuilder: (ctx, i) => ListTile(\n",[181,1597,1598],{"class":183,"line":202},[181,1599,1600],{},"    key: ValueKey(items[i].id),\n",[181,1602,1603],{"class":183,"line":209},[181,1604,1605],{},"    title: Text(items[i].name),\n",[181,1607,1608],{"class":183,"line":215},[181,1609,1610],{},"  ),\n",[181,1612,1613],{"class":183,"line":221},[181,1614,630],{},[13,1616,1617],{},[170,1618,42],{},[147,1620,1622],{"className":244,"code":1621,"language":246,"meta":155,"style":155},"\u003Ctemplate>\n  \u003Cul>\n    \u003Cli v-for=\"item in items\" :key=\"item.id\">\n      {{ item.name }}\n    \u003C\u002Fli>\n  \u003C\u002Ful>\n\u003C\u002Ftemplate>\n",[81,1623,1624,1632,1640,1664,1669,1678,1686],{"__ignoreMap":155},[181,1625,1626,1628,1630],{"class":183,"line":184},[181,1627,254],{"class":253},[181,1629,258],{"class":257},[181,1631,261],{"class":253},[181,1633,1634,1636,1638],{"class":183,"line":190},[181,1635,266],{"class":253},[181,1637,349],{"class":257},[181,1639,261],{"class":253},[181,1641,1642,1644,1646,1649,1651,1654,1657,1659,1662],{"class":183,"line":196},[181,1643,521],{"class":253},[181,1645,352],{"class":257},[181,1647,1648],{"class":298}," v-for",[181,1650,305],{"class":253},[181,1652,1653],{"class":308},"\"item in items\"",[181,1655,1656],{"class":298}," :key",[181,1658,305],{"class":253},[181,1660,1661],{"class":308},"\"item.id\"",[181,1663,261],{"class":253},[181,1665,1666],{"class":183,"line":202},[181,1667,1668],{"class":253},"      {{ item.name }}\n",[181,1670,1671,1674,1676],{"class":183,"line":209},[181,1672,1673],{"class":253},"    \u003C\u002F",[181,1675,352],{"class":257},[181,1677,261],{"class":253},[181,1679,1680,1682,1684],{"class":183,"line":215},[181,1681,557],{"class":253},[181,1683,349],{"class":257},[181,1685,261],{"class":253},[181,1687,1688,1690,1692],{"class":183,"line":221},[181,1689,280],{"class":253},[181,1691,258],{"class":257},[181,1693,261],{"class":253},[163,1695,1697],{"id":1696},"_63-事件绑定","6.3 事件绑定",[25,1699,1700,1710],{},[28,1701,1702],{},[31,1703,1704,1706,1708],{},[34,1705,39],{},[34,1707,42],{},[34,1709,690],{},[44,1711,1712,1727,1742,1757],{},[31,1713,1714,1719,1724],{},[49,1715,1716],{},[81,1717,1718],{},"onPressed: () => {}",[49,1720,1721],{},[81,1722,1723],{},"@click=\"handler\"",[49,1725,1726],{},"点击",[31,1728,1729,1734,1739],{},[49,1730,1731],{},[81,1732,1733],{},"onChanged: (v) => {}",[49,1735,1736],{},[81,1737,1738],{},"@input=\"handler\"",[49,1740,1741],{},"输入",[31,1743,1744,1749,1754],{},[49,1745,1746],{},[81,1747,1748],{},"onSubmitted: (v) => {}",[49,1750,1751],{},[81,1752,1753],{},"@submit.prevent=\"handler\"",[49,1755,1756],{},"表单提交",[31,1758,1759,1764,1774],{},[49,1760,1761],{},[81,1762,1763],{},"GestureDetector",[49,1765,1766,1769,1770,1773],{},[81,1767,1768],{},"@mousedown"," ",[81,1771,1772],{},"@touchstart"," 等",[49,1775,1776],{},"手势",[163,1778,1780],{"id":1779},"_64-属性绑定","6.4 属性绑定",[147,1782,1784],{"className":244,"code":1783,"language":246,"meta":155,"style":155},"\u003Ctemplate>\n  \u003C!-- 静态属性 -->\n  \u003Cimg src=\"\u002Flogo.png\" \u002F>\n\n  \u003C!-- 动态绑定（v-bind 缩写为 :） -->\n  \u003Cimg :src=\"imageUrl\" \u002F>\n\n  \u003C!-- class 绑定 -->\n  \u003Cdiv :class=\"{ active: isActive, disabled: isDisabled }\">\u003C\u002Fdiv>\n\n  \u003C!-- style 绑定 -->\n  \u003Cdiv :style=\"{ color: textColor, fontSize: size + 'px' }\">\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[81,1785,1786,1794,1799,1817,1821,1826,1842,1846,1851,1872,1876,1881,1901],{"__ignoreMap":155},[181,1787,1788,1790,1792],{"class":183,"line":184},[181,1789,254],{"class":253},[181,1791,258],{"class":257},[181,1793,261],{"class":253},[181,1795,1796],{"class":183,"line":190},[181,1797,1798],{"class":834},"  \u003C!-- 静态属性 -->\n",[181,1800,1801,1803,1806,1809,1811,1814],{"class":183,"line":196},[181,1802,266],{"class":253},[181,1804,1805],{"class":257},"img",[181,1807,1808],{"class":298}," src",[181,1810,305],{"class":253},[181,1812,1813],{"class":308},"\"\u002Flogo.png\"",[181,1815,1816],{"class":253}," \u002F>\n",[181,1818,1819],{"class":183,"line":202},[181,1820,206],{"emptyLinePlaceholder":205},[181,1822,1823],{"class":183,"line":209},[181,1824,1825],{"class":834},"  \u003C!-- 动态绑定（v-bind 缩写为 :） -->\n",[181,1827,1828,1830,1832,1835,1837,1840],{"class":183,"line":215},[181,1829,266],{"class":253},[181,1831,1805],{"class":257},[181,1833,1834],{"class":298}," :src",[181,1836,305],{"class":253},[181,1838,1839],{"class":308},"\"imageUrl\"",[181,1841,1816],{"class":253},[181,1843,1844],{"class":183,"line":221},[181,1845,206],{"emptyLinePlaceholder":205},[181,1847,1848],{"class":183,"line":227},[181,1849,1850],{"class":834},"  \u003C!-- class 绑定 -->\n",[181,1852,1853,1855,1857,1860,1862,1865,1868,1870],{"class":183,"line":233},[181,1854,266],{"class":253},[181,1856,514],{"class":257},[181,1858,1859],{"class":298}," :class",[181,1861,305],{"class":253},[181,1863,1864],{"class":308},"\"{ active: isActive, disabled: isDisabled }\"",[181,1866,1867],{"class":253},">\u003C\u002F",[181,1869,514],{"class":257},[181,1871,261],{"class":253},[181,1873,1874],{"class":183,"line":435},[181,1875,206],{"emptyLinePlaceholder":205},[181,1877,1878],{"class":183,"line":440},[181,1879,1880],{"class":834},"  \u003C!-- style 绑定 -->\n",[181,1882,1883,1885,1887,1890,1892,1895,1897,1899],{"class":183,"line":446},[181,1884,266],{"class":253},[181,1886,514],{"class":257},[181,1888,1889],{"class":298}," :style",[181,1891,305],{"class":253},[181,1893,1894],{"class":308},"\"{ color: textColor, fontSize: size + 'px' }\"",[181,1896,1867],{"class":253},[181,1898,514],{"class":257},[181,1900,261],{"class":253},[181,1902,1903,1905,1907],{"class":183,"line":452},[181,1904,280],{"class":253},[181,1906,258],{"class":257},[181,1908,261],{"class":253},[17,1910],{},[20,1912,1914],{"id":1913},"_7-组件通信对比","7. 组件通信对比",[25,1916,1917,1928],{},[28,1918,1919],{},[31,1920,1921,1924,1926],{},[34,1922,1923],{},"场景",[34,1925,39],{},[34,1927,42],{},[44,1929,1930,1942,1959,1980],{},[31,1931,1932,1935,1938],{},[49,1933,1934],{},"父→子",[49,1936,1937],{},"构造函数参数",[49,1939,1940],{},[81,1941,367],{},[31,1943,1944,1947,1953],{},[49,1945,1946],{},"子→父",[49,1948,1949,1950],{},"回调函数 ",[81,1951,1952],{},"onChanged",[49,1954,1955,1958],{},[81,1956,1957],{},"emit"," 事件",[31,1960,1961,1964,1972],{},[49,1962,1963],{},"跨层级",[49,1965,1966,764,1969],{},[81,1967,1968],{},"InheritedWidget",[81,1970,1971],{},"Provider",[49,1973,1974,764,1977],{},[81,1975,1976],{},"provide",[81,1978,1979],{},"inject",[31,1981,1982,1985,1988],{},[49,1983,1984],{},"全局状态",[49,1986,1987],{},"Riverpod \u002F Bloc",[49,1989,1990],{},"Pinia",[163,1992,1994],{"id":1993},"props-传递父子","Props 传递（父→子）",[13,1996,1997],{},[170,1998,39],{},[147,2000,2002],{"className":175,"code":2001,"language":177,"meta":155,"style":155},"\u002F\u002F 父组件\nUserCard(name: userName, onTap: () => goToProfile())\n\n\u002F\u002F 子组件\nclass UserCard extends StatelessWidget {\n  final String name;\n  final VoidCallback onTap;\n  \u002F\u002F ...\n}\n",[81,2003,2004,2009,2014,2018,2023,2028,2032,2037,2042],{"__ignoreMap":155},[181,2005,2006],{"class":183,"line":184},[181,2007,2008],{},"\u002F\u002F 父组件\n",[181,2010,2011],{"class":183,"line":190},[181,2012,2013],{},"UserCard(name: userName, onTap: () => goToProfile())\n",[181,2015,2016],{"class":183,"line":196},[181,2017,206],{"emptyLinePlaceholder":205},[181,2019,2020],{"class":183,"line":202},[181,2021,2022],{},"\u002F\u002F 子组件\n",[181,2024,2025],{"class":183,"line":209},[181,2026,2027],{},"class UserCard extends StatelessWidget {\n",[181,2029,2030],{"class":183,"line":215},[181,2031,193],{},[181,2033,2034],{"class":183,"line":221},[181,2035,2036],{},"  final VoidCallback onTap;\n",[181,2038,2039],{"class":183,"line":227},[181,2040,2041],{},"  \u002F\u002F ...\n",[181,2043,2044],{"class":183,"line":233},[181,2045,236],{},[13,2047,2048],{},[170,2049,42],{},[147,2051,2053],{"className":244,"code":2052,"language":246,"meta":155,"style":155},"\u003C!-- 父组件 -->\n\u003CUserCard :name=\"userName\" @tap=\"goToProfile\" \u002F>\n\n\u003C!-- 子组件 UserCard.vue -->\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{ name: string }>()\nconst emit = defineEmits\u003C{ tap: [] }>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv @click=\"emit('tap')\">{{ name }}\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[81,2054,2055,2060,2099,2103,2108,2124,2138,2159,2167,2171,2179,2199],{"__ignoreMap":155},[181,2056,2057],{"class":183,"line":184},[181,2058,2059],{"class":834},"\u003C!-- 父组件 -->\n",[181,2061,2062,2064,2067,2070,2072,2074,2077,2080,2082,2085,2088,2090,2092,2095,2097],{"class":183,"line":190},[181,2063,254],{"class":253},[181,2065,2066],{"class":257},"UserCard",[181,2068,2069],{"class":253}," :",[181,2071,323],{"class":298},[181,2073,305],{"class":253},[181,2075,2076],{"class":308},"\"",[181,2078,2079],{"class":253},"userName",[181,2081,2076],{"class":308},[181,2083,2084],{"class":253}," @",[181,2086,2087],{"class":298},"tap",[181,2089,305],{"class":253},[181,2091,2076],{"class":308},[181,2093,2094],{"class":253},"goToProfile",[181,2096,2076],{"class":308},[181,2098,1816],{"class":253},[181,2100,2101],{"class":183,"line":196},[181,2102,206],{"emptyLinePlaceholder":205},[181,2104,2105],{"class":183,"line":202},[181,2106,2107],{"class":834},"\u003C!-- 子组件 UserCard.vue -->\n",[181,2109,2110,2112,2114,2116,2118,2120,2122],{"class":183,"line":209},[181,2111,254],{"class":253},[181,2113,295],{"class":257},[181,2115,299],{"class":298},[181,2117,302],{"class":298},[181,2119,305],{"class":253},[181,2121,309],{"class":308},[181,2123,261],{"class":253},[181,2125,2126,2128,2130,2132,2134,2136],{"class":183,"line":215},[181,2127,316],{"class":298},[181,2129,319],{"class":253},[181,2131,323],{"class":322},[181,2133,327],{"class":326},[181,2135,331],{"class":330},[181,2137,334],{"class":253},[181,2139,2140,2142,2145,2147,2150,2152,2154,2156],{"class":183,"line":221},[181,2141,612],{"class":326},[181,2143,2144],{"class":330}," emit",[181,2146,618],{"class":326},[181,2148,2149],{"class":298}," defineEmits",[181,2151,319],{"class":253},[181,2153,2087],{"class":322},[181,2155,327],{"class":326},[181,2157,2158],{"class":253}," [] }>()\n",[181,2160,2161,2163,2165],{"class":183,"line":227},[181,2162,280],{"class":253},[181,2164,295],{"class":257},[181,2166,261],{"class":253},[181,2168,2169],{"class":183,"line":233},[181,2170,206],{"emptyLinePlaceholder":205},[181,2172,2173,2175,2177],{"class":183,"line":435},[181,2174,254],{"class":253},[181,2176,258],{"class":257},[181,2178,261],{"class":253},[181,2180,2181,2183,2185,2187,2189,2192,2195,2197],{"class":183,"line":440},[181,2182,266],{"class":253},[181,2184,514],{"class":257},[181,2186,540],{"class":298},[181,2188,305],{"class":253},[181,2190,2191],{"class":308},"\"emit('tap')\"",[181,2193,2194],{"class":253},">{{ name }}\u003C\u002F",[181,2196,514],{"class":257},[181,2198,261],{"class":253},[181,2200,2201,2203,2205],{"class":183,"line":446},[181,2202,280],{"class":253},[181,2204,258],{"class":257},[181,2206,261],{"class":253},[163,2208,2210],{"id":2209},"插槽-child-builder","插槽 = child \u002F builder",[13,2212,2213],{},[170,2214,39],{},[147,2216,2218],{"className":175,"code":2217,"language":177,"meta":155,"style":155},"Card(child: Text('内容'))\n\n\u002F\u002F builder 模式\nMyWidget(builder: (context) => Text('动态内容'))\n",[81,2219,2220,2225,2229,2234],{"__ignoreMap":155},[181,2221,2222],{"class":183,"line":184},[181,2223,2224],{},"Card(child: Text('内容'))\n",[181,2226,2227],{"class":183,"line":190},[181,2228,206],{"emptyLinePlaceholder":205},[181,2230,2231],{"class":183,"line":196},[181,2232,2233],{},"\u002F\u002F builder 模式\n",[181,2235,2236],{"class":183,"line":202},[181,2237,2238],{},"MyWidget(builder: (context) => Text('动态内容'))\n",[13,2240,2241],{},[170,2242,42],{},[147,2244,2246],{"className":244,"code":2245,"language":246,"meta":155,"style":155},"\u003C!-- 默认插槽 ≈ child -->\n\u003CCard>\n  \u003Cp>内容\u003C\u002Fp>\n\u003C\u002FCard>\n\n\u003C!-- 作用域插槽 ≈ builder -->\n\u003CMyList :items=\"items\">\n  \u003Ctemplate #default=\"{ item }\">\n    \u003Cspan>{{ item.name }}\u003C\u002Fspan>\n  \u003C\u002Ftemplate>\n\u003C\u002FMyList>\n",[81,2247,2248,2253,2262,2267,2275,2279,2284,2306,2311,2316,2321],{"__ignoreMap":155},[181,2249,2250],{"class":183,"line":184},[181,2251,2252],{"class":834},"\u003C!-- 默认插槽 ≈ child -->\n",[181,2254,2255,2257,2260],{"class":183,"line":190},[181,2256,254],{"class":253},[181,2258,2259],{"class":257},"Card",[181,2261,261],{"class":253},[181,2263,2264],{"class":183,"line":196},[181,2265,2266],{"class":253},"  \u003Cp>内容\u003C\u002Fp>\n",[181,2268,2269,2271,2273],{"class":183,"line":202},[181,2270,280],{"class":253},[181,2272,2259],{"class":257},[181,2274,261],{"class":253},[181,2276,2277],{"class":183,"line":209},[181,2278,206],{"emptyLinePlaceholder":205},[181,2280,2281],{"class":183,"line":215},[181,2282,2283],{"class":834},"\u003C!-- 作用域插槽 ≈ builder -->\n",[181,2285,2286,2288,2291,2293,2296,2298,2300,2302,2304],{"class":183,"line":221},[181,2287,254],{"class":253},[181,2289,2290],{"class":257},"MyList",[181,2292,2069],{"class":253},[181,2294,2295],{"class":298},"items",[181,2297,305],{"class":253},[181,2299,2076],{"class":308},[181,2301,2295],{"class":253},[181,2303,2076],{"class":308},[181,2305,261],{"class":253},[181,2307,2308],{"class":183,"line":227},[181,2309,2310],{"class":253},"  \u003Ctemplate #default=\"{ item }\">\n",[181,2312,2313],{"class":183,"line":233},[181,2314,2315],{"class":253},"    \u003Cspan>{{ item.name }}\u003C\u002Fspan>\n",[181,2317,2318],{"class":183,"line":435},[181,2319,2320],{"class":253},"  \u003C\u002Ftemplate>\n",[181,2322,2323,2325,2327],{"class":183,"line":440},[181,2324,280],{"class":253},[181,2326,2290],{"class":257},[181,2328,261],{"class":253},[17,2330],{},[20,2332,2334],{"id":2333},"_8-路由对比","8. 路由对比",[13,2336,2337],{},[170,2338,2339],{},"Flutter — GoRouter",[147,2341,2343],{"className":175,"code":2342,"language":177,"meta":155,"style":155},"GoRouter(routes: [\n  GoRoute(path: '\u002F', builder: (ctx, state) => HomePage()),\n  GoRoute(path: '\u002Fuser\u002F:id', builder: (ctx, state) {\n    final id = state.pathParameters['id']!;\n    return UserPage(id: id);\n  }),\n])\n\n\u002F\u002F 导航\ncontext.go('\u002Fuser\u002F42');\n",[81,2344,2345,2350,2355,2360,2365,2370,2375,2379,2383,2388],{"__ignoreMap":155},[181,2346,2347],{"class":183,"line":184},[181,2348,2349],{},"GoRouter(routes: [\n",[181,2351,2352],{"class":183,"line":190},[181,2353,2354],{},"  GoRoute(path: '\u002F', builder: (ctx, state) => HomePage()),\n",[181,2356,2357],{"class":183,"line":196},[181,2358,2359],{},"  GoRoute(path: '\u002Fuser\u002F:id', builder: (ctx, state) {\n",[181,2361,2362],{"class":183,"line":202},[181,2363,2364],{},"    final id = state.pathParameters['id']!;\n",[181,2366,2367],{"class":183,"line":209},[181,2368,2369],{},"    return UserPage(id: id);\n",[181,2371,2372],{"class":183,"line":215},[181,2373,2374],{},"  }),\n",[181,2376,2377],{"class":183,"line":221},[181,2378,1498],{},[181,2380,2381],{"class":183,"line":227},[181,2382,206],{"emptyLinePlaceholder":205},[181,2384,2385],{"class":183,"line":233},[181,2386,2387],{},"\u002F\u002F 导航\n",[181,2389,2390],{"class":183,"line":435},[181,2391,2392],{},"context.go('\u002Fuser\u002F42');\n",[13,2394,2395],{},[170,2396,2397],{},"Vue — Vue Router",[147,2399,2403],{"className":2400,"code":2401,"language":2402,"meta":155,"style":155},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F router\u002Findex.ts\nimport { createRouter, createWebHistory } from 'vue-router'\n\nconst router = createRouter({\n  history: createWebHistory(),\n  routes: [\n    { path: '\u002F', component: () => import('@\u002Fviews\u002FHome.vue') },\n    { path: '\u002Fuser\u002F:id', component: () => import('@\u002Fviews\u002FUser.vue') },\n  ],\n})\n","ts",[81,2404,2405,2410,2422,2426,2441,2452,2457,2486,2510,2515],{"__ignoreMap":155},[181,2406,2407],{"class":183,"line":184},[181,2408,2409],{"class":834},"\u002F\u002F router\u002Findex.ts\n",[181,2411,2412,2414,2417,2419],{"class":183,"line":190},[181,2413,594],{"class":326},[181,2415,2416],{"class":253}," { createRouter, createWebHistory } ",[181,2418,600],{"class":326},[181,2420,2421],{"class":308}," 'vue-router'\n",[181,2423,2424],{"class":183,"line":196},[181,2425,206],{"emptyLinePlaceholder":205},[181,2427,2428,2430,2433,2435,2438],{"class":183,"line":202},[181,2429,612],{"class":326},[181,2431,2432],{"class":330}," router",[181,2434,618],{"class":326},[181,2436,2437],{"class":298}," createRouter",[181,2439,2440],{"class":253},"({\n",[181,2442,2443,2446,2449],{"class":183,"line":209},[181,2444,2445],{"class":253},"  history: ",[181,2447,2448],{"class":298},"createWebHistory",[181,2450,2451],{"class":253},"(),\n",[181,2453,2454],{"class":183,"line":215},[181,2455,2456],{"class":253},"  routes: [\n",[181,2458,2459,2462,2465,2467,2470,2473,2475,2478,2480,2483],{"class":183,"line":221},[181,2460,2461],{"class":253},"    { path: ",[181,2463,2464],{"class":308},"'\u002F'",[181,2466,1189],{"class":253},[181,2468,2469],{"class":298},"component",[181,2471,2472],{"class":253},": () ",[181,2474,1041],{"class":326},[181,2476,2477],{"class":326}," import",[181,2479,624],{"class":253},[181,2481,2482],{"class":308},"'@\u002Fviews\u002FHome.vue'",[181,2484,2485],{"class":253},") },\n",[181,2487,2488,2490,2493,2495,2497,2499,2501,2503,2505,2508],{"class":183,"line":227},[181,2489,2461],{"class":253},[181,2491,2492],{"class":308},"'\u002Fuser\u002F:id'",[181,2494,1189],{"class":253},[181,2496,2469],{"class":298},[181,2498,2472],{"class":253},[181,2500,1041],{"class":326},[181,2502,2477],{"class":326},[181,2504,624],{"class":253},[181,2506,2507],{"class":308},"'@\u002Fviews\u002FUser.vue'",[181,2509,2485],{"class":253},[181,2511,2512],{"class":183,"line":233},[181,2513,2514],{"class":253},"  ],\n",[181,2516,2517],{"class":183,"line":435},[181,2518,1213],{"class":253},[147,2520,2522],{"className":244,"code":2521,"language":246,"meta":155,"style":155},"\u003C!-- 使用路由 -->\n\u003Ctemplate>\n  \u003Crouter-link to=\"\u002Fuser\u002F42\">Go to User\u003C\u002Frouter-link>\n  \u003Crouter-view \u002F>  \u003C!-- ≈ Navigator 的显示区域 -->\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport { useRoute, useRouter } from 'vue-router'\n\nconst route = useRoute()     \u002F\u002F 读取当前路由信息\nconst router = useRouter()   \u002F\u002F 编程式导航\n\nconsole.log(route.params.id) \u002F\u002F '42'\nrouter.push('\u002Fuser\u002F42')      \u002F\u002F ≈ context.go()\n\u003C\u002Fscript>\n",[81,2523,2524,2529,2537,2559,2572,2580,2584,2600,2611,2615,2633,2650,2654,2667,2686],{"__ignoreMap":155},[181,2525,2526],{"class":183,"line":184},[181,2527,2528],{"class":834},"\u003C!-- 使用路由 -->\n",[181,2530,2531,2533,2535],{"class":183,"line":190},[181,2532,254],{"class":253},[181,2534,258],{"class":257},[181,2536,261],{"class":253},[181,2538,2539,2541,2544,2547,2549,2552,2555,2557],{"class":183,"line":196},[181,2540,266],{"class":253},[181,2542,2543],{"class":257},"router-link",[181,2545,2546],{"class":298}," to",[181,2548,305],{"class":253},[181,2550,2551],{"class":308},"\"\u002Fuser\u002F42\"",[181,2553,2554],{"class":253},">Go to User\u003C\u002F",[181,2556,2543],{"class":257},[181,2558,261],{"class":253},[181,2560,2561,2563,2566,2569],{"class":183,"line":202},[181,2562,266],{"class":253},[181,2564,2565],{"class":257},"router-view",[181,2567,2568],{"class":253}," \u002F>  ",[181,2570,2571],{"class":834},"\u003C!-- ≈ Navigator 的显示区域 -->\n",[181,2573,2574,2576,2578],{"class":183,"line":209},[181,2575,280],{"class":253},[181,2577,258],{"class":257},[181,2579,261],{"class":253},[181,2581,2582],{"class":183,"line":215},[181,2583,206],{"emptyLinePlaceholder":205},[181,2585,2586,2588,2590,2592,2594,2596,2598],{"class":183,"line":221},[181,2587,254],{"class":253},[181,2589,295],{"class":257},[181,2591,299],{"class":298},[181,2593,302],{"class":298},[181,2595,305],{"class":253},[181,2597,309],{"class":308},[181,2599,261],{"class":253},[181,2601,2602,2604,2607,2609],{"class":183,"line":227},[181,2603,594],{"class":326},[181,2605,2606],{"class":253}," { useRoute, useRouter } ",[181,2608,600],{"class":326},[181,2610,2421],{"class":308},[181,2612,2613],{"class":183,"line":233},[181,2614,206],{"emptyLinePlaceholder":205},[181,2616,2617,2619,2622,2624,2627,2630],{"class":183,"line":435},[181,2618,612],{"class":326},[181,2620,2621],{"class":330}," route",[181,2623,618],{"class":326},[181,2625,2626],{"class":298}," useRoute",[181,2628,2629],{"class":253},"()     ",[181,2631,2632],{"class":834},"\u002F\u002F 读取当前路由信息\n",[181,2634,2635,2637,2639,2641,2644,2647],{"class":183,"line":440},[181,2636,612],{"class":326},[181,2638,2432],{"class":330},[181,2640,618],{"class":326},[181,2642,2643],{"class":298}," useRouter",[181,2645,2646],{"class":253},"()   ",[181,2648,2649],{"class":834},"\u002F\u002F 编程式导航\n",[181,2651,2652],{"class":183,"line":446},[181,2653,206],{"emptyLinePlaceholder":205},[181,2655,2656,2659,2661,2664],{"class":183,"line":452},[181,2657,2658],{"class":253},"console.",[181,2660,1387],{"class":298},[181,2662,2663],{"class":253},"(route.params.id) ",[181,2665,2666],{"class":834},"\u002F\u002F '42'\n",[181,2668,2669,2672,2675,2677,2680,2683],{"class":183,"line":458},[181,2670,2671],{"class":253},"router.",[181,2673,2674],{"class":298},"push",[181,2676,624],{"class":253},[181,2678,2679],{"class":308},"'\u002Fuser\u002F42'",[181,2681,2682],{"class":253},")      ",[181,2684,2685],{"class":834},"\u002F\u002F ≈ context.go()\n",[181,2687,2688,2690,2692],{"class":183,"line":464},[181,2689,280],{"class":253},[181,2691,295],{"class":257},[181,2693,261],{"class":253},[163,2695,2697],{"id":2696},"路由守卫-navigator-observer","路由守卫 ≈ Navigator Observer",[147,2699,2701],{"className":2400,"code":2700,"language":2402,"meta":155,"style":155},"router.beforeEach((to, from) => {\n  if (to.meta.requiresAuth && !isLoggedIn()) {\n    return '\u002Flogin'  \u002F\u002F 重定向\n  }\n})\n",[81,2702,2703,2726,2746,2757,2761],{"__ignoreMap":155},[181,2704,2705,2707,2710,2713,2716,2718,2720,2722,2724],{"class":183,"line":184},[181,2706,2671],{"class":253},[181,2708,2709],{"class":298},"beforeEach",[181,2711,2712],{"class":253},"((",[181,2714,2715],{"class":322},"to",[181,2717,1189],{"class":253},[181,2719,600],{"class":322},[181,2721,1195],{"class":253},[181,2723,1041],{"class":326},[181,2725,1200],{"class":253},[181,2727,2728,2731,2734,2737,2740,2743],{"class":183,"line":190},[181,2729,2730],{"class":326},"  if",[181,2732,2733],{"class":253}," (to.meta.requiresAuth ",[181,2735,2736],{"class":326},"&&",[181,2738,2739],{"class":326}," !",[181,2741,2742],{"class":298},"isLoggedIn",[181,2744,2745],{"class":253},"()) {\n",[181,2747,2748,2751,2754],{"class":183,"line":196},[181,2749,2750],{"class":326},"    return",[181,2752,2753],{"class":308}," '\u002Flogin'",[181,2755,2756],{"class":834},"  \u002F\u002F 重定向\n",[181,2758,2759],{"class":183,"line":202},[181,2760,230],{"class":253},[181,2762,2763],{"class":183,"line":209},[181,2764,1213],{"class":253},[17,2766],{},[20,2768,2770],{"id":2769},"_9-状态管理对比","9. 状态管理对比",[163,2772,2774],{"id":2773},"pinia-riverpod-provider","Pinia ≈ Riverpod \u002F Provider",[13,2776,2777],{},[170,2778,2779],{},"Flutter — Riverpod",[147,2781,2783],{"className":175,"code":2782,"language":177,"meta":155,"style":155},"final counterProvider = StateNotifierProvider\u003CCounterNotifier, int>(\n  (ref) => CounterNotifier(),\n);\n\nclass CounterNotifier extends StateNotifier\u003Cint> {\n  CounterNotifier() : super(0);\n  void increment() => state++;\n}\n\n\u002F\u002F 使用\nfinal count = ref.watch(counterProvider);\nref.read(counterProvider.notifier).increment();\n",[81,2784,2785,2790,2795,2800,2804,2809,2814,2819,2823,2827,2832,2837],{"__ignoreMap":155},[181,2786,2787],{"class":183,"line":184},[181,2788,2789],{},"final counterProvider = StateNotifierProvider\u003CCounterNotifier, int>(\n",[181,2791,2792],{"class":183,"line":190},[181,2793,2794],{},"  (ref) => CounterNotifier(),\n",[181,2796,2797],{"class":183,"line":196},[181,2798,2799],{},");\n",[181,2801,2802],{"class":183,"line":202},[181,2803,206],{"emptyLinePlaceholder":205},[181,2805,2806],{"class":183,"line":209},[181,2807,2808],{},"class CounterNotifier extends StateNotifier\u003Cint> {\n",[181,2810,2811],{"class":183,"line":215},[181,2812,2813],{},"  CounterNotifier() : super(0);\n",[181,2815,2816],{"class":183,"line":221},[181,2817,2818],{},"  void increment() => state++;\n",[181,2820,2821],{"class":183,"line":227},[181,2822,236],{},[181,2824,2825],{"class":183,"line":233},[181,2826,206],{"emptyLinePlaceholder":205},[181,2828,2829],{"class":183,"line":435},[181,2830,2831],{},"\u002F\u002F 使用\n",[181,2833,2834],{"class":183,"line":440},[181,2835,2836],{},"final count = ref.watch(counterProvider);\n",[181,2838,2839],{"class":183,"line":446},[181,2840,2841],{},"ref.read(counterProvider.notifier).increment();\n",[13,2843,2844],{},[170,2845,2846],{},"Vue — Pinia",[147,2848,2850],{"className":2400,"code":2849,"language":2402,"meta":155,"style":155},"\u002F\u002F stores\u002Fcounter.ts\nimport { defineStore } from 'pinia'\nimport { ref } from 'vue'\n\nexport const useCounterStore = defineStore('counter', () => {\n  const count = ref(0)\n  function increment() { count.value++ }\n  return { count, increment }\n})\n",[81,2851,2852,2857,2869,2879,2883,2911,2928,2944,2952],{"__ignoreMap":155},[181,2853,2854],{"class":183,"line":184},[181,2855,2856],{"class":834},"\u002F\u002F stores\u002Fcounter.ts\n",[181,2858,2859,2861,2864,2866],{"class":183,"line":190},[181,2860,594],{"class":326},[181,2862,2863],{"class":253}," { defineStore } ",[181,2865,600],{"class":326},[181,2867,2868],{"class":308}," 'pinia'\n",[181,2870,2871,2873,2875,2877],{"class":183,"line":196},[181,2872,594],{"class":326},[181,2874,597],{"class":253},[181,2876,600],{"class":326},[181,2878,603],{"class":308},[181,2880,2881],{"class":183,"line":202},[181,2882,206],{"emptyLinePlaceholder":205},[181,2884,2885,2888,2891,2894,2896,2899,2901,2904,2907,2909],{"class":183,"line":209},[181,2886,2887],{"class":326},"export",[181,2889,2890],{"class":326}," const",[181,2892,2893],{"class":330}," useCounterStore",[181,2895,618],{"class":326},[181,2897,2898],{"class":298}," defineStore",[181,2900,624],{"class":253},[181,2902,2903],{"class":308},"'counter'",[181,2905,2906],{"class":253},", () ",[181,2908,1041],{"class":326},[181,2910,1200],{"class":253},[181,2912,2913,2916,2918,2920,2922,2924,2926],{"class":183,"line":215},[181,2914,2915],{"class":326},"  const",[181,2917,615],{"class":330},[181,2919,618],{"class":326},[181,2921,621],{"class":298},[181,2923,624],{"class":253},[181,2925,627],{"class":330},[181,2927,630],{"class":253},[181,2929,2930,2933,2936,2939,2941],{"class":183,"line":221},[181,2931,2932],{"class":326},"  function",[181,2934,2935],{"class":298}," increment",[181,2937,2938],{"class":253},"() { count.value",[181,2940,859],{"class":326},[181,2942,2943],{"class":253}," }\n",[181,2945,2946,2949],{"class":183,"line":227},[181,2947,2948],{"class":326},"  return",[181,2950,2951],{"class":253}," { count, increment }\n",[181,2953,2954],{"class":183,"line":233},[181,2955,1213],{"class":253},[147,2957,2959],{"className":244,"code":2958,"language":246,"meta":155,"style":155},"\u003C!-- 使用 -->\n\u003Cscript setup lang=\"ts\">\nimport { useCounterStore } from '@\u002Fstores\u002Fcounter'\n\nconst counter = useCounterStore()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>{{ counter.count }}\u003C\u002Fp>\n  \u003Cbutton @click=\"counter.increment()\">+1\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[81,2960,2961,2966,2982,2994,2998,3012,3020,3024,3032,3045,3065],{"__ignoreMap":155},[181,2962,2963],{"class":183,"line":184},[181,2964,2965],{"class":834},"\u003C!-- 使用 -->\n",[181,2967,2968,2970,2972,2974,2976,2978,2980],{"class":183,"line":190},[181,2969,254],{"class":253},[181,2971,295],{"class":257},[181,2973,299],{"class":298},[181,2975,302],{"class":298},[181,2977,305],{"class":253},[181,2979,309],{"class":308},[181,2981,261],{"class":253},[181,2983,2984,2986,2989,2991],{"class":183,"line":196},[181,2985,594],{"class":326},[181,2987,2988],{"class":253}," { useCounterStore } ",[181,2990,600],{"class":326},[181,2992,2993],{"class":308}," '@\u002Fstores\u002Fcounter'\n",[181,2995,2996],{"class":183,"line":202},[181,2997,206],{"emptyLinePlaceholder":205},[181,2999,3000,3002,3005,3007,3009],{"class":183,"line":209},[181,3001,612],{"class":326},[181,3003,3004],{"class":330}," counter",[181,3006,618],{"class":326},[181,3008,2893],{"class":298},[181,3010,3011],{"class":253},"()\n",[181,3013,3014,3016,3018],{"class":183,"line":215},[181,3015,280],{"class":253},[181,3017,295],{"class":257},[181,3019,261],{"class":253},[181,3021,3022],{"class":183,"line":221},[181,3023,206],{"emptyLinePlaceholder":205},[181,3025,3026,3028,3030],{"class":183,"line":227},[181,3027,254],{"class":253},[181,3029,258],{"class":257},[181,3031,261],{"class":253},[181,3033,3034,3036,3038,3041,3043],{"class":183,"line":233},[181,3035,266],{"class":253},[181,3037,13],{"class":257},[181,3039,3040],{"class":253},">{{ counter.count }}\u003C\u002F",[181,3042,13],{"class":257},[181,3044,261],{"class":253},[181,3046,3047,3049,3051,3053,3055,3058,3061,3063],{"class":183,"line":435},[181,3048,266],{"class":253},[181,3050,537],{"class":257},[181,3052,540],{"class":298},[181,3054,305],{"class":253},[181,3056,3057],{"class":308},"\"counter.increment()\"",[181,3059,3060],{"class":253},">+1\u003C\u002F",[181,3062,537],{"class":257},[181,3064,261],{"class":253},[181,3066,3067,3069,3071],{"class":183,"line":440},[181,3068,280],{"class":253},[181,3070,258],{"class":257},[181,3072,261],{"class":253},[17,3074],{},[20,3076,3078],{"id":3077},"_10-样式对比","10. 样式对比",[13,3080,3081,3083],{},[170,3082,39],{}," — 所有样式通过 Widget 属性内联",[147,3085,3087],{"className":175,"code":3086,"language":177,"meta":155,"style":155},"Container(\n  padding: EdgeInsets.all(16),\n  decoration: BoxDecoration(\n    color: Colors.blue,\n    borderRadius: BorderRadius.circular(8),\n  ),\n  child: Text('Hello', style: TextStyle(fontSize: 18, color: Colors.white)),\n)\n",[81,3088,3089,3094,3099,3104,3109,3114,3118,3123],{"__ignoreMap":155},[181,3090,3091],{"class":183,"line":184},[181,3092,3093],{},"Container(\n",[181,3095,3096],{"class":183,"line":190},[181,3097,3098],{},"  padding: EdgeInsets.all(16),\n",[181,3100,3101],{"class":183,"line":196},[181,3102,3103],{},"  decoration: BoxDecoration(\n",[181,3105,3106],{"class":183,"line":202},[181,3107,3108],{},"    color: Colors.blue,\n",[181,3110,3111],{"class":183,"line":209},[181,3112,3113],{},"    borderRadius: BorderRadius.circular(8),\n",[181,3115,3116],{"class":183,"line":215},[181,3117,1610],{},[181,3119,3120],{"class":183,"line":221},[181,3121,3122],{},"  child: Text('Hello', style: TextStyle(fontSize: 18, color: Colors.white)),\n",[181,3124,3125],{"class":183,"line":227},[181,3126,630],{},[13,3128,3129,3131],{},[170,3130,42],{}," — CSS (Scoped)",[147,3133,3135],{"className":244,"code":3134,"language":246,"meta":155,"style":155},"\u003Ctemplate>\n  \u003Cdiv class=\"card\">\n    \u003Cp class=\"card-text\">Hello\u003C\u002Fp>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cstyle scoped>\n.card {\n  padding: 16px;\n  background-color: blue;\n  border-radius: 8px;\n}\n.card-text {\n  font-size: 18px;\n  color: white;\n}\n\u003C\u002Fstyle>\n",[81,3136,3137,3145,3161,3181,3189,3197,3201,3213,3220,3237,3249,3263,3267,3274,3288,3300,3304],{"__ignoreMap":155},[181,3138,3139,3141,3143],{"class":183,"line":184},[181,3140,254],{"class":253},[181,3142,258],{"class":257},[181,3144,261],{"class":253},[181,3146,3147,3149,3151,3154,3156,3159],{"class":183,"line":190},[181,3148,266],{"class":253},[181,3150,514],{"class":257},[181,3152,3153],{"class":298}," class",[181,3155,305],{"class":253},[181,3157,3158],{"class":308},"\"card\"",[181,3160,261],{"class":253},[181,3162,3163,3165,3167,3169,3171,3174,3177,3179],{"class":183,"line":196},[181,3164,521],{"class":253},[181,3166,13],{"class":257},[181,3168,3153],{"class":298},[181,3170,305],{"class":253},[181,3172,3173],{"class":308},"\"card-text\"",[181,3175,3176],{"class":253},">Hello\u003C\u002F",[181,3178,13],{"class":257},[181,3180,261],{"class":253},[181,3182,3183,3185,3187],{"class":183,"line":202},[181,3184,557],{"class":253},[181,3186,514],{"class":257},[181,3188,261],{"class":253},[181,3190,3191,3193,3195],{"class":183,"line":209},[181,3192,280],{"class":253},[181,3194,258],{"class":257},[181,3196,261],{"class":253},[181,3198,3199],{"class":183,"line":215},[181,3200,206],{"emptyLinePlaceholder":205},[181,3202,3203,3205,3208,3211],{"class":183,"line":221},[181,3204,254],{"class":253},[181,3206,3207],{"class":257},"style",[181,3209,3210],{"class":298}," scoped",[181,3212,261],{"class":253},[181,3214,3215,3218],{"class":183,"line":227},[181,3216,3217],{"class":298},".card",[181,3219,1200],{"class":253},[181,3221,3222,3225,3228,3231,3234],{"class":183,"line":233},[181,3223,3224],{"class":330},"  padding",[181,3226,3227],{"class":253},": ",[181,3229,3230],{"class":330},"16",[181,3232,3233],{"class":326},"px",[181,3235,3236],{"class":253},";\n",[181,3238,3239,3242,3244,3247],{"class":183,"line":435},[181,3240,3241],{"class":330},"  background-color",[181,3243,3227],{"class":253},[181,3245,3246],{"class":330},"blue",[181,3248,3236],{"class":253},[181,3250,3251,3254,3256,3259,3261],{"class":183,"line":440},[181,3252,3253],{"class":330},"  border-radius",[181,3255,3227],{"class":253},[181,3257,3258],{"class":330},"8",[181,3260,3233],{"class":326},[181,3262,3236],{"class":253},[181,3264,3265],{"class":183,"line":446},[181,3266,236],{"class":253},[181,3268,3269,3272],{"class":183,"line":452},[181,3270,3271],{"class":298},".card-text",[181,3273,1200],{"class":253},[181,3275,3276,3279,3281,3284,3286],{"class":183,"line":458},[181,3277,3278],{"class":330},"  font-size",[181,3280,3227],{"class":253},[181,3282,3283],{"class":330},"18",[181,3285,3233],{"class":326},[181,3287,3236],{"class":253},[181,3289,3290,3293,3295,3298],{"class":183,"line":464},[181,3291,3292],{"class":330},"  color",[181,3294,3227],{"class":253},[181,3296,3297],{"class":330},"white",[181,3299,3236],{"class":253},[181,3301,3302],{"class":183,"line":470},[181,3303,236],{"class":253},[181,3305,3306,3308,3310],{"class":183,"line":476},[181,3307,280],{"class":253},[181,3309,3207],{"class":257},[181,3311,261],{"class":253},[163,3313,3315],{"id":3314},"常用-css-布局-flutter-布局-widget","常用 CSS 布局 ≈ Flutter 布局 Widget",[25,3317,3318,3329],{},[28,3319,3320],{},[31,3321,3322,3324,3327],{},[34,3323,39],{},[34,3325,3326],{},"CSS",[34,3328,690],{},[44,3330,3331,3346,3361,3380,3395,3410,3425,3440,3455,3470],{},[31,3332,3333,3338,3343],{},[49,3334,3335],{},[81,3336,3337],{},"Column",[49,3339,3340],{},[81,3341,3342],{},"display: flex; flex-direction: column",[49,3344,3345],{},"纵向排列",[31,3347,3348,3353,3358],{},[49,3349,3350],{},[81,3351,3352],{},"Row",[49,3354,3355],{},[81,3356,3357],{},"display: flex; flex-direction: row",[49,3359,3360],{},"横向排列",[31,3362,3363,3368,3377],{},[49,3364,3365],{},[81,3366,3367],{},"Stack",[49,3369,3370,3373,3374],{},[81,3371,3372],{},"position: relative"," + ",[81,3375,3376],{},"position: absolute",[49,3378,3379],{},"层叠",[31,3381,3382,3387,3392],{},[49,3383,3384],{},[81,3385,3386],{},"Expanded(flex: 1)",[49,3388,3389],{},[81,3390,3391],{},"flex: 1",[49,3393,3394],{},"弹性占比",[31,3396,3397,3402,3407],{},[49,3398,3399],{},[81,3400,3401],{},"SizedBox(width: 100)",[49,3403,3404],{},[81,3405,3406],{},"width: 100px",[49,3408,3409],{},"固定尺寸",[31,3411,3412,3417,3422],{},[49,3413,3414],{},[81,3415,3416],{},"Padding",[49,3418,3419],{},[81,3420,3421],{},"padding: 16px",[49,3423,3424],{},"内边距",[31,3426,3427,3432,3437],{},[49,3428,3429],{},[81,3430,3431],{},"Center",[49,3433,3434],{},[81,3435,3436],{},"display: flex; justify-content: center; align-items: center",[49,3438,3439],{},"居中",[31,3441,3442,3447,3452],{},[49,3443,3444],{},[81,3445,3446],{},"ListView",[49,3448,3449],{},[81,3450,3451],{},"overflow-y: auto",[49,3453,3454],{},"滚动列表",[31,3456,3457,3462,3467],{},[49,3458,3459],{},[81,3460,3461],{},"GridView",[49,3463,3464],{},[81,3465,3466],{},"display: grid; grid-template-columns: ...",[49,3468,3469],{},"网格",[31,3471,3472,3477,3482],{},[49,3473,3474],{},[81,3475,3476],{},"Wrap",[49,3478,3479],{},[81,3480,3481],{},"display: flex; flex-wrap: wrap",[49,3483,3484],{},"自动换行",[17,3486],{},[20,3488,3490],{"id":3489},"_11-网络请求对比","11. 网络请求对比",[13,3492,3493],{},[170,3494,3495],{},"Flutter — dio",[147,3497,3499],{"className":175,"code":3498,"language":177,"meta":155,"style":155},"final dio = Dio();\nfinal response = await dio.get('\u002Fapi\u002Fusers');\nfinal users = response.data;\n",[81,3500,3501,3506,3511],{"__ignoreMap":155},[181,3502,3503],{"class":183,"line":184},[181,3504,3505],{},"final dio = Dio();\n",[181,3507,3508],{"class":183,"line":190},[181,3509,3510],{},"final response = await dio.get('\u002Fapi\u002Fusers');\n",[181,3512,3513],{"class":183,"line":196},[181,3514,3515],{},"final users = response.data;\n",[13,3517,3518],{},[170,3519,3520],{},"Vue — axios \u002F fetch",[147,3522,3524],{"className":2400,"code":3523,"language":2402,"meta":155,"style":155},"import axios from 'axios'\n\n\u002F\u002F 在 composable 中封装（≈ Flutter 的 Repository）\nexport function useUsers() {\n  const users = ref([])\n  const loading = ref(false)\n\n  async function fetchUsers() {\n    loading.value = true\n    try {\n      const { data } = await axios.get('\u002Fapi\u002Fusers')\n      users.value = data\n    } finally {\n      loading.value = false\n    }\n  }\n\n  onMounted(fetchUsers)\n  return { users, loading }\n}\n",[81,3525,3526,3538,3542,3547,3560,3574,3592,3596,3608,3618,3625,3657,3667,3677,3687,3692,3696,3700,3708,3715],{"__ignoreMap":155},[181,3527,3528,3530,3533,3535],{"class":183,"line":184},[181,3529,594],{"class":326},[181,3531,3532],{"class":253}," axios ",[181,3534,600],{"class":326},[181,3536,3537],{"class":308}," 'axios'\n",[181,3539,3540],{"class":183,"line":190},[181,3541,206],{"emptyLinePlaceholder":205},[181,3543,3544],{"class":183,"line":196},[181,3545,3546],{"class":834},"\u002F\u002F 在 composable 中封装（≈ Flutter 的 Repository）\n",[181,3548,3549,3551,3554,3557],{"class":183,"line":202},[181,3550,2887],{"class":326},[181,3552,3553],{"class":326}," function",[181,3555,3556],{"class":298}," useUsers",[181,3558,3559],{"class":253},"() {\n",[181,3561,3562,3564,3567,3569,3571],{"class":183,"line":209},[181,3563,2915],{"class":326},[181,3565,3566],{"class":330}," users",[181,3568,618],{"class":326},[181,3570,621],{"class":298},[181,3572,3573],{"class":253},"([])\n",[181,3575,3576,3578,3581,3583,3585,3587,3590],{"class":183,"line":215},[181,3577,2915],{"class":326},[181,3579,3580],{"class":330}," loading",[181,3582,618],{"class":326},[181,3584,621],{"class":298},[181,3586,624],{"class":253},[181,3588,3589],{"class":330},"false",[181,3591,630],{"class":253},[181,3593,3594],{"class":183,"line":221},[181,3595,206],{"emptyLinePlaceholder":205},[181,3597,3598,3601,3603,3606],{"class":183,"line":227},[181,3599,3600],{"class":326},"  async",[181,3602,3553],{"class":326},[181,3604,3605],{"class":298}," fetchUsers",[181,3607,3559],{"class":253},[181,3609,3610,3613,3615],{"class":183,"line":233},[181,3611,3612],{"class":253},"    loading.value ",[181,3614,305],{"class":326},[181,3616,3617],{"class":330}," true\n",[181,3619,3620,3623],{"class":183,"line":435},[181,3621,3622],{"class":326},"    try",[181,3624,1200],{"class":253},[181,3626,3627,3630,3633,3636,3639,3641,3644,3647,3650,3652,3655],{"class":183,"line":440},[181,3628,3629],{"class":326},"      const",[181,3631,3632],{"class":253}," { ",[181,3634,3635],{"class":330},"data",[181,3637,3638],{"class":253}," } ",[181,3640,305],{"class":326},[181,3642,3643],{"class":326}," await",[181,3645,3646],{"class":253}," axios.",[181,3648,3649],{"class":298},"get",[181,3651,624],{"class":253},[181,3653,3654],{"class":308},"'\u002Fapi\u002Fusers'",[181,3656,630],{"class":253},[181,3658,3659,3662,3664],{"class":183,"line":446},[181,3660,3661],{"class":253},"      users.value ",[181,3663,305],{"class":326},[181,3665,3666],{"class":253}," data\n",[181,3668,3669,3672,3675],{"class":183,"line":452},[181,3670,3671],{"class":253},"    } ",[181,3673,3674],{"class":326},"finally",[181,3676,1200],{"class":253},[181,3678,3679,3682,3684],{"class":183,"line":458},[181,3680,3681],{"class":253},"      loading.value ",[181,3683,305],{"class":326},[181,3685,3686],{"class":330}," false\n",[181,3688,3689],{"class":183,"line":464},[181,3690,3691],{"class":253},"    }\n",[181,3693,3694],{"class":183,"line":470},[181,3695,230],{"class":253},[181,3697,3698],{"class":183,"line":476},[181,3699,206],{"emptyLinePlaceholder":205},[181,3701,3702,3705],{"class":183,"line":482},[181,3703,3704],{"class":298},"  onMounted",[181,3706,3707],{"class":253},"(fetchUsers)\n",[181,3709,3710,3712],{"class":183,"line":487},[181,3711,2948],{"class":326},[181,3713,3714],{"class":253}," { users, loading }\n",[181,3716,3718],{"class":183,"line":3717},20,[181,3719,236],{"class":253},[17,3721],{},[20,3723,3725],{"id":3724},"_12-组合式函数-composables-mixin-hook","12. 组合式函数 (Composables) ≈ Mixin \u002F Hook",[13,3727,3728],{},"Vue 的 Composable 是复用逻辑的核心方式，类似 Flutter 中的 Mixin 或 Riverpod 的自定义 Provider。",[147,3730,3732],{"className":2400,"code":3731,"language":2402,"meta":155,"style":155},"\u002F\u002F composables\u002FuseMouse.ts（≈ Flutter 的 mixin）\nimport { ref, onMounted, onUnmounted } from 'vue'\n\nexport function useMouse() {\n  const x = ref(0)\n  const y = ref(0)\n\n  function update(e: MouseEvent) {\n    x.value = e.pageX\n    y.value = e.pageY\n  }\n\n  onMounted(() => window.addEventListener('mousemove', update))\n  onUnmounted(() => window.removeEventListener('mousemove', update))\n\n  return { x, y }\n}\n",[81,3733,3734,3739,3750,3754,3765,3782,3799,3803,3823,3833,3843,3847,3851,3872,3891,3895,3902],{"__ignoreMap":155},[181,3735,3736],{"class":183,"line":184},[181,3737,3738],{"class":834},"\u002F\u002F composables\u002FuseMouse.ts（≈ Flutter 的 mixin）\n",[181,3740,3741,3743,3746,3748],{"class":183,"line":190},[181,3742,594],{"class":326},[181,3744,3745],{"class":253}," { ref, onMounted, onUnmounted } ",[181,3747,600],{"class":326},[181,3749,603],{"class":308},[181,3751,3752],{"class":183,"line":196},[181,3753,206],{"emptyLinePlaceholder":205},[181,3755,3756,3758,3760,3763],{"class":183,"line":202},[181,3757,2887],{"class":326},[181,3759,3553],{"class":326},[181,3761,3762],{"class":298}," useMouse",[181,3764,3559],{"class":253},[181,3766,3767,3769,3772,3774,3776,3778,3780],{"class":183,"line":209},[181,3768,2915],{"class":326},[181,3770,3771],{"class":330}," x",[181,3773,618],{"class":326},[181,3775,621],{"class":298},[181,3777,624],{"class":253},[181,3779,627],{"class":330},[181,3781,630],{"class":253},[181,3783,3784,3786,3789,3791,3793,3795,3797],{"class":183,"line":215},[181,3785,2915],{"class":326},[181,3787,3788],{"class":330}," y",[181,3790,618],{"class":326},[181,3792,621],{"class":298},[181,3794,624],{"class":253},[181,3796,627],{"class":330},[181,3798,630],{"class":253},[181,3800,3801],{"class":183,"line":221},[181,3802,206],{"emptyLinePlaceholder":205},[181,3804,3805,3807,3810,3812,3815,3817,3820],{"class":183,"line":227},[181,3806,2932],{"class":326},[181,3808,3809],{"class":298}," update",[181,3811,624],{"class":253},[181,3813,3814],{"class":322},"e",[181,3816,327],{"class":326},[181,3818,3819],{"class":298}," MouseEvent",[181,3821,3822],{"class":253},") {\n",[181,3824,3825,3828,3830],{"class":183,"line":233},[181,3826,3827],{"class":253},"    x.value ",[181,3829,305],{"class":326},[181,3831,3832],{"class":253}," e.pageX\n",[181,3834,3835,3838,3840],{"class":183,"line":435},[181,3836,3837],{"class":253},"    y.value ",[181,3839,305],{"class":326},[181,3841,3842],{"class":253}," e.pageY\n",[181,3844,3845],{"class":183,"line":440},[181,3846,230],{"class":253},[181,3848,3849],{"class":183,"line":446},[181,3850,206],{"emptyLinePlaceholder":205},[181,3852,3853,3855,3857,3859,3862,3864,3866,3869],{"class":183,"line":452},[181,3854,3704],{"class":298},[181,3856,1038],{"class":253},[181,3858,1041],{"class":326},[181,3860,3861],{"class":253}," window.",[181,3863,1402],{"class":298},[181,3865,624],{"class":253},[181,3867,3868],{"class":308},"'mousemove'",[181,3870,3871],{"class":253},", update))\n",[181,3873,3874,3877,3879,3881,3883,3885,3887,3889],{"class":183,"line":458},[181,3875,3876],{"class":298},"  onUnmounted",[181,3878,1038],{"class":253},[181,3880,1041],{"class":326},[181,3882,3861],{"class":253},[181,3884,1441],{"class":298},[181,3886,624],{"class":253},[181,3888,3868],{"class":308},[181,3890,3871],{"class":253},[181,3892,3893],{"class":183,"line":464},[181,3894,206],{"emptyLinePlaceholder":205},[181,3896,3897,3899],{"class":183,"line":470},[181,3898,2948],{"class":326},[181,3900,3901],{"class":253}," { x, y }\n",[181,3903,3904],{"class":183,"line":476},[181,3905,236],{"class":253},[147,3907,3909],{"className":244,"code":3908,"language":246,"meta":155,"style":155},"\u003C!-- 使用 -->\n\u003Cscript setup lang=\"ts\">\nimport { useMouse } from '@\u002Fcomposables\u002FuseMouse'\nconst { x, y } = useMouse()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Mouse: {{ x }}, {{ y }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n",[81,3910,3911,3915,3931,3943,3965,3973,3977,3985,3998],{"__ignoreMap":155},[181,3912,3913],{"class":183,"line":184},[181,3914,2965],{"class":834},[181,3916,3917,3919,3921,3923,3925,3927,3929],{"class":183,"line":190},[181,3918,254],{"class":253},[181,3920,295],{"class":257},[181,3922,299],{"class":298},[181,3924,302],{"class":298},[181,3926,305],{"class":253},[181,3928,309],{"class":308},[181,3930,261],{"class":253},[181,3932,3933,3935,3938,3940],{"class":183,"line":196},[181,3934,594],{"class":326},[181,3936,3937],{"class":253}," { useMouse } ",[181,3939,600],{"class":326},[181,3941,3942],{"class":308}," '@\u002Fcomposables\u002FuseMouse'\n",[181,3944,3945,3947,3949,3952,3954,3957,3959,3961,3963],{"class":183,"line":202},[181,3946,612],{"class":326},[181,3948,3632],{"class":253},[181,3950,3951],{"class":330},"x",[181,3953,1189],{"class":253},[181,3955,3956],{"class":330},"y",[181,3958,3638],{"class":253},[181,3960,305],{"class":326},[181,3962,3762],{"class":298},[181,3964,3011],{"class":253},[181,3966,3967,3969,3971],{"class":183,"line":209},[181,3968,280],{"class":253},[181,3970,295],{"class":257},[181,3972,261],{"class":253},[181,3974,3975],{"class":183,"line":215},[181,3976,206],{"emptyLinePlaceholder":205},[181,3978,3979,3981,3983],{"class":183,"line":221},[181,3980,254],{"class":253},[181,3982,258],{"class":257},[181,3984,261],{"class":253},[181,3986,3987,3989,3991,3994,3996],{"class":183,"line":227},[181,3988,266],{"class":253},[181,3990,13],{"class":257},[181,3992,3993],{"class":253},">Mouse: {{ x }}, {{ y }}\u003C\u002F",[181,3995,13],{"class":257},[181,3997,261],{"class":253},[181,3999,4000,4002,4004],{"class":183,"line":233},[181,4001,280],{"class":253},[181,4003,258],{"class":257},[181,4005,261],{"class":253},[17,4007],{},[20,4009,4011],{"id":4010},"_13-开发工具链对比","13. 开发工具链对比",[25,4013,4014,4025],{},[28,4015,4016],{},[31,4017,4018,4021,4023],{},[34,4019,4020],{},"用途",[34,4022,39],{},[34,4024,42],{},[44,4026,4027,4042,4058,4069,4080,4095,4108,4121,4134,4145],{},[31,4028,4029,4032,4037],{},[49,4030,4031],{},"创建项目",[49,4033,4034],{},[81,4035,4036],{},"flutter create",[49,4038,4039],{},[81,4040,4041],{},"npm create vue@latest",[31,4043,4044,4047,4052],{},[49,4045,4046],{},"开发服务器",[49,4048,4049],{},[81,4050,4051],{},"flutter run",[49,4053,4054,4057],{},[81,4055,4056],{},"npm run dev"," (Vite)",[31,4059,4060,4063,4066],{},[49,4061,4062],{},"热重载",[49,4064,4065],{},"内置 Hot Reload",[49,4067,4068],{},"Vite HMR",[31,4070,4071,4074,4077],{},[49,4072,4073],{},"调试工具",[49,4075,4076],{},"Flutter DevTools",[49,4078,4079],{},"Vue DevTools (浏览器插件)",[31,4081,4082,4085,4090],{},[49,4083,4084],{},"构建",[49,4086,4087],{},[81,4088,4089],{},"flutter build",[49,4091,4092],{},[81,4093,4094],{},"npm run build",[31,4096,4097,4100,4105],{},[49,4098,4099],{},"代码检查",[49,4101,4102],{},[81,4103,4104],{},"dart analyze",[49,4106,4107],{},"ESLint",[31,4109,4110,4113,4118],{},[49,4111,4112],{},"格式化",[49,4114,4115],{},[81,4116,4117],{},"dart format",[49,4119,4120],{},"Prettier",[31,4122,4123,4126,4131],{},[49,4124,4125],{},"测试",[49,4127,4128],{},[81,4129,4130],{},"flutter test",[49,4132,4133],{},"Vitest",[31,4135,4136,4139,4142],{},[49,4137,4138],{},"组件测试",[49,4140,4141],{},"Widget Test",[49,4143,4144],{},"@vue\u002Ftest-utils",[31,4146,4147,4150,4153],{},[49,4148,4149],{},"E2E 测试",[49,4151,4152],{},"Integration Test",[49,4154,4155],{},"Cypress \u002F Playwright",[17,4157],{},[20,4159,4161],{"id":4160},"_14-快速上手路径","14. 快速上手路径",[4163,4164,4165,4174,4188,4194,4210,4216,4222],"ol",{},[352,4166,4167,4170,4171,4173],{},[170,4168,4169],{},"环境搭建","：安装 Node.js → ",[81,4172,4041],{},"（选 TypeScript + Router + Pinia）",[352,4175,4176,4179,4180,4183,4184,4187],{},[170,4177,4178],{},"先跑通","：看懂 ",[81,4181,4182],{},"App.vue","、",[81,4185,4186],{},"main.ts","、路由配置",[352,4189,4190,4193],{},[170,4191,4192],{},"写组件","：把你熟悉的 Flutter Widget 用 Vue SFC 重写一遍",[352,4195,4196,4199,4200,4183,4202,4183,4205,4183,4208],{},[170,4197,4198],{},"学响应式","：重点掌握 ",[81,4201,653],{},[81,4203,4204],{},"reactive",[81,4206,4207],{},"computed",[81,4209,1180],{},[352,4211,4212,4215],{},[170,4213,4214],{},"学路由","：Vue Router 的配置式路由和 GoRouter 非常像",[352,4217,4218,4221],{},[170,4219,4220],{},"学状态管理","：Pinia 比 Riverpod 简单，先用再深入",[352,4223,4224,4227],{},[170,4225,4226],{},"学 CSS","：这是 Flutter 工程师最大的新领域，重点学 Flexbox 和 Grid",[17,4229],{},[20,4231,4233],{"id":4232},"_15-核心思维转换","15. 核心思维转换",[25,4235,4236,4246],{},[28,4237,4238],{},[31,4239,4240,4243],{},[34,4241,4242],{},"Flutter 思维",[34,4244,4245],{},"Vue 思维",[44,4247,4248,4256,4264,4272,4285,4299,4309],{},[31,4249,4250,4253],{},[49,4251,4252],{},"一切皆 Widget",[49,4254,4255],{},"一切皆组件",[31,4257,4258,4261],{},[49,4259,4260],{},"Widget 树是不可变的，rebuild 整棵树",[49,4262,4263],{},"模板 + 响应式数据，只更新变化的 DOM",[31,4265,4266,4269],{},[49,4267,4268],{},"样式是 Widget 的属性",[49,4270,4271],{},"样式和结构分离 (CSS)",[31,4273,4274,4280],{},[49,4275,4276,4279],{},[81,4277,4278],{},"BuildContext"," 访问上层数据",[49,4281,4282,4284],{},[81,4283,747],{}," \u002F Pinia 访问共享数据",[31,4286,4287,4293],{},[49,4288,4289,4292],{},[81,4290,4291],{},"Key"," 控制 Widget 复用",[49,4294,4295,4298],{},[81,4296,4297],{},":key"," 控制 DOM 元素复用",[31,4300,4301,4306],{},[49,4302,4303,4305],{},[81,4304,612],{}," Widget 优化性能",[49,4307,4308],{},"Vue 编译器自动优化",[31,4310,4311,4318],{},[49,4312,4313,4314,4317],{},"手动 ",[81,4315,4316],{},"setState"," 触发重建",[49,4319,4320],{},"修改响应式数据自动触发更新",[3207,4322,4323],{},"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 .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 pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":155,"searchDepth":190,"depth":190,"links":4325},[4326,4327,4328,4332,4337,4338,4344,4348,4351,4354,4357,4358,4359,4360,4361],{"id":22,"depth":190,"text":23},{"id":144,"depth":190,"text":145},{"id":160,"depth":190,"text":161,"children":4329},[4330,4331],{"id":165,"depth":196,"text":166},{"id":381,"depth":196,"text":382},{"id":676,"depth":190,"text":677,"children":4333},[4334,4335,4336],{"id":792,"depth":196,"text":793},{"id":919,"depth":196,"text":920},{"id":1079,"depth":196,"text":1080},{"id":1226,"depth":190,"text":1227},{"id":1464,"depth":190,"text":1465,"children":4339},[4340,4341,4342,4343],{"id":1468,"depth":196,"text":1469},{"id":1570,"depth":196,"text":1571},{"id":1696,"depth":196,"text":1697},{"id":1779,"depth":196,"text":1780},{"id":1913,"depth":190,"text":1914,"children":4345},[4346,4347],{"id":1993,"depth":196,"text":1994},{"id":2209,"depth":196,"text":2210},{"id":2333,"depth":190,"text":2334,"children":4349},[4350],{"id":2696,"depth":196,"text":2697},{"id":2769,"depth":190,"text":2770,"children":4352},[4353],{"id":2773,"depth":196,"text":2774},{"id":3077,"depth":190,"text":3078,"children":4355},[4356],{"id":3314,"depth":196,"text":3315},{"id":3489,"depth":190,"text":3490},{"id":3724,"depth":190,"text":3725},{"id":4010,"depth":190,"text":4011},{"id":4160,"depth":190,"text":4161},{"id":4232,"depth":190,"text":4233},"2026-05-03 10:30:00 CST","以 Flutter\u002FDart 的概念为锚点，快速建立 Vue 3 Composition API 的心智模型。","md",{},"\u002Fnotes\u002F2026-05-03-flutter-to-vue-comparison",{"title":5,"description":4363},"从 Flutter 工程师视角对比学习 Vue 3，覆盖架构、组件、状态管理、路由、构建工具等核心概念。","Flutter 工程师的 Vue 对比学习指南｜个人笔记","flutter-to-vue-comparison","notes\u002F2026-05-03-flutter-to-vue-comparison","sB7CQdAzuxe4xbstp90bejh4KoNkwUyCRBul2jWwzO0",1777744305315]