方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Dart/TypeScript】共通点と違い。型推論ありの静的型付け・単一継承・抽象クラス・Mixin・非同期

この記事は約12分で読めます。

はじめに

FlutterとReactの両方のフレームワークを別々のプロジェクトで使っているので、プログラミング言語(DartとTypeScript)とフレームワーク(FlutterとReact)の共通点と違いについて、備忘録としてまとめておく。この記事ではプログラミング言語に関して。

DartとTypeScriptの共通点と違い

https://dart.dev/language

https://www.typescriptlang.org/docs/handbook/typescript-from-scratch.html

型システム:型推論ありの静的型付け・型注釈オプショナル

Dart, TypeScriptともに静的型付け言語(コンパイル時に型が決定する)だが、型注釈はオプショナルで強力な型推論が働く。

Dart:var(変数キーワード)・const・final・late(変数修飾子)

https://dart.dev/language/variables

  • var:初期化後も再代入が可能。型は初期化時に右辺の式からコンパイラによって推論される
  • const:コンパイル時定数。varとは組み合わせ不可
  • final:コンパイル時ではなく実行時定数。varとは組み合わせ不可
  • late:実行時に設定される遅延初期化。変数が宣言された後で初期化が行われ、初期化が行われるまで変数は値を持たない。varと組み合わせれば初期化後の再代入が可能。
Dart
// 型推論
var number = 42; // `number`はintと推論される
number = 100;    // 再代入可能
final name = 'Alice'; // `name`はStringと推論される

// 変数修飾子
const int x = 10; // コンパイル時定数。その後再代入不可能
final int x = 10; // 実行時定数。その後再代入不可能
late int x;
void main() {
  x = 10; // 初期化が遅延されているため、実行時に初期化
  print(x); // 10
}
late var number = getNumber(); // 実行時に初期化されるが、再代入可能

int add(int a, int b) {
  return a + b;
}

TypeScript:let(変数キーワード)const・readonly(変数修飾子)

  • let:変数宣言。再代入可能。同じスコープ内での再宣言は不可能。
  • const:コンパイル時定数。再代入不可
  • readonly:実行時定数というよりは変更不可のプロパティ。主にクラスのプロパティやインターフェースのフィールドに対して使用される。Dartのfinalに近い概念だが、readonlyは変数宣言には使用されない。
  • dartのlateに直接相当する機能はない
TypeScript
// 型推論
let number = 42; // `number`は`number`型と推論される
number = 39; //再代入可能
const name = 'Alice'; // `name`は`string`型と推論される
name = 'Bob'; //エラー。再代入不可

// 変数修飾子
const PI = 3.14; // コンパイル時定数

class Example {
    readonly constantValue: number = 10; // クラスのプロパティとしてのreadonly
}
const example = new Example();
// example.constantValue = 15; // エラー: cannot assign to 'constantValue' because it is a read-only property.


// add関数のパラメータ`x`と`y`、戻り値は型推論により`any`型とされる
function add(x, y) {
  return x + y;
}

// add関数のパラメータに型注釈を追加(推奨)
function add(x: number, y: number) {
  return x + y; // 戻り値は`number`型と推論される
}

関数:引数(オプション・名前つき・必須)

Dart

  • 一般的な定義の他にオプション引数/名前つき引数/必須引数が設定できる
Dart
// オプション引数
// 引数がなくても呼び出せる(デフォルトの値が使われる)
void func1(String param1, [int param2=0]){
  print("$param1,$param2");
}
// 名前つき引数
// 名前を指定して呼び出せる
void func2({String? param1, int? param2}){
  print("$param1,$param2");
}
// 必須引数
// 必ず引数を渡すことを強制できる
void func3({required String param1, required int param2}){
  print("$param1,$param2");
}
void main() {
  func1("func1");
  func2(param1:"func2");
  func3(param1:"func3",param2:3);
}

// 出力結果
// func1,0
// func2,null
// func3,3

TypeScript

TypeScript
// オプション引数
// 引数がない場合、undefinedがデフォルト値として使われる
function func1(param1: string, param2?: number) {
  console.log(`${param1}, ${param2}`);
}

// 名前付き引数(オブジェクトを引数として使用)
// 引数の順番に関係なく、名前で指定して値を渡すことができる
function func2({param1, param2}: {param1?: string, param2?: number}) {
  console.log(`${param1}, ${param2}`);
}

// 必須引数
// 引数の前に`?`をつけないと、その引数は必須となる
function func3(param1: string, param2: number) {
  console.log(`${param1}, ${param2}`);
}

