【TypeScript】TypeSript中級者になる為に知っておくと良い108個のこと

【TypeScript】TypeSript中級者になる為に知っておくと良い108個のこと

徹底的にやるTypeSript108個のこと。
仕事でもっと使いこなしたい方と自分に向けた記事

TypeScriptすきです(TypeScript練習問題集)

aboutme

GW前にこんなこと

徹底的にやるTypeSript108個のこと

を発言してしまったために
108個炙り出すことをしていた

~結論、WIP(2020/5/7現在)。今のところ98個。~

~ですが、~
~あと少しなのでこのページ自体を更新し続けます~

その後108個達成

もちろん、自分が知らないのはこれだけしかないのではなく、

今回はTSへの理解を深めるスタートという位置付けになりました

まとめきれてないのでご覧いただくにはガチャガチャしていますが

以下メモです(syntax highlightどうにかならないか...)

1. Excess Property Check

interface SquareConfig {
color?: string;
width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
return {color: "red", area: 1}
}

let mySquare = createSquare({ colour: "red", width: 100 }); //これだとエラーだが

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig); //これでもいける

// config.opacityを関数内で参照する時エラー

この場合 colour はSquareConfigに含まれていないからerror
余分にあるpropertyとなる
余分なpropertyを許可する場合

関数への引数は余剰は認めるが使う時エラー

playground

2. 1つのkeyしか受け付けない型

playgrounnd

stackoverflow

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

// Uはなんでもいい。Uが渡されていればUを引数に取るFunction型を返し、次の処理、UがIにキャプチャされてI(U)となり、Uがtypeになる

var fff: UnionToIntersection<{ a: string }> = { a: "1" }

次のここ
stackoverflow

type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

var fff: IsUnion<{a: "a"}> = false
// error

3. inferがunionTypeを引数にとれない??

playground
このこと

値はunionTypeにできないから。どちらかを実行時に指定しなくてはいけないから?

inferで"a"は通って、"b"はneverが変わる意味がわからない
-> わかった
下の4番の決まり事のせい。
inferが最初の値 "a"型にしてしまうので次に型計算?されるbを受け付けなくなる

let a: UnionToIntersection<"a" | "a"> = "a"

なら行ける。
つまりaは"a"しか受け付けないし、1つのリテラル型しか返さない

4. ジェネリクスの型は合成できない

stackoverflow

function compare<T>(x: T, y: T): number {
return 2
}
// Could infer T: string | number here... but that'd be bad
compare('oops', 42);

これはerror。先に型が決まってしまう
TypeScript in general will not synthesize a union type during generic inference. The reason, in simplified terms, is that it's not desirable to do inference like this:
推論中は共用型をつくらない

5. なぜ関数の型を使っているのか

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

関数の引数は反変で、関数のUnionTypeはその引数をintersectionにする

6. 共変

https://kenjimorita.jp/typescript-subtype-vs-supertype/

関数Aが関数Bに代入できる時
アリティがAの方が少なく、その引数がAの方がスーパータイプで、Bの戻り値の方がスーパータイプ
の場合

7. 引数に割り当てられたtypeが自動的にgenericsの Tに割り当てる

タイミングについてわからなかった
「TypeScriptは、関数の引数から型を自動的に取得してTに割り当てます。」
なるほど。
TypeScript will then automatically grab the type from the function arguments and assign it to T.
https://medium.com/better-programming/typescript-generic-objects-1eecc13dca93

省略できるよ
https://medium.com/@rossbulat/typescript-generics-explained-15c6493b510f

8 extendsを使いたいときは

もちろん、より制限したい時だけど、ちがう感じで言っていないか
開発者が欲しい「最低限の形状」をextendsのスーパータイプにする

This is where we’re going to utilize TypeScripts’ keyword extends. extends will make sure our generic type is at least a given shape. For mapAnyProductData,

https://medium.com/better-programming/typescript-generic-objects-1eecc13dca93

なるほどそうすることで元データの変更なしで型を変更できる...

9. angled brackets アングルブラケット

<>
このこと

10 return typeがgenericsである場合、angled bracketsの省略はできず明示しなければならない

11. 型変数、型パラメータ、ジェネリックパラメータ。呼び名が多いけど..

TypeScriptのドキュメントは「Type変数」

12. class をインスタンスする時のangled typeは必須

let myObj = new ClassName<T>{}

13 推論の限界

https://learning.oreilly.com/library/view/effective-typescript/9781492053736/ch01.html#ch-intro

type State {
name: string
capital: string
}

型注釈をつけることでプロパティ名の間違えを明確にしてくれる
// Property 'capital' does not exist on type
// '{ name: string; capitol: string; }'.
// Did you mean 'capitol'?

この提案自体のプロパティ名が違う場合に
実際はcapitolではなくcapitalが正しいがここではそれまでは指摘してくれない

14 TypeScript Compiler APIとは

https://katashin.info/2018/02/24/221

15 なぜこれがエラーになるのかを知りたかった

function test<T extends { [key: string]: string }>(map: T) {
    const key: keyof T = "hello"
    map[key] = "hi there"
}

TypeScript/issues

ジェネリクスで

16 TypeScirpt 3.81のインデックスシグネチャの動き

