typescript: Bir arayüzden [anahtar,değer türü] demeti oluştur
Bir TypeScript tuple tipi (bir çift) oluşturmaya çalışıyorum; bu tip yalnızca ilk değeri, geçerli işlemleri anahtar olarak tutan bir arayüzün anahtarı olan ve beklenen argümanın türünü değer olarak tutan değerleri içerebilsin.
Sanırım aşağıdaki kod, ne yapmaya çalıştığımı daha iyi açıklayacaktır:
interface IEventsAndArguments {
'+': number,
'-': number,
'change_name': string,
}
// Now I want to produce tuples,
// where the first value is in keyof IEventsAndArguments
// and the second value is of the corresponding type
// (but a type with 2 properties name and value would do as well)
// it works for function arguments
function myFunction<K extends keyof IEventsAndArguments>(x:K, y:IEventsAndArguments[K]) {}
myFunction('+', 'a string'); // invalid as expected, '+' needs a number
myFunction('+', 4);
myFunction('change_name', 'John');
function myFunction2<K extends keyof IEventsAndArguments>({x, y}:{x:K, y:IEventsAndArguments[K]}) {}
myFunction2({x: '+', y: 'another string'}); // invalid as expected
myFunction2({x: '-', y: 3});
// it doesn't work when I try to define a tuple type
type TTuple<K extends keyof IEventsAndArguments> = [K, IEventsAndArguments[K]];
// I would like the next tuple to be INVALID
// but it is valid since the second element is of type number | string
const tuple1:TTuple<keyof IEventsAndArguments> = [ '+', 'hello' ]
// What I ultimately would want is something similar to this
type TTuple2<K extends keyof IEventsAndArguments> = [
K,
(v:IEventsAndArguments[K]) => void
];
// I would like the next tuple to be VALID but it is invalid
// since the function argument must be of type number | string
const tuple2:TTuple2<keyof IEventsAndArguments> = [
'+',
(t:number) => {},
];
İşte aynı kodla bir oyun alanı bağlantısı.
Bunu çalıştırmanın bir yolu var mı? Biri, neden fonksiyonlarda istediğim gibi davrandığını ama yeni bir tip tanımladığında böyle olmadığını açıklayabilir mi?
Teşekkürler.
DÜZENLEME: 'Yeterince iyi' bir mekanizma bulduğumu düşünüyorum: işlev için çalıştığına göre geçerli bir demeti üretmek için bir işlev kullanabiliriz!
// if we use a function to produce the tuple, we can produce 'valid'
// tuples, even if the tuple type itself is not as strict as we'd like...
function tuple<K extends keyof IEventsAndArguments>(
a:K, f:(v:IEventsAndArguments[K]) => void
):TTuple2<K>
{
return [a, f];
}
const tuple3 = tuple('+', (v:number) => v + 1); // valid, as expected
const tuple4 = tuple('+', (v:string) => v + 1); // invalid, also as expected
Ama hayal kırıklığı devam ediyor: neden bu, fonksiyonlar için işe yarıyor (her iki parametre de arayüzün aynı girdisine 'bağlı'), ama yeni bir tür oluştururken işe yaramıyor? Şu anda, fonksiyonu kullanmayarak hâlâ 'geçersiz' tuple'lar üretebileceğiniz için, türü istediğimiz kadar katı hâle getirebilmemiz güzel olurdu.
Ah: ayrıca önemli: Bunu genel tutmak istiyorum, bu nedenle insanların daha sonra arabirime eklediği girdiler ne olursa olsun çalışmaya devam etmelidir, bu yüzden IEventsAndArguments tanımının bilinmediği ve başka yerlerde genişletilebileceği varsayılmalıdır.
Cevap #1
TTuple türünüzün ya ["+", (v: number) => void] şeklinde bir tuple, ya da ["-", (v: number) => void] şeklinde bir tuple, ya da ["change_name", (v: string) => void] şeklinde bir tuple olmasını istiyorsunuz. Bunu generics kullanmadan, TTuple türünü bu türlerin bir birleşimi yaparak elde edebilirsiniz:
type TTuple =
["+", (v: number) => void] |
["-", (v: number) => void] |
["change_name", (v: string) => void];
Bunun işe yaradığını doğrulayabilirsiniz:
let tuple: TTuple;
tuple = ['+', (t: number) => console.log(t.toFixed())]; // okay
tuple = ['change_name', (t: number) => console.log(t.toFixed())]; // error
tuple = ['change_name', (t: string) => console.log(t.toUpperCase())]; // okay
Elbette TTuple'u manuel olarak tanımlamak istemezsiniz; TTuple'u IEventsAndArguments'ın bir fonksiyonu olarak derleyicinin hesaplaması tercih edilir.
Bunu yapmanın bir yolu, sözde bir "dağıtıcı nesne tipi" (terminoloji microsoft/TypeScript#47109'dan gelmektedir) oluşturmaktır; burada önce IEventsAndArguments türü üzerinde gezinerek aynı anahtarlara sahip yeni bir nesne tipi oluşturursunuz, ancak özellik değer türleri ilgili demet türleri olur ve ardından hemen bu eşlenen türe IEventsAndArguments anahtarlarının birleşimi ile indeksleme yaparsınız; bu da demetlerin birleşimini üretir. İşte nasıl göründüğü:
type TTuple = { [K in keyof IEventsAndArguments]: [
K,
(v: IEventsAndArguments[K]) => void
] }[keyof IEventsAndArguments];
Bunun üzerinden geçerken, eşlenmiş tip {[K in keyof]... }, "+", "-" ve "change_name" anahtarları üzerinde yineleme yaparak şuna benzer bir şey üretir
{
"+": ["+", (v: IEventsAndArguments["+"] )=> void],
"-": ["-", (v: IEventsAndArguments["-"] )=> void],
"change_name": ["change_name", (v: IEventsAndArguments["change_name"] )=> void]
}
ki bu şu hale indirgenir
{
"+": ["+", (v: number) => void],
"-": ["-", (v: number) => void],
"change_name": ["change_name", (v: string) => void]
}
Sonra buna "+" | "-" | "change_name" ile indeksleme yapıyoruz, bu da istenildiği gibi ["+", (v: number) => void] | ["-", (v: number) => void] | ["change_name", (v: string) => void] birlikteliğini üretir.
Artık IEventsAndArguments türüne özellik ekler veya değiştirirseniz, TTuple türü bunu karşılayacak şekilde otomatik olarak değişecektir.
Koda yönelik oyun alanı bağlantısı
