6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年3月25日 りあクト! 第4章 TypeScriptで型をご安全に(p.171~)

アクセス修飾子

アクセス修飾子をプロパティの宣言時につけるとそのプロパティがどこからどこまでアクセスできるのかを指定できます。 アクセス修飾子を何も指定しない場合は、暗黙的にpublicとなります。

アクセス修飾子 内容
public 自クラス、子クラス、インスタンス全てからアクセス可能。デフォルトでは全てのメンバーがこのpublicになる
private 自クラスからのみアクセス可能
protected 自クラス、子クラスからアクセス可能

アクセス修飾子 private

自クラスからのみアクセス可能なアクセス修飾子です。

class Yano {
  constructor(private _name: string) {
  }

  say(): void {
    console.log(this._name + "です")
  }
}

const yano = new Yano("やの");
console.log(yano._name);
  • 宣言時にprivate修飾子を書きます。
  • インスタンス化して呼び出そうとした場合、以下のコンパイルエラーが発生します。
    Property '_name' is private and only accessible within class 'Yano'.(2341)

_(アンダーバー)を付ける記述方法についてはJavaScriptPrimerに補足がありました。

外から直接読み書きしてほしくないプロパティを_(アンダーバー)で開始するのはただの習慣であるため、構文としての意味はありません。

アクセス修飾子 protected

自クラス、子クラスからアクセス可能なアクセス修飾子です。

class Person {  
 // protectedでnameを宣言する
  protected name;

  constructor(name: string) {
    this.name = name;
  }
}

class Children extends Person {
  constructor(name: string) {
    super(name);
  }
  method() {
    // protectedで宣言されているので、 子クラスでもnameにアクセス可能です
    console.log(this.name);
  }
}

const a = new Person('yano');

// インスタンスからはアクセス不可能です
console.log(a.name); // => Property 'name' is protected and only accessible within class 'Person' and its subclasses.(2445)

クラスは継承よりも合成

TypeScriptではクラスを継承するよりも一部を部品として扱う"合成"が推奨されています。

まずは継承や合成の元となるRectangleクラスを定義します。
class Rectangle {
  readonly name = 'rectangle';
  sideA: number;
  sideB: number;

  constructor(sideA: number, sideB: number) {
    this.sideA = sideA;
    this.sideB = sideB;
  }

  getArea = (): number => this.sideA * this.sideB;
}

const yano = new Rectangle(3, 4);
console.log(yano.getArea());

出力結果: 12

以下のパターンが継承です。

Rectangleクラスを継承したSquareクラスを定義しています。

class Square extends Rectangle {
  readonly name = 'square';
  side: number;

  constructor(side: number) {
    super(side, side);
  } 
}
  • 継承の場合、sideAやsideBなどの不必要なメンバー変数まで継承してしまい、バグの元になりかねないというデメリットがあります 。
  • getArea() メソッドが 完全に共有されているため、親クラスの実装を不用意に変更出来ないため保守性が悪いです。
  • 子クラスでnameプロパティを新たに定義する場合、親クラス Rectangle の name プロパティから readonly 修飾子を削除する必要があります。
以下のパターンが合成です。

Rectangleクラスを独立したただの部品として扱っています。

class Square {
  readonly name = 'square';
  side: number;

  constructor(side: number) {
    this.side = side;
  }

  getArea = (): number => new Rectangle(this.side, this.side).getArea();
}
  • Rectangleクラス内部の実装を知る必要はなく使いたい部分の仕様さえ知っていれば良いです。
  • 依存がないため、合成元の内部の変更に影響されません。

クラスの継承と合成(別の例)

class Yunosuke {
    live = 'thiba';
    likeCountry = 'thai';
    a: string;
    b: string;

    constructor(a: string, b: string) {
        this.a = a;
        this.b = b;
    }
    getArea = (): string => this.a + this.b;
}

const yuno = new Yunosuke("1","2");
console.log(yuno);
console.log(yuno.getArea());

class Yuki extends Yunosuke {
     constructor(a: string){
         super(a,a);
     }
 }

 const yuki = new Yuki("3");
 console.log(yuki);

 class Yano {
     a: string;
     constructor(a: string) {
         this.a = a;
     }
     getArea = (): string => new Yunosuke(this.a, this.a).getArea(); 
 }

 const yano = new Yano("5");
 console.log(yano.getArea());

継承の問題点

  • YukiさんはYunosukeさんを継承しているのでYunosukeさんが引っ越しをしてlive が福岡になってしまったらYukiさんの福岡に行ってしまいます。

合成にするべき理由

  • YanoさんはYunosukeさんのメソッドを合成していてextendsやsuperがないので独立しています。
  • Yunosukeさんが引っ越したとしてもYanoさんが引っ越すことはありません。
  • Yunosukeさんの変更に影響されずに使いたいメソッドだけを使う事ができています。

Reactでは現在、関数コンポーネントを使っていてクラスコンポーネントは非推奨なのですが、書籍には以下のようにありました。

Reactでもコンポーネントをクラスで作成するときは、継承を避けるよう公式ドキュメントに書かれています。

Facebook では、何千というコンポーネントで React を使用していますが、コンポーネント の継承による階層構造を作ることが推奨されるケースはひとつもありませんでした。 props と composition(合成)は、コンポーネントの見た目とふるまいを明示的かつ安全に カスタマイズするのに必要十分な柔軟性を備えています。

参考

りあクト! 【Ⅰ. 言語・環境編】 p.171~