Predicat multiple de tip în Typescript


Am un caz în care verific frecvent atât valoarea unui boolean, cât și un alt tip union împreună. Deși sunt conectate logic și ipotetic aș putea verifica doar unul, trebuie să verific ambele pentru ca compilatorul TS să cunoască ambele tipuri.

let multiple: boolean
let value: Array<string> | string 
...
if (multiple && isArray(value)) {
  // TS knows multiple === true and value is an Array
}

Există o modalitate de a scrie o funcție de verificare a tipului în care predicatul de tip afirmă mai multe valori?

Ceva de genul acesta:

// Not valid Typescript
function isMulti (
  multi: boolean, 
  value: Array<string> | string
): (multi is true) && (value is Array<string>) {
  return multi && isArray(value)
}

În majoritatea cazurilor mă descurc doar verificând isArray, dar există cazuri în care vreau să reiau o verificare precum cea de mai sus. Este posibil acest lucru?


Ca răspuns pentru @kaya3:

Pentru a oferi un caz de utilizare mai precis, am ceva similar cu următorul:

Aș putea să atribui multiple și value unui obiect cu un tip DiscriminatedUnion, dar asta adaugă mai multă complexitate decât cred că este necesar.

type ValueType<M extends boolean> = M extends true ? Array<string> : string;

interface PropTypes<M extends boolean> {
  multiple: M,
  // more properties
}

type DiscriminatedUnion = {
  multiple: true,
  value: Array<string>
} | {
  multiple: false,
  value: string
}

function myFunction<M extends boolean>({
  multiple
}: PropTypes<M>) {

  let value: ValueType<M>;

  // ... later

  if (multiple) {
    // TS will not assert the type of `value` here
  }

  // Trying discriminated union
  // TS Error: Type '{ multiple: boolean; value: string | string[]; }' is not assignable to type '{ multiple: false; value: string; }'
  let obj: DiscriminatedUnion = {
    multiple,
    value
  }
}

Răspuns #1

În prezent, nu este posibil să vă rezolvați problema în acest mod; @jcalz menționează cu amabilitate că există o cerere de funcție deschisă pentru aceasta. Cu toate acestea, problema dvs. probabil că este mai bine rezolvată într-un mod diferit.

Pentru aproape orice întrebare de genul „Am aceste două valori, cum pot să le spun lui Typescript că tipurile lor sunt corelate?”, răspunsul este să le pui împreună ca proprietăți într-un obiect. Apoi poți face ca tipul obiectului să fie o uniune discriminată, astfel încât un test asupra uneia dintre proprietăți să poată restrânge tipul întregului obiect.

type DiscriminatedUnion =
    | {multiple: false, value: string}
    | {multiple: true, value: string[]}

declare let obj: DiscriminatedUnion;

if(obj.multiple) {
    // here, obj.value is narrowed to string[]
    obj.value
}

Link către terenul de joacă

Răspunsul #2

Cu TypeScript, uneori trebuie să renunți la deconstruirea obiectelor până când pot fi discriminate corespunzător. De multe ori, acest lucru impune o reașezare a fluxului. Dacă nu deconstruiești proprietățile imediat, acesta poate să deducă corect tipul:

type Props = {
    value: string[],
    multiple: true
} | {
    value: string,
    multiple: false
}

function doSomethingAndDiscriminate(props:Props) {
    if(props.multiple) {
        const value = props.value[0]
        return {somethingNew: value}
    } else {
        const value = props.value
        return {somethingNew: value}
    }
}

Link către terenul de joacă

În acest exemplu, deduce corect tipul de retur al ambelor posibilități ca fiind { somethingNew: string }