WIP

17. TypeScript 3.9の変更点は?

https://devblogs.microsoft.com/typescript/announcing-typescript-3-9-beta/

if conditionの中でfunction callがされていない場合のerrorに続いて、三項演算の中におけるそれもサポートした

18 unionTypeのindex signatureが絡んできた時の理解

WIP

19 どのようにgenelicsはcompilerで解決されるか

WIP

20. Tを同じ型として扱うにはどうしたらいいか

stackoverflow

function add<T extends (number | string)>(a: T, b: T): T {
if (typeof a === 'string') {
return a + b;
} else if (typeof a === 'number') {
return a + b;
}
}

関数の中のgeneric parameterの型は絞り込むことはできない。なのでaをテストした時、bをコンパイラに伝えなくてはならない。さらに返値の方もコンパイラに伝えなくてはならない

function add<T extends (number | string)>(a: T, b: T): T {
if (typeof a === 'string' && typeof b === "string") {
return a + b as T
} else if (typeof a === 'number' && typeof b === "number") {
return a + b as T
}
throw Error("not Support")
}

21. 要素ないを取得してunionTypeを作っている説明が理解し切れてなかった

TypeScript/issues
ここの答えのReadonlyArryのところ。それじゃなくちゃいけない理由など
理解した
playground

21. type guards比較はinも便利

type NotHumman = Animal | Bird

const b = (b: NotHumman) => {
    if ("run" in b) {
        b.run
    } else {
        b.fly
    }
}

https://gist.github.com/kenmori/8cea4b82dd12ad31f565721c9c456662
問39を修正

22. 配列がもつ要素にアクセスする方法で見たことなかったこれ

const type = <const>[
'room',
'room_with_gifter',
'user_send'
];

type Activity = {
id?: string;
type: typeof type[number];
}

23. インデックスシグネチャへのプロパティアクセス方法のいろいろ

stackoverflow

24. ThisParameterTypeとOmitThisParameterType

stackoverflow

25 inferを複数回使うところ

stackoverflow

26. tsserverのこと

stackoverflow

node_moduels/typescript/lib/tsserver.js

27: unknown[]

任意の型の配列またはタプル
また

let a: any
let b: unknown

未定義という意味ではanyよりunknownを使った方が型安全

28 Genericsがbindするタイミング

WIP

29 distributive conditional type

ということば。
32のこと

30 type-festというutilityライブラリの実装を見ていると

とりあえずunknownにしといて型変数だけ作り、関数の引数の型にして、次のconditional中でその関数の引数の方を抽出するテクニックがあるのだな
と感じる

https://github.com/sindresorhus/type-fest/blob/master/source/union-to-intersection.d.ts#L48

31 type-driven-developmentとは

型を先に作って詳細を後から作ること

32 これがわかりやすい

stackoverflow

下は理解不足だった頃

nakid type と non nakid type
型パラメータが別の型(配列、タプル、関数、プロミス、その他のジェネリック型)にラップされずにラップされずに存在すること
条件付きの型(conditional type)の際に、nakid typeはユニオンに分散され、それぞれ評価される

stackoverflow
nakid パラメータtypeのことをチェックするconditional-typeをdistributive conditional typeという
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types
distributive conditional typeのTはここの構成要素を差す

33 共変と反変

関数の引数のみ反変

関数の変性
A <:B になるときは
関数Aが関数Bとそれより低いアリティをもち次の条件を満たす場合AはBのサブタイプ(AはBに割り当て可能)

  • Aのthis型が指定されていない。または「Aのthis型 >: Bのthis型」である
  • 対応するそれぞれの引数
    について Aのパラメータの型 >: Bのパラメータの型」である
  • Aの戻り値の型 <: Bの戻り値の型

34 割り当て可能なという意味

assignability
型Bが要求しているところで別の型Aを使用できるかどうかについてのTypeScriptのルール
A <;B なら使うことができる
列挙型以外

35 excess property checkingの深い理解

オブジェクトリテラルに対して、targetの型にないプロパティをチェックすること

これがあるからプロパティの誤字をエラー起こしてくれるが
「フレッシュ(Freshness)なオブジェクトリテラル」を型アサーションや、一回別の変数に割り当てると
TypeScirptは過剰なプロパティチェックをしなくなる
3.8の確認

過剰なプロパティチェックはオブジェクトリテラルを直接渡した場合、判定されるがなぜ代入するとそれが「撤退する」のかとsubtypeになっていればそれでもいいのではないかと思っていた。
なぜこんかことをしているか
-> 可読性の観点からだという
some({a: "a", b: "b"})
上のbが余分だとして、
直接リテラルを渡した場合、
このように書くと、開発者はbを渡しているので、
someはbを求めていて、引数として必要で、関数の中で使われる
と思うが、
実際はbは求められておらず、
使われてもいない
これを注意喚起してくれている
-> リテラルという誰もが誤解する形で渡している場合、
エラーにしている
https://basarat.gitbook.io/typescript/type-system/freshness#freshness

object literal may only specify known properties

明示的に過剰なプロパティチェックを許可するという意味で、インデックスシグネチャで明示的にすることができる <- なるほどと思った
これが、UnionTypeで、過剰でかつ、プロパティ値の型が違う
場合、今回3.8ではチェックする
ようになった

