typescript - Hvorfor mister grensesnittet sine egenskaper når en egenskap er definert med indeks-signaturer?


Nedenfor er eksempel på koden for problemet mitt. Jeg bruker tRPC og Zod i eksemplet.

import { initTRPC, inferRouterOutputs } from '@trpc/server';
import { z } from "zod";
const t = initTRPC.create();
const { router, procedure } = t;

interface ITest {
  [key: string]: object | unknown;
  prop: string;
}

export const appRouter = router({
  foo: procedure.input(z.undefined()).query(() => {
    let test: ITest = { prop: "test" };

    return test;
  }),
});

export type AppRouter = typeof appRouter;

type bar = inferRouterOutputs<AppRouter>["foo"];
//   ^?
// type bar = { [x: string]: never; [x: number]: never; }
// expected: ITest

Her er bar skrevet som

type bar = {
    [x: string]: never;
    [x: number]: never;
}

Dette resulterer i en typemismatch, ettersom spørringsdataene ikke lenger er typet som ITest og heller ikke har prop-egenskapen, i motsetning til de faktiske dataene. Er dette standard TypeScript-oppførsel eller tRPC? Hvordan kan jeg omgå dette problemet?

Svar #1

Malfunksjonen `` er i filen types.d.ts, og er i en seksjon merket som «utdatert, vil bli fjernet i neste store versjon siden det er v9-greier».

/**
 * @deprecated will be removed in next major as it's v9 stuff
 */
...
export type inferSubscriptionOutput<
  TRouter extends AnyRouter,
  TPath extends string & keyof TRouter["_def"]["subscriptions"]
> = inferObservableValue<
  inferProcedureOutput<TRouter["_def"]["subscriptions"][TPath]>
>;
export type inferProcedureClientError<TProcedure extends AnyProcedure> =
  inferProcedureParams<TProcedure>["_config"]["errorShape"];
type GetInferenceHelpers<
  TType extends "input" | "output",
  TRouter extends AnyRouter
> = {
  [TKey in keyof TRouter["_def"]["record"]]: TRouter["_def"]["record"][TKey] extends infer TRouterOrProcedure
    ? TRouterOrProcedure extends AnyRouter
      ? GetInferenceHelpers<TType, TRouterOrProcedure>
      : TRouterOrProcedure extends AnyProcedure
      ? TType extends "input"
        ? inferProcedureInput<TRouterOrProcedure>
        : inferTransformedProcedureOutput<TRouterOrProcedure>
      : never
    : never;
};
export type inferRouterInputs<TRouter extends AnyRouter> = GetInferenceHelpers<
  "input",
  TRouter
>;
export type inferRouterOutputs<TRouter extends AnyRouter> = GetInferenceHelpers<
  "output",
  TRouter
>;

Dette er ikke den fullstendige listen - definisjonen kaller videre andre betingede funksjoner for maler som ikke vises ovenfor.

Forfatterne testet ikke for alle tilfeller, inkludert tilfellet som bruker en generell indeksnøkkel. Det er svaret.

Som en separat kommentar, TypeScript-malfunksjoner er kvalitativt skjøre, og komplekse malfunksjoner er vanskelige å feilsøke. Faktisk vil malfunksjoner stille returnere et feil svar hvis beregningen blir for dyp - selv om det kanskje ikke har skjedd her. Det kan bare ha feilet noen av extends-betingelsene og truffet en never.

Det kan være derfor malfunksjonen du spør om -inferRouterOutputs - blir faset ut.


Forrige svar kan sees ved å se på redigeringshistorikken.

Svar #2

Noe tilleggsinformasjon som er svært relevant for det stilte spørsmålet, men som kanskje er eller ikke er relatert til den observerte oppførselen:

Merk1:

type X = object | unknown; // tooltip shows "type X = unknown"
type Y = object | any; // tooltip shows "type Y = any"

TypeScript-lekeplass

object|unknown er ekvivalent med unknown (se TypeScript-dokumentasjonen "New Unknown Top Type"). Det betyr at å skrive object|unknown bør ikke være annerledes enn å skrive unknown. Imidlertid kan det være bivirkninger i TypeScript-malefunksjoner hvor det faktisk gjør en forskjell.

På samme måte er object|any ekvivalent med any.

Merk2 Selv om det sannsynligvis ikke er relatert til problemet ditt - bruk av object i stedet for {[x:string]:unknown} eller {[x:string]:any} kan ha uventede bivirkninger, som vist i følgende kode:

function foo(obj: object) {
    for (const key in obj) {
        const val = obj[key]; // Compilation error: type 'string' can't be used to index type '{}'
    }
}

function bar(obj: { [key: string]: unknown }) {
    for (const key in obj) {
        const val = obj[key]; // works :)
    }
}

Se dette Stack Overflow-svaret.

Merk3

Jeg endret koden på dette fungerende nettbaserte trpc-stedet for å kopiere inn din i tillegg, og endret eventuelle variabelnavn som kolliderte.

Jeg fant at resultatet av å bruke

interface ITest {
  [key: string]: object | unknown;
  prop: string;

var

"bar = {}"

Typen {} i Typescript er faktisk ikke et objekt uten egenskaper - det er i stedet et spesialtilfelle som behandles som { [x:string]:any }.

Så i det minste i den testen i det trpc online-miljøet, ble prop:string-egenskapen effektivt konvertert til prop: any.

Jeg nevner dette fordi du sa i kommentaren din at

interface ITest {
  [key: string]: any;
  prop: string;
}

fungerte for deg.