iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Mobile Development

Flutter / Dart 跨平台App開發體驗系列 第 4

Flutter體驗 Day 4-Dart CheatSheet (2)

Dart CheatSheet (2)

認識 Dart 程式語言,從官方提供的dart-cheatsheet掌握該語言的特色

Dart CheatSheet

Getters and Settiers

這邊的行為與 JS (ES6) 語法類似,我們直接看範例程式。

class Age {
  int age;
  Age(this.age);

  bool get isComeOfAge {
    return age >= 18;
  }
}

class User {
  String name;
  Age? _age;

  User(this.name, [int? age]) {
    if (age != null) {
      this._age = Age(age);
    }
  }

  int? get age {
    return _age?.age;
  }

  set age(int? value) {
    if (value != null) {
      _age = Age(value);
    }
  }

  bool get isComeOfAge {
    return _age!.isComeOfAge;
  }
}

void main() {
  // age 為可選參數
  var user = User("Leo");
  print(user.age); // null

  user.age = 35;
  print(user.age); // 35
  print(user.isComeOfAge); // true
}

條件屬性存取 (Conditional property access)

類似 JS (ES11) 上的 Optional chaining,用來保護 Object 上的屬性存取。

這邊以 Getters and Settiers 中的範例為例,因為年齡可為空,因為在取用前需要特別加上(?.)。

  int? get age {
    return _age?.age;
  }

可選位置參數 (Optional positional parameters)

Dart參數傳遞的方式有兩種:位置參數以及命名參數。

這邊以 Getters and Settiers 中的範例為例:

因為年齡可為空,可選位置參數放在方法參數的最後並使用中刮號[]包起來。

class User {
  String name;
  Age? _age;

  User(this.name, [int? age]) {
    if (age != null) {
      this._age = Age(age);
    }
  }
  ...
}

可選命名參數 (Optional named parameters)

我們改寫一下上面User類別的建構式方法,因為年齡可為空,可選命名參數使用大刮號{}包起來。

在 Flutter 控件的原始碼,語法大多是以可選命名參數進行設計,當可選參數較多時這種命名參數的可讀性很高。

class User {
  String name;
  Age? _age;

  User(this.name, {Age? age}) {
    if (age != null) {
      this._age = age;
    }
  }
...
}

void main() {
  var user = User("Leo", age: 35);
  print(user.age); // 35
}

注意:一個方法不能同時使用可選位置參數和可選命名參數。

例外處理 (Exceptions)

對於例於處理,我們可以使用 try..catch 的方式補獲可能的例外狀況,而不影響到主程式的運行,與 JS 不同的地方在於 dart 可指定多個 catch 語句。請參考下列範例:

class CustomException implements Exception {
  String cause;
  CustomException(this.cause);
}

class LogicalException implements Exception {
  String _cause = "打我啊笨蛋";

  toString() {
    return "LogicalException: $_cause";
  }
}

somethingWillThrowError(type) {
  switch (type) {
    case 0:
      throw CustomException("Sing Hosanna");
    case 1:
      throw LogicalException();
    default:
      throw "Unknown Error";
  }
}

void test() {
  try {
    somethingWillThrowError(DateTime.now().microsecondsSinceEpoch % 3);
  } on CustomException {
    print("? He's got the whole world in His hands~");
  } on Exception catch (e) {
    // 取得Exception類型的錯誤
    print('Exception: ${e}');
    rethrow;
  } catch (e) {
    // 取得未知類型的錯誤
    print("$e");
  }
}

void main() {
  try {
    test();
  } on LogicalException catch (e) {
    print(e.runtimeType); // LogicalException
    print(e.toString());
  } finally {
    print("I Got You!!!");
  }
}

類別 (Class)

在建構方法上有提供不同類型的建構方式,查看下方的範例:

enum JOB { NEW, FIGHTER, WARRIOR }

class Fighter extends Player {
  final JOB _job = JOB.FIGHTER;
  Fighter(name) : super(name: name);
}

class Warrior extends Player {
  final JOB _job = JOB.WARRIOR;
  Warrior(name) : super(name: name);
}

class Player {
  String name;

  JOB _job;

  // 在建構式中使用 this,可以用來對應類別的屬性
  Player({required this.name}) : _job = JOB.NEW;

