Urobiť jednu vlastnosť voliteľnou v TypeScripte


V TypeScripte 2.2...

Povedzme, že mám typ Osoba:

interface Person {
  name: string;
  hometown: string;
  nickname: string;
}

A rád by som vytvoril funkciu, ktorá vráti osobu, ale nevyžaduje prezývku:

function makePerson(input: ???): Person {
  return {...input, nickname: input.nickname || input.name};
}

Aký by mal byť typ input? Hľadám dynamický spôsob, ako určiť typ, ktorý je identický s Person, okrem toho, že nickname je voliteľný (nickname?: string | undefined). Najbližšie, čo som doteraz zistil, je toto:

type MakePersonInput = Partial<Person> & {
  name: string;
  hometown: string;
}

ale to nie je celkom to, čo hľadám, pretože musím špecifikovať všetky typy, ktoré sú povinné, namiesto tých, ktoré sú voliteľné.

Odpoveď č. 1

Môžete urobiť aj niečo takéto, čiastočne len niektoré kľúče.

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'>

Odpoveď č. 2

Tu je môj voliteľný typ utility pre Typescript 3.5+

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'>

Odpoveď č. 3

Pre riešenie typu plug and play zvážte použitie vynikajúceho balíka utility-types:

npm i utility-types --save

Potom jednoducho použite 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;
// }

Odpoveď č. 4

Aktualizácia:

Od verzie TypeScript 2.8 je to podporované oveľa stručnejšie pomocou podmienených typov! Doteraz sa zdá, že to je tiež spoľahlivejšie ako predchádzajúce implementácie.

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};
}

Ako predtým, MakePersonInput je ekvivalentné s:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

Zastaralé:

Od verzie TypeScript 2.4.1 to vyzerá, že je k dispozícii ďalšia možnosť, ako navrhol používateľ GitHub ahejlsberg v vlákne o odčítaní typov: 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};
}

Podľa Intellisense je MakePersonInput ekvivalentné:

type MakePersonInput = {
    name: string;
    hometown: string;
} & {
    nickname?: string;
}

ktorý vyzerá trochu smiešne, ale úplne splní svoju úlohu.

Na druhej strane, budem musieť chvíľu pozerať na ten typ Diff, kým začnem chápať, ako funguje.

Odpoveď č. 5

Balík type-fest má utilitu SetOptional - https://github.com/sindresorhus/type-fest/blob/main/source/set-optional.d.ts

import { SetOptional } from 'type-fest';

type PersonWithNicknameOptional = SetOptional<Person, 'nickname'>

Myslím si, že knižnica je dobre udržiavaná a podporuje najnovšie verzie TypeScriptu. Podľa môjho názoru stojí za to ju pridať do projektu v TypeScripte.

Odpoveď č. 6

Ak používate nedávnu verziu TypeScriptu, jednoduché riešenie je urobiť

function makePerson(input: Omit<Person, 'nickname'> & { nickname?: string }): Person {
  return {...input, nickname: input.nickname || input.name};
}

V podstate odstránite vlastnosť „nickname“ z rozhrania a znova ju pridáte ako voliteľnú

Ak chcete mať istotu, že zostane synchronizované s pôvodným rozhraním, môžete urobiť

Omit<Person, 'nickname'> & Partial<Pick<Person, 'nickname'>>

ktorý vás upozorní, ak niekedy zmeníte vlastnosť „nickname“ v pôvodnom rozhraní

Súvisiace články