Typescript official manual translation plan [Xi]: type control – template literal type

Time:2022-4-22
  • explain: at present, there is no Chinese translation of the latest official documents of typescript on the Internet, so there is such a translation plan. Because I am also a beginner of typescript, I can’t guarantee the 100% accuracy of translation. If there are errors, please point them out in the comment area;
  • Translation content: tentative translationTypeScript HandbookLater, other parts of the translated documents will be supplemented when available;
  • Project addressTypeScript-Doc-Zh, if it helps you, you can click a star~

Official document address of this chapter:Template Literal Types

Template literal type

Template literal type based onLiteral type stringConstruction, which can be expanded into a variety of strings through union types.

Its grammar andTemplate string in JavaScriptSame, but used to represent types in typescript. When used with a specific literal type, the template literal will produce a new string literal type by splicing the content.

type World = 'world';

type Greeting = `hello ${World}`;
      ^
     // type Greeting = 'hello world'      

When the union type is used at the interpolation position of the template literal, the resulting type is a collection of string literals that each member of the union type can represent:

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
          ^
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

If the template literal has multiple interpolation positions, cross product operation will be carried out between the joint types at each position to obtain the final type:

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
 
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
           ^ 
// type LocaleMessageIDs = "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id"

For large string union types, we recommend that you generate them in advance. For smaller string union types, you can use the method in the above example to generate.

String union type in type

The power of template literals is that they can define a new string based on the existing information in the type.

Suppose there is onemakeWatchedObjectFunction, which can add aonmethod. In JavaScript, the call form of this function is as follows:makeWatchedObject(baseObject)。 The passed in object parameters are similar to the following:

const passedObject = {
    firstName: 'Saoirse',
    lastName: 'Ronan',
    age: 26,
};

To be added to the objectonMethod will accept two parameters, one iseventName(string), one iscallBack(callback function).

eventNameThe form is similar toattributeInThePassedObject + 'Changed'。 For example, the incoming object has afirstNameProperty, then there will be a corresponding one calledfirstNameChangedofeventName

andcallBackThe callback function, when called, will:

  • Accept a parameter, parameter type andattributeInThePassedObjectAssociated with the type of. for instance,firstNameThe type of isstring, thenfirstNameChangedThe callback function corresponding to this event is also expected to accept an error when calledstringParameter of type. Similarly, andageThe associated event callback function should accept an event when callednumberParameter of type.
  • The return value type isvoid(to facilitate the explanation of examples)

on()The simple version of the function signature might look like this:on(eventName: string, callBack: (newValue: any) => void)。 However, from the above description, we find that we still need to implement very important type constraints in the code. The template literal type can help us do this.

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});
 
//The makewatchedobject function adds an on method to an anonymous object
 
person.on("firstNameChanged", (newValue) => {
  console.log(`firstName was changed to ${newValue}!`);
});

be careful,onThe event monitored is"firstNameChanged", not"firstName"。 If we want to ensure that the collection of eligible event names is constrained by the union type of the object property name (with “changed” at the end), then our simple versionon()The method needs to be further improved. Although we can easily achieve this effect in JavaScript, such as usingObject.keys(passedObject).map(x => ${x}Changed)However, template literals in the type system provide a similar way to manipulate strings:

type PropEventSource<Type> = {
    on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
 
//Create a listening object with an on method to listen for changes in object properties
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

In this way, when an incorrect parameter is passed in, typescript will throw an error:

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});
 
person.on("firstNameChanged", () => {});
 
//Prevent common human errors (incorrectly using object property names instead of event names)
person.on("firstName", () => {});
// Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
 
//Spelling mistakes
person.on("frstNameChanged", () => {});
// Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

Inference of template literal

Note that so far we have not fully utilized the information provided by the incoming object.firstNameWhen changing (trigger)firstNameChangedEvent), we expect the callback function to accept astringParameter of type. Similarly,ageWhen changing, the corresponding callback function will also accept onenumberParameter of type. But for now, we just useanyAs the type of callback function parameter. Here we need to use the template literal type again, which can ensure that the data type of the attribute is consistent with the parameter type of the callback function corresponding to the attribute.

The key to this is that we can use a function with generics to ensure that:

  1. The literal in the first parameter can be captured as a literal type
  2. The valid properties of the generic form a union type, which can verify whether the captured literal type is a member of the union type
  3. You can view the types of verified properties by index access in the generic structure
  4. This type information can be further used to ensure that the parameters of the callback function are also of the same type
type PropEventSource<Type> = {
    on<Key extends string & keyof Type>(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void): void;
}

declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});
 
person.on("firstNameChanged", newName => {
                                 ^
                     // (parameter) newName: string
    console.log(`new name is ${newName.toUpperCase()}`);
});
 
person.on("ageChanged", newAge => {
                           ^     
                 // (parameter) newAge: number
    if (newAge < 0) {
        console.warn("warning! negative age");
    }
})

Here we letonBecomes a generic method.

When the developer passes the string"firstNameChanged"CalledonMethod, typescript will try to inferKeyThe correct type of. Specifically, it willKeyand"Changed"Match the previous part and infer the string"firstName"。 Once the typescript inference is complete,onMethod to retrieve the original objectfirstNameType of attribute – i.estringType. Similarly, when passing"ageChanged"Typescript will also find when calling methodsageThe type of the property isnumber

Inference has many different combinations, which are usually used to deconstruct strings and reconstruct strings in different ways.

Built in string manipulation type

To facilitate manipulation of strings, typescript introduces some related types. To improve performance, these types are built into the compiler and cannot be included with typescript.d.tsFound in file.

Uppercase<StringType>

Converts each character in the string to uppercase.

Example:

type Greeting = 'Hello, world'
type ShoutyGreeting = Uppercase<Greeting>
        ^
        // type ShoutyGreeting = 'HELLO, WORLD'    
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<'my_app'>
      ^
      // type MainID = 'ID-MY_APP'

Lowercase<StringType>

Converts each character in a string to lowercase.

Example:

type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
          ^ 
      // type QuietGreeting = "hello, world"
 
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
        ^ 
    // type MainID = "id-my_app"

Capitalize<StringType>

Converts the first character of a string to uppercase.

Example:

type LowercaseGreeting = 'hello, world';
type Greeting = Capitalize<LowercaseGreeting>;
        ^
       // type Greeting = 'Hello, world'     

Uncapitalize<StringType>

Converts the first character of a string to lowercase.

Example:

type UppercaseGreeting = 'HELLO WORLD';
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
           ^    
          // type UncomfortableGreeting = "hELLO WORLD"     

Some technical details about the built-in string manipulation type:

Starting from typescript 4.1, the implementation of these built-in functions directly uses the string runtime functions of JavaScript for operation, and cannot achieve localized recognition.

function applyStringMapping(symbol: Symbol, str: string) {
    switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
        case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
        case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
        case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
        case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
    }
    return str;
}

Recommended Today

redis day2

persistence mechanism client redis[memory] —–&gt; memory data-data persistence–&gt;disk Redis officially provides two different persistence methods to store data in memory to the hard disk: Snapshot AOF (Append Only File) only appends log files   Snapshot Features:In this way, all data at a certain moment can be written to the hard disk, of course, this is […]