cv.cm

編碼 · 程式語言

Dart 入門教學

閱讀約 9 分鐘

簡答

Dart 是 Google 推出的靜態型別物件導向語言,以 Flutter 應用程式框架使用的語言最為人熟知。它具備健全的空值安全,可編譯成原生程式碼、JavaScript 或 WebAssembly,開發時還有快速的熱重載。本頁依序介紹環境設定、型別、空值安全、流程控制、函式、集合、類別、非同步程式碼與錯誤處理。

Dart 是什麼、可以拿來做什麼

Dart 是 Google 開發、針對用戶端最佳化的程式語言。它是具備型別推斷的靜態型別語言,擁有健全的空值安全,既能編譯成原生機器碼(用於行動裝置、桌面與伺服器),也能編譯成 JavaScript 或 WebAssembly(用於網頁)。開發期間它執行在支援熱重載的虛擬機器上,修改後幾秒內就能在執行中的應用程式看到結果。

多數人學 Dart 是為了用 Flutter 開發應用程式。Flutter 是 Google 的 UI 工具包,一份程式碼就能建置 Android、iOS、網頁、Windows、macOS 與 Linux 應用程式。Dart 也用於命令列工具與後端服務。

安裝 Dart 並執行 Hello, world

請從 dart.dev 安裝 Dart SDK(或安裝已內含 Dart 的 Flutter)。每個 Dart 程式都從頂層的 main 函式開始執行。

// hello.dart
void main() {
  print('Hello, world!');
}
dart run hello.dart
# Hello, world!
dart compile exe hello.dart   # builds a native executable
dart create my_app            # creates a new console project

本頁的所有範例也都可以在 DartPad 中試跑,它是位於 dartpad.dev 的瀏覽器線上編輯器。

變數與基本型別

用 var 讓編譯器推斷型別,用 final 宣告只會指派一次的變數,用 const 宣告編譯期常數。你也可以隨時改成明確寫出型別。

void main() {
  var name = 'Dart';        // inferred as String
  int year = 2011;          // explicit type
  final now = DateTime.now(); // set once, at run time
  const pi = 3.14159;       // compile-time constant
  double ratio = 2;         // int literal becomes 2.0
  bool ok = year > 2000;
  print('$name since $year, pi=${pi.toStringAsFixed(2)}, ok=$ok, ratio=$ratio');
  // Dart since 2011, pi=3.14, ok=true, ratio=2.0
  print(now.year > 2000);   // true
}
型別範例說明
int42整數;在原生平台上為 64 位元
double3.1464 位元浮點數
num7 或 7.5int 與 double 的父型別
booltrue條件式中只允許 true 與 false
String'hi'UTF-16;支援 $x 與 ${expr} 插值
dynamic任何值關閉靜態檢查;大多數情況下應避免使用

在網頁平台上,int 值會以 JavaScript 數字表示,因此非常大的整數在那裡可能會損失精度。

健全的空值安全

自 Dart 3 起,所有 Dart 程式碼都採用健全的空值安全。型別為 String 的變數永遠不會是 null;若要允許 null,請寫成 String?。編譯器會檢查每一處使用,因此 null 錯誤在程式執行前就能被發現。

int lengthOf(String? s) {
  // return s.length;         // compile error: s might be null
  if (s == null) return 0;
  return s.length;            // promoted to String after the check
}
void main() {
  String? nickname;
  print(nickname?.length);    // null (null-aware access)
  print(nickname ?? 'guest'); // guest (default if null)
  nickname ??= 'dee';         // assign only if null
  print(lengthOf(nickname));  // 3
  late String config;         // initialized later, checked at run time
  config = 'loaded';
  print(config);              // loaded
}

流程控制

Dart 有常見的 if、for、while 與 do-while,另有可走訪任何可迭代物件的 for-in 迴圈。自 Dart 3 起,switch 支援模式比對,也可以當作運算式使用。

void main() {
  var scores = [72, 95, 48];
  for (var s in scores) {
    if (s >= 90) {
      print('$s excellent');
    } else if (s >= 50) {
      print('$s pass');
    } else {
      print('$s fail');
    }
  }
  for (var i = 0; i < 3; i++) {
    print('i = $i');
  }
  var day = 6;
  var kind = switch (day) {
    6 || 7 => 'weekend',
    >= 1 && <= 5 => 'weekday',
    _ => 'invalid',
  };
  print(kind); // weekend
}

函式

函式是一等物件。參數可以是位置參數、放在 [] 內的選擇性位置參數,或放在 {} 內的具名參數。具名參數除非標記為 required,否則都是選擇性的。箭頭語法 => 是只回傳一個運算式的函式的簡寫。

int add(int a, int b) => a + b;
String greet(String name, {String greeting = 'Hello', required bool loud}) {
  var text = '$greeting, $name';
  return loud ? text.toUpperCase() : text;
}
String label(String text, [int? width]) =>
    width == null ? text : text.padRight(width, '.');
void main() {
  print(add(2, 3));                              // 5
  print(greet('Ana', loud: false));              // Hello, Ana
  print(greet('Ana', greeting: 'Hi', loud: true)); // HI, ANA
  print(label('id', 5));                         // id...
  var twice = (int x) => x * 2;                  // anonymous function
  print([1, 2, 3].map(twice).toList());          // [2, 4, 6]
}