  // 在建構式函式執行可使用 (:) 進行初始化的動作
  Player.fighter({required this.name}) : _job = JOB.FIGHTER {
    print("Build: Player.fighter [$job]");
  }

  // 可以定義不同建構方法
  Player.fromFighter(String name) : this.fighter(name: name);

  // 工廠方式,使用 factory 宣告讓方式會返回 子類別 或是 null
  factory Player.fromJson(Map<String, String> json) {
    var name = json['name']!;
    var job = json['job']!;
    if (job == 'fighter') return Fighter(name);
    if (job == 'warrior') return Warrior(name);

    throw ArgumentError('Unrecognized $job');
  }

  get job {
    return this._job;
  }

  toSring() {
    return "$name $job";
  }
}

void main() {
  var newPlayer = Player(name: "新手");
  print(newPlayer.toSring());

  // 此類別重新轉達其他的建構方法 (這邊範例實作命名參數轉成位置參數的建構方法)
  var fighter = Player.fromFighter("戰士");
  print(fighter.toSring());

  var warrior = Player.fromJson({"name": "鬥士", "job": "warrior"});
  print(warrior.toSring());
}

var、const、final

在之前的範例中,我們大部份都是使用var讓 dart 透過推論的方式導出變數的型別。

如果我們想要宣告一個變數為常數,我們可以使用關鍵字final或是const取代var修飾其存取行為,這兩個的差別如下:

  1. final的變數只可以被賦值一次,const的變數指的是編譯時的常數,這邊指是在編譯後,其值將永遠不會被改變。

  2. 在類別中的實例(instance)變數可以是final但不可以是const

  3. 雖然 final 的 object 無法被修改,但是其屬性是可以被改變的;相對的 const 的 object 其屬性是 immutable 的喔。

class Area {
  String city = "";
  Area(this.city);
}

class User {
  final String name; // 2. 不可為 const
  final Area area;

  User(this.name, this.area);

  // 3. 使用 const 需加上 static 表示有靜態常數
  static const value = 999;
}

void main() {
  final aFinalStr = "hi";
  // aFinalStr = "cc";  // 1. 不可修改
  print(aFinalStr);

  const bConstStr = "hello";
  // bConstStr = "cc";  // 1. 不可修改
  print(bConstStr);

  var user = User("Leo", Area("Taiwan"));
  // user.name = "cc"; // 1. 不可修改
  print(user.name);
  print(user.area.city);
  user.area.city = "Taipei";
  // user.area = Area("Tainan"); // 3. 不可修改
  print(user.area.city); // 3. 被異動了

  print(User.value);
}

const建構方式 (Const constructors)

這邊我直接引用官網上的範例,概念與上述類別的靜態常數是一樣的,當你定義的對象永遠不會改變時,可以宣告一個 const 建構式用來在編譯中建立靜態常數物件。

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final int x;
  final int y;

  const ImmutablePoint(this.x, this.y);
}

mixins

Mixin是一種在多重繼承中複用方法的模式,使用關鍵字 with 將 Mixin 類別中方法混入至想要複用其方法的類別上。

P.S. Mixin沒有收錄在速查表上,不過其語法的特殊性,我覺得還是需要特別提一下。因為在之後的 Flutter SDK 內的 runApp 函數裡有一個類別 WidgetsFlutterBinding 就應用到多個Binding的mixin模式,而建構出 Flutter 運行的底層架構。這邊我們先從下列簡單的範例認識一下 mixin 的使用方法。

mixin Hello {
  late String name;
  void say() {
    print("Hi, $name !!!");
  }
}

class User with Hello {
  String name;
  User(this.name);
}

void main() {
  var user = User("Leo");
  user.say(); // Hi, Leo !!!
}

課程練習

麻煩至官方速查表中,完成今日學習的章節裡的代碼範列,連結在這

小結

練習成果

今日完成速查表中列出的dart語言特性的學習,當然還有許多課程內容包含泛型、同步非同步、核心函式庫、第三方套件等需要時間慢慢上手。


上一篇
Flutter體驗 Day 3-Dart CheatSheet (1)
下一篇
Flutter體驗 Day 5-Widget 樂高積木
系列文
Flutter / Dart 跨平台App開發體驗30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言