36 APIの戻り値ではルックアップ型が便利

  • APIの型を部分的に型を切り出して名前つけて、みたいなのをやらなくて済む
  • 自動生成される
    ような型はなおさら

APIResponse["user"]["friendList"][number]
こうすると配列の型がとれる。number。
タプルの場合は[0]とか

37 Object vs object vs { }

  • strictNullChecksが無効なら
    それぞれに null、undefinedが代入できる
    有効ならエラー

  • Object
    全ての値が割り当て可能

  • {}
    empty Object
    Objectと同じ

object
non-primitiveな値が代入可能
boolean、number、string、symbolはダメ

38 freshnessを調べたら

フレッシュネスバーガーが出てきた。
TypeScriptでも出てくる。
フレッシュネスバーガーがTypeScriptでサイトを作り、それをメインページで報告するとさらに上にきそう。「TSバーガー」でも作って

39 Exact Typeというのが議論されていること

TypeScript/issues

  • 式から新しい型を作成している時、余分な値が入ってくるのを避けるため。新しいプロパティが入ってくるのを避けるため
type User = { username: string, email: string }
const user3: User = Object.assign({ username: ' x ' }, { email: ' y ', foo: ' z ' })
const user4: User = { ...{ username: ' x ' }, ...{ email: ' y ', foo: ' z ' }}

これは現在エラーにならない
これをエラーにしたいという議論

40. リリースされた3.9.0 betaのこと

  • Promise.allとPromise.raceの型でnullとundefinedが入った場合の推論を改善した
  • awaited Operater(Promiseのawaite時の型)においてはまだ機能設計をする必要があるのでブランチを切った(testだけ入れた)
    https://github.com/KDE/syntax-highlighting/commit/1f1f03198aa761500ce58b1525f15b7b1918e393#diff-1114302f2932621cc9fd427ef812fae7R283
  • material-uiやstyled-componentsなどのパッケージを使った時のコンパイルスピードが改善された (unionType,intersection, conditional type, mappded typeにおける)
  • //@ts-expect-error
  • 条件式に書かれている関数が呼び出されない場合、エラーを吐くようになった。if文のみ。三項演算の場合もいけるようになった
  • エディタの改善 -> CommonJSモジュールを使用したJavaScriptファイルを自動的にimportされるようになった。以前はimport * as Hoge fromなどのESmoduleで常にしていたが、モジュールタイプが違う場合も自動で検出するようになった

など
https://devblogs.microsoft.com/typescript/announcing-typescript-3-9-beta/

41. 数値型インデックスの戻り値の型は文字列型のインデックスシグネチャの戻り値型のサブクラスである必要がある

何を言っているかわかった
playground
こういうことか
number型のインデックスシグネチャはJSでは文字列に変換されるためその安全性を考慮して
string型のプロパティが返す型。例の場合Dogのサブタイプでなければならない理解
つまり、
playground

42. ネストされたindexシグネチャに注意の意味

type B = {
name: string
}

// ネストされた同じ名前のプロパティにたいして、これだと検知できない
type A = {
name?: string
[ind: string]: string | B | undefined
}
// errorになって欲しい
const a: A = {
naame: "fafa"
}

playground

43. interfaceにあるRecord<string, string[]>はtypeで拡張するとよさそう

playground

{ } この場合のanyは仕方ないのか??

44

TypeScript/issues

このこと

45 Anders Hejlsberg

https://en.wikipedia.org/wiki/Anders_Hejlsberg

TypeScriptのコアメンバー

46 コンパイルのこと

TSコントリビューターになるまでの軌跡
https://dev.to/remojansen/learn-how-to-contribute-to-the-typescript-compiler-on-github-through-a-real-world-example-4df0

TypeScriptの深いところを学んだ人がこれいいよっていうリンクが貼られている記事
Douglas Crockfordという人の
parserのことASTのこと、それを生成するところの説明(ビデオ)
https://www.youtube.com/watch?v=Nlqv6NtBXcA

どのようにcompilerが動くか (Vincent Ogloblinsky)
https://www.youtube.com/watch?v=WkJagE7b5U0

有名なAndersHejlsbergさんへのインタビュービデオ
https://www.youtube.com/watch?v=jBddlEWNsps

Grammatically Rooting Oneself With Parse Trees
https://res.cloudinary.com/practicaldev/image/fetch/s--x7QWZlJP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AyKjaulmzLsRxN4JBvy5Q2w.jpeg

TypeScript アーキテクチャの概要
https://github.com/Microsoft/TypeScript/wiki/Architectural-Overview

Compiler Internals
https://github.com/Microsoft/TypeScript/wiki/Compiler-Internals

一度これしたほうがいいよとのこと。
ソースコードからUMLdiagrams を生成する
https://github.com/remojansen/TsUML

47. エラー処理の種類。使い分け

null

  • cons 簡単
  • pros 具体的なエラーを返せない。「なんらかの理由で」。その後nullチェックをする必要があり、操作のネストや連鎖を行いたい時に冗長になる

