Skip to content

Instantly share code, notes, and snippets.

@mizchi
Last active August 19, 2023 14:09
Show Gist options
  • Select an option

  • Save mizchi/9e71569f72187af749adfecea49fb38a to your computer and use it in GitHub Desktop.

Select an option

Save mizchi/9e71569f72187af749adfecea49fb38a to your computer and use it in GitHub Desktop.

Revisions

  1. mizchi revised this gist Jul 10, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Nondestructive_TypeScript.md
    Original file line number Diff line number Diff line change
    @@ -315,7 +315,7 @@ circleci ない人は husky などで頑張って
    "module": "es2015",
    "esModuleInterop": true,
    // 段階的に有効化
    "strict": false, // "use strict" 有効化
    "alwaysStrict": false, // "use strict" 有効化
    "strictNullChecks": false, // null|undefined 厳格化
    "noImplicitAny": false // 推論不可能なときにアノテーション必須に
    }
  2. mizchi revised this gist Jul 10, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Nondestructive_TypeScript.md
    Original file line number Diff line number Diff line change
    @@ -342,7 +342,7 @@ circleci ない人は husky などで頑張って
    - 型付けるとコスパ良い順
    - ORM 周り (node.js)
    - API レスポンスの返り値や、それを使う周辺
    - モデル層(redux/vuex)
    - Model/Store 層(redux/vuex)
    - View の入力(React/Vue Props)
    - View ステート層(React state / Vue data)

  3. mizchi revised this gist Jul 10, 2019. 1 changed file with 2 additions and 12 deletions.
    14 changes: 2 additions & 12 deletions Nondestructive_TypeScript.md
    Original file line number Diff line number Diff line change
    @@ -417,19 +417,9 @@ export class StateManager<A, B, C, D, E, F> {...}
    ## 難しい型を書いてしまう

    - 例: react-redux@connect のヤバさ
    - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts
    - ライブラリ作者は抽象度が必要 <=> アプリケーション内で難しい型は割れ窓
    - 難しい型は使いこなせなくて、同僚に any にされる

    ---

    ## 難しい型を書いてしまう: 対策

    ```ts
    // アプリケーションコンテキストで決め打ちしてしまう
    export const myConnect: <Mapped> (fn: (state: MyRootState) => Mapped) => =
    (fn) => ReactRedux.connect(fn);
    ```
    - 同僚に any にされる
    - **any になる可能性があるものは、 any になる**

    ---

  4. mizchi revised this gist Jul 10, 2019. 1 changed file with 24 additions and 2 deletions.
    26 changes: 24 additions & 2 deletions Nondestructive_TypeScript.md
    Original file line number Diff line number Diff line change
    @@ -27,6 +27,8 @@ mizchi / TypeScript Meetup 2
    推論あればそうでもなかった。むしろドキュメントとして有用。
    でも動的が流行ったあとだから、一旦は漸進的型付けで行く」

    ---

    ## この発表の目的

    - TypeScript を導入しない言い訳を全部潰す
    @@ -369,9 +371,9 @@ circleci ない人は husky などで頑張って
    ## とにかく静的解析を強化

    - `@typescripc-eslint`
    - 何にせよ `no-ununsed-vars` が効く
    - 色々やったが `@typescripc-eslint/no-ununsed-vars` が一番効く
    - `prettier`
    - jest のカバレッジ機能
    - jest のカバレッジ機能 + React SSR でスナップショットなど

    ---

    @@ -381,6 +383,7 @@ circleci ない人は husky などで頑張って
    - Generics, Union Type, Promise 表現
    - 自作ライブラリの `.d.ts` 書く
    - フレームワークごとのイディオム
    - 個人的に Scala の経験が生きた

    ---

    @@ -411,6 +414,25 @@ export class StateManager<A, B, C, D, E, F> {...}

    ---

    ## 難しい型を書いてしまう

    - 例: react-redux@connect のヤバさ
    - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts
    - ライブラリ作者は抽象度が必要 <=> アプリケーション内で難しい型は割れ窓
    - 難しい型は使いこなせなくて、同僚に any にされる

    ---

    ## 難しい型を書いてしまう: 対策

    ```ts
    // アプリケーションコンテキストで決め打ちしてしまう
    export const myConnect: <Mapped> (fn: (state: MyRootState) => Mapped) => =
    (fn) => ReactRedux.connect(fn);
    ```

    ---

    ## 潰れる Action

    ```ts
  5. mizchi created this gist Jul 10, 2019.
    460 changes: 460 additions & 0 deletions Nondestructive_TypeScript.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,460 @@
    # 非破壊 TypeSctript

    mizchi / TypeScript Meetup 2

    ---

    ## About

    - mizchi / 竹馬光太郎
    - フロントエンドと Node.js
    - 株式会社プレイド 2019/7~
    - Frontend Ops 周り
    - DOM 差分監視して色々

    ---

    ## はじめに

    - Modern JS ≒ TypeScript の時代になった
    - なので型を書け

    ---

    ## これまでの(歴史の)あらすじ!

    「Java の静的型付けが大変で、反動で動的なのが流行ったけど、
    推論あればそうでもなかった。むしろドキュメントとして有用。
    でも動的が流行ったあとだから、一旦は漸進的型付けで行く」

    ## この発表の目的

    - TypeScript を導入しない言い訳を全部潰す
    - そのために痛みがない導入・運用を提示する

    ---

    ## Outline

    - TS の型アノテーションとはなにか?
    - 導入編
    - 発展編
    - アンチパターン

    ---

    ## TS の型アノテーションとはなにか?

    ---

    ## TS の型アノテーションとは何「ではない」か

    - メモリ確保量を決めるもの、ではない
    - TS の型はインターフェースしか知らない
    - 実行時の挙動を決めるもの、ではない
    - TS の型宣言はランタイムに関与しない

    ---

    ```ts
    // TS は ArrayBuffer であることを知っているが
    // VM で確保されるメモリに興味がない
    const buf: ArrayBuffer = new ArrayBuffer(8);
    ```

    ---

    ## それでも、なぜ型アノテーションを書くのか

    - 人間のためのインターフェース宣言
    - 実行可能な Lint
    - それらによくコード品質の向上・レビューコストの削減

    ---

    ### TypeScript はコンパイラというより、「**型を検証可能な Lint ツール**

    ---

    ## TypeScript Compiler(tsc) の役割

    主機能

    - 型の静的検査
    - エディタへの情報提供 - Language Server Protocol

    おまけ機能 (babel で代替可能)

    - 型アノテーションの除去
    - ES2015 => ES5

    ---

    ## 型は JIT に優しい

    - V8 等の JIT(実行時最適化)は、何度も実行される処理のデータのシェイプ(≒ 型)を仮定する
    - 仮定が崩れると速度が落ちる(deopt)
    - 厳密には色々あるが、綺麗な型がつく方が高速な傾向

    ---

    ## 導入編

    ---

    ## TS 導入最小ステップを考える

    ---

    ## コンパイラとして使う

    ```
    $ npm install typescript webpack webpack-cli ts-loader --save-dev
    ```

    最小 `tsconfig.json`

    ```js
    {
    "compilerOptions": {
    "target": "es5",
    "module": "es2015", // ESM は webpack が変形する
    "esModuleInterop": true
    }
    }
    ```

    ---

    ## 最小 webpack.config.js

    ```js
    module.exports = {
    resolve: {
    extensions: [".ts"]
    },
    module: {
    rules: [
    {
    test: /\.ts$/,
    use: [
    {
    loader: "ts-loader",
    options: {
    transpileOnly: true // 型チェックしない!!!
    }
    }
    ]
    }
    ]
    }
    };
    ```

    ---

    ## 最小 TS 用 Hello World

    src/index.ts

    ```ts
    // 型はわざと間違ってる。アノテーションが取り除かれることを確認する
    const text: number = "World";
    console.log(`Hello, ${text}`);
    ```

    ---

    ## 最初に覚えるコマンド

    ```bash
    $ npx webpack # build only
    # => dist/main.js
    $ npx tsc -p . --noEmit # type check only
    ```

    エラーを確認

    ```
    src/index.ts:1:7 - error TS2322: Type '"World"' is not assignable to type 'number'.
    1 const text: number = "World";
    ```

    ---

    ## 最初にやること

    - 「コードを修正せずに」自分の `.js``.ts` にする
    - 「CI で型違反を検査せずに」ふるまいを手動/ユニットテストで確認

    ---

    ## なぜこうなるか

    - 初手はコンパイラとしての機能を保証する
    - どうせ最初は型チェックを通せない
    - 後々効く: コンパイルと型チェックの分離で高速化
    - どうせ後でも IDE で型違反を見る
    - どうせ後でも CI で型チェックする

    (awesome-typescript-loader やめとけ)

    ---

    ## ライブラリ型定義をいれる

    ```
    $ npm install --save-dev @types/<pkg-you-want>
    ```

    自分がほしいやつを一通り叩いてみる (一部のライブラリは TS 型定義を同梱)

    ```ts
    // npm install --save @types/lodash
    import { range } from "lodash";
    range(3);
    ```

    ---

    ## ライブラリ型定義を潰す

    たぶん全部の型定義ファイルは揃わないので一旦潰す

    ```typescript
    // src/decls.d.ts
    declare module "xxx"; // xxx の型定義がない or きつい
    // ちょっと頑張る
    declare module "yyy" {
    export function foo(input: string): number;
    }
    ```

    ---

    ## **any** 祭り

    - `tsc -p . --noEmit` で落ちた場所を修正していく
    - 読み下しながら自明な範囲で `number``string` を付与する
    - わからなかったら `any``@ts-ignore` で無視
    - **ロジックを変更しない!!!!**

    ---

    ## 潰されるコード

    ```ts
    function genMagicId(): number | string {
    // -- なんかやばいコード --
    // @ts-ignore
    return super_magical_func();
    }
    ```

    - 本当に守りたいのは関数の入出力
    - 暗黒面に落ちるぐらいなら any で全部潰す

    ---

    ## 慣用句: as any as ...

    ```ts
    const foo: Foo = (foobar as any) as Foo;
    ```

    - 推論過程が導けない場合に仕方なく書くもの
    - 多用厳禁

    ---

    ## 中身を無視して型定義

    - src/foo.js # 中は気にしないことにする
    - src/foo.d.ts # 外から型を指定する

    ```ts
    // foo.d.ts
    export const hoge: <T>(t: T) => Promise<T>;
    ```

    ---

    ## CI を通す

    - パスしたら CI で型チェックを流す

    `.circleci/config.yml`

    ```yaml
    steps:
    ...
    - run: npx tsc -p . --noEmit
    ```
    circleci ない人は husky などで頑張って
    ---
    ## 結果: ガバガバ状態で導入完了
    ---
    # 発展編
    ---
    ## がんばる tsconfig.json
    ```js
    {
    "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "esModuleInterop": true,
    // 段階的に有効化
    "strict": false, // "use strict" 有効化
    "strictNullChecks": false, // null|undefined 厳格化
    "noImplicitAny": false // 推論不可能なときにアノテーション必須に
    }
    }
    ```

    ↓ ほど難易度高い

    ---

    ## 大事なこと(これだけ覚えて帰って!)

    - 型とロジックを同時に修正しない
    - 推論などで発見すると嬉しくて修正したくなる
    - => 確認の工数がかかる
    - => マージされない
    - => 治安悪い状態が続く
    - 別 Issue にしましょう

    ---

    ## 型のコスパ感覚

    - 型付けるとコスパ良い順
    - ORM 周り (node.js)
    - API レスポンスの返り値や、それを使う周辺
    - モデル層(redux/vuex)
    - View の入力(React/Vue Props)
    - View ステート層(React state / Vue data)

    ---

    ## テストのコスパ感覚

    - 型アノテーション増やす > 単体テスト
    - テスト書かないでいいわけではないが型書くのを優先
    - 単体テストで型の効果を補強するイメージ
    - そもそもフロントエンドのテストはやりづらい(ので静的解析優先)

    ---

    ## ドメイン層を守る

    - 本当に守りたいのは(たぶん)データベースと API
    - スキーマ定義から型定義生成ツールがあると良い
    - grpc => .d.ts
    - graphql => .d.ts
    - jsonschema => .d.ts
    - フレームワークのメタデータから自作

    ---

    ## とにかく静的解析を強化

    - `@typescripc-eslint`
    - 何にせよ `no-ununsed-vars` が効く
    - `prettier`
    - jest のカバレッジ機能

    ---

    ## TS 筋を鍛える

    - DeepDive https://typescript-jp.gitbook.io/deep-dive/
    - Generics, Union Type, Promise 表現
    - 自作ライブラリの `.d.ts` 書く
    - フレームワークごとのイディオム

    ---

    ## アンチパターン集

    ---

    ## typeof initialState

    ```ts
    const initialState = {...};
    export type State = typeof initialState;
    ```

    - 型とインスタンスの従属関係が逆
    - 本当に管理したいのは潜在的に State のとりうる状態

    ---

    ## 型引数多すぎ問題

    ```ts
    export class StateManager<A, B, C, D, E, F> {...}
    ```

    - 人間が管理できる型引数はたぶん 2 つぐらいまで
    - ライブラリ作者は 3 つ、アプリケーション内なら 2 つまでが感覚的なセーフライン

    ---

    ## 潰れる Action

    ```ts
    export type Action = {
    type: string;
    payload: any;
    };
    ```

    - 間違っちゃいないんだけど union type でもっと詳細に書ける

    ---

    ## namespace と import foo = require(...)

    ```ts
    import module = require("module");
    ```

    - ESModules 導入以前の独自モジュールシステム。新規で書く場合には不要

    ---

    ## キャストによる治安悪化

    ```ts
    const id: string = number_or_string as string;
    ```

    - 本当に型の契約が守れてるかが自己責任
    - 新規にコードを書く際は Type Refinements (if /switch による union type 絞り込み) を優先

    ---

    ## 議論用のテーマ: どう思いますか?

    ![inline](https://gyazo.com/bc8eab1221f9606d36cbb93c3991f3fb.png)

    ---

    ## 参考

    - [TypeScript 入門以前ガイド - mizchi's blog](https://mizchi.hatenablog.com/entry/2018/10/03/195854)

    ---

    ## おわり