# type predicate

{% tabs %}
{% tab title="📘 手冊" %}

* [Narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html)
* [this-based type guards](https://www.typescriptlang.org/docs/handbook/2/classes.html#this-based-type-guards) (**`this is «T»`**)
  {% endtab %}

{% tab title="📗 參考" %}

* 👉 [discriminated union](https://lochiwei.gitbook.io/web/appendix/typescript/type/union/discriminated-union)
  {% endtab %}
  {% endtabs %}

{% tabs %}
{% tab title="«param» is «T»" %}

```typescript
// ⭐️ type predicate: «param» is «T»
//    «param» : parameter name of current function
//    «T»     : a type
//                                ╭ type predicate ╮ ⭐️
function isFish(pet: Fish | Bird):    pet is Fish    {
  return (pet as Fish).swim !== undefined;
}

// both calls to 'swim' and 'fly' are now okay.
let pet = getPet();
 
if (isFish(pet)) pet.swim();
else pet.fly();
```

{% endtab %}

{% tab title="this is «T»" %}

```typescript
class FileSystemObject {

  // this-based type guard
  //       ╭ this-is type guard ╮ ⭐️
  isFile():    this is FileRep   {
    return this instanceof FileRep;
  }
  
  isDirectory(): this is Directory {
    return this instanceof Directory;
  }
  
  isNetworked(): this is Networked & this {
    return this.networked;
  }
  
  constructor(public path: string, private networked: boolean) {}
}
 
class FileRep extends FileSystemObject {
  constructor(path: string, public content: string) {
    super(path, false);
  }
}
 
class Directory extends FileSystemObject {
  children: FileSystemObject[];
}
 
interface Networked {
  host: string;
}
 
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
  fso.content;
  
const fso: FileRep
} else if (fso.isDirectory()) {
  fso.children;
  
const fso: Directory
} else if (fso.isNetworked()) {
  fso.host;
  
const fso: Networked & FileSystemObject
}
```

{% endtab %}
{% endtabs %}