例外をスロー

  • try-catchの中で特定の失敗モードを処理できるようになる。失敗に関するデータを得られる
  • エラーの種類をサブタスク化して種類ごとにインスタンスを投げることでなぜ失敗したかがわかる。ログも解析できる
  • 名前があるのでドキュメントもかける。

例外を返す

  • 関数の返値型にユニオンタイプ。関数利用者にどんなエラーがあるか伝わる。再スローすることを強制できる
  • 欠点はすぐにネストになること

Option型

  • Haskell, Ocaml、Scala, Rustに由来するもの
  • 値ではなくコンテナ(値が入っている)を返す
  • エラーが発生した理由を利用者に伝えない欠点
    -> ここ理解しようとすると深い

48 シグネチャの名前

https://www.bookstack.cn/read/ts-spec/spilt.9.spilt.3.spec.md#5.2.2

{
func1(num: number): number // メソッドシグネチャ
func2: (num: number) => number //Function シグネチャ
func3 : { (num: number ): number } // オブジェクトタイプリテラル
}
これらは全て同じ

49. 違いはあるのか

playground
全く同じ

stackoverflow

なぜ書き方に違いがあるか

50. なぜ type K = keyof { } // neverなのか

  • keyがないため

51. 互換性のない型アサーションでエラー

WIP

51

filterでは型を絞り込むことはできない。ワンライナーの書き方

type User = { name: string }
type WithIdUser = { name: string, id: number }
type WithIdOreNotUser = WithIdUser | User
type WithIdOreNotUserList = WithIdOreNotUser[]
const users = [{ name: "a", id: 1 }, { name: "b" }]

users.filter((e) => "id" in e ) // error

playground

52 インデックスシグネチャで余分なプロパティを許容するにはundefinedも型に入れる。その後 unionTypeになる

stackoverflow

playground

53. Object.keyで推論されないことがある

const config: {name: string, age: number} = { name: "mori", age: 90 }
Object.keys(config).map(e => e)

これは仕様によるため
TypeScript/issues

TypeScript/pullRequest
JSは任意のプロパティを追加できる。keyofは同じものを返さない可能性があるから

アサーションを使って解決する
playground

54. WIP

TypeScript/issues

55. unknownとunknownをintersection したらunknownになるとかここらへん

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type

type Test<T> = unknown extends T ? true : false;
const b:Test<boolean> = false // とにかくfalseになる

playground

56 初めて見た。keyof を引数の型でつかう

type User = {
status: {
login: boolean,
point: number
}
}

const getKeys = (keys: keyof User["status"]) => {
return keys
}
const keys = getKeys("login") // "login" | "point"

57

variableに対してのreadonlyはas const
propertyに対してはreadonly

58。オプショナルの賢い使い方

なるほど、ベースのを変えるのではなく、第二引数にそれ用のオプショナルの型を受け取りmergeする
playground

59 enumとconst enum どちらが使いやすいか

書き出されるコード

enum A {
a = 1
}

A.a

const enum B {
a = 1
}

B.a

"use strict";
/// A.a
var A;

(function (A) {

A[A["a"] = 1] = "a";

})(A || (A = {}));

A.a;

/// B.a
1 /* a */; // enumの定義すら書き出されない

60

TypeScript/issues
intersection Typeは全てを満たさないといけないが、indexsignatureを使っている場合、過剰なプロパティチェックが走り、エラーにする。回避方法

interface I {
[key: string]: string;
}

interface C {
foo?: boolean;
bar?: number;
}

type T = I & C;

// this does not work:
let t: T = {
asdf: 'foo',
foo: true,
bar: 3,
};
// this does work:
let tt: T = {};
tt.asdf = 'foo';
tt.foo = true;
tt.bar = 3;

https://qiita.com/kuy/items/f342fbd1737f557cf42a

playground

61 No overload matches this call.。アサインしようとしている型はオーバーロードしている型のどれにも当てはまらない

type A = (x: number) => string;
type B = (x: string) => number;
declare var x: A & B;
x(1); // error
x({a:"w"}); // error

No overload matches this call.
  Overload 1 of 2, '(x: number): string', gave the following error.
    Argument of type '{ a: string; }' is not assignable to parameter of type 'number'.
  Overload 2 of 2, '(x: string): number', gave the following error.
    Argument of type '{ a: string; }' is not assignable to parameter of type 'string'.(2769)

62 any型はinterfaceで作られたboolean型のneverタイプに割り当てられない。3.5

https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/#improved-excess-property-checks-in-union-types

これの影響らしい
「構成要素の各タイプが個別にチェックされる」

playground

TypeScript/issues

playground

63 type T2 = string & boolean; // never

なぜか。

64 3.4以下では特定の過剰なプロパティは許可されていた

///////////1//////////////
type Point = {
x: number;
y: number;
};

type Label = {
name: string;
};

const thing: Point | Label = { // 3.5.1からはここで解決しなくてはいけない。(Source(代入元のobjectリテラル)にnameに違う型だった場合
x: 0,
y: 0,
name: true // uh-oh!
};

thing.name // TypeScript 3.3は参照する際に解決しないといけなかった。上ではエラーにならない

playground

