A tour of the Dart languageを読んだ

ので、気になったところをピックアップしていく。

Basics

 printInteger(int aNumber) {
    print('The number is $aNumber.');
 }
 
 main() {
    var number = 42;
    printInteger(number);
 }

mainはエントリポイント。 行の末尾にはセミコロン必須の模様。これはイマドキっぽくない感じ。

メソッド定義にはfuncなどのキーワード不要。

$aNumberで変数展開ができる。${aNumber}でもOK varで型を指定せず変数の宣言。イミュータブルにしたい場合letではなくfinalを使うみたい。

Important concepts

全てはオブジェクト。全てのオブジェクトはクラスのインスタンス。全てのオブジェクトはObjectクラスを継承している。

Dartでは型のアノテーションは必須ではなく、型推論が使える。型が決まらないものはdynamicとして扱える。

Generic型がある。List<int>とかで宣言できる。

top-levelな関数と変数の概念がある。main()はtop-level func。

Javaとは違い、public, protectedprivateというキーワードはない。

_から始まると、privateとして扱われる。

default value

初期化されていない変数はnullを初期値とする。optionalは無い。まじ?

Final and const

もし変数を変更したくなかったら、finalconstを使うと良い。

  • constコンパイル時に値が決定する。
  • finalは再代入できない。

Maps

ハッシュ。

 var gifts = {
    // key: Value
    'first': 'partridge',
    'second': 'turtledoves',
    'fifth': 'golden rings'
 };
 var nobleGases = {
    2: 'helium',
    10: 'neon',
    18: 'argon',
 };
 
 var gifts2 = Map();
 gifts2['first'] = 'partridge';

Symbols

SymbolはDartのプログラムで宣言されているoperatorやidentifierを表すオブジェクト。 普通に生きてる限りは使うことにはならないらしい。

#に続けてidentifierを書くことで、Symbolを取得できる

 #radix
 #bar

Functions

functionはFunction型のオブジェクト。変数に代入したり、引数として渡したりできる。

引数に名前をつけると、その引数は必須ではなくなる。

 enableFlags(bold: true, hidden: false);

もし必須にしたかったら、@required属性をつけると実現できる。 ちなみにmetaパッケージが必要らしいと書いてあるが、意識したことがない。変わってるかも。

package:meta/meta.dart

 const Scrollbar({ Key key, @required Widget child})

なお、ここには書いてなかったが、Protectedを表す@protectedオペレータを使う場合にはmeta.dartのインポートが必要だった。

Anonymous functions

無名関数が定義できる。

 (int hoge) { /* do something */ };
 
 // 1行ならこう書ける
 (hoge) => print(hoge);

Optional parameters

{}で囲んだパラメータは必須ではなくなる。

 /// Sets the [bold and [hidden flags ...
 void enableFlags({bool bold, bool hidden}) {...}

が、@requiredをつけたパラメータは必須になる。

 const Scrollbar({Key key, @required Widget child})

optionalとnamedがごっちゃになってるので正直ちょっと使いづらい。

Type casts

as, is, is!が使える。

asはキャスト、isis!は型が一致するか調べる。

  if (emp is Person) {
        emp.firstName = 'Bob';
     }
     
     (emp as Person).firstName = 'Bob';

この辺まで読んだ感想

  • そこはかとなく漂うレガシー感
  • JavaScriptよりはマシ?でもダックタイピングができないのにインターフェイスがないのは正直つらい

cascade notation

..という記法。 同一のオブジェクトにメソッドを続ける時に使う?これも使ったことない。普通に.で繋げられる。

 querySelector('#confirm') // Get an object.
   ..text = 'Confirm' // Use its members.
   ..classes.add('important')
   ..onClick.listen((e) => window.alert('Confirmed!'));

Classes

obj.runtimeTypeで実行時の型を知ることができる。 print('The type of a is ${a.runtimeType}'); 返り値はType型のオブジェクト。

あくまでType型なので、レシーバのクラスオブジェクトを取得できるわけではない。

Constructors

