Slide 1

Slide 1 text

TypeScript ͷ
 ܕͱύϑΥʔϚϯε TypeScript, Type Checking and its Performance. ypresto ɾ 2024/05/11 @ Nakano, Tokyo

Slide 2

Slide 2 text

ypresto ←ϓϨετͰ͢ • Tw... X: @yuya_presto • LayerX バクラク事業部 • フロントエンド寄りのフルスタック • TypeScript歴7年 • 最近の趣味:写真

Slide 3

Slide 3 text

Hello, I'm from Osaka.

Slide 4

Slide 4 text

͓඼ॻ͖ • エディタと型 (遅いと・・?) • 型が重い #とは • 型計算のホットスポット • ライブラリでの例 • どうすれば改善できる?

Slide 5

Slide 5 text

ΤσΟλͷܕ͕ॏ͍ͱ ײͨ͜͡ͱ͸͋Γ·͔͢ʁ • 補完 • 定義へのジャンプ • エラー表示 • これらが遅い、反応しない • → tsserverが型の計算を頑張ってる WTDPEF UTTFSWFS ิ׬ͯͪ͠ΐ͏͍ͩ ܕܭࢉதɾɾɾ

Slide 6

Slide 6 text

ฤूମݧ͸ॏ͍ܕͷӨڹΛड͚΍͍͢ • tscの場合は、若干重いなら影響は少ない • tsserverの場合は、若干重いでも大きな割合を占める ࣮ߦ एׯॏ͍ܕ ฤू एׯॏ͍ܕ ϑΝΠϧͷܕݕࠪ ࣌ؒ ฤू एׯॏ͍ܕ ϑΝΠϧͷܕݕࠪ ฤू एׯॏ͍ܕ ϑΝΠϧͷܕݕࠪ ฤू एׯॏ͍ܕ ϑΝΠϧͷܕݕ ϑΝΠϧͷܕݕࠪ ଞͷϑΝΠϧͷܕݕࠪ ଞͷϑΝΠϧͷܕݕࠪ ※ 型検査は開いているファイルが対象:タブを閉じるとマシになります एׯॏ͍ܕ

Slide 7

Slide 7 text

࣮ࡍɺ͓࢓ࣄͷίʔυͰ ਺ඵ͔͔Δ΄Ͳॏ͘ͳͬͨ ReactɺMUIɺreact-hook-formɺetc. Λ࢖ͬͯͨ

Slide 8

Slide 8 text

૬ख͸50,000ߦҎ্ɾ3MB͍ۙίʔυ

Slide 9

Slide 9 text

஍ࠈͷίʔυϦʔσΟϯάͱ break pointσόοά ݁ՌTypeScript΍MUIʹPR͠·ͨ͠ɻͦͷ஌ݟΛ࿩͠·͢ɻ

Slide 10

Slide 10 text

ܕ͕ॏ͍ #ͱ͸ʁ

Slide 11

Slide 11 text

\ ͣ͹Γɺܕੜ੒ྔΦʔμ / δΣωϦοΫܕͰͷϧʔϓ΍࠶ؼ ↓ େྔͷType Instantiation

Slide 12

Slide 12 text

What is Type Instantiationɿ ܕύϥϝʔλΛຒΊͨܕΛ࡞Δ type ABC = { a: number, b: number; c: number } type ABOnly = Omit; // ==> { a: number, b: number } // ܕνΣοΫ΍ิ׬ͳͲͰඞཁͳͱ͖ʹߦΘΕΔ

Slide 13

Slide 13 text

OmitͷType Instantiation (1/5) type Omit = Pick T = { a: number, b: number; c: number } / K = 'c'

Slide 14

Slide 14 text

OmitͷType Instantiation (2/5) type Omit = Pick Exclude<'a' | 'b' | 'c', K> T = { a: number, b: number; c: number } / K = 'c'

Slide 15

Slide 15 text

OmitͷType Instantiation (3/5) type Omit = Pick Exclude<'a' | 'b' | 'c', K> Pick T = { a: number, b: number; c: number } / K = 'c' // a, b, c͔ΒcΛऔΓআ͘

Slide 16

Slide 16 text

OmitͷType Instantiation (4/5) type Omit = Pick Exclude<'a' | 'b' | 'c', K> Pick T = { a: number, b: number; c: number } / K = 'c' // a, b, c͔ΒcΛऔΓআ͘ // ΦϒδΣΫτ͔Βa, b͚ͩΛಘΔ { a: number, b: number }