3.5以前はtarget側がUnionでかつ、Source側がオブジェクトリテラルを渡す場合、両方どちらかがsubTypeならその余分なプロパティの型チェックまでは行っていなかった。
(参照するときにエラー)
3.5からは余分なプロパティの型チェックまでアサイン時にする

non-disciminated union・・・

65 genelics引数の型がunion Typeの場合の理解

playground

stackoverflow

declare function takeA(val: 'A'): void;
export function bounceAndTakeIfA<AB extends 'A' | 'B'>(value: AB): AB {
if (value === 'A') {
takeA(value);
return value;
}
else {
return value;
}
}
と同じ
type Common = { id: number };
type A = { tag: 'A' } & Common;
type B = { tag: 'B' } & Common & { foo: number };

type MyUnion = A | B;

const fn = (value: MyUnion) => {
    value.foo; // error, good!
    if ('foo' in value) {
        value.foo; // no error, good!
    }
    if (value.tag === 'B') {
        value.foo; // no error, good!
    }
};

const fn2 = <T extends MyUnion>(value: T) => {
    value.foo; // error, good!
    if ('foo' in value) {
        value.foo; // error, bad!
    }
    if (value.tag === 'B') {
        value.foo; // error, bad!
    }
}
  • タイプがジェネリックである場合、ラップされて絞り込めない。
    ナローイングが機能するためにはunion Typeでなくてはならない
    union型の引数をもつジェネリックを解除するとうまくいく
  • ネストされたプロパティによる絞り込み
    ネストされたプロパティでもナローイングは発生しません
    テスト(type ガード)によりフィールドのタイプを絞り込むことはできるが、
    ルートオブジェクトは絞り込めません

stackoverflow

66 unionをkeyofしたさいに共通のキーがない場合 never

const str = 'hi';
const obj = {};
const complexObj = {
name: 'complexObject',
innerObj: {
name: 'InnerObject',
},
};

let strUnion: typeof str | string; // type: string
let objUnion: typeof obj | string; // type: string | {}
let complexUnion: typeof complexObj | string; // type: string | { ... as expected ... }

let strTyped: keyof typeof str; // type: number | "toString" | "charAt" | ...
let objTyped: keyof typeof obj; // type: never (which makes sense as there are no keys)
let complexObjTyped: keyof typeof complexObj; // type: "name" | "innerObject"

let strUnionTyped: keyof typeof strUnion; // type: number | "toString" | ...
let objUnionTyped: keyof typeof objUnion; // type: never (expected: number | "toString" | ... (same as string))
let complexUnionTyped: keyof typeof complexUnion; // type: never (expected: "name" | "innerObject" | number | "toString" | ... and all the rest of the string properties ...)
let manuallyComplexUnionTyped: keyof string | { to: string, innerObj: { name: string }}; // type: number | "toString" | ... (works as expected)

playground

let manuallyComplexUnionTyped: keyof string | { name: string, innerObj: { name: string }};

let manuallyComplexUnionTyped: (keyof string) | { name: string, innerObj: { name: string }};
であり
let manuallyComplexUnionTyped: keyof (string | { name: string, innerObj: { name: string }})
ではないため、すべてのkeyを取得できない

67 union全てのkeyを取得するにはconditional Typeを使う

type AllUnionMemberKeys<T> = T extends any ? keyof T : never;
let objUnionTyped2: AllUnionMemberKeys<typeof objUnion>; // = (keyof string) | (keyof {})
let complexUnionTyped2: AllUnionMemberKeys<typeof complexUnion>; // = (keyof string) | (keyof {name: ...,innerObj: {}})

なぜなら
型パラメータにunionな場合、
conditional typeがnaked typeとしてそれぞれ評価されるため

// 前述の32のことを言っている
チェックされる型(T)がネイキッド型パラメーターであるconditional typeは、distributive conditional typeと呼ばれる。

naked type・・・型パラメータが別の型(配列、タプル、関数、プロミス、その他のジェネリック型)にラップされずにラップされずに存在すること
distributive conditional typeとはcheck されるタイプがジェネリックパラメータ型で且つ、nakeked typeの
場合
stackoverflow

68 これが通らない理由も同じ

type VoidableCallback<EventValue> = EventValue extends void ? () => void : (val: EventValue) => void;
type Action = { type: 'add'; n: number } | { type: 'multiply'; n: number };
declare function fn<T>(): VoidableCallback<T>;
const callback = fn<Action>();
callback({ type: 'add', n: 1 });

playground

callbackで呼び出している箇所は
関数のシグネチャがunionTypeの場合、パラメータは全てのシグネチャのunionTypeに互換性がある必要があるため
パラメータは全ての可能なパラメータ型の共通部分ではなくてはならない

早い修正は

type VoidableCallback<EventValue> = [EventValue] extends [void] ? () => void : (val: EventValue) => void;
type Action = { type: 'add'; n: number } | { type: 'multiply'; n: number };
declare function fn<T>(): VoidableCallback<T>;
const callback = fn<Action>();
callback({ type: 'add', n: 1 }); //ok now

distributive conditional typeにさせないこと(タプルにしてnon naked typeにする)

69 型パラメータでないunionTypeはdistributive conditional typeではなく、分配がされずtsは両方の型を同時に満たす互換性を求めてのシグネチャの型が推論される

