TypeScript Template Literal String Type
Description
TypeScript supports template literal strings1, with the same syntax as JavaScript Template Literals. There is also an in progress PR to switch to type alias helpers2.
Syntax
type World = "world";
type Greeting = `hello ${World}`;
// same as
// type Greeting = "hello world";
Examples
Unions
With unions you can mix and match.
type Color = "red" | "blue";
type Quantity = "one" | "two";
type SeussFish = `${Quantity | Color} fish`;
// same as
// type SeussFish = "one fish" | "two fish"
// | "red fish" | "blue fish";
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
// Takes
// | "top-left" | "top-center" | "top-right"
// | "middle-left" | "middle-center" | "middle-right"
// | "bottom-left" | "bottom-center" | "bottom-right"
declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;
setAlignment("top-left"); // works!
setAlignment("top-middel"); // error!
setAlignment("top-pot"); // error! but good doughnuts if you're ever in Seattle
Objects
type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
let person = makeWatchedObject({
firstName: "Homer",
age: 42, // give-or-take
location: "Springfield",
});
// error!
person.on("firstName", () => {
});
// error!
person.on("frstNameChanged", () => {
});
// success!
person.on("firstNameChanged", () => {
console.log(`firstName was changed!`);
});
Inference
Here we made on into a generic method. When a user calls with the string
'firstNameChanged'
, TypeScript will try to infer the right type for
K
. To do that, it will match K
against the content prior to
"Changed"
and infer the string "firstName"
. Once TypeScript figures
that out, the on
method can fetch the type of firstName
on the
original object, which is string
in this case. Similarly, when we call
with "ageChanged"
, it finds the type for the property age
which is
number
).
type PropEventSource<T> = {
on<K extends string & keyof T>
(eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void;
};
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
let person = makeWatchedObject({
firstName: "Homer",
age: 42,
location: "Springfield",
});
// works! 'newName' is typed as 'string'
person.on("firstNameChanged", newName => {
// 'newName' has the type of 'firstName'
console.log(`new name is ${newName.toUpperCase()}`);
});
// works! 'newAge' is typed as 'number'
person.on("ageChanged", newAge => {
if (newAge < 0) {
console.log("warning! negative age");
}
})