TypeScript: Hvordan forbedre en type der en av dens egenskaper sannsynligvis er null


Jeg prøver å forstå hvordan man skriver en funksjon som vil forbedre typen min og deretter returnere den.

Det fungerer for det sendte objektet selv og innenfor funksjonskroppen, men ikke utenfor. Videre synes jeg det er merkelig at selv om det fungerer innenfor funksjonskroppen, slutter det å fungere på selve return-setningen.

Min forventning ville være at TypeScript korrekt returnerer en raffinert/innsnevret type, med sjekkede egenskaper inkludert, ikke bare hovedobjektet.

Hva gjør jeg galt? Krever TypeScript mer hjelp, kanskje en generisk returtype?

Eksempel:

 
type Referrer = { id: string }

type User = {
    id: string;
    referrer: Referrer | null;
} | null;


function checkUserHasReferrer(user: User) {
  if (!user) {
      throw Error('No User');
  }

  if (!user.referrer) {
    console.log(user.referrer) // referrer correctly narrowed to `referrer: null`
    throw Error('No Referrer');
  }
  const referrer = user.referrer; // referrer correctly narrowed to `referrer: Referrer`
  console.log(user.referrer).     // same here, `referrer: Referrer`
  

  return user; // <- Here however it's back to `referrer: Referrer | null`
}


let initialUser: User = null;
const user = checkUserHasReferrer(initialUser);
console.log(user.id);           // <-- knows User is not `null`
console.log(user.referrer.id);  // However it doesn't know referrer is not `null`

TypeScript-lekeplass

Svar #1

Du må bruke assertionsfunksjoner

type Referrer = { id: string }

type User = {
  id: string;
  referrer: Referrer | null;
}



function notNull(user: unknown): asserts  user {
  if (!user) {
    throw Error('No User');
  }
}

declare const user: User | null


notNull(user)
notNull(user.referrer)

console.log(user.id);
console.log(user.referrer.id);  // ok

Lekeplass

Når du trenger å kaste en feil i TypeScript, er vanligvis en slik funksjon en god kandidat til å være assertion function


Eller, du kan bruke egendefinerte typeguard-funksjoner

type Referrer = { id: string }

type User = {
  id: string;
  referrer: Referrer | null;
}

const isValidUser = (user: User | null): user is User & { referrer: Referrer } =>
  Boolean(user && user.referrer)

declare const user: User

if (isValidUser(user)) {
  console.log(user.id);
  console.log(user.referrer.id);  // ok
}

Lekeplass

Svar #2

Dette er for øyeblikket en kjent begrensning i TypeScript. Se her eller her. TypeScript innskrenker korrekt typen til egenskapen, men typen til forelderen blir ikke endret, så den forblir bare User.

En mulig løsning uten typepåstander ville være å spre objektet når det returneres.

return {
    ...user,
    referrer
};

Dette endrer returtypen til funksjonen til

{
    referrer: Referrer;
    id: string;
}

Lekeplass

Relaterte artikler