巧用TypeScript

🌀巧用註釋

/** A cool guy. */
interface Person {
  /** A cool name. */
  name: string,
}

通過/** */形式的註釋可以給 TS 類型做標記,編輯器會有更好的提示:

 

注:Don't do this:

 

🌀巧用註釋 進階

註釋有很多規範的字段,基本和 JSDOC 一致。但不用着急翻文檔,在 /** */ 裏輸入 @ 就可以看到豐富的選擇:

 

🌀巧用 typeof

我們一般先寫類型,再使用:

interface Opt {
  timeout: number
}
const defaultOption: Opt = {
  timeout: 500
}

有時候可以反過來:

const defaultOption = {
  timeout: 500
}
type Opt = typeof defaultOption

當一個 interface 總有一個字面量初始值時,可以考慮這種寫法以減少重複代碼。

🌀巧用聯合類型

Dinner 要麼有 fish 要麼有 bear

// 🙁 Not good.
interface Dinner1 {
  fish?: number,
  bear?: number,
}

// 🙂 Awesome!
type Dinner2 = {
  fish: number,
} | {
  bear: number,
}

一些區別:

let d1: Dinner1 = {}  // Opps
d1 = {fish:1, bear:1} // Opps

let d2: Dinner2 = {}  // Protected!
d2 = {fish:1, bear:1} // Protected!

if ('fish' in d2) {
  // `d2` has `fish` and no `bear` here.
} else {
  // `d2` has `bear` and no `fish` here.
}

🌀巧用查找類型

interface Person {
  addr: {
    city: string,
    street: string,
    num: number,
  }
}

當需要使用 addr 的類型時,除了把類型提出來

interface Address {
  city: string,
  street: string,
  num: number,
}
interface Person {
  addr: Address,
}

還可以

Person["addr"] // This is Address.

有些場合後者會讓代碼更整潔、易讀。

🌀巧用查找類型+泛型+keyof

interface API {
  '/user': { name: string },
  '/menu': { foods: Food[] },
}
const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
  return fetch(url).then(res => res.json())
}

上面的定義極大地增強了代碼提示:

 

 

🌀巧用顯式泛型

$('button') 是個 DOM 元素選擇器,可是返回值的類型是運行時才能確定的,除了返回 any ,還可以

function $<T extends HTMLElement>(id: string):T {
  return document.getElementById(id)
}

// Tell me what element it is.
$<HTMLInputElement>('input').value

函數泛型不一定非得自動推導出類型,有時候顯式指定類型就好。

🌀巧用 DeepReadonly

type DeepReadonly<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>;
}

const a = { foo: { bar: 22 } }
const b = a as DeepReadonly<typeof a>
b.foo.bar = 33 // Hey, stop!

🌀巧用 Omit

import { Button, ButtonProps } from './components/button'

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
type BigButtonProps = Omit<ButtonProps, 'size'>

function BigButton(props: BigButtonProps) {
  return Button({ ...props, size: 'big' })
}

高階組件比較常用。

 

業務中,我們經常會寫枚舉和對應的映射:

type AnimalType = 'cat' | 'dog' | 'frog';
const AnimalMap = {
  cat: { name: '貓', icon: '🐱'},
  dog: { name: '狗', icon: '🐶' },
  forg: { name: '蛙', icon: '🐸' },
};

注意到上面 forg 拼錯了嗎?

Record 可以保證映射完整:

type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription { name: string, icon: string }
const AnimalMap: Record<AnimalType, AnimalDescription> = {
  cat: { name: '貓', icon: '🐱'},
  dog: { name: '狗', icon: '🐶' },
  forg: { name: '蛙', icon: '🐸' }, // Hey!
};

還能有代碼提示

如果你喜歡用 enum ,寫法也一樣的

enum AnimalType {
  CAT = 'cat',
  DOG = 'dog',
  FROG = 'frog',
}
const AnimalMap: Record<AnimalType, AnimalDescription>

🌀巧用Partial

就是部分的意思,很常用:

const mergeOptions = (options: Opt, patch: Partial<Opt>) {
  return { ...options, ...patch };
}

class MyComponent extends React.PureComponent<Props> {
  defaultProps: Partial<Props> = {};
}

 

🌀巧用tsx+extends

.tsx 文件裏,泛型可能會被當做 jsx 標籤

const toArray = <T>(element: T) => [element]; // Error in .tsx file.

extends 可破

const toArray = <T extends {}>(element: T) => [element]; // No errors.

🌀巧用ClassOf

有時候,我們要傳入類本身,而不是類的實例

abstract class Animal extends React.PureComponent {
  /* Common methods here. */
}
class Cat extends Animal {}
class Dog extends Animal {}

// `AnimalComponent` must be a class of Animal.
const renderAnimal = (AnimalComponent: Animal) => {
  return <AnimalComponent/>; // WRONG!
}

上面的代碼是錯的,因爲 Animal 是實例類型,不是類本身。應該

interface ClassOf<T> {
  new (...args: any[]): T;
}
const renderAnimal = (AnimalComponent: ClassOf<Animal>) => {
  return <AnimalComponent/>; // Good!
}

renderAnimal(Cat); // Good!
renderAnimal(Dog); // Good!

🌀巧用類型查找+類方法

我們通常會在 React 組件中把方法傳下去

class Parent extends React.PureComponent {
  private updateHeader = (title: string, subTitle: string) => {
    // Do it.
  };
  render() {
    return <Child updateHeader={this.updateHeader}/>;
  }
}

interface ChildProps {
  updateHeader: (title: string, subTitle: string) => void;
}
class Child extends React.PureComponent<ChildProps> {
  private onClick = () => {
    this.props.updateHeader('Hello', 'Typescript');
  };
  render() {
    return <button onClick={this.onClick}>Go</button>;
  }
}

其實可以在 ChildProps 中直接引用類的方法

interface ChildProps {
  updateHeader: Parent['updateHeader'];
}

兩個好處:不用重複寫方法簽名,能從方法調用跳到方法定義 。

傳統的跳轉:

傳統的跳轉

 

神奇的跳轉:

神奇的跳轉

尤其在多層傳遞的場合,從孫子的一個方法調用,跳到爺爺的方法定義,爽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章