func1("func1");
func2({param1: "func2"});
func3("func3", 3);

オブジェクティブ指向:抽象クラス・Mixins

抽象クラス

  • Dartの抽象クラスはサブクラスに具体的な実装を強制する手段を提供し、TypeScriptのinterfaceはクラスに特定の構造を強制する手段を提供する。

Dart:

  • オブジェクト指向言語。インスタンスの生成:newキーワードはオプショナル
  • 単一継承:extends→Mixin(特定のメソッドやプロパティを複数のクラス間で共有)でカバー
  • _で始めるとスコープがプライベートになる。
  • static:クラスに属するフィールド/メソッドになる
  • setter/getterでフィールドにアクセスする。
  • 抽象クラス:abstract。継承での利用を前提とした、インスタンス化できないクラスを作成。
Dart
abstract class Shape { // 抽象クラス定義
  void draw(); // 抽象メソッド
  void move() {
    print('Moving a shape');
  } // 具体的なメソッド実装
}

class Circle extends Shape { // サブクラス定義
  void draw() {
    print('Drawing a circle');
  } // 抽象メソッドの具体的な実装
}

void main() {
  var circle = Circle();
	var circle = new Circle(); //newキーワードは使っても使わなくても良い。上記と同じ
	var shape = Shape();  // エラー: 抽象クラスのインスタンスを生成しようとしている
  circle.draw(); // "Drawing a circle" を出力
  circle.move(); // "Moving a shape" を出力

Mixins

  • クラスのコードを複数のクラス階層で再利用するための機能
  • mixinclassキーワードの代わりにmixinキーワードを使って定義する
  • mixinは、withキーワードを使用して他のクラスによって”ミックスイン”されることを意図しており、単独でインスタンス化できない。
  • ミックスインされたクラスは、mixinが提供するメソッドやプロパティにアクセスできる。
Dart
mixin Painter {
  void paint() {
    print('Painting...');
  }
}

mixin Writer {
  void write() {
    print('Writing...');
  }
}

class Artist with Painter, Writer {}

void main() {
  final artist = Artist();
  artist.paint();  // "Painting..." を出力
  artist.write();  // "Writing..." を出力
}

TypeScript:

  • 単一継承→interfaceでカバー。interface:オブジェクトの形状(プロパティやメソッドのシグネチャ)を定義できるが、メソッドの具体的な実装を含めることはできない(抽象クラスは含めることができる)。直接インスタンス化できない(抽象クラスと同じ)。
TypeScript
interface Shape {
  draw(): void; // メソッドのシグネチャ
  move(): void; // メソッドのシグネチャ
}

class Circle implements Shape {
  draw() {
    console.log('Drawing a circle');
  } // インターフェイスで定義されたメソッドの実装

  move() {
    console.log('Moving a circle');
  } // インターフェイスで定義されたメソッドの実装
}

let circle = new Circle();
circle.draw(); // "Drawing a circle" を出力
circle.move(); // "Moving a circle" を出力
  • アクセス修飾子private, public, protectedを使ってメンバーの可視性を明示的に制御する。
TypeScript
class Student {
  fullName: string;
  constructor(
    public firstName: string,
    public middleInitial: string,
    public lastName: string
  ) {
    this.fullName = firstName + " " + middleInitial + " " + lastName;
  }
  getFullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
}

interface Person {
  firstName: string;
  lastName: string;
  getFullName(): string;
}

function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

document.body.textContent = greeter(user);

非同期:Future(Dart)・Promise(TypeScript)

Dart

  • Futureasync/awaitを用いた非同期プログラミング
  • Futureは、将来のある時点で値を返す可能性があるオブジェクト
Dart
Future<String> fetchUserData() {
  // 擬似的にユーザーデータの取得を非同期で行う例
  return Future.delayed(Duration(seconds: 2), () {
    return 'User data';
  });
}

void main() async {
  print('Fetching user data...');
  String userData = await fetchUserData();
  print(userData); // 2秒後に'User data'が出力される
}

TypeScript

  • Promiseasync/awaitを用いた非同期プログラミング(JavaScriptと同じ)
  • Promiseは将来の値を表すオブジェクト
TypeScript
function fetchUserData(): Promise<string> {
  // 擬似的にユーザーデータの取得を非同期で行う例
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('User data');
    }, 2000);
  });
}

async function main() {
  console.log('Fetching user data...');
  const userData = await fetchUserData();
  console.log(userData); // 2秒後に'User data'が出力される
}

main();

コメント

タイトルとURLをコピーしました