Flutter開発で知っておくべきDart知識「JavaScriptを習得した私の差分理解」初見でも初めて会った感じがしない、つまり気が合う
はじめに
みなさん、こんにちは。
今回はDartの基本文法についてまとめています。
他の言語を習得済みの方の差分理解の手助けになれば幸いです。
(特にJavaScript習得をした方)
DartPadで練習できる
Dartの練習を手軽に行えるDartPadというページがあります。
Dartの特徴
概要
- 静的型付け言語
- クラスベースのオブジェクト指向言語
- null安全
- 型推論が効く
- Dartでは1つのファイルをライブラリと呼ぶ
- Dartの言語仕様は他の多くの言語と似ている
- Dart3で様々な機能が登場し難しい機能も増えてきた
Dartは静的型付け言語であり、型を活用しております。さらにnull安全にもなっており安全なプログラミングが行えます。型は推論されるため全てを自分で入力する必要はありません。
Dartはクラスベースのオブジェクト指向言語であり、かつ関数の仕組みも持っています。JavaとJavaScriptを合体させたような雰囲気の言語です。
Dartは他の言語と似ているので初見でも理解しやすいですが、Dart3から難しい機能も増えてきており理解するのに時間がかかる言語仕様も出てきました。
Dartでは1つのファイルをライブラリと呼ぶようです。
変数・基本データ型
概要
-
final
,const
で定数宣言 -
$変数名
と${式}
で文字列に値の埋め込み - nullの許可は
変数名?
- nullの可能性がある場合はnullチェックや
変数名!
で変数を利用 - nullの可能性がある場合は
変数名?.プロパティ(やメソッド())
で利用 - 論理値はbool型
宣言
変数宣言はvar
キーワードで行います。Dartには型推論の機能があるので、型を明示的に指定しない場合は値から推論されます。
変数宣言は定数とするとが推奨されています。定数はfinal
キーワードで宣言します。
final title = 'abc';
// final String title = 'abc';と同じ
文字列への値の埋め込み
文字列の表現は’(シングルクォート)
と”(ダブルクォート)
の二つがあります。$変数名
で変数の値を埋め込むことができます。${式}
で演算結果や関数呼び出しの戻り値を埋め込むことができます。
final title = 'abc';
final sentence = 'Title is $title.';
print(sentence); // -> Title is abc.
print('Length is ${title.length}.'); // -> Length is 3.
print('Upper is ${title.toUpperCase()}.'); // -> Upper is ABC.
print('1 + 1 = ${1 + 1}'); // -> 1 + 1 = 2
dynamic型は型チェックされない
TypeScriptにおけるany型のように「なんでも代入可能」を示すのがdynamic
型です。コンパイラにチェックされないためどんな値でも代入できますが、その分、型によるエラーに気づかなかったりエディタの補完が効かなかったり不都合でもあります。
// 型チェックされない
dynamic dynamicValue = 'どんな型でも';
jsonDecode
メソッドなどは戻り値にdynamic
型が用いられています。その場合は別の型に変換した上で利用すると安全です。またdynamic
型はObject?
型と相互に変換可能です。
Object? objectValue = 'Hello';
dynamic dynamicValue = objectValue;
nullの代入
Dartはnull安全であるためデフォルトでnullの代入は許可されてません。変数にnullを代入する場合は?
マークを型名の後ろに記述します。
処理の戻り値が、想定した値とnullのいずれかになっていることがよくあります。受け取る値がnullかもしれない場合は?
マークをつけておきます。
String? nullAble = null;
nullチェックと非nullアサーション
null安全な言語では、nullの可能性がある変数はそのままでは利用できません。if
文を使いnullではないことを確認するnullチェックを行うか、変数名の後ろに!
をつけてコンパイラにnullではないことを明示する非nullアサーションを指定します。
nullの可能性がある変数からプロパティやメソッドを呼び出す場合は?.
で呼び出すことができます。nullじゃない場合は正常にプロパティやメソッドが呼び出され、nullの場合はnullが値として返ってきます。
int? price = 500;
print(price + 10); // コンパイルエラー(it is potentially null)
print(price! + 10); // ! で非nullアサーション
// nullではないことをチェック
if (price != null) {
print(price + 10);
}
print(price?.isEven); // -> priceがnullならnull、500ならtrue
制御構文
概要
- for-in文で要素数分のループ
- switch式で定数やパターンに応じて値を取り出せる
for-in文
Dartの繰り返し構文は通常のfor
文ももちろんあり、要素数を基準に繰り返すfor-in
文も用意されています。
// Listの宣言
final colors = ['red', 'blue', 'green'];
// 要素数分ループ
for (final color in colors) {
print(color);
}
switch式
Dart3(2023/5)からswitch
式が登場しました。switch
文のように値を基準に判断し、該当するケースの値を返却します。値の返却があるというのがポイントですね。まるでReact
におけるReducer
関数のようですね。あちらはAction
のType
に応じてState
を返しているので、なんだか似ている気がしてきます。
書き方は従来のswitch
に似ています。各ケースにのパターンには値を指定し、合致する場合に返す値は=>
の右側に値そのものか式として記述します。_
をパターンに指定するとデフォルトケースとして扱われます。
final 変数 = switch(判断に使う値){
値 => 返す値や式,
値 => 返す値や式,
_ => デフォルトの値
};
final value = 1;
final result = switch(value){
1 => value * 10,
2 => value - 10,
_ => value
};
String型の値を扱う例です。デフォルトケースを配置しないとコンパイルエラーになります。
final item = 'pencil';
// 引数に渡された値にマッチする値を返す
final price = switch (item) {
'pencil' || 'pen' => 120, // or(やand)も指定可能
'notebook' => 200,
_ => 0, // デフォルトケース
};
print(price); // -> 120
enumの値を扱う例です。全てのenum定数を網羅しないとコンパイルエラーになります。
enum Title {
titleA, titleB, titleC
}
void main() {
final selected = Title.titleA;
// 全ての列挙定数を網羅していないとコンパイルエラー
final price = switch (selected) {
Title.titleA => 1000,
Title.titleB => 2000,
Title.titleC => 3000,
};
print(price); // -> 1000
}
sealedクラスのサブクラスを扱う例です。全てのサブクラスを網羅しないとコンパイルエラーになります。
sealed class Food {}
class Rice extends Food {}
class Blead extends Food {}
void main() {
final selected = Blead();
// 全てのsealedクラスのサブクラスを網羅していないとコンパイルエラー
final price = switch (selected) {
Rice() => 1000,
Blead() => 2000,
};
print(price); // -> 2000
}
リスト
概要
- 配列に相当するListクラスがある
- 要素の型は推論され、異なる型を入れようとするとコンパイルエラー
- []で宣言、addで追加
- whereメソッドで抽出
- mapメソッドで加工・変換
- forEachメソッドで繰り返し
- mapメソッド、whereメソッドは戻り値がIterable型
- toListでList化
Dartには配列に相当するList型があります。Listには要素を操作するためのメソッドが多数用意されていますが、よく使いそうなものをピックアップします。
宣言
まず宣言は[]
で行うことができます。要素の型は推論され型の違う値は混在できないようになります。なおListを生成するには他にも様々なコンストラクタが利用できます。
// 宣言
final items = [5, 10, 15];
追加(addメソッド)
要素の追加はadd
メソッドで行います。引数の要素をListの末尾に追加します。追加する要素の型がListの要素と異なる場合はコンパイルエラーになります。
// add
items.add(20);
print(items); // -> [5, 10, 15, 20]
// items.add('abc'); // 型不一致でコンパイルエラー
抽出(whereメソッド)
where
メソッドで条件に合う要素のみを抽出できます。引数に渡した処理の結果がtrueとなる要素を抽出します。結果はIterable型でList型ではありません。
// where
final evenList = items.where((item) => item.isEven);
print(evenList); // -> (10, 20)
加工・変換(mapメソッド)
map
メソッドで要素を加工・変換することができます。引数に渡した処理の結果が加工後の要素です。このメソッドも結果はIterable型でList型ではありません。
// map
final doubleList = items.map((item) => item * 2);
print(doubleList); // -> (10, 20, 30, 40)
繰り返し(forEachメソッド)
forEach
メソッドは要素を順番に扱って繰り返し処理します。map
との違いは戻り値がvoidで結果を返さないことです。よって引数に渡す処理も戻り値のない処理を指定します。
// forEach
items.forEach((item) => print(item)); // -> 5 10 15 20
メソッドチェーンで記述(where map forEach)
where
メソッドやmap
メソッドは戻り値にIterable型の値を返します。そしてそのIterable型からメソッドを繋げて呼び出すことができます。
// where map forEachを繋げて
items
.where((item) => item.isEven)
.map((item) => item * 2)
.forEach((item) => print(item));
Iterable型をList型に変換(toListメソッド)
where
メソッドやmap
メソッドは戻り値がIterable型です。FlutterのWidgetには引数にList型を要求するものがありますが、これらのメソッドで変換したIterable型をそのまま渡せません。そこでtoList
メソッドを使うことでList型に変換することができます。
// IterableをList型に(doubleListはmapの戻り値を代入した変数)
doubleList.toList();
マップ
概要
- key-value形式のデータのまとまりの表現
- JSのオブジェクトリテラルに相当
- key-valueの型は推論され、異なる型を入れようとするとコンパイルエラー
- {}で宣言
- [key]を指定し参照と追加・更新
- JSONと相互変換できるメソッドがある
- 通信で扱うデータ(JSON)をMapに変換して扱う
key-value形式のデータのまとまりを表すMap
型があります。key-valueの型は値から推論され、異なる型の値を入れようとするとコンパイルエラーになります。
宣言
DartにはJavaScriptのようなオブジェクトリテラルはありませんが、Map
がその代わりになります。JavaScriptのオブジェクトのようにkey-valueのvalueに様々な型の値を扱いたい場合は、Map<String, Object>
型とすることで可能です。
宣言は{}
で行います。キーと値は:
で区切ります。またコンストラクタも多数用意されており、それらを使っての宣言も可能です。
// 宣言 Map<String, Object>
final item = {
'name': 'abc',
'price': 2000
};
追加や取得
要素の追加や取得は[キー]
で行います。値を代入すれば更新になります。存在しないキーを指定して値を代入すると値を追加できます。
// 追加
item['title'] = 'def';
// 取得
print(item['name']); // -> abc
JSONとの相互変換
Map
は通信で扱うJSONデータと相互変換して扱うことがあります。jsonEncode
メソッドはMap
からJSONに変換します。jsonDecode
は逆にJSONをMap
に変換します。これらのメソッドは標準ライブラリのdart:convert
にあります。
// JSONの変換機能を持つ標準ライブラリ
import 'dart:convert';
// MapからJSONに変換
final json = jsonEncode(item);
// JSONからMapに変換
final data = jsonDecode(json);
サンプルコード全体像
// JSONの変換機能を持つ標準ライブラリ
import 'dart:convert';
void main() {
// 宣言 Map<String, Object>
final item = {
'name': 'abc',
'price': 2000
};
// 追加
item['title'] = 'def';
// 取得
print(item['name']); // -> abc
// MapからJSONに変換
final json = jsonEncode(item);
// JSONからMapに変換
final data = jsonDecode(json);
}
関数
概要
- トップレベルで関数を宣言できる
- 関数は値として扱うことができる
- アロー演算子を使った省略表現が存在する
- 引数の仕様(省略可能引数、名前付き引数と必須化)
宣言
Dartは関数を宣言でき、JavaScriptと同じように関数を値として扱うことができます。宣言時に引数の型は省略することができますが、記述しておいた方が安全でしょう。
// 関数宣言
String createTitle(String name) {
return name + 'def';
}
// 呼び出し
print(createTitle('abc')); // -> abcdef
省略表現
関数宣言には=>
を使った省略表現があります。処理が1行のみの場合は{}
とreturn
を省略することができます。
// 関数宣言(省略表現)
void showData() => print('data');
int calc(int x) => x + 5;
省略可能引数
Dartの関数には「省略可能引数」という仕様があります。引数の宣言を[]
で囲むことで省略可能となります。省略した場合はnull
が渡されるので、型名に?
を付与しnull許容にします。引数が渡されなかった場合にnullではなくデフォルト値を設定することもできます。
// 省略可能引数
void doSome1(String name, [int? age]) => print('did');
// 省略可能引数(デフォルト値)
void doSome2(String name, [int age = 20]) => print('did');
名前付き引数
引数の仕様に「名前付き引数」という表現もあります。実行時に名前を指定して引数を渡すことができるので可読性の向上に役立ちます。{}
で囲って引数リストの末尾に宣言します。
名前付き引数はデフォルトで省略可能引数という扱いになります。必須にするにはrequired
キーワードをつけます。
呼び出す際は、名前付き引数は順番を変えても問題ありません。
// 名前付き引数(省略可能)
void doSome3(String name, {int? age}) => print('did');
doSome3('abc',age: 20); // age引数は名前付きで値を設定
doSome3('abc'); // age引数は省略可能(nullになる)
// 名前付き引数(必須)
void doSome4(String name, {required int age}) => print('did');
doSome4('abc',age: 20); // age引数は必須
doSome4(age: 20,'abc'); // 引数の順番を変えてもOK
デバッグ
概要
- print関数、debugPrint関数でコンソール出力
- runtimeTypeで型を確認
- assert関数で中断
デバッグで使える機能をいくつかピックアップしました。
print関数
コンソール出力はprint
関数を使います。
print('abc'); // -> abc
debugPrint関数
FlutterではdebugPrint
関数という機能もあります。こちらは長いメッセージでも欠損なく表示できます。一方でprint
関数はあまりに長いメッセージは欠損してしまうようです。
import 'package:flutter/foundation.dart';
debugPrint('def'); // -> def
型情報を取得するruntimeType
値の型情報を取得するにはruntimeType
を参照します。
print('abc'.runtimeType); // -> String
デバッグ時に処理を中断する機能としてassert
関数があります。引数がfalse
だった場合に処理を中断します。
assert(false); // 中断
おわりに
Dartの言語使用や基本文法は割ととっつき易いと思いました。
一方でここに掲載していない複雑な文法も存在しています。そういった複雑だったり新しめな文法はあまり活用されていないのかなあという印象を持ちましたが、引き続き学習をしていきます。
Discussion