Pocketbook on TypeScript. Part 6. Manipulation of types

image







We continue to publish a series of adapted and supplemented translations " TypeScript



. "






Other parts:









The type system TS



allows you to create types based on other types.







(generics). . , .









identity



, :







function identity(arg: number): number {
 return arg
}
      
      





, , any



:







function identity(arg: any): any {
 return arg
}
      
      





, .







- . , , , :







function identity<Type>(arg: Type): Type {
 return arg
}
      
      





Type



, .







(), .







. , :







const output = identity<string>('myStr')
   // let output: string
      
      





.







:







const output = identity('myStr')
   // let output: string
      
      





. , , .









arg



?







function loggingIdentity<Type>(arg: Type): Type {
 console.log(arg.length)
 // Property 'length' does not exist on type 'Type'.
 //  'length'     'Type'
 return arg
}
      
      





, (, , ) , , arg



length



, , .







, Type



:







function loggingIdentity<Type>(arg: Type[]): Type[] {
 console.log(arg.length)
 return arg
}
      
      





, Type



arg



, Type



, Type



. , .







:







function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
 console.log(arg.length)
 return arg
}
      
      







(-) , :







function identity<Type>(arg: Type): Type {
 return arg
}

const myIdentity: <Type>(arg: Type) => Type = identity
      
      





:







function identity<Type>(arg: Type): Type {
 return arg
}

const myIdentity: <Input>(arg: Input) => Input = identity
      
      





:







function identity<Type>(arg: Type): Type {
 return arg
}

const myIdentity: { <Type>(arg: Type): Type } = identity
      
      





:







interface GenericIdentityFn {
 <Type>(arg: Type): Type
}

function identity<Type>(arg: Type): Type {
 return arg
}

const myIdentity: GenericIdentityFn = identity
      
      





, , :







interface GenericIdentityFn<Type> {
 (arg: Type): Type
}

function identity<Type>(arg: Type): Type {
 return arg
}

const myIdentity: GenericIdentityFn<number> = identity
      
      





, .







, (enums) (namespaces) .









, :







class GenericNumber<NumType> {
 zeroValue: NumType
 add: (x: NumType, y: NumType) => NumType
}

const myGenericNum = new GenericNumber<number>()
myGenericNum.zeroValue = 0
myGenericNum.add = (x, y) => x + y
      
      





. :







const stringNumeric = new GenericNumber<string>()
stringNumeric.zeroValue = ''
stringNumeric.add = (x, y) => x + y

console.log(stringNumeric.add(stringNumeric.zeroValue, 'test'))
      
      





: . . , .









, , , . loggingIdentity



length



arg



, , , :







function loggingIdentity<Type>(arg: Type): Type {
 console.log(arg.length)
 // Property 'length' does not exist on type 'Type'.
 return arg
}
      
      





, , length



. .







, . length



extends



:







interface Lengthwise {
 length: number
}

function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
 console.log(arg.length)
 //         `length`
 return arg
}
      
      





, :







loggingIdentity(3)
// Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
//   'number'       'Lengthwise'
      
      





, :







loggingIdentity({ length: 10, value: 3 })
      
      







, . . , , . :







function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
 return obj[key]
}

const x = { a: 1, b: 2, c: 3, d: 4 }

getProperty(x, 'a')
getProperty(x, 'm')
// Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'.
      
      







, -. :







function create<Type>(c: { new (): Type }): Type {
 return new c()
}
      
      





prototype



- :







class BeeKeeper {
 hasMask: boolean
}

class ZooKeeper {
 nametag: string
}

class Animal {
 numLegs: number
}

class Bee extends Animal {
 keeper: BeeKeeper
}

class Lion extends Animal {
 keeper: ZooKeeper
}

function createInstance<A extends Animal>(c: new () => A): A {
 return new c()
}

createInstance(Lion).keeper.nametag
createInstance(Bee).keeper.hasMask
      
      





.







keyof





keyof



"" :







type Point = { x: number, y: number }
type P = keyof Point
 // type P = keyof Point
      
      





(index signature) string



number



, keyof



:







type Arrayish = { [n: number]: unknown }
type A = keyof Arrayish
 // type A = number

type Mapish = { [k: string]: boolean }
type M = keyof Mapish
 // type M = string | number
      
      





, M



string | number



. , JS



, obj[0]



— , obj['0']



.







keyof



(mapped types), .







typeof





JS



typeof



, :







console.log(typeof ', !') // string
      
      





TS



typeof



:







const s = ''
const n: typeof s
 // const n: string
      
      





typeof



. , ReturnType<T>



. :







type Predicate = (x: unknown) => boolean
type K = ReturnType<Predicate>
 // type K = boolean
      
      





