めろたんのアレ

色々書いてくやつ

今更TypeScriptをやる ぱーと2

前回は下の

merotan.hatenablog.com

これの続きをやっていく。

Interfaces

ダックタイピングとかストラクチャルサブタイピングとかのやつ。 JavaとかのInterfaceと違って、明示的に継承とか実装?(implements だっけ?)とかしてなくても、メンバがアレばよしなに動くよ〜みたいな感じ。

interface Test {
  a: string;
  b: () => string;
}

const f = (t: Test) => {
  console.log(t.a);
  console.log(t.b());
}

f({a: '', b: ()=>('a')});
f({a: 1, b: ()=>('a')}); // エラー。 aはstringでないのでだめ。
f({a: '', b: ()=>(2)}); // エラー。 bの返り値がstringではないのでだめ。

基本はこういう感じかな。

Optional Properties

オプションなやつ。あってもなくてもいいよ〜っていうあれっすね。

interface Test {
  a?: string;
  b?: number;
}


const test = (t: Test) => {
  if (t.a) {
    console.log(t.a);
  }
  if (t.b) {
    console.log(t.b);
  }
}

test({ a: '' });
test({ b: 1 });
test({});
test({ b: ''}); // エラー。bがnumberではない。

なくてもよいよ〜とできる。

Readonly properties

そのままですね。 読み取り専用。 各種viewのフレームワーク?ライブラリのプロパティとかにつけるといいのかな?

interface Test {
  readonly a: string;
  b: number;
}

const test = (t: Test) => {
  t.a = ''; // エラー。読み取り専用なので代入できんよ〜。
  t.b = 1;
}

便利〜

ReadonlyArray とかもあるらしい。 Array を読み取り専用にするものらしい。ほかにも ReadonlyMap とか ReadonlySet とかがある様子。

Excess Property Checks

interface Test {
  a?: string;
  b?: number;
}

const test = (t: Test) => {
  if (t.a) {
    console.log(t.a);
  }
  if (t.b) {
    console.log(t.b);
  }
}

test({ a: '', c: 1}); // エラー。cがあるので型がアレ。

みたいなので c とか他のプロパティもいい感じに許容したいよ〜みたいなアレ。

interface Test {
  a?: string;
  b?: number;
  [propName: string]: any;
}

こうするといい感じになるとかなんとか。 あんまりよくない気がする。

interface Test {
  a?: string;
  b?: number;
}

const test = (t: Test) => {
  if (t.a) {
    console.log(t.a);
  }
  if (t.b) {
    console.log(t.b);
  }
}

const t = { a: '', c: 1};

test(t);

これでどうにかなるし、functionの引数を直で書くときはそりゃその引数の型に厳格でいけよ。というだけな気がするし、まあはい。 こういう風に任意のプロパティをアレできるよ〜っていうのは覚えておいたほうが良いねというのはある。

Function Types

functionの型をあれするinterfaceをかくこともできる。

interface Test {
  (a: string): number;
}

interface Test2<T> {
  (a: T): T;
}

let a: Test = (b) => {
  return 1;
}

a = () => (''); // エラー

引数のところでエラーが出てないけどなんでだろ?

JS、引数渡さなかったり、多く渡してもfunctionを実行できたりするからそれ関係でなんかあれなのかな?

arguments とかどうなるんだろ。

interface Test {
  (a: string): number;
}

interface Test2<T> {
  (a: T): T;
}

let a: Test = function () {
  const b = arguments[0];
  return b.length;
}

a('a');

any になったわ。まあそうねという感じ。

Indexable Types

Excess Property Checks のところで出てきた [hoge: string]: string みたいなやつね。

interface Test {
  [index: number]: string;
}

const test = (t: Test) => {
  t[0] = 'asdf';
  t[4] = 'asdf';
  t[5] = 1; // エラー。stringではないから。
};

はい。

index のところの型は stringnumber のみです。

複数定義することも可能。

interface Test {
  [index: number]: string;
  [index: string]: string;
}

