與 TypeScript 共舞 🐉 - 跟我想的不一樣R之真實世界篇

俗話說 OOP 學得好,Coding 沒煩惱。然後 Swift 有 POP,Ruby 有 mixin,還暫且不說 FP ㄏ

From the ancent......the Dragon 🐲......

讓我們從講古開始切入,其實 TypeScript 已經從古早時候就一直存在 (好吧說長不長說短不短的 5 年),只是近期內開始受到歡迎,吸引更多的開發者選擇使用 TypeScript,並且有穩定的商業應用表現 (有興趣的各位可以聽聽看 SoftwareEnginneringDaily 訪問 slack 使用 TypeScript 的經驗分享 - 傳送門)。

Compare with Flow!?

Facebook 毋庸置疑的贏了一場漂亮的戰爭,不管是 React 也好 babel 也行,

interface IntlExtension {
  intl: InjectedIntl
}

function mountWithIntl<C extends IntlExtension>(node: React.ReactElement<C>) {
  const intlProvider = new IntlProvider({ locale: 'zh-TW', messages }, {})
  const { intl } = intlProvider.getChildContext()

  return mount(
    React.cloneElement(node, { intl }) as React.ReactElement<C>,
    {
      context: { intl },
      childContextTypes: { intl: intlShape }
    }
  )
}

https://softwareengineeringdaily.com/?s=typescript

export interface TableProps<Data extends BasicDataDefine> {
  classNamePrefix?: string
  wrapperClassName?: string
  className?: string
  loading?: boolean
  loader?: JSX.Element
  rowSelection?: RowSelectionOption<Data>
  pagination?: PaginationOption
  dataSource: Data[]
  columns: BasicColumnDefine[]
  children?: undefined

  // #TODO: filter or sorting function
  onChange?: (currentPage: number, pageSize: number) => void
}

interface DefaultProps {
  classNamePrefix: string
  wrapperClassName: string
  className: string
  loading: boolean
  loader: JSX.Element
}

type PropsWithDefault = TableProps & DefaultProps

TS2315: Type 'PropsWithDefault' is not generic.

deconstructor

this.selectedRecords = dataSource.filter(data =>
  (data.key ? false : selectedKeys.includes(data.key))
)

TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.
  
this.selectedRecords = dataSource.filter(data =>
  (data.key ? false : selectedKeys.includes(data.key as string))
)
const foo?: string = undefined
console.log(foo as string)

Overload!?

我們期望的 Overload in Swift

class User {
  func register(name: String, gender: Gender, age: Int) {
    ...
  }
  func register(facebookID: String, gender: Gender) {
    ...
  }
  func register(referURL: URL) {
    ...
  }
}

Protocol 版本

protocol User {
  func register(name: String, gender: Gender, age: Int)
  func register(facebookID: String, gender: Gender)
  func register(referURL: URL)
}

哦哦哦!? TypeScript 有型態,所以把前面的邏輯慣例應該可以套用在 TypeScript 吧?

class User {
  register(name: string, gender: Gender, age: number): void {
    ...
  }
  register(facebookID: string, gender: Gender): void {
    ...
  }
  register(referURL: URL): void {
    ...
  }
}

......淦,怎麼只有最後一個 method 被叫到!!看了一下官方的文件...好吧,手動處理囉!

class User {
  register(name: string, gender: Gender, age: number): void;
  register(facebookID: string, gender: Gender): void;
  register(referURL: URL): void;
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number): void {
    ......
  }
}

雖然這樣寫真心非常醜,不過至少可以算是一種往前踏了一個閃身的改進了吧(?)
typescript-overload-for-classes

嗯,那這樣訂個 interface 如果要有 overload 大概也知道要怎麼做了。

interface User {
  register(name: string, gender: Gender, age: number): void;
  register(facebookID: string, gender: Gender): void;
  register(referURL: URL): void;
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number): void;
}

overload-for-interface
WTF!? 怎麼有 4 個 method,做 Overload 處理的只有三個啊?最後一個實作細節怎麼被暴露出來了...
其實在 interface 宣告層想要有 Overload 效果的話,只要宣告需要的 method,而實際上會被呼叫到的 method 在 implement interface 的時候寫上就沒問題了。

interface User {
  register(name: string, gender: Gender, age: number);
  register(facebookID: string, gender: Gender);
  register(referURL: URL);
}
const user: User {
  ...
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number) {
    ......
  }
}
// Or
class Member implements User {
  ...
  register(nameOrFacebookIDOrReferURL: URL | string, gender?: Gender, age?: number) {
    ......
  }
}