TypeScript 팀은 강력한 템플릿 리터럴 유형, 매핑된 유형의 키 재매핑 및 재귀 조건부 유형을 포함하는 TypeScript 4.1을 출시했습니다. 다음 기사에서는 TypeScript의 템플릿 리터럴 유형을 안내합니다. 도움이 되기를 바랍니다.
템플릿 리터럴 유형은 문자열 리터럴 유형을 기반으로 하며 공용체 유형을 통해 여러 문자열로 확장될 수 있습니다.
JavaScript 템플릿 문자열과 동일한 구문을 가지고 있지만 유형 작업에만 사용할 수 있습니다. 템플릿 리터럴 유형이 사용되면 템플릿의 변수가 바뀌고 새 문자열 리터럴이 반환됩니다.
type World = "world"; type Greeting = `hello ${World}`; // type Greeting = "hello world"
템플릿의 변수가 공용체 유형인 경우 가능한 모든 문자열 리터럴이 표시됩니다.
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"
템플릿 리터럴은 공용체 유형이므로 결과는 교차 곱셈됩니다. 예를 들어 다음 예에는 2 2 3, 총 12개의 결과가 있습니다.
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"
정말 긴 경우 문자열 공용체 유형을 권장합니다. 이 유형은 여전히 더 짧은 상황에 적합합니다.
템플릿 리터럴의 가장 유용한 점은 유형의 내부 정보를 기반으로 새 문자열을 정의할 수 있다는 것입니다.
여기에 이러한 함수는 makeWatchedObject
입니다. > 전달된 객체에 on
메서드를 추가합니다. JavaScript에서 해당 호출은 makeWatchedObject(baseObject)
와 같습니다. 수신 객체는 다음과 같다고 가정합니다. makeWatchedObject
, 它会给传入的对象添加了一个 on
方法。在 JavaScript 中,它的调用看起来是这样:makeWatchedObject(baseObject)
,我们假设这个传入对象为:
const passedObject = { firstName: "Saoirse", lastName: "Ronan", age: 26, };
这个 on
方法会被添加到这个传入对象上,该方法接受两个参数,eventName
( string
类型) 和 callBack
(function
类型):
// 伪代码 const result = makeWatchedObject(baseObject); result.on(eventName, callBack);
我们希望 eventName
是这种形式:attributeInThePassedObject + "Changed"
,举个例子,passedObject
有一个属性 firstName
,对应产生的 eventName
为 firstNameChanged
,同理,lastName
对应的是 lastNameChanged
,age
对应的是 ageChanged
。
当这个 callBack
函数被调用的时候:
attributeInThePassedObject
相同类型的值。比如 passedObject
中, firstName
的值的类型为 string
, 对应 firstNameChanged
事件的回调函数,则接受传入一个 string
类型的值。age
的值的类型为 number
,对应 ageChanged
事件的回调函数,则接受传入一个 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"
,而回调函数传入的值 newValue
,我们希望约束为 string
类型。我们先实现第一点。
在这个例子里,我们希望传入的事件名的类型,是对象属性名的联合,只是每个联合成员都还在最后拼接一个 Changed
字符,在 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>;
注意,我们在这里例子中,模板字面量里我们写的是 string & keyof Type
,我们可不可以只写成 keyof Type
呢?如果我们这样写,会报错:
type PropEventSource<Type> = { on(eventName: `${keyof Type}Changed`, callback: (newValue: any) => void): void; }; // Type 'keyof Type' is not assignable to type 'string | number | bigint | boolean | null | undefined'. // Type 'string | number | symbol' is not assignable to type 'string | number | bigint | boolean | null | undefined'. // ...
从报错信息中,我们也可以看出报错原因,在 《TypeScript 系列之 Keyof 操作符》里,我们知道 keyof
操作符会返回 string | number | symbol
类型,但是模板字面量的变量要求的类型却是 string | number | bigint | boolean | null | undefined
,比较一下,多了一个 symbol 类型,所以其实我们也可以这样写:
type PropEventSource<Type> = { on(eventName: `${Exclude<keyof Type, symbol>}Changed`, callback: (newValue: any) => void): void; };
再或者这样写:
type PropEventSource<Type> = { on(eventName: `${Extract<keyof Type, string>}Changed`, callback: (newValue: any) => void): void; };
使用这种方式,在我们使用错误的事件名时,TypeScript 会给出报错:
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", () => {}); // Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'. // It's typo-resistant person.on("frstNameChanged", () => {}); // Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
现在我们来实现第二点,回调函数传入的值的类型与对应的属性值的类型相同。我们现在只是简单的对 callBack
的参数使用 any
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"); } })
on
메소드는 수신 객체에 추가됩니다. 객체에서 이 메소드는 eventName
(string
유형) 및 callBack
(function
유형)이라는 두 개의 매개변수를 허용합니다. 🎜 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"🎜
eventName
이 attributeInThePassedObject + "Changed"
형식이기를 바랍니다. 예를 들어 passedObject
에는 firstName, 해당 <code>eventName
은 firstNameChanged
입니다. 마찬가지로 lastName
은 lastNameChanged
, age는 <code>ageChanged
에 해당합니다. 🎜🎜이 callBack
함수가 호출될 때: 🎜attributeInThePassedObject
와 동일한 유형의 값이 전달되어야 합니다. 예를 들어 passedObject
에서 firstName
의 값 유형은 string
이고 firstNameChanged
에 해당하는 콜백 함수 이벤트는 전달된 문자열
유형의 값을 입력합니다. age
의 값 유형은 number
이고 ageChanged
이벤트에 해당하는 콜백 함수는 전달된 number
유형을 허용합니다. in. 가치. void
유형입니다. on()
메소드의 서명은 처음에 다음과 같습니다: on(eventName: string, callBack: (newValue: any) => void)
. 이러한 서명을 사용하면 위에서 언급한 제약 조건을 구현할 수 없습니다. 이때 템플릿 리터럴을 사용할 수 있습니다. 🎜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"🎜이 예에서는
on
메서드에 의해 추가된 이벤트의 이름이 입니다. "firstName"
뿐만 아니라 >"firstNameChanged"
이고 콜백 함수에 의해 전달된 값이 newValue
이므로 제약 조건을 로 설정하려고 합니다. 문자열
코드> 유형입니다. 먼저 첫 번째 사항을 구현해 보겠습니다. 🎜🎜이 예에서는 전달된 이벤트 이름의 유형이 객체 속성 이름의 합집합이길 바라지만 각 합집합 멤버는 JavaScript에서 끝에 Changed
문자로 연결됩니다. , we 다음과 같은 계산을 할 수 있습니다: 🎜type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello, world"🎜템플릿 리터럴은 유사한 문자열 연산을 제공합니다: 🎜
type UppercaseGreeting = "HELLO WORLD"; type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // type UncomfortableGreeting = "hELLO WORLD"🎜여기의 예에서 템플릿 리터럴에 작성한 내용은
string & keyof Type
입니다. 그냥 keyof Type
으로 쓰면 되나요? 이렇게 작성하면 오류가 보고됩니다. 🎜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; }🎜오류 메시지에서 오류의 원인도 "TypeScript 시리즈의 Keyof 연산자"에서 확인할 수 있습니다. > 연산자는
string | number | 기호
를 반환하지만 템플릿 리터럴 변수는 string | bigint | boolean | 또 하나의 기호 유형이므로 실제로 다음과 같이 작성할 수도 있습니다: 🎜rrreee🎜 또는 다음과 같이 작성할 수도 있습니다: 🎜rrreee🎜이 방법을 사용하면 잘못된 이벤트 이름을 사용하면 TypeScript에서 오류가 발생합니다. 🎜 rrreee<h3><strong>템플릿 리터럴을 사용한 템플릿 추론</strong></h3>🎜이제 두 번째 점을 실현해 보겠습니다. 콜백 함수에 의해 전달된 값의 유형은 해당 속성 값의 유형과 동일합니다. . 이제 <code>callBack
의 매개변수에 대해 any
유형을 사용하고 있습니다. 이 제약 조건을 실현하는 열쇠는 일반 함수를 사용하는 것입니다: 🎜捕获泛型函数第一个参数的字面量,生成一个字面量类型
该字面量类型可以被对象属性构成的联合约束
对象属性的类型可以通过索引访问获取
应用此类型,确保回调函数的参数类型与对象属性的类型是同一个类型
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"); } })
这里我们把 on
改成了一个泛型函数。
当一个用户调用的时候传入 "firstNameChanged"
,TypeScript 会尝试着推断 Key
正确的类型。它会匹配 key
和 "Changed"
前的字符串 ,然后推断出字符串 "firstName"
,然后再获取原始对象的 firstName
属性的类型,在这个例子中,就是 string
类型。
TypeScript 的一些类型可以用于字符操作,这些类型处于性能的考虑被内置在编译器中,你不能在 .d.ts
文件里找到它们。
把每个字符转为大写形式:
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"
把每个字符转为小写形式:
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"
把字符串的第一个字符转为大写形式:
type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello, world"
把字符串的第一个字符转换为小写形式:
type UppercaseGreeting = "HELLO WORLD"; type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // type UncomfortableGreeting = "hELLO WORLD"
从 TypeScript 4.1 起,这些内置函数会直接使用 JavaScript 字符串运行时函数,而不是本地化识别 (locale aware)。
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; }
【相关推荐:javascript学习教程】
위 내용은 TypeScript 데이터 유형의 템플릿 리터럴 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!