playground

[string] | [number]になるのは引数の型だから
TypeScript/issues

70 抽出されるようすがわかりやすい説明 [keyof T]

type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];

KeysMatching<Thing, string> ➡

{[K in keyof Thing]: Thing[K] extends string ? K : never}[keyof Thing] ➡

{
  id: string extends string ? 'id' : never;
  price: number extends string ? 'number' : never;
  other: { stuff: boolean } extends string ? 'other' : never;
}['id'|'price'|'other'] ➡

{ id: 'id', price: never, other: never }['id' | 'price' | 'other'] ➡

'id' | never | never ➡

'id'

stackoverflow

71 discriminated unionとは

Unionのそれぞれの型にtype(タグ)をプロパティとしてつけて、それで識別すること
シングルトンタイププロパティ

const enum CarTransmission {
Automatic = 200,
Manual = 300
}
type Motorcycle {
type: "motorcycle"; // discriminant
make: number; // year
}

type Car {
type: "car"; // discriminant
transmission: CarTransmission
}

type Truck {
type: "truck"; // discriminant
capacity: number; // in tons
}

72 なぜ TypeScriptはvoid とundefinedを区別しているか

playground

  • voidはその真偽を評価させることはできないところがundefinedとの違い
  • functionのcallbackで使う場合promptやalertなど実行するのみで何も返さない型のreturn型にvoidではなくundefinedを指定すると、明示的にundefinedを返さなくてはいけない。voidに指定すると、返す型に「意味がない」、「単に無視して」とTSに伝えることができる

73

要素なしの配列(never[])にspreadingする

const arrayOfArrays = [1, 2,3]
[].concat(...arrayOfArrays) // no
new Array<number>().concat(...arrayOfArrays)

playground

74 Type 'null' does not satisfy the constraint '{}'.(2344)

playground

{}はnullとundefinedを除外する不明なものとしてであり、どんなタイプでも受け付けるという意味で制約するところ(superTypeとして)に使えない

https://levelup.gitconnected.com/getting-to-know-and-love-typescripts-meta-types-5e17a8856b17

75 Numeric Separators

1_000_000_0001000000000とコンパイルされる

playground

76 抽出したり、 unPackしたりする

WIP

77 この人stackoverflow内で解決する人としてよく見かける

stackoverflow

78 LiteralUnionという便利があるらしい

現状リテラルなUnionTypeにプリミティブな値も加えると、補完機能が効かなくなる

type Pet = 'dog' | 'cat' | string;
const a:Pet = "dog" // string
// ライブラリ、type-fest(https://github.com/sindresorhus/type-fest)のLiteralUnionを使うと解決する

export type Primitive =
| null
| undefined
| string
| number
| boolean
| symbol;

export type LiteralUnion<
LiteralType extends BaseType,
BaseType extends Primitive
> = LiteralType | (BaseType & { _?: never });

const b: LiteralUnion<Pet, Primitive> = "dog"
b // "dog"

playground

79. Object.valuesが推論効かない -> target2017にして

// 一回keyを挟む方法もある
stackoverflow

const data = {
  a: "first",
  b: "second",
};

const values = Object.keys(data).map(key => data[key]);

const commaJoinedValues = values.join(",");
console.log(commaJoinedValues);

playground

80. typescriptのOption全部説明できる?? -> できない -> 復習

ドキュメント以外の解説

  • checkJs・・・allowJSと一緒に使うと、JSのファイルに対してもエラーを検出してくれる
    など

WIP

81. eventで得た型をより汎用的にする方法

https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24

汎用的にしたければ型を抽象的にして、厳密にしたければ絞り込む。それはイベントの型も一緒

82

https://github.com/sindresorhus/type-fest の中で読めないところを列挙する

83 v3.8.3からエラーになった

stackoverflow

indexSignatureは全てのintersectionの型を一つ一つチェックするようになった(今まではstringならok)

playground

3.8.3以前のTypeScript振る舞い
TypeScript/issues
この例題のokとされているところがわからなかった 84へ

84. Intersection をinterfaceと一緒に使うということ

stackoverflow

85. Denoとは

https://dev.classmethod.jp/articles/deno-init/

86 nullであてっも通ってしまう

https://qiita.com/mangano-ito/items/5583783cd88ea5f4deb4#null-%E3%82%82%E9%80%9A%E3%81%99
ここのこと

87 Apparernt Member(明らかなメンバー)とは

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#3111-apparent-members

  • subtype、supertypeの中にあり、互換性のあるメンバー

88 型引数の推論が決定されていく順序、決まり

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#4152-type-argument-inference

function choose<T>(x: T, y: T): T {
    return Math.random() < 0.5 ? x : y;
}
var x = choose(10, 20);     // Ok, x of type number
var y = choose("Five", 5);  // Error

左から推論され、stringがTになった後、5(number)はstringでも他のタイプのsuertypeではないため推論は失敗してErrorが起きる

これを説明してみる

function zip<S, T, U>(x: S[], y: T[], combine: (x: S) => (y: T) => U): U[] {
    var len = Math.max(x.length, y.length);
    var result: U[] = [];
    for (var i = 0; i < len; i++) result.push(combine(x[i])(y[i]));
    return result;
}

