LoginSignup
34
31

More than 3 years have passed since last update.

よくわかるDart 🎯

Last updated at Posted at 2019-12-25

概要

現在弊社でリリースしている iOS アプリの Android バージョン開発に向けて、
Flutter が候補として挙がったので、 Dart について調べました。
どんな言語なのか、どんな書き方ができるのかなど、
1〜2日で調べた内容を共有します。

自己紹介

株式会社ITMONOでCTOをしています。
自分のキャリアとしては、サーバーサイドが一番長めで、
Java → PHP → Ruby, Python, Node.js あたりをふらふらしてきました。
直近1~2年はずっとRubyを書きつつ、
最近はSRE的な仕事が多めで、AWSの構築/保守/運用をしています。

想定読者

  • これから Dart をやってみようかなと思っている人
  • Dart がどういう言語なのかさくっと知りたい人

すでに Dart を書いている人向けではないです悪しからず。

本編

歴史

  • JavaScriptの欠点を補うために2011年Googleが公開
  • Chromeブラウザに組み込まれるはずだった
  • 2015年断念し、Javascriptへコンパイルして使う方へ専念
  • 2018年最も学ぶ価値のない言語1位になる
  • 2018年12月 Google がモバイルSDK Flutter をリリース
  • 2019年のランキングでは14位まで下がる

言語仕様

  • クラスベースのオブジェクト指向言語
  • 全てがオブジェクト
  • 静的型付けができる
  • ただ強制ではなく、動的型付けにすることもできる
  • public, private のキーワードはないが、プレフィックスに _ をつけることで private になる

インストール

とりあえず Mac に入れる。

$ brew tap dart-lang/dart
$ brew install dart
$ dart --version
Dart VM version: 2.7.0 (Fri Dec 6 16:26:51 2019 +0100) on "macos_x64"

2.7.0 が入った

Hello world

hello.dart
void main() {
  print("Hello world");
}
$ dart hello.dart
Hello world

Javaっぽい。
public static 書かなくていい分楽。
文末セミコロンなきゃだめなのかー

Every app must have a top-level main() function, which serves as the entrypoint to the app. The main() function returns void and has an optional List parameter for arguments.
https://dart.dev/guides/language/language-tour#the-main-function

main() はエントリーポイントとなる関数で、必ずアプリケーションのトップレベルに置く必要がある。
引数として List<String> を取る。このへんも Java と同じ。

hello.dart
void main(List<String> args) {
  print(args[0]);
}
$ dart hello.dart moi!
moi!

エディタ

手軽にブラウザで試せるやつがある
DartPad

普段は Visual Studio Code 使うのでこのプラグイン入れる
Dart-Code

他も有名どころはだいたいある感じ。

 2019-12-25 20.33.11.png

文法

samplesチートシートのやつざっと見ていく。

変数

型を明示しない場合は var で宣言する

var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
  'tags': ['saturn'],
  'url': '//path/to/saturn.jpg'
};

型を指定する場合は String とかをつける。
dynamicvar と同じ。

String name = 'Voyager I';
int year = 1977;
double antennaDiameter = 3.7;
List<String> flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
Map<String, dynamic> image = {
  'tags': ['saturn'],
  'url': '//path/to/saturn.jpg'
};

初期値を指定しない場合は null になる。

int lineCount; // null

定数

finalconst を使う。

final name = 'Bob';
final String nickname = 'Bobby';

2つのキーワードの違いは、一度だけ初期化されるか、コンパイル時かみたいなことらしい。

If you never intend to change a variable, use final or const, either instead of var or in addition to a type. A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.

詳しくは Final and const あたりを。

コレクション

List, Set, Map あたり。

final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
  'one': 1,
  'two': 2,
  'three': 3,
};

final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};

制御構文

特に目新しいところはない。Javaと一緒。
switch もある。

if (year >= 2001) {
  print('21st century');
} else if (year >= 1901) {
  print('20th century');
}

for (var object in flybyObjects) {
  print(object);
}

for (int month = 1; month <= 12; month++) {
  print(month);
}

while (year < 2016) {
  year += 1;
}

