Gjør en enkelt egenskap valgfri i TypeScript
I TypeScript, 2.2...
La oss si at jeg har en Person-type:
interface Person {
name: string;
hometown: string;
nickname: string;
}
Og jeg vil gjerne lage en funksjon som returnerer en Person, men som ikke krever kallenavn:
function makePerson(input: ???): Person {
return {...input, nickname: input.nickname || input.name};
}
Hva bør være typen til input? Jeg ser etter en dynamisk måte å spesifisere en type som er identisk med Person bortsett fra at nickname er valgfri (nickname?: string | undefined). Det nærmeste jeg har funnet ut så langt er dette:
type MakePersonInput = Partial<Person> & {
name: string;
hometown: string;
}
men det er ikke helt det jeg ser etter, siden jeg må spesifisere alle typene som er påkrevd i stedet for de som er valgfrie.
Svar #1
Du kan også gjøre noe slik, delvis bare noen av nøklene.
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
interface Person {
name: string;
hometown: string;
nickname: string;
}
type MakePersonInput = PartialBy<Person, 'nickname'>
Svar #2
Her er min Typescript 3.5+ valgfrie hjelpetype
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
// and your use case
type MakePersonInput = Optional<Person, 'nickname'>
// and if you wanted to make the hometown optional as well
type MakePersonInput = Optional<Person, 'hometown' | 'nickname'>
Svar #3
For en plug-and-play-løsning, vurder å bruke den briljante utility-types-pakken:
npm i utility-types --save
Deretter kan du enkelt bruke Optional<T, K>:
import { Optional } from 'utility-types';
type Person = {
name: string;
hometown: string;
nickname: string;
}
type PersonWithOptionalNickname = Optional<Person, 'nickname'>;
// Expect:
//
// type PersonWithOptionalNickname {
// name: string;
// hometown: string;
// nickname?: string;
// }
Svar #4
Oppdatering:
Fra og med TypeScript 2.8 støttes dette mye mer konsist av Conditional Types! Så langt ser dette også ut til å være mer pålitelig enn tidligere implementeringer.
type Overwrite<T1, T2> = {
[P in Exclude<keyof T1, keyof T2>]: T1[P]
} & T2;
interface Person {
name: string;
hometown: string;
nickname: string;
}
type MakePersonInput = Overwrite<Person, {
nickname?: string;
}>
function makePerson(input: MakePersonInput): Person {
return {...input, nickname: input.nickname || input.name};
}
Som før, er MakePersonInput tilsvarende:
type MakePersonInput = {
name: string;
hometown: string;
} & {
nickname?: string;
}
Utdatert:
Fra og med TypeScript 2.4.1 ser det ut til at det finnes et annet alternativ tilgjengelig, som foreslått av GitHub-brukeren ahejlsberg i en tråd om typetrekkning: https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U;
interface Person {
name: string;
hometown: string;
nickname: string;
}
type MakePersonInput = Overwrite<Person, {
nickname?: string
}>
function makePerson(input: MakePersonInput): Person {
return {...input, nickname: input.nickname || input.name};
}
Ifølge Intellisense er MakePersonInput ekvivalent med:
type MakePersonInput = {
name: string;
hometown: string;
} & {
nickname?: string;
}
som ser litt morsom ut, men som absolutt gjør jobben.
På baksiden kommer jeg til å måtte stirre på den Diff-typen en stund før jeg begynner å forstå hvordan den fungerer.
Svar #5
Type-fest-pakken har et verktøy SetOptional - https://github.com/sindresorhus/type-fest/blob/main/source/set-optional.d.ts
import { SetOptional } from 'type-fest';
type PersonWithNicknameOptional = SetOptional<Person, 'nickname'>
Jeg synes biblioteket er godt vedlikeholdt, og støtter de nyeste versjonene av TypeScript. Det er verdt å legge til i et TypeScript-prosjekt etter min mening.
Svar #6
Hvis du bruker en nyere versjon av TypeScript, er en enkel løsning å gjøre
function makePerson(input: Omit<Person, 'nickname'> & { nickname?: string }): Person {
return {...input, nickname: input.nickname || input.name};
}
I utgangspunktet fjerner du 'nickname'-egenskapen fra grensesnittet og legger den til igjen som valgfri
Hvis du vil sørge for å holde det synkronisert med det originale grensesnittet, kan du gjøre
Omit<Person, 'nickname'> & Partial<Pick<Person, 'nickname'>>
som vil advare deg hvis du noen gang endrer "kallenavn"-egenskapen i det originale grensesnittet
