Pocketbook on TypeScript. Part 3. Narrowing types

image







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



. "






Other parts:









Suppose we have a function called padLeft



:







function padLeft(padding: number | string, input: string): string {
 throw new Error('  !')
}
      
      





padding



number



, , input



. padding



string



, , padding



input



. , padLeft



number



padding



:







function padLeft(padding: number | string, input: string): string {
 return new Array(padding + 1).join(' ') + input
 // Operator '+' cannot be applied to types 'string | number' and 'number'.  '+'       'string | number'
}
      
      





. TS



, number



number | string



, . , padding



- :







function padLeft(padding: number | string, input: string): string {
 if (typeof padding === 'number') {
   return new Array(padding + 1).join(' ') + input
 }
 return padding + input
}
      
      





typeof padding === 'number'



(type guard). (narrowing).







function padLeft(padding: number | string, input: string) {
 if (typeof padding === "number") {
   return new Array(padding + 1).join(" ") + input;
                   // (parameter) padding: number
 }
 return padding + input;
       // (parameter) padding: string
}
      
      





.







typeof





typeof



:







  • "string"
  • "number"
  • "bigint"
  • "boolean"
  • "symbol"
  • "undefined"
  • "object"
  • "function"


:







function printAll(strs: string | string[] | null) {
 if (typeof strs === "object") {
   for (const s of strs) {
     // Object is possibly 'null'.     'null'
     console.log(s);
   }
 } else if (typeof strs === "string") {
   console.log(strs);
 } else {
   // ...
 }
}
      
      





printAll



, strs



(). , typeof null



object



( ), .







, string[] | null



string[]



.







(truthiness narrowing)



JS



, &&



, ||



, if



, !



.. , if



boolean



:







function getUsersOnlineMessage(numUsersOnline: number) {
 if (numUsersOnline) {
   return `   ${numUsersOnline} !`;
 }
 return "   :(";
}
      
      





JS



if



( ) (true



false



).







  • 0
  • NaN
  • "" ( )
  • 0n (bigint- )
  • null
  • undefined


, .. false



, , .. true



. Boolean



(!!



):







//    `true`
Boolean('hello')
!!'world'
      
      





null



undefined



. printAll



:







function printAll(strs: string | string[] | null) {
 if (strs && typeof strs === "object") {
   for (const s of strs) {
     console.log(s);
   }
 } else if (typeof strs === "string") {
   console.log(strs);
 }
}
      
      





, , strs



. :







TypeError: null is not iterable
 : null    () 
      
      





, . printAll



:







function printAll(strs: string | string[] | null) {
 // !!!
 //     
 // !!!
 if (strs) {
   if (typeof strs === "object") {
     for (const s of strs) {
       console.log(s);
     }
   } else if (typeof strs === "string") {
     console.log(strs);
   }
 }
}
      
      





, : .







, "":







function multiplyAll(
 values: number[] | undefined,
 factor: number
): number[] | undefined {
 if (!values) {
   return values
 } else {
   return values.map((x) => x * factor)
 }
}
      
      





(equality narrowing)



switch



===



, !==



, ==



, !=



, :







function example(x: string | number, y: string | boolean) {
 if (x === y) {
   //       
   x.toUpperCase()
   // (method) String.toUpperCase(): string
   y.toLowerCase()
   // (method) String.toLowerCase(): string
 } else {
   console.log(x)
           // (parameter) x: string | number
   console.log(y)
           // (parameter) y: string | boolean
 }
}
      
      





x



y



, TS



, . string



— , x



, y



, TS



, x



y



string



.







printAll



, . :







function printAll(strs: string | string[] | null) {
 if (strs !== null) {
   if (typeof strs === "object") {
     for (const s of strs) {
                   // (parameter) strs: string[]
       console.log(s);
     }
   } else if (typeof strs === "string") {
     console.log(strs);
               // (parameter) strs: string
   }
 }
}
      
      





(==



!=



) , , (===



!==



). , == null



null



, undefined



. == undefined



undefined



, null



.







interface Container {
 value: number | null | undefined;
}

function multiplyValue(container: Container, factor: number) {
 //  'null'  'undefined'  
 if (container.value != null) {
   console.log(container.value);
               // (property) Container.value: number

   //      'container.value'
   container.value *= factor;
 }
}
      
      





in





JS



in



. TS



.







, 'value' in x



, 'value' — , x



— , x



, value



, , :







type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
 if ("swim" in animal) {
   return animal.swim();
 }

 return animal.fly();
}
      
      





, , , (swim), (fly) ( ):







type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = {  swim?: () => void, fly?: () => void };

function move(animal: Fish | Bird | Human) {
 if ("swim" in animal) {
   animal
   // (parameter) animal: Fish | Human
 } else {
   animal
   // (parameter) animal: Bird | Human
 }
}
      
      





instanceof





instanceof



, "" . , x instanceof Foo



, Foo.prototype



