Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Search Gists
Sign in Sign up

Instantly share code, notes, and snippets.

@mizchi
Last activeAugust 19, 2023 14:09

    Select an option

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

    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 は ArrayBuffer であることを知っているが// VM で確保されるメモリに興味がないconstbuf:ArrayBuffer=newArrayBuffer(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

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

    最小 webpack.config.js

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

    最小 TS 用 Hello World

    src/index.ts

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

    最初に覚えるコマンド

    $ 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 型定義を同梱)

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

    ライブラリ型定義を潰す

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

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

    any 祭り

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

    潰されるコード

    functiongenMagicId():number|string{// -- なんかやばいコード --//@ts-ignorereturnsuper_magical_func();}
    • 本当に守りたいのは関数の入出力
    • 暗黒面に落ちるぐらいなら any で全部潰す

    慣用句: as any as ...

    constfoo:Foo=(foobarasany)asFoo;
    • 推論過程が導けない場合に仕方なく書くもの
    • 多用厳禁

    中身を無視して型定義

    • src/foo.js # 中は気にしないことにする
    • src/foo.d.ts # 外から型を指定する
    // foo.d.tsexportconsthoge:<T>(t:T)=>Promise<T>;

    CI を通す

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

    .circleci/config.yml

    steps:...      -run:npx tsc -p . --noEmit

    circleci ない人は husky などで頑張って


    結果: ガバガバ状態で導入完了


    発展編


    がんばる tsconfig.json

    {"compilerOptions":{"target":"es5","module":"es2015","esModuleInterop":true,// 段階的に有効化"alwaysStrict":false,// "use strict" 有効化"strictNullChecks":false,// null|undefined 厳格化"noImplicitAny":false// 推論不可能なときにアノテーション必須に}}

    ↓ ほど難易度高い


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

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

    型のコスパ感覚

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

    テストのコスパ感覚

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

    ドメイン層を守る

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

    とにかく静的解析を強化

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

    TS 筋を鍛える


    アンチパターン集


    typeof initialState

    constinitialState={...};exporttypeState=typeofinitialState;
    • 型とインスタンスの従属関係が逆
    • 本当に管理したいのは潜在的に State のとりうる状態

    型引数多すぎ問題

    exportclassStateManager<A,B,C,D,E,F>{...}
    • 人間が管理できる型引数はたぶん 2 つぐらいまで
    • ライブラリ作者は 3 つ、アプリケーション内なら 2 つまでが感覚的なセーフライン

    難しい型を書いてしまう

    • 例: react-redux@connect のヤバさ
    • ライブラリ作者は抽象度が必要 <=> アプリケーション内で難しい型は割れ窓
      • 同僚に any にされる
      • any になる可能性があるものは、 any になる

    潰れる Action

    exporttypeAction={type:string;payload:any;};
    • 間違っちゃいないんだけど union type でもっと詳細に書ける

    namespace と import foo = require(...)

    importmodule= require("module");
    • ESModules 導入以前の独自モジュールシステム。新規で書く場合には不要

    キャストによる治安悪化

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

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

    inline


    参考


    おわり

    Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

    [8]ページ先頭

    ©2009-2025 Movatter.jp