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

Relaterte artikler