This code fails to compile:
const nodeIsUseless = (node: unknown) =>
node !== null &&
typeof node === "object" &&
"type" in node &&
typeof node.type === "string" &&
node.type === "JSXText";
because on the last 2 lines:
Property 'type' does not exist on type 'object'.(2339)
...which I can understand by itself, but I do not understand why after "type" in node
check, TS infers node
to still be of type object
instead of type { type: unknown; [key: string]: unknown }
, which would not trigger error.
CodePudding user response:
You should do this:
const nodeIsUseless = (node: unknown) =>
node !== null &&
node !== undefined &&
node instanceof Object &&
!Array.isArray(node) &&
"type" in node &&
typeof node["type"] === "string" &&
node["type"] === "JSXText";
Just need to check insonceof
instead of typeof
and use object["key"]
method to access values instead of .key
.
Also, it's good practice to ensure that the item isn't an Array since instonceof [] === 'object'
too
CodePudding user response:
Unfortunately, the TypeScript built-in in
operator type guard is not as powerful as you expect it to be.
From a bare object
, it will not infer that the tested property is available. It can infer that it is indeed there, only if it is already potentially available, e.g. in one of a union-ed types. I.e. the control flow will not make the tested property "appear", but only try to differentiate between union-ed types.
declare const o: object;
if ("type" in o) {
o.type // Error: Property 'type' does not exist on type 'object'.
//^? object
}
declare const u: Number | String; // Note: using class only for the sake of the demo
if ("toFixed" in u) {
u.toFixed(); // Okay
//^? Number
}
In your case, you could therefore specify a union for the node
argument, with a possible type of e.g. { type: unknown }
.
However, the unknown
top type absorbs all other types in a union, so it would have to be replaced by everything else, e.g. using the special type {}
to represent common types:
const nodeIsUseless = (node: undefined | null | {} | { type: unknown }) =>
node !== null &&
typeof node === "object" &&
// ^? {} | { type: unknown } | undefined
"type" in node &&
// ^? {} | { type: unknown }
typeof node.type === "string" && // Okay
// ^? { type: unknown }
node.type === "JSXText"; // Okay