if はワンライナーでも書ける。

var flg = true;
if (flg) print('Hello');

条件式は bool の値しか取らない。
ここは JavaScript とかと違うところ。
Dart において truebool true のみ。

// Error
var flg = 1;
if (flg) print('Hello');

こういうのはいける

var name = '';
if (name.isEmpty) print('Hello');

関数

戻り値の型を指定するのが推奨

int fibonacci(int n) {
  if (n == 0 || n == 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

var result = fibonacci(20);

アロー => を使ったショートハンドも書ける。
無名関数扱うのに便利。
あと forEach(print) みたいに引数として渡せる。

flybyObjects.where((name) => name.contains('turn')).forEach(print);

愚直に書くとこんな感じかな?

bool containsTurn(String name) {
  return name.contains('turn');
}

flybyObjects.where(containsTurn).forEach((name) {
  print(name);
});

インポート

ライブラリや別ファイルのインポート。
ライブラリのインストールは後述。

// Importing core libraries
import 'dart:math';

// Importing libraries from external packages
import 'package:test/test.dart';

// Importing files
import 'path/to/my_other_file.dart';

クラス

宇宙船 クラスで、名前打ち上げ日 をプロパティとして持っている。
クラス名でコンストラクタを定義する。

class Spacecraft {
  String name;
  DateTime launchDate;

  Spacecraft(this.name, this.launchDate);
}

上のコンストラクタはシンタックスシュガーで、同名のプロパティに値を割り当ててくれる。
これと同義。

Spacecraft(name, launchDate) {
  this.name = name;
  this.launchDate = launchDate;
}

名前付きのコンストラクタも定義できる。
: this(name, null) はデフォルトのコンストラクタを続けて呼び出す super 的なやつ。

// Named constructor that forwards to the default one.
Spacecraft.unlaunched(String name) : this(name, null);

getter はこんな感じ。もちろん setter もある。

int get launchYear => launchDate?.year;

メソッドは特に解説要らないすね。
日付まわり使いやすそう。

void describe() {
  print('Spacecraft: $name');
  if (launchDate != null) {
    int years = DateTime.now().difference(launchDate).inDays ~/365;
    print('Launched: $launchYear ($years years ago)');
  } else {
    print('Unlaunched');
  }
}

インスタンス生成も目新しいところはない。

var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();

var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();

Nullまわりの便利演算子

?.

launchDatenull だったら null を返す。
null でなければ後続のメソッドを呼ぶ。

launchDate?.year

これと同義です。

(launchDate != null) ? launchDate.year : null;

Ruby の &.hoge とか .try(:hoge) みたいな感じか。

??=

左辺の値が null だったら右辺の値を代入する。
null でなければ何もしないみたいなやつ。
Rubyでいうところの ||=

int a; // The initial value of a is null.
a ??= 3;
print(a); // <-- Prints 3.

a ??= 5;
print(a); // <-- Still prints 3.

??

先の例で評価をそのまま返すパターン。
Rubyでいうところの ||

print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.

変数展開

$変数名 で展開される。
Dart ではシングルクォートが推奨されているが、
シングルでもダブルでも展開される。

print('Launched: $launchYear ($years years ago)');

式を書きたいときは ${} とする。

print('2 + 3 = ${2 + 3}');

継承

extends キーワードで単一継承です。

class Orbiter extends Spacecraft {
  num altitude;
  Orbiter(String name, DateTime launchDate, this.altitude) : super(name, launchDate);
}

ミックスイン

with キーワードでクラスにメソッドを生やせるやつ。
Ruby の extend 的な。

class Piloted {
  int astronauts = 1;
  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

class PilotedCraft extends Spacecraft with Piloted {
  // ···
}

var piloted = PilotedCraft('Piloted Craft I', DateTime(2001, 10,3));
piloted.describeCrew();

これインスタンス変数のスコープとかどうなるのかなー。
どっち優先されるとかいろいろ考えないといけないやつかな:thinking:

インターフェース

Dart には interface というキーワードはない。
あらゆるクラスは暗黙的にインターフェースを定義している。

Dart has no interface keyword. Instead, all classes implicitly define an interface. Therefore, you can implement any class.

class MockSpaceship implements Spacecraft {
  // ···
}

インターフェース使いたいならクラスが持つメンバーとメソッド全部実装しろよってことっぽい。

詳しくは implicit-interfaces

抽象クラス

これはそのままJavaっぽい。
インスタンス化できないクラス。

abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

非同期

async/await でやる

const oneSecond = Duration(seconds: 1);
// ···
Future<void> printWithDelay(String message) async {
  await Future.delayed(oneSecond);
  print(message);
}

Future/then を使った書き方もある

Future<void> printWithDelay(String message) {
  return Future.delayed(oneSecond).then((_) {
    print(message);
  });
}

ストリームはこう扱う。

Stream<String> report(Spacecraft craft, Iterable<String> objects) async* {
  for (var object in objects) {
    await Future.delayed(oneSecond);
    yield '${craft.name} flies by $object';
  }
}

カスケード記法

.. でオブジェクトに対するメソッド呼び出しをチェーンできる。
オブジェクト(querySelector)が返ってくるので、プロパティの classes とかにアクセスできてる。

querySelector('#confirm')
    ..text = 'Confirm'
    ..classes.add('important')
    ..onClick.listen((e) => window.alert('Confirmed!'));

チェーンしやすい...!

これと同義です。

var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));

こういうことですね

// this returns value
myObject.someMethod()
// this returns reference to object
myObject..someMethod()

Optional positional parameters

[]で引数をオプショナルにできる

int sumUp(int a, int b, int c) {
  return a + b + c;
}

int total = sumUp(1, 2, 3);

int sumUpToFive(int a, [int b, int c, int d, int e]) {
  int sum = a;
  if (b != null) sum += b;
  if (c != null) sum += c;
  if (d != null) sum += d;
  if (e != null) sum += e;
  return sum;
}

int total = sumUptoFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);

