mcpField
D
dart-3-updates

dart-3-updates

github.com/evanca開発663223 回インストール3 週間前に更新
リポジトリ

dart-3-updates について

Dart 3 の言語機能を使う Dart 開発者向けのスキルガイドです。分岐、パターン、レコードの活用方法と、既存コードを更新する手順を扱います。

主な機能

  1. 01switch と if-case の分岐
  2. 02パターンマッチと分解
  3. 03レコードと適用場面の理解
  4. 04sealed 型による網羅性チェック
  5. 05既存の Dart コードの段階的な更新

活用シーン

  1. 01if-else 連鎖や switch 文のリファクタリング
  2. 02コレクションやオブジェクトからの分解
  3. 03レコードとデータクラスの選択
  4. 04コードを Dart 3 の書き方に更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/evanca/flutter-ai-rules --skill dart-3-updates

インストール後、Claude Code で/dart-3-updatesを入力して読み込まれたか確認します。

詳細

Dart 3 の更新スキル

Dart 3 の言語機能(分岐、パターン、パターンの種類、レコード)を正しく、慣用的に適用します。

使用する場面

次のような場合に、このスキルを使用します。

  • switch 文や if-else チェーンを記述またはリファクタリングするとき。
  • データを保持する新しいクラスを作成し、シールドクラス、レコード、通常のクラスから選択するとき。
  • マップ、リスト、レコード、オブジェクトから値を分解するとき。
  • パターン、網羅性チェック、または switch 式を使って、Dart 3 より前のコードを最新化するとき。

1. 分岐

if / if-case
// Standard if
if (score >= 90) {
  grade = 'A';
} else if (score >= 80) {
  grade = 'B';
} else {
  grade = 'C';
}

// if-case: match and destructure against a single pattern
if (pair case [int x, int y]) {
  print('$x, $y');
}
  • if の条件は bool に評価される必要があります。
  • if-case では、パターン内で宣言された変数は、一致した分岐内でのみ有効です。
  • パターンが一致しない場合、(存在すれば)制御は else 分岐に移ります。
switch 文
switch (command) {
  case 'quit':
    quit();
  case 'start' || 'begin': // logical-or pattern
    startGame();
  default:
    print('Unknown command');
}
  • 一致した case の本体が実行され、末尾に移ります。break は不要です。
  • 空でない case は、continue、throw、または return で終了できます。
  • 一致しない値を処理するには、default または _ を使用します。
  • 空の case はフォールスルーします。空の case でのフォールスルーを防ぐには、break を使用します。
  • 順番どおりでないフォールスルーには、ラベル付きの continue を使用します。
  • 複数の case で本体を共有するには、論理 OR パターン(case a || b)を使用します。
switch 式
final color = switch (shape) {
  Circle() => 'red',
  Square() => 'blue',
  _ => 'unknown',
};
  • case は省略し、本体には => を使用し、各 case はカンマで区切ります。
  • デフォルトには default ではなく _ を使用します。
  • 値を生成します。
網羅性
  • Dart はコンパイル時に、switch 文と式の網羅性をチェックします。
  • 網羅性を満たすには、default/_、列挙型、または sealed 型を使用します。
sealed class Shape {}
class Circle extends Shape {}
class Square extends Shape {}

// Dart knows all subtypes — no default needed:
String describe(Shape s) => switch (s) {
  Circle() => 'circle',
  Square() => 'square',
};
ガード節
switch (point) {
  case (int x, int y) when x == y:
    print('Diagonal: $x');
  case (int x, int y):
    print('$x, $y');
}
  • パターンの後に when condition を追加すると、一致条件をさらに絞り込めます。
  • if-case、switch 文、switch 式で使用できます。
  • ガードが false の場合、次の case に処理が進みます。

2. パターン

パターンは、一致判定と分解のために値の形状を表します。

用途
// Variable declaration
var (a, [b, c]) = ('str', [1, 2]);

// Variable assignment (swap)
(b, a) = (a, b);

// for-in loop destructuring
for (final MapEntry(:key, :value) in map.entries) { ... }

// switch / if-case (see Branches section)
  • ワイルドカード _ は、一致した値の一部を無視します。
  • リストパターンの残余要素(...)は、残りの要素を無視します。
  • case パターンは失敗可能です。一致しない場合、次の case に処理が進みます。
  • case 内で分解された値は、その case の本体内でのみ有効なローカル変数になります。
オブジェクトパターン
var Foo(:one, :two) = myFoo;
JSON/ネストされたデータの検証
if (data case {'user': [String name, int age]}) {
  print('$name, $age');
}

