LoginSignup
16
8

More than 5 years have passed since last update.

TypeScriptで型パラメータを省略して型推論に頼る

Last updated at Posted at 2017-06-07

TypeScriptをしばらく触って、「あれ、もっと型推論に頼っていいじゃん!」と今更判ってきたのでまとめる。

今回はreselectを例に挙げる

reselectについて

reselectにおいてcreateSelectorの型定義は下記のような具合で列挙されている

// 本当はもうちょっと複雑だが、説明用に簡略化している
export function createSelector<S, R1, R2, T>(
  selector1: (state: S) => R1,
  selector2: (state: S) => R2,
  combiner: (res: R1, res: R2) => T,
): (state: S) => T

つまり、selector1, selector2でそれぞれ計算された結果を、combinerでまとめて、<T>型を返す、という具合に定義されている。

型パラメータを明示する(型推論しない)場合

reslectを利用するときに型パラメータを指定する場合、下記のような具合になる

import { createSelector } from "reselect"

interface Item{
  price: number
}
interface State {
  items: Item[]
}

// それぞれ
// <元のstateの型, 最初の関数の返り値, 最後の返り値>
const itemSumPrice = createSelector<State, number[], number>( 
  (state) => state.items.map( item => item.price),
  (prices) => {
    return prices.reduce( (a, b) => a + b, 0)
  }
)

これはかなり保守がしんどい。ちょっと拡張しようとして、例えば「消費税を計算して、単位もつける」となるとこんな具合になる。

interface State {
  items: Item[]
  tax: number
  unit: string
}

// <Stateの型、最初の関数の返り値,二番目の関数の返り値.... 最終的な返り値>
const itemSumPrice = createSelector<State, number[], string, number,string>( 
  (state) => state.items.map( item => item.price),
  (state) => state.unit,
  (state) => state.tax,
  (prices, unit, tax) => {
    return `${prices.reduce( (a, b) => a + b, 0)  * tax} ${unit}`
  }
)

型が多くなってだいぶ読み辛い。脳内スタックが追いつかない

型推論に頼る

ということで、型推論に頼る。createSelectorの型パラメータをあえて省略する。

const itemSumPrice = createSelector( 
  (state : State) => state.items.map( item => item.price),
  // pricesはnumber[]型として推論されてくれる
  (prices) : number => {
    return prices.reduce( (a, b) => a + b, 0)
  }
)

Stateを明示することで、その後のpricesの型が推論される。

Stateの型をつけるのがしんどい場合は、あまり褒められ無いやり方だが、anyにしてしまって返り値を指定することである程度推論してくれる

const itemSumPrice = createSelector( 
  (state: any) : number[] => state.items.map( item => item.price),
  // pricesがnumber[]として推論されてくれる
  (prices) : number => {
    return prices.reduce( (a, b) => a + b, 0)
  }
)

この方法を使えば、複雑化しても見通しがそこそこ良くなる

const itemSumPrice = createSelector( 
  (state: State): number[] => state.items.map( item => item.price),
  (state: State): string => state.unit,
  (state: State): number => state.tax,
  // prices, unit, taxがそれぞれ推論される。
  (prices, unit, tax): string => {
    return `${prices.reduce( (a, b) => a + b, 0)  * tax} ${unit}`
  }
)

また、下記のような矛盾するselectorを書いてもちゃんと怒ってくれる

const invalidCreator= createSelector( 
  (state: number): number => state,
  (state: string): string => state,
  (item1, item2): string => {
    return `${item1} ${item2}`
  }
)
// =>
// The type argument for type parameter 'S' cannot be // inferred from the usage. Consider specifying the type arguments explicitly.

react-reduxconnectでも似たような感じで推論させたほうが良かったりするので、どんどん推論に頼っていきたい

16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8