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 を渡さなかった時にエラーが出ます。
redux / react-redux と合わせる場合の型の付け方
Props をあれこれいじったりする上、提供される型が複雑なのでつまづきやすく、注意が必要です。
しかし、先の React のコンポーネントと同じく変更に強くなりますので、型を付けて書くことをおすすめします。
connect
関数が props をつなぎ替える関数と意識しながら書くと理解の助けになります。
先程のSampleComponent
を利用したコンテナとして以下の仕様で実装するとします。
- Redux のストアから itemsState を受け取って
SampleComponent
の Props の items に渡す - Lifecycle の
componentDidMount
でsetItemsAction
の戻り値を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 に渡るので、StateProps
とDispatchProps
を定義して各所に渡してあげます。
これでSampleComponentWithProps
コンポーネントは ReactRedux のProvider
から Props が流れてくるようになります。
まとめ
型を明示することで余計に難しいように見えるかもしれませんが、実際のところ型無しで同じことをするのも大変です。
最初は良くてもあとで修正できなくなりかねません。
型をしっかりつけておく事で、実装を修正した場合に動作確認の前にエラーにすることができるようになります。型を付けた開発は必ず後で役に立つでしょう。