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); // ✅ 同一文件内可以访问!
}

延迟初始化变量: 两个主要场景

  1. 延迟初始化
Dartclass UserService {
  late Database db; // 不在构造时初始化

  void init() {
    db = Database.connect(); // 稍后初始化
  }
}
  1. 延迟计算(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 用于为已有类型扩展方法和计算属性,不能加字段,也不能扩展类本身

并发