Slide 17

Slide 17 text

OmitͷType Instantiation (5/5) type Omit = Pick Exclude<'a' | 'b' | 'c', K> Pick T = { a: number, b: number; c: number } / K = 'c' { a: number, b: number } 1ճ 1ճ + தͰ3ճ 1ճʙ ࢀߟ஋ɿ6ճʙ

Slide 18

Slide 18 text

Type Instantiationͷର৅ InstantiableType • T TypeParameter • T[xxx], xxx[T], or xxx[keyof T] IndexedAccessType • keyof T IndexType • T extends U ? X : Y Conditional Type • `...${T}...` TemplateLiteralType • Uppercase StringMappingType • T extends string ? Foo => Foo SubstitutionType ※ TypeScript 5.4.5の types.ts より

Slide 19

Slide 19 text

ܕͷߏ଄͕େ͖͍͔Ͱ͸ͳ͘ Instantiaionͷճ਺ ྫ͑͹ { [P in keyof T]: XXX } ͸ XXX ͷத਎࣍ୈ

Slide 20

Slide 20 text

Type Instantiationͷճ਺ʹؔ܎͢ΔΩʔϫʔυ Distributive Conditional Types Template Literal Types Generic Constraints (Base Constraint)

Slide 21

Slide 21 text

Distributive Conditional Types • Conditional Types • type Foo = T extends U ? true : false • Distributive Conditional Types [link] • 条件部分にUnionが渡ってきた場合は分配される

Slide 22

Slide 22 text

Conditional Union Distribution (1/2) Exclude<'a' | 'b' | 'c', 'c'> ͷྫ // ఆٛ type Exclude = T extends U ? never : T; // ෼഑͕ͳ͍ͱ͖ʙ (T_T) ('a' | 'b' | 'c') extends 'c' ? never : ('a' | 'b' | 'c' ) => 'a' | 'b' | 'c' ʹͳͬͯ͠·ͬͯػೳ͠ͳ͍

Slide 23

Slide 23 text

Conditional Union Distribution (2/2) Exclude<'a' | 'b' | 'c', 'c'> ͷྫ // ఆٛ type Exclude = T extends U ? never : T; // ෼഑ʹΑ࣮ͬͯݱ͞ΕΔ | ('a' extends 'c' ? never : 'a') | ('b' extends 'c' ? never : 'b') | ('c' extends 'c' ? never : 'c') // 3ճ෼ɺϧʔϓͨ͠..! => 'a' | 'b'

Slide 24

Slide 24 text

JSX.IntrinsicElements • キー数178のドデカ構造 • keyofで便利 • Reactを入れるだけで簡単に ゲットできる • !!悪用厳禁!!

Slide 25

Slide 25 text

࠷΋୯७ͳҧ๏TypeScriptίʔυ 3ॏϧʔϓ 178x178x178 > 500ສ Type Instantiation is excessively deep and possibly in fi nite.

Slide 26

Slide 26 text

Distributive Conditional Type ͕ՐΛਧ͍ͨྫɿ MUI 2021〜2年当時、このコー ドを開いて編集するだけで、 数秒待たされる状態に。 ※ TS最新版+M3 Proでも200ms。

Slide 27

Slide 27 text

JS ProfilerɺtraceͰσόοά • getConditionalTypeInstantiation() が長い • 配下に大量の関数呼び出し → ループしてそう • 違法改造トレースで見ると • DistributiveOmit • ComponentPropsWithRef • ※ 違法改造TS:型情報やトレースポイントが増えています [link]

Slide 28

Slide 28 text

MUIͷButtonͷܕͷ࣮૷ (1/3) ֘౰Օॴ export type OverrideProps< Buttonͷܕఆٛ, RootComponent extends React.ElementType > = ( & BaseProps & DistributiveOmit< React.ComponentPropsWithRef, keyof BaseProps > );

Slide 29

Slide 29 text

MUIͷButtonͷܕͷ࣮૷ (2/3) OverridePropsͷ࢖͍ಓ => ... => ... // OverrideProps<"a"> => { href?: string, ... }

Slide 30

Slide 30 text

MUIͷButtonͷܕͷ࣮૷ (3/3) Buttonͷܕຊମ // Buttonͱ͍͏ؔ਺ͷܕఆٛ interface OverridableComponent { // 1: ͷͱ͖͸ɺ͜ͷΦʔόʔϩʔυ (γάωνϟ) ( props: { component: RootComponent; // λά໊͔ίϯϙʔωϯτ } & OverrideProps, ): JSX.Element | null; // 2: component="..." ͕ॻ͍ͯͳ͍࣌͸͜͜ʹདྷΔ (props: DefaultComponentProps): JSX.Element | null; }

