『プログラミングTypeScript』勉強会用
疑問②
ふともじ
読書会用のテスト
簡単にだけど、tsconfig.json
やeslintrc.js
の各設定項目がそれぞれどういう意味合いを持っているのかが説明されているの地味に助かる(p12~, p306~)。
let d = [1, 'a']
d.map(_ => {
if (typeof _ === 'number') return _ * 3
})
TS関係ないけれども、ここに出てくるアンダースコアが謎。やってることはなんとなくわかるが(p36)
ただの変数名ですね!!(変数名のルール上、_ だけでもOK)
実際 _ は、使われないけどメソッド仕様上受け取らざるを得ない変数の名前とかに
よく使われるイメージですが、本書では全体的に「どの名前でもいいけど、ちゃんとした名前にすると
本質的な理解を阻害する恐れがあるので _ にしておこう」みたいな感じなように思いました。
ちなみにeslintにこんな感じに
設定することで「_」という変数名だけ、linterのno-unused-varsを回避できます
shintaroさん、わかりやすすぎる解説ありがとうございます!!
なんとなく変数な気はしてはいましたが実際にアンダースコアのみの変数名を目の当たりにすると変な感じがしますね笑
ESLintルールについてもありがとうございますー!今度参考にして使ってみます!🥳
関数を宣言する際、仮引数(仮パラメーター)の値は基本的に型を指定する(返り値は多くの場合型推論に任せる)。
仮引数に「?」を付けることで、引数として渡さなくてもよいオプションパラメーターとすることができる(※仮引数リストの最後に置く必要あり)。
オプションパラメーターの代わりにデフォルトパラメーターを指定することが出来る。デフォルトパラメーターを指定した場合は、そのデフォルト値の値を型推論してくれるため、明示的に型注釈する必要はない。
また、オプションパラメーターより、デフォルトパラメーターの方が頻繁に使う(p50)。
決まった個数の引数を取る関数 → 固定アリティ関数(fixed-arity function)
任意の数の引数を取る関数 → 可変長引数関数(variadic function)
なんかカッコいい…笑(p50)
可変長引数を関数で簡単に扱うことができる「レストパラメーター」(p52)
ジェネレーター関数難しい。。。(p54)
『プログラミングTS』の中で例として書かれているようなフィボナッチ数みたいなある一定の規則性のあるものを求めるときに有効なのかな?
一応ジェネレーターの説明としては「ある一連の値を生成するための便利な方法」って書いてあるし。
- ジェネレーター →ある一連の値を生成するための便利な方法
- イテレーター → ジェネレーターが生成した値を利用するための方法
ジェネレーターは反復可能オブジェクト(Iterable Object)を返すのか?
反復可能オブジェクトはmapとかで回せるみたいな認識で合ってる?
ジェネレーターは反復可能オブジェクト(Iterable Object)を返すのか?
その認識。。
ジェネレーターは反復可能オブジェクト とイテレーターの両方である
(P.56)
反復可能オブジェクトはmapとかで回せるみたいな認識で合ってる?
多分あっていると思います。。
なんかループできないデータをループ構文に渡したりすると確かに
Symbol.iteratorが含まれないオブジェクト が渡されました
みたいなエラーが出るのを見たことある気がする
返信ありがとうございます!
ジェネレーターとイテレーターのところ今まで意識したことなかった用語だらけで震えてました😂
もうちょい勉強してみます!
関数に予め用意した関数用の型を付ける(呼び出しシグネチャ(call signature), 型シグネチャ(type signature))
// アロー関数チックに書く方法
type Sum = (a: number, b: number) => number
// 呼び出しシグネチャの完全版
type Sum = {
(a: number, b: number) : number
}
関数の呼び出しシグネチャはあくまで「関数の"型"を定義するもの」であるから、「デフォルトパラメーター」のような「値」を呼び出しシグネチャの中に定義することは出来ない(p58)。
基本的には省略版を使うので十分だが、呼び出しシグネチャの完全版を使うタイミングの一つは、「オーバーロードされた関数」を使うとき。
「オーバーロードされた関数」とは、複数の呼び出しシグネチャを持つ関数のこと。つまり、「複数の関数の型定義を持つ関数」を使うときに完全版の呼び出しシグネチャを使う。
関数の章
- 関数宣言(4種類)
- オプションパラメーターとデフォルトパラメーター
- レストパラメーター
- イテレーター(?)
- 呼び出しシグネチャ(型シグネチャ)(関数の型定義)
- ポリモーフィズム
TypeScriptで開発をするときは型駆動で開発をするようにしよう(特に関数)。(p84)
JSにおいて関数はとても大事。
JSでは関数は第一級オブジェクト。色々できるよ
関数型宣言の例(パラメーターの説明、パラメーターに基本的には型注釈←パラメーターの型は推論してくれないから)。
→但し文脈的型付け(呼び出しシグネチャによる型定義)の場合はパラメーターに型注釈する必要がない。文脈からTypeScriptが読み取る。
基本的に返り値の型は推論させる。
関数型宣言の4つの方法
余談。アロー関数式か関数宣言か問題。型定義の見易さ的にアロー関数式の方がよき
レストパラメーターを用いた例(calculateSumPrice関数?)
基本原則、推論できるところは推論させよう。
apply、call、bindは、関数を呼び出すときに、applyなどの引数に取った値を関数内のthisにバインドすることができるのか。
p53のやつは明示的にapplyとかでthisをバインドしてあげないと実行できないのね。
function型は無能型
関数の型定義1
用語の整理
- パラメーター:関数が実行されるために必要なデータ、値。仮パラメーターとも。
-
引数:関数を呼び出すときに渡すデータ、値。実パラメーターとも。
関数の型宣言
関数の型宣言は主に2通りある。
- 関数宣言 ver.
- アロー関数式 ver.
// 関数宣言 ver.
function functionDeclaration(text: string): void {
console.log(text)
}
// アロー関数式 ver.
const arrowFunction = (text: string): void => {
console.log(text)
}
基本的にTypeScriptは、パラメーターの型については推論をしてくれないため、明示的に型を付ける必要がある。但し、返り値の型については推論が効くため基本的には型推論に任せる。
基本の型定義同様、型推論に任せることの出来る部分は基本的に型推論に委ねる方針を採る。
余談:関数宣言とアロー関数式どちらを使うかに関して、特に理由がない限りアロー関数式を使う方が型定義が見易くなってわかりやすい。
関数の型定義2
オプションパラメーターとデフォルトパラメーター
- オプションパラメーター:パラメーター名に「?」をつけることで、そのパラメーターを省略可能なものとして指定することができる。
- デフォルトパラメーター:実パラメーターとして値が渡されなかった際にデフォルト値として値が設定されるもの。実パラメーターを渡す必要がない点においてオプションパラメーターと意味合い的には同じ意味を持つ。
オプションパラメーターとデフォルトパラメーターの例
以下の2つは意味合い的には同じことをしている点に注目。
デフォルトパラメーターの方がよく使うとのこと。
// オプションパラメーター
const optionParameter = (id: number, name?: string) => {
console.log(id, name || "名無しさん")
}
// デフォルトパラメーター
const defaultParameter = (id: number, name = "名無しさん") => {
console.log(id, name)
}
optionParameter(1) // 1, 名無しさん
defaultParameter(100) // 100, 名無しさん
デフォルトパラメーターに対して、明示的に型注釈をつけることもできるが、基本的にはデフォルト値の値からTypeScript側が型推論してくれるので付ける必要はない。
レストパラメーター
- レストパラメーター:関数が任意の数の引数を安全に受け付けてくれるもの。パラメーターの最後に1つだけ指定することが出来る。
関数のパラメーターに対して要素の数が変わりうる配列を渡すのは型安全ではない。そういう時に安全に任意の数の要素を持つ配列を渡すことができるものが「レストパラメーター」くん。
// レストパラメーター
const sumNumber = (...numbers: number[]): number => {
return numbers.reduce((sum, n) => sum + n, 0)
}
sumNumber(1, 2, 3) // 6 と評価
注意点としては、レストパラメーターはあくまで「残り物を配列として格納してくれる」だけだから、引数を渡す段階で配列を渡す必要はない。(sumNumber([1, 2, 3])
はNG)
因みに、決まった個数の引数を取る関数を「固定アリティ関数(fixed-arity function)」と言い、任意の数の引数を取る関数を「可変長引数関数(variadic function)」と言うらしい。カッケェ…
※扱うか未定
apply, call, bind と thisの型付け
apply, call, bindは基本的には役割は同じで「引数に取った値をthisにバインドする」というもの。
第2引数以降の挙動が異なる。一旦割愛。
関数のパラメーター定義の段階で、thisにバインドされるものの型を定義しておくことで、変な値がthisにバインドされることを防ぐことができる。一旦割愛。
ジェネレーターとイテレーター
ジェネレーターとは、何か一連の値を生成する際に便利な方法。ジェネレーター関数を用いてジェネレーター本体を作成し、実行する命令(nextメソッド)を与えた時のみジェネレーター内に定義した処理を実行する。
イテレーターとは、値をひとつずつ取り出すことができるデータのこと。nextメソッドを持ち、このメソッドを呼ぶたびにイテレータから値がひとつずつ得ることができる。
むずい。一旦割愛。
関数の型定義3
呼び出しシグネチャ(型シグネチャ)
ここにとてもシンプルな関数がある。
const add = (x: number, y: number): number => x + y
パラメーターそれぞれに型を付けていて、且つ返り値にも型を付けているため問題なく動く。
この時、「add関数そのもの」の型はなにか?(以下の???に入るもの)
const add: ??? = (x: number, y: number): number => x + y
この???
を型定義するものこそ、「呼び出しシグネチャ(型シグネチャ)」。
呼び出しシグネチャは、「ある関数がどういう関数なのかを型定義したもの(パラメーターの型、返り値の型などを定義する)」のこと。
呼び出しシグネチャでの型定義
上のadd関数を呼び出しシグネチャで型定義する
// 呼び出しシグネチャ(省略版)
type Add = (x: number, y: number) => number
// 呼び出しシグネチャ(完全版)
type Add = {
(x: number, y: number): number
}
// 実際の使用例
const add: Add = (x, y) => x + y
呼び出しシグネチャの省略版はアロー関数に意図的に似るように設計されている。
完全版の呼び出しシグネチャはオーバーロードされた関数(複数の型定義を持つ関数)を定義する際などに主に利用する。
因みに、上記で見た「add関数にAdd型を割り当てることでパラメーターなどの型をAdd型から文脈的に推論すること」を「文脈的型付け」と呼ぶ。
オーバーロードされた関数
オーバーロードされた関数とは、複数の型定義を持つ関数のこと。受け取る引数に応じて出力する型が動的に変わる。
個人的にブラウザのDOM APIの例が分かりやすかったので以下はその例。
新しいHTML要素を作り出すcreateElement
関数の型は以下の様になっている。
type CreateElement = {
(tag: "a"): HTMLAnchorElement
(tag: "table"): HTMLTableElement
// ...
(tag: string): HTMElement // ここで最終的にリテラル型に合致しなかった文字列の値を引き受ける
}
const createElement: CreateElement = (tag: string): HTMLElement => {
// ...
}
createElement
関数のパラメーターの型は"a" | "table" | ... | string
と書くことも出来るが、"a"型などのリテラル型はstring型のサプタイプでありstring型で引き受けることができるため、string型を指定しておくのみでよい。返り値のHTMLElement
も同様の理由。過度に一般的な型を指定しないように注意(極端な話any型
とか)
ポリモーフィズム
ポリモーフィズムとは、ざっくり言えば「何にでもなれるよ」なこと。
解説予定
70-71ジェネリック型<T>がバインドされるタイミング(→関数呼び出し時に引数の型からバインド。1つ目の引数から全てをバインドする)
72ジェネリック型パラメーター<T>を宣言した位置による有効スコープ(→型エイリアス横にジェネリック型を付ける場合はその型エイリアス全体にそのスコープが広がる、且つ実際の型注釈時にジェネリック型に対して明示的に型をバインドする。これが一般的)
7章 エラー処理
TypeScriptは実行時に例外を発生させないよう、極限までコンパイル時に例外を発覚させるように頑張ってはくれるが、それでもすり抜けてしまう実行時例外は存在する。
これは、どの静的型付け言語でも起こりうることで、TypeScriptはその例外の発生を防ぐことがあくまで得意というだけ。
TypeScriptでエラーを表現したり処理したりする方法を以下で見ていく。
- nullを返す
- 例外をスローする
- 例外を返す
- Option型
nullを返す
// nullを返す
let birthday = parce(ask());
// nullが返ってくる可能性のある場合は、値を使用する時にnullかどうかのチェックをする必要がある
if (birthday) {
console.info(`誕生日は${birthday.toISOString()}です`);
} else {
console.error('なんらかの理由で日付の解析に失敗しました')
}
function ask() {
return prompt("誕生日は?")
}
function parce(birthday: string) {
let date = new Date(birthday);
// 日付のバリデーションチェックをかけて不適当であれば`nullを返す`ようにする
if (!isValid(date)) {
return null;
}
return date;
}
function isValid(date: Date) {
return (
Object.prototype.toString.call(date) === '[object Date]' &&
!Number.isNaN(date.getTime())
);
}
メリット
- 型安全でかつ簡単にエラーを処理することが出来る(エラーがあったことを知らせることが出来る。但し具体的な内容までは伝えられない)
デメリット
- エラーが発生した理由を特定することができない(どういう形式で入力すれば正しい値として受け取ってもらえるのかという情報を伝えることができない。正確に言えば出来る(nullが返ってきたときに"こういう風に入力してねとエラー文を出す")けど細かい指定ができない)
- このbirthday変数を用いる全てのところでnullかどうかのチェックを行なう必要が出てくる
例外をスローする
メリット
- 特定の失敗した状況を処理することが出来る
- より簡単にデバッグできる