タイプスクリプトで日常的に働いている5年と私は、ジェネリックがそれをすることができたという考えが全くありませんでした🤯!
32562 ワード
文脈
私たちが所有していないAPIは、同じプロパティを含むオブジェクトを返すことができます.例:
interface Order {
// some unique keys
id: string;
name: string;
// some keys "grouped" by 3
price1: number;
price2: number;
price3: number;
quantity1: number;
quantity2: number;
quantity3: number;
shippingDate1: string;
shippingDate2: string;
shippingDate3: string;
}
注意:prices
, quantities
and shippingDates
配列がポイントを超えています.修正できないAPIを消費しようとしましょう挑戦
バックエンドが良い理由のためにこのようにそれを公開するかもしれませんが、我々のケースでは、フロントエンド側で、我々はむしろより良い方法で我々のニーズに合っているデータ構造を持っていたいです.
第1部
ビルドA
getGroupedValues
持つ関数Order
最初の引数として、2番目の引数として渡すことができますprice
, quantity
or shippingDate
Order
, 2番目の引数がprice
我々は、出力として期待するnumber[]
でももしshippingDate
我々は期待するstring[]
interface OrderRemap {
id: string;
name: string;
prices: number[];
quantities: number[];
shippingDates: string[];
}
declare const order: Order; // no need to know where it's coming from for the example
const orderRemap: OrderRemap = {
id: order.id,
name: order.name,
prices: getGroupedValues(order, 'price'),
quantities: getGroupedValues(order, 'quantity'),
shippingDates: getGroupedValues(order, 'shippingDate'),
};
主な目的は:可能な限り安全なタイプとしてその機能を持っている.第2部
次のすべてを処理する関数を構築しましょう.
const orderRemap: OrderRemap = {
id: order.id,
name: order.name,
prices: getGroupedValues(order, 'price'),
quantities: getGroupedValues(order, 'quantity'),
shippingDates: getGroupedValues(order, 'shippingDate'),
};
代わりに、型がこのように定義されている場合:interface OrderDetail {
price: number;
quantity: number;
shippingDate: string;
}
interface OrderRemapGrouped {
id: string;
name: string;
orderDetails: OrderDetail[]
}
単純に行う必要があります.declare const order: Order; // no need to know where it's coming from for the example
const orderRemapGrouped: OrderRemapGrouped = groupProperties(order, 'orderDetails');
あなたはこれらの課題は、解決策を読む前に自分自身を移動したい場合は、今は良い時間です!単に開くことができますhttps://www.typescriptlang.org/play そして、結局、コメントとしてあなたの遊び場のURLを共有してください😄.実装
第1部
このAPIは常にこれらのプロパティを返します.
price1
, price2
, price3
).そこで、最近ではtypescriptの機能を使ってここから始めます.Template literal types .
type Indices = 1 | 2 | 3;
type GroupedKeys<T> = T extends `${infer U}${Indices}` ? U : never;
それは3回右に表示されるすべてのプロパティの基本キーを抽出することはかなり簡単ですか?テストをするなら、以下のように出力します:
interface Order {
id: string;
name: string;
price1: number;
price2: number;
price3: number;
quantity1: number;
quantity2: number;
quantity3: number;
shippingDate1: string;
shippingDate2: string;
shippingDate3: string;
}
type Indices = 1 | 2 | 3;
type GroupedKeys<T> = T extends `${infer U}${Indices}` ? U : never;
type Result = GroupedKeys<keyof Order>; // "price" | "quantity" | "shippingDate" 🎉
しかし、心配しないでください、それは私が得ていたところでありません.があります.では、私たちのジェネリックを作りましょう
getGroupedValues
関数(ポイントとしての実装ではなく、定義)function getGroupedValues<Obj, BaseKey extends GroupedKeys<keyof Obj>>(object: Obj, baseKey: BaseKey): Array<Obj[`${BaseKey}${Indices}`]> {
return null as any; // we're not implementing the function, just focusing on its definition
}
これはまさに我々が欲しいものかもしれない感じ!Obj
上記の制約はないObj
, したがって、私たちは書くBaseKey extends GroupedKeys<keyof Obj>
object: Obj, baseKey: BaseKey
price1
, price2
, price3
) でbaseKey
入力として渡されますprice
. したがって、オブジェクトにアクセスするならば${BaseKey}${Indices}
, 我々は得るprice1
| price2
| price3
どれが欲しいのかObj[`${BaseKey}${Indices}`]
と言う.Type '
${BaseKey}1
|${BaseKey}2
|${BaseKey}3
' cannot be used to index type 'Obj'
そして、それは合法的に見えます.我々は、何も拡張しないジェネリックのプロパティにアクセスしようとしています
Obj
種類).しかし、どのように我々はこの機能を一般的に保つことができて、我々のオブジェクトがベースキーとインデックスから成るキーを持つと指定することができますか🤔...
Would
Obj extends Record<`${BaseKey}${Indices}`, any>
仕事?確かにそれは動作できませんBaseKey
の後に定義されObj
:Obj extends Record<`${BaseKey}${Indices}`, any>, BaseKey extends GroupedKeys<keyof Obj>
よく、これはtypescriptのためにちょうどよいです🤯.再びこれを読んでください.
与えられたタイプと言うのは良いことだ
Obj
オブジェクトになります(Record
) これは他の型のキーを含むBaseKey
), そのキーを読むことによって定義されるObj
.ミー
私は、タイプスクリプトがリストを持つことができるリストを定義するかどうか、リストを持つことができるように、ちょうどあなたが再帰的に再帰を扱うことができるということを知っていました.
interface List {
propA: number;
list?: List
}
const list: List = {
propA: 1,
list: {
propA: 2,
list: {
propA: 3
}
}
}
でもこれ?私はそれが驚くべきだと思う間、タイプスクリプトがどのようにタイプを解決するかを確信していません.これをラップするには、完全な機能があります(実装がなければすべての型があります).
function getGroupedValues<Obj extends Record<`${BaseKey}${Indices}`, any>, BaseKey extends GroupedKeys<keyof Obj>>(
object: Obj,
baseKey: BaseKey,
): Array<Obj[`${BaseKey}${Indices}`]> {
return null as any; // we're not implementing the function, just focusing on its definition
}
試してみると以下のようになります.// some mock for an order
const order: Order = {
id: 'order-1',
name: 'Order 1',
price1: 10,
price2: 20,
price3: 30,
quantity1: 100,
quantity2: 200,
quantity3: 300,
shippingDate1: '10 Oct',
shippingDate2: '11 Oct',
shippingDate3: '12 Oct',
}
const orderRemap: OrderRemap = {
id: order.id,
name: order.name,
prices: getGroupedValues(order, 'price') // inferred return type: `number[]`
quantities: getGroupedValues(order, 'quantity') // inferred return type: `number[]`
shippingDates: getGroupedValues(order, 'shippingDate') // inferred return type: `string[]`
};
私は、ちょうどエラーでしばらく立ち往生した後、多くの望みなしで金曜日の午後にこのトリックをためしましたType '
${BaseKey}1
|${BaseKey}2
|${BaseKey}3
' cannot be used to index type 'Obj'
私は、どうにか、タイプスクリプトが同じレベルで定義されて、互いを利用しているそれらのジェネリックでOKであるのを見て驚きました
Obj extends Record<`${BaseKey}${Indices}`, any>, BaseKey extends GroupedKeys<keyof Obj>
私は唯一のものですか?ご存知ですか?誰でも、タイプスクリプトがこれでOKでありえる方法を説明することができますか?いずれにせよ、コメントを残して、あなたがそれについてどう思うか、そして、これが役に立つならば、私に話してください😄!ここではTypescript Playground link 上記からのすべてのコードで.
第2部
パート1でAPIを作成している間、我々は
getGroupedValues
関数を複数回、キーを渡し、残りのプロパティなどのオブジェクト全体を再作成します.簡単に!そこで、どのようにしてこれらをすべての関数として作成し、そのインデックスに基づいて異なるプロパティをグループ化するかを見ることができます.
const orderRemapGrouped: OrderRemapGrouped = groupProperties(order, 'orderDetails');
だからここで.orderDetails
型のオブジェクトを含む配列です{price: number, quantity: number; shippingDate: string}
値が同じインデックスから来る場合.例orderDetails[0]
, それにはprice
, quantity
and shippingDate
of price1
, quantity1
and shippingDate1
. などfunction groupProperties<Obj extends Record<`${Keys}${Indices}`, any>, Keys extends GroupedKeys<keyof Obj>, NewKey extends string>(
object: Obj,
newKey: NewKey,
): Omit<Obj, `${Keys}${Indices}`> & Record<NewKey, Array<{[key in Keys]: Obj[`${key}${Indices}`]}>> {
return null as any; // we're not implementing the function, just focusing on its definition
}
ここでいくつかの類似点を参照してください?正確に!最大の違いは、戻り型です.それで、それを壊しましょう
Omit<Obj, `${Keys}${Indices}`>
まず最初に、返される新しいオブジェクトがインデックスを持つプロパティのいずれかを持っていないことを知っています.price1
, price2
, price3
). それで、我々はビルトインOmit
インデックスに連結された共通キーから除外する型.Then:
Record<NewKey, Array<{[key in Keys]: Obj[`${key}${Indices}`]}>>
戻り値の1つのプロパティに追加されます.この関数は、NewKey
). そのキーには、オブジェクトの配列になる値があります.これらのオブジェクトはすべて共通のキーになります
price
, quantity
and shippingDate
), すべてのこれらのプロパティのunion型に関連付けられます.例えば、price
我々は、ユニオンのタイプを取得しますprice1
, price2
, price3
はnumber
彼らはすべての数字として.と他の同じ.ここではTypescript Playground link 上記からのすべてのコードで.
結論
私は、オブジェクトとそれのキー(パート1)の間で2つのジェネリックを参照することが働くと思いませんでした.しかし、それは最高です.いくつかのプロパティの一般的なビットを抽出するために、そのテンプレートとそのトリックの組み合わせは、非常に強力であり、私たちの関数には、かなり異なる別のデータ構造を変更するための堅牢なタイピングを与えます.
私は、ちょうどタイプスクリプトが好きです❤️✨. 同様にしてください、そして、あなたが役に立つこの記事を見つけたならば、コメントで知らせてください.また、私は本当にそれを試みた人々から若干の試み/解決を見ることに興味があります!
タイポを発見?
あなたがtypo、改善されることができた文またはこのブログ柱で更新されるべき何かを見つけたならば、あなたはそれをGITリポジトリでそれにアクセスして、プル要求をすることができます.コメントを投稿するのではなく、直接あなたの変更で新しいプル要求を行ってください.
Reference
この問題について(タイプスクリプトで日常的に働いている5年と私は、ジェネリックがそれをすることができたという考えが全くありませんでした🤯!), 我々は、より多くの情報をここで見つけました https://dev.to/maxime1992/5-years-working-on-a-daily-basis-with-typescript-and-i-had-no-idea-typescript-generics-were-capable-of-doing-that-21p6テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol