質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

Q&A

0回答

1742閲覧

TypeScriptで_.mapValuesの型付け

7Kreuz

総合スコア112

Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

TypeScript

TypeScriptは、マイクロソフトによって開発された フリーでオープンソースのプログラミング言語です。 TypeScriptは、JavaScriptの構文の拡張であるので、既存の JavaScriptのコードにわずかな修正を加えれば動作します。

0グッド

2クリップ

投稿2018/12/28 01:12

編集2019/01/05 04:55

前提・実現したいこと

Electron + Vue + TypeScript にて、データベースから取得したデータを加工して表示するようなアプリケーションを作っています(今回はElectronとVueはあまり関係ない……と思います)。

この際_.mapValuesを多用するのですが、これをうまく型付けするのが難しく、悩んでいる次第です。

やりたいことを抽象化したコードは、次のようになります。

typescript

1import _ from 'lodash' 2 3const obj = { 4 a1: {b1: 1, b2: 'b'}, 5 a2: {b1:2, b3: 3} 6} 7 8const func = <T>(arg: T) => ({x1: {...arg, b4: 'c'}, x2: 1}) 9 10const result = _.mapValues(obj, func)

このときのtypeof result

typescript

1{ 2 a1: { 3 x1: {b1: number; b2: string; b4: string}; 4 x2: number 5 }; 6 a2: { 7 x1: {b1: number; b3: number; b4: string}; 8 x2: number 9 } 10}

であるはずです。これを推論させるのが目標となります。

試したこと

#####① _.mapValuesの型を定義するだけでいいなら最善のはず

_.mapValuesが@typesの型定義のままではうまくいかないということで、まずは次のようにしてみました。

typescript

1type MapValues<O, F> = { 2 (obj: O, func: F): { 3 [K in keyof O]: F extends {(n1: O[K]): infer R} ? R : never 4 } 5} 6 7const result = (_.mapValues as MapValues<typeof obj, typeof func>)(obj, func)

しかし、このときのtypeof result

typescript

1{ 2 a1: { 3 x1: {b4: string}; 4 x2: number 5 }; 6 a2: { 7 x1: {b4: string}; 8 x2: number 9 } 10}

となり、result.a1.x1.b1などが存在しないことになってしまいます。

② 世の中にはいろいろライブラリがあるから……

この手の型の扱いは、関数型系統のライブラリなら強いはず、と思い、ramda.js、sanctuary.jsを試してみました。
しかし、ramda.jsのmapObjIndexed、sanctuary.js、のmapともに、typeof result

typescript

1{ 2 [key: string]: { 3 x1: {b4: string;}; 4 x2: number; 5 }; 6}

となってしまいました。

③ 丁寧に書けば一応できる

前提のコードにおいて、typeof funcは下記になります。

typescript

1<T>(arg: T) => {x1: T & {b4: string;}; x2: number;}

そこで、

typescript

1type FuncType<T> = (arg: T) => {x1: T & {b4: string}; x2: number} 2type MapValues<O> = (obj: O, func: any) => {[K in keyof O]: ReturnType<FuncType<O[K]>>} 3 4const result= (_.mapValues as MapValues<typeof obj>)(obj, func)

とすることで、resultの型を正しく推論できるようになりました。

###では何が問題なのか

  1. _.mapValuesを利用するたびに、FuncTypeを定義する必要があります。<T>(arg: T) => (Tのナニカ)という型から、FuncType<T> = (arg: T) => (Tのナニカ)という型を生成できればいいのですが……。
  2. _.mapValuesを利用するたびに、MapValues(そのときの_.mapValuesの型)を定義する必要があります。MapValuesの型引数としてFuncTypeが渡せればいいのですが、型引数がついていないFuncTypeは型ではないので、うまくいきません。

###望んでいる解決策

  1. (最上)mapValues的なものの型推論が希望通りに働くライブラリを見つける。
  2. (上)FuncTypeを毎回定義してくても、typeof funcからFuncType<T>を生成できる、……ような感じのことをする。
  3. (中)FuncType<T>は毎回書くことにするが、type MapValuesは毎回定義しなくても、FuncTypeを型引数にして生成できる、……ような感じのことをする。

mapValuesについてググってみると、同様の悩みを持っている人は多いようで、やはり難しそうではありますが、ご回答をよろしくお願いいたします。

###補足
_.mapValuesのcallbackは(value, key, object)の3引数を持ちますが、上記はとりあえずvalueだけ用いて試行錯誤した結果です。最終的にはすべて使えるのが目標です。

###続・試したこと(ミス修正しました)
完全な解決ではありませんが、実用上そこそこいい感じになったものを追記します。

typescript

1type ObjValue<O> = { 2 [K in keyof O]: { 3 [L in keyof { 4 [K in keyof O]: {[M in keyof O[keyof O]]: O[K][M]} 5 }[keyof O]]: O[K][L] 6 } & 7 { 8 [K in keyof O]: { 9 [L in {[M in keyof O]: keyof O[M]}[keyof O]]?: L extends keyof O[K] 10 ? O[K][L] 11 : never 12 } 13 }[keyof O] 14}[keyof O] 15 16const func = <T extends ObjValue<typeof obj>>(arg: T) => ({ 17 x1: {...arg, b4: 'c'}, 18 x2: 1 19}) 20 21type MapValues<O, F> = { 22 (obj: O, func: F): { 23 [K in keyof O]: F extends {(n1: O[K]): infer R} ? R : never 24 } 25} 26 27const result = (_.mapValues as MapValues<typeof obj, typeof func>)(obj, func)

こうすると、ObjValue<typeof obj>{b1: number; b2?: string; b3?: number}となり、typeof resultは下記のようになります。

typescript

1{ 2 a1: {x1: {b1: number; b2?: string; b3?: number; b4: string}; x2: number}; 3 a2: {x1: {b1: number; b2?: string; b3?: number; b4: string}; x2: number}; 4}

本来の目標から見ると、型の情報はけっこう抜け落ちてしまっているものの、インテリセンスは実用上役に立つレベルで効きますので、最悪、本質的な解決ができないならこんな感じで行こうと思います。

とはいえ、やっぱり本質的な解決が望ましいので、まだまだ回答をお待ちしています。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問