3. パターンの種類

パターン構文説明
論理 ORp1 || p2いずれかの分岐が一致すると一致します。すべての分岐で同じ変数をバインドする必要があります。
論理 ANDp1 && p2両方が一致すると一致します。変数名が重複してはいけません。
関係== c、< c、>= c値を定数と比較します。範囲を表すには && と組み合わせます。
キャストsubpattern as Type型を検証してから、内側のパターンを照合します。型が一致しない場合は例外がスローされます。
Null チェックsubpattern?null 以外に一致し、null 非許容型をバインドします。
Null アサーションsubpattern!null 以外に一致し、一致しない場合は例外がスローされます。宣言で使用すると、null を排除できます。
定数42、'str'、const Foo()値が定数と等しい場合に一致します。
変数var name、final Type name一致した値を新しい変数にバインドします。型付き形式は、宣言された型に一致する場合のみ一致します。
ワイルドカード_、Type _値をバインドせずに、任意の値に一致します。
括弧付き(subpattern)優先順位を制御します。
リスト[p1, p2]位置でリストに一致します。残余要素を使用しない場合、長さも一致する必要があります。
残余要素...、...rest任意の長さの末尾部分に一致するか、残りの要素を収集します。
マップ{'key': subpattern}キーでマップに一致します。キーが存在しない場合、StateError がスローされます。
レコード(p1, p2)、(x: p1, y: p2)形状でレコードに一致します。推論できる場合、フィールド名は省略できます。
オブジェクトClassName(field: p)型で一致し、ゲッターを通じて分解します。追加のフィールドは無視されます。
  • 優先順位の低いパターンをグループ化するには、括弧を使用します。
  • すべてのパターンの種類は、ネストして組み合わせることができます。

4. レコード

// Create
var record = ('first', a: 2, b: true, 'last');

// Type annotation
({int a, bool b}) namedRecord;

// Access
print(record.$1);   // positional: 'first'
print(record.a);    // named: 2
  • レコードは匿名、不変、固定サイズの集約です。
  • 各フィールドは異なる型を持つことができます(異種)。
  • フィールドには組み込みゲッター($1、$2、.name)を介してアクセスします。セッターはありません。
  • 同じ形状で、フィールド値が等しい場合、2 つのレコードは等しいとみなされます。
  • hashCode と == は自動的に定義されます。
複数の戻り値
(String name, int age) userInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['age'] as int);
}

var (name, age) = userInfo(json);
// Named fields:
final (:name, :age) = userInfo(json);
レコードとデータクラスの比較

次の場合はレコードを使用します。

  • 1 つの関数から複数の値を返す場合(小規模で、一度限りの使用)。
  • コードベース内で再利用しない少数の値を、ローカルにまとめる場合。
  • 追加の振る舞いを持たずに、構造的な等価性が必要な場合。

次の場合はクラスを使用します。

  • 複数のファイルや機能で型を再利用する場合。
  • メソッド、カプセル化、継承、または copyWith が必要な場合。
  • 型が公開 API または長期にわたって使用されるデータモデルの一部である場合。
  • 形状の変更を、コードベース全体で型システムに検出させる必要がある場合。
その他のベストプラクティス
  • 可読性と保守性を高めるために、レコード型には typedef を使用します。
  • レコード型のエイリアスを変更しても、コードベース全体で型安全性が保証されるわけではありません。完全な抽象化を提供するのはクラスだけです。

5. 移行ワークフロー

Dart 3 より前のコードを最新化する場合は、次の手順に従います。

ステップ 1 — if-else チェーンを switch 式に置き換える
// Before (pre-Dart 3)
String label;
if (status == Status.loading) {
  label = 'Loading...';
} else if (status == Status.success) {
  label = 'Done';
} else {
  label = 'Error';
}

// After (Dart 3)
final label = switch (status) {
  Status.loading => 'Loading...',
  Status.success => 'Done',
  Status.error => 'Error',
};
ステップ 2 — 抽象クラスの階層を sealed クラスに変換する
// Before
abstract class Result {}
class Success extends Result { final String data; Success(this.data); }
class Failure extends Result { final String error; Failure(this.error); }

// After — enables exhaustive switch
sealed class Result {}
final class Success extends Result { const Success(this.data); final String data; }
final class Failure extends Result { const Failure(this.error); final String error; }
ステップ 3 — 複数の戻り値に分割代入を使用する

複数の値を返すことだけを目的とするラッパークラスを、レコードに置き換えます。

ステップ 4 — 検証する

変更を加えるたびに dart analyze を実行し、網羅性と型安全性を確認します。


コメント