admin管理员组文章数量:1289496
Considering this very simple code:
type ExempleType = {
someProperty?: string[];
};
const someVar: ExempleType = { someProperty: [] };
someVar.someProperty.push('test'); // => 'someVar.someProperty' is possibly 'undefined'.ts(18048)
I don't understand why typescript returns the error on this last line. someProperty
cannot be undefined since it was explicitly defined.
If I slightly update the code and define someProperty
just after the variable instantiation, it works:
type ExempleType = {
someProperty?: string[];
};
const someVar: ExempleType = {};
someVar.someProperty = [];
someVar.someProperty.push('test'); // => No error!
This looks like a bug in Typescript to me... But on another end, I would expect such a bug to be caught (and fixed) very quickly, so it's probably not a bug.
So did I miss something that could explain this error?
Considering this very simple code:
type ExempleType = {
someProperty?: string[];
};
const someVar: ExempleType = { someProperty: [] };
someVar.someProperty.push('test'); // => 'someVar.someProperty' is possibly 'undefined'.ts(18048)
I don't understand why typescript returns the error on this last line. someProperty
cannot be undefined since it was explicitly defined.
If I slightly update the code and define someProperty
just after the variable instantiation, it works:
type ExempleType = {
someProperty?: string[];
};
const someVar: ExempleType = {};
someVar.someProperty = [];
someVar.someProperty.push('test'); // => No error!
This looks like a bug in Typescript to me... But on another end, I would expect such a bug to be caught (and fixed) very quickly, so it's probably not a bug.
So did I miss something that could explain this error?
Share Improve this question edited Feb 19 at 19:33 VLAZ 29.1k9 gold badges62 silver badges84 bronze badges asked Feb 19 at 18:00 ZoddoZoddo 2261 silver badge11 bronze badges 6 | Show 1 more comment2 Answers
Reset to default 1If you annotate a variable with a (non-union) type like ExempleType
, then that's all TypeScript knows about the type of that variable. Any more specific information about the type of a value you assign to it is simply not tracked. There is such a thing as assignment narrowing, but that only works for union types. So if you write let x: string[] | undefined = []
, then x
is narrowed to string[]
after that assignment. But the similar-looking let y: { x: string[] | undefined } = { x: [] }
doesn't work the same way because you are assigning to a non-union object type whose property happens to be a union. It would be nice if assignment narrowing worked on non-union types, also, but it doesn't happen (see this comment on microsoft/TypeScript#8513 for more information).
This explains the difference between const someVar: ExempleType = { someProperty: [] };
and const someVar: ExempleType = {}; someVar.someProperty = [];
. In the latter case you have assigned the property, which is effectively a union type (optional properties behave similarly to unions with undefined
) so it is narrowed to string[]
. Subsequent direct property accesses like someVar.someProperty
will then see string[]
, but someVar
itself does not get narrowed:
const someVar: ExempleType = {};
someVar.someProperty = [];
someVar.someProperty.push('test'); // no error
function foo(r: Required<ExempleType>) { }
foo(someVar); // error! Types of property 'someProperty' are incompatible.
Generally speaking if you want TypeScript to keep track of specific information from initializers (and you're not planning to do a lot of mutation) then you might want to avoid annotating entirely, and use the satisfies
operator to check that the initializer is compatible with that type.
This is a little messy with empty arrays, because []
will end up being inferred as never[]
, even with satisfies
. There's an open issue at microsoft/TypeScript#51853. Right now the "safe" way that uses satisfies
is wordy:
const someVar = {
someProperty: [] satisfies string[] as string[]
} satisfies ExempleType;
/* const someVar: { someProperty: string[]; } */
The expr satisfies T as T
construction effectively first checks that expr
is assignable to T
and then widens it to T
, so [] satisfies string[] as string[]
ends up with []
as type string[]
after checking that it works. It's easier to just write [] as string[]
but that's a little less safe. And then the satisfies
at the end makes sure that { someProperty: string[] }
is compatible with ExempleType
, which it is... but someVar
is now narrowed than ExempleType
; it's someProperty
is known to be string[]
, and the rest of your code works as desired:
someVar.someProperty.push('test'); // okay
function foo(r: Required<ExempleType>) { }
foo(someVar); // okay
Playground link to code
The reason Typescript tells you the property could be undefined
is because that is what you tell it. When you write const x: T = y
, Typescript treats x
as T
, regardless of the actual value of y
(though y
must be assignable to T
).
If you really want Typescript to use the type of y
for x
, you can use const x = y satisfies T
, which tells Typescript to check that y
is assignable to T
without casting y
to T
.
本文标签: typescriptproperty is possibly 39undefined39 while it39s been explicitly definedStack Overflow
版权声明:本文标题:typescript - property is possibly 'undefined' while it's been explicitly defined - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741479388a2381075.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
undefined
, then don't make it optional. If you want it optional, then be prepared to handle the case when it's missing. You can't have both. – VLAZ Commented Feb 19 at 18:12ExampleType
comes from a library you cannot modify (because the object you construct is intended to be passed to a method of this lib, and it's effectively optional to provide this property). I understand the typing says it can be undefined, but TypeScript should infer that it's not undefined in this case (like it does in the second example). – Zoddo Commented Feb 19 at 18:16someVar
with typeExempleType
, so that's all TS knows about it. Specific info from the initializer is lost. TS does have assignment narrowing, but that only works on unions.ExempleType
is not a union, so assigning to it doesn't narrow. ButExempleType["someProperty"]
is effectively a union,string[] | undefined
, so assigning does narrow (and I think optional prop assignments narrow). Does that fully address the q? If so I'll write an a or find a duplicate; if not, what's missing? – jcalz Commented Feb 19 at 18:28