ただし、値の型は一致させておく必要があります。 なぜならJSの挙動で、 t[0]t['0'] が同じものを指すので型を違えることはできません。
ベースがJSであるゆえの問題というかなんというか。

まあこんな使い方をすることがなさそうだからなんとアレだけどね。

あと、indexstringを使った場合は他のプロパティもそれの型に合わせる必要があります。

interface Test {
  [index: string]: string;
  x: number; // エラー。stringにしないといけない。
}

理由は上のアレと似たようが話ですね。 t.xt['x'] は同じものを指すので、型を違えることはできないわけですね。

Class Types

クラスに実装(implements)をするぞ〜というアレ。

interface Test {
  a: number;
}

class MyClass implements Test {
  a = 1;
  b = 2;
}

const myClass = new MyClass();

const test = (t: Test) => {
  return t.a;
};

console.log(test(myClass));

こんな感じっすね。

MyClassa を定義しないとエラーになる。

コンストラクタのあれこれもかけるとかなんとか。

interface TestConstructor {
  new (a: number);
}

interface Test {
  a: number;
}

class MyClass implements Test, TestConstructor { // エラー
  a = 1;
  b = 2;

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

こういうふう。 new っていうのを使う。 でこれはエラーになる。

implementsインスタンスの型のチェックになるため、対象のクラスに実装しようとすると変になる。 のでうまくやるには以下のようにする。

interface TestConstructor {
  new (a: number): Test;
}

interface Test {
  a: number;
}

class MyClass implements Test {
  a = 1;
  b = 2;

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

const createMyClass = (t: TestConstructor, a: number) => {
  return new t(a);
}

const test = (t: Test) => {
  return t.a;
};

console.log(test(createMyClass(MyClass, 1)));

こういう感じで別にインスタンスを作るfunctionを定義してよしなに使う感じっぽい。

interface TestConstructor {
  new (a: number): Test;
}

interface Test {
  a: number;
}

const MyClass: TestConstructor = class implements Test {
  a = 1;
  b = 2;

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

const test = (t: Test) => {
  return t.a;
};

const a = new MyClass(1);

console.log(test(a));

こういうふうでも可。

ただ、newインスタンスにしたものの型がnewのところに書いた型になってしまうので、いまいちアレかなぁとおもった。

Extending Interfaces

まあ interface の継承ですね。

interface Test1 {
  a: number;
}

interface Test2 {
  b: string;
}

interface Test3 extends Test1, Test2 {
  c (d: number): number;
}

const test = (t: Test3) => {
  t.a;
  t.b;
  t.c(1);
}

こういう感じ。

Hybrid Types

Function Types といろいろ合体するやつ。

interface Test {
  (a: number): number;
  a: number;
  b: string;
}

const test: Test = (a) => {
  return a;
}

test.a = 1;
test.b = '';

こういう感じ。 ただ、 testaとかが後で追加みたいな感じでちょっとイケてないので、 getTest みたいなのをなんか作ってまとめちゃうとかがいいっぽい。

interface Test {
  (a: number): number;
  a: number;
  b: string;
}

const getTest = (a: number, b: number): Test => {
  const test: Test = (a) => {
    return a;
  }
  test.a = 1;
  test.b = ''; 

  return test;
}

こういう感じ。

Interfaces Extending Classes

クラスをinterfaceとして扱えるという感じなのかな?

class Test1 {
  private a = 1;
  add(b: number): number {
    return b + 1;
  }
}

interface Test2 extends Test1 {
  sub(c: number): number;
}

class Test3 extends Test1 implements Test2 {
  sub(c) {
    return c - 2;
  }
}

class Test4 implements Test2 { // エラー。 private の a が無いという感じ。
  add(b) {
    return b;
  }
  sub(c) {
    return c;
  }
}

private のプロパティとかを作ると、 interface として扱った場合に、private の宣言がアレするので死ぬ感じっぽい。 なので、使い所はよくよく考えたほうが良い様子。

はい

ガバーっとみた。 なるほどな〜というのと、これは使わないだろうなぁ…というのがあったりした。

また引き続きやっていく。