ReturnType



, :







function f() {
 return { x: 10, y: 3 }
}
type P = ReturnType<f>
// 'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?
// 'f'    ,    . ,    'typeof f'
      
      





: — . f



typeof



:







function f() {
 return { x: 10, y: 3 }
}
type P = ReturnType<typeof f>
 // type P = { x: number, y: number }
      
      







TS



, typeof



.







typeof



( ) . , :







//    ReturnType<typeof msgbox>,    
const shouldContinue: typeof msgbox(' ,   ?')
// ',' expected
      
      





(indexed access types)



:







type Person = { age: number, name: string, alive: boolean }
type Age = Person['age']
 // type Age = number
      
      





— , , keyof



:







type I1 = Person['age' | 'name']
 // type I1 = string | number

type I2 = Person[keyof Person]
 // type I2 = string | number | boolean

type AliveOrName = 'alive' | 'name'
type I3 = Person[AliveOrName]
 // type I3 = string | boolean
      
      





:







type I1 = Person['alve']
// Property 'alve' does not exist on type 'Person'.
      
      





number



. typeof



:







const MyArray = [
 { name: 'Alice', age: 15 },
 { name: 'Bob', age: 23 },
 { name: 'John', age: 38 },
]

type Person = typeof MyArray[number]

type Person = {
   name: string
   age: number
}
type Age = typeof MyArray[number]['age']

type Age = number
// 
type Age2 = Person['age']

type Age2 = number
      
      





, const



, :







const key = 'age'
type Age = Person[key]
/*
 Type 'any' cannot be used as an index type.
 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
*/
/*
  'any'        .
 'key'    ,    . ,    'typeof key'
*/
      
      





, (type alias):







type key = 'age'
type Age = Person[key]
      
      





(conditional types)



, . TS



. .







interface Animal {
 live(): void
}
interface Dog extends Animal {
 woof(): void
}

type Example1 = Dog extends Animal ? number : string
 // type Example1 = number

type Example2 = RegExp extends Animal ? number : string
 // type Example2 = string
      
      





, JS



( ? :



).







SomeType extends OtherType ? TrueType : FalseType
      
      





extends



extends



, (), , ().







. ( ).







:







interface IdLabel {
 id: number /*   */
}
interface NameLabel {
 name: string /*   */
}

function createLabel(id: number): IdLabel
function createLabel(name: string): NameLabel
function createLabel(nameOrId: string | number): IdLabel | NameLabel
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
 throw ' '
}
      
      





createLabel



, .







:







  1. , .
  2. 3 : , ( string



    number



    ), (string



    number



    ). .


, :







type NameOrId<T extends number | string> = T extends number
 ? IdLabel
 : NameLabel
      
      





:







function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
 throw ' '
}

let a = createLabel('typescript')
 // let a: NameLabel

let b = createLabel(2.8)
 // let b: IdLabel

let c = createLabel(Math.random() ? 'hello' : 42)
 // let c: NameLabel | IdLabel
      
      







. , (type guards) , , .







:







type MessageOf<T> = T['message']
// Type '"message"' cannot be used to index type 'T'.
//  '"message"'        'T'
      
      





, TS



T



message



. T



, TS



"":







type MessageOf<T extends { message: unknown }> = T['message']

interface Email {
 message: string
}

interface Dog {
 bark(): void
}

type EmailMessageContents = MessageOf<Email>
 // type EmailMessageContents = string
      
      





, MessageOf



, "" never



? "" :







type MessageOf<T> = T extends { message: unknown } ? T['message'] : never

interface Email {
 message: string
}

interface Dog {
 bark(): void
}

type EmailMessageContents = MessageOf<Email>
 // type EmailMessageContents = string

type DogMessageContents = MessageOf<Dog>
 // type DogMessageContents = never
      
      





, TS



, T



message



.







Flatten



, , :







type Flatten<T> = T extends any[] ? T[number] : T

//   
type Str = Flatten<string[]>
 // type Str = string

//  
type Num = Flatten<number>
 // type Num = number
      
      





Flatten



, number



string[]



. , .









. , .







infer



. , Flatten



:







type Flatten<Type> = Type extends Array<infer Item> ? Item : Type
      
      





infer



Item



T



. "" , .







(type aliases) infer



. , :







type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
 ? Return
 : never

type Num = GetReturnType<() => number>
 // type Num = number

type Str = GetReturnType<(x: string) => string>
 // type Str = string

type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>
 // type Bools = boolean[]
      
      





( ), . .







declare function stringOrNum(x: string): number
declare function stringOrNum(x: number): string
declare function stringOrNum(x: string | number): string | number

type T1 = ReturnType<typeof stringOrNum>
 // type T1 = string | number
      
      





