Problem med att definiera lambda-funktion i TypeScript
Jag fastnade just med följande lambda-funktionsdefinition i TypeScript. Det kan vara en nybörjarfråga.
type P = { id: string }
type F = <T>(x: T) => string
const f: F = (x: P) => x.id
f({ id: 'abc' }
Men det fortsatte att klaga över följande:
Typen '(x: P) => string' kan inte tilldelas typen 'F'.
Typerna av parametrarna 'x' och 'x' är inkompatibla.
Typ 'T' kan inte tilldelas typen 'P'.
Uppdatering #1
Jag bara kommenterar sammanhanget varför jag behöver en generell funktion. Jag gör en funktion för att verifiera ett inmatningsobjekt, och varje väg i objektet är konfigurerbar för verifiering som nedan:
type Option = { path: string; isVerified: <T>(t: T) => boolean }
const verify = (obj: any, options: Option[]): boolean => {
const results = options.map(option => {
const { path, isVerified } = option
return isVerified(obj[path])
})
return results.every(x => x)
}
type A = number
type B = string
const obj = { a: 1 as A, b: 'abc' as B }
verify(obj, [
{ path: 'a', isVerified: (x: A): boolean => x > 0 },
{ path: 'b', isVerified: (x: B): boolean => x.startsWith('a') }
])
Uppdatering #2
Tack för det första svaret, det löste problemet som anges i Update #1. Dock är Update #1 egentligen ett förenklat problem. I det verkliga fallet kan path under Option-typen vara antingen string eller RegExg, vilket gör koden i svaret med Paths[K] ogiltig, som nedan:
type Option = { path: string | RegExp; isVerified: <T>(t: T) => boolean }
const verify = (obj: any, options: Option[]): boolean => {
const results = Object.keys(obj).map(k => {
const verifiers = options
.filter(opt =>
typeof opt.path === 'string' ? opt.path === k : opt.path.test(k)
)
.map(x => x.isVerified)
return verifiers.every(verify => verify(obj[k]))
})
return results.every(x => x)
}
type A = number
type B = string
const obj = { a: 1 as A, b: 'abc' as B }
verify(obj, [
{ path: 'a', isVerified: (x: A): boolean => x > 0 },
{ path: /b/, isVerified: (x: B): boolean => x.startsWith('a') }
])
Se Playground-länken för mer information.
Och det fortsatte att klaga nedan:
Typen '(x: number) => boolean' kan inte tilldelas typen '(t: T) => boolean'.
Typerna av parametrarna 'x' och 't' är inkompatibla.
Typ 'T' kan inte tilldelas typen 'number'.
Svar #1
Angående exempel koden:
type P = { id: string }
type F = <T>(x: T) => string
const f: F = (x: P) => x.id
Typen F är en generisk funktion från vilken typ T som helst till string; så i huvudsak, för att en funktion ska vara av typen F måste den acceptera alla typer av argument. Din funktion f accepterar endast argument av typen P, så funktionen är inte av typen F.
När det gäller det faktiska problemet du försöker lösa, är det verkliga problemet här att Option borde vara en unionstyp, eftersom parametertypen för isVerified beror på värdet av path:
type Option = {
path: 'a',
isVerified: (t: number) => boolean
} | {
path: 'b',
isVerified: (t: string) => boolean
}
För att göra denna unionstyp enklare att deklarera för ett verkligt användningsfall (där det kan finnas många fler fält), definiera en typ Paths för de objekt du verifierar, och konstruera sedan Option som en mappad typ:
type Paths = { a: number, b: string }
type Option = {
[K in keyof Paths]: {
path: K,
isVerified: (t: Paths[K]) => boolean
}
}[keyof Paths]
Nu kommer ditt funktionsanrop till verify att typkontrollera, och kommer till och med att härleda parametertyperna åt dig utan att du behöver skriva uttryckliga anteckningar:
const obj = { a: 1, b: 'abc' }
// OK
verify(obj, [
{ path: 'a', isVerified: x => x > 0 },
{ path: 'b', isVerified: x => x.startsWith('a') }
])
Dessutom kommer du att få ett typfel (som önskat) om isVerified har fel parameter typ, eftersom path-egenskapen specificerar vilken typ isVerified ska ha:
// type error
verify(obj, [
{ path: 'a', isVerified: x => x.startsWith('a') },
{ path: 'b', isVerified: x => x > 0 }
])
Det finns en ändring till som du behöver göra - en typangivelse i verify-funktionen, eftersom annars isVerified antas ta en parameter av typen never (dvs. snittet number & string):
const verify = (obj: any, options: Option[]): boolean => {
const results = options.map(option => {
const { path, isVerified } = option
// this line needs a type assertion
return (isVerified as (t: Paths[typeof path]) => boolean)(obj[path])
})
return results.every(x => x)
}
Lekplatslänk
