Типы литералов шаблонов
Типы литералов шаблонов основаны на типах строковых литералов и имеют возможность расширяться во множество строк через объединения.
Они имеют тот же синтаксис, что и строковые литералы шаблонов в JavaScript, но используются в позициях типов. При использовании с конкретными типами литералов, литерал шаблона создает новый тип строкового литерала, конкатенируя содержимое.
type World = "world";
type Greeting = `hello ${World}`; Когда в интерполируемой позиции используется объединение, тип представляет собой множество всех возможных строковых литералов, которые могли бы быть представлены каждым членом объединения:
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; Для каждой интерполируемой позиции в литерале шаблона объединения перемножаются:
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`; В целом рекомендуется использовать генерацию заранее для больших объединений строк, но это полезно в небольших случаях.
Объединения строк в типах
Мощность литералов шаблонов проявляется при определении новой строки на основе информации внутри типа.
Рассмотрим случай, когда функция (makeWatchedObject) добавляет новую функцию, называемую on(), к переданному объекту. В JavaScript вызов может выглядеть так: makeWatchedObject(baseObject). Мы можем представить базовый объект как:
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
}; Функция on, которая будет добавлена к базовому объекту, ожидает два аргумента: eventName (тип string) и callback (тип function).
Функция eventName должна иметь вид attributeInThePassedObject + "Changed"; таким образом, firstNameChanged, полученная из атрибута firstName в базовом объекте.
Функция callback, при вызове:
- Должна получать значение типа, связанного с именем
attributeInThePassedObject; таким образом, так какfirstNameтипизировано какstring, обработчик событияfirstNameChangedожидает, что при вызове ему будет передан аргументstring. Аналогично, события, связанные сage, должны ожидать вызов с аргументомnumber - Должна иметь возвращаемый тип
void(для простоты демонстрации)
Наивный сигнатура функции on() может быть такой: on(eventName: string, callback: (newValue: any) => void). Однако в предыдущем описании мы определили важные ограничения по типам, которые мы хотели бы задокументировать в нашем коде. Типы литералов шаблонов позволяют нам внести эти ограничения в наш код.
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
// makeWatchedObject has added `on` to the anonymous Object
person.on("firstNameChanged", (newValue) => {
console.log(`firstName was changed to ${newValue}!`);
}); Обратите внимание, что on прослушивает событие "firstNameChanged", а не только "firstName". Наш наивный спецификация on() могла бы быть более надежной, если бы мы гарантировали, что набор допустимых имен событий был ограничен объединением имен атрибутов в наблюдаемом объекте с добавленным в конце «Изменено». Хотя мы не против выполнения такого расчета в JavaScript, т.е. Object.keys(passedObject).map(x => `${x}Changed`), литералы шаблонов внутри системы типов обеспечивают аналогичный подход к манипуляции строками:
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
/// Create a "watched object" with an `on` method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>; Благодаря этому мы можем создать что-то, что выдает ошибку при передаче неправильного свойства:
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
person.on("firstNameChanged", () => {});
// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});
// It's typo-resistant
person.on("frstNameChanged", () => {}); Выведение типов с помощью литералов шаблонов
Обратите внимание, что мы не воспользовались всей информацией, предоставленной в исходном переданном объекте. При изменении firstName (т.е. события firstNameChanged), мы должны ожидать, что обработчик получит аргумент типа string. Аналогично, обработчик изменений age должен получить аргумент типа number. Мы наивно используем any для типизации аргумента callback. Опять же, типы литералов шаблонов позволяют гарантировать, что тип данных атрибута будет таким же, как и тип первого аргумента обработчика этого атрибута.
Ключевой момент, который делает это возможным, заключается в следующем: мы можем использовать функцию с обобщенным типом таким образом, что:
- Литерал, используемый в первом аргументе, захватывается как тип литерала
- Этот тип литерала может быть проверен как принадлежащий объединению допустимых атрибутов в обобщенном типе
- Тип валидированного атрибута может быть получен из структуры обобщенного типа с использованием индексированного доступа
- Эта информация о типах может затем использоваться для обеспечения того, что аргумент функции-обработчика имеет тот же тип
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 => {
console.log(`new name is ${newName.toUpperCase()}`);
});
person.on("ageChanged", newAge => {
if (newAge < 0) {
console.warn("warning! negative age");
}
}) Здесь мы преобразовали on в метод с обобщенным типом.
Когда пользователь вызывает функцию со строкой "firstNameChanged", TypeScript попытается вывести правильный тип для Key. Для этого он сопоставит Key с содержимым перед "Changed" и выведет строку "firstName". Как только TypeScript это выяснит, метод on сможет извлечь тип firstName из исходного объекта, который в данном случае равен string. Аналогично, при вызове со строкой "ageChanged", TypeScript находит тип свойства age, который равен number.
Выведение типов можно комбинировать различными способами, часто для декомпозиции строк и их последующей реконструиции разными способами.
Встроенные типы для манипуляции строками
Для помощи в манипулировании строками TypeScript включает набор типов, которые могут быть использованы для манипуляции строками. Эти типы встроены в компилятор для повышения производительности и не находятся в файлах .d.ts с TypeScript.
Uppercase<StringType>
Преобразует каждый символ в строке в верхний регистр.
Пример
type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app"> Lowercase<StringType>
Преобразует каждый символ в строке в нижний регистр.
Пример
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP"> Capitalize<StringType>
Преобразует первый символ в строке в верхний регистр.
Пример
type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>;
Uncapitalize<StringType>
Преобразует первый символ в строке в нижний регистр.
Пример
type UppercaseGreeting = "HELLO WORLD"; type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
Технические детали встроенных типов для манипуляции строками
Код, по состоянию на TypeScript 4.1, для этих встроенных функций напрямую использует JavaScript-функции для обработки строк и не учитывает языковые настройки.
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;
}
© 2012-2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html