クラス名がそのままコンストラクタになる。 コンストラクタのパラメータをプロパティにセットするだけならシンタックスシュガーで書くと良い。

 class Point {
   num x, y;
 
   // Syntactic sugar for setting x and y
   // before the constructor body runs.
   Point(this.x, this.y);
 }
  • 他にも色々
    • コンストラクタを実装しなかった場合、引数を取らないコンストラクタが自動で生成される
      • 1つでもコンストラクタを実装した場合、生成されない
      • unnamed constructorって書かれてるけど、クラス名と同じで引数を取らないコンストラクタのことっぽい[osamtimizer.icon
    • 名前付きコンストラクタが作れる
    • サブクラスはコンストラクタを継承しない
      • 名前付きコンストラクタも例外ではない

Named Constructor

 class Point {
   num x, y;
 
   Point(this.x, this.y);
 
   // Named constructor
   Point.origin() {
     x = 0;
     y = 0;
   }
 }

コンストラクタのオーバーロードとして使う。

  • User.fromJsonのように、コンストラクタにラベルをつける感じで使われることが多いみたい。
    • Swiftのinit(from json: Any)っぽいと思った

Invoking a non-default superclass constructor

サブクラスはコンストラクタが呼ばれた時、自動でスーパークラスのコンストラクタを呼ぶ。 呼ばれるタイミングは、サブクラスのコンストラクタの中身に到達する前。 initializer listが定義されていた場合、そちらが優先して呼ばれる。

呼ばれる順番は以下の通り。 1. initializer list 2. superclass’s no-arg constructor 3. main class’s no-arg constructor

親クラスがunnamedなコンストラクタを持っていない場合、親クラスのコンストラクタのどれを呼ぶかはサブクラスのコンストラクタで決めないといけない。

 class Person {
   String firstName;
 
   Person.fromJson(Map data) {
     print('in Person');
   }
 }
 
 class Employee extends Person {
   // Person does not have a default constructor;
   // you must call super.fromJson(data).
   // :の後に、親クラスのコンストラクタを指定する。その後に続くブロックの中身はサブクラスのコンストラクタ
   Employee.fromJson(Map data) : super.fromJson(data) {
     print('in Employee');
   }
 }
 
 main() {
   // Employeeの名前付きコンストラクタを呼ぶ
   var emp = new Employee.fromJson({});
 
   // Prints:
   // in Person
   // in Employee
   if (emp is Person) {
     // Type check
     emp.firstName = 'Bob';
   }
   (emp as Person).firstName = 'Bob';
 }

実行結果は以下の通り。

 in Person
 in Employee

initializer list

親クラスのコンストラクタを呼ぶ代わりに、インスタンス変数をコンストラクタのBodyに到達する前に初期化できる。 initilizerは,で区切ることができる。

 // Initializer list sets instance variables before
 // the constructor body runs.
 Point.fromJson(Map<String, num> json)
     : x = json['x',
       y = json['y' {
   print('In Point.fromJson(): ($x, $y)');
 }

Development環境では、initializer listにassertメソッドが使える。

 Point.withAssert(this.x, this.y) : assert(x >= 0) {
   print('In Point.withAssert(): ($x, $y)');
 }

Redirecting constructors

コンストラクタから、別のコンストラクタを呼ぶことができる。

 class Point {
   num x, y;
 
   // The main constructor for this class.
   Point(this.x, this.y);
 
   // Delegates to the main constructor.
   Point.alongXAxis(num x) : this(x, 0);
 }

Swiftのconvenience init的に使える。

Factory constructors

factoryキーワードを使って、クラスのインスタンスを生成しないコンストラクタを作れる。 例えば、コンストラクタからキャッシュやサブタイプを返す場合に使える。

よくJSONからモデルを生成するときにModel.fromJSONを生やす。

 class Logger {
   final String name;
   bool mute = false;
 
   // _cache is library-private, thanks to
   // the _ in front of its name.
   static final Map<String, Logger> _cache =
       <String, Logger>{};
 
   factory Logger(String name) {
     return _cache.putIfAbsent(
         name, () => Logger._internal(name));
   }
 
   // privateなコンストラクタ
   Logger._internal(this.name);
 
   void log(String msg) {
     if (!mute) print(msg);
   }
 }
 
 var logger = Logger('UI');
 logger.log('Button clicked');

おわりに

言語仕様に不満はあるものの、Flutterの開発体験が良いのでトータルでプラスという印象。 JavaScriptの代替としてはTypeScriptのほうが優秀かな...。