typescript - Typ 'A | B' kan inte tilldelas typen 'A & B'


Varför kompileras inte denna kod?

type A = { n: number }
type B = { s: string }
type Thing = {
  a: A
  b: B
}
function update(obj: Thing, path: keyof Thing) {
  obj[path] = obj[path]
}

Jag skulle förvänta mig att båda sidor av tilldelningen har typen A | B, men TypeScript-kompilatorn misslyckas med:

error TS2322: Type 'A | B' is not assignable to type 'A & B'.
  Type 'A' is not assignable to type 'A & B'.
    Property 's' is missing in type 'A' but required in type 'B'.

10     obj[path] = obj[path]
       ~~~~~~~~~

Finns det ett sätt att få det att fungera?

Svar #1

Prova detta

function update<T extends Thing>(obj: T, path: keyof T) {
  obj[path] = obj[path]
}

Svar #2

När du får åtkomst till obj[path] kan det antingen returnera en A eller en B, och kompilatorn vet inte vilken, och den (korrekt) kommer inte tillåta tilldelningen om inte alla begränsningar är uppfyllda:

type key = keyof Thing;
const keys: key[] = ['a', 'b'];

const path = keys[Math.round(Math.random())];
const thing: Thing = {
    a: {
        n: 3
    },

    b: {
        s: 'hi'
    }
};

const result = thing[path]; // A | B, compiler can't know which!

Så nu om jag använder en väg för att komma åt en Sak, får jag en A eller en B? Kompilatorn kan inte veta, och den kan inte begränsa typen vid åtkomstpunkten. Du och jag vet att det är samma sak, men kompilatorn kan inte bevisa det. Detsamma gäller för funarg.

Om du använder något som kan kännäs statiskt, tillåter kompilatorn tilldelningen:

function update(obj: Thing) {
  obj.a = obj.a;
  obj['a'] = obj['a'];
}

Lekplats

Så det täcker varför, men om du vill veta hur du får kompilatorn att förstå rätt typ, kolla in detta kanoniska svar