React + Reduxで簡単なゲームを作る機会があり、その際に感じたことを自分への教訓の意味も込めて記したいと思います。
PropTypesなんていらないと思っていた
存在は知っていながら、使ってこなかったPropTypes。
必須ではないし、今まで大掛かりなwebアプリを実装したことがなかったからか必要性も感じなかったのですが、
規模が大きくなればなるほどPropsが正しくコンポーネントに渡っているか、という点が担保される、というのはとても重要なことだとわかりました。
実際それでいくらか時間を無駄にする場面が何度かあったので、その中から実例を一つ紹介します。
いつの間にかON/OFFが切り替わらない…
ゲーム中にBGMのON/OFFを切り替えるトグルボタンの実装で、
containers/SoundControllerContainer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { connect } from 'react-redux' import { toggleSound } from '../actions' import SoundController from '../components/SoundController' const mapStateToProps = state => ({ sound: state.soundController.sound }) const mapDispatchToProps = dispatch => ({ onToggleSound: () => { dispatch(toggleSound()) } }) export default connect( mapStateToProps, mapDispatchToProps )(SoundController) |
components/SoundController
1 2 3 4 5 6 7 8 9 10 11 12 |
import React from 'react' const SoundController = ({ sound, onToggleSound }) => ( pug ` div button(onClick=() => onToggleSound()) |音${sound ? 'OFF' : 'ON'} ` ) export default SoundController |
reducers/soundController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { TOGGLE_SOUND } from '../actions' const soundController = ( state = { sound: false, }, action) => { switch(action.type) { case TOGGLE_SOUND: return Object.assign({}, state, { sound: !state.sound }) default: return state } } export default soundController |
上記のような構成にしていました。(不要部分は削除しつつ簡略化しています)
仕組みとしてはbuttonのonClickでstate.soundController.soundを現在の値と逆の真偽値に入れ替えるというものです。
コンポーネントは渡ってきたProps.soundの値によってボタンの表示テキストを「音ON」もしくは「音OFF」に切り替えてレンダーします。
至ってシンプルな仕組みですが、ある日ふと気付くと、BGMは正しくON/OFFが切り替えられるものの、どちらにしてもボタンのテキスト表示が「音OFF」のままになってしまう現象に見舞われました。
Props.soundをconsoleに出力しながら遡っていくと、containersのmapStateToPropsの中で、
state.soundController.soundとすべきところがstate.soundControllerになっており、何かの拍子に書き換えてしまったようでした。
soundプロパティだけ渡すところオブジェクト丸ごと渡っていたので三項演算子でtrueに扱われ、常に「OFF」が選ばれていました。
当然ながら、これに関して何らかのエラーが出ることはありませんし、問題なくレンダーされます。
これを解明するのはたかが数分かもしれませんが、こんなことがチリツモになると馬鹿になりません。
こんなことで時間を無駄にしないためにもほんの数行書き足して、Propsが正しく渡ってきているかチェックすべきだと思い知りました。
具体的なチェック方法
型チェックは今までReact.PropTypesが使用できたのですが、React v15.5から別パッケージになったので別途installが必要です。
1 |
$ npm install --save-dev prop-types |
チェックしたいコンポーネントで
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import React from 'react' + import PropTypes from 'prop-types' const SoundController = ({ sound, onToggleSound }) => ( pug ` div button(onClick=() => onToggleSound()) |音${sound ? 'OFF' : 'ON'} ` ) + SoundController.propTypes = { + sound: PropTypes.bool.isRequired, + onToggleSound: PropTypes.func.isRequired, + } export default SoundController |
上記のように追記することで、Props.soundは必須で、値は真偽値だと定義することができ、
定義した型以外の値が渡ってくると、consoleにエラーを出してくれます。
上述の例だと、真偽値であるべきところにobjectが入ってきているので下記のエラーが出力されます。
1 |
warning.js:35 Warning: Failed prop type: Invalid prop `sound` of type `object` supplied to `SoundController`, expected `boolean`. |
PropTypesはconsoleにエラー出力するだけですが、さらにjestによるtestを追加することで、コンパイルを止めることもできます。
jestでのtestについては別記事を書きたいと思います。