[{"data":1,"prerenderedAt":8078},["ShallowReactive",2],{"notes-cross-platform-comparison":3},{"id":4,"title":5,"body":6,"category":8066,"date":8067,"description":8068,"extension":8069,"meta":8070,"navigation":1237,"order":8066,"path":8071,"seo":8072,"seoDescription":8073,"seoTitle":8074,"slug":8075,"stem":8076,"__hash__":8077},"notes\u002Fnotes\u002F2026-05-03-cross-platform-comparison.md","Flutter \u002F iOS \u002F Android \u002F Vue 四平台横向对比",{"type":7,"value":8,"toc":8002},"minimark",[9,16,19,23,100,102,106,111,230,234,281,330,372,488,502,542,565,567,571,704,739,783,808,906,920,954,968,970,974,1156,1202,1204,1208,1260,1320,1368,1555,1580,1582,1586,1590,1702,1706,1759,1807,1851,1956,1976,2001,2003,2007,2146,2187,2189,2193,2223,2261,2298,2349,2402,2404,2408,2412,2524,2596,2632,2664,2770,2799,2844,2846,2850,2942,2974,3016,3018,3022,3120,3139,3141,3145,3149,3370,3374,3382,3416,3457,3494,3496,3500,3504,3670,3674,3749,3793,3836,3954,3978,4027,4056,4058,4062,4066,4200,4204,4257,4290,4315,4424,4459,4496,4528,4530,4534,4627,4652,4700,4702,4706,4710,4804,4808,4935,4969,5001,5018,5020,5024,5028,5161,5165,5204,5252,5309,5432,5467,5522,5556,5558,5562,5721,5723,5727,5731,5831,5835,5841,5862,5881,5883,5887,6044,6092,6094,6098,6102,6195,6199,6205,6225,6263,6306,6329,6350,6367,6369,6373,6377,6463,6539,6623,6703,6849,6851,6855,6956,6976,6993,6995,6999,7067,7069,7073,7077,7184,7188,7304,7308,7353,7397,7441,7561,7594,7625,7639,7641,7645,7731,7748,7750,7754,7998],[10,11,12],"blockquote",{},[13,14,15],"p",{},"以同一主题为轴，横向对比四个平台的基础、重难点、易错点与面试高频考点。\n适合有 Flutter\u002FiOS 经验、正在拓展 Android 和 Vue 的工程师复习使用。",[17,18],"hr",{},[20,21,22],"h2",{"id":22},"目录",[24,25,26,34,40,46,52,58,64,70,76,82,88,94],"ol",{},[27,28,29],"li",{},[30,31,33],"a",{"href":32},"#1-%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80%E5%AF%B9%E6%AF%94","语言基础对比",[27,35,36],{},[30,37,39],{"href":38},"#2-ui-%E6%9E%84%E5%BB%BA%E8%8C%83%E5%BC%8F","UI 构建范式",[27,41,42],{},[30,43,45],{"href":44},"#3-%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86","状态管理",[27,47,48],{},[30,49,51],{"href":50},"#4-%E5%B8%83%E5%B1%80%E7%B3%BB%E7%BB%9F","布局系统",[27,53,54],{},[30,55,57],{"href":56},"#5-%E5%AF%BC%E8%88%AA%E4%B8%8E%E8%B7%AF%E7%94%B1","导航与路由",[27,59,60],{},[30,61,63],{"href":62},"#6-%E7%BD%91%E7%BB%9C%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%B1%82","网络与数据层",[27,65,66],{},[30,67,69],{"href":68},"#7-%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8","持久化存储",[27,71,72],{},[30,73,75],{"href":74},"#8-%E5%B9%B6%E5%8F%91%E4%B8%8E%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B","并发与异步编程",[27,77,78],{},[30,79,81],{"href":80},"#9-%E6%B8%B2%E6%9F%93%E6%9C%BA%E5%88%B6%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96","渲染机制与性能优化",[27,83,84],{},[30,85,87],{"href":86},"#10-%E5%B9%B3%E5%8F%B0%E9%80%9A%E4%BF%A1%E4%B8%8E%E5%8E%9F%E7%94%9F%E8%83%BD%E5%8A%9B","平台通信与原生能力",[27,89,90],{},[30,91,93],{"href":92},"#11-%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F","架构模式",[27,95,96],{},[30,97,99],{"href":98},"#12-%E6%9E%84%E5%BB%BA%E6%B5%8B%E8%AF%95%E4%B8%8E%E5%8F%91%E5%B8%83","构建、测试与发布",[17,101],{},[20,103,105],{"id":104},"_1-语言基础对比","1. 语言基础对比",[107,108,110],"h3",{"id":109},"_11-语言概览","1.1 语言概览",[112,113,114,136],"table",{},[115,116,117],"thead",{},[118,119,120,124,127,130,133],"tr",{},[121,122,123],"th",{},"维度",[121,125,126],{},"Dart (Flutter)",[121,128,129],{},"Swift (iOS)",[121,131,132],{},"Kotlin (Android)",[121,134,135],{},"TypeScript (Vue)",[137,138,139,155,179,196,213],"tbody",{},[118,140,141,145,148,150,152],{},[142,143,144],"td",{},"类型系统",[142,146,147],{},"静态强类型",[142,149,147],{},[142,151,147],{},[142,153,154],{},"静态强类型（JS超集）",[118,156,157,160,163,171,176],{},[142,158,159],{},"空安全",[142,161,162],{},"Sound null safety",[142,164,165,166,170],{},"Optional (",[167,168,169],"code",{},"?",")",[142,172,173,174,170],{},"Nullable (",[167,175,169],{},[142,177,178],{},"strict null checks",[118,180,181,184,187,190,193],{},[142,182,183],{},"编译方式",[142,185,186],{},"AOT (release) + JIT (debug)",[142,188,189],{},"AOT",[142,191,192],{},"JVM 字节码 \u002F Native",[142,194,195],{},"转译为 JS",[118,197,198,201,204,207,210],{},[142,199,200],{},"内存管理",[142,202,203],{},"GC（分代回收）",[142,205,206],{},"ARC（引用计数）",[142,208,209],{},"GC（JVM）",[142,211,212],{},"GC（V8 引擎）",[118,214,215,218,221,224,227],{},[142,216,217],{},"函数式支持",[142,219,220],{},"有（不如 Kotlin 强）",[142,222,223],{},"协议 + 泛型 + 高阶函数",[142,225,226],{},"最强（扩展函数、作用域函数）",[142,228,229],{},"原生支持",[107,231,233],{"id":232},"_12-空安全机制对比","1.2 空安全机制对比",[235,236,241],"pre",{"className":237,"code":238,"language":239,"meta":240,"style":240},"language-dart shiki shiki-themes github-light github-dark","\u002F\u002F Dart — Sound null safety\nString? name;          \u002F\u002F 可空\nString nonNull = '';   \u002F\u002F 不可空\nname?.length;          \u002F\u002F 安全调用\nname!.length;          \u002F\u002F 强制解包（可能抛异常）\nname ?? 'default';     \u002F\u002F 空合并\n","dart","",[167,242,243,251,257,263,269,275],{"__ignoreMap":240},[244,245,248],"span",{"class":246,"line":247},"line",1,[244,249,250],{},"\u002F\u002F Dart — Sound null safety\n",[244,252,254],{"class":246,"line":253},2,[244,255,256],{},"String? name;          \u002F\u002F 可空\n",[244,258,260],{"class":246,"line":259},3,[244,261,262],{},"String nonNull = '';   \u002F\u002F 不可空\n",[244,264,266],{"class":246,"line":265},4,[244,267,268],{},"name?.length;          \u002F\u002F 安全调用\n",[244,270,272],{"class":246,"line":271},5,[244,273,274],{},"name!.length;          \u002F\u002F 强制解包（可能抛异常）\n",[244,276,278],{"class":246,"line":277},6,[244,279,280],{},"name ?? 'default';     \u002F\u002F 空合并\n",[235,282,286],{"className":283,"code":284,"language":285,"meta":240,"style":240},"language-swift shiki shiki-themes github-light github-dark","\u002F\u002F Swift — Optional\nvar name: String?       \u002F\u002F 可空\nvar nonNull: String = \"\"\u002F\u002F 不可空\nname?.count             \u002F\u002F 可选链\nname!.count             \u002F\u002F 强制解包（可能崩溃）\nname ?? \"default\"       \u002F\u002F 空合并\nif let n = name { }     \u002F\u002F 可选绑定（Dart 没有）\nguard let n = name else { return }  \u002F\u002F 提前退出\n","swift",[167,287,288,293,298,303,308,313,318,324],{"__ignoreMap":240},[244,289,290],{"class":246,"line":247},[244,291,292],{},"\u002F\u002F Swift — Optional\n",[244,294,295],{"class":246,"line":253},[244,296,297],{},"var name: String?       \u002F\u002F 可空\n",[244,299,300],{"class":246,"line":259},[244,301,302],{},"var nonNull: String = \"\"\u002F\u002F 不可空\n",[244,304,305],{"class":246,"line":265},[244,306,307],{},"name?.count             \u002F\u002F 可选链\n",[244,309,310],{"class":246,"line":271},[244,311,312],{},"name!.count             \u002F\u002F 强制解包（可能崩溃）\n",[244,314,315],{"class":246,"line":277},[244,316,317],{},"name ?? \"default\"       \u002F\u002F 空合并\n",[244,319,321],{"class":246,"line":320},7,[244,322,323],{},"if let n = name { }     \u002F\u002F 可选绑定（Dart 没有）\n",[244,325,327],{"class":246,"line":326},8,[244,328,329],{},"guard let n = name else { return }  \u002F\u002F 提前退出\n",[235,331,335],{"className":332,"code":333,"language":334,"meta":240,"style":240},"language-kotlin shiki shiki-themes github-light github-dark","\u002F\u002F Kotlin — Nullable\nvar name: String? = null  \u002F\u002F 可空\nvar nonNull: String = \"\"  \u002F\u002F 不可空\nname?.length              \u002F\u002F 安全调用\nname!!.length             \u002F\u002F 强制解包\nname ?: \"default\"         \u002F\u002F Elvis 操作符\nname?.let { println(it) } \u002F\u002F 作用域函数（Dart\u002FSwift 没有）\n","kotlin",[167,336,337,342,347,352,357,362,367],{"__ignoreMap":240},[244,338,339],{"class":246,"line":247},[244,340,341],{},"\u002F\u002F Kotlin — Nullable\n",[244,343,344],{"class":246,"line":253},[244,345,346],{},"var name: String? = null  \u002F\u002F 可空\n",[244,348,349],{"class":246,"line":259},[244,350,351],{},"var nonNull: String = \"\"  \u002F\u002F 不可空\n",[244,353,354],{"class":246,"line":265},[244,355,356],{},"name?.length              \u002F\u002F 安全调用\n",[244,358,359],{"class":246,"line":271},[244,360,361],{},"name!!.length             \u002F\u002F 强制解包\n",[244,363,364],{"class":246,"line":277},[244,365,366],{},"name ?: \"default\"         \u002F\u002F Elvis 操作符\n",[244,368,369],{"class":246,"line":320},[244,370,371],{},"name?.let { println(it) } \u002F\u002F 作用域函数（Dart\u002FSwift 没有）\n",[235,373,377],{"className":374,"code":375,"language":376,"meta":240,"style":240},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F TypeScript — strict null checks\nlet name: string | null = null;  \u002F\u002F 联合类型\nlet nonNull: string = \"\";\nname?.length;                     \u002F\u002F 可选链\nname!.length;                     \u002F\u002F 非空断言\nname ?? \"default\";                \u002F\u002F 空值合并\n","typescript",[167,378,379,385,419,439,453,471],{"__ignoreMap":240},[244,380,381],{"class":246,"line":247},[244,382,384],{"class":383},"sJ8bj","\u002F\u002F TypeScript — strict null checks\n",[244,386,387,391,395,398,402,405,408,411,413,416],{"class":246,"line":253},[244,388,390],{"class":389},"szBVR","let",[244,392,394],{"class":393},"sVt8B"," name",[244,396,397],{"class":389},":",[244,399,401],{"class":400},"sj4cs"," string",[244,403,404],{"class":389}," |",[244,406,407],{"class":400}," null",[244,409,410],{"class":389}," =",[244,412,407],{"class":400},[244,414,415],{"class":393},";  ",[244,417,418],{"class":383},"\u002F\u002F 联合类型\n",[244,420,421,423,426,428,430,432,436],{"class":246,"line":259},[244,422,390],{"class":389},[244,424,425],{"class":393}," nonNull",[244,427,397],{"class":389},[244,429,401],{"class":400},[244,431,410],{"class":389},[244,433,435],{"class":434},"sZZnC"," \"\"",[244,437,438],{"class":393},";\n",[244,440,441,444,447,450],{"class":246,"line":265},[244,442,443],{"class":393},"name?.",[244,445,446],{"class":400},"length",[244,448,449],{"class":393},";                     ",[244,451,452],{"class":383},"\u002F\u002F 可选链\n",[244,454,455,458,461,464,466,468],{"class":246,"line":271},[244,456,457],{"class":393},"name",[244,459,460],{"class":389},"!",[244,462,463],{"class":393},".",[244,465,446],{"class":400},[244,467,449],{"class":393},[244,469,470],{"class":383},"\u002F\u002F 非空断言\n",[244,472,473,476,479,482,485],{"class":246,"line":277},[244,474,475],{"class":393},"name ",[244,477,478],{"class":389},"??",[244,480,481],{"class":434}," \"default\"",[244,483,484],{"class":393},";                ",[244,486,487],{"class":383},"\u002F\u002F 空值合并\n",[10,489,490],{},[13,491,492,493,497,498,501],{},"⚠️ ",[494,495,496],"strong",{},"重难点","：Dart 的 null safety 是 sound 的（编译器保证运行时不会出现非空变量为 null），Swift 的 Optional 是类型系统的一部分（",[167,499,500],{},"Optional\u003CString>"," 是独立类型），Kotlin 在 JVM 上需要处理 Java 互操作的 platform type，TypeScript 的 null check 只在编译时生效，运行时仍是 JS。",[10,503,504,511],{},[13,505,506,507,510],{},"❌ ",[494,508,509],{},"易错点","：",[512,513,514,521,528,531],"ul",{},[27,515,516,517,520],{},"Dart：",[167,518,519],{},"late"," 变量未初始化就使用会运行时报错，不是编译时错误",[27,522,523,524,527],{},"Swift：",[167,525,526],{},"implicitly unwrapped optional (!)"," 声明容易忘记判空",[27,529,530],{},"Kotlin：Java 互操作时 platform type 不会触发空检查",[27,532,533,534,537,538,541],{},"TypeScript：",[167,535,536],{},"any"," 类型绕过所有类型检查，",[167,539,540],{},"as"," 断言不做运行时校验",[10,543,544,550],{},[13,545,546,547,510],{},"🎯 ",[494,548,549],{},"面试考点",[512,551,552,555,562],{},[27,553,554],{},"Dart sound null safety 和 Kotlin null safety 的区别？（Dart 编译器完全保证，Kotlin 有 platform type 漏洞）",[27,556,557,558,561],{},"Swift Optional 的本质是什么？（是枚举 ",[167,559,560],{},"enum Optional\u003CT> { case none, some(T) }","）",[27,563,564],{},"TypeScript 的类型检查发生在什么阶段？（仅编译时，运行时无类型信息）",[17,566],{},[107,568,570],{"id":569},"_13-值类型-vs-引用类型","1.3 值类型 vs 引用类型",[112,572,573,592],{},[115,574,575],{},[118,576,577,580,583,586,589],{},[121,578,579],{},"概念",[121,581,582],{},"Dart",[121,584,585],{},"Swift",[121,587,588],{},"Kotlin",[121,590,591],{},"TypeScript",[137,593,594,621,649,674],{},[118,595,596,599,602,612,618],{},[142,597,598],{},"值类型",[142,600,601],{},"无（除 int\u002Fdouble 等基本类型）",[142,603,604,607,608,611],{},[167,605,606],{},"struct",", ",[167,609,610],{},"enum",", 元组",[142,613,614,617],{},[167,615,616],{},"data class","（copy 语义）",[142,619,620],{},"原始类型（number, string, boolean）",[118,622,623,626,631,635,639],{},[142,624,625],{},"引用类型",[142,627,628],{},[167,629,630],{},"class",[142,632,633],{},[167,634,630],{},[142,636,637],{},[167,638,630],{},[142,640,641,607,644,607,647],{},[167,642,643],{},"object",[167,645,646],{},"array",[167,648,630],{},[118,650,651,654,660,663,668],{},[142,652,653],{},"Copy 机制",[142,655,656,657],{},"手动 ",[167,658,659],{},"copyWith",[142,661,662],{},"自动 copy-on-write",[142,664,656,665],{},[167,666,667],{},".copy()",[142,669,670,671],{},"手动展开 ",[167,672,673],{},"{...obj}",[118,675,676,679,688,692,697],{},[142,677,678],{},"不可变",[142,680,681,684,685],{},[167,682,683],{},"final"," \u002F ",[167,686,687],{},"const",[142,689,690],{},[167,691,390],{},[142,693,694],{},[167,695,696],{},"val",[142,698,699,684,701],{},[167,700,687],{},[167,702,703],{},"readonly",[235,705,707],{"className":237,"code":706,"language":239,"meta":240,"style":240},"\u002F\u002F Dart — 没有 struct，用 class + copyWith 模拟值语义\nclass Point {\n  final int x, y;\n  const Point(this.x, this.y);\n  Point copyWith({int? x, int? y}) => Point(x ?? this.x, y ?? this.y);\n}\n",[167,708,709,714,719,724,729,734],{"__ignoreMap":240},[244,710,711],{"class":246,"line":247},[244,712,713],{},"\u002F\u002F Dart — 没有 struct，用 class + copyWith 模拟值语义\n",[244,715,716],{"class":246,"line":253},[244,717,718],{},"class Point {\n",[244,720,721],{"class":246,"line":259},[244,722,723],{},"  final int x, y;\n",[244,725,726],{"class":246,"line":265},[244,727,728],{},"  const Point(this.x, this.y);\n",[244,730,731],{"class":246,"line":271},[244,732,733],{},"  Point copyWith({int? x, int? y}) => Point(x ?? this.x, y ?? this.y);\n",[244,735,736],{"class":246,"line":277},[244,737,738],{},"}\n",[235,740,742],{"className":283,"code":741,"language":285,"meta":240,"style":240},"\u002F\u002F Swift — struct 是值类型，赋值即拷贝\nstruct Point {\n    var x: Int\n    var y: Int\n}\nvar a = Point(x: 1, y: 2)\nvar b = a       \u002F\u002F 拷贝，修改 b 不影响 a\nb.x = 10       \u002F\u002F a.x 仍为 1\n",[167,743,744,749,754,759,764,768,773,778],{"__ignoreMap":240},[244,745,746],{"class":246,"line":247},[244,747,748],{},"\u002F\u002F Swift — struct 是值类型，赋值即拷贝\n",[244,750,751],{"class":246,"line":253},[244,752,753],{},"struct Point {\n",[244,755,756],{"class":246,"line":259},[244,757,758],{},"    var x: Int\n",[244,760,761],{"class":246,"line":265},[244,762,763],{},"    var y: Int\n",[244,765,766],{"class":246,"line":271},[244,767,738],{},[244,769,770],{"class":246,"line":277},[244,771,772],{},"var a = Point(x: 1, y: 2)\n",[244,774,775],{"class":246,"line":320},[244,776,777],{},"var b = a       \u002F\u002F 拷贝，修改 b 不影响 a\n",[244,779,780],{"class":246,"line":326},[244,781,782],{},"b.x = 10       \u002F\u002F a.x 仍为 1\n",[235,784,786],{"className":332,"code":785,"language":334,"meta":240,"style":240},"\u002F\u002F Kotlin — data class 提供 copy()，但仍是引用类型\ndata class Point(val x: Int, val y: Int)\nval a = Point(1, 2)\nval b = a.copy(x = 10)  \u002F\u002F a 不变\n",[167,787,788,793,798,803],{"__ignoreMap":240},[244,789,790],{"class":246,"line":247},[244,791,792],{},"\u002F\u002F Kotlin — data class 提供 copy()，但仍是引用类型\n",[244,794,795],{"class":246,"line":253},[244,796,797],{},"data class Point(val x: Int, val y: Int)\n",[244,799,800],{"class":246,"line":259},[244,801,802],{},"val a = Point(1, 2)\n",[244,804,805],{"class":246,"line":265},[244,806,807],{},"val b = a.copy(x = 10)  \u002F\u002F a 不变\n",[235,809,811],{"className":374,"code":810,"language":376,"meta":240,"style":240},"\u002F\u002F TypeScript — 对象是引用类型，需展开操作符\ninterface Point { x: number; y: number }\nconst a: Point = { x: 1, y: 2 };\nconst b = { ...a, x: 10 };  \u002F\u002F 浅拷贝\n",[167,812,813,818,852,880],{"__ignoreMap":240},[244,814,815],{"class":246,"line":247},[244,816,817],{"class":383},"\u002F\u002F TypeScript — 对象是引用类型，需展开操作符\n",[244,819,820,823,827,830,834,836,839,842,845,847,849],{"class":246,"line":253},[244,821,822],{"class":389},"interface",[244,824,826],{"class":825},"sScJk"," Point",[244,828,829],{"class":393}," { ",[244,831,833],{"class":832},"s4XuR","x",[244,835,397],{"class":389},[244,837,838],{"class":400}," number",[244,840,841],{"class":393},"; ",[244,843,844],{"class":832},"y",[244,846,397],{"class":389},[244,848,838],{"class":400},[244,850,851],{"class":393}," }\n",[244,853,854,856,859,861,863,865,868,871,874,877],{"class":246,"line":259},[244,855,687],{"class":389},[244,857,858],{"class":400}," a",[244,860,397],{"class":389},[244,862,826],{"class":825},[244,864,410],{"class":389},[244,866,867],{"class":393}," { x: ",[244,869,870],{"class":400},"1",[244,872,873],{"class":393},", y: ",[244,875,876],{"class":400},"2",[244,878,879],{"class":393}," };\n",[244,881,882,884,887,889,891,894,897,900,903],{"class":246,"line":265},[244,883,687],{"class":389},[244,885,886],{"class":400}," b",[244,888,410],{"class":389},[244,890,829],{"class":393},[244,892,893],{"class":389},"...",[244,895,896],{"class":393},"a, x: ",[244,898,899],{"class":400},"10",[244,901,902],{"class":393}," };  ",[244,904,905],{"class":383},"\u002F\u002F 浅拷贝\n",[10,907,908],{},[13,909,492,910,912,913,915,916,919],{},[494,911,496],{},"：Swift 的 struct 是真正的值类型（栈分配 + copy-on-write），Kotlin 的 ",[167,914,616],{}," 虽然提供 ",[167,917,918],{},"copy()"," 但本质仍在堆上。Dart 完全没有值类型 struct，全部是引用类型。",[10,921,922,926],{},[13,923,506,924,510],{},[494,925,509],{},[512,927,928,938,944],{},[27,929,930,931,933,934,937],{},"Swift 在 ",[167,932,606],{}," 中使用 ",[167,935,936],{},"mutating"," 方法时容易混淆值语义",[27,939,940,941,943],{},"TypeScript 的 ",[167,942,673],{}," 只是浅拷贝，嵌套对象仍是引用共享",[27,945,946,947,949,950,953],{},"Kotlin ",[167,948,616],{}," 的 ",[167,951,952],{},"equals()"," 只比较主构造函数参数",[10,955,956,960],{},[13,957,546,958,510],{},[494,959,549],{},[512,961,962,965],{},[27,963,964],{},"Swift 中什么时候用 struct，什么时候用 class？（默认用 struct，需要继承\u002F引用语义\u002Fdeinit 时用 class）",[27,966,967],{},"Dart 为什么没有 struct？（Dart 全部对象在堆上，GC 管理，不适合栈分配值类型）",[17,969],{},[107,971,973],{"id":972},"_14-集合操作与函数式编程","1.4 集合操作与函数式编程",[112,975,976,991],{},[115,977,978],{},[118,979,980,983,985,987,989],{},[121,981,982],{},"操作",[121,984,582],{},[121,986,585],{},[121,988,588],{},[121,990,591],{},[137,992,993,1015,1038,1061,1084,1110,1135],{},[118,994,995,998,1003,1007,1011],{},[142,996,997],{},"映射",[142,999,1000],{},[167,1001,1002],{},".map()",[142,1004,1005],{},[167,1006,1002],{},[142,1008,1009],{},[167,1010,1002],{},[142,1012,1013],{},[167,1014,1002],{},[118,1016,1017,1020,1025,1030,1034],{},[142,1018,1019],{},"过滤",[142,1021,1022],{},[167,1023,1024],{},".where()",[142,1026,1027],{},[167,1028,1029],{},".filter()",[142,1031,1032],{},[167,1033,1029],{},[142,1035,1036],{},[167,1037,1029],{},[118,1039,1040,1043,1048,1053,1057],{},[142,1041,1042],{},"归约",[142,1044,1045],{},[167,1046,1047],{},".fold()",[142,1049,1050],{},[167,1051,1052],{},".reduce()",[142,1054,1055],{},[167,1056,1047],{},[142,1058,1059],{},[167,1060,1052],{},[118,1062,1063,1066,1071,1076,1080],{},[142,1064,1065],{},"扁平化",[142,1067,1068],{},[167,1069,1070],{},".expand()",[142,1072,1073],{},[167,1074,1075],{},".flatMap()",[142,1077,1078],{},[167,1079,1075],{},[142,1081,1082],{},[167,1083,1075],{},[118,1085,1086,1089,1095,1101,1106],{},[142,1087,1088],{},"排序",[142,1090,1091,1094],{},[167,1092,1093],{},".sort()"," (原地)",[142,1096,1097,1100],{},[167,1098,1099],{},".sorted()"," (新数组)",[142,1102,1103],{},[167,1104,1105],{},".sortedBy()",[142,1107,1108,1094],{},[167,1109,1093],{},[118,1111,1112,1115,1120,1125,1130],{},[142,1113,1114],{},"首个匹配",[142,1116,1117],{},[167,1118,1119],{},".firstWhere()",[142,1121,1122],{},[167,1123,1124],{},".first(where:)",[142,1126,1127],{},[167,1128,1129],{},".first {}",[142,1131,1132],{},[167,1133,1134],{},".find()",[118,1136,1137,1140,1143,1148,1153],{},[142,1138,1139],{},"分组",[142,1141,1142],{},"手动",[142,1144,1145],{},[167,1146,1147],{},"Dictionary(grouping:by:)",[142,1149,1150],{},[167,1151,1152],{},".groupBy {}",[142,1154,1155],{},"手动或 lodash",[10,1157,1158,1162],{},[13,1159,506,1160,510],{},[494,1161,509],{},[512,1163,1164,1178,1187,1197],{},[27,1165,1166,1167,1169,1170,1173,1174,1177],{},"Dart 的 ",[167,1168,1002],{}," 返回 ",[494,1171,1172],{},"惰性 Iterable","，不是 List！需要 ",[167,1175,1176],{},".toList()"," 才能多次遍历",[27,1179,1180,1181,1183,1184,1186],{},"Swift 的 ",[167,1182,1099],{}," 返回新数组，",[167,1185,1093],{}," 原地排序（注意 mutating）",[27,1188,1189,1190,1192,1193,1196],{},"Kotlin 的 ",[167,1191,1002],{}," 立即求值返回 List，",[167,1194,1195],{},".asSequence().map()"," 才是惰性的",[27,1198,940,1199,1201],{},[167,1200,1093],{}," 是原地排序且返回自身（容易误以为返回新数组）",[17,1203],{},[107,1205,1207],{"id":1206},"_15-模式匹配","1.5 模式匹配",[235,1209,1211],{"className":237,"code":1210,"language":239,"meta":240,"style":240},"\u002F\u002F Dart 3 — switch 表达式 + sealed class\nsealed class Shape {}\nclass Circle extends Shape { final double r; Circle(this.r); }\nclass Rect extends Shape { final double w, h; Rect(this.w, this.h); }\n\nString describe(Shape s) => switch (s) {\n  Circle(r: var r) => '圆形 r=$r',\n  Rect(w: var w, h: var h) => '矩形 ${w}x$h',\n};\n",[167,1212,1213,1218,1223,1228,1233,1239,1244,1249,1254],{"__ignoreMap":240},[244,1214,1215],{"class":246,"line":247},[244,1216,1217],{},"\u002F\u002F Dart 3 — switch 表达式 + sealed class\n",[244,1219,1220],{"class":246,"line":253},[244,1221,1222],{},"sealed class Shape {}\n",[244,1224,1225],{"class":246,"line":259},[244,1226,1227],{},"class Circle extends Shape { final double r; Circle(this.r); }\n",[244,1229,1230],{"class":246,"line":265},[244,1231,1232],{},"class Rect extends Shape { final double w, h; Rect(this.w, this.h); }\n",[244,1234,1235],{"class":246,"line":271},[244,1236,1238],{"emptyLinePlaceholder":1237},true,"\n",[244,1240,1241],{"class":246,"line":277},[244,1242,1243],{},"String describe(Shape s) => switch (s) {\n",[244,1245,1246],{"class":246,"line":320},[244,1247,1248],{},"  Circle(r: var r) => '圆形 r=$r',\n",[244,1250,1251],{"class":246,"line":326},[244,1252,1253],{},"  Rect(w: var w, h: var h) => '矩形 ${w}x$h',\n",[244,1255,1257],{"class":246,"line":1256},9,[244,1258,1259],{},"};\n",[235,1261,1263],{"className":283,"code":1262,"language":285,"meta":240,"style":240},"\u002F\u002F Swift — enum + 关联值 + switch 穷举\nenum Shape {\n    case circle(r: Double)\n    case rect(w: Double, h: Double)\n}\nfunc describe(_ s: Shape) -> String {\n    switch s {\n    case .circle(let r): return \"圆形 r=\\(r)\"\n    case .rect(let w, let h): return \"矩形 \\(w)x\\(h)\"\n    }\n}\n",[167,1264,1265,1270,1275,1280,1285,1289,1294,1299,1304,1309,1315],{"__ignoreMap":240},[244,1266,1267],{"class":246,"line":247},[244,1268,1269],{},"\u002F\u002F Swift — enum + 关联值 + switch 穷举\n",[244,1271,1272],{"class":246,"line":253},[244,1273,1274],{},"enum Shape {\n",[244,1276,1277],{"class":246,"line":259},[244,1278,1279],{},"    case circle(r: Double)\n",[244,1281,1282],{"class":246,"line":265},[244,1283,1284],{},"    case rect(w: Double, h: Double)\n",[244,1286,1287],{"class":246,"line":271},[244,1288,738],{},[244,1290,1291],{"class":246,"line":277},[244,1292,1293],{},"func describe(_ s: Shape) -> String {\n",[244,1295,1296],{"class":246,"line":320},[244,1297,1298],{},"    switch s {\n",[244,1300,1301],{"class":246,"line":326},[244,1302,1303],{},"    case .circle(let r): return \"圆形 r=\\(r)\"\n",[244,1305,1306],{"class":246,"line":1256},[244,1307,1308],{},"    case .rect(let w, let h): return \"矩形 \\(w)x\\(h)\"\n",[244,1310,1312],{"class":246,"line":1311},10,[244,1313,1314],{},"    }\n",[244,1316,1318],{"class":246,"line":1317},11,[244,1319,738],{},[235,1321,1323],{"className":332,"code":1322,"language":334,"meta":240,"style":240},"\u002F\u002F Kotlin — sealed class + when\nsealed class Shape\ndata class Circle(val r: Double) : Shape()\ndata class Rect(val w: Double, val h: Double) : Shape()\n\nfun describe(s: Shape) = when (s) {\n    is Circle -> \"圆形 r=${s.r}\"\n    is Rect -> \"矩形 ${s.w}x${s.h}\"\n}\n",[167,1324,1325,1330,1335,1340,1345,1349,1354,1359,1364],{"__ignoreMap":240},[244,1326,1327],{"class":246,"line":247},[244,1328,1329],{},"\u002F\u002F Kotlin — sealed class + when\n",[244,1331,1332],{"class":246,"line":253},[244,1333,1334],{},"sealed class Shape\n",[244,1336,1337],{"class":246,"line":259},[244,1338,1339],{},"data class Circle(val r: Double) : Shape()\n",[244,1341,1342],{"class":246,"line":265},[244,1343,1344],{},"data class Rect(val w: Double, val h: Double) : Shape()\n",[244,1346,1347],{"class":246,"line":271},[244,1348,1238],{"emptyLinePlaceholder":1237},[244,1350,1351],{"class":246,"line":277},[244,1352,1353],{},"fun describe(s: Shape) = when (s) {\n",[244,1355,1356],{"class":246,"line":320},[244,1357,1358],{},"    is Circle -> \"圆形 r=${s.r}\"\n",[244,1360,1361],{"class":246,"line":326},[244,1362,1363],{},"    is Rect -> \"矩形 ${s.w}x${s.h}\"\n",[244,1365,1366],{"class":246,"line":1256},[244,1367,738],{},[235,1369,1371],{"className":374,"code":1370,"language":376,"meta":240,"style":240},"\u002F\u002F TypeScript — 联合类型 + 判别属性\ntype Shape =\n  | { kind: 'circle'; r: number }\n  | { kind: 'rect'; w: number; h: number };\n\nfunction describe(s: Shape): string {\n  switch (s.kind) {\n    case 'circle': return `圆形 r=${s.r}`;\n    case 'rect': return `矩形 ${s.w}x${s.h}`;\n  }\n}\n",[167,1372,1373,1378,1389,1415,1448,1452,1479,1487,1514,1546,1551],{"__ignoreMap":240},[244,1374,1375],{"class":246,"line":247},[244,1376,1377],{"class":383},"\u002F\u002F TypeScript — 联合类型 + 判别属性\n",[244,1379,1380,1383,1386],{"class":246,"line":253},[244,1381,1382],{"class":389},"type",[244,1384,1385],{"class":825}," Shape",[244,1387,1388],{"class":389}," =\n",[244,1390,1391,1394,1396,1399,1401,1404,1406,1409,1411,1413],{"class":246,"line":259},[244,1392,1393],{"class":389},"  |",[244,1395,829],{"class":393},[244,1397,1398],{"class":832},"kind",[244,1400,397],{"class":389},[244,1402,1403],{"class":434}," 'circle'",[244,1405,841],{"class":393},[244,1407,1408],{"class":832},"r",[244,1410,397],{"class":389},[244,1412,838],{"class":400},[244,1414,851],{"class":393},[244,1416,1417,1419,1421,1423,1425,1428,1430,1433,1435,1437,1439,1442,1444,1446],{"class":246,"line":265},[244,1418,1393],{"class":389},[244,1420,829],{"class":393},[244,1422,1398],{"class":832},[244,1424,397],{"class":389},[244,1426,1427],{"class":434}," 'rect'",[244,1429,841],{"class":393},[244,1431,1432],{"class":832},"w",[244,1434,397],{"class":389},[244,1436,838],{"class":400},[244,1438,841],{"class":393},[244,1440,1441],{"class":832},"h",[244,1443,397],{"class":389},[244,1445,838],{"class":400},[244,1447,879],{"class":393},[244,1449,1450],{"class":246,"line":271},[244,1451,1238],{"emptyLinePlaceholder":1237},[244,1453,1454,1457,1460,1463,1466,1468,1470,1472,1474,1476],{"class":246,"line":277},[244,1455,1456],{"class":389},"function",[244,1458,1459],{"class":825}," describe",[244,1461,1462],{"class":393},"(",[244,1464,1465],{"class":832},"s",[244,1467,397],{"class":389},[244,1469,1385],{"class":825},[244,1471,170],{"class":393},[244,1473,397],{"class":389},[244,1475,401],{"class":400},[244,1477,1478],{"class":393}," {\n",[244,1480,1481,1484],{"class":246,"line":320},[244,1482,1483],{"class":389},"  switch",[244,1485,1486],{"class":393}," (s.kind) {\n",[244,1488,1489,1492,1494,1497,1500,1503,1505,1507,1509,1512],{"class":246,"line":326},[244,1490,1491],{"class":389},"    case",[244,1493,1403],{"class":434},[244,1495,1496],{"class":393},": ",[244,1498,1499],{"class":389},"return",[244,1501,1502],{"class":434}," `圆形 r=${",[244,1504,1465],{"class":393},[244,1506,463],{"class":434},[244,1508,1408],{"class":393},[244,1510,1511],{"class":434},"}`",[244,1513,438],{"class":393},[244,1515,1516,1518,1520,1522,1524,1527,1529,1531,1533,1536,1538,1540,1542,1544],{"class":246,"line":1256},[244,1517,1491],{"class":389},[244,1519,1427],{"class":434},[244,1521,1496],{"class":393},[244,1523,1499],{"class":389},[244,1525,1526],{"class":434}," `矩形 ${",[244,1528,1465],{"class":393},[244,1530,463],{"class":434},[244,1532,1432],{"class":393},[244,1534,1535],{"class":434},"}x${",[244,1537,1465],{"class":393},[244,1539,463],{"class":434},[244,1541,1441],{"class":393},[244,1543,1511],{"class":434},[244,1545,438],{"class":393},[244,1547,1548],{"class":246,"line":1311},[244,1549,1550],{"class":393},"  }\n",[244,1552,1553],{"class":246,"line":1317},[244,1554,738],{"class":393},[10,1556,1557,1561],{},[13,1558,546,1559,510],{},[494,1560,549],{},[512,1562,1563,1566,1569],{},[27,1564,1565],{},"Dart 3 的 sealed class 和 Kotlin 的 sealed class 有什么区别？（Dart 要求同文件，Kotlin 要求同 package）",[27,1567,1568],{},"Swift enum 的关联值 vs Kotlin sealed class，哪个更灵活？（Kotlin 的子类可以有自己的方法和属性）",[27,1570,1571,1572,1575,1576,1579],{},"TypeScript 的判别联合 (discriminated union) 如何保证穷举？（开启 ",[167,1573,1574],{},"strictNullChecks"," + ",[167,1577,1578],{},"never"," 类型兜底）",[17,1581],{},[20,1583,1585],{"id":1584},"_2-ui-构建范式","2. UI 构建范式",[107,1587,1589],{"id":1588},"_21-声明式-ui-对比","2.1 声明式 UI 对比",[112,1591,1592,1610],{},[115,1593,1594],{},[118,1595,1596,1598,1601,1604,1607],{},[121,1597,123],{},[121,1599,1600],{},"Flutter (Widget)",[121,1602,1603],{},"iOS (SwiftUI)",[121,1605,1606],{},"Android (Compose)",[121,1608,1609],{},"Vue (SFC)",[137,1611,1612,1632,1649,1669,1685],{},[118,1613,1614,1617,1620,1623,1626],{},[142,1615,1616],{},"构建单元",[142,1618,1619],{},"Widget 类",[142,1621,1622],{},"View struct",[142,1624,1625],{},"@Composable 函数",[142,1627,1628,1631],{},[167,1629,1630],{},".vue"," 单文件组件",[118,1633,1634,1637,1640,1643,1646],{},[142,1635,1636],{},"最小更新单位",[142,1638,1639],{},"Element 树 diff",[142,1641,1642],{},"View diff",[142,1644,1645],{},"Recomposition",[142,1647,1648],{},"Virtual DOM diff",[118,1650,1651,1654,1657,1660,1663],{},[142,1652,1653],{},"UI 描述方式",[142,1655,1656],{},"Dart 代码嵌套",[142,1658,1659],{},"Swift DSL（ViewBuilder）",[142,1661,1662],{},"Kotlin DSL",[142,1664,1665,1668],{},[167,1666,1667],{},"\u003Ctemplate>"," HTML 模板",[118,1670,1671,1674,1677,1680,1682],{},[142,1672,1673],{},"样式机制",[142,1675,1676],{},"Widget 属性",[142,1678,1679],{},"Modifier 链式调用",[142,1681,1679],{},[142,1683,1684],{},"CSS \u002F Scoped CSS",[118,1686,1687,1690,1693,1696,1699],{},[142,1688,1689],{},"有\u002F无状态区分",[142,1691,1692],{},"StatelessWidget \u002F StatefulWidget",[142,1694,1695],{},"View (统一)",[142,1697,1698],{},"@Composable (统一)",[142,1700,1701],{},"统一（Composition API）",[107,1703,1705],{"id":1704},"_22-基本组件写法","2.2 基本组件写法",[235,1707,1709],{"className":237,"code":1708,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter — StatelessWidget\nclass 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', style: TextStyle(fontSize: 16));\n  }\n}\n",[167,1710,1711,1716,1721,1726,1731,1736,1741,1746,1751,1755],{"__ignoreMap":240},[244,1712,1713],{"class":246,"line":247},[244,1714,1715],{},"\u002F\u002F Flutter — StatelessWidget\n",[244,1717,1718],{"class":246,"line":253},[244,1719,1720],{},"class Greeting extends StatelessWidget {\n",[244,1722,1723],{"class":246,"line":259},[244,1724,1725],{},"  final String name;\n",[244,1727,1728],{"class":246,"line":265},[244,1729,1730],{},"  const Greeting({required this.name});\n",[244,1732,1733],{"class":246,"line":271},[244,1734,1735],{},"  \n",[244,1737,1738],{"class":246,"line":277},[244,1739,1740],{},"  @override\n",[244,1742,1743],{"class":246,"line":320},[244,1744,1745],{},"  Widget build(BuildContext context) {\n",[244,1747,1748],{"class":246,"line":326},[244,1749,1750],{},"    return Text('Hello, $name', style: TextStyle(fontSize: 16));\n",[244,1752,1753],{"class":246,"line":1256},[244,1754,1550],{},[244,1756,1757],{"class":246,"line":1311},[244,1758,738],{},[235,1760,1762],{"className":283,"code":1761,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI — View\nstruct Greeting: View {\n    let name: String\n    \n    var body: some View {\n        Text(\"Hello, \\(name)\")\n            .font(.body)\n    }\n}\n",[167,1763,1764,1769,1774,1779,1784,1789,1794,1799,1803],{"__ignoreMap":240},[244,1765,1766],{"class":246,"line":247},[244,1767,1768],{},"\u002F\u002F SwiftUI — View\n",[244,1770,1771],{"class":246,"line":253},[244,1772,1773],{},"struct Greeting: View {\n",[244,1775,1776],{"class":246,"line":259},[244,1777,1778],{},"    let name: String\n",[244,1780,1781],{"class":246,"line":265},[244,1782,1783],{},"    \n",[244,1785,1786],{"class":246,"line":271},[244,1787,1788],{},"    var body: some View {\n",[244,1790,1791],{"class":246,"line":277},[244,1792,1793],{},"        Text(\"Hello, \\(name)\")\n",[244,1795,1796],{"class":246,"line":320},[244,1797,1798],{},"            .font(.body)\n",[244,1800,1801],{"class":246,"line":326},[244,1802,1314],{},[244,1804,1805],{"class":246,"line":1256},[244,1806,738],{},[235,1808,1810],{"className":332,"code":1809,"language":334,"meta":240,"style":240},"\u002F\u002F Compose — @Composable\n@Composable\nfun Greeting(name: String) {\n    Text(\n        text = \"Hello, $name\",\n        fontSize = 16.sp\n    )\n}\n",[167,1811,1812,1817,1822,1827,1832,1837,1842,1847],{"__ignoreMap":240},[244,1813,1814],{"class":246,"line":247},[244,1815,1816],{},"\u002F\u002F Compose — @Composable\n",[244,1818,1819],{"class":246,"line":253},[244,1820,1821],{},"@Composable\n",[244,1823,1824],{"class":246,"line":259},[244,1825,1826],{},"fun Greeting(name: String) {\n",[244,1828,1829],{"class":246,"line":265},[244,1830,1831],{},"    Text(\n",[244,1833,1834],{"class":246,"line":271},[244,1835,1836],{},"        text = \"Hello, $name\",\n",[244,1838,1839],{"class":246,"line":277},[244,1840,1841],{},"        fontSize = 16.sp\n",[244,1843,1844],{"class":246,"line":320},[244,1845,1846],{},"    )\n",[244,1848,1849],{"class":246,"line":326},[244,1850,738],{},[235,1852,1856],{"className":1853,"code":1854,"language":1855,"meta":240,"style":240},"language-vue shiki shiki-themes github-light github-dark","\u003C!-- Vue 3 — SFC -->\n\u003Cscript setup lang=\"ts\">\ndefineProps\u003C{ name: string }>()\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cspan style=\"font-size: 16px\">Hello, {{ name }}\u003C\u002Fspan>\n\u003C\u002Ftemplate>\n","vue",[167,1857,1858,1863,1887,1904,1913,1917,1926,1948],{"__ignoreMap":240},[244,1859,1860],{"class":246,"line":247},[244,1861,1862],{"class":383},"\u003C!-- Vue 3 — SFC -->\n",[244,1864,1865,1868,1872,1875,1878,1881,1884],{"class":246,"line":253},[244,1866,1867],{"class":393},"\u003C",[244,1869,1871],{"class":1870},"s9eBZ","script",[244,1873,1874],{"class":825}," setup",[244,1876,1877],{"class":825}," lang",[244,1879,1880],{"class":393},"=",[244,1882,1883],{"class":434},"\"ts\"",[244,1885,1886],{"class":393},">\n",[244,1888,1889,1892,1895,1897,1899,1901],{"class":246,"line":259},[244,1890,1891],{"class":825},"defineProps",[244,1893,1894],{"class":393},"\u003C{ ",[244,1896,457],{"class":832},[244,1898,397],{"class":389},[244,1900,401],{"class":400},[244,1902,1903],{"class":393}," }>()\n",[244,1905,1906,1909,1911],{"class":246,"line":265},[244,1907,1908],{"class":393},"\u003C\u002F",[244,1910,1871],{"class":1870},[244,1912,1886],{"class":393},[244,1914,1915],{"class":246,"line":271},[244,1916,1238],{"emptyLinePlaceholder":1237},[244,1918,1919,1921,1924],{"class":246,"line":277},[244,1920,1867],{"class":393},[244,1922,1923],{"class":1870},"template",[244,1925,1886],{"class":393},[244,1927,1928,1931,1933,1936,1938,1941,1944,1946],{"class":246,"line":320},[244,1929,1930],{"class":393},"  \u003C",[244,1932,244],{"class":1870},[244,1934,1935],{"class":825}," style",[244,1937,1880],{"class":393},[244,1939,1940],{"class":434},"\"font-size: 16px\"",[244,1942,1943],{"class":393},">Hello, {{ name }}\u003C\u002F",[244,1945,244],{"class":1870},[244,1947,1886],{"class":393},[244,1949,1950,1952,1954],{"class":246,"line":326},[244,1951,1908],{"class":393},[244,1953,1923],{"class":1870},[244,1955,1886],{"class":393},[10,1957,1958,1962],{},[13,1959,492,1960,510],{},[494,1961,496],{},[512,1963,1964,1967,1970],{},[27,1965,1966],{},"Flutter 的 Widget 是不可变的配置描述，真正的可变状态在 State 对象中；SwiftUI 的 View 也是 struct（值类型），每次 body 重新计算",[27,1968,1969],{},"Compose 的 @Composable 不是类，是函数 + 编译器插件注入的位置 key",[27,1971,1972,1973,1975],{},"Vue 的 ",[167,1974,1667],{}," 编译为 render 函数，最终通过 Virtual DOM diff 更新真实 DOM",[10,1977,1978,1982],{},[13,1979,546,1980,510],{},[494,1981,549],{},[512,1983,1984,1987,1994],{},[27,1985,1986],{},"Flutter Widget 为什么设计成 immutable？（频繁创建+销毁，GC 高效处理短命对象；真正昂贵的是 RenderObject，它被复用）",[27,1988,1989,1990,1993],{},"SwiftUI 的 ",[167,1991,1992],{},"some View"," 是什么？（不透明返回类型，编译器知道具体类型但调用者不知道）",[27,1995,1996,1997,2000],{},"Vue ",[167,1998,1999],{},"\u003Cscript setup>"," 和 Options API 的区别？（setup 是 Composition API 的语法糖，更好的类型推导和 tree-shaking）",[17,2002],{},[107,2004,2006],{"id":2005},"_23-组件生命周期对比","2.3 组件生命周期对比",[112,2008,2009,2028],{},[115,2010,2011],{},[118,2012,2013,2016,2019,2022,2025],{},[121,2014,2015],{},"阶段",[121,2017,2018],{},"Flutter",[121,2020,2021],{},"SwiftUI",[121,2023,2024],{},"Compose",[121,2026,2027],{},"Vue 3",[137,2029,2030,2053,2078,2096,2121],{},[118,2031,2032,2035,2040,2045,2048],{},[142,2033,2034],{},"创建",[142,2036,2037],{},[167,2038,2039],{},"createState()",[142,2041,2042],{},[167,2043,2044],{},"init()",[142,2046,2047],{},"首次组合",[142,2049,2050],{},[167,2051,2052],{},"setup()",[118,2054,2055,2058,2063,2068,2073],{},[142,2056,2057],{},"挂载",[142,2059,2060],{},[167,2061,2062],{},"initState()",[142,2064,2065],{},[167,2066,2067],{},"onAppear",[142,2069,2070],{},[167,2071,2072],{},"LaunchedEffect",[142,2074,2075],{},[167,2076,2077],{},"onMounted()",[118,2079,2080,2083,2088,2091,2093],{},[142,2081,2082],{},"更新",[142,2084,2085],{},[167,2086,2087],{},"didUpdateWidget()",[142,2089,2090],{},"body 重新计算",[142,2092,1645],{},[142,2094,2095],{},"响应式自动触发",[118,2097,2098,2101,2106,2111,2116],{},[142,2099,2100],{},"卸载",[142,2102,2103],{},[167,2104,2105],{},"dispose()",[142,2107,2108],{},[167,2109,2110],{},"onDisappear",[142,2112,2113],{},[167,2114,2115],{},"DisposableEffect",[142,2117,2118],{},[167,2119,2120],{},"onUnmounted()",[118,2122,2123,2126,2131,2136,2141],{},[142,2124,2125],{},"依赖变化",[142,2127,2128],{},[167,2129,2130],{},"didChangeDependencies()",[142,2132,2133],{},[167,2134,2135],{},"onChange(of:)",[142,2137,2138],{},[167,2139,2140],{},"LaunchedEffect(key)",[142,2142,2143],{},[167,2144,2145],{},"watch()",[10,2147,2148,2152],{},[13,2149,506,2150,510],{},[494,2151,509],{},[512,2153,2154,2165,2174,2181],{},[27,2155,2156,2157,2160,2161,2164],{},"Flutter: ",[167,2158,2159],{},"initState"," 中不能直接用 ",[167,2162,2163],{},"context"," 获取 InheritedWidget（此时 didChangeDependencies 还没调用）",[27,2166,2167,2168,2170,2171],{},"SwiftUI: ",[167,2169,2067],{}," 可能被多次调用（NavigationStack 返回时），不等于 ",[167,2172,2173],{},"viewDidLoad",[27,2175,2176,2177,2180],{},"Compose: ",[167,2178,2179],{},"LaunchedEffect(Unit)"," 只在首次组合执行，key 变化会取消重启",[27,2182,2183,2184,2186],{},"Vue: ",[167,2185,2052],{}," 在组件实例创建后、挂载前执行，此时 DOM 不存在",[17,2188],{},[107,2190,2192],{"id":2191},"_24-条件渲染与列表渲染","2.4 条件渲染与列表渲染",[235,2194,2196],{"className":237,"code":2195,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter\nColumn(children: [\n  if (isLoggedIn) Text('Welcome'),      \u002F\u002F 条件\n  ...items.map((e) => ListTile(title: Text(e))), \u002F\u002F 列表\n])\n",[167,2197,2198,2203,2208,2213,2218],{"__ignoreMap":240},[244,2199,2200],{"class":246,"line":247},[244,2201,2202],{},"\u002F\u002F Flutter\n",[244,2204,2205],{"class":246,"line":253},[244,2206,2207],{},"Column(children: [\n",[244,2209,2210],{"class":246,"line":259},[244,2211,2212],{},"  if (isLoggedIn) Text('Welcome'),      \u002F\u002F 条件\n",[244,2214,2215],{"class":246,"line":265},[244,2216,2217],{},"  ...items.map((e) => ListTile(title: Text(e))), \u002F\u002F 列表\n",[244,2219,2220],{"class":246,"line":271},[244,2221,2222],{},"])\n",[235,2224,2226],{"className":283,"code":2225,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI\nVStack {\n    if isLoggedIn { Text(\"Welcome\") }    \u002F\u002F 条件\n    ForEach(items, id: \\.self) { item in \u002F\u002F 列表\n        Text(item)\n    }\n}\n",[167,2227,2228,2233,2238,2243,2248,2253,2257],{"__ignoreMap":240},[244,2229,2230],{"class":246,"line":247},[244,2231,2232],{},"\u002F\u002F SwiftUI\n",[244,2234,2235],{"class":246,"line":253},[244,2236,2237],{},"VStack {\n",[244,2239,2240],{"class":246,"line":259},[244,2241,2242],{},"    if isLoggedIn { Text(\"Welcome\") }    \u002F\u002F 条件\n",[244,2244,2245],{"class":246,"line":265},[244,2246,2247],{},"    ForEach(items, id: \\.self) { item in \u002F\u002F 列表\n",[244,2249,2250],{"class":246,"line":271},[244,2251,2252],{},"        Text(item)\n",[244,2254,2255],{"class":246,"line":277},[244,2256,1314],{},[244,2258,2259],{"class":246,"line":320},[244,2260,738],{},[235,2262,2264],{"className":332,"code":2263,"language":334,"meta":240,"style":240},"\u002F\u002F Compose\nColumn {\n    if (isLoggedIn) { Text(\"Welcome\") }  \u002F\u002F 条件\n    items.forEach { item ->              \u002F\u002F 列表\n        Text(item)\n    }\n}\n",[167,2265,2266,2271,2276,2281,2286,2290,2294],{"__ignoreMap":240},[244,2267,2268],{"class":246,"line":247},[244,2269,2270],{},"\u002F\u002F Compose\n",[244,2272,2273],{"class":246,"line":253},[244,2274,2275],{},"Column {\n",[244,2277,2278],{"class":246,"line":259},[244,2279,2280],{},"    if (isLoggedIn) { Text(\"Welcome\") }  \u002F\u002F 条件\n",[244,2282,2283],{"class":246,"line":265},[244,2284,2285],{},"    items.forEach { item ->              \u002F\u002F 列表\n",[244,2287,2288],{"class":246,"line":271},[244,2289,2252],{},[244,2291,2292],{"class":246,"line":277},[244,2293,1314],{},[244,2295,2296],{"class":246,"line":320},[244,2297,738],{},[235,2299,2301],{"className":1853,"code":2300,"language":1855,"meta":240,"style":240},"\u003C!-- Vue -->\n\u003Cdiv>\n  \u003Cspan v-if=\"isLoggedIn\">Welcome\u003C\u002Fspan>    \u003C!-- 条件 -->\n  \u003Cdiv v-for=\"item in items\" :key=\"item\">   \u003C!-- 列表 -->\n    {{ item }}\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\n",[167,2302,2303,2308,2317,2322,2327,2332,2341],{"__ignoreMap":240},[244,2304,2305],{"class":246,"line":247},[244,2306,2307],{"class":383},"\u003C!-- Vue -->\n",[244,2309,2310,2312,2315],{"class":246,"line":253},[244,2311,1867],{"class":393},[244,2313,2314],{"class":1870},"div",[244,2316,1886],{"class":393},[244,2318,2319],{"class":246,"line":259},[244,2320,2321],{"class":393},"  \u003Cspan v-if=\"isLoggedIn\">Welcome\u003C\u002Fspan>    \u003C!-- 条件 -->\n",[244,2323,2324],{"class":246,"line":265},[244,2325,2326],{"class":393},"  \u003Cdiv v-for=\"item in items\" :key=\"item\">   \u003C!-- 列表 -->\n",[244,2328,2329],{"class":246,"line":271},[244,2330,2331],{"class":393},"    {{ item }}\n",[244,2333,2334,2337,2339],{"class":246,"line":277},[244,2335,2336],{"class":393},"  \u003C\u002F",[244,2338,2314],{"class":1870},[244,2340,1886],{"class":393},[244,2342,2343,2345,2347],{"class":246,"line":320},[244,2344,1908],{"class":393},[244,2346,2314],{"class":1870},[244,2348,1886],{"class":393},[10,2350,2351,2355],{},[13,2352,506,2353,510],{},[494,2354,509],{},[512,2356,2357,2367,2378,2388],{},[27,2358,1972,2359,2362,2363,2366],{},[167,2360,2361],{},"v-for"," 必须绑定 ",[167,2364,2365],{},":key","，否则 diff 算法会就地复用导致状态错乱",[27,2368,2369,2370,2373,2374,2377],{},"Flutter 的 ",[167,2371,2372],{},"ListView.builder"," 要用 ",[167,2375,2376],{},"Key"," 来保持列表项状态",[27,2379,1989,2380,2383,2384,2387],{},[167,2381,2382],{},"ForEach"," 中的 ",[167,2385,2386],{},"id"," 参数必须唯一，否则 diff 出错",[27,2389,2390,2391,2394,2395,949,2398,2401],{},"Compose 的 ",[167,2392,2393],{},"LazyColumn"," 中 ",[167,2396,2397],{},"items",[167,2399,2400],{},"key"," 同理必须唯一且稳定",[17,2403],{},[20,2405,2407],{"id":2406},"_3-状态管理","3. 状态管理",[107,2409,2411],{"id":2410},"_31-局部状态","3.1 局部状态",[112,2413,2414,2429],{},[115,2415,2416],{},[118,2417,2418,2421,2423,2425,2427],{},[121,2419,2420],{},"场景",[121,2422,2018],{},[121,2424,2021],{},[121,2426,2024],{},[121,2428,2027],{},[137,2430,2431,2462,2483],{},[118,2432,2433,2436,2444,2449,2454],{},[142,2434,2435],{},"组件内状态",[142,2437,2438,1575,2441],{},[167,2439,2440],{},"StatefulWidget",[167,2442,2443],{},"setState()",[142,2445,2446],{},[167,2447,2448],{},"@State",[142,2450,2451],{},[167,2452,2453],{},"remember { mutableStateOf() }",[142,2455,2456,684,2459],{},[167,2457,2458],{},"ref()",[167,2460,2461],{},"reactive()",[118,2463,2464,2467,2470,2473,2478],{},[142,2465,2466],{},"派生状态",[142,2468,2469],{},"手动计算（或 ValueNotifier）",[142,2471,2472],{},"computed property",[142,2474,2475],{},[167,2476,2477],{},"derivedStateOf",[142,2479,2480],{},[167,2481,2482],{},"computed()",[118,2484,2485,2488,2495,2506,2513],{},[142,2486,2487],{},"副作用",[142,2489,2490,684,2492],{},[167,2491,2159],{},[167,2493,2494],{},"didUpdateWidget",[142,2496,2497,684,2500,684,2503],{},[167,2498,2499],{},".onAppear",[167,2501,2502],{},".onChange",[167,2504,2505],{},".task",[142,2507,2508,684,2510],{},[167,2509,2072],{},[167,2511,2512],{},"SideEffect",[142,2514,2515,684,2518,684,2521],{},[167,2516,2517],{},"onMounted",[167,2519,2520],{},"watch",[167,2522,2523],{},"watchEffect",[235,2525,2527],{"className":237,"code":2526,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter\nclass Counter extends StatefulWidget {\n  @override State\u003CCounter> createState() => _CounterState();\n}\nclass _CounterState extends State\u003CCounter> {\n  int count = 0;\n  @override\n  Widget build(BuildContext context) {\n    return TextButton(\n      onPressed: () => setState(() => count++),\n      child: Text('$count'),\n    );\n  }\n}\n",[167,2528,2529,2533,2538,2543,2547,2552,2557,2561,2565,2570,2575,2580,2586,2591],{"__ignoreMap":240},[244,2530,2531],{"class":246,"line":247},[244,2532,2202],{},[244,2534,2535],{"class":246,"line":253},[244,2536,2537],{},"class Counter extends StatefulWidget {\n",[244,2539,2540],{"class":246,"line":259},[244,2541,2542],{},"  @override State\u003CCounter> createState() => _CounterState();\n",[244,2544,2545],{"class":246,"line":265},[244,2546,738],{},[244,2548,2549],{"class":246,"line":271},[244,2550,2551],{},"class _CounterState extends State\u003CCounter> {\n",[244,2553,2554],{"class":246,"line":277},[244,2555,2556],{},"  int count = 0;\n",[244,2558,2559],{"class":246,"line":320},[244,2560,1740],{},[244,2562,2563],{"class":246,"line":326},[244,2564,1745],{},[244,2566,2567],{"class":246,"line":1256},[244,2568,2569],{},"    return TextButton(\n",[244,2571,2572],{"class":246,"line":1311},[244,2573,2574],{},"      onPressed: () => setState(() => count++),\n",[244,2576,2577],{"class":246,"line":1317},[244,2578,2579],{},"      child: Text('$count'),\n",[244,2581,2583],{"class":246,"line":2582},12,[244,2584,2585],{},"    );\n",[244,2587,2589],{"class":246,"line":2588},13,[244,2590,1550],{},[244,2592,2594],{"class":246,"line":2593},14,[244,2595,738],{},[235,2597,2599],{"className":283,"code":2598,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI\nstruct Counter: View {\n    @State private var count = 0\n    var body: some View {\n        Button(\"\\(count)\") { count += 1 }\n    }\n}\n",[167,2600,2601,2605,2610,2615,2619,2624,2628],{"__ignoreMap":240},[244,2602,2603],{"class":246,"line":247},[244,2604,2232],{},[244,2606,2607],{"class":246,"line":253},[244,2608,2609],{},"struct Counter: View {\n",[244,2611,2612],{"class":246,"line":259},[244,2613,2614],{},"    @State private var count = 0\n",[244,2616,2617],{"class":246,"line":265},[244,2618,1788],{},[244,2620,2621],{"class":246,"line":271},[244,2622,2623],{},"        Button(\"\\(count)\") { count += 1 }\n",[244,2625,2626],{"class":246,"line":277},[244,2627,1314],{},[244,2629,2630],{"class":246,"line":320},[244,2631,738],{},[235,2633,2635],{"className":332,"code":2634,"language":334,"meta":240,"style":240},"\u002F\u002F Compose\n@Composable\nfun Counter() {\n    var count by remember { mutableStateOf(0) }\n    Button(onClick = { count++ }) { Text(\"$count\") }\n}\n",[167,2636,2637,2641,2645,2650,2655,2660],{"__ignoreMap":240},[244,2638,2639],{"class":246,"line":247},[244,2640,2270],{},[244,2642,2643],{"class":246,"line":253},[244,2644,1821],{},[244,2646,2647],{"class":246,"line":259},[244,2648,2649],{},"fun Counter() {\n",[244,2651,2652],{"class":246,"line":265},[244,2653,2654],{},"    var count by remember { mutableStateOf(0) }\n",[244,2656,2657],{"class":246,"line":271},[244,2658,2659],{},"    Button(onClick = { count++ }) { Text(\"$count\") }\n",[244,2661,2662],{"class":246,"line":277},[244,2663,738],{},[235,2665,2667],{"className":1853,"code":2666,"language":1855,"meta":240,"style":240},"\u003C!-- Vue 3 -->\n\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\nconst count = ref(0)\n\u003C\u002Fscript>\n\u003Ctemplate>\n  \u003Cbutton @click=\"count++\">{{ count }}\u003C\u002Fbutton>\n\u003C\u002Ftemplate>\n",[167,2668,2669,2674,2690,2704,2724,2732,2740,2762],{"__ignoreMap":240},[244,2670,2671],{"class":246,"line":247},[244,2672,2673],{"class":383},"\u003C!-- Vue 3 -->\n",[244,2675,2676,2678,2680,2682,2684,2686,2688],{"class":246,"line":253},[244,2677,1867],{"class":393},[244,2679,1871],{"class":1870},[244,2681,1874],{"class":825},[244,2683,1877],{"class":825},[244,2685,1880],{"class":393},[244,2687,1883],{"class":434},[244,2689,1886],{"class":393},[244,2691,2692,2695,2698,2701],{"class":246,"line":259},[244,2693,2694],{"class":389},"import",[244,2696,2697],{"class":393}," { ref } ",[244,2699,2700],{"class":389},"from",[244,2702,2703],{"class":434}," 'vue'\n",[244,2705,2706,2708,2711,2713,2716,2718,2721],{"class":246,"line":265},[244,2707,687],{"class":389},[244,2709,2710],{"class":400}," count",[244,2712,410],{"class":389},[244,2714,2715],{"class":825}," ref",[244,2717,1462],{"class":393},[244,2719,2720],{"class":400},"0",[244,2722,2723],{"class":393},")\n",[244,2725,2726,2728,2730],{"class":246,"line":271},[244,2727,1908],{"class":393},[244,2729,1871],{"class":1870},[244,2731,1886],{"class":393},[244,2733,2734,2736,2738],{"class":246,"line":277},[244,2735,1867],{"class":393},[244,2737,1923],{"class":1870},[244,2739,1886],{"class":393},[244,2741,2742,2744,2747,2750,2752,2755,2758,2760],{"class":246,"line":320},[244,2743,1930],{"class":393},[244,2745,2746],{"class":1870},"button",[244,2748,2749],{"class":825}," @click",[244,2751,1880],{"class":393},[244,2753,2754],{"class":434},"\"count++\"",[244,2756,2757],{"class":393},">{{ count }}\u003C\u002F",[244,2759,2746],{"class":1870},[244,2761,1886],{"class":393},[244,2763,2764,2766,2768],{"class":246,"line":326},[244,2765,1908],{"class":393},[244,2767,1923],{"class":1870},[244,2769,1886],{"class":393},[10,2771,2772,2776],{},[13,2773,492,2774,510],{},[494,2775,496],{},[512,2777,2778,2783,2788,2794],{},[27,2779,2369,2780,2782],{},[167,2781,2443],{}," 标记当前 Element dirty，触发 build 重建整棵 Widget 子树（但 Element\u002FRenderObject 会复用）",[27,2784,1989,2785,2787],{},[167,2786,2448],{}," 内部是 property wrapper，值存储在框架管理的存储区，View struct 重建不影响状态",[27,2789,2390,2790,2793],{},[167,2791,2792],{},"remember"," 将值绑定到组合树的位置 (positional memoization)",[27,2795,1972,2796,2798],{},[167,2797,2458],{}," 基于 Proxy 代理实现响应式追踪",[10,2800,2801,2805],{},[13,2802,546,2803,510],{},[494,2804,549],{},[512,2806,2807,2812,2828,2835],{},[27,2808,2809,2811],{},[167,2810,2443],{}," 是同步还是异步的？（同步的，但 build 是在下一帧微任务中执行）",[27,2813,2814,2815,2817,2818,2817,2821,2817,2824,2827],{},"SwiftUI ",[167,2816,2448],{}," vs ",[167,2819,2820],{},"@Binding",[167,2822,2823],{},"@ObservedObject",[167,2825,2826],{},"@StateObject"," 的区别？（所有权和生命周期不同）",[27,2829,1996,2830,2817,2832,2834],{},[167,2831,2458],{},[167,2833,2461],{}," 怎么选？（ref 适合基本类型和需要替换的对象，reactive 适合不会整体替换的对象）",[27,2836,2837,2838,2817,2840,2843],{},"Compose ",[167,2839,2792],{},[167,2841,2842],{},"rememberSaveable"," 的区别？（后者在 configuration change 时保存状态）",[17,2845],{},[107,2847,2849],{"id":2848},"_32-全局跨组件状态","3.2 全局\u002F跨组件状态",[112,2851,2852,2867],{},[115,2853,2854],{},[118,2855,2856,2859,2861,2863,2865],{},[121,2857,2858],{},"方案",[121,2860,2018],{},[121,2862,2021],{},[121,2864,2024],{},[121,2866,2027],{},[137,2868,2869,2891,2908,2925],{},[118,2870,2871,2874,2877,2882,2888],{},[142,2872,2873],{},"官方推荐",[142,2875,2876],{},"Provider \u002F Riverpod",[142,2878,2879],{},[167,2880,2881],{},"@EnvironmentObject",[142,2883,2884,2885],{},"ViewModel + ",[167,2886,2887],{},"hiltViewModel()",[142,2889,2890],{},"Pinia",[118,2892,2893,2896,2899,2902,2905],{},[142,2894,2895],{},"依赖注入",[142,2897,2898],{},"Provider \u002F GetIt \u002F GetX",[142,2900,2901],{},"Environment \u002F @EnvironmentObject",[142,2903,2904],{},"Hilt (Dagger)",[142,2906,2907],{},"provide \u002F inject",[118,2909,2910,2913,2916,2919,2922],{},[142,2911,2912],{},"事件总线",[142,2914,2915],{},"EventBus \u002F Stream",[142,2917,2918],{},"Combine Publisher",[142,2920,2921],{},"SharedFlow \u002F Channel",[142,2923,2924],{},"mitt \u002F EventBus",[118,2926,2927,2930,2933,2936,2939],{},[142,2928,2929],{},"不可变状态流",[142,2931,2932],{},"BLoC (Stream)",[142,2934,2935],{},"Combine",[142,2937,2938],{},"StateFlow + MVI",[142,2940,2941],{},"Pinia + actions",[10,2943,2944,2948],{},[13,2945,492,2946,510],{},[494,2947,496],{},[512,2949,2950,2961,2968,2971],{},[27,2951,2952,2953,2956,2957,2960],{},"Flutter Provider 基于 InheritedWidget，",[167,2954,2955],{},"context.watch\u003CT>()"," 和 ",[167,2958,2959],{},"context.read\u003CT>()"," 的区别是前者订阅变化后者不订阅",[27,2962,2814,2963,2817,2965,2967],{},[167,2964,2826],{},[167,2966,2823],{},"：前者拥有对象生命周期，后者不拥有（View 重建可能导致对象重建）",[27,2969,2970],{},"Compose 的 ViewModel 在 Activity\u002FFragment 的 ViewModelStore 中，存活于 configuration change",[27,2972,2973],{},"Vue 的 Pinia store 是单例，但 SSR 场景下需要注意 store 隔离",[10,2975,2976,2980],{},[13,2977,506,2978,510],{},[494,2979,509],{},[512,2981,2982,2991,3000,3007],{},[27,2983,2156,2984,2987,2988],{},[167,2985,2986],{},"Provider"," 放错位置导致 ",[167,2989,2990],{},"ProviderNotFoundException",[27,2992,2993,2994,2996,2997,2999],{},"SwiftUI: 用 ",[167,2995,2823],{}," 代替 ",[167,2998,2826],{}," 导致状态丢失（View 重建时对象被重新创建）",[27,3001,3002,3003,3006],{},"Compose: 在 ",[167,3004,3005],{},"@Composable"," 之外收集 StateFlow 导致不响应更新",[27,3008,3009,3010,3012,3013,561],{},"Vue: 在 ",[167,3011,1999],{}," 外部解构 Pinia store 导致响应性丢失（需要 ",[167,3014,3015],{},"storeToRefs()",[17,3017],{},[107,3019,3021],{"id":3020},"_33-响应式原理对比","3.3 响应式原理对比",[112,3023,3024,3039],{},[115,3025,3026],{},[118,3027,3028,3030,3032,3034,3036],{},[121,3029,123],{},[121,3031,2018],{},[121,3033,2021],{},[121,3035,2024],{},[121,3037,3038],{},"Vue",[137,3040,3041,3069,3086,3103],{},[118,3042,3043,3046,3054,3060,3066],{},[142,3044,3045],{},"触发机制",[142,3047,656,3048,3050,3051],{},[167,3049,2443],{}," 或 ",[167,3052,3053],{},"notifyListeners()",[142,3055,3056,3059],{},[167,3057,3058],{},"@Published"," 属性变化",[142,3061,3062,3065],{},[167,3063,3064],{},"MutableState"," 值变化",[142,3067,3068],{},"Proxy getter\u002Fsetter 拦截",[118,3070,3071,3074,3077,3080,3083],{},[142,3072,3073],{},"追踪粒度",[142,3075,3076],{},"Widget 级别",[142,3078,3079],{},"View body 级别",[142,3081,3082],{},"读取点级别（最细）",[142,3084,3085],{},"属性级别",[118,3087,3088,3091,3094,3097,3100],{},[142,3089,3090],{},"更新范围",[142,3092,3093],{},"标记 dirty 的 Element 子树",[142,3095,3096],{},"body 重新求值",[142,3098,3099],{},"只重组读取了变化状态的 Composable",[142,3101,3102],{},"关联的组件重新渲染",[118,3104,3105,3108,3111,3114,3117],{},[142,3106,3107],{},"批量更新",[142,3109,3110],{},"同一帧合并",[142,3112,3113],{},"同一 RunLoop 合并",[142,3115,3116],{},"快照系统 (Snapshot)",[142,3118,3119],{},"nextTick 微任务合并",[10,3121,3122,3126],{},[13,3123,546,3124,510],{},[494,3125,549],{},[512,3127,3128,3131,3134],{},[27,3129,3130],{},"Vue 的响应式原理是什么？（Vue 3 用 Proxy 拦截 get\u002Fset，收集依赖 track，派发更新 trigger）",[27,3132,3133],{},"Compose 的 Recomposition 如何做到精确更新？（编译器插入的代码在状态读取时建立依赖关系，只重组受影响的 scope）",[27,3135,2369,3136,3138],{},[167,3137,687],{}," Widget 为什么能优化性能？（canUpdate 时 widget == 旧widget，跳过 build）",[17,3140],{},[20,3142,3144],{"id":3143},"_4-布局系统","4. 布局系统",[107,3146,3148],{"id":3147},"_41-布局方式对比","4.1 布局方式对比",[112,3150,3151,3167],{},[115,3152,3153],{},[118,3154,3155,3158,3160,3162,3164],{},[121,3156,3157],{},"布局",[121,3159,2018],{},[121,3161,2021],{},[121,3163,2024],{},[121,3165,3166],{},"Vue (CSS)",[137,3168,3169,3193,3217,3242,3270,3298,3337],{},[118,3170,3171,3174,3179,3184,3188],{},[142,3172,3173],{},"水平排列",[142,3175,3176],{},[167,3177,3178],{},"Row",[142,3180,3181],{},[167,3182,3183],{},"HStack",[142,3185,3186],{},[167,3187,3178],{},[142,3189,3190],{},[167,3191,3192],{},"display: flex",[118,3194,3195,3198,3203,3208,3212],{},[142,3196,3197],{},"垂直排列",[142,3199,3200],{},[167,3201,3202],{},"Column",[142,3204,3205],{},[167,3206,3207],{},"VStack",[142,3209,3210],{},[167,3211,3202],{},[142,3213,3214],{},[167,3215,3216],{},"flex-direction: column",[118,3218,3219,3222,3227,3232,3237],{},[142,3220,3221],{},"层叠",[142,3223,3224],{},[167,3225,3226],{},"Stack",[142,3228,3229],{},[167,3230,3231],{},"ZStack",[142,3233,3234],{},[167,3235,3236],{},"Box",[142,3238,3239],{},[167,3240,3241],{},"position: relative\u002Fabsolute",[118,3243,3244,3247,3252,3260,3265],{},[142,3245,3246],{},"网格",[142,3248,3249],{},[167,3250,3251],{},"GridView",[142,3253,3254,684,3257],{},[167,3255,3256],{},"LazyVGrid",[167,3258,3259],{},"LazyHGrid",[142,3261,3262],{},[167,3263,3264],{},"LazyVerticalGrid",[142,3266,3267],{},[167,3268,3269],{},"display: grid",[118,3271,3272,3275,3283,3288,3293],{},[142,3273,3274],{},"弹性占比",[142,3276,3277,684,3280],{},[167,3278,3279],{},"Expanded",[167,3281,3282],{},"Flexible",[142,3284,3285],{},[167,3286,3287],{},".frame(maxWidth: .infinity)",[142,3289,3290],{},[167,3291,3292],{},"Modifier.weight()",[142,3294,3295],{},[167,3296,3297],{},"flex: 1",[118,3299,3300,3303,3311,3319,3326],{},[142,3301,3302],{},"间距",[142,3304,3305,684,3308],{},[167,3306,3307],{},"SizedBox",[167,3309,3310],{},"Padding",[142,3312,3313,684,3316],{},[167,3314,3315],{},".padding()",[167,3317,3318],{},"Spacer()",[142,3320,3321,684,3323],{},[167,3322,3318],{},[167,3324,3325],{},"Modifier.padding()",[142,3327,3328,684,3331,684,3334],{},[167,3329,3330],{},"margin",[167,3332,3333],{},"padding",[167,3335,3336],{},"gap",[118,3338,3339,3342,3350,3358,3365],{},[142,3340,3341],{},"滚动",[142,3343,3344,684,3347],{},[167,3345,3346],{},"ListView",[167,3348,3349],{},"SingleChildScrollView",[142,3351,3352,684,3355],{},[167,3353,3354],{},"ScrollView",[167,3356,3357],{},"List",[142,3359,3360,684,3362],{},[167,3361,2393],{},[167,3363,3364],{},"verticalScroll",[142,3366,3367],{},[167,3368,3369],{},"overflow: auto",[107,3371,3373],{"id":3372},"_42-约束传递机制","4.2 约束传递机制",[235,3375,3380],{"className":3376,"code":3378,"language":3379},[3377],"language-text","Flutter: 父传约束 → 子确定大小 → 父决定位置（单次遍历，O(n)）\n         BoxConstraints(minW, maxW, minH, maxH) → Size → Offset\n\nSwiftUI: 父提供建议尺寸 → 子返回实际尺寸 → 父决定位置\n         ProposedViewSize → ViewDimensions → position\n\nCompose: 父传约束 → 子测量返回 Placeable → 父布局\n         Constraints → MeasureResult → layout { placeable.place(x, y) }\n\nVue\u002FCSS: 盒模型 → 流式布局\u002FFlex\u002FGrid\n         content-box \u002F border-box → 流式计算\n","text",[167,3381,3378],{"__ignoreMap":240},[10,3383,3384,3388],{},[13,3385,492,3386,510],{},[494,3387,496],{},[512,3389,3390,3396,3403,3409],{},[27,3391,2369,3392,3395],{},[167,3393,3394],{},"Constraints go down, Sizes go up, Parent sets position"," 是布局三原则",[27,3397,3398,3399,3402],{},"SwiftUI 布局是",[494,3400,3401],{},"子控制大小","：父只提建议，子自己决定多大（和 Flutter 不同）",[27,3404,2390,3405,3408],{},[167,3406,3407],{},"intrinsic measurement"," 允许子组件查询兄弟尺寸（SubcomposeLayout）",[27,3410,3411,3412,3415],{},"CSS 的 ",[167,3413,3414],{},"flex-grow\u002Fshrink\u002Fbasis"," 三属性组合是 Flexbox 的核心",[10,3417,3418,3422],{},[13,3419,506,3420,510],{},[494,3421,509],{},[512,3423,3424,3437,3445,3451],{},[27,3425,2156,3426,3428,3429,3432,3433,3050,3435,561],{},[167,3427,3202],{}," 中子组件高度无限 + 外层没有约束 → ",[167,3430,3431],{},"unbounded height"," 崩溃（需 ",[167,3434,3279],{},[167,3436,3282],{},[27,3438,2156,3439,3441,3442,3444],{},[167,3440,3178],{}," 中放 ",[167,3443,3346],{}," 不加约束 → 无限宽度崩溃",[27,3446,2167,3447,3450],{},[167,3448,3449],{},".frame()"," 只是提建议，不是强制约束；子 View 可以超出 frame",[27,3452,3453,3454,3456],{},"Vue\u002FCSS: ",[167,3455,3297],{}," 在嵌套 flex 容器中不生效时，检查父容器是否设了高度",[10,3458,3459,3463],{},[13,3460,546,3461,510],{},[494,3462,549],{},[512,3464,3465,3481,3484],{},[27,3466,3467,3468,2956,3470,3472,3473,3476,3477,3480],{},"Flutter 中 ",[167,3469,3279],{},[167,3471,3282],{}," 的区别？（Expanded 是 ",[167,3474,3475],{},"fit: FlexFit.tight"," 必须填满，Flexible 是 ",[167,3478,3479],{},"loose"," 可以小于分配空间）",[27,3482,3483],{},"解释 Flutter 的 \"tight constraints\" 和 \"loose constraints\"？（tight: min==max, loose: min==0）",[27,3485,3486,3487,2956,3490,3493],{},"CSS ",[167,3488,3489],{},"flex: 1 1 0",[167,3491,3492],{},"flex: 1 1 auto"," 的区别？（basis 为 0 按比例分配，auto 先满足内容再分配剩余）",[17,3495],{},[20,3497,3499],{"id":3498},"_5-导航与路由","5. 导航与路由",[107,3501,3503],{"id":3502},"_51-路由方案对比","5.1 路由方案对比",[112,3505,3506,3523],{},[115,3507,3508],{},[118,3509,3510,3512,3514,3516,3519,3521],{},[121,3511,123],{},[121,3513,2018],{},[121,3515,1603],{},[121,3517,3518],{},"iOS (UIKit)",[121,3520,1606],{},[121,3522,3038],{},[137,3524,3525,3545,3563,3583,3603,3625,3647],{},[118,3526,3527,3530,3533,3536,3539,3542],{},[142,3528,3529],{},"声明式路由",[142,3531,3532],{},"GoRouter",[142,3534,3535],{},"NavigationStack",[142,3537,3538],{},"—",[142,3540,3541],{},"Navigation Compose",[142,3543,3544],{},"Vue Router",[118,3546,3547,3550,3553,3555,3558,3560],{},[142,3548,3549],{},"命令式路由",[142,3551,3552],{},"Navigator.push",[142,3554,3538],{},[142,3556,3557],{},"UINavigationController.push",[142,3559,3538],{},[142,3561,3562],{},"router.push",[118,3564,3565,3568,3571,3574,3577,3580],{},[142,3566,3567],{},"路由定义",[142,3569,3570],{},"RouteConfiguration",[142,3572,3573],{},"NavigationPath",[142,3575,3576],{},"Storyboard\u002F代码",[142,3578,3579],{},"NavHost + composable()",[142,3581,3582],{},"createRouter()",[118,3584,3585,3588,3591,3594,3597,3600],{},[142,3586,3587],{},"参数传递",[142,3589,3590],{},"pathParameters \u002F extra",[142,3592,3593],{},"NavigationDestination init",[142,3595,3596],{},"prepareForSegue \u002F 属性赋值",[142,3598,3599],{},"arguments Bundle \u002F 类型安全",[142,3601,3602],{},"params \u002F query \u002F props",[118,3604,3605,3608,3611,3616,3619,3622],{},[142,3606,3607],{},"深链接",[142,3609,3610],{},"GoRouter deepLink",[142,3612,3613],{},[167,3614,3615],{},"onOpenURL",[142,3617,3618],{},"Universal Links",[142,3620,3621],{},"Deep Links",[142,3623,3624],{},"Vue Router 本身",[118,3626,3627,3630,3633,3635,3637,3639],{},[142,3628,3629],{},"路由守卫",[142,3631,3632],{},"GoRouter redirect",[142,3634,3538],{},[142,3636,3538],{},[142,3638,3538],{},[142,3640,3641,684,3644],{},[167,3642,3643],{},"beforeEach",[167,3645,3646],{},"beforeEnter",[118,3648,3649,3652,3655,3658,3661,3664],{},[142,3650,3651],{},"嵌套路由",[142,3653,3654],{},"ShellRoute",[142,3656,3657],{},"NavigationSplitView",[142,3659,3660],{},"Tab + Nav 组合",[142,3662,3663],{},"nested NavHost",[142,3665,3666,3669],{},[167,3667,3668],{},"children"," 嵌套",[107,3671,3673],{"id":3672},"_52-基本路由代码","5.2 基本路由代码",[235,3675,3677],{"className":237,"code":3676,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter — GoRouter\nfinal router = GoRouter(\n  routes: [\n    GoRoute(path: '\u002F', builder: (_, __) => HomePage()),\n    GoRoute(\n      path: '\u002Fproduct\u002F:id',\n      builder: (_, state) => ProductPage(id: state.pathParameters['id']!),\n    ),\n  ],\n  redirect: (context, state) {\n    if (!isLoggedIn) return '\u002Flogin';\n    return null;\n  },\n);\n",[167,3678,3679,3684,3689,3694,3699,3704,3709,3714,3719,3724,3729,3734,3739,3744],{"__ignoreMap":240},[244,3680,3681],{"class":246,"line":247},[244,3682,3683],{},"\u002F\u002F Flutter — GoRouter\n",[244,3685,3686],{"class":246,"line":253},[244,3687,3688],{},"final router = GoRouter(\n",[244,3690,3691],{"class":246,"line":259},[244,3692,3693],{},"  routes: [\n",[244,3695,3696],{"class":246,"line":265},[244,3697,3698],{},"    GoRoute(path: '\u002F', builder: (_, __) => HomePage()),\n",[244,3700,3701],{"class":246,"line":271},[244,3702,3703],{},"    GoRoute(\n",[244,3705,3706],{"class":246,"line":277},[244,3707,3708],{},"      path: '\u002Fproduct\u002F:id',\n",[244,3710,3711],{"class":246,"line":320},[244,3712,3713],{},"      builder: (_, state) => ProductPage(id: state.pathParameters['id']!),\n",[244,3715,3716],{"class":246,"line":326},[244,3717,3718],{},"    ),\n",[244,3720,3721],{"class":246,"line":1256},[244,3722,3723],{},"  ],\n",[244,3725,3726],{"class":246,"line":1311},[244,3727,3728],{},"  redirect: (context, state) {\n",[244,3730,3731],{"class":246,"line":1317},[244,3732,3733],{},"    if (!isLoggedIn) return '\u002Flogin';\n",[244,3735,3736],{"class":246,"line":2582},[244,3737,3738],{},"    return null;\n",[244,3740,3741],{"class":246,"line":2588},[244,3742,3743],{},"  },\n",[244,3745,3746],{"class":246,"line":2593},[244,3747,3748],{},");\n",[235,3750,3752],{"className":283,"code":3751,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI — NavigationStack (iOS 16+)\nNavigationStack(path: $path) {\n    HomeView()\n        .navigationDestination(for: Product.self) { product in\n            ProductDetailView(product: product)\n        }\n}\n\u002F\u002F 导航: path.append(product)\n",[167,3753,3754,3759,3764,3769,3774,3779,3784,3788],{"__ignoreMap":240},[244,3755,3756],{"class":246,"line":247},[244,3757,3758],{},"\u002F\u002F SwiftUI — NavigationStack (iOS 16+)\n",[244,3760,3761],{"class":246,"line":253},[244,3762,3763],{},"NavigationStack(path: $path) {\n",[244,3765,3766],{"class":246,"line":259},[244,3767,3768],{},"    HomeView()\n",[244,3770,3771],{"class":246,"line":265},[244,3772,3773],{},"        .navigationDestination(for: Product.self) { product in\n",[244,3775,3776],{"class":246,"line":271},[244,3777,3778],{},"            ProductDetailView(product: product)\n",[244,3780,3781],{"class":246,"line":277},[244,3782,3783],{},"        }\n",[244,3785,3786],{"class":246,"line":320},[244,3787,738],{},[244,3789,3790],{"class":246,"line":326},[244,3791,3792],{},"\u002F\u002F 导航: path.append(product)\n",[235,3794,3796],{"className":332,"code":3795,"language":334,"meta":240,"style":240},"\u002F\u002F Compose — Navigation\nNavHost(navController, startDestination = \"home\") {\n    composable(\"home\") { HomeScreen() }\n    composable(\"product\u002F{id}\") { backStackEntry ->\n        ProductScreen(id = backStackEntry.arguments?.getString(\"id\"))\n    }\n}\n\u002F\u002F 导航: navController.navigate(\"product\u002F123\")\n",[167,3797,3798,3803,3808,3813,3818,3823,3827,3831],{"__ignoreMap":240},[244,3799,3800],{"class":246,"line":247},[244,3801,3802],{},"\u002F\u002F Compose — Navigation\n",[244,3804,3805],{"class":246,"line":253},[244,3806,3807],{},"NavHost(navController, startDestination = \"home\") {\n",[244,3809,3810],{"class":246,"line":259},[244,3811,3812],{},"    composable(\"home\") { HomeScreen() }\n",[244,3814,3815],{"class":246,"line":265},[244,3816,3817],{},"    composable(\"product\u002F{id}\") { backStackEntry ->\n",[244,3819,3820],{"class":246,"line":271},[244,3821,3822],{},"        ProductScreen(id = backStackEntry.arguments?.getString(\"id\"))\n",[244,3824,3825],{"class":246,"line":277},[244,3826,1314],{},[244,3828,3829],{"class":246,"line":320},[244,3830,738],{},[244,3832,3833],{"class":246,"line":326},[244,3834,3835],{},"\u002F\u002F 导航: navController.navigate(\"product\u002F123\")\n",[235,3837,3839],{"className":374,"code":3838,"language":376,"meta":240,"style":240},"\u002F\u002F Vue Router\nconst router = createRouter({\n  routes: [\n    { path: '\u002F', component: HomePage },\n    { path: '\u002Fproduct\u002F:id', component: ProductPage, props: true },\n  ],\n})\nrouter.beforeEach((to, from) => {\n  if (!isLoggedIn && to.meta.requiresAuth) return '\u002Flogin'\n})\n",[167,3840,3841,3846,3861,3865,3876,3892,3896,3901,3926,3950],{"__ignoreMap":240},[244,3842,3843],{"class":246,"line":247},[244,3844,3845],{"class":383},"\u002F\u002F Vue Router\n",[244,3847,3848,3850,3853,3855,3858],{"class":246,"line":253},[244,3849,687],{"class":389},[244,3851,3852],{"class":400}," router",[244,3854,410],{"class":389},[244,3856,3857],{"class":825}," createRouter",[244,3859,3860],{"class":393},"({\n",[244,3862,3863],{"class":246,"line":259},[244,3864,3693],{"class":393},[244,3866,3867,3870,3873],{"class":246,"line":265},[244,3868,3869],{"class":393},"    { path: ",[244,3871,3872],{"class":434},"'\u002F'",[244,3874,3875],{"class":393},", component: HomePage },\n",[244,3877,3878,3880,3883,3886,3889],{"class":246,"line":271},[244,3879,3869],{"class":393},[244,3881,3882],{"class":434},"'\u002Fproduct\u002F:id'",[244,3884,3885],{"class":393},", component: ProductPage, props: ",[244,3887,3888],{"class":400},"true",[244,3890,3891],{"class":393}," },\n",[244,3893,3894],{"class":246,"line":277},[244,3895,3723],{"class":393},[244,3897,3898],{"class":246,"line":320},[244,3899,3900],{"class":393},"})\n",[244,3902,3903,3906,3908,3911,3914,3916,3918,3921,3924],{"class":246,"line":326},[244,3904,3905],{"class":393},"router.",[244,3907,3643],{"class":825},[244,3909,3910],{"class":393},"((",[244,3912,3913],{"class":832},"to",[244,3915,607],{"class":393},[244,3917,2700],{"class":832},[244,3919,3920],{"class":393},") ",[244,3922,3923],{"class":389},"=>",[244,3925,1478],{"class":393},[244,3927,3928,3931,3934,3936,3939,3942,3945,3947],{"class":246,"line":1256},[244,3929,3930],{"class":389},"  if",[244,3932,3933],{"class":393}," (",[244,3935,460],{"class":389},[244,3937,3938],{"class":393},"isLoggedIn ",[244,3940,3941],{"class":389},"&&",[244,3943,3944],{"class":393}," to.meta.requiresAuth) ",[244,3946,1499],{"class":389},[244,3948,3949],{"class":434}," '\u002Flogin'\n",[244,3951,3952],{"class":246,"line":1311},[244,3953,3900],{"class":393},[10,3955,3956,3960],{},[13,3957,492,3958,510],{},[494,3959,496],{},[512,3961,3962,3965,3972,3975],{},[27,3963,3964],{},"Flutter Navigator 2.0 的声明式路由（RouterDelegate + RouteInformationParser）非常复杂，GoRouter 是社区对其的简化封装",[27,3966,3967,3968,3971],{},"SwiftUI 在 iOS 16 之前没有好的 programmatic navigation 方案，",[167,3969,3970],{},"NavigationLink"," 是纯声明式的",[27,3973,3974],{},"Compose Navigation 的参数传递用字符串不够类型安全，社区有 type-safe navigation 方案",[27,3976,3977],{},"Vue Router 的路由守卫支持异步（返回 Promise），可以做权限校验和数据预加载",[10,3979,3980,3984],{},[13,3981,506,3982,510],{},[494,3983,509],{},[512,3985,3986,3996,4007,4014],{},[27,3987,2156,3988,3991,3992,3995],{},[167,3989,3990],{},"Navigator.pop()"," 返回数据时需要 ",[167,3993,3994],{},"await Navigator.push()"," 接收",[27,3997,2167,3998,949,4000,4003,4004],{},[167,3999,3535],{},[167,4001,4002],{},"path"," 数组中的类型必须是 ",[167,4005,4006],{},"Hashable",[27,4008,4009,4010,4013],{},"Compose: 避免在 ",[167,4011,4012],{},"composable()"," 中传大对象，应该传 ID 再从 ViewModel 获取",[27,4015,4016,4017,4020,4021,4024,4025],{},"Vue: 动态路由 ",[167,4018,4019],{},"\u002Fproduct\u002F:id"," 切换时组件不销毁重建，需要 ",[167,4022,4023],{},"watch(() => route.params.id)"," 或加 ",[167,4026,2365],{},[10,4028,4029,4033],{},[13,4030,546,4031,510],{},[494,4032,549],{},[512,4034,4035,4038,4053],{},[27,4036,4037],{},"Flutter Navigator 1.0 和 2.0 的区别？（1.0 命令式栈操作，2.0 声明式路由配置）",[27,4039,4040,4041,4044,4045,4048,4049,4052],{},"Vue Router 的 ",[167,4042,4043],{},"hash"," 模式和 ",[167,4046,4047],{},"history"," 模式的区别？（hash 用 ",[167,4050,4051],{},"#"," 不需服务端配置，history 需要服务端 fallback）",[27,4054,4055],{},"iOS deep link 的 Universal Links 和 URL Schemes 的区别？（Universal Links 走 HTTPS 验证更安全，不会弹确认框）",[17,4057],{},[20,4059,4061],{"id":4060},"_6-网络与数据层","6. 网络与数据层",[107,4063,4065],{"id":4064},"_61-http-客户端对比","6.1 HTTP 客户端对比",[112,4067,4068,4085],{},[115,4069,4070],{},[118,4071,4072,4074,4076,4079,4082],{},[121,4073,123],{},[121,4075,2018],{},[121,4077,4078],{},"iOS",[121,4080,4081],{},"Android",[121,4083,4084],{},"Vue \u002F 前端",[137,4086,4087,4115,4132,4149,4166,4183],{},[118,4088,4089,4092,4097,4103,4109],{},[142,4090,4091],{},"主流库",[142,4093,4094],{},[494,4095,4096],{},"Dio",[142,4098,4099,4102],{},[494,4100,4101],{},"URLSession"," (原生) \u002F Alamofire",[142,4104,4105,4108],{},[494,4106,4107],{},"Retrofit"," + OkHttp",[142,4110,4111,4114],{},[494,4112,4113],{},"Axios"," \u002F fetch API",[118,4116,4117,4120,4123,4126,4129],{},[142,4118,4119],{},"拦截器",[142,4121,4122],{},"Dio Interceptor",[142,4124,4125],{},"URLProtocol \u002F Alamofire Interceptor",[142,4127,4128],{},"OkHttp Interceptor",[142,4130,4131],{},"Axios Interceptor",[118,4133,4134,4137,4140,4143,4146],{},[142,4135,4136],{},"序列化",[142,4138,4139],{},"json_serializable \u002F freezed",[142,4141,4142],{},"Codable",[142,4144,4145],{},"Gson \u002F Moshi \u002F kotlinx.serialization",[142,4147,4148],{},"原生 JSON \u002F zod \u002F io-ts",[118,4150,4151,4154,4157,4160,4163],{},[142,4152,4153],{},"取消请求",[142,4155,4156],{},"CancelToken",[142,4158,4159],{},"URLSessionTask.cancel()",[142,4161,4162],{},"Coroutine cancel",[142,4164,4165],{},"AbortController",[118,4167,4168,4171,4174,4177,4180],{},[142,4169,4170],{},"文件上传",[142,4172,4173],{},"FormData (Dio)",[142,4175,4176],{},"URLSession uploadTask",[142,4178,4179],{},"MultipartBody (OkHttp)",[142,4181,4182],{},"FormData (Axios)",[118,4184,4185,4188,4191,4194,4197],{},[142,4186,4187],{},"WebSocket",[142,4189,4190],{},"web_socket_channel",[142,4192,4193],{},"URLSessionWebSocketTask",[142,4195,4196],{},"OkHttp WebSocket",[142,4198,4199],{},"原生 WebSocket \u002F Socket.io",[107,4201,4203],{"id":4202},"_62-json-序列化对比","6.2 JSON 序列化对比",[235,4205,4207],{"className":237,"code":4206,"language":239,"meta":240,"style":240},"\u002F\u002F Dart — json_serializable\n@JsonSerializable()\nclass User {\n  final int id;\n  final String name;\n  User({required this.id, required this.name});\n  factory User.fromJson(Map\u003CString, dynamic> json) => _$UserFromJson(json);\n  Map\u003CString, dynamic> toJson() => _$UserToJson(this);\n}\n\u002F\u002F 需要 build_runner 生成 .g.dart\n",[167,4208,4209,4214,4219,4224,4229,4233,4238,4243,4248,4252],{"__ignoreMap":240},[244,4210,4211],{"class":246,"line":247},[244,4212,4213],{},"\u002F\u002F Dart — json_serializable\n",[244,4215,4216],{"class":246,"line":253},[244,4217,4218],{},"@JsonSerializable()\n",[244,4220,4221],{"class":246,"line":259},[244,4222,4223],{},"class User {\n",[244,4225,4226],{"class":246,"line":265},[244,4227,4228],{},"  final int id;\n",[244,4230,4231],{"class":246,"line":271},[244,4232,1725],{},[244,4234,4235],{"class":246,"line":277},[244,4236,4237],{},"  User({required this.id, required this.name});\n",[244,4239,4240],{"class":246,"line":320},[244,4241,4242],{},"  factory User.fromJson(Map\u003CString, dynamic> json) => _$UserFromJson(json);\n",[244,4244,4245],{"class":246,"line":326},[244,4246,4247],{},"  Map\u003CString, dynamic> toJson() => _$UserToJson(this);\n",[244,4249,4250],{"class":246,"line":1256},[244,4251,738],{},[244,4253,4254],{"class":246,"line":1311},[244,4255,4256],{},"\u002F\u002F 需要 build_runner 生成 .g.dart\n",[235,4258,4260],{"className":283,"code":4259,"language":285,"meta":240,"style":240},"\u002F\u002F Swift — Codable（编译器自动合成）\nstruct User: Codable {\n    let id: Int\n    let name: String\n}\nlet user = try JSONDecoder().decode(User.self, from: data)\n",[167,4261,4262,4267,4272,4277,4281,4285],{"__ignoreMap":240},[244,4263,4264],{"class":246,"line":247},[244,4265,4266],{},"\u002F\u002F Swift — Codable（编译器自动合成）\n",[244,4268,4269],{"class":246,"line":253},[244,4270,4271],{},"struct User: Codable {\n",[244,4273,4274],{"class":246,"line":259},[244,4275,4276],{},"    let id: Int\n",[244,4278,4279],{"class":246,"line":265},[244,4280,1778],{},[244,4282,4283],{"class":246,"line":271},[244,4284,738],{},[244,4286,4287],{"class":246,"line":277},[244,4288,4289],{},"let user = try JSONDecoder().decode(User.self, from: data)\n",[235,4291,4293],{"className":332,"code":4292,"language":334,"meta":240,"style":240},"\u002F\u002F Kotlin — kotlinx.serialization\n@Serializable\ndata class User(val id: Int, val name: String)\nval user = Json.decodeFromString\u003CUser>(jsonString)\n",[167,4294,4295,4300,4305,4310],{"__ignoreMap":240},[244,4296,4297],{"class":246,"line":247},[244,4298,4299],{},"\u002F\u002F Kotlin — kotlinx.serialization\n",[244,4301,4302],{"class":246,"line":253},[244,4303,4304],{},"@Serializable\n",[244,4306,4307],{"class":246,"line":259},[244,4308,4309],{},"data class User(val id: Int, val name: String)\n",[244,4311,4312],{"class":246,"line":265},[244,4313,4314],{},"val user = Json.decodeFromString\u003CUser>(jsonString)\n",[235,4316,4318],{"className":374,"code":4317,"language":376,"meta":240,"style":240},"\u002F\u002F TypeScript — 运行时无类型，需要手动校验或用 zod\ninterface User { id: number; name: string }\n\u002F\u002F 简单方式（不安全）\nconst user = response.data as User;\n\u002F\u002F 安全方式（zod）\nconst UserSchema = z.object({ id: z.number(), name: z.string() });\nconst user = UserSchema.parse(response.data);\n",[167,4319,4320,4325,4350,4355,4373,4378,4407],{"__ignoreMap":240},[244,4321,4322],{"class":246,"line":247},[244,4323,4324],{"class":383},"\u002F\u002F TypeScript — 运行时无类型，需要手动校验或用 zod\n",[244,4326,4327,4329,4332,4334,4336,4338,4340,4342,4344,4346,4348],{"class":246,"line":253},[244,4328,822],{"class":389},[244,4330,4331],{"class":825}," User",[244,4333,829],{"class":393},[244,4335,2386],{"class":832},[244,4337,397],{"class":389},[244,4339,838],{"class":400},[244,4341,841],{"class":393},[244,4343,457],{"class":832},[244,4345,397],{"class":389},[244,4347,401],{"class":400},[244,4349,851],{"class":393},[244,4351,4352],{"class":246,"line":259},[244,4353,4354],{"class":383},"\u002F\u002F 简单方式（不安全）\n",[244,4356,4357,4359,4362,4364,4367,4369,4371],{"class":246,"line":265},[244,4358,687],{"class":389},[244,4360,4361],{"class":400}," user",[244,4363,410],{"class":389},[244,4365,4366],{"class":393}," response.data ",[244,4368,540],{"class":389},[244,4370,4331],{"class":825},[244,4372,438],{"class":393},[244,4374,4375],{"class":246,"line":271},[244,4376,4377],{"class":383},"\u002F\u002F 安全方式（zod）\n",[244,4379,4380,4382,4385,4387,4390,4392,4395,4398,4401,4404],{"class":246,"line":277},[244,4381,687],{"class":389},[244,4383,4384],{"class":400}," UserSchema",[244,4386,410],{"class":389},[244,4388,4389],{"class":393}," z.",[244,4391,643],{"class":825},[244,4393,4394],{"class":393},"({ id: z.",[244,4396,4397],{"class":825},"number",[244,4399,4400],{"class":393},"(), name: z.",[244,4402,4403],{"class":825},"string",[244,4405,4406],{"class":393},"() });\n",[244,4408,4409,4411,4413,4415,4418,4421],{"class":246,"line":320},[244,4410,687],{"class":389},[244,4412,4361],{"class":400},[244,4414,410],{"class":389},[244,4416,4417],{"class":393}," UserSchema.",[244,4419,4420],{"class":825},"parse",[244,4422,4423],{"class":393},"(response.data);\n",[10,4425,4426,4430],{},[13,4427,492,4428,510],{},[494,4429,496],{},[512,4431,4432,4437,4451,4456],{},[27,4433,1180,4434,4436],{},[167,4435,4142],{}," 最优雅，编译器自动生成 encode\u002Fdecode（无需 code gen 工具）",[27,4438,4439,4440,1575,4443,4446,4447,4450],{},"Dart 需要 ",[167,4441,4442],{},"build_runner",[167,4444,4445],{},"json_serializable"," 生成代码，或者用 ",[167,4448,4449],{},"freezed"," 同时生成 copyWith\u002Fequals\u002FtoString",[27,4452,940,4453,4455],{},[167,4454,822],{}," 在运行时被擦除，JSON 解析无法自动校验类型，zod 等库填补这个空白",[27,4457,4458],{},"Kotlin 有多种方案：Gson（反射）、Moshi（反射或 codegen）、kotlinx.serialization（编译器插件）",[10,4460,4461,4465],{},[13,4462,506,4463,510],{},[494,4464,509],{},[512,4466,4467,4478,4484,4490],{},[27,4468,4469,4470,4473,4474,4477],{},"Dart: 忘记运行 ",[167,4471,4472],{},"dart run build_runner build"," 导致 ",[167,4475,4476],{},".g.dart"," 文件不存在",[27,4479,4480,4481],{},"Swift: JSON key 和属性名不一致时需要自定义 ",[167,4482,4483],{},"CodingKeys",[27,4485,4486,4487,4489],{},"Kotlin: Gson 用反射创建对象，可以绕过 ",[167,4488,696],{}," 约束创建不合法对象",[27,4491,4492,4493,4495],{},"TypeScript: ",[167,4494,540],{}," 类型断言不做任何运行时检查，JSON 字段类型错误不会报错",[10,4497,4498,4502],{},[13,4499,546,4500,510],{},[494,4501,549],{},[512,4503,4504,4512,4525],{},[27,4505,4506,4507,2817,4509,4511],{},"Dart ",[167,4508,4445],{},[167,4510,4449],{}," 的区别？（freezed 额外生成 copyWith、equals、sealed union 支持）",[27,4513,4514,4515,4517,4518,2956,4521,4524],{},"Swift ",[167,4516,4142],{}," 的底层原理？（编译器自动合成 ",[167,4519,4520],{},"init(from: Decoder)",[167,4522,4523],{},"encode(to: Encoder)","，可自定义 container）",[27,4526,4527],{},"Retrofit 的原理？（动态代理 + 注解处理，接口方法 → HTTP 请求映射）",[17,4529],{},[107,4531,4533],{"id":4532},"_63-错误处理模式","6.3 错误处理模式",[112,4535,4536,4552],{},[115,4537,4538],{},[118,4539,4540,4543,4546,4548,4550],{},[121,4541,4542],{},"模式",[121,4544,4545],{},"Flutter (Dart)",[121,4547,585],{},[121,4549,588],{},[121,4551,591],{},[137,4553,4554,4586,4607],{},[118,4555,4556,4559,4567,4574,4580],{},[142,4557,4558],{},"异常",[142,4560,4561,1575,4564],{},[167,4562,4563],{},"try-catch",[167,4565,4566],{},"throw",[142,4568,4569,1575,4572],{},[167,4570,4571],{},"do-try-catch",[167,4573,4566],{},[142,4575,4576,1575,4578],{},[167,4577,4563],{},[167,4579,4566],{},[142,4581,4582,1575,4584],{},[167,4583,4563],{},[167,4585,4566],{},[118,4587,4588,4591,4594,4599,4604],{},[142,4589,4590],{},"Result 类型",[142,4592,4593],{},"无内置（手写或三方）",[142,4595,4596],{},[167,4597,4598],{},"Result\u003CSuccess, Failure>",[142,4600,4601],{},[167,4602,4603],{},"Result\u003CT>",[142,4605,4606],{},"无内置（手写或 neverthrow）",[118,4608,4609,4612,4615,4618,4624],{},[142,4610,4611],{},"推荐模式",[142,4613,4614],{},"sealed class 包装",[142,4616,4617],{},"Result + typed throws (Swift 6)",[142,4619,4620,4623],{},[167,4621,4622],{},"runCatching {}"," \u002F sealed class",[142,4625,4626],{},"联合类型或 Result 模式",[235,4628,4630],{"className":237,"code":4629,"language":239,"meta":240,"style":240},"\u002F\u002F Dart — sealed class 包装（推荐）\nsealed class ApiResult\u003CT> {}\nclass Success\u003CT> extends ApiResult\u003CT> { final T data; Success(this.data); }\nclass Failure\u003CT> extends ApiResult\u003CT> { final String message; Failure(this.message); }\n",[167,4631,4632,4637,4642,4647],{"__ignoreMap":240},[244,4633,4634],{"class":246,"line":247},[244,4635,4636],{},"\u002F\u002F Dart — sealed class 包装（推荐）\n",[244,4638,4639],{"class":246,"line":253},[244,4640,4641],{},"sealed class ApiResult\u003CT> {}\n",[244,4643,4644],{"class":246,"line":259},[244,4645,4646],{},"class Success\u003CT> extends ApiResult\u003CT> { final T data; Success(this.data); }\n",[244,4648,4649],{"class":246,"line":265},[244,4650,4651],{},"class Failure\u003CT> extends ApiResult\u003CT> { final String message; Failure(this.message); }\n",[235,4653,4655],{"className":283,"code":4654,"language":285,"meta":240,"style":240},"\u002F\u002F Swift — Result 类型\nfunc fetchUser() async -> Result\u003CUser, APIError> {\n    do {\n        let user = try await api.getUser()\n        return .success(user)\n    } catch {\n        return .failure(.networkError(error))\n    }\n}\n",[167,4656,4657,4662,4667,4672,4677,4682,4687,4692,4696],{"__ignoreMap":240},[244,4658,4659],{"class":246,"line":247},[244,4660,4661],{},"\u002F\u002F Swift — Result 类型\n",[244,4663,4664],{"class":246,"line":253},[244,4665,4666],{},"func fetchUser() async -> Result\u003CUser, APIError> {\n",[244,4668,4669],{"class":246,"line":259},[244,4670,4671],{},"    do {\n",[244,4673,4674],{"class":246,"line":265},[244,4675,4676],{},"        let user = try await api.getUser()\n",[244,4678,4679],{"class":246,"line":271},[244,4680,4681],{},"        return .success(user)\n",[244,4683,4684],{"class":246,"line":277},[244,4685,4686],{},"    } catch {\n",[244,4688,4689],{"class":246,"line":320},[244,4690,4691],{},"        return .failure(.networkError(error))\n",[244,4693,4694],{"class":246,"line":326},[244,4695,1314],{},[244,4697,4698],{"class":246,"line":1256},[244,4699,738],{},[17,4701],{},[20,4703,4705],{"id":4704},"_7-持久化存储","7. 持久化存储",[107,4707,4709],{"id":4708},"_71-存储方案对比","7.1 存储方案对比",[112,4711,4712,4726],{},[115,4713,4714],{},[118,4715,4716,4718,4720,4722,4724],{},[121,4717,2420],{},[121,4719,2018],{},[121,4721,4078],{},[121,4723,4081],{},[121,4725,4084],{},[137,4727,4728,4745,4770,4787],{},[118,4729,4730,4733,4736,4739,4742],{},[142,4731,4732],{},"键值对",[142,4734,4735],{},"SharedPreferences",[142,4737,4738],{},"UserDefaults",[142,4740,4741],{},"DataStore (Preferences)",[142,4743,4744],{},"localStorage",[118,4746,4747,4750,4756,4762,4767],{},[142,4748,4749],{},"轻量数据库",[142,4751,4752,4755],{},[494,4753,4754],{},"Drift"," \u002F sqflite \u002F Hive",[142,4757,4758,4761],{},[494,4759,4760],{},"SwiftData"," \u002F Core Data \u002F GRDB",[142,4763,4764],{},[494,4765,4766],{},"Room",[142,4768,4769],{},"IndexedDB \u002F Dexie",[118,4771,4772,4775,4778,4781,4784],{},[142,4773,4774],{},"文件存储",[142,4776,4777],{},"path_provider + File",[142,4779,4780],{},"FileManager",[142,4782,4783],{},"Context.filesDir",[142,4785,4786],{},"File API \u002F Blob",[118,4788,4789,4792,4795,4798,4801],{},[142,4790,4791],{},"安全存储",[142,4793,4794],{},"flutter_secure_storage",[142,4796,4797],{},"Keychain",[142,4799,4800],{},"EncryptedSharedPreferences",[142,4802,4803],{},"— (后端处理)",[107,4805,4807],{"id":4806},"_72-orm-对比","7.2 ORM 对比",[112,4809,4810,4826],{},[115,4811,4812],{},[118,4813,4814,4817,4820,4823],{},[121,4815,4816],{},"特性",[121,4818,4819],{},"Drift (Flutter)",[121,4821,4822],{},"SwiftData (iOS)",[121,4824,4825],{},"Room (Android)",[137,4827,4828,4848,4871,4889,4903,4921],{},[118,4829,4830,4833,4836,4842],{},[142,4831,4832],{},"定义方式",[142,4834,4835],{},"Dart 类 + 注解",[142,4837,4838,4841],{},[167,4839,4840],{},"@Model"," 宏",[142,4843,4844,4847],{},[167,4845,4846],{},"@Entity"," 注解",[118,4849,4850,4853,4856,4865],{},[142,4851,4852],{},"查询方式",[142,4854,4855],{},"类型安全 Dart DSL",[142,4857,4858,4861,4862],{},[167,4859,4860],{},"@Query"," 宏 + ",[167,4863,4864],{},"#Predicate",[142,4866,4867,4870],{},[167,4868,4869],{},"@Dao"," + SQL 字符串",[118,4872,4873,4876,4879,4884],{},[142,4874,4875],{},"关系",[142,4877,4878],{},"外键 + Join",[142,4880,4881],{},[167,4882,4883],{},"@Relationship",[142,4885,4886],{},[167,4887,4888],{},"@Relation",[118,4890,4891,4894,4897,4900],{},[142,4892,4893],{},"迁移",[142,4895,4896],{},"手动 schema 版本",[142,4898,4899],{},"自动轻量迁移",[142,4901,4902],{},"手动 Migration",[118,4904,4905,4908,4913,4916],{},[142,4906,4907],{},"响应式",[142,4909,4910,4912],{},[167,4911,2145],{}," 返回 Stream",[142,4914,4915],{},"SwiftUI 自动观察",[142,4917,4918],{},[167,4919,4920],{},"Flow\u003CList\u003CT>>",[118,4922,4923,4926,4929,4932],{},[142,4924,4925],{},"Code Gen",[142,4927,4928],{},"需要 build_runner",[142,4930,4931],{},"编译器宏（无 code gen 步骤）",[142,4933,4934],{},"需要 kapt\u002Fksp",[10,4936,4937,4941],{},[13,4938,492,4939,510],{},[494,4940,496],{},[512,4942,4943,4956,4959],{},[27,4944,4945,4946,4948,4949,4951,4952,4955],{},"Room 的 ",[167,4947,4869],{}," 方法返回 ",[167,4950,4920],{}," 可以直接在 Compose 中 ",[167,4953,4954],{},"collectAsState()","，实现数据库→UI 的响应式链路",[27,4957,4958],{},"SwiftData 是 Core Data 的现代封装，用 Swift 宏替代了繁琐的 NSManagedObject 子类",[27,4960,4961,4962,4964,4965,4968],{},"Drift 的 ",[167,4963,2145],{}," 返回 Stream，数据库变更时自动推送新数据，配合 ",[167,4966,4967],{},"StreamBuilder"," 使用",[10,4970,4971,4975],{},[13,4972,506,4973,510],{},[494,4974,509],{},[512,4976,4977,4984,4990],{},[27,4978,4979,4980,4983],{},"SharedPreferences \u002F UserDefaults ",[494,4981,4982],{},"不是","加密存储！敏感数据（token、密码）必须用 Keychain \u002F flutter_secure_storage",[27,4985,4986,4987,561],{},"Room 的迁移如果漏写某个 schema 变更会导致崩溃（",[167,4988,4989],{},"IllegalStateException",[27,4991,4992,4993,4996,4997,5000],{},"Core Data 的 ",[167,4994,4995],{},"NSManagedObjectContext"," 不是线程安全的，必须用 ",[167,4998,4999],{},"perform {}"," 包裹",[10,5002,5003,5007],{},[13,5004,546,5005,510],{},[494,5006,549],{},[512,5008,5009,5012,5015],{},[27,5010,5011],{},"SharedPreferences 的底层实现？（Android: XML 文件，iOS: plist\u002FUserDefaults，Flutter: 平台桥接）",[27,5013,5014],{},"Room vs Realm vs SQLDelight 怎么选？（Room 是 Google 官方，SQLDelight 跨平台，Realm 已停止维护）",[27,5016,5017],{},"前端 localStorage vs sessionStorage vs Cookie 的区别？（localStorage 持久化，sessionStorage 会话级，Cookie 每次请求自动发送）",[17,5019],{},[20,5021,5023],{"id":5022},"_8-并发与异步编程","8. 并发与异步编程",[107,5025,5027],{"id":5026},"_81-线程并发模型对比","8.1 线程\u002F并发模型对比",[112,5029,5030,5045],{},[115,5031,5032],{},[118,5033,5034,5036,5038,5040,5042],{},[121,5035,123],{},[121,5037,582],{},[121,5039,585],{},[121,5041,588],{},[121,5043,5044],{},"JavaScript\u002FTypeScript",[137,5046,5047,5073,5090,5127,5145],{},[118,5048,5049,5052,5058,5064,5069],{},[142,5050,5051],{},"线程模型",[142,5053,5054,5057],{},[494,5055,5056],{},"单线程"," + Event Loop",[142,5059,5060,5063],{},[494,5061,5062],{},"多线程"," + GCD\u002Fasync-await",[142,5065,5066,5068],{},[494,5067,5062],{}," + Coroutines",[142,5070,5071,5057],{},[494,5072,5056],{},[118,5074,5075,5078,5081,5084,5087],{},[142,5076,5077],{},"并行隔离",[142,5079,5080],{},"Isolate（独立内存）",[142,5082,5083],{},"Actor（共享内存 + 隔离访问）",[142,5085,5086],{},"Thread \u002F Coroutine Dispatcher",[142,5088,5089],{},"Web Worker（独立内存）",[118,5091,5092,5095,5103,5111,5119],{},[142,5093,5094],{},"异步原语",[142,5096,5097,684,5100],{},[167,5098,5099],{},"Future",[167,5101,5102],{},"Stream",[142,5104,5105,684,5108],{},[167,5106,5107],{},"async\u002Fawait",[167,5109,5110],{},"AsyncSequence",[142,5112,5113,684,5116],{},[167,5114,5115],{},"suspend fun",[167,5117,5118],{},"Flow",[142,5120,5121,684,5124],{},[167,5122,5123],{},"Promise",[167,5125,5126],{},"AsyncIterator",[118,5128,5129,5132,5135,5138,5143],{},[142,5130,5131],{},"调度器",[142,5133,5134],{},"Event Loop（不可选）",[142,5136,5137],{},"MainActor \u002F 自定义 Actor",[142,5139,5140],{},[167,5141,5142],{},"Dispatchers.Main\u002FIO\u002FDefault",[142,5144,5134],{},[118,5146,5147,5150,5153,5156,5159],{},[142,5148,5149],{},"取消机制",[142,5151,5152],{},"无内置（需手动）",[142,5154,5155],{},"Task.cancel() (cooperative)",[142,5157,5158],{},"Job.cancel() (cooperative)",[142,5160,4165],{},[107,5162,5164],{"id":5163},"_82-异步代码对比","8.2 异步代码对比",[235,5166,5168],{"className":237,"code":5167,"language":239,"meta":240,"style":240},"\u002F\u002F Dart — Future + async\u002Fawait\nFuture\u003CUser> fetchUser() async {\n  final response = await dio.get('\u002Fuser');\n  return User.fromJson(response.data);\n}\n\u002F\u002F 并行\nfinal results = await Future.wait([fetchUser(), fetchOrders()]);\n",[167,5169,5170,5175,5180,5185,5190,5194,5199],{"__ignoreMap":240},[244,5171,5172],{"class":246,"line":247},[244,5173,5174],{},"\u002F\u002F Dart — Future + async\u002Fawait\n",[244,5176,5177],{"class":246,"line":253},[244,5178,5179],{},"Future\u003CUser> fetchUser() async {\n",[244,5181,5182],{"class":246,"line":259},[244,5183,5184],{},"  final response = await dio.get('\u002Fuser');\n",[244,5186,5187],{"class":246,"line":265},[244,5188,5189],{},"  return User.fromJson(response.data);\n",[244,5191,5192],{"class":246,"line":271},[244,5193,738],{},[244,5195,5196],{"class":246,"line":277},[244,5197,5198],{},"\u002F\u002F 并行\n",[244,5200,5201],{"class":246,"line":320},[244,5202,5203],{},"final results = await Future.wait([fetchUser(), fetchOrders()]);\n",[235,5205,5207],{"className":283,"code":5206,"language":285,"meta":240,"style":240},"\u002F\u002F Swift — structured concurrency\nfunc fetchUser() async throws -> User {\n    let (data, _) = try await URLSession.shared.data(from: url)\n    return try JSONDecoder().decode(User.self, from: data)\n}\n\u002F\u002F 并行\nasync let user = fetchUser()\nasync let orders = fetchOrders()\nlet (u, o) = try await (user, orders)\n",[167,5208,5209,5214,5219,5224,5229,5233,5237,5242,5247],{"__ignoreMap":240},[244,5210,5211],{"class":246,"line":247},[244,5212,5213],{},"\u002F\u002F Swift — structured concurrency\n",[244,5215,5216],{"class":246,"line":253},[244,5217,5218],{},"func fetchUser() async throws -> User {\n",[244,5220,5221],{"class":246,"line":259},[244,5222,5223],{},"    let (data, _) = try await URLSession.shared.data(from: url)\n",[244,5225,5226],{"class":246,"line":265},[244,5227,5228],{},"    return try JSONDecoder().decode(User.self, from: data)\n",[244,5230,5231],{"class":246,"line":271},[244,5232,738],{},[244,5234,5235],{"class":246,"line":277},[244,5236,5198],{},[244,5238,5239],{"class":246,"line":320},[244,5240,5241],{},"async let user = fetchUser()\n",[244,5243,5244],{"class":246,"line":326},[244,5245,5246],{},"async let orders = fetchOrders()\n",[244,5248,5249],{"class":246,"line":1256},[244,5250,5251],{},"let (u, o) = try await (user, orders)\n",[235,5253,5255],{"className":332,"code":5254,"language":334,"meta":240,"style":240},"\u002F\u002F Kotlin — Coroutines\nsuspend fun fetchUser(): User {\n    val response = apiService.getUser()\n    return response.body()!!\n}\n\u002F\u002F 并行\ncoroutineScope {\n    val user = async { fetchUser() }\n    val orders = async { fetchOrders() }\n    val (u, o) = user.await() to orders.await()\n}\n",[167,5256,5257,5262,5267,5272,5277,5281,5285,5290,5295,5300,5305],{"__ignoreMap":240},[244,5258,5259],{"class":246,"line":247},[244,5260,5261],{},"\u002F\u002F Kotlin — Coroutines\n",[244,5263,5264],{"class":246,"line":253},[244,5265,5266],{},"suspend fun fetchUser(): User {\n",[244,5268,5269],{"class":246,"line":259},[244,5270,5271],{},"    val response = apiService.getUser()\n",[244,5273,5274],{"class":246,"line":265},[244,5275,5276],{},"    return response.body()!!\n",[244,5278,5279],{"class":246,"line":271},[244,5280,738],{},[244,5282,5283],{"class":246,"line":277},[244,5284,5198],{},[244,5286,5287],{"class":246,"line":320},[244,5288,5289],{},"coroutineScope {\n",[244,5291,5292],{"class":246,"line":326},[244,5293,5294],{},"    val user = async { fetchUser() }\n",[244,5296,5297],{"class":246,"line":1256},[244,5298,5299],{},"    val orders = async { fetchOrders() }\n",[244,5301,5302],{"class":246,"line":1311},[244,5303,5304],{},"    val (u, o) = user.await() to orders.await()\n",[244,5306,5307],{"class":246,"line":1317},[244,5308,738],{},[235,5310,5312],{"className":374,"code":5311,"language":376,"meta":240,"style":240},"\u002F\u002F TypeScript — Promise + async\u002Fawait\nasync function fetchUser(): Promise\u003CUser> {\n  const res = await axios.get('\u002Fuser');\n  return res.data;\n}\n\u002F\u002F 并行\nconst [user, orders] = await Promise.all([fetchUser(), fetchOrders()]);\n",[167,5313,5314,5319,5346,5372,5380,5384,5388],{"__ignoreMap":240},[244,5315,5316],{"class":246,"line":247},[244,5317,5318],{"class":383},"\u002F\u002F TypeScript — Promise + async\u002Fawait\n",[244,5320,5321,5324,5327,5330,5333,5335,5338,5340,5343],{"class":246,"line":253},[244,5322,5323],{"class":389},"async",[244,5325,5326],{"class":389}," function",[244,5328,5329],{"class":825}," fetchUser",[244,5331,5332],{"class":393},"()",[244,5334,397],{"class":389},[244,5336,5337],{"class":825}," Promise",[244,5339,1867],{"class":393},[244,5341,5342],{"class":825},"User",[244,5344,5345],{"class":393},"> {\n",[244,5347,5348,5351,5354,5356,5359,5362,5365,5367,5370],{"class":246,"line":259},[244,5349,5350],{"class":389},"  const",[244,5352,5353],{"class":400}," res",[244,5355,410],{"class":389},[244,5357,5358],{"class":389}," await",[244,5360,5361],{"class":393}," axios.",[244,5363,5364],{"class":825},"get",[244,5366,1462],{"class":393},[244,5368,5369],{"class":434},"'\u002Fuser'",[244,5371,3748],{"class":393},[244,5373,5374,5377],{"class":246,"line":265},[244,5375,5376],{"class":389},"  return",[244,5378,5379],{"class":393}," res.data;\n",[244,5381,5382],{"class":246,"line":271},[244,5383,738],{"class":393},[244,5385,5386],{"class":246,"line":277},[244,5387,5198],{"class":383},[244,5389,5390,5392,5395,5398,5400,5403,5406,5408,5410,5412,5414,5417,5420,5423,5426,5429],{"class":246,"line":320},[244,5391,687],{"class":389},[244,5393,5394],{"class":393}," [",[244,5396,5397],{"class":400},"user",[244,5399,607],{"class":393},[244,5401,5402],{"class":400},"orders",[244,5404,5405],{"class":393},"] ",[244,5407,1880],{"class":389},[244,5409,5358],{"class":389},[244,5411,5337],{"class":400},[244,5413,463],{"class":393},[244,5415,5416],{"class":825},"all",[244,5418,5419],{"class":393},"([",[244,5421,5422],{"class":825},"fetchUser",[244,5424,5425],{"class":393},"(), ",[244,5427,5428],{"class":825},"fetchOrders",[244,5430,5431],{"class":393},"()]);\n",[10,5433,5434,5438],{},[13,5435,492,5436,510],{},[494,5437,496],{},[512,5439,5440,5449,5457,5464],{},[27,5441,5442,5443,5445,5446,5448],{},"Dart 和 JS 都是",[494,5444,5056],{},"，",[167,5447,5107],{}," 不会创建新线程，只是将回调注册到 Event Loop（微任务队列）",[27,5450,1180,5451,5453,5454,5456],{},[167,5452,5107],{}," 是真正的",[494,5455,5062],{},"并发，编译器在 await 点做线程挂起\u002F恢复",[27,5458,5459,5460,5463],{},"Kotlin Coroutines 是基于 CPS (Continuation Passing Style) 的编译器变换，",[167,5461,5462],{},"suspend"," 函数被编译为状态机",[27,5465,5466],{},"Dart Isolate 之间不能共享内存，通信通过 SendPort\u002FReceivePort（类似 Actor 模型）",[10,5468,5469,5473],{},[13,5470,506,5471,510],{},[494,5472,509],{},[512,5474,5475,5484,5491,5498,5510],{},[27,5476,5477,5478,5480,5481,5483],{},"Dart: ",[167,5479,5099],{}," 一创建就开始执行，不是 lazy 的（Kotlin 的 ",[167,5482,5462],{}," 是 lazy 的）",[27,5485,5486,5487,5490],{},"Swift: ",[167,5488,5489],{},"Task {}"," 创建的非结构化任务不会随父 Task 取消而自动取消",[27,5492,5493,5494,5497],{},"Kotlin: 在 ",[167,5495,5496],{},"GlobalScope.launch"," 中启动协程不受生命周期管理，容易内存泄漏",[27,5499,4492,5500,5502,5503,5505,5506,5509],{},[167,5501,5123],{}," 不可取消，需要 ",[167,5504,4165],{}," 配合 ",[167,5507,5508],{},"fetch"," 实现取消",[27,5511,5512,510,5515,5517,5518,5521],{},[494,5513,5514],{},"通用陷阱",[167,5516,5323],{}," 函数中忘记 ",[167,5519,5520],{},"await"," 导致异步操作\"射后不管\"（fire-and-forget）",[10,5523,5524,5528],{},[13,5525,546,5526,510],{},[494,5527,549],{},[512,5529,5530,5541,5544,5553],{},[27,5531,5532,5533,5536,5537,5540],{},"Dart 的 Event Loop 机制？（微任务队列优先于事件队列，",[167,5534,5535],{},"Future.microtask"," > ",[167,5538,5539],{},"Timer"," > I\u002FO 回调）",[27,5542,5543],{},"Swift Actor 解决了什么问题？（数据竞争：Actor 保证同一时间只有一个任务访问其可变状态）",[27,5545,1189,5546,2956,5549,5552],{},[167,5547,5548],{},"CoroutineScope",[167,5550,5551],{},"supervisorScope"," 的区别？（前者一个子协程失败全部取消，后者互不影响）",[27,5554,5555],{},"JS 的微任务 (microtask) 和宏任务 (macrotask) 的执行顺序？（同步代码 → 微任务队列清空 → 一个宏任务 → 微任务队列清空 → ...）",[17,5557],{},[107,5559,5561],{"id":5560},"_83-响应式流对比","8.3 响应式流对比",[112,5563,5564,5582],{},[115,5565,5566],{},[118,5567,5568,5570,5573,5576,5579],{},[121,5569,579],{},[121,5571,5572],{},"Dart Stream",[121,5574,5575],{},"Swift AsyncSequence",[121,5577,5578],{},"Kotlin Flow",[121,5580,5581],{},"Vue (RxJS\u002Fwatch)",[137,5583,5584,5608,5636,5671,5696],{},[118,5585,5586,5589,5594,5599,5604],{},[142,5587,5588],{},"冷流（lazy）",[142,5590,5591],{},[167,5592,5593],{},"Stream.fromIterable",[142,5595,5596],{},[167,5597,5598],{},"AsyncStream",[142,5600,5601],{},[167,5602,5603],{},"flow {}",[142,5605,5606],{},[167,5607,2523],{},[118,5609,5610,5613,5618,5623,5631],{},[142,5611,5612],{},"热流（shared）",[142,5614,5615],{},[167,5616,5617],{},"StreamController.broadcast",[142,5619,5620],{},[167,5621,5622],{},"AsyncChannel",[142,5624,5625,684,5628],{},[167,5626,5627],{},"SharedFlow",[167,5629,5630],{},"StateFlow",[142,5632,5633,5635],{},[167,5634,2458],{}," \u002F Pinia state",[118,5637,5638,5641,5651,5657,5666],{},[142,5639,5640],{},"操作符",[142,5642,5643,5645,5646,5645,5648],{},[167,5644,1002],{}," ",[167,5647,1024],{},[167,5649,5650],{},".transform()",[142,5652,5653,5645,5655],{},[167,5654,1002],{},[167,5656,1029],{},[142,5658,5659,5645,5661,5645,5663],{},[167,5660,1002],{},[167,5662,1029],{},[167,5664,5665],{},".collect()",[142,5667,5668,5669],{},"RxJS 全套 \u002F ",[167,5670,2520],{},[118,5672,5673,5676,5682,5685,5693],{},[142,5674,5675],{},"背压",[142,5677,5678,5681],{},[167,5679,5680],{},"StreamController"," 支持 pause\u002Fresume",[142,5683,5684],{},"内置",[142,5686,5687,684,5690],{},[167,5688,5689],{},"buffer()",[167,5691,5692],{},"conflate()",[142,5694,5695],{},"无（单线程无需）",[118,5697,5698,5701,5706,5711,5716],{},[142,5699,5700],{},"取消",[142,5702,5703],{},[167,5704,5705],{},"StreamSubscription.cancel()",[142,5707,5708],{},[167,5709,5710],{},"Task.cancel()",[142,5712,5713],{},[167,5714,5715],{},"Job.cancel()",[142,5717,5718,5720],{},[167,5719,2523],{}," 返回 stop 函数",[17,5722],{},[20,5724,5726],{"id":5725},"_9-渲染机制与性能优化","9. 渲染机制与性能优化",[107,5728,5730],{"id":5729},"_91-渲染管线对比","9.1 渲染管线对比",[112,5732,5733,5747],{},[115,5734,5735],{},[118,5736,5737,5739,5741,5743,5745],{},[121,5738,2015],{},[121,5740,2018],{},[121,5742,2021],{},[121,5744,2024],{},[121,5746,3038],{},[137,5748,5749,5766,5783,5799,5816],{},[118,5750,5751,5754,5757,5760,5763],{},[142,5752,5753],{},"UI 描述",[142,5755,5756],{},"Widget 树",[142,5758,5759],{},"View 树",[142,5761,5762],{},"@Composable 树",[142,5764,5765],{},"Virtual DOM",[118,5767,5768,5771,5774,5777,5780],{},[142,5769,5770],{},"Diff\u002FReconcile",[142,5772,5773],{},"Element 树 canUpdate",[142,5775,5776],{},"View body diff",[142,5778,5779],{},"Slot Table + positional key",[142,5781,5782],{},"VNode diff",[118,5784,5785,5787,5790,5793,5796],{},[142,5786,3157],{},[142,5788,5789],{},"RenderObject layout",[142,5791,5792],{},"Layout Protocol",[142,5794,5795],{},"Measure + Place",[142,5797,5798],{},"CSS 引擎 (Blink\u002FWebKit)",[118,5800,5801,5804,5807,5810,5813],{},[142,5802,5803],{},"绘制",[142,5805,5806],{},"RenderObject paint → Skia\u002FImpeller",[142,5808,5809],{},"Core Animation layer",[142,5811,5812],{},"Canvas + RenderNode",[142,5814,5815],{},"DOM 操作 → 合成层绘制",[118,5817,5818,5821,5824,5826,5828],{},[142,5819,5820],{},"帧率目标",[142,5822,5823],{},"60\u002F120fps",[142,5825,5823],{},[142,5827,5823],{},[142,5829,5830],{},"60fps",[107,5832,5834],{"id":5833},"_92-三棵树-vs-其他机制","9.2 三棵树 vs 其他机制",[235,5836,5839],{"className":5837,"code":5838,"language":3379},[3377],"Flutter 三棵树:\n  Widget 树  ──build()──→  Element 树  ──createRenderObject()──→  RenderObject 树\n  (配置\u002F蓝图)              (生命周期管理)                        (布局\u002F绘制)\n\nSwiftUI:\n  View struct  ──body──→  View Graph (AttributeGraph)  ──→  Core Animation Layer\n  (声明\u002F配置)              (内部状态图)                       (渲染)\n\nCompose:\n  @Composable  ──composition──→  Slot Table  ──→  LayoutNode  ──→  RenderNode\n  (函数)                         (线性存储)       (布局)          (绘制)\n\nVue:\n  Template  ──compile──→  Render Function  ──exec──→  VNode 树  ──diff\u002Fpatch──→  真实 DOM\n  (模板)                  (渲染函数)                  (虚拟DOM)                  (浏览器DOM)\n",[167,5840,5838],{"__ignoreMap":240},[10,5842,5843,5847],{},[13,5844,492,5845,510],{},[494,5846,496],{},[512,5848,5849,5856,5859],{},[27,5850,5851,5852,5855],{},"Flutter: Widget→Element 的对应关系由 ",[167,5853,5854],{},"canUpdate()"," 决定（runtimeType + key 相同则更新，否则新建）",[27,5857,5858],{},"Compose: Slot Table 是线性数组，利用 Gap Buffer 高效插入\u002F删除，比树结构更快",[27,5860,5861],{},"Vue: 编译器在编译阶段就能标记静态节点 (static hoisting)、Patch Flags，跳过不需要 diff 的部分",[10,5863,5864,5868],{},[13,5865,546,5866,510],{},[494,5867,549],{},[512,5869,5870,5875,5878],{},[27,5871,3467,5872,5874],{},[167,5873,2376],{}," 的作用？（帮助 Element 树正确匹配 Widget，用于列表重排、动画 Hero 等场景）",[27,5876,5877],{},"GlobalKey vs LocalKey 的区别？（GlobalKey 全局唯一可跨子树访问 State，LocalKey 在同级中唯一）",[27,5879,5880],{},"Vue 的 Virtual DOM diff 算法复杂度？（O(n) 同层比较，不跨层）",[17,5882],{},[107,5884,5886],{"id":5885},"_93-性能优化手段","9.3 性能优化手段",[112,5888,5889,5904],{},[115,5890,5891],{},[118,5892,5893,5896,5898,5900,5902],{},[121,5894,5895],{},"优化项",[121,5897,2018],{},[121,5899,2021],{},[121,5901,2024],{},[121,5903,3038],{},[137,5905,5906,5941,5965,5985,6010,6027],{},[118,5907,5908,5911,5916,5922,5929],{},[142,5909,5910],{},"减少重建",[142,5912,5913,5915],{},[167,5914,687],{}," Widget、合理拆分 Widget",[142,5917,5918,5921],{},[167,5919,5920],{},"EquatableView","、提取子 View",[142,5923,5924,5926,5927],{},[167,5925,2400],{}," 稳定、",[167,5928,2792],{},[142,5930,5931,5934,5935,5934,5938],{},[167,5932,5933],{},"v-once","、",[167,5936,5937],{},"computed",[167,5939,5940],{},"shallowRef",[118,5942,5943,5946,5951,5958,5962],{},[142,5944,5945],{},"列表优化",[142,5947,5948,5950],{},[167,5949,2372],{}," (懒加载)",[142,5952,5953,684,5956],{},[167,5954,5955],{},"LazyVStack",[167,5957,3357],{},[142,5959,5960],{},[167,5961,2393],{},[142,5963,5964],{},"虚拟列表 (vue-virtual-scroller)",[118,5966,5967,5970,5976,5979,5982],{},[142,5968,5969],{},"图片优化",[142,5971,5972,5975],{},[167,5973,5974],{},"cached_network_image"," + 尺寸限制",[142,5977,5978],{},"AsyncImage + 缓存",[142,5980,5981],{},"Coil (异步+缓存)",[142,5983,5984],{},"懒加载 + srcset + WebP",[118,5986,5987,5990,5996,6002,6007],{},[142,5988,5989],{},"动画",[142,5991,5992,5995],{},[167,5993,5994],{},"AnimationController"," 60fps 独立",[142,5997,5998,6001],{},[167,5999,6000],{},"withAnimation"," 隐式动画",[142,6003,6004],{},[167,6005,6006],{},"animate*AsState",[142,6008,6009],{},"CSS Transition \u002F GSAP",[118,6011,6012,6015,6018,6021,6024],{},[142,6013,6014],{},"分析工具",[142,6016,6017],{},"DevTools (Timeline\u002FWidget Inspector)",[142,6019,6020],{},"Instruments (Time Profiler)",[142,6022,6023],{},"Layout Inspector \u002F Profiler",[142,6025,6026],{},"Chrome DevTools (Performance)",[118,6028,6029,6032,6035,6038,6041],{},[142,6030,6031],{},"内存分析",[142,6033,6034],{},"DevTools Memory 视图",[142,6036,6037],{},"Instruments (Allocations\u002FLeaks)",[142,6039,6040],{},"Android Profiler",[142,6042,6043],{},"Chrome Memory Snapshot",[10,6045,6046,6050],{},[13,6047,506,6048,510],{},[494,6049,509],{},[512,6051,6052,6059,6065,6072,6079,6087],{},[27,6053,6054,6055,6058],{},"Flutter: 在 ",[167,6056,6057],{},"build()"," 中创建大对象（如 AnimationController）导致每帧重复创建",[27,6060,6061,6062,6064],{},"Flutter: 不加 ",[167,6063,687],{}," 导致 Widget 无法复用，整棵子树每帧重建",[27,6066,6067,6068,6071],{},"SwiftUI: 在 ",[167,6069,6070],{},"body"," 中做耗时计算（body 可能频繁调用）",[27,6073,3002,6074,2394,6076,6078],{},[167,6075,3005],{},[167,6077,2792],{}," 之外创建对象，每次 recomposition 重新创建",[27,6080,2183,6081,6083,6084,6086],{},[167,6082,2361],{}," 不加 ",[167,6085,2400],{}," 或用 index 做 key 导致列表状态错乱",[27,6088,2183,6089,6091],{},[167,6090,5937],{}," 依赖了不需要追踪的变量导致不必要的重算",[17,6093],{},[20,6095,6097],{"id":6096},"_10-平台通信与原生能力","10. 平台通信与原生能力",[107,6099,6101],{"id":6100},"_101-通信机制对比","10.1 通信机制对比",[112,6103,6104,6121],{},[115,6105,6106],{},[118,6107,6108,6110,6112,6115,6118],{},[121,6109,123],{},[121,6111,2018],{},[121,6113,6114],{},"SwiftUI \u002F UIKit",[121,6116,6117],{},"Compose \u002F Android",[121,6119,6120],{},"Vue \u002F Web",[137,6122,6123,6143,6159,6178],{},[118,6124,6125,6128,6134,6137,6140],{},[142,6126,6127],{},"调用原生",[142,6129,6130,6133],{},[494,6131,6132],{},"MethodChannel"," \u002F EventChannel",[142,6135,6136],{},"直接调用 Framework",[142,6138,6139],{},"直接调用 Android SDK",[142,6141,6142],{},"Web API \u002F JS Bridge",[118,6144,6145,6148,6151,6154,6156],{},[142,6146,6147],{},"通信协议",[142,6149,6150],{},"异步消息传递（JSON\u002F二进制）",[142,6152,6153],{},"直接函数调用",[142,6155,6153],{},[142,6157,6158],{},"postMessage \u002F Bridge",[118,6160,6161,6164,6170,6173,6175],{},[142,6162,6163],{},"原生 UI 嵌入",[142,6165,6166,6169],{},[167,6167,6168],{},"PlatformView"," (性能开销大)",[142,6171,6172],{},"直接使用",[142,6174,6172],{},[142,6176,6177],{},"iframe \u002F Web Component",[118,6179,6180,6183,6186,6189,6192],{},[142,6181,6182],{},"插件机制",[142,6184,6185],{},"pub.dev 插件 (Dart + 原生)",[142,6187,6188],{},"Swift Package \u002F Framework",[142,6190,6191],{},"Gradle 依赖",[142,6193,6194],{},"npm 包",[107,6196,6198],{"id":6197},"_102-flutter-platform-channel","10.2 Flutter Platform Channel",[235,6200,6203],{"className":6201,"code":6202,"language":3379},[3377],"Flutter (Dart)                     Native (Swift\u002FKotlin)\n     │                                     │\n     │  MethodChannel('com.app\u002Fbattery')   │\n     │  ──── invokeMethod('level') ────→   │\n     │                                     │  处理请求\n     │  ←──── result.success(85) ────────  │\n     │                                     │\n     │  EventChannel('com.app\u002Fevents')     │\n     │  ──── listen() ────→               │\n     │  ←──── stream of events ────────   │\n",[167,6204,6202],{"__ignoreMap":240},[235,6206,6208],{"className":237,"code":6207,"language":239,"meta":240,"style":240},"\u002F\u002F Dart 端\nconst channel = MethodChannel('com.app\u002Fbattery');\nfinal level = await channel.invokeMethod\u003Cint>('getBatteryLevel');\n",[167,6209,6210,6215,6220],{"__ignoreMap":240},[244,6211,6212],{"class":246,"line":247},[244,6213,6214],{},"\u002F\u002F Dart 端\n",[244,6216,6217],{"class":246,"line":253},[244,6218,6219],{},"const channel = MethodChannel('com.app\u002Fbattery');\n",[244,6221,6222],{"class":246,"line":259},[244,6223,6224],{},"final level = await channel.invokeMethod\u003Cint>('getBatteryLevel');\n",[235,6226,6228],{"className":283,"code":6227,"language":285,"meta":240,"style":240},"\u002F\u002F Swift (iOS) 端\nlet channel = FlutterMethodChannel(name: \"com.app\u002Fbattery\", binaryMessenger: messenger)\nchannel.setMethodCallHandler { call, result in\n    if call.method == \"getBatteryLevel\" {\n        result(UIDevice.current.batteryLevel * 100)\n    }\n}\n",[167,6229,6230,6235,6240,6245,6250,6255,6259],{"__ignoreMap":240},[244,6231,6232],{"class":246,"line":247},[244,6233,6234],{},"\u002F\u002F Swift (iOS) 端\n",[244,6236,6237],{"class":246,"line":253},[244,6238,6239],{},"let channel = FlutterMethodChannel(name: \"com.app\u002Fbattery\", binaryMessenger: messenger)\n",[244,6241,6242],{"class":246,"line":259},[244,6243,6244],{},"channel.setMethodCallHandler { call, result in\n",[244,6246,6247],{"class":246,"line":265},[244,6248,6249],{},"    if call.method == \"getBatteryLevel\" {\n",[244,6251,6252],{"class":246,"line":271},[244,6253,6254],{},"        result(UIDevice.current.batteryLevel * 100)\n",[244,6256,6257],{"class":246,"line":277},[244,6258,1314],{},[244,6260,6261],{"class":246,"line":320},[244,6262,738],{},[235,6264,6266],{"className":332,"code":6265,"language":334,"meta":240,"style":240},"\u002F\u002F Kotlin (Android) 端\nval channel = MethodChannel(flutterEngine.dartExecutor, \"com.app\u002Fbattery\")\nchannel.setMethodCallHandler { call, result ->\n    if (call.method == \"getBatteryLevel\") {\n        val level = getBatteryLevel()\n        result.success(level)\n    }\n}\n",[167,6267,6268,6273,6278,6283,6288,6293,6298,6302],{"__ignoreMap":240},[244,6269,6270],{"class":246,"line":247},[244,6271,6272],{},"\u002F\u002F Kotlin (Android) 端\n",[244,6274,6275],{"class":246,"line":253},[244,6276,6277],{},"val channel = MethodChannel(flutterEngine.dartExecutor, \"com.app\u002Fbattery\")\n",[244,6279,6280],{"class":246,"line":259},[244,6281,6282],{},"channel.setMethodCallHandler { call, result ->\n",[244,6284,6285],{"class":246,"line":265},[244,6286,6287],{},"    if (call.method == \"getBatteryLevel\") {\n",[244,6289,6290],{"class":246,"line":271},[244,6291,6292],{},"        val level = getBatteryLevel()\n",[244,6294,6295],{"class":246,"line":277},[244,6296,6297],{},"        result.success(level)\n",[244,6299,6300],{"class":246,"line":320},[244,6301,1314],{},[244,6303,6304],{"class":246,"line":326},[244,6305,738],{},[10,6307,6308,6312],{},[13,6309,492,6310,510],{},[494,6311,496],{},[512,6313,6314,6321,6326],{},[27,6315,6316,6317,6320],{},"MethodChannel 是",[494,6318,6319],{},"异步","的，数据需要序列化\u002F反序列化（StandardMessageCodec），大量数据传输有性能开销",[27,6322,6323,6325],{},[167,6324,6168],{},"（在 Flutter 中嵌入原生 View）有显著性能开销：Android 用 VirtualDisplay 或 Hybrid Composition，iOS 用 UIKitView",[27,6327,6328],{},"Pigeon 工具可以生成类型安全的 Platform Channel 代码，避免手写字符串匹配",[10,6330,6331,6335],{},[13,6332,506,6333,510],{},[494,6334,509],{},[512,6336,6337,6340,6343],{},[27,6338,6339],{},"Channel 名称拼写不一致导致调用无响应",[27,6341,6342],{},"在非主线程调用 MethodChannel 导致崩溃（Flutter engine 要求主线程通信）",[27,6344,6345,6346,6349],{},"忘记处理 ",[167,6347,6348],{},"FlutterMethodNotImplemented"," 错误",[10,6351,6352,6356],{},[13,6353,546,6354,510],{},[494,6355,549],{},[512,6357,6358,6361,6364],{},[27,6359,6360],{},"MethodChannel vs EventChannel vs BasicMessageChannel 的区别？（Method: 一次性调用，Event: 持续数据流，Basic: 自定义编解码）",[27,6362,6363],{},"Flutter 如何嵌入原生 View？性能问题如何优化？（PlatformView，尽量用 Texture 替代）",[27,6365,6366],{},"Flutter 的 FFI (dart:ffi) 和 Platform Channel 的区别？（FFI 是直接调用 C 函数，同步，无序列化开销）",[17,6368],{},[20,6370,6372],{"id":6371},"_11-架构模式","11. 架构模式",[107,6374,6376],{"id":6375},"_111-mvvm-在四平台的实现","11.1 MVVM 在四平台的实现",[112,6378,6379,6394],{},[115,6380,6381],{},[118,6382,6383,6386,6388,6390,6392],{},[121,6384,6385],{},"层级",[121,6387,2018],{},[121,6389,2021],{},[121,6391,2024],{},[121,6393,3038],{},[137,6395,6396,6413,6430,6447],{},[118,6397,6398,6401,6404,6407,6409],{},[142,6399,6400],{},"View",[142,6402,6403],{},"Widget (build)",[142,6405,6406],{},"View (body)",[142,6408,3005],{},[142,6410,6411],{},[167,6412,1667],{},[118,6414,6415,6418,6421,6424,6427],{},[142,6416,6417],{},"ViewModel",[142,6419,6420],{},"ChangeNotifier \u002F GetxController \u002F BLoC",[142,6422,6423],{},"ObservableObject",[142,6425,6426],{},"ViewModel (AAC)",[142,6428,6429],{},"Composition API (setup)",[118,6431,6432,6435,6438,6441,6444],{},[142,6433,6434],{},"Model",[142,6436,6437],{},"Dart class \u002F freezed",[142,6439,6440],{},"struct + Codable",[142,6442,6443],{},"data class + Room Entity",[142,6445,6446],{},"TypeScript interface + API",[118,6448,6449,6452,6455,6458,6460],{},[142,6450,6451],{},"绑定方式",[142,6453,6454],{},"Provider \u002F Obx \u002F BlocBuilder",[142,6456,6457],{},"@StateObject + @Published",[142,6459,4954],{},[142,6461,6462],{},"ref + template 自动绑定",[235,6464,6466],{"className":237,"code":6465,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter MVVM — ChangeNotifier + Provider\nclass UserViewModel extends ChangeNotifier {\n  User? _user;\n  User? get user => _user;\n  \n  Future\u003Cvoid> loadUser() async {\n    _user = await userRepository.getUser();\n    notifyListeners();\n  }\n}\n\n\u002F\u002F View\nConsumer\u003CUserViewModel>(\n  builder: (_, vm, __) => Text(vm.user?.name ?? 'Loading'),\n)\n",[167,6467,6468,6473,6478,6483,6488,6492,6497,6502,6507,6511,6515,6519,6524,6529,6534],{"__ignoreMap":240},[244,6469,6470],{"class":246,"line":247},[244,6471,6472],{},"\u002F\u002F Flutter MVVM — ChangeNotifier + Provider\n",[244,6474,6475],{"class":246,"line":253},[244,6476,6477],{},"class UserViewModel extends ChangeNotifier {\n",[244,6479,6480],{"class":246,"line":259},[244,6481,6482],{},"  User? _user;\n",[244,6484,6485],{"class":246,"line":265},[244,6486,6487],{},"  User? get user => _user;\n",[244,6489,6490],{"class":246,"line":271},[244,6491,1735],{},[244,6493,6494],{"class":246,"line":277},[244,6495,6496],{},"  Future\u003Cvoid> loadUser() async {\n",[244,6498,6499],{"class":246,"line":320},[244,6500,6501],{},"    _user = await userRepository.getUser();\n",[244,6503,6504],{"class":246,"line":326},[244,6505,6506],{},"    notifyListeners();\n",[244,6508,6509],{"class":246,"line":1256},[244,6510,1550],{},[244,6512,6513],{"class":246,"line":1311},[244,6514,738],{},[244,6516,6517],{"class":246,"line":1317},[244,6518,1238],{"emptyLinePlaceholder":1237},[244,6520,6521],{"class":246,"line":2582},[244,6522,6523],{},"\u002F\u002F View\n",[244,6525,6526],{"class":246,"line":2588},[244,6527,6528],{},"Consumer\u003CUserViewModel>(\n",[244,6530,6531],{"class":246,"line":2593},[244,6532,6533],{},"  builder: (_, vm, __) => Text(vm.user?.name ?? 'Loading'),\n",[244,6535,6537],{"class":246,"line":6536},15,[244,6538,2723],{},[235,6540,6542],{"className":283,"code":6541,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI MVVM — ObservableObject\n@Observable class UserViewModel {\n    var user: User?\n    \n    func loadUser() async {\n        user = try? await userRepository.getUser()\n    }\n}\n\n\u002F\u002F View\nstruct UserView: View {\n    @State var vm = UserViewModel()\n    var body: some View {\n        Text(vm.user?.name ?? \"Loading\")\n            .task { await vm.loadUser() }\n    }\n}\n",[167,6543,6544,6549,6554,6559,6563,6568,6573,6577,6581,6585,6589,6594,6599,6603,6608,6613,6618],{"__ignoreMap":240},[244,6545,6546],{"class":246,"line":247},[244,6547,6548],{},"\u002F\u002F SwiftUI MVVM — ObservableObject\n",[244,6550,6551],{"class":246,"line":253},[244,6552,6553],{},"@Observable class UserViewModel {\n",[244,6555,6556],{"class":246,"line":259},[244,6557,6558],{},"    var user: User?\n",[244,6560,6561],{"class":246,"line":265},[244,6562,1783],{},[244,6564,6565],{"class":246,"line":271},[244,6566,6567],{},"    func loadUser() async {\n",[244,6569,6570],{"class":246,"line":277},[244,6571,6572],{},"        user = try? await userRepository.getUser()\n",[244,6574,6575],{"class":246,"line":320},[244,6576,1314],{},[244,6578,6579],{"class":246,"line":326},[244,6580,738],{},[244,6582,6583],{"class":246,"line":1256},[244,6584,1238],{"emptyLinePlaceholder":1237},[244,6586,6587],{"class":246,"line":1311},[244,6588,6523],{},[244,6590,6591],{"class":246,"line":1317},[244,6592,6593],{},"struct UserView: View {\n",[244,6595,6596],{"class":246,"line":2582},[244,6597,6598],{},"    @State var vm = UserViewModel()\n",[244,6600,6601],{"class":246,"line":2588},[244,6602,1788],{},[244,6604,6605],{"class":246,"line":2593},[244,6606,6607],{},"        Text(vm.user?.name ?? \"Loading\")\n",[244,6609,6610],{"class":246,"line":6536},[244,6611,6612],{},"            .task { await vm.loadUser() }\n",[244,6614,6616],{"class":246,"line":6615},16,[244,6617,1314],{},[244,6619,6621],{"class":246,"line":6620},17,[244,6622,738],{},[235,6624,6626],{"className":332,"code":6625,"language":334,"meta":240,"style":240},"\u002F\u002F Compose MVVM — ViewModel + StateFlow\nclass UserViewModel @Inject constructor(\n    private val repo: UserRepository\n) : ViewModel() {\n    private val _user = MutableStateFlow\u003CUser?>(null)\n    val user = _user.asStateFlow()\n    \n    fun loadUser() { viewModelScope.launch { _user.value = repo.getUser() } }\n}\n\n\u002F\u002F Composable\n@Composable\nfun UserScreen(vm: UserViewModel = hiltViewModel()) {\n    val user by vm.user.collectAsState()\n    Text(user?.name ?: \"Loading\")\n}\n",[167,6627,6628,6633,6638,6643,6648,6653,6658,6662,6667,6671,6675,6680,6684,6689,6694,6699],{"__ignoreMap":240},[244,6629,6630],{"class":246,"line":247},[244,6631,6632],{},"\u002F\u002F Compose MVVM — ViewModel + StateFlow\n",[244,6634,6635],{"class":246,"line":253},[244,6636,6637],{},"class UserViewModel @Inject constructor(\n",[244,6639,6640],{"class":246,"line":259},[244,6641,6642],{},"    private val repo: UserRepository\n",[244,6644,6645],{"class":246,"line":265},[244,6646,6647],{},") : ViewModel() {\n",[244,6649,6650],{"class":246,"line":271},[244,6651,6652],{},"    private val _user = MutableStateFlow\u003CUser?>(null)\n",[244,6654,6655],{"class":246,"line":277},[244,6656,6657],{},"    val user = _user.asStateFlow()\n",[244,6659,6660],{"class":246,"line":320},[244,6661,1783],{},[244,6663,6664],{"class":246,"line":326},[244,6665,6666],{},"    fun loadUser() { viewModelScope.launch { _user.value = repo.getUser() } }\n",[244,6668,6669],{"class":246,"line":1256},[244,6670,738],{},[244,6672,6673],{"class":246,"line":1311},[244,6674,1238],{"emptyLinePlaceholder":1237},[244,6676,6677],{"class":246,"line":1317},[244,6678,6679],{},"\u002F\u002F Composable\n",[244,6681,6682],{"class":246,"line":2582},[244,6683,1821],{},[244,6685,6686],{"class":246,"line":2588},[244,6687,6688],{},"fun UserScreen(vm: UserViewModel = hiltViewModel()) {\n",[244,6690,6691],{"class":246,"line":2593},[244,6692,6693],{},"    val user by vm.user.collectAsState()\n",[244,6695,6696],{"class":246,"line":6536},[244,6697,6698],{},"    Text(user?.name ?: \"Loading\")\n",[244,6700,6701],{"class":246,"line":6615},[244,6702,738],{},[235,6704,6706],{"className":1853,"code":6705,"language":1855,"meta":240,"style":240},"\u003C!-- Vue MVVM — Composition API -->\n\u003Cscript setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport { getUser } from '@\u002Fapi\u002Fuser'\n\nconst user = ref\u003CUser | null>(null)\nonMounted(async () => { user.value = await getUser() })\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cspan>{{ user?.name ?? 'Loading' }}\u003C\u002Fspan>\n\u003C\u002Ftemplate>\n",[167,6707,6708,6713,6729,6740,6752,6756,6782,6808,6816,6820,6828,6841],{"__ignoreMap":240},[244,6709,6710],{"class":246,"line":247},[244,6711,6712],{"class":383},"\u003C!-- Vue MVVM — Composition API -->\n",[244,6714,6715,6717,6719,6721,6723,6725,6727],{"class":246,"line":253},[244,6716,1867],{"class":393},[244,6718,1871],{"class":1870},[244,6720,1874],{"class":825},[244,6722,1877],{"class":825},[244,6724,1880],{"class":393},[244,6726,1883],{"class":434},[244,6728,1886],{"class":393},[244,6730,6731,6733,6736,6738],{"class":246,"line":259},[244,6732,2694],{"class":389},[244,6734,6735],{"class":393}," { ref, onMounted } ",[244,6737,2700],{"class":389},[244,6739,2703],{"class":434},[244,6741,6742,6744,6747,6749],{"class":246,"line":265},[244,6743,2694],{"class":389},[244,6745,6746],{"class":393}," { getUser } ",[244,6748,2700],{"class":389},[244,6750,6751],{"class":434}," '@\u002Fapi\u002Fuser'\n",[244,6753,6754],{"class":246,"line":271},[244,6755,1238],{"emptyLinePlaceholder":1237},[244,6757,6758,6760,6762,6764,6766,6768,6770,6772,6774,6777,6780],{"class":246,"line":277},[244,6759,687],{"class":389},[244,6761,4361],{"class":400},[244,6763,410],{"class":389},[244,6765,2715],{"class":825},[244,6767,1867],{"class":393},[244,6769,5342],{"class":825},[244,6771,404],{"class":389},[244,6773,407],{"class":400},[244,6775,6776],{"class":393},">(",[244,6778,6779],{"class":400},"null",[244,6781,2723],{"class":393},[244,6783,6784,6786,6788,6790,6793,6795,6798,6800,6802,6805],{"class":246,"line":320},[244,6785,2517],{"class":825},[244,6787,1462],{"class":393},[244,6789,5323],{"class":389},[244,6791,6792],{"class":393}," () ",[244,6794,3923],{"class":389},[244,6796,6797],{"class":393}," { user.value ",[244,6799,1880],{"class":389},[244,6801,5358],{"class":389},[244,6803,6804],{"class":825}," getUser",[244,6806,6807],{"class":393},"() })\n",[244,6809,6810,6812,6814],{"class":246,"line":326},[244,6811,1908],{"class":393},[244,6813,1871],{"class":1870},[244,6815,1886],{"class":393},[244,6817,6818],{"class":246,"line":1256},[244,6819,1238],{"emptyLinePlaceholder":1237},[244,6821,6822,6824,6826],{"class":246,"line":1311},[244,6823,1867],{"class":393},[244,6825,1923],{"class":1870},[244,6827,1886],{"class":393},[244,6829,6830,6832,6834,6837,6839],{"class":246,"line":1317},[244,6831,1930],{"class":393},[244,6833,244],{"class":1870},[244,6835,6836],{"class":393},">{{ user?.name ?? 'Loading' }}\u003C\u002F",[244,6838,244],{"class":1870},[244,6840,1886],{"class":393},[244,6842,6843,6845,6847],{"class":246,"line":2582},[244,6844,1908],{"class":393},[244,6846,1923],{"class":1870},[244,6848,1886],{"class":393},[17,6850],{},[107,6852,6854],{"id":6853},"_112-依赖注入对比","11.2 依赖注入对比",[112,6856,6857,6871],{},[115,6858,6859],{},[118,6860,6861,6863,6865,6867,6869],{},[121,6862,2858],{},[121,6864,2018],{},[121,6866,4078],{},[121,6868,4081],{},[121,6870,3038],{},[137,6872,6873,6891,6907,6939],{},[118,6874,6875,6878,6880,6883,6889],{},[142,6876,6877],{},"框架级",[142,6879,2876],{},[142,6881,6882],{},"SwiftUI Environment",[142,6884,6885,6888],{},[494,6886,6887],{},"Hilt"," (Dagger)",[142,6890,2907],{},[118,6892,6893,6896,6899,6902,6905],{},[142,6894,6895],{},"第三方",[142,6897,6898],{},"GetIt \u002F get_it",[142,6900,6901],{},"Swinject \u002F Factory",[142,6903,6904],{},"Koin",[142,6906,3538],{},[118,6908,6909,6912,6918,6926,6934],{},[142,6910,6911],{},"注册方式",[142,6913,6914,6917],{},[167,6915,6916],{},"Provider\u003CT>()"," 在 Widget 树中",[142,6919,6920,684,6923],{},[167,6921,6922],{},"@Environment",[167,6924,6925],{},"environmentObject",[142,6927,6928,1575,6931,4847],{},[167,6929,6930],{},"@Inject",[167,6932,6933],{},"@Module",[142,6935,6936],{},[167,6937,6938],{},"app.provide()",[118,6940,6941,6944,6947,6950,6953],{},[142,6942,6943],{},"作用域",[142,6945,6946],{},"Widget 子树",[142,6948,6949],{},"View 子树",[142,6951,6952],{},"Activity\u002FFragment\u002FViewModel scope",[142,6954,6955],{},"组件子树",[10,6957,6958,6962],{},[13,6959,492,6960,510],{},[494,6961,496],{},[512,6963,6964,6967,6970],{},[27,6965,6966],{},"Hilt 是编译时 DI（基于 Dagger 的注解处理），性能最好但配置最复杂",[27,6968,6969],{},"Riverpod 独立于 Widget 树（不依赖 BuildContext），可以在测试中轻松 override",[27,6971,1972,6972,6975],{},[167,6973,6974],{},"provide\u002Finject"," 不是响应式的（需要 provide ref 才能响应式）",[10,6977,6978,6982],{},[13,6979,546,6980,510],{},[494,6981,549],{},[512,6983,6984,6987,6990],{},[27,6985,6986],{},"Provider vs Riverpod 的核心区别？（Provider 依赖 BuildContext，Riverpod 不依赖；Riverpod 有编译时安全）",[27,6988,6989],{},"Hilt 的 Component 层级？（SingletonComponent → ActivityComponent → FragmentComponent → ViewModelComponent → ViewComponent）",[27,6991,6992],{},"Vue 的 provide\u002Finject 和 Pinia 的区别？（provide\u002Finject 是组件树级别的，Pinia 是全局单例 store）",[17,6994],{},[107,6996,6998],{"id":6997},"_113-模块化方案对比","11.3 模块化方案对比",[112,7000,7001,7015],{},[115,7002,7003],{},[118,7004,7005,7007,7009,7011,7013],{},[121,7006,2858],{},[121,7008,2018],{},[121,7010,4078],{},[121,7012,4081],{},[121,7014,3038],{},[137,7016,7017,7033,7050],{},[118,7018,7019,7022,7025,7027,7030],{},[142,7020,7021],{},"代码组织",[142,7023,7024],{},"Package \u002F 文件夹约定",[142,7026,6188],{},[142,7028,7029],{},"Gradle Module",[142,7031,7032],{},"npm workspace \u002F monorepo",[118,7034,7035,7038,7041,7044,7047],{},[142,7036,7037],{},"动态加载",[142,7039,7040],{},"Deferred Components",[142,7042,7043],{},"Dynamic Framework",[142,7045,7046],{},"Dynamic Feature Module",[142,7048,7049],{},"路由级 lazy import",[118,7051,7052,7055,7058,7061,7064],{},[142,7053,7054],{},"路由解耦",[142,7056,7057],{},"路由表注册",[142,7059,7060],{},"Coordinator 模式",[142,7062,7063],{},"Navigation Component + Deep Link",[142,7065,7066],{},"Vue Router lazy loading",[17,7068],{},[20,7070,7072],{"id":7071},"_12-构建测试与发布","12. 构建、测试与发布",[107,7074,7076],{"id":7075},"_121-构建工具对比","12.1 构建工具对比",[112,7078,7079,7093],{},[115,7080,7081],{},[118,7082,7083,7085,7087,7089,7091],{},[121,7084,123],{},[121,7086,2018],{},[121,7088,4078],{},[121,7090,4081],{},[121,7092,3038],{},[137,7094,7095,7118,7135,7150,7167],{},[118,7096,7097,7100,7103,7106,7112],{},[142,7098,7099],{},"构建工具",[142,7101,7102],{},"Flutter CLI",[142,7104,7105],{},"Xcode Build System",[142,7107,7108,7111],{},[494,7109,7110],{},"Gradle"," (Kotlin DSL\u002FGroovy)",[142,7113,7114,7117],{},[494,7115,7116],{},"Vite"," \u002F Webpack",[118,7119,7120,7123,7126,7129,7132],{},[142,7121,7122],{},"包管理",[142,7124,7125],{},"pub (pubspec.yaml)",[142,7127,7128],{},"SPM \u002F CocoaPods",[142,7130,7131],{},"Gradle Dependencies",[142,7133,7134],{},"npm \u002F pnpm (package.json)",[118,7136,7137,7139,7141,7144,7147],{},[142,7138,4925],{},[142,7140,4442],{},[142,7142,7143],{},"Swift 宏",[142,7145,7146],{},"kapt \u002F KSP",[142,7148,7149],{},"Vite 插件",[118,7151,7152,7155,7158,7161,7164],{},[142,7153,7154],{},"热重载",[142,7156,7157],{},"✅ Hot Reload (保留状态)",[142,7159,7160],{},"✅ Xcode Previews (SwiftUI)",[142,7162,7163],{},"❌ (仅 Apply Changes 部分支持)",[142,7165,7166],{},"✅ Vite HMR",[118,7168,7169,7172,7175,7178,7181],{},[142,7170,7171],{},"产物",[142,7173,7174],{},"APK \u002F AAB \u002F IPA",[142,7176,7177],{},"IPA",[142,7179,7180],{},"APK \u002F AAB",[142,7182,7183],{},"静态文件 (HTML\u002FJS\u002FCSS)",[107,7185,7187],{"id":7186},"_122-测试框架对比","12.2 测试框架对比",[112,7189,7190,7204],{},[115,7191,7192],{},[118,7193,7194,7196,7198,7200,7202],{},[121,7195,6385],{},[121,7197,2018],{},[121,7199,4078],{},[121,7201,4081],{},[121,7203,3038],{},[137,7205,7206,7225,7244,7263,7287],{},[118,7207,7208,7211,7216,7219,7222],{},[142,7209,7210],{},"单元测试",[142,7212,7213],{},[167,7214,7215],{},"flutter_test",[142,7217,7218],{},"XCTest",[142,7220,7221],{},"JUnit \u002F MockK",[142,7223,7224],{},"Vitest \u002F Jest",[118,7226,7227,7230,7235,7238,7241],{},[142,7228,7229],{},"组件测试",[142,7231,7232],{},[167,7233,7234],{},"WidgetTester",[142,7236,7237],{},"XCTest + ViewInspector",[142,7239,7240],{},"Compose Testing",[142,7242,7243],{},"Vue Test Utils + @testing-library\u002Fvue",[118,7245,7246,7249,7254,7257,7260],{},[142,7247,7248],{},"集成测试",[142,7250,7251],{},[167,7252,7253],{},"integration_test",[142,7255,7256],{},"XCUITest",[142,7258,7259],{},"Espresso \u002F UI Automator",[142,7261,7262],{},"Cypress \u002F Playwright",[118,7264,7265,7268,7273,7278,7281],{},[142,7266,7267],{},"快照测试",[142,7269,7270],{},[167,7271,7272],{},"golden_toolkit",[142,7274,7275],{},[167,7276,7277],{},"PreviewSnapshots",[142,7279,7280],{},"Paparazzi \u002F Roborazzi",[142,7282,7283,7286],{},[167,7284,7285],{},"@vue\u002Ftest-utils"," + snapshot",[118,7288,7289,7292,7295,7298,7301],{},[142,7290,7291],{},"Mock",[142,7293,7294],{},"mockito",[142,7296,7297],{},"Swift mock protocols",[142,7299,7300],{},"Mockito-Kotlin \u002F MockK",[142,7302,7303],{},"vitest mock \u002F msw",[107,7305,7307],{"id":7306},"_123-测试代码对比","12.3 测试代码对比",[235,7309,7311],{"className":237,"code":7310,"language":239,"meta":240,"style":240},"\u002F\u002F Flutter — Widget 测试\ntestWidgets('Counter increments', (tester) async {\n  await tester.pumpWidget(MaterialApp(home: CounterPage()));\n  expect(find.text('0'), findsOneWidget);\n  await tester.tap(find.byIcon(Icons.add));\n  await tester.pump();\n  expect(find.text('1'), findsOneWidget);\n});\n",[167,7312,7313,7318,7323,7328,7333,7338,7343,7348],{"__ignoreMap":240},[244,7314,7315],{"class":246,"line":247},[244,7316,7317],{},"\u002F\u002F Flutter — Widget 测试\n",[244,7319,7320],{"class":246,"line":253},[244,7321,7322],{},"testWidgets('Counter increments', (tester) async {\n",[244,7324,7325],{"class":246,"line":259},[244,7326,7327],{},"  await tester.pumpWidget(MaterialApp(home: CounterPage()));\n",[244,7329,7330],{"class":246,"line":265},[244,7331,7332],{},"  expect(find.text('0'), findsOneWidget);\n",[244,7334,7335],{"class":246,"line":271},[244,7336,7337],{},"  await tester.tap(find.byIcon(Icons.add));\n",[244,7339,7340],{"class":246,"line":277},[244,7341,7342],{},"  await tester.pump();\n",[244,7344,7345],{"class":246,"line":320},[244,7346,7347],{},"  expect(find.text('1'), findsOneWidget);\n",[244,7349,7350],{"class":246,"line":326},[244,7351,7352],{},"});\n",[235,7354,7356],{"className":283,"code":7355,"language":285,"meta":240,"style":240},"\u002F\u002F SwiftUI — XCTest + ViewInspector\nfunc testCounterIncrements() throws {\n    var sut = CounterView()\n    let button = try sut.inspect().find(button: \"+\")\n    try button.tap()\n    let text = try sut.inspect().find(text: \"1\")\n    XCTAssertNotNil(text)\n}\n",[167,7357,7358,7363,7368,7373,7378,7383,7388,7393],{"__ignoreMap":240},[244,7359,7360],{"class":246,"line":247},[244,7361,7362],{},"\u002F\u002F SwiftUI — XCTest + ViewInspector\n",[244,7364,7365],{"class":246,"line":253},[244,7366,7367],{},"func testCounterIncrements() throws {\n",[244,7369,7370],{"class":246,"line":259},[244,7371,7372],{},"    var sut = CounterView()\n",[244,7374,7375],{"class":246,"line":265},[244,7376,7377],{},"    let button = try sut.inspect().find(button: \"+\")\n",[244,7379,7380],{"class":246,"line":271},[244,7381,7382],{},"    try button.tap()\n",[244,7384,7385],{"class":246,"line":277},[244,7386,7387],{},"    let text = try sut.inspect().find(text: \"1\")\n",[244,7389,7390],{"class":246,"line":320},[244,7391,7392],{},"    XCTAssertNotNil(text)\n",[244,7394,7395],{"class":246,"line":326},[244,7396,738],{},[235,7398,7400],{"className":332,"code":7399,"language":334,"meta":240,"style":240},"\u002F\u002F Compose — UI 测试\n@Test\nfun counterIncrements() {\n    composeTestRule.setContent { CounterScreen() }\n    composeTestRule.onNodeWithText(\"0\").assertIsDisplayed()\n    composeTestRule.onNodeWithContentDescription(\"add\").performClick()\n    composeTestRule.onNodeWithText(\"1\").assertIsDisplayed()\n}\n",[167,7401,7402,7407,7412,7417,7422,7427,7432,7437],{"__ignoreMap":240},[244,7403,7404],{"class":246,"line":247},[244,7405,7406],{},"\u002F\u002F Compose — UI 测试\n",[244,7408,7409],{"class":246,"line":253},[244,7410,7411],{},"@Test\n",[244,7413,7414],{"class":246,"line":259},[244,7415,7416],{},"fun counterIncrements() {\n",[244,7418,7419],{"class":246,"line":265},[244,7420,7421],{},"    composeTestRule.setContent { CounterScreen() }\n",[244,7423,7424],{"class":246,"line":271},[244,7425,7426],{},"    composeTestRule.onNodeWithText(\"0\").assertIsDisplayed()\n",[244,7428,7429],{"class":246,"line":277},[244,7430,7431],{},"    composeTestRule.onNodeWithContentDescription(\"add\").performClick()\n",[244,7433,7434],{"class":246,"line":320},[244,7435,7436],{},"    composeTestRule.onNodeWithText(\"1\").assertIsDisplayed()\n",[244,7438,7439],{"class":246,"line":326},[244,7440,738],{},[235,7442,7444],{"className":374,"code":7443,"language":376,"meta":240,"style":240},"\u002F\u002F Vue — Vitest + Vue Test Utils\ntest('counter increments', async () => {\n  const wrapper = mount(Counter)\n  expect(wrapper.text()).toContain('0')\n  await wrapper.find('button').trigger('click')\n  expect(wrapper.text()).toContain('1')\n})\n",[167,7445,7446,7451,7471,7486,7509,7538,7557],{"__ignoreMap":240},[244,7447,7448],{"class":246,"line":247},[244,7449,7450],{"class":383},"\u002F\u002F Vue — Vitest + Vue Test Utils\n",[244,7452,7453,7456,7458,7461,7463,7465,7467,7469],{"class":246,"line":253},[244,7454,7455],{"class":825},"test",[244,7457,1462],{"class":393},[244,7459,7460],{"class":434},"'counter increments'",[244,7462,607],{"class":393},[244,7464,5323],{"class":389},[244,7466,6792],{"class":393},[244,7468,3923],{"class":389},[244,7470,1478],{"class":393},[244,7472,7473,7475,7478,7480,7483],{"class":246,"line":259},[244,7474,5350],{"class":389},[244,7476,7477],{"class":400}," wrapper",[244,7479,410],{"class":389},[244,7481,7482],{"class":825}," mount",[244,7484,7485],{"class":393},"(Counter)\n",[244,7487,7488,7491,7494,7496,7499,7502,7504,7507],{"class":246,"line":265},[244,7489,7490],{"class":825},"  expect",[244,7492,7493],{"class":393},"(wrapper.",[244,7495,3379],{"class":825},[244,7497,7498],{"class":393},"()).",[244,7500,7501],{"class":825},"toContain",[244,7503,1462],{"class":393},[244,7505,7506],{"class":434},"'0'",[244,7508,2723],{"class":393},[244,7510,7511,7514,7517,7520,7522,7525,7528,7531,7533,7536],{"class":246,"line":271},[244,7512,7513],{"class":389},"  await",[244,7515,7516],{"class":393}," wrapper.",[244,7518,7519],{"class":825},"find",[244,7521,1462],{"class":393},[244,7523,7524],{"class":434},"'button'",[244,7526,7527],{"class":393},").",[244,7529,7530],{"class":825},"trigger",[244,7532,1462],{"class":393},[244,7534,7535],{"class":434},"'click'",[244,7537,2723],{"class":393},[244,7539,7540,7542,7544,7546,7548,7550,7552,7555],{"class":246,"line":277},[244,7541,7490],{"class":825},[244,7543,7493],{"class":393},[244,7545,3379],{"class":825},[244,7547,7498],{"class":393},[244,7549,7501],{"class":825},[244,7551,1462],{"class":393},[244,7553,7554],{"class":434},"'1'",[244,7556,2723],{"class":393},[244,7558,7559],{"class":246,"line":320},[244,7560,3900],{"class":393},[10,7562,7563,7567],{},[13,7564,492,7565,510],{},[494,7566,496],{},[512,7568,7569,7578,7585],{},[27,7570,2369,7571,684,7574,7577],{},[167,7572,7573],{},"pump()",[167,7575,7576],{},"pumpAndSettle()"," 控制帧推进，是测试异步 UI 的关键",[27,7579,7580,7581,7584],{},"SwiftUI 的测试生态还不成熟，",[167,7582,7583],{},"ViewInspector"," 是社区库",[27,7586,7587,7588,2956,7591],{},"Compose 的测试基于语义树（semantics），需要正确设置 ",[167,7589,7590],{},"contentDescription",[167,7592,7593],{},"testTag",[10,7595,7596,7600],{},[13,7597,506,7598,510],{},[494,7599,509],{},[512,7601,7602,7611,7618],{},[27,7603,2156,7604,7607,7608,7610],{},[167,7605,7606],{},"pumpWidget"," 后没有 ",[167,7609,7573],{}," 导致状态变化未反映到 UI",[27,7612,7613,7614,7617],{},"Vue: 异步操作后没有 ",[167,7615,7616],{},"await nextTick()"," 导致断言时 DOM 还没更新",[27,7619,7620,7621,7624],{},"Android: Espresso 在异步操作时需要 ",[167,7622,7623],{},"IdlingResource","，否则测试不稳定",[10,7626,7627,7631],{},[13,7628,546,7629,510],{},[494,7630,549],{},[512,7632,7633,7636],{},[27,7634,7635],{},"Flutter 的三种测试有什么区别？（Unit: 纯逻辑，Widget: 单组件渲染，Integration: 真设备完整流程）",[27,7637,7638],{},"如何测试异步操作？各平台的方案？（Flutter: pump, Swift: async test + expectation, Kotlin: runTest, Vue: nextTick\u002Fflush-promises）",[17,7640],{},[107,7642,7644],{"id":7643},"_124-cicd-与发布","12.4 CI\u002FCD 与发布",[112,7646,7647,7661],{},[115,7648,7649],{},[118,7650,7651,7653,7655,7657,7659],{},[121,7652,123],{},[121,7654,2018],{},[121,7656,4078],{},[121,7658,4081],{},[121,7660,3038],{},[137,7662,7663,7680,7697,7714],{},[118,7664,7665,7668,7671,7674,7677],{},[142,7666,7667],{},"CI\u002FCD",[142,7669,7670],{},"GitHub Actions \u002F Codemagic",[142,7672,7673],{},"Xcode Cloud \u002F Fastlane",[142,7675,7676],{},"GitHub Actions \u002F Bitrise",[142,7678,7679],{},"Vercel \u002F Netlify \u002F GitHub Pages",[118,7681,7682,7685,7688,7691,7694],{},[142,7683,7684],{},"发布渠道",[142,7686,7687],{},"App Store + Google Play",[142,7689,7690],{},"App Store (TestFlight)",[142,7692,7693],{},"Google Play (内部测试轨道)",[142,7695,7696],{},"CDN 部署",[118,7698,7699,7702,7705,7708,7711],{},[142,7700,7701],{},"签名",[142,7703,7704],{},"Android: keystore, iOS: certificate + provisioning",[142,7706,7707],{},"Certificate + Provisioning Profile",[142,7709,7710],{},"keystore",[142,7712,7713],{},"无需签名",[118,7715,7716,7719,7722,7725,7728],{},[142,7717,7718],{},"热更新",[142,7720,7721],{},"Shorebird (实验性)",[142,7723,7724],{},"不允许 (App Store 政策)",[142,7726,7727],{},"不允许 (Play Store 政策)",[142,7729,7730],{},"直接部署新版本",[10,7732,7733,7737],{},[13,7734,546,7735,510],{},[494,7736,549],{},[512,7738,7739,7742,7745],{},[27,7740,7741],{},"Flutter 的 Release 模式和 Debug 模式的区别？（Debug: JIT + assert + DevTools, Release: AOT + tree-shaking + 无 assert）",[27,7743,7744],{},"iOS 的 Code Signing 流程？（开发者证书 + App ID + Provisioning Profile 三件套）",[27,7746,7747],{},"Vue 的 Vite 构建做了哪些优化？（Tree-shaking、Code Splitting、CSS 提取、预构建依赖）",[17,7749],{},[20,7751,7753],{"id":7752},"附录四平台速查表","附录：四平台速查表",[112,7755,7756,7774],{},[115,7757,7758],{},[118,7759,7760,7763,7765,7768,7771],{},[121,7761,7762],{},"你想做...",[121,7764,2018],{},[121,7766,7767],{},"iOS (Swift)",[121,7769,7770],{},"Android (Kotlin)",[121,7772,7773],{},"Vue 3 (TS)",[137,7775,7776,7797,7818,7842,7867,7881,7894,7909,7923,7939,7956,7975],{},[118,7777,7778,7781,7786,7789,7792],{},[142,7779,7780],{},"创建项目",[142,7782,7783],{},[167,7784,7785],{},"flutter create",[142,7787,7788],{},"Xcode New Project",[142,7790,7791],{},"Android Studio New Project",[142,7793,7794],{},[167,7795,7796],{},"npm create vue@latest",[118,7798,7799,7802,7807,7810,7813],{},[142,7800,7801],{},"运行项目",[142,7803,7804],{},[167,7805,7806],{},"flutter run",[142,7808,7809],{},"Cmd+R (Xcode)",[142,7811,7812],{},"Run (Android Studio)",[142,7814,7815],{},[167,7816,7817],{},"npm run dev",[118,7819,7820,7823,7828,7834,7837],{},[142,7821,7822],{},"安装依赖",[142,7824,7825],{},[167,7826,7827],{},"flutter pub get",[142,7829,7830,7831],{},"SPM resolve \u002F ",[167,7832,7833],{},"pod install",[142,7835,7836],{},"Gradle Sync",[142,7838,7839],{},[167,7840,7841],{},"npm install",[118,7843,7844,7847,7854,7858,7863],{},[142,7845,7846],{},"状态变量",[142,7848,7849,684,7851],{},[167,7850,2443],{},[167,7852,7853],{},".obs",[142,7855,7856],{},[167,7857,2448],{},[142,7859,7860],{},[167,7861,7862],{},"mutableStateOf",[142,7864,7865],{},[167,7866,2458],{},[118,7868,7869,7872,7874,7876,7879],{},[142,7870,7871],{},"全局状态",[142,7873,2876],{},[142,7875,2881],{},[142,7877,7878],{},"ViewModel + Hilt",[142,7880,2890],{},[118,7882,7883,7886,7888,7890,7892],{},[142,7884,7885],{},"网络请求",[142,7887,4096],{},[142,7889,4101],{},[142,7891,4107],{},[142,7893,4113],{},[118,7895,7896,7899,7902,7904,7907],{},[142,7897,7898],{},"路由跳转",[142,7900,7901],{},"GoRouter \u002F Navigator",[142,7903,3535],{},[142,7905,7906],{},"NavHost",[142,7908,3544],{},[118,7910,7911,7914,7916,7918,7921],{},[142,7912,7913],{},"本地存储",[142,7915,4735],{},[142,7917,4738],{},[142,7919,7920],{},"DataStore",[142,7922,4744],{},[118,7924,7925,7928,7931,7934,7936],{},[142,7926,7927],{},"数据库",[142,7929,7930],{},"Drift \u002F sqflite",[142,7932,7933],{},"SwiftData \u002F Core Data",[142,7935,4766],{},[142,7937,7938],{},"IndexedDB",[118,7940,7941,7944,7946,7949,7951],{},[142,7942,7943],{},"列表组件",[142,7945,2372],{},[142,7947,7948],{},"LazyVStack \u002F List",[142,7950,2393],{},[142,7952,7953,7955],{},[167,7954,2361],{}," + 虚拟列表",[118,7957,7958,7961,7966,7969,7972],{},[142,7959,7960],{},"格式化",[142,7962,7963],{},[167,7964,7965],{},"dart format",[142,7967,7968],{},"Xcode 格式化",[142,7970,7971],{},"ktlint \u002F detekt",[142,7973,7974],{},"ESLint + Prettier",[118,7976,7977,7980,7985,7988,7993],{},[142,7978,7979],{},"运行测试",[142,7981,7982],{},[167,7983,7984],{},"flutter test",[142,7986,7987],{},"Cmd+U (Xcode)",[142,7989,7990],{},[167,7991,7992],{},".\u002Fgradlew test",[142,7994,7995],{},[167,7996,7997],{},"npm run test",[7999,8000,8001],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":240,"searchDepth":253,"depth":253,"links":8003},[8004,8005,8012,8018,8023,8027,8031,8036,8040,8045,8050,8054,8059,8065],{"id":22,"depth":253,"text":22},{"id":104,"depth":253,"text":105,"children":8006},[8007,8008,8009,8010,8011],{"id":109,"depth":259,"text":110},{"id":232,"depth":259,"text":233},{"id":569,"depth":259,"text":570},{"id":972,"depth":259,"text":973},{"id":1206,"depth":259,"text":1207},{"id":1584,"depth":253,"text":1585,"children":8013},[8014,8015,8016,8017],{"id":1588,"depth":259,"text":1589},{"id":1704,"depth":259,"text":1705},{"id":2005,"depth":259,"text":2006},{"id":2191,"depth":259,"text":2192},{"id":2406,"depth":253,"text":2407,"children":8019},[8020,8021,8022],{"id":2410,"depth":259,"text":2411},{"id":2848,"depth":259,"text":2849},{"id":3020,"depth":259,"text":3021},{"id":3143,"depth":253,"text":3144,"children":8024},[8025,8026],{"id":3147,"depth":259,"text":3148},{"id":3372,"depth":259,"text":3373},{"id":3498,"depth":253,"text":3499,"children":8028},[8029,8030],{"id":3502,"depth":259,"text":3503},{"id":3672,"depth":259,"text":3673},{"id":4060,"depth":253,"text":4061,"children":8032},[8033,8034,8035],{"id":4064,"depth":259,"text":4065},{"id":4202,"depth":259,"text":4203},{"id":4532,"depth":259,"text":4533},{"id":4704,"depth":253,"text":4705,"children":8037},[8038,8039],{"id":4708,"depth":259,"text":4709},{"id":4806,"depth":259,"text":4807},{"id":5022,"depth":253,"text":5023,"children":8041},[8042,8043,8044],{"id":5026,"depth":259,"text":5027},{"id":5163,"depth":259,"text":5164},{"id":5560,"depth":259,"text":5561},{"id":5725,"depth":253,"text":5726,"children":8046},[8047,8048,8049],{"id":5729,"depth":259,"text":5730},{"id":5833,"depth":259,"text":5834},{"id":5885,"depth":259,"text":5886},{"id":6096,"depth":253,"text":6097,"children":8051},[8052,8053],{"id":6100,"depth":259,"text":6101},{"id":6197,"depth":259,"text":6198},{"id":6371,"depth":253,"text":6372,"children":8055},[8056,8057,8058],{"id":6375,"depth":259,"text":6376},{"id":6853,"depth":259,"text":6854},{"id":6997,"depth":259,"text":6998},{"id":7071,"depth":253,"text":7072,"children":8060},[8061,8062,8063,8064],{"id":7075,"depth":259,"text":7076},{"id":7186,"depth":259,"text":7187},{"id":7306,"depth":259,"text":7307},{"id":7643,"depth":259,"text":7644},{"id":7752,"depth":253,"text":7753},null,"2026-05-03 10:00:00 CST","以同一主题为轴，横向对比四个平台的基础、重难点、易错点与面试高频考点。","md",{},"\u002Fnotes\u002F2026-05-03-cross-platform-comparison",{"title":5,"description":8068},"横向对比 Flutter、iOS、Android、Vue 四个平台的语言基础、UI 构建、状态管理、路由导航等核心主题。","Flutter \u002F iOS \u002F Android \u002F Vue 四平台横向对比｜个人笔记","cross-platform-comparison","notes\u002F2026-05-03-cross-platform-comparison","H2Emwbp-SKgHbW9rE6ITAecIJ7mXiIiCKZR2c5L8ecY",1778291670698]