デフォルト値も設定できる。

int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
  ...
}

int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15

Optional named parameters

キーワード引数の。
{} シンタックスこれかよとは思うが。

void printName(String firstName, String lastName, {String suffix}) {
  print('$firstName $lastName ${suffix ?? ''}');
}

printName('Avinash', 'Gupta');
printName('Poshmeister', 'Moneybuckets', suffix: 'IV');

void printName(String firstName, String lastName, {String suffix = ''}) {
  print('$firstName $lastName ${suffix}');
}

例外

Javaと違って全部 unchecked exception らしい。
これは必ず catch しろよみたいなのがない。

Dart code can throw and catch exceptions. In contrast to Java, all of Dart’s exceptions are unchecked exceptions. Methods don’t declare which exceptions they might throw, and you aren’t required to catch any exceptions.

Exception, Error の他に null 以外のオブジェクトならなんでも投げてよし。

throw Exception('Something bad happened.');
throw 'Waaaaaaah!';

例外の種類は on キーワードで定義する。
catch は省略できる。例外オブジェクトを参照したい時につける。

try {
  breedMoreLlamas();
} on OutOfLlamasException {
  // A specific exception
  buyMoreLlamas();
} on Exception catch (e) {
  // Anything else that is an exception
  print('Unknown exception: $e');
} catch (e) {
  // No specified type, handles all
  print('Something really unknown: $e');
}

呼び出し元に伝播させるには rethrow キーワードを使う。

finally は普通にある。

Initializer lists

なんかコンストラクタで前処理必要なときに先に変換しておけるみたいなこと?
final のフィールドはコンストラクタの中身が実行される前に初期化しないといけない事情があるから。

Point.fromJson(Map<String, num> json) 
    : x = json['x'], 
      y = json['y'] { 
  print('In Point.fromJson(): ($x, $y)');
}

Sometimes when you implement a constructor, you need to do some setup before the constructor body executes. For example, final fields must have values before the constructor body executes. Do this work in an initializer list, which goes between the constructor’s signature and its body:

開発中はアサーション置く場所として使えるねみたいなことも書いてある。

The initializer list is also a handy place to put asserts, which run only during development:

NonNegativePoint(this.x, this.y) 
    : assert(x >= 0), 
      assert(y >= 0) { 
  print('I just made a NonNegativePoint: ($x, $y)');
}

Factory constructors

継承してる親クラスから子クラスを透過的にインスタンス化できると。
factory なんてキーワード初めて見たな。

class Square extends Shape {}

class Circle extends Shape {}

class Shape {
  Shape();

  factory Shape.fromTypeName(String typeName) {
    if (typeName == 'square') return Square();
    if (typeName == 'circle') return Circle();

    print('I don\'t recognize $typeName');
    return null;
  }
}

Const constructors

噛みそうな名前(笑)

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

最初からイミュータブルなインスタンスとして生成する。

class ImmutablePoint {
  const ImmutablePoint(this.x, this.y);

  final int x;
  final int y;

  static const ImmutablePoint origin = ImmutablePoint(0, 0);
}

パッケージ

Dart のパッケージ管理ツールは pub 
ここでパッケージが公開されてる

よくこのドメイン取れたなw

webやサーバーサイド開発なら stagehand
モバイル開発なら Flutter のツールを使ってわちゃわちゃやる感じらしい。

サードパーティのライブラリをインストールするためには、自分のアプリをまずパッケージ化しないといけない。
npm init 的なノリですね。

pubspec.yaml というファイルが必要になる。

Stagehand の入手から。

$ pub global activate stagehand
Warning: Pub installs executables into $HOME/.pub-cache/bin, which is not on your path.
You can fix that by adding this to your shell's config file (.bashrc, .bash_profile, etc.):

export PATH="$PATH":"$HOME/.pub-cache/bin"

Activated stagehand 3.3.5.

こんなメッセージが出たので言われた通りパスを通して実行すると、
どんな種類のテンプレートが作れるか表示される。

$ stagehand

とりあえず生成してみる。

$ stagehand console-full

ファイルやらディレクトリがわちゃわちゃ作成される。

依存関係をpubspec.yamldependencies に記述する。

    dependencies:
      vector_math: ^2.0.7

pubspec.lock とは Gemfile Gemfile.lock 的な関係なんだろう。

pub get でインストールすると、import して使えるようになる。

import 'package:vector_math/vector_math.dart';

void main() {
  var x = Vector3.zero(); // Zero vector
  var y = Vector4.all(4.0); // Vector with 4.0 in all lanes
  print('x: $x');
  print('y: $y');
  x.zyx = y.xzz; // Sets z,y,x the values in x,z,z
  print('x: $x');
  print('y: $y');
}

スタイルガイドとか

軽く見ておく。
https://dart.dev/guides/language/effective-dart

命名

3種類の規則を使う

  • UpperCamelCase
  • loweCamelCase
  • lowercase_with_underscores

クラスとか型定義には UpperCamel

class SliderMenu { ... }

class HttpRequest { ... }

typedef Predicate<T> = bool Function(T value);

ライブラリ、パッケージ、ファイル名などは lowercase_with_underscores
これは case-sensitive じゃないファイルシステムがあるから

library peg_parser.source_scanner;

import 'file_system.dart';
import 'slider_menu.dart';

メンバ、変数、パラメーターとかは lowerCamel

var item;

HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}

定数は今は lowerCamel。
昔は SCREAMING_CAPS だったらしいですね。(大文字+アンダースコア)

const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
  static final numberGenerator = Random();
}

フォーマット

dartfmt でやる。

$ dartfmt -w hoge.dart

タブではなくスペース。
2つと4つどっちがデファクトなんだろう:thinking:

他はこのあたり〜
https://github.com/dart-lang/dart_style/wiki/Formatting-Rules

Tips

プラクティス的なことはこのへん見れば良いと思います。

nullfalse にしたかったらこう、とか

optionalThing?.isEnabled ?? false;

コミットしてはいけないファイル

テスト

終わり

ざっとでしたが、Javaに根ざしたオブジェクト指向に、
スクリプト系(JavaScript, Ruby, Ptyhon)のエッセンスが入ってて使いやすそうな印象でした〜

次は Flutter 調べよ。

34
31
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
31