javascript - Typescript oppslagstyper - korrekt begrense mengden egenskaper når man slår sammen med Partial
Jeg eksperimenterer med oppslagstyper og ønsker å lage en slags sikker-sammenføyningsverktøyfunksjon (en som tar en enhet av typen T og et objekt som inneholder en delmengde av nøklene til T for å oppdatere). Målet mitt er å la kompilatoren fortelle meg når jeg stavefeiler en egenskap eller prøver å legge til en ikke-eksisterende for T.
Så jeg har Person og bruker innebygd Partial i (v2.1) slik:
interface Person {
name: string
age: number
active: boolean
}
function mergeAsNew<T>(a: T, b: Partial<T>): T {
return Object.assign({}, a, b);
}
Nå anvender jeg dette på følgende data:
let p: Person = {
name: 'john',
age: 33,
active: false
};
let newPropsOk = {
name: 'john doe',
active: true
};
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
mergeAsNew(p, newPropsOk);
mergeAsNew(p, newPropsErr); // <----- I want tsc to yell at me here because of trying to assign non-existing props
Jeg ønsker at TS-kompilatoren skal rope til meg ved andre kjøring ettersom fullname og enabled ikke er props til Person. Dessverre kompilerer dette fint lokalt, men... når jeg gjør det samme i online TS Playground får jeg mer eller mindre det jeg forventer:
The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
Type argument candidate 'Person' is not a valid type argument because it is not a supertype of candidate '{ fullname: string; enabled: boolean; }'.
Property 'name' is missing in type '{ fullname: string; enabled: boolean; }'.
Ser ut som lekeplassen bruker samme versjon som jeg har lokalt (2.1.4). Har noen en idé om hvorfor disse to kan være forskjellige?
Bonusspørsmål:
når jeg prøver følgende oppgave:
let x: Person = mergeAsNew(p, newPropsOk);
Jeg får følgende feil på x, men bare på lekeplassen (det fungerer helt fint lokalt):
Type '{ name: string; active: boolean; }' is not assignable to type 'Person'.
Property 'age' is missing in type '{ name: string; active: boolean; }'.
Hvorfor er det slik? Burde det ikke være av Person-type, siden første mergeAsNew-argument er Person og alt annet er et delsett av Person-egenskaper (så det er maksimalt Person)?
REDIGER Her er min tsconfig.json:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"noEmitOnError": true,
"allowJs": false,
"sourceMap": true,
"strictNullChecks": true
},
"exclude": ["dist", "scripts"]
}
Svar #1
Når du vil uttrykke at én type bare har et delsett av egenskapene til en annen type, kan vanlig gammel extends også være til hjelp
interface Person {
name: string
age: number
active: boolean
}
let p: Person = {
name: 'john',
age: 33,
active: false
};
let newPropsOk = {
name: 'john doe',
active: true
};
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
// a extends b and not the other way round
//because we don't want to allow b to have properties not in a
function mergeAsNew<T2, T1 extends T2>(a: T1, b: T2): T1 {
return Object.assign({}, a, b);
}
mergeAsNew(p, newPropsOk); // ok
mergeAsNew(p, newPropsErr);
// Argument of type 'Person' is not assignable
// to parameter of type '{ fullname: string; enabled: boolean; }'.
// Property 'fullname' is missing in type 'Person'.
PS har ingen anelse om hva som skjer med playground og kartlagte typer
Svar #2
Ok, det ser ut til at jeg var for fiksert på det i går. Jeg fikk litt søvn, og det ser ut til at det (sannsynligvis) er umulig å gjøre det jeg vil på den måten (ved å bruke Partial). Hvis vi legger denne merkelige playground-adferden til side en stund, her er hvorfor (i det minste tror jeg det):
For å oppsummere:
interface Person {
name: string
age: number
active: boolean
}
function mergeAsNew<T>(a: T, b: Partial<T>): T {
return Object.assign({}, a, b);
}
I denne koden kan Partial<Person> (men må ikke) inneholde noen av Person-egenskapene, så følgende er en helt gyldig Partial<Person>-type:
let newPropsErr = {
fullname: 'john doe',
enabled: true
};
Siden Typescript har strukturell typing bryr det seg ikke om ekstra egenskaper, det sjekker bare objektets form, og her passer formen til Partial<Person> helt fint.
Den eneste måten å på en eller annen måte sjekke argumentets form mot Partial<Person> for ekstra egenskaper er å sende det som en objektliteral (literalene sjekkes strengt for å samsvare med typen)
Nå til selve mergeAsNew-funksjonen. Jeg gjør Object.assign her som gjør akkurat det det er laget for - slår sammen props fra begge argumentene til et nytt objekt (alle sammen). Fordi typer kun er for TS-kompilering, finnes det ingen måte under kjøring å begrense og selektivt velge props fra b og bruke dem til å overstyre a-props.
Ser ut som @Artems svar som gjør det jeg trenger med extend og uten map-/oppslagstyper.
Uansett, jeg forstår fortsatt ikke hvorfor TS Playground fungerer som det gjør (det som først ga meg inntrykk av at alt det der var helt gjennomførbart).
