LoginSignup
9
6

More than 1 year has passed since last update.

便利なTypeScriptの書き方4選

Last updated at Posted at 2021-12-25

はじめに

今年から本格的にTypeScriptを書き始めたので、個人的に便利だなと思った便利記法4つを選んでまとめてみました。

※個人ブログから技術的アウトプットはQiitaへ引っ越ししたので、こちらは過去に書いたブログとなります。

1/ 変数末尾に 「 ! 」 をつけることで初期化チェックを割愛

きっかけ

継承がうまくいかないと思った時に、先輩からレビューいただき学びがありました。

うまくいかなかったこと

エラーにはなっていないが、冗長的なクラス文

class Personality {
  personality: PersonalityEntity;
  constructor(personality: Personality, channel: Channel) {
    this.personality = {
      id : personality.id,
      name : personality.name, 
      .....省略....
      age : personality.age
         };
       }
     }

//↑を継承
class PersonalityDetail extends Hoge {
  personality:PersonalityDetailEntity
  include:hoge;
  constructor(personality: Personality ,channel: Channel, fugas : Fuga) {
    super(personality) // ---- ①
    this.personality = {
      id:personality.id, 
      name:personality.name, 
       .....省略....
      age : personality.age,
      gender : personality.gender(継承先で新しく追加したプロパティ)
        }
    this.include = fugas.map(fuga => { 
     .....省略.... 
     });
  }

内容は違うけど、ざっくりこんな感じで書いていて、

構文には間違っていないので動くけど、
継承してる割にはPersonalityDetailのconstructorのthisの中身が同じで冗長的なふうに感じ、リファクタリングできないかなと感じていた。

よくやってしまうミス

継承をしない

現状、上記のコードだとsuperで継承しているPersonalityクラスのpersonalityプロパティが生成された後(上記①)に、継承先で新しく追加したプロパティ(ここではgender)と一緒に上書きしてしまっているような状態で、二重の初期化がかかり、superが意味のない処理になっています

そこで、継承をせずに下記のように修正。

class Personality {
  personality: PersonalityEntity;
  constructor(personality: Personality, channel: Channel) {
    this.personality = {
      id : personality.id,
      name : personality.name, 
       .....省略....
      age : personality.age
      };
    }
 }

class PersonalityDetail { //継承しない
  personality : PersonalityDetailEntity
  include : hoge;
  constructor(personality: Personality ,channel: Channel, fugas : Fuga) {
    this.personality = { 
      id : personality.id, 
      name : personality.name, 
      .....省略....
      age : personality.age,
      gender : personality.gender 
     }
     this.include = fugas.map(fuga => { 
      .....省略.... 
      });
    }
 }

上記のように継承しないで PersonalityDetailを1から新しく作り、親であったPerosonalityのクラスは全く関係ないよと宣言したパターン。

これだと、上下のクラスで全く依存関係はないので、初期化はthis以降の1回だけで、上書きは起きない。が、しかし

継承をしないと

上記の場合だと、PersonalityDetailはPersonalityの子クラスではあるため、もし継承を外したら、親クラスのPersonalityの作り方が変わった場合に、子クラスのPersonalityDetailもその都度修正しなくちゃいけなくなるのでエラーにならなくても望ましくない。

解決方法

スプレッド構文で親クラスのコンストラクタ内のプロパティを継承

class PersonalityDetail extends Hoge { //継承する
  personalit : PersonalityDetailEntity
  include : hoge;
  constructor(personality: Personality ,channel: Channel, fugas : Fuga) {
    super(personality,channel) 
    this.personality = {
       ...this.feed, //スプレッド構文でpersonalityプロパティを継承
       gender : personality.gender (継承先で新しく追加したプロパティ
        }
    this.include = fugas.map(fuga => { 
     .....省略.... 
     })
   }

このように、スプレッド構文「...」で親クラスのコンストラクタ内で定義したプロパティを丸っと(←ここを良い感じにしてくれる)継承します。

スプレッド構文を使うことで、冗長さはなくなり見通しの良いコードになりました。

スプレッド構文「...」ついては別途まとめます。

しかしこれだけだと、this.personalityを使おうとするとプロパティ 'personality' は割り当てられる前に使用されています。

「コンストラクタで初期化できないエラー」がでるんで、無視させる必要があります。

今日のテーマはここからです。笑

そこで、

class PersonalityDetail extends Hoge {
  personality! : PersonalityDetailEntity
  include : hoge;
  constructor(personality: Personality ,channel: Channel, fugas : Fuga) {
    super(personality,channel) 
    this.personality = {
      ...this.field
      gender : personality.gender 継承先で新しく追加したプロパティ
       }
    this.include = fugas.map(fuga => { 
     .....省略.... 
    })
  }

と、Personalityという変数末尾に「 ! 」 をつけることで初期化チェックを割愛(無視)できます。

↑記事によると「newした後に他のメソッドが呼ばれるまでにプロパティが初期化できる」ということがわかる場合があり、
そういう時のための抜け穴という訳らしい。

これで継承もうまくいった!

今回はあくまで例文でしたが、最終的にはこんな感じ。

class Personality {
  personality : PersonalityEntity;
  constructor(personality: Personality, channel: Channel) {
  this.personality = {
    id : personality.id,
    name : personality.name, 
    .....省略....
    age : personality.age
    };
  }
}

class PersonalityDetail extends Hoge {
  personality! : PersonalityDetailEntity
  include : hoge[];
  constructor(personality: Personality ,channel: Channel, fugas : Fuga) {
    super(personality,channel) 
    this.personality = {
      ...this.field
      gender : personality.gender (継承先で新しく追加したプロパティ)
        }
    this.include = fugas.map(fuga => { 
      .....省略.... 
    })
 }

【まとめ】 変数末尾に 「 ! 」 をつける

  • super(継承したプロパティ)とthis.継承したプロパティで同じ継承したプロパティをセットしていたら、 せっかくsuperで親から継承している処理に対し、thisで上書きしていることになり2*重の初期化*が掛かってしまうことになる。
  • スプレッド構文「...」で親クラスのコンストラクタ内で定義したプロパティを丸っと(←ここを良い感じにしてくれる)継承できる。
  • 変数末尾に「 ! 」 をつけることで初期化を割愛(無視)できる

2/ スプレッド構文の便利な使い方

スプレッド構文とは

記述方法

...//ピリオド3つ

です。なんとシンプル。

意味

JavaScriptやTypeScriptにおいて

💡 オブジェクトのプロパティや配列の要素を引き継いで、展開できる

になります。

具体例

こんな感じで使う

const person = { 
  age: 34, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa', 
}; 

console.log(person); 

const actress = {
  ...person, // ここでスプレッド構文を使う 
  area: 'Tokyo', 
  title: 'Beauty Actress', 
}; 

console.log(actress); 
// arr2 = { foo: 1, bar: 2 }

です

実行結果

{ 
  age: 34, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa' 
} 
{ 
  age: 34, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa' ,
  area: 'Tokyo', 
  title: 'Beauty Actress' 
}

です。

内容はさておき、上のコードではまず person という変数で、
名前 (firstName, lastName) や年齢 (age) などのプロパティを設定したオブジェクトを作っています。

次に person の内容をスプレッド構文で引き継いで
さらにエリア (area) とか役職 (title) などのプロパティを追加設定して、actress オブジェクトを作成しています。

このように、元のプロパティの内容を全て引き継いで、展開することを可能にするのがスプレッド構文

値を全部引き継いでから書き換える

スプレッド構文で値を引き継いだ後に、データを更新(上書き)することもできます

例えば、上の例で、personの内容の中で、ageだけ書き換えて、

子のオブジェクトに引き継ぐには次のようにかけます。

const person = { 
  age: 34, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa' 
};

console.log(person); 

const actress = { 
  ...person, 
  age: 29, //34から変更  
  area: 'Tokyo', 
  title: 'Beauty Actress'
}; 

console.log(actress);

です。

実行結果

{ 
  age: 29, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa' 
} 
{ 
  age: 34, 
  firstName: 'Keiko', 
  lastName: 'Kitagawa' ,
  area: 'Tokyo', 
  title: 'Beauty Actress' 
}

配列の場合

配列も引き継げます。

const foo = [1, 2]; // 配列のクローン  
const bar = [...foo]; // 実行結果=> [1, 2]  // 要素を追加して新しい配列を生成
const baz = [...foo, 3, 4]; // 実行結果 => [1, 2, 3, 4]  // 配列をマージ 
const hoge = [...foo, ...bar]; // 実行結果 => [1, 2, 1, 2]

Array.prototype.concat()と同じようなことが簡潔にかけます。

配列を分割する

スプレッド構文の組み合わせで、配列要素を取り出しつつ、残りを変数に代入することも可能。

let [a, b, ...other] = [1, 2, 3, 4, 5]; 
console.log(a); // 1  
console.log(b); // 2  
console.log(other); // [3, 4, 5]

このように分割代入と組み合わせると配列の分割も簡単。分割代入は別途書きます。

【まとめ】 スプレッド構文

  • スプレッド構文の記述方法は...とピリオド3つ
  • オブジェクトのプロパティや配列の要素を引き継いで、展開できる
  • スプレッド構文で値を引き継いだ後に、データを更新(上書き)することも可能。
  • スプレッド構文と分割代入との組合わせも可能

参考

3/ 分割代入とは

初めて見たときは「なんじゃこりゃ」と思ったが今でも覚えてる構文です。

分割代入とは

分割代入とは

💡 配列とオブジェクトを分解して、要素とプロパティ値を1つ1つの変数に分解するための構文。

言葉だと、わかりずらいので例文。

const player = ['大谷翔平' , 26 , 193 , 165 ]

この配列の中の4つの値は、それぞれ名前・年齢・身長・球速を表しています。そこで、分かりやすいように名前をつける。

const name = player[0]; 
const age  = player[1]; 
const height = player[2]; 
const speed  = player[3];

これは間違ってはないけど、手間である。

そこで、分割代入が使える。

const name = player[0]; 
const age  = player[1]; 
const height = player[2]; 
const speed  = player[3];`

const player = ['大谷翔平' , 26 , 193 , 165 ] 
const [name, age, height, speed] = player; 

console.log(name) // ['大谷翔平'] 
console.log(age) // [26] 
console.log(height) // [193] 
console.log(speed) // [165]`

一部の値だけ使う場合

ちなみにnameとspeedだけ使いたい場合は、

const [name , speed] = player;

こうも書ける。

上記と同じく一部の値を省略することもできる。

const player = ['大谷翔平' , 26 , 193 , 165 ] 
const [, , height, speed] = player; 

console.log(height) // [193] 
console.log(speed) // [165]

スプレッド構文との併用

スプレッド構文を使うと、残りの要素をまとめて拾うことができる。

const player = ['大谷翔平' , 26 , 193 , 165 ]
const [name, age, ...other] = player;

console.log(other)
> [ 193 , 165 ]

オブジェクトを作る場合

例えば、これをつかってオブジェクトを作る場合

const player = ['大谷翔平' , 26 , 193 , 165 ] 
const [, , height, speed] = player; 
const baseballPlayer = { 
  name : name, 
  age : age , 
  hight : hight, 
  speed : speed,
}

↑はもっと簡単にかける

const player = ['大谷翔平' , 26 , 193 , 165 ]
const [, , height, speed] = player;
const baseballPlayer = { 
  name,
  age ,
  hight,
  speed,
}

と、オブジェクトを作成する際、

オブジェクトのキー名(変数名)と値(value)が同じ場合、値(value)を省略して、変数名だけ記述

することができます。またこれは別途書きます。

実務で配列の分割代入を使用する例

上記は説明するために書いたが、実際の運用コードでは [1, 2] のような要素が固定された配列はあんまり登場しない。

では、どんなときに配列の分割代入を使用するメリットがあるのか?

経験上、実務では、配列の順番に意味がある時 によく使う。

例えば

const hogeChannel = ....省略....
const hogeChannel = ....省略....
const hogeChannel = ....省略....
const hogePersonality = ....省略....

const [episodes, channel, personality] = 
  await Promise.all([hogeIncludes , hogeChannel , hogePersonality]); 
  return { channel, episodes, personality, };

とこんな感じ関数の引数として配列の順番を意識して使ったり、上の続きで、

async function getAllData(id: string):

....省略....

const [episodes, channel, personality] = 
  await Promise.all([hogeIncludes , hogeChannel , hogePersonality]);
    return { channel, episodes, personality, };
}

async function main(hoge: Request): Promise<Response> { 
  return getAllData(hoge.id) 
    .then(({ channel, episodes, personality }) => { 
      return new Response( channel, episodes, personality); 
})

とこんな感じで、

(Rest)APIのレスポンスを返す際に、今まで定義した変数をまとめてインスタンスを作成し、コンストラクタにつっこむ時に上手く使ったりする。

【まとめ】 分割代入

  • 分割代入とは、配列(やオブジェクト)を分解して、要素とプロパティ値を1つ1つの変数に分解するための構文。
  • 配列の要素は省略可能。
  • 興味のない要素は、「,,,」で変数名を省略することで無視ができる。
  • スプレッド構文を使うと、残りの要素をまとめて拾うことができる。
  • 配列と同じく、変数宣言と同時にオブジェクトの要素の値が代入できる。
  • 経験上、実務では配列の順番に意味がある時関数の引数や、APIのレスポンスを返す際に、今まで定義した変数をまとめてインスタンスを作成し、コンストラクタにつっこむ時に分割代入を使う。

参考

4/ valueの省略記法

key:valueの省略

といっても上記がすべて。

ES6(ES2015)にて、オブジェクトのプロパティのキー名と値の変数名が等しい場合、

省略して変数名だけで記述することができます。

const foo = 1 
const arr = { 
  foo: foo 
} 

// ES6 
const foo = 1 
const arr = {
    foo
} 
// => arr = { foo: 1 } 

const arr2 = { 
  foo,  //foo : foo と key : value = 変数名:値 の構成で書かなくてOK 
  bar : 2 
} 
// arr2 = { foo: 1, bar: 2 }

このように、foo : fookey : valueの構成が同じ場合、

つまり変数名 : 値の構成が同じ場合、変数名だけ書いてvalue(値)を省略することができる。

ECMAScript 6: New Features: Overview and Comparison

【まとめ】 valueの省略記法

  • オブジェクトのプロパティのキー名と値の変数名が等しい場合、 変数名 : 値の構成で書かずに、変数名だけ書いてvalue(値)を省略することができる。

参考

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6