The compiler uses some heuristics to determine when to widen literals. One of them is the following:
- The type inferred for a property in an object literal is the widened literal type of the expression unless the property has a contextual type that includes literal types.
So by default, that "foo" | "bar"
gets widened to string
inside the object literal you've assigned to foobar
.
UPDATE FOR TS 3.4
You can now use const
assertions to ask for narrower types:
const foobar = {
bar
} as const;
/* const foobar: {
readonly bar: "foo" | "bar";
} */
The literally()
function in the rest of this answer may still be of some use, but I'd suggest using as const
where possible.
Note the part of the heuristic that says "unless the property has a contextual type that includes literal types." One of the ways to hint to the compiler that a type like "foo" | "bar"
should stay narrowed is to have it match a type constrained to string
(or a union containing it).
The following is a helper function I sometimes use to do this:
type Narrowable = string | number | boolean | symbol | object |
null | undefined | void | ((...args: any[]) => any) | {};
const literally = <
T extends V | Array<V | T> | { [k: string]: V | T },
V extends Narrowable
>(t: T) => t;
The literally()
function just returns its argument, but the type tends to be narrower. Yes, it's ugly... I keep it in a utils library out of sight.
Now you can do:
const foobar = literally({
bar
});
and the type is inferred as { bar: "foo" | "bar" }
as you expected.
Whether or not you use something like literally()
, I hope this helps you; good luck!
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…