x



. , new



. :







function logValue(x: Date | string) {
 if (x instanceof Date) {
   console.log(x.toUTCString());
             // (parameter) x: Date
 } else {
   console.log(x.toUpperCase());
             // (parameter) x: string
 }
}
      
      





(assignments)



, , TS



"" :







let x = Math.random() < 0.5 ? 10 : "hello world!";
 // let x: string | number

x = 1;

console.log(x);
         // let x: number

x = "goodbye!";

console.log(x);
         // let x: string
      
      





, , x



, string | number



. , x



, :







x = true;
// Type 'boolean' is not assignable to type 'string | number'.

console.log(x);
         // let x: string | number
      
      







(control flow analysis) — , TS



(reachability) . , :







function example() {
 let x: string | number | boolean;

 x = Math.random() < 0.5;

 console.log(x);
           // let x: boolean

 if (Math.random() < 0.5) {
   x = "hello";
   console.log(x);
             // let x: string
 } else {
   x = 100;
   console.log(x);
             // let x: number
 }

 return x;
     // let x: string | number
}
      
      





(type predicates)



, . , :







function isFish(pet: Fish | Bird): pet is Fish {
 return (pet as Fish).swim !== undefined
}
      
      





pet is Fish



— . parameterName is Type



, parameterName



.







isFish



, TS



"" , , , .







const pet = getSmallPet()

if (isFish(pet)) {
 pet.swim()
} else {
 pet.fly()
}
      
      





: TS



, pet



Fish



if



, , else



pet



Bird



.







isFish



Fish | Bird



Fish



:







const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
const underWater1: Fish[] = zoo.filter(isFish)
// 
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]

//    ,     
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
 if (pet.name === 'sharkey') return false
 return isFish(pet)
})
      
      





(discriminated unions)



, , . "" , — . , , kind



. Shape



:







interface Shape {
 kind: 'circle' | 'square'
 radius?: number
 sideLength: number
}
      
      





'circle' | 'square'



string



:







function handleShape(shape: Shape) {
 // !
 if (shape.kind === 'rect') {
   // This condition will always return 'false' since the types '"circle" | "square"' and '"rect"' have no overlap.     `false`,   '"circle" | "square"'  '"rect"'  
   // ...
 }
}
      
      





getArea



. :







function getArea(shape: Shape) {
 return Math.PI * shape.radius ** 2;
 // Object is possibly 'undefined'.     'undefined'
}
      
      





strictNullChecks



, radius



. kind



?







function getArea(shape: Shape) {
 if (shape.kind === "circle") {
   return Math.PI * shape.radius ** 2;
   // Object is possibly 'undefined'.
 }
}
      
      





, TS



- , . , , . (!



shape.radius



), , radius



:







function getArea(shape: Shape) {
 if (shape.kind === "circle") {
   return Math.PI * shape.radius! ** 2;
 }
}
      
      





, . , , . , , radius



sideLength



kind



. Shape



:







interface Circle {
 kind: "circle";
 radius: number;
}

interface Square {
 kind: "square";
 sideLength: number;
}

type Shape = Circle | Square;
      
      





Shape



kind



, radius



sideLength



.







, radius



Shape



:







function getArea(shape: Shape) {
 return Math.PI * shape.radius ** 2;
 // Property 'radius' does not exist on type 'Shape'. Property 'radius' does not exist on type 'Square'.
}
      
      





. TS



, shape



Square



, radius



. kind



?







function getArea(shape: Shape) {
 if (shape.kind === "circle") {
   return Math.PI * shape.radius ** 2;
                   // (parameter) shape: Circle
 }
}
      
      





. , TS



.







, kind



( Shape



). shape



. , kind



'circle'



, shape



Circle



.







switch



. getArea



!



:







function getArea(shape: Shape) {
 switch (shape.kind) {
   case "circle":
     return Math.PI * shape.radius ** 2;
                     // (parameter) shape: Circle
   case "square":
     return shape.sideLength ** 2;
           // (parameter) shape: Square
 }
}
      
      





never





, , TS



never



.







(exhaustiveness checking)



never



; , never



( never



). , never



switch



.







, default



getArea



:







function getArea(shape: Shape) {
 switch (shape.kind) {
   case "circle":
     return Math.PI * shape.radius ** 2;
   case "square":
     return shape.sideLength ** 2;
   default:
     const _exhaustiveCheck: never = shape;
     return _exhaustiveCheck;
 }
}
      
      





Shape



:







interface Triangle {
 kind: "triangle";
 sideLength: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape) {
 switch (shape.kind) {
   case "circle":
     return Math.PI * shape.radius ** 2;
   case "square":
     return shape.sideLength ** 2;
   default:
     const _exhaustiveCheck: never = shape;
     // Type 'Triangle' is not assignable to type 'never'.
     return _exhaustiveCheck;
 }
}
      
      








TypeScript.







10% !














All Articles