FlutterとDart 3:レコード、パターンマッチング、上級面接質問ガイド
Dart 3のレコード、パターンマッチング、シールドクラスをFlutterコード例で徹底解説。網羅的パターンマッチング、状態モデリング、技術面接対策を網羅。

Dart 3のレコード、パターンマッチング、シールドクラスは、2026年のモダンFlutter開発の基盤を形成している。Dart 3.0で導入され、Dart 3.11まで改良が重ねられたこれらの機能は、代数的データ型、網羅的パターンマッチング、そしてランタイム前にバグのカテゴリ全体を検出するコンパイル時安全性を実現する。
レコードはクラスなしで複数の値をまとめる。パターンはそれを1行で分解する。シールドクラスはコンパイル時にswitch文の網羅性を保証する。これら3つが組み合わさることで、従来のボイラープレートの多い状態モデリングコードの大部分を置き換えることができる。
Dart 3レコードの理解:軽量データ集約
レコードは、Dartの長年の課題であった「専用クラスを作成せずに関数から複数の値を返す」問題を解決する。レコードは匿名で不変な集約型である。
// Returning multiple values with a positional record
(String, int) parseUserInput(String raw) {
final parts = raw.split(':');
return (parts[0].trim(), int.parse(parts[1]));
}
// Named fields improve readability for complex returns
({String name, String email, bool isVerified}) fetchUserProfile(String id) {
// Simulated database lookup
return (
name: 'Alice Chen',
email: 'alice@example.com',
isVerified: true,
);
}レコードは構造的に型付けされる。同じフィールド型と名前を持つ2つのレコードは同一の型となる。これにより、Dart 3以前にFlutter開発者が頼っていたTuple2、Pair、その他のユーティリティクラスが不要になる。
// Structural equality — no need for custom == operator
void main() {
final a = (1, 'hello');
final b = (1, 'hello');
print(a == b); // true — records compare by value
// Named fields also support equality
final profile1 = (name: 'Alice', role: 'admin');
final profile2 = (name: 'Alice', role: 'admin');
print(profile1 == profile2); // true
}レコードは関数の戻り値、ローカルなデータ集約、マップのキーに最適である。メソッドやバリデーションを持つドメインモデルには、依然としてクラスが適切な選択肢となる。
Dartパターンマッチング:分解と制御フロー
Dart 3のパターンマッチングは、単純な型チェックを超えている。パターンは値を分解し、変数をバインドし、制御フローに参加する。これらすべてを1つの式で実行できる。
// Destructuring a record with pattern matching
void main() {
final (name, email, isVerified) = fetchUserProfile('u-123');
// name, email, isVerified are now local variables
// List patterns with rest operator
final scores = [98, 87, 92, 76, 84];
final [first, second, ...remaining] = scores;
print('Top two: $first, $second'); // 98, 87
print('Others: $remaining'); // [92, 76, 84]
}switch式はパターンと組み合わせることで、冗長なif-elseチェーンを簡潔で網羅的なマッチングに置き換える:
// Switch expression with guard clauses
String classifyScore(int score) => switch (score) {
>= 90 => 'Excellent',
>= 80 => 'Good',
>= 70 => 'Average',
>= 60 => 'Below Average',
_ => 'Needs Improvement',
};
// Object pattern matching with type checking
String describeValue(Object value) => switch (value) {
int n when n < 0 => 'Negative integer: $n',
int n => 'Positive integer: $n',
String s when s.isEmpty => 'Empty string',
String s => 'String of length ${s.length}',
List l when l.isEmpty => 'Empty list',
List l => 'List with ${l.length} elements',
_ => 'Unknown type',
};ガード句(whenキーワード)はパターンに条件ロジックを追加し、ネストを排除する。これによりswitch式はフラットで可読性が高くなる。
シールドクラス:網羅的な状態モデリング
シールドクラスはサブタイプ階層を同一ファイル内に制限し、Dartコンパイラがswitch文ですべてのサブタイプが処理されていることを検証できるようにする。これが型安全な状態管理の重要なピースである。
// Sealed class defining all possible authentication states
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
final String userId;
final String displayName;
AuthAuthenticated({required this.userId, required this.displayName});
}
class AuthError extends AuthState {
final String message;
final int? statusCode;
AuthError({required this.message, this.statusCode});
}コンパイラは網羅性を強制する。すべてのサブクラスが処理される必要がある:
// Exhaustive switch — compiler error if a case is missing
Widget buildAuthUI(AuthState state) => switch (state) {
AuthInitial() => const LoginPrompt(),
AuthLoading() => const CircularProgressIndicator(),
AuthAuthenticated(
displayName: final name
) => Text('Welcome, $name'),
AuthError(
message: final msg,
statusCode: final code,
) => ErrorBanner(
message: msg,
code: code,
),
};新しいAuthStateサブクラス(例:AuthSessionExpired)を追加すると、そのAuthStateを処理するすべてのswitch式で直ちにコンパイルエラーが発生する。これにより、文字列ベースやenum ベースの状態管理を使用するFlutterアプリで一般的なバグの原因である「状態処理の忘れ」が排除される。
Flutterの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
Flutterにおけるレコード・パターン・シールドクラスの統合活用
これら3つの機能を組み合わせたときに真の力が発揮される。Flutter APIコールの実用的なパターンとして、結果型にシールドクラスを、構造化データにレコードを組み合わせる方法がある:
// Generic sealed result type for API operations
sealed class ApiResult<T> {}
class ApiSuccess<T> extends ApiResult<T> {
final T data;
final ({int statusCode, Map<String, String> headers}) metadata;
ApiSuccess(this.data, {required this.metadata});
}
class ApiFailure<T> extends ApiResult<T> {
final String error;
final int? statusCode;
ApiFailure(this.error, {this.statusCode});
}// Consuming the sealed result with pattern matching
Widget buildProductList(ApiResult<List<Product>> result) => switch (result) {
ApiSuccess(
data: final products,
metadata: (statusCode: 200, headers: _),
) when products.isNotEmpty => ListView.builder(
itemCount: products.length,
itemBuilder: (_, i) => ProductCard(products[i]),
),
ApiSuccess(data: final products) when products.isEmpty =>
const EmptyState(message: 'No products found'),
ApiFailure(statusCode: 401) =>
const AuthExpiredBanner(),
ApiFailure(error: final msg) =>
ErrorDisplay(message: msg),
};このパターンにより、nullチェック、型キャスト、ランタイムアサーションが不要になる。すべての状態がコンパイル時に考慮される。
Dart 3機能に関する上級面接質問
2026年のFlutterポジションの技術面接では、Dart 3言語機能の理解度をテストする質問が増加している。以下は構文を超えてアーキテクチャ的思考を問う質問である。
Q: データモデリングにおいてレコードとクラスはどう異なるか?
レコードは構造的に型付けされ、不変で匿名である。同一フィールド型を持つ2つのレコードは同じ型となる。クラスは名目的に型付けされる。同一フィールドを持つ2つのクラスは異なる型である。レコードは関数の戻り値、ローカル集約、マップキーに適する。クラスはメソッド、バリデーション、アイデンティティを必要とするドメインモデルに適する。
Q: enum よりシールドクラス階層が好まれるのはどのような場合か?
enumはバリアントごとのデータを持たない。シールドクラスは各サブタイプが異なるフィールドを保持できる。例えば、AuthAuthenticatedはuserIdを持ち、AuthErrorはmessageとstatusCodeを持つ。enumではこれをモデル化できない。シールドクラスはバリアント名だけでなく、フィールドの深いパターンマッチングもサポートする。
Q: シールドクラスに新しいサブタイプを追加するとコンパイル時に何が起こるか?
Dartアナライザは、そのシールドクラスを処理するすべてのswitch式またはswitch文を直ちに非網羅的としてフラグ付けする。新しいケースがすべての場所で処理されるまでコードはコンパイルされない。このコンパイル時保証が、網羅性チェックを提供しない抽象クラスに対する主な利点である。
Q: パターンにおける_とワイルドカード変数としての_(Dart 3.7以降)の違いを説明せよ。
Dart 3.7以前は、_はパターン内でのみワイルドカードとして機能した。var [_, _, third] = listはパターン_がバインドしないため動作した。パターン外では_は通常の変数名であった。Dart 3.7でこれが統一され、_は関数パラメータやローカル変数を含むあらゆる場所で非バインドとなった。同一スコープ内の複数の_宣言が競合しなくなった。
Q: パターンマッチングはnull安全性を実際にどう改善するか?
パターンはif-caseによる簡潔なnullチェックを可能にする:
// If-case for null-safe extraction
void processUser(Map<String, dynamic> json) {
if (json case {'name': String name, 'age': int age}) {
// name and age are non-nullable here
print('$name is $age years old');
} else {
print('Invalid user data');
}
}パターンはマップ構造の検証、型チェック、非null変数のバインドを同時に行う。パターンがなければ、ネストされたnullチェックと手動の型キャストが必要となる。
移行戦略:既存FlutterアプリへのDart 3機能導入
既存のFlutterコードベースへのDart 3パターン移行は段階的に行える。最も価値の高い変更から着手する。
ステップ1 — Tuple/Pair型をレコードに置換。 Tuple2、Pair、または戻り値のバンドル目的のみで使用されるカスタム2フィールドクラスを検索する。レコードに置き換えることで即座にコード量が削減される。
ステップ2 — if-else型チェックをswitch式に変換。 if (value is TypeA) ... else if (value is TypeB)のチェーンはすべて、パターンマッチングを用いたswitch式になる。これは特にウィジェットの構築や状態処理に適用される。
ステップ3 — 状態階層のシール化。 BLoCの状態、Riverpodのnotifier状態、カスタム状態クラスにsealed修飾子を追加する。コンパイラが状態処理の不完全な箇所をすべて特定する。
ステップ4 — switch文でのガード句の採用。 caseブロック内のifチェックをwhenガードに置き換え、よりフラットで読みやすい制御フローを実現する。
各ステップは独立して価値があり、コードベース全体を一度に修正する必要はない。
Dart 3.10(2025年11月)ではドットショートハンドが導入され、コンテキストから型が既知の場合にEnumType.valueの代わりに.valueと記述できるようになった。これはパターンマッチングとの相性が良く、enum的な型を処理するswitch式の視覚的ノイズを削減する。
レコードはメソッド、カスタムコンストラクタ、継承を持てない。レコード型に振る舞いを追加しようとすることは、クラスまたはシールドクラス階層の方が適切であるサインである。レコードは振る舞いを持たないデータ集約のみに使用すべきである。
まとめ
- レコードは複数値返却のボイラープレートを排除する。Tuple、Pair、単一目的クラスの代わりに使用する
- パターンマッチングはif-else型チェックチェーンを網羅的でコンパイラ検証済みのswitch式に置き換える
- シールドクラスはすべての状態バリアントが処理されることを保証し、ランタイムバグをコンパイル時エラーに変換する
- ガード句(
when)はswitch式をフラットに保ち、caseブロック内のネストされた条件分岐を排除する - Dart 3.7のワイルドカード変数(
_)は、パターンの非バインド動作を通常のパラメータやローカル変数と統一する - 移行は段階的に行える:関数の戻り値にレコードを導入し、型チェックをswitchに変換し、状態階層をシールする
- これらの機能はFlutter技術面接で積極的にテストされている。構文だけでなく「なぜ」の理解が重要である
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

モバイル開発者向けFlutter面接質問トップ20
Flutter面接で頻出する20問を徹底解説します。ウィジェット、状態管理、Dart言語、アーキテクチャ、ベストプラクティスをコード例とともに紹介します。

2026年のFlutter状態管理:Riverpod vs Bloc vs GetX 徹底比較ガイド
Riverpod 3.0、Bloc 9.0、GetXの3大Flutter状態管理ソリューションを、コード例・パフォーマンス・テスト戦略の観点から徹底比較します。

Flutterの状態管理: Riverpod vs BLoC - 完全比較ガイド
Flutterの状態管理におけるRiverpodとBLoCの詳細な比較です。アーキテクチャ、パフォーマンス、テスト容易性、ユースケースから最適なソリューションを選びます。