Slide 31

Slide 31 text

ͪΐͬͱ·ͬͯɾɾ ͷ࣌఺Ͱॏͯ͘ ͦ΋ͦ΋component={...} ͳΜ͔ࢦఆ͍ͯ͠ͳ͍Μ͚ͩͲ

Slide 32

Slide 32 text

ຊ౰͸ා͍ Generic Constraintsͷ࿩ ͷ U ͷ͜ͱ

Slide 33

Slide 33 text

TypeScriptͷؔ਺ܕɾΦʔόʔϩʔυղܾ (chooseOverload) (1/4) function foo(): undefined function foo(a: { foo: string }): number function foo(a: T): T function foo(a: number): number foo({ b: 1, c: 2 }) ্͔ΒॱʹධՁ (1ͭͷͱ͖΋ಉ͡ॲཧ)

Slide 34

Slide 34 text

TypeScriptͷؔ਺ܕɾΦʔόʔϩʔυղܾ (chooseOverload) (2/4) function foo(): undefined function foo(a: { foo: string }): number function foo(a: T): T function foo(a: number): number foo({ b: 1, c: 2 }) ্͔ΒॱʹධՁ // 1 // 2 // 3 Instantiate͠ͳ͍ͱൺֱͰ͖ͳ͍

Slide 35

Slide 35 text

TypeScriptͷؔ਺ܕɾΦʔόʔϩʔυղܾ (chooseOverload) (3/4) function foo(): undefined function foo(a: { foo: string }): number function foo(a: T): T function foo(a: number): number foo({ b: 1, c: 2 }) ্͔ΒॱʹධՁ // 1 // 2 // 3 3.1: Ҿ਺νΣοΫ΍ܕਪ࿦ͷաఔͰ T = { b: number } (a: T) → (a: { b: number })

Slide 36

Slide 36 text

TypeScriptͷؔ਺ܕɾΦʔόʔϩʔυղܾ (chooseOverload) (4/4) function foo(): undefined function foo(a: { foo: string }): number function foo(a: T): T function foo(a: number): number foo({ b: 1, c: 2 }) ==> ฦΓ஋ܕ͸ { b: number, c: number } // 1 // 2 // 3 3.1: Ҿ਺νΣοΫ΍ܕਪ࿦ͷաఔͰ T = { b: number } (a: T) → (a: { b: number }) function foo(a: ...): { b: number, c: number } 3.2: ਪ࿦ͨ͠ T Ͱ γάωνϟΛ instantiate ֬ఆͯ͠ऴΘΓ

Slide 37

Slide 37 text

Generic ConstraintʹΑΔInstantiate (1/2) // 1: ݩͷγάωνϟ // React.ElementType͸ɺJSX.IntrinsicElementsͱ΄΅ಉ͡ʹ178ݸ͋Δʂ (...): ... // 2: Ҿ਺ͷ RootComponent ΛɺͰ͔͍ React.ElementType ʹஔ͖׵͑ ( props: { component: React.ElementType; } & OverrideProps, ): JSX.Element | null;

Slide 38

Slide 38 text

Generic ConstraintʹΑΔInstantiate (2/2) // 1. OverrideProps ( & BaseProps & DistributiveOmit< React.ComponentPropsWithRef, // શλάͷͦΕͧΕͷpropsͷܕͷUnion keyof BaseProps > ); // 2. ComponentPropsWithRefͷதͷܕͷྫ type PropsWithoutRef

= // શλάͷpropsͷUnion P extends any ? ("ref" extends keyof P ? Omit

: P) : P; (λά਺)ճϧʔϓ ExcludeͰkeyof Pճϧʔϓ (ωετ)

Slide 39

Slide 39 text

O(mn) where m = λά਺ (178), n = props਺ (aλά͸281) → 5ສ ࣮ࡍ͸ɺComponentPropsWithRefશମͰ15ສճ΄ͲͷΑ͏Ͱͨ͠

Slide 40

Slide 40 text

TypeScript 5ܥ΁ͷόʔδϣϯΞοϓͰߴ଎Խ TypeScript 4.x → ਺ඵ TypeScript 5.x → ਺100msʂ

Slide 41

Slide 41 text