(distributive conditional types)



, (union). :







type ToArray<Type> = Type extends any ? Type[] : never
      
      





ToArray



, .







type ToArray<Type> = Type extends any ? Type[] : never

type StrArrOrNumArr = ToArray<string | number>
 // type StrArrOrNumArr = string[] | number[]
      
      





StrOrNumArray



:







string | number
      
      





:







ToArray<string> | ToArray<number>
      
      





:







string[] | number[]
      
      





, . extends



:







type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never

// 'StrOrNumArr'    
type StrOrNumArr = ToArrayNonDist<string | number>
 // type StrOrNumArr = (string | number)[]
      
      





(mapped types)



, , :







type OnlyBoolsAndHorses = {
 [key: string]: boolean | Horse
}

const conforms: OnlyBoolsAndHorses = {
 del: true,
 rodney: false,
}
      
      





— , , keyof



, :







type OptionsFlags<Type> = {
 [Property in keyof Type]: boolean
}
      
      





OptionsFlag



Type



boolean



.







type FeatureFlags = {
 darkMode: () => void
 newUserProfile: () => void
}

type FeatureOptions = OptionsFlags<FeatureFlags>
 // type FeatureOptions = { darkMode: boolean, newUserProfile: boolean }
      
      





(mapping modifiers)



, : readonly



?



, () , .







-



+



. , +



.







//   `readonly`   
type CreateMutable<Type> = {
 -readonly [Property in keyof Type]: Type[Property]
}

type LockedAccount = {
 readonly id: string
 readonly name: string
}

type UnlockedAccount = CreateMutable<LockedAccount>
 // type UnlockedAccount = { id: string, name: string }
      
      





//   `optional`   
type Concrete<Type> = {
 [Property in keyof Type]-?: Type[Property]
}

type MaybeUser = {
 id: string
 name?: string
 age?: number
}

type User = Concrete<MaybeUser>
 // type User = { id: string, name: string, age: number }
      
      





as





TS



4.1 , as



:







type MappedTypeWithNewProperties<Type> = {
 [Properties in keyof Type as NewKeyType]: Type[Properties]
}
      
      





, (. ):







type Getters<Type> = {
 [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
}

interface Person {
 name: string
 age: number
 location: string
}

type LazyPerson = Getters<Person>
 // type LazyPerson = { getName: () => string, getAge: () => number, getLocation: () => string }
      
      





never



:







//   `kind`
type RemoveKindField<Type> = {
   [Property in keyof Type as Exclude<Property, 'kind'>]: Type[Property]
}

interface Circle {
 kind: 'circle'
 radius: number
}

type KindlessCircle = RemoveKindField<Circle>
 // type KindlessCircle = { radius: number }
      
      





, , . true



false



, pii



true



:







type ExtractPII<Type> = {
 [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false
}

type DBFields = {
 id: { format: 'incrementing' }
 name: { type: string, pii: true }
}

type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>
 // type ObjectsNeedingGDPRDeletion = { id: false, name: true }
      
      





(template literal types)



.







, JS



, . , :







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'
*/
      
      





:







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'
*/
      
      





, .









.







, JS



. , on



, :







const person = makeWatchedObject({
 firstName: 'John',
 lastName: 'Smith',
 age: 30,
})

person.on('firstNameChanged', (newValue) => {
 console.log(`    ${newValue}!`)
})
      
      





, on



firstNameChanged



, firstName



.







:







type PropEventSource<Type> = {
   on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void
}

//  " "   `on`,
//      
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>
      
      





:







const person = makeWatchedObject({
 firstName: 'John',
 lastName: 'Smith',
 age: 26
})

person.on('firstNameChanged', () => {})

person.on('firstName', () => {})
// Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
//   '"firstName"'     ...

person.on('frstNameChanged', () => {})
// Argument of type '"firstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
      
      







, . any



. .







, eventName



:







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: 'Jane',
 lastName: 'Air',
 age: 26
})

person.on('firstNameChanged', newName => {
                         // (parameter) newName: string
   console.log(`  - ${newName.toUpperCase()}`)
})

person.on('ageChanged', newAge => {
                   // (parameter) newAge: number
   if (newAge < 0) {
       console.warn('!  ')
   }
})
      
      





on



.







firstNameChanged



, TS



Key



. TS



Key



"", Changed



, firstName



. on



firstName



, string



. ageChanged



, TS



age



, number



.







(intrisic string manipulation types)



TS



, . .d.ts



, TS



.







  • Uppercase<StringType>





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>





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'
      
      





  • Capitilize<StringType>





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





  • Uncapitilize<StringType>





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





:







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
}
      
      








.







10% !














All Articles