var names = ["Peter", "Paul", "Mary"];
var ages = [7, 9, 12];
var pairs = zip(names, ages, s => n => ({ name: s, age: n }));
to
function zip<string, number, U>(x: string[], y: number[], combine: (x: string) => (y: number) => U): U[] {
    var len = Math.max(x.length, y.length);
    var result: U[] = [];
    for (var i = 0; i < len; i++) result.push(combine(x[i])(y[i]));
    return result;
}

var names = ["Peter", "Paul", "Mary"];
var ages = [7, 9, 12];
var pairs = zip(names, ages, s => n => ({ name: s, age: n }));

推論をつかってSとTが順番に決定され、関数内のそれぞれの型が決定されると、

function zip<string, number, {name: strinng, age: number}>(x: string[], y: number[], combine: (x: string) => (y: number) => {name: string, age: number}): {name: string, age: number}[] {
    var len = Math.max(x.length, y.length);
    var result: {name: string, age: number}[] = [];
    for (var i = 0; i < len; i++) result.push(combine(x[i])(y[i]));
    return result;
}

var names = ["Peter", "Paul", "Mary"];
var ages = [7, 9, 12];
var pairs = zip(names, ages, s => n => ({ name: s, age: n }));

Uが決まり、
結果zipは

var pairs = zip<string, number, { name: string; age: number }>(
    names, ages, s => n => ({ name: s, age: n }));

全て推論された状態

89. base constraintの決定のされ方

https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#361-type-parameter-lists

90. TS3.5の変更点

3.5以前代入可能

declare function someFunc(): void;
declare function fn(arg: { [k: string]: unknown }): void;
fn(someFunc);

3.5以降

Argument of type '() => void' is not assignable to parameter of type '{ [k: string]: unknown; }'.
Index signature is missing in type '() => void'.

targetがUnionの場合過剰なプロパティの型チェックは行われなかったのが行われるようになった
(Unionの場合それぞれの型チェックの際にプロパティが存在しさえすればよかったが、その型までチェックされてなかった)
playground

91 3.4のバグを3.5以降修正

type A = {
s: string;
n: number;
};

const a: A = { s: "", n: 0 };
function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
//2. ここでなんらかの上程で文字列が入ってもokになっている..
arg[key] = "hello, world";
}
write(a, "n", 1); //1. nはnumberで1を渡しているが...

3.5以上は

Type '"hello, world"' is not assignable to type 'A[K]'.
Type '"hello, world"' is not assignable to type 'string & number'.
Type '"hello, world"' is not assignable to type 'number'.

https://github.com/microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#fixes-to-unsound-writes-to-indexed-access-types

92 Grammar Ambiguities とは

WIP

issue

93 const foo = <T, >(x: T) => x

tsxだとこのような書き方ができる
extends {} だという

stackoverflow

94 any型かどうかを知る型定義

stackoverflow

95 TypeScriptとbable

おさらい

https://iamturns.com/typescript-babel/

使う場合。以下はコンパイルされない(2020/5/6)

  • namespace
  • bracket style assrtion. i.e <Foo>x
  • 複数マージされたenums i.e enum merging
    -レガシースタイルなimport export i.e. import foo = require(...) and export = foo

https://babeljs.io/blog/2020/03/16/7.9.0
Babel 8は 2020年4月予定?

96 keyof unionTypeは共通のkeyを返す

interface Foo {
    foo: string;
}
interface Bar {
    bar: string;
}
type Batz = Foo | Bar;
type AvailableKeys = keyof Batz; // never, has not common key

// 共通のが欲しい場合
////fix////

type Batz2 = Foo | Bar;

type KeysOfUnion<T> = T extends any ? keyof T: never;
// AvailableKeys will basically be keyof Foo | keyof Bar
// so it will be  "foo" | "bar"
type AvailableKeys2 = KeysOfUnion<Batz>;

//Unionの単純なkeyが機能しない理由は、
// keyofが常に型のアクセス可能なキーを返すためです。これは、共通キーのみになるunionの場合です。
// KeysOfUnionの条件付きタイプは、実際にはユニオンの各メンバーを取得してそのキーを取得するため、
//結果はユニオンの各メンバーに適用されるkeyofのユニオンになります。

97 Transform union type to intersection type

反変を利用したUnionTypeからintersectionTypeにする方法

type UnionToIntersection<U> =
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

stackoverflow

なんで関数の引数に渡しているんだと思った。
反変が起こる箇所(関数の引数)はintersectionTypeになる

stackoverflow

98 convariance(共変) and contravariance(反変)のわかりやすい説明

stephanboyer.com

* A ≼ B means A is a subtype of B.
* A → B is the type of functions for which the argument type is A and the return type is B.
* x : A means x has type A.

だとして
Dog → Dog
Animal → Greyhound
が引数は反変、返り型は共変
strictFunctionTypesは引数の型を厳密にする

型変数が推論されるinferの位置が共変位置にある場合Unionに推論される
型変数が推論されるinferの位置が反変位置(関数の引数)にある場合intersectionに推論される

pullrequest

99 条件型の解決するか遅延するかの条件とは?

