ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter for OpenHarmony开发前,先掌握这些Dart基础语法

Flutter for OpenHarmony开发前,先掌握这些Dart基础语法 我刚入坑 Flutter for OpenHarmony 时第一反应是去找各种 Widget 和状态管理的教程结果看了一堆组件框架回到自己的代码里还是觉得别扭。后来才意识到问题的根源Dart 基础没打牢。很多人和我一样容易跳过语言基础直接冲 UI等到需要写复杂逻辑、接入服务端数据时各种语法细节全变成了坑。所以这篇笔记打算先聊聊学习 Flutter for OpenHarmony 之前必须搞定的 Dart 基础语法用实战的角度过一遍我实际开发中经常用到的部分算是“上篇”主要覆盖变量、类型、流程控制、函数和集合这些日常写 Widget 和页面逻辑时绕不开的内容。这个内容适合两类读者一类是完全没碰过 Dart、打算以 Flutter 在 OpenHarmony 平台上做应用开发的新手另一类是写过别的语言、想快速对比 Dart 和 Java/C/Python 差异的转岗朋友。我会尽量少讲理论黑话多给可以直接跑的代码和我在实际项目里踩过的坑。1. 先铺垫为什么 Flutter for OpenHarmony 的开发者可以绕开很多东西但绕不开 Dart先说个大前提。Flutter 是 UI 框架而 Dart 是它唯一的应用开发语言。你用 Flutter 写界面、写业务逻辑、处理平台通道所有代码都是在 Dart 环境下跑起来的。OpenHarmony 上的 Flutter 适配工作也一样适配层虽然涉及 C 和 Java但应用开发者天天接触的永远是 Dart 那套东西。所以别想着“我会 JavaDart 随便看看就行”字符串处理、集合操作、异步回调这些东西写起来风格差别很大Java 的经验能帮助你理解概念但写出来的代码还是要符合 Dart 的习惯。另外OpenHarmony 平台的 API 风格和 Android 不完全一样但 Flutter 这层做了封装。大多数时候你只需要关心 Dart 层的数据怎么组织、怎么传给原生端以及原生端返回的数据怎么解析。如果 Dart 基础不牢连自定义 Model 类、处理 JSON 列表都费劲后面接 camera、传感器这些插件时会很痛苦。所以我的建议是无论你多急着想跑出一个 OpenHarmony 上的 Flutter Demo都先花两三天把 Dart 语法过一遍这比折腾环境配置划算得多。1.1 Flutter 为什么选了 Dart 而不是别的语言Flutter 选 Dart不是拍脑袋主要是看中它的高性能 AOT 编译加灵活的 JIT 热重载。AOT 编译在 OpenHarmony 上发布的时候很重要首帧渲染和 UI 操作的实时性都能保证开发时 JIT 又让改动能秒级出现在模拟器和真机上这是很多其他跨平台方案给不了的体验。Dart 是单线程模型配合事件循环来处理异步意味着你写 UI 逻辑时不用像传统 Java 那样处处担心多线程共享变量的锁问题这对新手来说很友好。还有一个点可能容易被忽略Dart 的语法吸收了 C 和 Java 的骨架又揉进了不少函数式语言的习惯。你会看到一切皆对象、支持闭包、支持类型推断这和 Flutter 里 Widget 树一层层嵌套的写法配合得非常默契。比如 Column 里放多个子组件每个子组件就是一个表达式这种“万物返回 Widget”的习惯本质上就是 Dart 的表达式化风格的延续。1.2 学习路径先语法还是先 Widget我跟人聊 Flutter for OpenHarmony经常被问“是不是应该先看 component 怎么用”。我的答案是Widget 可以边用边学但 Dart 基础必须在写第一个复杂页面之前过一遍。因为像列表加载、下拉刷新、数据缓存、插件调用这些常见需求全部要跟 Dart 语法打交道。如果你不理解 named parameter 和 list spread很多官方示例代码你看得懂但不会自己写。我给自己定的路径是Dart 基础语法变量、集合、函数、异常→ Flutter 基础布局 → 异步和 Future → 再用 Flutter for OpenHarmony 跑真机。前面五到七天看着慢但后面写页面反而快。这篇笔记就是我对这条路径上第一步的整理。2. 环境安装与第一个 Dart 小程序用最直接的方式感知语法Dart 的语法如果光看不练过两天就忘。我建议第一件事不是去看大段的语法文档而是先跑一个能输出结果的小程序。在这一节我会分享怎么把 Dart 运行环境搭起来以及几个常被忽略的细节。2.1 安装 Dart SDK选对版本在 OpenHarmony 开发中你通常是用 DevEco Studio 或通过命令行来跑 Flutter 项目但为了单独练 Dart 语法最好单独装一个 Dart SDK。打开 dart.dev 下载对应你操作系统的 SDK 压缩包解压后把bin目录加进系统 PATH。如果你本机已经装了 Flutter SDK那 Flutter 自带了一个 Dart在终端输入dart --version能看到版本一般不用再额外装。需要注意的一点是Flutter for OpenHarmony 的适配分支对 Dart 版本有要求别把系统的 Dart 装得比 Flutter 自带的还新否则容易出现版本不一致导致的包解析问题。我一般直接用 Flutter 自带的 Dart让它跟着 Flutter 版本走。装好之后可以用dart --version验证。如果输出版本号说明 SDK 可用了。接下来准备编辑器。我用的 VSCode装两个插件就行Dart和Flutter。新建一个目录比如dart_basics在里面创建一个hello.dart文件。void main() { print(Hello OpenHarmony, from Dart); }在 VSCode 里直接按 F5 调试或者打开终端输入dart hello.dart屏幕上就会打印出这行字。我头一次跑的时候最不习惯的是main()函数前面没有public static void这一堆修饰词干净利落。Dart 的入口函数就叫main()顶层函数不需要类包裹这一点和 C 有点像但比 Java 轻量多了。2.2 用 DartPad 做语法小实验如果你连 SDK 都懒得装也可以直接打开 dartpad.dev 在浏览器里写 Dart。DartPad 支持大部分标准库可以运行、可以调试控制台输出对练习语法足够了。我在学习阶段会刻意把 DartPad 当成草稿纸想到什么语法就敲一段不用额外建工程。尤其是做字符串、列表、Map 操作的实验DartPad 很快。很多人忽略的一点DartPad 不支持插件和文件读写但模拟起来跟本地环境几乎一致。所以当你看到一个奇怪的报错不确定是不是编译器的问题丢进 DartPad 跑一下能帮你快速定位。比如dart_vm_initializer.cc里的 unhandled exception 报错在单纯的 SDK 环境里不常出现但如果你在 Flutter 项目里跑错误信息会包含一堆环境日志。先用 DartPad 把逻辑本身跑通再放进项目里调试会省很多时间。2.3 刚学 Dart 时最容易踩的两个环境小坑第一个坑是中文乱码。早期版本的 Dart SDK 在 Windows 控制台输出中文时经常会出现乱码因为默认编码不是 UTF-8。现在新版本基本解决了但如果你用的还是旧版本建议在终端里先执行chcp 65001切到 UTF-8 再跑。第二个坑是dart run和dart hello.dart的区别。在现代 Dart 2.x 版本中dart run hello.dart更规范因为它会按项目方式加载。直接dart hello.dart也能跑但如果你在脚手架的 Flutter 项目里使用可能会因为缺少包配置文件而出错。建议养成dart run 文件名.dart的习惯。3. 变量、类型和空安全Dart 写起来跟 Java/C 很不一样把 hello world 跑通了接下来就要正式面对语法细节。这一节我按照实际写 Flutter 业务时的频率来排序空安全排在前面因为它直接影响你定义变量和函数参数的方式。3.1var、final、const怎么选Dart 里的var不是“动态类型”而是“类型推断”。写var name Dart;之后name的类型就已经确定为String之后你不能再给它赋一个 int 值。这和 JavaScript 的let有本质区别。很多新人在这里踩坑以为var就是“随便放什么类型”。其实在 Dart 中如果你想声明一个可以放任意类型的变量要用dynamic但dynamic会放弃编译期类型检查很容易在运行时踩空我不建议在业务代码里用。选择规则很简单能确定赋值后不会变的用final。比如一个从接口读到的用户 ID只要赋值了就不改。const则是编译期常量必须能在编译时就确定值比如固定的列表、字符串、颜色值。在 Flutter 的 Widget 构造中经常能看到const Text(...)这就是用const来让这个 Text 对象被复用减少构建开销。我写代码的默认习惯是优先const不行就final最后才考虑var。3.2 数据类型int、double、String、bool 的正确用法Dart 的 int 和 double 都是num的子类可以互相运算。但要注意int / int的结果是 double而不是 int。比如print(5 / 2);输出的是2.5不是2。如果要做整除要用~/运算符即5 ~/ 2输出2。这在写分页、字段截取时很常用。同样取余用%。我在计算列表分页时经常写pageIndex index ~/ pageSize而不是先转 double 再转回 int。String的处理比 Java 舒服很多。单引号双引号都能用三引号可以写多行字符串。重点是多行拼接和模板字符串int count 3; String tip 共有 $count 个待办事项; String detail 总价为${count * 10}元;$后面直接跟变量名如果需要表达式要用${}。这个语法在 Flutter 里极其常用比如写Text(当前第 $index 项)。注意如果你在字符串里想显示$符号本身要写成\$或者用转义。bool类型在 Dart 里有点严格if (非布尔值)是不允许的。不像 C 语言里if (1)是 trueDart 只能接受 bool。比如if (list.isNotEmpty)而不是if (list.length)。这个习惯越早培养越好否则容易写出类型不对的表达式。3.3 空安全Dart 区别于很多老语言的核心特性Dart 2.12 以后全面启用了空安全这意味着默认情况下普通类型的变量是不允许为 null 的。声明String name;而不赋值编译器直接报错。如果你确实需要一个可以为空的变量必须在类型后面加?String? nullableName; int? maybeAge;当你要用到这个可为空的值时还得处理 null。最简单的是加!强制断言表示“我确定这个值不为空”String name nullableName!;但断言如果失败运行时会直接崩溃所以只在你有把握的时候用。更安全的写法是?.和??String display nullableName ?? 默认用户; int length nullableName?.length ?? 0;??前面为 null 就取后面的值.?前面为 null 就整个表达式返回 null不会抛异常。刚开始我也觉得麻烦但经过几个项目之后我发现空安全设计确实帮我在联调阶段挡掉了大量空指针问题。OpenHarmony 上跑 Flutter 插件的时候原生侧返回的值经常可能为 null你在 Dart 层如果不处理页面上可能直接白屏。所以建议从现在起写每个变量都考虑“它到底能不能为空”。4. 运算符与控制流真正把逻辑写出来语法基础里控制流可能是最“无聊”的部分每个人都有一定了解Dart 的差异在细节上。但这一节我还是想用几个 Flutter 场景来带着看你会发现这些细节恰恰是写 UI 时最容易出错的点。4.1 Dart 运算符中容易忽略的细节算术运算符里、--、、-这些和 Java/C 几乎一样没什么可说的。真正值得注意的是类型比较int a 1; double b 1.0; print(a b); // false因为类型不同这在做数据转换的时候很常见。比如从 JSON 接口里解析出来的 num 值当你拿它和 int 比较时要先做类型转换num jsonValue serverResult; int pageNum jsonValue.toInt();逻辑运算符、||、!也都一样但有一点体验很好Dart 的和||会做短路求值且表达式可以返回非 bool 的值吗注意不能。Dart 严格限制条件表达式必须为 bool所以你不能写if (a b)如果 a 是 int 的话会报错。这其实是好事强制你写得更清晰。4.2 if/else 和 switch 的写法以及 Flutter 里的场景int level 3; String role; if (level 5) { role 高级; } else if (level 2) { role 中级; } else { role 初级; }这就是普通的写法没什么好说的。值得一提的是一种简洁赋值模式使用switch表达式。Dart 3 支持了 switch 表达式用起来和 Kotlin 的 when 有点接近String statusName switch (statusCode) { 0 未开始, 1 进行中, 2 已完成, _ 未知 };这个写法非常适合做映射比一串 if/else 更清爽。在 Flutter 里状态控制、页面路由、按钮状态切换经常用到。注意switch 表达式用而不是:最后必须有一个_兜底。4.3 for/while 循环集合遍历的方式写得有意思点Dart 里最常见的循环是for (var i 0; i list.length; i)当然可以但更常用的是 for-inListString items [A, B, C]; for (var item in items) { print(item); }如果你还需要索引可以用List.generate或者手动计数但更推荐用items.asMap().entriesfor (var entry in items.asMap().entries) { print(index${entry.key}, value${entry.value}); }在 Flutter 的 ListView.builder 里你往往是根据 index 去取数据所以 for 循环的使用频率不算太高但你在处理后台任务、计算分页时循环还是躲不开。一个容易踩的坑是在 for 循环里直接调用异步函数结果可能和你预期不一样因为异步回调不会阻塞循环。这个后面讲 Future 时会专门说这里先记住不要在循环里直接 for 一个 async 函数。5. 函数Dart 的老祖宗契约和 Flutter 的构造器有莫大关系函数是 Dart 的核心因为 Flutter 里的 Widget 到处都是构造器而构造器本质上就是函数的语法糖。熟练掌握函数参数的各种写法能帮你瞬间看懂官方文档里的 Widget 构造函数。5.1 命名参数与可选参数Flutter 构造器里全是它Dart 函数的参数有两种基本形式位置参数和命名参数。位置参数就是你按顺序传参命名参数则是用{参数名: 值}的形式。命名参数在 Flutter 中无处不在比如Text的style、textAlign都是命名参数。定义函数时命名参数要写在花括号里void showInfo(String name, {int? age, String tag 默认}) { print($name-$tag); if (age ! null) { print(Age: $age); } }调用方式就是showInfo(小明, tag: 新人); showInfo(小红, age: 18);注意命名参数传参时必须写参数名不能只写值。在这个函数里age是可为空的tag有默认值。可选位置参数用方括号[]包裹但实际开发中我用得很少感觉可读性不如命名参数。另一个常见要求是如果你声明了一个参数为可选那它要么有一个默认值要么是可空类型否则编译器会报错。5.2 箭头函数不叫“一个箭头的事”Dart 的箭头函数实际上是一个表达式别名不是所有函数都能写成箭头形式。比如bool isAdult(int age) age 18;这等同于bool isAdult(int age) { return age 18; }注意箭头函数只能写一条表达式不能写多条语句。写多条语句还是用大括号。在 Flutter 里你会看到大量用法尤其是一些简单的回调比如onPressed: () Navigator.pop(context)。这个写法很紧凑但如果你想在点击时做多件事还是老老实实写大括号吧。一个常见的误解是认为箭头函数自带返回 true其实不是它返回的是箭头后面表达式的结果。5.3 函数类型变量、匿名函数和闭包理解“函数也是对象”在 Dart 中函数本身是对象类型就是Function。你可以把一个函数赋给变量也可以把函数作为参数传给另一个函数void sayHello() { print(hello); } void callTwice(Function fn) { fn(); fn(); } void main() { var myFunc sayHello; callTwice(myFunc); }匿名函数用(参数) { 函数体 }或(参数) 表达式表示。Flutter 里的事件回调、列表 item 构造回调到处都是匿名函数。比如list.forEach((item) { print(item); });闭包的概念也和匿名函数相关一个函数访问了定义它的作用域中的变量就叫闭包。Dart 里闭包很自然比如Function makeCounter() { int count 0; return () { count; return count; }; }这个count被内层函数捕获每次调用都会自增。在 Flutter 里写一些定时器、事件订阅时闭包用得很多理解它有助于避免内存泄漏。尤其是当你在 Widget 里注册了一个回调回调捕获了 BuildContext如果没处理好页面销毁后回调还会被调用导致奇怪的异常。这部分等后面讲异步时可以展开但基础概念建议现在就懂。6. 集合与字符串写页面真正天天碰的东西说实话Dart 的集合类型和 Java 的 List/Map/Set 类似但 API 风格更像 Kotlin 和 JavaScript 的混合体。这一节我会着重讲那些你在 Flutter for OpenHarmony 中几乎每天都在用的操作。6.1 List 的常用操作与展开符List 字面量Listint scores [90, 85, 78];在 Flutter 中你需要把一个数据列表变成一组 Widget这时你会在 children 里使用展开符...children: [ Text(标题), ...scoreWidgets, ]...scoreWidgets表示把 scoreWidgets 这个 List 里的每个元素插入到当前列表里。这个写法非常常用比先 addAll 再塞进去或者用expanded要直观得多。另外一个常见操作是mapListString names users.map((u) u.name).toList();map返回一个懒加载的 Iterable所以要调用.toList()才会生成新的 List。我在初学时经常忘记.toList()结果在循环里拿到一个 Iterable 也能用但一旦尝试通过索引访问就报错。现在我会在看完 map 后立刻加 toList形成条件反射。如果要对列表过滤用wherevar adults users.where((u) u.age 18).toList();第一次见 where 时可能会有点困惑其实就是保留满足条件的元素。这个操作在处理列表数据、搜索过滤时极其常用。6.2 Map 与 Set 的使用场景Map 就是键值对。Dart 的 Map 字面量语法和 JSON 非常像这也是它适合对接网络数据的原因MapString, dynamic user { name: 小明, age: 18 };注意dynamic关键字因为 JSON 值可能是任何类型。在实际解析 JSON 时最常见的操作是String userName user[name] as String; int userAge (user[age] as num).toInt();使用as做强制类型转换。如果你觉得麻烦也可以先判断类型再转但业务里这么写已经足够。Set 则是一个元素唯一的集合用来去重非常方便Listint numbers [1, 2, 2, 3, 3, 3]; Setint uniqueNumbers numbers.toSet();如果你要判断一个元素在不在集合里Set 的contains性能比 List 好很多。在写“是否已收藏”“是否已点赞”这类业务时我倾向于用 Set 来维护 ID 列表查找效率高代码也更清晰。6.3 字符串拼接、模板字符串与换行处理Dart 的字符串模板刚才提过这里补充几个细节。多行字符串可以这样写String multiLine 第一行 第二行 ;这在写一些固定的协议说明文本时很省事。如果要在字符串里包含单引号或双引号可以用相反的引号或者使用转义。比如String sentence Its a good day.; String another He said hello.;还有一种特殊字符串raw string在字符串前面加r里面的转义字符不会被处理。比如rC:\Users\name会原样输出反斜杠。在处理 Windows 路径和正则表达式时很有用。我在写正则表达式过滤用户输入时几乎都会用 raw string这样不用写一堆\\。7. 异常处理与其到处崩溃不如学会提前兜底Dart 的异常处理语法和 Java 很接近但有几个细节值得注意。7.1 try/catch/finally 的标准姿势try { int result int.parse(abc); } catch (e) { print(解析失败$e); } finally { print(无论如何都会执行); }这里int.parse遇到非数字字符串会抛FormatException。在实际项目中解析网络返回的字符串、处理文件读取、调用平台通道时很可能抛出异常不包裹的话应用直接崩。Dart 的catch还能接第二个参数StackTrace用来打印更详细的堆栈try { // ... } catch (e, st) { print($e); print(st); }如果你只需要判断异常类型可以写on子句try { // ... } on FormatException { print(格式错误); } catch (e) { print(其他错误$e); }注意on只能捕获特定类型后面还可以再跟 catch。刚开始我看到这种写法觉得语法复杂可一旦理解了“on 处理已知类型catch 处理未知类型”逻辑就清楚了。7.2 自定义异常类怎么定义你可以实现Exception接口或者继承Error。但我更常用的写法是实现Exceptionclass ApiException implements Exception { final int code; final String message; ApiException(this.code, this.message); override String toString() ApiException(code: $code, message: $message); }这个异常类可以携带状态码和消息。在对接 OpenHarmony 的后端服务时经常需要把 HTTP 状态码、业务状态码映射成这种自定义异常抛出然后在 UI 层统一捕获展示错误信息。有不少人误以为 Dart 里所有异常都必须用 try/catch 包裹。其实不是很多情况下你可以在函数声明中直接让异常向上传递只要调用方处理就行。Dart 没有检查型异常的概念所以编译器不强求你捕获。但为了项目稳定我建议在入口层做统一兜底比如 Flutter 的runZonedGuarded这个不属于基础语法但算是一个提醒学异常时不要只记语法更要想好你的项目中哪些地方需要吞掉异常、哪些地方需要上报。8. 一些让 Dart 写起来更顺手的“奇怪”小知识最后补充几个我在实战中慢慢发现的 Dart 特性不算语法主流但非常实用。8.1 集合的..级联操作符级联运算符..可以让你对同一个对象连续调用多个方法而不必重复写变量名Listint list []; list ..add(1) ..add(2) ..add(3);这个写法和 Builder 模式有点像。在 Flutter 中一些复杂的 Widget 配置、样式拼接会用到级联。但注意..作用于表达式本身不是赋值运算符。我在初始化一些测试数据时特别爱用这一招省去多次list.add的重复。8.2 记录类型与模式匹配Dart 3 引入了 record 和 pattern虽然不算“基础语法”但你在阅读较新的 Flutter 代码时可能会碰到。简单理解record 允许你把几个值打包成一个值(String, int) student (小明, 18);然后解构var (name, age) student; print(name);这个特性在函数的多个返回值场景非常好用。比如一个方法要同时返回成功状态和数据用 record 就不用再自定义一个 Result 类了。不过对新手来说别急着用先看得懂就行。8.3 可空变量的!和??的正确姿势除了前面说的??还有一个写起来很爽的??意思是“如果左边为空则把右边的值赋给它”String? cache; cache ?? default;这比写if (cache null) { cache default; }精简多了。这个操作在缓存初始化、懒加载一些单例对象时非常有用。如果说有什么使用建议别过度依赖!强行断言宁可多写一次??逻辑也不能拿页面稳定性开玩笑。我之前在某个页面中因为“确信不为空”加了个!结果切换到后台再恢复时状态被系统回收直接血崩。自那以后我对!的使用变得非常克制。这篇先整理到这里。我自己的体会是Dart 基础语法的学习是需要“用”出来的光看概念记不牢。你在看 Flutter 官方示例时每段代码都可以先拆开把变量声明、函数参数、集合操作分别标记出来再想想如果不这样写还能怎么写。等这些基础部分形成肌肉记忆后后面再去碰异步、类和泛型就会顺很多。下一篇笔记我打算接着整理 Dart 的面向对象特性以及如何在 Flutter 中定义自己的 Model 类先把基础这块补完整再说。
返回列表