Web の世界の進歩とともに今までの Web の作り方とは異なるやり方で実装することが増えてきました。
今回から、React とともに使うことであとで調整する時に困りにくくなるやり方を連載で紹介します。

    第1回. Storybook をつかう
    第2回. Redux を Typescript から利用する
    第3回. styled-components を使う

第2回目は型の提供などによってコーディングの手助けになる TypeScript についてです。

React, Redux を Typescript から利用する

React を使った開発をするならば TypeScript のような型付きの環境で開発するのがおすすめです。
多くの場合 0 から書き始めると思うので、 最初に TypeScript の環境で揃えてしまうと良いです。
今回は TypeScript のセットアップは飛ばします。ご了承ください。
React を使う場合でも tsconfig.json の compilerOptions に "jsx": "react"を追加する程度で、通常の TypeScript の使い方と大きく違いはありません。

{
  "compilerOptions": {
    ...
    "jsx": "react"
  },
  ...
}

React のコンポーネントを定義する

基本的に細かいコンポーネントは関数の形式で書くと良いです。
JavaScript でコンポーネントを定義した場合は以下のようになります。

import React from 'react'

export default function SampleComponent({ items }) {
  return (
    <div>
      <p>GaprotList</p>
      <ul>{items.map(item => <li key={item}>{item}</li>)}</ul>
    </div>
  )
}

この場合問題になるのは受け取る Props ですね。
Null でもいいのか、型はなんなのか、 JavaScript なのでそのままでは全くわかりません。
コンポーネントを使用する側も JSX なので普通の関数以上にわかりづらいのも曲者。
ちなみに上の書き方の場合、 Props の items を渡さない場合、実行時にエラーになっちゃいます。
Props の型を変更してもエラーにならないのも困りものです。デフォルト値を付けるなどで対処できなくもないのですが、ここは TypeScript の力をかりましょう。

import * as React from 'react'

interface Props {
  items: Array<string>
}

export default function SampleComponent({ items }: Props) {
  return (
    <div>
      <p>GaprotList</p>
      <ul>{items.map(item => <li key={item}>{item}</li>)}</ul>
    </div>
  )
}

違いは interface の宣言と Props の受け取りに型がついている点です。
TypeScript ならば以下のような感じで Props を渡さなかった時にエラーが出ます。

typescript-props-error

redux / react-redux と合わせる場合の型の付け方

Props をあれこれいじったりする上、提供される型が複雑なのでつまづきやすく、注意が必要です。
しかし、先の React のコンポーネントと同じく変更に強くなりますので、型を付けて書くことをおすすめします。
connect関数が props をつなぎ替える関数と意識しながら書くと理解の助けになります。

先程のSampleComponentを利用したコンテナとして以下の仕様で実装するとします。

  • Redux のストアから itemsState を受け取ってSampleComponentの Props の items に渡す
  • Lifecycle のcomponentDidMountsetItemsAction の戻り値をdispatchする
import * as React from 'react'
import { Action, Dispatch } from 'redux'
import { connect } from 'react-redux'

// Redux 全体の State
import { ReduxState } from '../store'
import { setItemsAction } from '../store/items'
import SampleComponent from './component'

// connect するコンポーネントが必要としている Store を受け取る Props の型
interface StoreProps {
  items: Array<string>
}

// connect するコンポーネントが必要としている Dispatch を受け取る Props の型
interface DispatchProps {
  setItems: (items: Array<string>) => void
}

// StoreProps と DispatchProps の両方を満たす Props 型
type Props = StoreProps & DispatchProps

// connect で渡される関数の戻り値が Props に渡される
class SampleComponentWithProps extends React.Component<Props> {
  componentDidMount() {
    this.props.setItems(['Gaprot', 'Container', 'is', 'Here'])
  }

  render() {
    return <SampleComponent items={this.props.items} />
  }
}

// StoreProps と DispatchProps の両方を満たす Props 型について
// SampleComponentWithProps の Props の StoreProps を mapStateToProps で満たし
// DispatchProps を mapDispatchToProps で満たす
export default connect<StoreProps, DispatchProps>(
  (state: ReduxState) => {
    return state.itemsState
  },
  (dispatch: Dispatch<Action>) => {
    return {
      setItems: items => {
        dispatch(setItemsAction(items))
      },
    }
  },
)(SampleComponentWithProps)

ポイントは Props の使い方です。 connectの第一引数mapStateToProps、第二引数mapDispatchToPropsを関数で渡します。
この時のそれぞれの関数の戻り値が内側のコンポーネントの Props に渡るので、StatePropsDispatchPropsを定義して各所に渡してあげます。
これでSampleComponentWithPropsコンポーネントは ReactRedux のProviderから Props が流れてくるようになります。

まとめ

型を明示することで余計に難しいように見えるかもしれませんが、実際のところ型無しで同じことをするのも大変です。
最初は良くてもあとで修正できなくなりかねません。
型をしっかりつけておく事で、実装を修正した場合に動作確認の前にエラーにすることができるようになります。型を付けた開発は必ず後で役に立つでしょう。