Typescript: Elimină un atribut dintr-un obiect în siguranță


Aș dori să scriu o funcție TypeScript care ia un obiect care include parametrul foo și returnează același obiect fără parametrul foo. Vreau să fac acest lucru în mod complet sigur din punct de vedere al tipurilor. Pare destul de simplu, dar mă împotmolesc. Iată codul pe care îl am până acum:

interface FooProp {
    foo: string;
}
type WithFooProp<P extends object> = P extends FooProp ? P : never;
type WithoutFooProp<P extends object> = P extends FooProp ? never: P;

function withoutFooProp<P extends object>(input: WithFooProp<P>): WithoutFooProp<P> {
    // How do I get rid of these any's
    const {foo, ...withoutFoo} = input as any;
    return withoutFoo as any;
}

Acest lucru nu este grozav pentru că folosesc any destul de des. M-aș aștepta oarecum ca codul să funcționeze așa cum este fără any, dar TS se plânge că input nu este un obiect și nu poate fi destructurat. Cum aș putea îmbunătăți această metodă?

În plus, când folosesc această funcție, TS mă obligă să furnizez explicit tipurile generice. Mi-aș dori să le deducă implicit. Există vreo modalitate prin care aș putea scrie funcția pentru a prelua parametrii implicit.

// Compiles - but I have to specify the props explicitly
withoutFooProp<{foo: string, bar: string}>({
    foo: 'hi',
    bar: 'hi',
});

// This doesn't compile - I wish it did!  How can I make it?
withoutFooProp({
    foo: 'hi',
    bar: 'hi',
});

Mulțumesc anticipat!

Răspuns #1

Poți lăsa tipul parametrului să fie pur și simplu P, unde P este constrâns să conțină proprietatea foo, astfel încât inferența tipului să funcționeze: va seta pur și simplu P la tipul argumentului. Apoi, folosește Pick și Exclude pentru a genera tipul obiectului de ieșire care conține toate proprietățile lui P cu excepția foo.

interface FooProp {
    foo: string;
}
type WithoutFooProp<P> = Pick<P, Exclude<keyof P, "foo">>;

function withoutFooProp<P extends FooProp>(input: P): WithoutFooProp<P> {
    const {foo, ...withoutFoo} = input as any;
    return withoutFoo;
}

Nu vei putea scăpa de any decât dacă/până când această sugestie va fi implementată. Nu m-aș îngrijora în legătură cu asta.

Această funcție withoutFooProp ar trebui să funcționeze pe orice tip concret de intrare și să ofere un tip concret de ieșire. Dacă folosești withoutFooProp pe obiecte de tipuri generice, atunci s-ar putea să întâmpini probleme din cauza capacității limitate a TypeScript de a raționa despre Pick și Exclude, și s-ar putea să vrei să folosești în schimb intersecțiile (chiar dacă acest lucru este tehnic nesigur), așa cum este demonstrat în acest răspuns în contextul componentelor de ordin superior din React.

Răspunsul #2

De asemenea, puteți folosi această funcție tip-sigură care funcționează ca generic și poate fi folosită pentru orice obiect:

export function deleteProperty<TObject, TProp extends keyof TObject>(object: TObject, prop: TProp): Omit<TObject, TProp> {
 const {
    [prop]: toDelete,
    ...newObject
 } = object

 return newObject;
}