集合:List、Set、Map 與 Record

核心集合有 List(有順序)、Set(元素不重複)與 Map(鍵值對)。集合字面值支援 if、for 與展開運算子 ...。Dart 3 新增的 Record 可以把多個值組合在一起,不必另外宣告類別。

void main() {
  var nums = [3, 1, 2];               // List<int>
  nums.add(4);
  nums.sort();
  print(nums);                        // [1, 2, 3, 4]
  var tags = {'dart', 'flutter', 'dart'};
  print(tags.length);                 // 2
  var ages = {'Ann': 31, 'Bo': 25};   // Map<String, int>
  ages['Cy'] = 40;
  print(ages['Bo']);                  // 25
  var showAll = false;
  var menu = ['Home', if (showAll) 'Admin', for (var n in nums) 'Item $n'];
  print(menu);  // [Home, Item 1, Item 2, Item 3, Item 4]
  (String, int) pair = ('Ann', 31);   // a record
  var (who, age) = pair;              // destructuring
  print('$who is $age');              // Ann is 31
}

類別與 async/await

Dart 是物件導向語言:每個值都是物件。類別支援帶有初始化形式參數(this.x)的建構函式、具名建構函式、getter、mixin 與介面。非同步程式碼使用 Future 與 Stream,搭配 async 和 await,讓 Flutter 應用程式在等待檔案或網路時仍保持順暢回應。

class Point {
  final double x, y;
  const Point(this.x, this.y);
  Point.origin() : x = 0, y = 0;     // named constructor
  double get sum => x + y;           // getter
  @override
  String toString() => 'Point($x, $y)';
}
Future<String> fetchUser() async {
  await Future.delayed(Duration(milliseconds: 100));
  return 'ana';
}
Future<void> main() async {
  print(Point(1, 2).sum);            // 3.0
  print(Point.origin());             // Point(0.0, 0.0)
  var user = await fetchUser();
  print('user: $user');              // user: ana
}

錯誤處理

用 throw 拋出例外,用 try、on、catch 與 finally 處理。使用 on SomeType 可捕捉特定型別。Dart 區分 Exception(程式可以處理的預期問題)與 Error(程式錯誤,例如斷言失敗或範圍錯誤),後者通常不應該捕捉。

int parseAge(String s) {
  var n = int.tryParse(s);            // null instead of throwing
  if (n == null || n < 0) {
    throw FormatException('Not a valid age: $s');
  }
  return n;
}
void main() {
  for (var input in ['42', 'abc']) {
    try {
      print(parseAge(input));
    } on FormatException catch (e) {
      print('Error: ${e.message}');
    } finally {
      print('checked $input');
    }
  }
}
// 42 / checked 42 / Error: Not a valid age: abc / checked abc

完整的小程式:購物車總額

這個程式用類別表示商品、用 Map 儲存購物車,並印出收據。它結合了類別、Map、字串格式化與集合方法。

class Product {
  final String name;
  final double price;
  const Product(this.name, this.price);
}
void main() {
  const apple = Product('Apple', 0.5);
  const bread = Product('Bread', 2.25);
  final cart = <Product, int>{apple: 6, bread: 2};
  var total = 0.0;
  cart.forEach((product, qty) {
    final line = product.price * qty;
    total += line;
    print('${product.name.padRight(6)} x$qty  ${line.toStringAsFixed(2)}');
  });
  final count = cart.values.reduce((a, b) => a + b);
  print('$count items, total ${total.toStringAsFixed(2)}');
}
// Apple  x6  3.00
// Bread  x2  4.50
// 8 items, total 7.50

接下來可以學什麼

  • dart.dev 上的 Dart 語言導覽與教學。
  • Effective Dart,官方的風格與設計指南。
  • 想開發應用程式的話,可參考 flutter.dev 上的 Flutter 文件。
  • pub.dev,Dart 與 Flutter 的套件庫。

常見問題

Dart 只能用在 Flutter 嗎?

不是。Dart 是通用程式語言,可以開發命令列工具、伺服器與網頁應用程式。只是 Flutter 是大家學它最主要的原因。

Dart 是編譯式還是直譯式?

兩種模式都有。開發時,Dart 在支援即時編譯與熱重載的虛擬機器上執行;發行時則預先編譯成原生機器碼,網頁則編譯成 JavaScript 或 WebAssembly。

Dart 的 final 和 const 有什麼差別?

final 變數只指派一次,可以在執行期才決定。const 值必須在編譯期就已知,而且是深層不可變的,這讓 Flutter 能重複使用常數 widget。

Dart 和 Java 或 JavaScript 很像嗎?

它的 C 風格語法與類別,對 Java、C# 和 JavaScript 開發者來說都很熟悉。與 JavaScript 不同的是,Dart 是靜態型別,並具備健全的空值安全。

Dart 的 ?? 運算子有什麼作用?

a ?? b 在 a 不為 null 時回傳 a,否則回傳 b。相關的 a ??= b 只有在 a 為 null 時才會把 b 指派給 a。

全部編碼教學