0 Background
最近刚入职新组,后续工作的要求需要用到 Flutter 技术栈,学习过程中在此会记录一些笔记和心得。然后特别要说明的是,本人之前的语言技术栈主要涉及 Objective-C、 Swift、 Python 这些,可能一些技术思维会偏 iOS 一点,比如一些特性的对比和类比,如果你也是类似经历,希望可以帮助到你。
这篇主要先记录 Dart 语言基础。因为 Flutter 归根到底是 Dart 在承载业务逻辑和 UI 描述,所以如果 Dart 的一些核心概念没有建立起来,后面看 Flutter 代码时会比较吃力。
1 Dart Basic Grammar
变量
可以用下面这种方式声明变量
var name = 'Bob'; // 隐式推断
int x = 10; // 显式声明
乍看会以为 Dart 是弱类型、强动态,但实际上 Dart 是强类型语言,不支持隐式类型转换。 它是静态声明 + 类型推断
int x = 10; // 显式声明类型
var y = 20; // 推断为 int,类型固定
y = "hello"; // ❌ 编译错误
可空类型声明:
String name = "Alice"; // 不可为 null
String? nickname = null; // 可空类型,需显式声明
这个概念和 Swift 很像,安全使用都需要解包或者?.链式调用,然后这里可空类型的默认值是 null。
dynamic 和 Object
- Object 是 Dart 所有对象的根基类,也就是说所有类型都是 Object 的子类,所以任何类型的数据都可以赋值给Object声明的对象.
- dynamic 与 var 一样都是关键词,声明的变量可以赋值任意对象. 而 dynamic 与 Object 相同之处在于,他们声明的变量可以在后期改变赋值类型.
Object a = 42;
Object b = "hello";
// ❌ 编译报错:Object 没有 .length 属性
print(b.length);
// ✅ 需要先类型检查或强制转换
if (b is String) {
print(b.length); // 智能转型,OK
}
print((b as String).length); // 强制转换,OK
dynamic a = 42;
dynamic b = "hello";
// ✅ 编译通过,运行时才检查
print(b.length); // 5,正常
print(b.substring(1)); // "ello",正常
a = "now a string"; // 随时改变类型,也OK
print(a.length); // 12
Dart 只有 public 和 private 权限,没有提供关键字声明,默认 public,想要实现 private,可以在变量名前加一个_
需要注意:Dart 的 private 是库级别(library),不是类级别
class MyClass {
String name = 'public'; // public
String _secret = 'private'; // private
void show() {} // public
void _init() {} // private
}
// 同一个 .Dart 文件内,即使在类外也能访问 _
class Foo {
String _name = 'Dart';
}
void main() {
final f = Foo();
print(f._name); // ✅ 同一文件内可以访问!
}
延迟初始化变量: 两个主要场景
- 延迟初始化
Dartclass UserService {
late Database db; // 不在构造时初始化
void init() {
db = Database.connect(); // 稍后初始化
}
}
- 延迟计算(lazy initialization)
Dartlate String heavyData = _loadFromDisk(); // 只在第一次访问时才执行
这里 _loadFromDisk() 不会在声明时执行,而是第一次读取 heavyData 时才调用。
通配符 _
通配符变量 _ 声明了一个不作为可用变量的局部变量或参数,本质上它是一个占位符。如果有声明初始化的方法,方法仍会被执行,但通配符变量的值不可访问。同一命名空间中可以有多个名为 _ 的声明,而不会引发冲突错误
说人话就是,可以把这个当成是一个垃圾桶变量,相当于一个没有用&不可访问的临时量,这点和 Swift 的设计不谋而合。
然后有个关键区别是 Dart 中没有 @discardableResult, 默认就允许忽略任何返回值.
相应的,如果需要强制使用返回值,可以使用@useResult 声明。
@useResult
int importantValue() => 42;
importantValue(); // ⚠️ 分析器警告:返回值未被使用
类型
Dart 的核心数据类型
int // 整数 64位
double // 浮点数
String // 字符串,UTF-16
bool // true / false
List // 数组
Map // 键值对
Set // 不重复集合
Null // null
对,你没有看错,Dart 没有 float 类型!
Records
这个数据结构类似于 Swift 的元组,可以把多个值打包在一起,一般在函数想轻量级返回多值时使用。
var point = (10, 20);
print(point.$1); // 10(位置字段用 $1, $2...)
print(point.$2); // 20
var person = (name: 'Alice', age: 18);
print(person.name); // Alice(命名字段直接用名字)
print(person.age); // 18
// ✅ 简洁地返回多个值
(String name, int age) fetchUser() {
return (name: 'Alice', age: 18);
}
void main() {
var user = fetchUser();
print(user.name); // Alice
print(user.age); // 18
}
字符串拼接 字符串拼接:Dart字符串拼接有多种方式
void main() {
String s1 = "First string.....";
String s2 = " Second string" + "ddd";
print(s1 + s2);
// 输出:First string..... Second stringddd
// 相邻字符串自动拼接
String str2 = '单引号符串'
'换行了'
'再加空格'
'拼接';
print("str2: $str2");
// 输出:str2: 单引号符串换行了再加空格拼接
// 单双引号混合拼接
String str3 = "单双引号空格字符串" '拼接' "~";
print("str3: $str3");
// 输出:str3: 单双引号空格字符串拼接~
// 使用 \n 实现真正换行
String str4 = "单双引号符串\n"
'换行了'
'再加空格'
'拼接';
print("str4: $str4");
// 输出:
// str4: 单双引号符串
// 换行了再加空格拼接
}
模式匹配
也就是 switch 语法,Dart 中的 switch 不限于enum,可以发散到几乎任意类型。
// int / String / double / bool / Object / Record ...
switch (score) {
case >= 90 => 'A',
case >= 80 => 'B',
case >= 60 => 'C',
_ => 'F',
};
switch (obj) {
case int n => 'int: $n',
case String s => 'string: $s',
_ => 'other',
};
基础模式
类型模式
switch (obj) {
case int n: print('int: $n');
case String s: print('string: $s');
}
Record 模式
switch (point) {
case (x: 0, y: 0): print('原点');
case (x: var x, y: var y): print('点: $x, $y');
}
List 模式
switch (list) {
case []: print('空');
case [var x]: print('只有一个: $x');
case [var first, ...]: print('第一个: $first'); // ... 表示剩余元素
}
Map 模式
switch (json) {
case {'name': String name, 'age': int age}:
print('$name, $age');
}
Class 对象模式
switch (shape) {
case Circle(radius: var r): print('圆,半径 $r');
case Rectangle(w: var w, h: var h): print('矩形 $w x $h');
}
Guard 条件(when)
switch (point) {
case (var x, var y) when x == y:
print('在对角线上');
case (var x, var y):
print('普通点: $x, $y');
}
if-case(局部模式匹配)
if (value case (name: String name, age: int age) when age >= 18) {
print('成年人: $name');
}
穷举检查
| 类型 | 穷举检查 |
|---|---|
| int / String / Object 等 | ❌ 需要 _ |
| bool | ✅ true/false 覆盖即可 |
| enum | ✅ 漏 case 编译报错 |
| sealed class | ✅ 漏 case 编译报错 |
sealed class
sealed class 是 Dart 3.0 引入的修饰符,核心作用:
- 限制继承范围:只能在同一文件内继承
- 穷举检查:配合 switch,漏掉子类编译器报错
- 不能实例化
// shape.Dart
sealed class Shape {}
class Circle extends Shape { final double radius; Circle(this.radius); }
class Rectangle extends Shape { final double w, h; Rectangle(this.w, this.h); }
class Triangle extends Shape { final double base, height; Triangle(this.base, this.height); }
// other.Dart
class Pentagon extends Shape {} // ❌ 编译报错!
穷举检查示例
double area(Shape shape) => switch (shape) {
Circle(:var radius) => 3.14 * radius * radius,
Rectangle(:var w, :var h) => w * h,
Triangle(:var base, :var height) => base * height / 2,
// 漏掉任何一个 → 编译报错 ✅
};
实际应用:网络请求结果
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Failure<T> extends Result<T> {
final String message;
Failure(this.message);
}
class Loading<T> extends Result<T> {}
// 使用
void handleResult(Result<User> result) {
switch (result) {
case Success(:var data): print('成功: ${data.name}');
case Failure(:var message): print('失败: $message');
case Loading(): print('加载中...');
// 漏掉任何一个 → 编译报错 ✅
}
}
实际使用下来这个概念几乎和 Swift 的枚举关联值一模一样,可以通过"枚举传值",定义不同case的表现。
控制流
Dart 的控制流比较简单,基本上和现代流行编程语言语法一致,这里不过过多记录,简单记了一下常见用法。
循环
for 循环
var list = [1, 2, 3, 'Dart', true];
//forEach
list.forEach((element) {
print(element);
});
//for..in
for (var item in list) {
print(item);
}
for (var i=0; i<list.length; i++) {
print(list[i]);
}
do-while 循环
var i =0;
var sum = 10;
while(i<10){
sum+=i;
i++;
}
do{
sum+=i;
i++;
}while(i<10);
分支
还是 if / else if / else 那些,没有变化
if (xxx) {
}else if(xxx){
} else {
}
错误处理
处理流程和 Swift 一样,都是 try - throw - which exception - catch - finally 但是 Dart 的错误处理似乎更灵活,甚至可以在任意位置 throw,暂时想不到这个应用场景是什么。
try / on / catch / finally
try {
final file = File('data.json').readAsStringSync();
final json = jsonDecode(file);
} on FormatException catch (e) {
// 类型匹配 + 获取异常对象
print('格式错误: $e');
} on IOException {
// 只匹配类型,不需要异常对象
print('IO 错误');
} catch (e, s) {
// 捕获所有其他异常,s 是 StackTrace
print('未知错误: $e\n$s');
} finally {
print('总是执行,适合释放资源');
}
throw 与 rethrow
// 抛出异常
throw FormatException('Invalid input');
// 抛出任意对象(不推荐,但合法)
throw '出错了';
// rethrow:在 catch 中重新抛出,保留原始 StackTrace
try {
riskyOperation();
} catch (e) {
log(e);
rethrow; // 不是 throw e,rethrow 保留堆栈
}
assert
assert(text != null); // 调试时检查
assert(number >= 0, 'number must be ≥ 0'); // 附带消息
需要注意的是,Dart 中的 assert 只在 debug 模式有效,flutter run –release 中完全忽略。
2 Dart Core Features
函数
Dart 的函数基础语法
返回类型 函数名(参数类型 参数名) { 方法体 }
e.g.
int addSome(int num1, int num2) {
return num1 + num2;
}
和 Swift 函数不一样,Dart 中参数名在调用时是默认缺省的,如果需要加上命名参数,需要通过 {} 框起来,类似上面这个简单的函数。如果需要显示指定参数名,需要通过以下这种方式:
int addSomeNeedName({ int? num1, required int num2, int num3 = 0}) {
return num1 ?? 0 + num2;
}
int addSome(int num1, int num2) {
return num1 + num2;
}
void main() {
addSome(1, 2);
print(addSomeNeedName(num2:1));
addSomeNeedName(num1: null, num2: 1);
}
需要注意的是 在 Dart 中,{} 里的命名参数;它们默认可选。只有你希望调用方必须传时,才加 required。 可以给 默认值 或者 用 ?修饰 表示可空。
然后普通函数也支持类似特性, 但需要用 [] 修饰来标识默认值
int addSome(int? num1, [int num2 = 2, int? num3]) {
return num1 ?? 0 + num2;
}
void main() {
addSome(1, 2);
addSome(0);
addSome(null, 1);
addSome(0, 1);
addSome(0, 1, 2);
}
Dart 中不支持重载,这点是和 Swift 很大的不同,如果想这样做,只能通过命名参数 / 重命名来实现。
箭头函数 当函数语句只有一行时,可以用=>连接参数列表和函数体,省略大括号和return关键字。
int multiply(int a, int b) => a * b;
闭包 类似的,Dart 中也支持闭包,实际上也是可以像 Swift 那样实现链式调用,但由于语法上的差异,感觉不如 Swift 里的链式调用那样直观,不知道后续项目里实际使用是怎样的。 然后功能上也简单一点,一样的支持捕获外部变量,支持作为入参传入,延迟执行(真正被调用时才生效)。
typedef Validator = String Function(String);
Validator makeValidator(int min, bool mustContainNumber) {
return (String text) {
if (text.length < min) {
return '长度不能小于 $min';
}
if (mustContainNumber && !text.contains(RegExp(r'\d'))) {
return '必须包含数字';
}
return '合法';
};
}
void escape(String param1, Validator validator) {
print(validator(param1));
}
void main() {
escape("12345", (text) {
return makeValidator(6, true)(text);
});
escape("765454", makeValidator(6, true));
}
需要特别说明的是,Dart 函数中的参数传递都是值传递,所以
- 在函数里给参数重新赋值,不会影响外面变量本身
- 但如果参数指向一个对象,在函数里改对象内容,外面能相应发生修改
类、继承、抽象类
走到类这个章节,会感觉语法差异越来越大,频繁的看到示例里的 this.xx, abstract 关键字,也是感觉相当面向对象了,这点和 Swift 完全不一样,虽然都有类,但 Swift 推崇面向协议编程,相当一部分程度上弱化了这个概念。 但还好,也并不复杂,如果你曾经接触过C++ / Java,这部分内容也应该很好接受。
- 类(class):定义对象的属性和行为
- 继承(extends):复用父类实现
- 抽象类(abstract class):不能直接实例化,通常用来约束子类
- 接口(interface):Dart 里每个类天然都是接口
类
你可以在类里定义属性、方法,这没什么好说的,值得一提的是,除了标准化的构造方法外,Dart 还支持自己命名 / 重定向构造参数,也可以通过 factory 关键字实现工厂构造来达成类似效果。 这一点有点像 Swift 里的便捷构造器 (Convenience Init)
class Point {
double x, y;
Point(this.x, this.y);
Point.alongXAxis(double x) : this(x, 0);
Point.convineience(): x = 1.0, y = 2.0;
factory Point.fromJson(Map<String, dynamic> json) {
return Point(
json['anchor_x'] as double,
json['anchor_y'] as double,
);
}
}
void main() {
var _ = Point.alongXAxis(2);
var _ = Point.convineience();
var _ = Point.fromJson({'anchor_x': 1.0, "anchor_y": 2.0});
}
继承: extends
Dart 是 单继承,一个类只能 extends 一个父类。支持方法重写,使用 override 关键字标注就行。
class Animal {
void sound() {
print('animal sound');
}
}
class Dog extends Animal {
@override
void sound() {
super.sound();
print('wang wang');
}
}
然后父类有构造参数时,子类需要相应的传给父类。
class Animal {
final String name;
Animal(this.name);
}
class Dog extends Animal {
final int age;
Dog(super.name, this.age);
}
抽象类: abstract class
通过 abstract 关键字声明,定义一组相关类的接口规范,提供部分实现和部分抽象方法,然后不能被直接实例化,必须被子类继承后才能使用。
关键特征:
| 特征 | 说明 |
|---|---|
abstract 关键字 |
声明一个类为抽象类 |
| 不可实例化 | var obj = ClassName(); ❌ 编译错误 |
| 可被继承 | class Child extends AbstractClass ✅ |
| 可被实现 | class Child implements AbstractClass ✅ |
一个类实现某个抽象类,需要重写全部属性、方法。 但在继承的时候可以选择性重写,可以复用一部分父类的实现。
abstract class Shape {
late String name; // 属性
// 抽象 getter
double get area;
// 抽象 setter
set color(String c);
// 具体方法
void describe() {
print('这是一个 $name');
}
}
class Circle extends Shape {
double radius;
Circle(this.radius, String shapeName) {
name = shapeName;
}
@override
double get area => 3.14 * radius * radius;
@override
set color(String c) {
print('圆形颜色设置为: $c');
}
}
// 作为接口使用
abstract class Drawable {
void draw();
}
// 多实现
class Square implements Shape, Drawable {
@override
double get area => 0;
@override
set color(String c) {}
@override
void draw() {
print('绘制正方形');
}
}
void main() {
Circle circle = Circle(5, '圆形');
print('面积: ${circle.area}'); // 输出: 面积: 78.5
circle.describe(); // 输出: 这是一个 圆形
circle.color = '红色'; // 输出: 圆形颜色设置为: 红色
}
在 Dart 中,任何类都可以作为接口(通过 implements),但抽象类更适合:
- 共享实现代码
- 定义相关方法组合
mixin 和 extension
mixin
mixin 用于为类“混入”一组方法和属性,实现代码复用,解决 Dart 只支持单继承的问题。
特点:
- 可以包含方法、getter/setter、字段(属性)
- 不能被直接实例化
- 通过
with关键字混入到类中
示例:
mixin Logger {
void log(String msg) => print('[LOG] $msg');
}
class Service with Logger {
void fetch() {
log('fetching...');
}
}
多个 mixin 可以链式混入:class Foo with MixinA, MixinB {}
extension
extension 用于为已有类型扩展方法、getter、setter(即“行为”),但不能扩展类本身,也不能添加实例字段。
特点:
- 不能修改原类型定义
- 不能为类型新增存储属性(字段)
- 只能添加方法、getter、setter(计算属性)
- 常用于为系统类型或第三方类型补充便捷 API
示例:
extension StringX on String {
bool get isBlank => trim().isEmpty;
String ellipsis(int max) => length <= max ? this : substring(0, max) + '...';
}
void main() {
print(' '.isBlank); // true
print('hello world'.ellipsis(5)); // hello...
}
注意:extension 不能扩展“类”本身(比如不能让 String 继承新父类),只能扩展方法/属性。
一句话总结:
mixin用于为类混入方法和属性,实现代码复用extension用于为已有类型扩展方法和计算属性,不能加字段,也不能扩展类本身
...