Reputation: 394
Using the tsc --strict
command I get the following error:
error TS2339: Property 'foo' does not exist on type 'T'. Property 'foo' does not exist on type 'Bar'. 16 console.log(obj.foo)
What I don't understand is why I can assign the object literal to obj
but get an error using the same property inside the function. Thanks!
type Foo = {
foo: string
xyz: string
}
type Bar = {
bar: string
xyz: string
}
type T = Foo | Bar
let obj: T = { foo: "foo", xyz: "xyz" }
const sayHello = (obj: T) => {
console.log(obj.foo)
}
Upvotes: 1
Views: 628
Reputation: 51034
The type Foo
is more specific than the type Foo | Bar
. If you have a box which you can put Foo | Bar
s, in, then you can put a Foo
into that box, no problem. The variable obj
and the parameter also named obj
are the same in this regard; you can assign either a Foo
or a Bar
to the variable, and you can call the function with either a Foo
or a Bar
as the argument.
The difference is when you try to get obj.foo
from obj
which has type Foo | Bar
. The .foo
property is only defined on things of type Foo
; but the parameter obj
isn't necessarily a Foo
, it could be a Bar
instead, so it doesn't necessarily have a .foo
property. That's why you get an error.
But you'll get the same error if you try to do console.log(obj.foo);
with a variable of type T
outside the function. It has nothing to do with being a parameter or not.
In your specific code, you don't see this error because even though you declared obj
to have type T
, Typescript knows that you've assigned a Foo
to it, so because of control-flow type narrowing the variable is actually of type Foo
after the assignment, and will remain as such until the variable is reassigned. If you change the declaration to avoid narrowing with an as T
type assertion, then the error will appear as expected:
let obj: T = { foo: "foo", xyz: "xyz" } as T
console.log(obj.foo); // type error
Upvotes: 0
Reputation: 249546
When you assign to a union you can assign either one of the constituent types from the union. This is why the assignment succeeds
When you try to access a parameter/variable of a union type, you don't actually know which of the union constituent types will actually be in it. It could be wither of them, so typescript considers as safe only access properties that are common to all members of the union. In your example accessing xyz
is safe as it exists in both union constituents
You need to use a type guard in order to get the compiler to narrow the type of the parameter to either one or the other constituent types:
const sayHello = (obj: T) => {
console.log(obj.xyz) // ok common
if ('bar' in obj) {
obj.bar // ok
} else {
obj.foo //ok
}
}
Upvotes: 2