Type Instantiationͷճ਺ʹؔ܎͢ΔΩʔϫʔυ Distributive Conditional Types Template Literal Types Generic Constraints (Base Constraint)

Slide 42

Slide 42 text

Template Literal Types type A = PathSplit<'a.b.c'> // ==> ['a', 'b', 'c']
 
 type PathSplit = T extends `${infer A}.${infer B}` ? (B extends '' ? [A] : [A, ...PathSplit]) : [T] type B = PathJoin<['a','b','c']> // ==> 'a.b.c' type PathJoin = T extends [infer A, ...infer Rest] ? A extends string ? Rest extends [] ? A : Rest extends string[] ? `${A}.${PathJoin}` : never : never : "" // aͱb.cʹόϥ͢ // [a, ...] ʹ͚ͬͭ͘Δ // aͱ [b, c] ʹόϥ͢ // `a.${࢒Γ}` ʹ͚ͬͭ͘Δ

Slide 43

Slide 43 text

Template Literal Types ͷ׆༻ྫ • react-hook-form • type Values = { a: { b: { c: number } } } • form.setValue('a.b.c', 123) ←パスの位置の型を取得 • hono • "/api/posts" • client.api.posts.$get() ←オブジェクトを組み立て

Slide 44

Slide 44 text

Template Literal Types͕ՐΛਧ͍ͨྫɿ react-hook-form // Values͕େ͖͍ߏ଄ const form = useForm() // setValue͕ͱʹ͔͘ॏ͍ʂ form.setValue('foo.bar.baz', 123) // ·ΔͬͱऔΔ৔߹͸໰୊ͳ͠ const values = form.getValues() type Bar3 = { baz: Bar4 baz2: Bar4 baz3: Bar4 ... baz10: Bar4 } type Bar4 = { qux: number qux2: number qux3: number ... qux20: number } interface Values { foo: { bar: { baz: Bar3 }[] }[] } ↓のどでかオブジェクトは適当ですm(_ _)m

Slide 45

Slide 45 text

҆ఆͷgetConditionalTypeInstantiation() 1ඵ͘Β͍ Τϥʔදࣔʹ PathValue<...>

Slide 46

Slide 46 text

react-hook-formΛࢧ͑Δ2ͭͷܕ // TͷΦϒδΣΫτͰࢦఆͰ͖Δશͯͷύε (ࠓճ215͙Β͍) type Path // T͔ΒPathͷҐஔʹ͋ΔܕΛऔΓग़͢ type PathValue setValue(path: Path<...>, value: PathValue<...>)

Slide 47

Slide 47 text

ॏ͔ͬͨͷ͸ ύεʹରԠ͢ΔܕΛऔΔܕ export type PathValue | ArrayPath> = T extends any ? P extends `${infer K}.${infer R}` // Split (6ఔ౓) ? K extends keyof T ? R extends Path // શ݅औಘ (215ఔ౓) ? PathValue : never : K extends `${ArrayKey}` ? T extends ReadonlyArray ? PathValue> // 215ఔ౓ (ωετͰ͸ͳ͍) ... 6x215x2 = 2580ɻMUIͰ͸5ສͱ͔ɻ ύεͰ͋Δͱ͜ΖͷP͕େ͖͍Unionʹɾɾʁ

Slide 48

Slide 48 text

ͪΐͬͱ·ͬͯɾɾʁ ύεʹݻఆͷจࣈྻΛ ౉ͯ͠ΔΜͰ͚͢Ͳ

Slide 49

Slide 49 text

ຊ౰͸ා͍ Generic Constraintsͷ࿩ ܁Γฦ͠ʹͳΓ·͕͢ɾɾɾ

Slide 50

Slide 50 text

Base ConstraintͰղܾ → औΓ͏ΔશͯͷύεͷܕͰϧʔϓ // TFiledValues͸Values͕ೖΓ·͢ export declare function useForm( props?: ... ): { setValue: = Path>( name: TFieldName, value: PathValue ) => void }

Slide 51

Slide 51 text

O(mn2) m = υοτ෼ׂͷ࠶ؼ਺ɺn = औΓ͏Δύεͷ߹ܭ਺ 6x215 x215 Ͱ28ສऑ ࣮ࡍ͸ɺ45ສճఔ౓ͷΑ͏Ͱͨ͠

Slide 52

Slide 52 text

͓඼ॻ͖ • エディタと型 (重いと・・?) • 型が重い #とは • 型計算のホットスポット • ライブラリでの例 • どうすれば改善できる?

Slide 53

Slide 53 text

͜͏͍͏ॏ͍ܕɺ Ͳ͏͢Ε͹վળͰ͖Δʁ

Slide 54

Slide 54 text

ܕύϥϝʔλΛࣗ෼Ͱݻఆ (΍Γͨ͘ͳ͍) /> setValue<'path', number>(...)

Slide 55

Slide 55 text

Φʔόʔϩʔυ΍ΠϯλϑΣʔεΛ޻෉ͯ͠ճආ͢Δ 1. ස౓ͷߴ͍ͯܰ͘γάωνϟΛલʹ 2. ؔ਺ͷܕύϥϝʔλͷࢦఆͱ࢖༻Λ෼͚Δ

Slide 56

Slide 56 text

ස౓ͷߴ͍ͯܰ͘ Φʔόʔϩʔυ (γάωνϟ) Λ্ʹ foo(1) ͷ৔߹ function foo(a: { rarelyUsed: Complex }): void function foo(a: number): void function foo(a: number): void // ͪ͜Β͚ͩ࢖͏ਓ͸ɺͲͰ͔͍ܕͰinstantiate͞ΕͣʹࡁΉ function foo(a: { rarelyUsed: Complex }): void function foo(a: number): void // ΤϥʔදࣔͷͨΊ࠷ޙʹ͜Ε΋࢒͢΂͖͔΋

Slide 57

Slide 57 text

hono͸࣮ࡍʹ γάωνϟҠಈ Ͱվળ (...) (...) (...) ... (path: string, ...) (path: string, ...) (path: string, ...) ... (...) (path: string, ...) (...) (path: string, ...) (...) (path: string, ...) ... 他の修正と合わせて、 7倍くらい速くなった とのこと https://github.com/honojs/hono/pull/2412

Slide 58

Slide 58 text

ؔ਺ͷܕύϥϝʔλͷࢦఆͱ࢖༻Λ෼͚Δ form.setField(path, value) 1ඵʙ import { FieldPath, FieldPathValue, FieldValues, SetValueConfig } from 'react-hook-form' export interface CurriedForm { field: = FieldPath>( name: TFieldName ) => ForField } interface ForField> { setValue: (value: FieldPathValue, options?: SetValueConfig) => void } form.field(path).setValue(value) 20msʙ (pathの型パラメータが定まらないまま、PathValueに渡していたのを、field()で固定してから渡るようにした)

Slide 59

Slide 59 text

ඞཁʹԠͯ͡ TypeScriptͷܭࢉʹదͨ͠ ΠϯλϑΣʔεઃܭ͕ඞཁ

Slide 60

Slide 60 text

σόοάํ๏ • tsc --noEmit --skipLibCheck --generateTrace [dirName] --extendedDiagnostics [fileName] • tsconfig.jsonを読み込めない • Debuggerを直接接続: TS Server Debug 拡張 • 型情報を見るのが難しい • tsserverでgenerate trace • 起動時からtsserverのtraceを有効化: vscodeの設定 • 動いているtsserverでtraceを実行するvscode拡張 ←New..!!!

Slide 61

Slide 61 text

େ͍ͳΔܕύϫʔʹ͸ େ͍ͳΔϧʔϓ͕൐͏

Slide 62

Slide 62 text

ίϯύΠϥͷؾ࣋ͪʹͳΕ͹ ճආͰ͖Δʂʂ

Slide 63

Slide 63 text

·ͱΊ • 若干重いだけでも編集体験に影響 • 重いとはType Instantiation回数 • Distributed Conditional Types • Template Literal Types • Generic Constraints • 型定義を工夫し、Instantiationを 回避する

Slide 64

Slide 64 text

͓΋͠Ζྫ • function Component({ sx }: { sx: SxProps }) { // 型パラメータが違ってstructualTypeRelatedToが重い。 // SxPropsが正解。 return } • TS PRした件:keyPropertyNameによるinstantiation • Unionが10個以上のときだけ、最適化のため (が、重い) • primitiveは数に含めない修正をPRしてmergeされた

Slide 65

Slide 65 text

FAQ • プロパティ数が多いTをkeyof Tで参照するだけでは重くない? • 確かにループだが、instantiationのほうが遅い模様 • • 型パラメータの推論は、型を広げてから狭める形で解決する気がす る (i.e. Contextual Type)。 •

Slide 66

Slide 66 text

Not FAQ • 引数型を T extends Base ? never : T にするとオーバーロード 解決どうなる?