WIP

100 いろいろなtypeを調べるのにもう少し簡潔な書き方

inferは実際には1つめで変数にして、2つ目のif文で使う
stackoverflow

playground

101 tupleのkeyが欲しい時

type TupleKeys<T extends any[]> = Exclude<keyof T, keyof []>
type A = TupleKeys<[string, string, string]> // "0" | "1" | "2"

playground

arrayが持つproperyを除外するとインデックスのキーになるということ

102

型の移り変わりの説明わかりやすいし面白い
stackoverflow

IntersectItems<[{ a: 1 } | { b: 2 }, { c: 3 }]> =
Unfoo<Intersect<Values<Foo<[{ a: 1 } | { b: 2 }, { c: 3 }]>>>> =
Unfoo<Intersect<Values<{0: { foo: { a: 1 } | { b: 2 } }, 1: { foo: { c: 3 } }}>>> =
Unfoo<Intersect<{ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }>> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }) extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } extends any ? ((x: T) => 0) : never) | ({ foo: { c: 3 } } extends any ? ((x: T) => 0) : never)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(((x: { foo: { a: 1 } | { b: 2 } }) => 0) | ((x: { foo: { c: 3 } }) => 0)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<{ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } }> =
({ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } })["foo"] =
({ a: 1 } | { b: 2 }) & { c: 3 } =
{ a: 1 } & { c: 3 } | { b: 2 } & { c: 3 }

103 interfaceではできないこと

  • オブジェクトにたいしてツリー構造を作ること
    type Tree<T> = T & {parent: Tree<T>}

  • 変数を制限して少数の値のみに割り当てる型
    type Choise = "A" | "B" | "C";

  • conditional typeを使ったNonNullableのようなもの
    type NonNullable<T> = T extends null | undefined ? never : T;

104 enumのkeyをGenericsで取得する

`enum MyEnum { A, B, C };`
`type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum;`
// 'T' only refers to a type, but is being used as a value here.(
type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

playground

stackoverflow

105 road map TypeScript

https://github.com/microsoft/TypeScript/issues/36948
6月までのroadmap

106 DDD with TypeScript

An Introduction to Domain-Driven Design - DDD w/ TypeScript

107 本来存在しないpropertyはneverにする

// key1がtrueのとき、key3が存在し、key1がfalseの場合、key3が存在しないをどのように表現するか
interface Sample {
    key1: boolean;
    key2?: string;
    key3?: number;
};

// type aliasを使う。
type Sample3 = { key1: true, key2?: string, key3: number } | { key1: false, key2?: string }

// neverにしてあることに注意
type Sample2 = { key1: true, key2?: string, key3: number } | { key1: false, key2?: string, key3: never }

playgrounnd

108 this.hoge[key] = hoge[key]がnverに対してアサインしようとしている理由

interface Opts {
  onFrame: () => void;
  onAudioSample: null;
  emulateSound: boolean;
  sampleRate: number;
}

class NES {
    constructor(opts: Opts) {
        this.opts = {
            onFrame() { },
            onAudioSample: null,
            emulateSound: true,
            sampleRate: 44100,
        }
        if (typeof opts !== "undefined") {
            let key: keyof Opts
            for (key in this.opts) {
                if (typeof opts[key] !== "undefined") {
                    // Type 'number | boolean | (() => void) | null' is not assignable to type 'never'.
                    // Type 'null' is not assignable to type 'never'
                    this.opts[key] = opts[key];
                }
            }
        }
    }
    opts: Opts
}

playground

this.opts[key]opts[key]が同じなため推論できない

opts[key]Function, null, boolean, number

this.opts[key]はkeyに応じてFunction, null, boolean, or numberを受け取る必要があるが、分からない

keyがunionTypeで推論が効かない場合、それぞれの共通のsuperTypeに推論されてneverになり、それを要求している

stackoverflow

Object.assignで解決できる

class NES2 {
    constructor(opts: Opts) {
        this.opts = {
            onFrame() { },
            onAudioSample: null,
            emulateSound: true,
            sampleRate: 44100,
        }
        if (typeof opts !== "undefined") {
                this.opts = Object.assign({
                onFrame() {},
                onAudioSample: null,
                emulateSound: true,
                sampleRate: 44100,
            }, opts);
        }
    }
    opts: Opts
}
``

or

```ts
(this.opts[key] as Opts[typeof key]) = opts[key];

playground

ref

https://dev.to/achimoraites/typescript-tips-and-tricks-4fnh (TypeScript Tips and Tricks)
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type
https://dev.to/busypeoples/notes-on-typescript-mapped-types-and-lookup-types-i36
https://itnext.io/5-typescript-tips-to-improve-your-applications-e882d69592bd
https://levelup.gitconnected.com/getting-to-know-and-love-typescripts-meta-types-5e17a8856b17
https://itnext.io/typescript-extract-unpack-a-type-from-a-generic-baca7af14e51
https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24
https://qiita.com/vvakame/items/e7bbaff54db8fbf986bb

まとめ

  • stack overflowの悩んでいるところを解決するようになった
  • なぜ~なのかという目線で調べると深いところまで行けそう
  • わかりやすい回答をする人の回答一覧をみると理解深まる