Share
Embed
Transcript
156 slides
-
About Me ▪ Takefumi Yoshii / @Takepepe ▪ DeNA /
DeSC Healthcare ▪ Frontend Engineer ▪ TypeScript Meetup JP member 2 -
1-4. 不自然なコードベース 単純な関数定義を思い出してみてください。 28 28 28 function getRank(amount?: number) {
if (!amount) return null if (amount > 50) return 'A' return 'B' } const rank1 = getRank() const rank2 = getRank(50) const rank1: "A" | "B" | null const rank2: "A" | "B" | null inferred -
1-4. 不自然なコードベース 推論器は実装内容を正しく捉え、型注釈なくして型情報は導かれます。 29 29 29 function getRank(amount?: number) {
if (!amount) return null if (amount > 50) return 'A' return 'B' } const rank1 = getRank() const rank2 = getRank(50) const rank1: "A" | "B" | null const rank2: "A" | "B" | null inferred -
1-4. 不自然なコードベース 私はこれを「TypeScriptのアイデンティティ」だと感じています。 30 30 30 function getRank(amount?: number) {
if (!amount) return null if (amount > 50) return 'A' return 'B' } const rank1 = getRank() const rank2 = getRank(50) const rank1: "A" | "B" | null const rank2: "A" | "B" | null inferred -
1-4. 不自然なコードベース 多くの型定義を必要としないコードこそ TypeScript らしいコードであり、 親しまれている「DX」だと私は考えています。 その「DX」は Vue.js や Vuex
らしい、 守りたいアイデンティティです。 31 31 31 -
2-3. vuex-guardian code base 動画で紹介した コードベースを おさらいします。 44 44 44
import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base はじめに、LocalContext を import 。単一 Module の
Context を特定します。 45 45 45 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] // Module NameSpace export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base これを action 関数の 引数に型注釈します。 46 46
46 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base これだけで、単一 Module 内の定義に対し、 型安全が約束されます。 47 47
47 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base mutation 関数引数を 変更したら、 コンパイルエラーを得る ことが出来ます。 48
48 48 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: string }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base 関数名の変更も、 すぐさま検知されます。 49 49 49 import
{ LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setValue(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base 定義することは、 これだけです。 実装を型定義の 一次ソースとしています。 50 50
50 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base 'vuex' パッケージには、 全 Store 実装の「型」 が格納されています。
51 51 51 import { LocalContext } from 'vuex' type Context = LocalContext['counter'] export const mutations = { setCount(state: State, payload: { amount: number }) { state.count = payload.amount } } export const actions = { acyncSetCount(ctx: Context, payload: { amount: number }) { ctx.commit({ type: 'setCount', amount: payload.amount }) } } -
2-3. vuex-guardian code base 少し調整が必要ですが、 SFC における Vue.extend 記法であっても、 型推論は行き届きます。
53 53 53 参考:https://qiita.com/Takepepe/items/593d2d9e7dfd38eddbd4 -
3-2. 型の部分抽出 簡単な例を紹介します。 次の様な関数から「第二引数型だけ」抽出したい場合。 62 62 62 function addTodo(state: State,
todo: Todo) { state.todos.push(todo) } -
3-2. 型の部分抽出 簡単な例を紹介します。 次の様な関数から「第二引数型だけ」抽出したい場合。 63 63 63 function addTodo(state: State,
todo: Todo) { state.todos.push(todo) } -
3-2. 型の部分抽出 次の「A2」の様な補助型を利用すると、第二引数型が抽出できます。 64 64 64 type A2<T> = T
extends (a1: any, a2: infer I) => any ? I : never type T = typeof addTodo type U = A2<typeof addTodo> type T = (state: State, todo: Todo) => void type U = Todo inferred -
3-2. 型の部分抽出 ここが「Type Inference in conditional types」です。 65 65 65
type A2<T> = T extends (a1: any, a2: infer I) => any ? I : never type T = typeof addTodo type U = A2<typeof addTodo> type T = (state: State, todo: Todo) => void type U = Todo inferred -
3-2. 型の部分抽出 typeof 型クエリーを使うことで、実装から型を抽出できます。 66 66 66 type A2<T> =
T extends (a1: any, a2: infer I) => any ? I : never type T = typeof addTodo type U = A2<typeof addTodo> type T = (state: State, todo: Todo) => void type U = Todo inferred -
3-2. 型の部分抽出 「addTodo」という、さきほどの関数定義を参照しています。 67 67 67 type A2<T> = T
extends (a1: any, a2: infer I) => any ? I : never type T = typeof addTodo type U = A2<typeof addTodo> type T = (state: State, todo: Todo) => void type U = Todo inferred -
3-2. 型の部分抽出 それは新しい型として定義され、参照を継続します。 68 68 68 type A2<T> = T
extends (a1: any, a2: infer I) => any ? I : never type T = typeof addTodo type U = A2<typeof addTodo> type T = (state: State, todo: Todo) => void type U = Todo inferred -
3-2. 型の部分抽出 実際に mutation 関数から型を抽出してみます。 69 69 69 export const
mutations = { addTodo(state: State, payload: { todo: Todo }) { state.todos.push(payload.todo) } } type T = A2<typeof mutations['addTodo']> type T = { todo: Todo } -
3-2. 型の部分抽出 型定義は JavaScript と同じように、添字参照が出来ます。 70 70 70 export const
mutations = { addTodo(state: State, payload: { todo: Todo }) { state.todos.push(payload.todo) } } type T = A2<typeof mutations['addTodo']> type T = { todo: Todo } -
3-2. 型の部分抽出 typeof 型クエリーと、さきほどの「A2」型を併用すると… 71 71 71 export const mutations
= { addTodo(state: State, payload: { todo: Todo }) { state.todos.push(payload.todo) } } type T = A2<typeof mutations['addTodo']> type T = { todo: Todo } -
3-2. 型の部分抽出 これはまさに、store.commit に必要な payload 型です。 72 72 72 export
const mutations = { addTodo(state: State, payload: { todo: Todo }) { state.todos.push(payload.todo) } } type T = A2<typeof mutations['addTodo']> type T = { todo: Todo } -
3-2. 型の部分抽出 次に、getter 関数を見ていきましょう。 73 73 73 export const getters
= { doneCount(state: State) { return state.todos.filter(todo => todo.done).length } } type T = ReturnType<typeof getters['doneCount']> type T = number -
3-2. 型の部分抽出 この関数は、外部からは値として見えます。 74 74 74 export const getters =
{ doneCount(state: State) { return state.todos.filter(todo => todo.done).length } } type T = ReturnType<typeof getters['doneCount']> type T = number this.$store.getters['todos/doneCount'] -
3-2. 型の部分抽出 これは、戻り型で表現することができます。 75 75 75 export const getters =
{ doneCount(state: State) { return state.todos.filter(todo => todo.done).length } } type T = ReturnType<typeof getters['doneCount']> type T = number -
3-2. 型の部分抽出 ビルトインユーティリティ型の「ReturnType」を利用すれば… 76 76 76 export const getters =
{ doneCount(state: State) { return state.todos.filter(todo => todo.done).length } } type T = ReturnType<typeof getters['doneCount']> type T = number -
3-2. 型の部分抽出 値に相当する型を抽出することができました。 77 77 77 export const getters =
{ doneCount(state: State) { return state.todos.filter(todo => todo.done).length } } type T = ReturnType<typeof getters['doneCount']> type T = number -
3-3. 型の文字列参照 Root参照文字列を見てみましょう。 81 81 81 interface RootGetters { 'todos/doneCount':
LocalGetters['todos']['doneCount'] } -
3-3. 型の文字列参照 文字列をプロパティキーとすることで、 参照文字列を表現できます。 82 82 82 interface RootGetters {
'todos/doneCount': LocalGetters['todos']['doneCount'] } -
3-3. 型の文字列参照 そこへ、対応する参照型を格納します。 83 83 83 interface RootGetters { 'todos/doneCount':
LocalGetters['todos']['doneCount'] } -
3-3. 型の文字列参照 参照型は「一つの型定義」に集約します。 84 84 84 interface RootGetters { 'todos/doneItems':
LocalGetters['todos']['doneItems'] 'todos/visibleItems': LocalGetters['todos']['visibleItems'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } -
3-3. 型の文字列参照 全ての参照は、集約されたこの型を利用します。 85 85 85 interface RootGetters { 'todos/doneItems':
LocalGetters['todos']['doneItems'] 'todos/visibleItems': LocalGetters['todos']['visibleItems'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } -
3-4. 宣言空間の活用 集約した参照型は、 'vuex' 名前空間に対し Ambient Module 宣言。 87 87
87 import 'vuex' declare module 'vuex' { // Ambient Module Declaration interface RootGetters { 'counter/double': LocalGetters['counter']['double'] 'counter/expo': LocalGetters['counter']['expo'] 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } } -
3-4. 宣言空間の活用 集約した参照型は、 'vuex' 名前空間に対し Ambient Module 宣言。 88 88
88 import 'vuex' declare module 'vuex' { interface RootGetters { 'counter/double': LocalGetters['counter']['double'] 'counter/expo': LocalGetters['counter']['expo'] 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } } -
3-4. 宣言空間の活用 Ambient Module 宣言 によりその型定義は、 まるでライブラリに本来 備わったものである かの様に振る舞います。 89
89 89 import 'vuex' declare module 'vuex' { interface RootGetters { 'counter/double': LocalGetters['counter']['double'] 'counter/expo': LocalGetters['counter']['expo'] 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } } -
3-4. 宣言空間の活用 vuex-guardian は、 より強固に定義された 「StrictStore型」を 持ちます。 90 90 90
import 'vuex' declare module 'vuex' { interface StrictStore extends Store<RootState> { getters: RootGetters commit: StrictCommit<MutationTypes> dispatch: StrictDispatch<ActionTypes> } } -
3-4. 宣言空間の活用 「StrictStore型」は このように宣言空間に 宣言された集約型を 参照します。 91 91 91 import
'vuex' declare module 'vuex' { interface StrictStore extends Store<RootState> { getters: RootGetters commit: StrictCommit<MutationTypes> dispatch: StrictDispatch<ActionTypes> } } -
3-4. 宣言空間の活用 この「StrictStore型」が もつ commit / dispatch について、もう少し 深掘りしていきます。 92
92 92 import 'vuex' declare module 'vuex' { interface StrictStore extends Store<RootState> { getters: RootGetters commit: StrictCommit<MutationTypes> dispatch: StrictDispatch<ActionTypes> } } -
3-5. 文字列参照と Lookup $store.dispatch について確認していきましょう。 94 94 94 interface ActionTypes
{ 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 従来、文字列参照による型の特定は出来ませんでした。 95 95 95 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 参照型が格納されたプロパティキーは、文字列です。 96 96 96 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 格納されている参照型は、payload型に相当します。 97 97 97 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 実は、これは強推論のために用意されたものです。 98 98 98 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 関数は与えられた文字列から、型の Lookup が可能です。 99 99 99 interface
ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup 第一引数(ActionType文字列)が与えられた時… 100 100 100 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup それに対応する型が確定します。 101 101 101 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup もう一度。第一引数(ActionType文字列)が与えられた時… 102 102 102 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup それに対応する型が確定します。 103 103 103 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup この機能により、コードに型情報がなくとも、型安全が担保されます。 104 104 104 interface ActionTypes {
'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
3-5. 文字列参照と Lookup JavaScript コードと「同じ」であっても、型情報はついてまわります。 105 105 105 interface ActionTypes
{ 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } interface StrictDispatch<A> { <T extends keyof A>(type: T, payload?: A[T], options?: DispatchOptions): Promise<void> } this.$store.dispatch('todos/acyncDoneTodo', { id: this.data.id }) -
4-1. 創出の型パズル「Compiler API」 こうして全ての参照型を集約し 強化された「StrictStore型」は、 SFC の $store に適用されます。 108
108 108 interface LocalGetters { todos: { todosCount:ReturnType<Modules['todos']['getters']['todosCount']> doneCount: ReturnType<Modules['todos']['getters']['doneCount']> } } interface RootGetters { 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } interface MutationTypes { 'todos/addTodo': LocalMutationTypes['todos']['addTodo'] 'todos/doneTodo': LocalMutationTypes['todos']['doneTodo'] } interface ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } -
4-1. 創出の型パズル「Compiler API」 そのため SFC であっても、 Vuex における変化を、 コンパイルエラーとして 検出することが出来ます。
109 109 109 interface LocalGetters { todos: { todosCount:ReturnType<Modules['todos']['getters']['todosCount']> doneCount: ReturnType<Modules['todos']['getters']['doneCount']> } } interface RootGetters { 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } interface MutationTypes { 'todos/addTodo': LocalMutationTypes['todos']['addTodo'] 'todos/doneTodo': LocalMutationTypes['todos']['doneTodo'] } interface ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } -
4-1. 創出の型パズル「Compiler API」 しかし、参照型の量は膨大です。 手動でこの型を定義していくのは、 あまりにも骨が折れます。 110 110 110 interface
LocalGetters { todos: { todosCount:ReturnType<Modules['todos']['getters']['todosCount']> doneCount: ReturnType<Modules['todos']['getters']['doneCount']> } } interface RootGetters { 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } interface MutationTypes { 'todos/addTodo': LocalMutationTypes['todos']['addTodo'] 'todos/doneTodo': LocalMutationTypes['todos']['doneTodo'] } interface ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } -
4-1. 創出の型パズル「Compiler API」 リファクタリングしようものなら、 多大な修正を強いられます。 これは求めたものではありません。 111 111 111 interface
LocalGetters { todos: { todosCount:ReturnType<Modules['todos']['getters']['todosCount']> doneCount: ReturnType<Modules['todos']['getters']['doneCount']> } } interface RootGetters { 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } interface MutationTypes { 'todos/addTodo': LocalMutationTypes['todos']['addTodo'] 'todos/doneTodo': LocalMutationTypes['todos']['doneTodo'] } interface ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } -
4-1. 創出の型パズル「Compiler API」 この「参照型」を自動生成 するために最適な機能が 「CompilerAPI」です。 112 112 112 interface
LocalGetters { todos: { todosCount:ReturnType<Modules['todos']['getters']['todosCount']> doneCount: ReturnType<Modules['todos']['getters']['doneCount']> } } interface RootGetters { 'todos/todosCount': LocalGetters['todos']['todosCount'] 'todos/doneCount': LocalGetters['todos']['doneCount'] } interface MutationTypes { 'todos/addTodo': LocalMutationTypes['todos']['addTodo'] 'todos/doneTodo': LocalMutationTypes['todos']['doneTodo'] } interface ActionTypes { 'todos/acyncAddTodo': LocalActionTypes['todos']['acyncAddTodo'] 'todos/acyncDoneTodo': LocalActionTypes['todos']['acyncDoneTodo'] } -
4-3. tsc と CompilerAPI の概要 tsc は TypeScript プロジェクトを JavaScript
にビルドするコマンドですね。 117 117 117 -
4-3. tsc と CompilerAPI の概要 tsc ビルド時、はじめに tsconfig がパースされます。 118
118 118 tsconfig -
4-3. tsc と CompilerAPI の概要 そこに記された設定から、対象 SRC となるファイルパス一覧を取得。 119 119
119 'path/to/modules/todo.ts', 'path/to/modules/counter.ts' -
4-3. tsc と CompilerAPI の概要 その AST をもって、指定された target バージョンの
JavaScript に トランスパイルされ、出力に至ります。 121 121 121 emitFiles -
4-4. AST を辿る 先程捉えた参照型を再確認してみます。 これを生成するためには、 参照文字列や関数名を知る必要がありました。 127 127 127 doneCount:
ReturnType<Modules['todos']['getters']['doneCount']> -
4-4. AST を辿る TypeScript AST が構築されれば 「どこに・なにが・どんな名称で」 定義されているのか全てを把握することができます。 128 128
128 doneCount: ReturnType<Modules['todos']['getters']['doneCount']> where what whatName -
4-4. AST を辿る 特定の名称(例えばgetters)の定義を特定し、 そこに含まれる関数名一覧を取得することができます。 129 129 129 doneItems: ReturnType<Modules['todos']['getters']['doneItems']>,
visibleItems: ReturnType<Modules['todos']['getters']['visibleItems']>, doneCount: ReturnType<Modules['todos']['getters']['doneCount']>, -
4-4. AST を辿る 名称一覧を取得したうえで、この型定義を創り出す。 これが可能になるタイミングはどこでしょうか? 130 130 130 doneItems: ReturnType<Modules['todos']['getters']['doneItems']>,
visibleItems: ReturnType<Modules['todos']['getters']['visibleItems']>, doneCount: ReturnType<Modules['todos']['getters']['doneCount']>, -
4-4. AST を辿る Nuxt.js の Vuex Module Mode は、 規約に従います。
Node.js のファイルシステムが 開発をサポートします。 133 133 133 store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-4. AST を辿る Vuex Module Mode ならではの、 このファイルツリー構造規約が、 今回のアイディアに合致しました。 134
134 134 store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts "todos/todosCount": ReturnType<Modules["todos"]["getters"]["todosCount"]> -
4-4. AST を辿る プロパティキーに相当する文字列は、 ファイルパスから辿ることが できるためです。 135 135 135 "todos/todosCount":
ReturnType<Modules["todos"]["getters"]["todosCount"]> store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-4. AST を辿る 型推論では不可能だった、 文字列型の合成は、 Node.js にとっては容易いことです。 136 136 136
"todos/todosCount": ReturnType<Modules["todos"]["getters"]["todosCount"]> store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-4. AST を辿る AST から得た関数名は、 この様に文字列合成され、 参照型の一部を担います。 137 137 137
"todos/todosCount": ReturnType<Modules["todos"]["getters"]["todosCount"]> store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-4. AST を辿る 文字列型は、TypeScript の強推論に はなくてはならないものです。 それは、Lookup を可能にします。 138 138
138 "todos/todosCount": ReturnType<Modules["todos"]["getters"]["todosCount"]> store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-4. AST を辿る 文字列型を自由に創出可能 ということは、大きな可能性を 意味します。 139 139 139 "todos/todosCount":
ReturnType<Modules["todos"]["getters"]["todosCount"]> store ├── counter │ └── index.ts ├── index.ts └── todos └── index.ts -
4-5. AST を創る CompilerAPI は、その SRC コードを パースするに留まりません。 ASTを構築するための、 ファクトリ関数群があります。
141 141 141 import 'vuex' ts.createImportDeclaration( undefined, undefined, undefined, ts.createStringLiteral('vuex') ) -
4-5. AST を創る たとえば、この様な SRC コードを生成したい場合。 142 142 142 import
'vuex' ts.createImportDeclaration( undefined, undefined, undefined, ts.createStringLiteral('vuex') ) -
4-5. AST を創る これに相当する AST は、 TypeScript から提供されている このファクトリ関数をもって 創ることができます。
143 143 143 import 'vuex' ts.createImportDeclaration( undefined, undefined, undefined, ts.createStringLiteral('vuex') ) -
4-5. AST を創る ファクトリ関数に変数を与えれば、 任意の SRC コードを 創ることができます。 144 144
144 import 'vuex' ts.createImportDeclaration( undefined, undefined, undefined, ts.createStringLiteral('vuex') ) -
4-5. AST を創る たとえば、この様な SRC コードを生成したい場合。 145 145 145 import
* as Module from '/path/to/module' ts.createImportDeclaration( undefined, undefined, ts.createImportClause( undefined, ts.createNamespaceImport( ts.createIdentifier('Module') ) ), ts.createStringLiteral('/path/to/module') ) -
4-5. AST を創る これに相当する AST は、 TypeScript から提供されている このファクトリ関数をもって 創ることができます。
146 146 146 import * as Module from '/path/to/module' ts.createImportDeclaration( undefined, undefined, ts.createImportClause( undefined, ts.createNamespaceImport( ts.createIdentifier('Module') ) ), ts.createStringLiteral('/path/to/module') ) -
4-5. AST を創る ファクトリ関数に変数を与えれば、 任意の SRC コードを 創ることができます。 147 147
147 import * as Module from '/path/to/module' ts.createImportDeclaration( undefined, undefined, ts.createImportClause( undefined, ts.createNamespaceImport( ts.createIdentifier('Module') ) ), ts.createStringLiteral('/path/to/module') ) -
4-5. AST を創る AST ファクトリ関数を組み合わせ、 目的に沿った型定義を 創り出すことができます。 148 148 148
import * as Module from '/path/to/module' ts.createImportDeclaration( undefined, undefined, ts.createImportClause( undefined, ts.createNamespaceImport( ts.createIdentifier('Module') ) ), ts.createStringLiteral('/path/to/module') ) -
vuex-guardian https://github.com/takefumi-yoshii/vuex-guardian TypeScript CompilerAPI - 創出の落書帳 - https://booth.pm/ja/items/1575217 Vuex型推論・最終章 -
vuex-guardian - https://qiita.com/Takepepe/items/593d2d9e7dfd38eddbd4 155 155 155 参考文献