Generisk identitetsfunktion i Typescript
Jag (eller rättare sagt, ett bibliotek jag använder) har många gränssnitt för olika alternativ där mycket är valfritt.
interface Options {
a?: number;
b?: number;
c?: number;
}
Jag skapar objekt av de alternativ jag tänker använda, men behöver också referera till dessa själv. Jag vill ha typkontroll när jag skapar objektet, men efter det vill jag inte att saker jag inte har specificerat ska vara tillgängliga, eller att saker jag har specificerat möjligen ska vara odefinierade, som här:
const o: Options = { a: 1 } // Good: Object is type checked
o.a.toFixed(0) // Bad: `a` is possibly undefined
Man skulle kunna hoppa över typen, men då finns det ingen typkontroll av alternativen:
const o = { a: 1, d: 2 } // Bad: `d` isn't an option
o.a.toFixed(0) // Good: `a` is defined
Så, jag skapade en hjälpfunktion:
const createOptions = <O extends Options>(options: O): O => options
const o = createOptions({ a: 1 }) // Good: Object is type checked
o.a.toFixed(0) // Good: `a` is defined
Detta fungerar, men att behöva skapa en funktion som denna för varje alternativtyp börjar bli irriterande och rörigt. Är det möjligt att skapa en enda generisk hjälpfunktion för detta?
Mitt första nybörjarförsök var följande, men här kräver Typescript att jag anger 2 typer, istället för bara 1 (Options) som borde vara nödvändigt.
const create = <T, U extends T>(obj: U): U => obj
const o = create<Options>({ a: 1 }) // Bad: Typescript wants me to specify U
Hur kan jag skriva denna identitetsfunktion så att jag bara behöver ange T, och låta Typescript själv härleda U från objektet jag skickar in?
Lekplatslänk
Svar #1
På grund av bristen på partiell typargumentinferenz är det för närvarande allt eller inget: du måste antingen tillhandahålla alla typargument uttryckligen eller lämna uppgiften helt till TS.
Hur som helst finns det en lösning på det:
function create<T extends object>() {
return <U extends T>(obj:U) => obj
}
const o3 = create<Options>()({ a: 4 })
Det är inte särskilt vackert, men det gör sitt jobb.
Vad sägs om delen "Jag vill inte att saker jag inte specificerat ska vara tillgängliga"? Nåväl, ur TS:s synvinkel, ett objekt som { a: 4, d: "hi" } utvidgar (kan tilldelas) Options.
Så du kan använda ett verktyg som följande:
type ExtractImplementedKeys<ST, T> = {
[K in Extract<keyof T, keyof ST>]: ST[K]
}
function create<T extends object>() {
return <U extends T>(obj:U): ExtractImplementedKeys<U, T> => obj
}
Du kommer fortfarande att kunna skapa ett objekt som:
const o3 = create<Options>()({ a: 4, d:"Hi" })
Men då kommer du inte kunna använda överskottsattribut:
o3.d; // <-- Error
En annan strategi är följande:
type StrictExtendCheck<ST, T> = keyof ST extends keyof T ? ST : never
function create<T extends object>() {
return <U extends T>(obj:StrictExtendCheck<U, T>) => obj
}
Sedan, något som:
const o3 = create<Options>()({ a: 4, d:"Hi" })
Kommer att ge ett fel, tyvärr inte ett särskilt beskrivande sådant.
