ユーザー操作のイベントを使って状態を更新する
それでは、state
の具体的な使い方について例を挙げていきましょう。まずはユーザー操作を受け付けて状態を変更する方式をサンプルにします。単純なほうがよいので、ボタンを押すと数字が増減するカウンターアプリにしてみましょう(図2)。
ソースコードはリスト6のとおりです。
import React from 'react'; import { StyleSheet, Text, View, Button } from 'react-native'; export default class App extends React.Component { constructor(props) { super(props); this.state = { count: 0, } } onPressMinusOne = () => { this.setState({ count: this.state.count - 1 }); // (2) }; onPressPlusOne = () => { this.setState({ count: this.state.count + 1 }); // (3) } render() { return ( <View style={styles.container}> <Button title="-1" onPress={this.onPressMinusOne} />{/* (1) */} <Text>{this.state.count}</Text> <Button title="+1" onPress={this.onPressPlusOne} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', } });
React Native標準のButtonコンポーネントを使ってみました。React Nativeはモバイルアプリ開発のツールである都合上、画面はクリックするものではなくタップするものという前提があります。そのため、タップイベントを受け取るためのpropsの名前はonclick
やonClick
ではなく、onPress
になっています。
onPress
は関数オブジェクトを受け取るpropsです。(1)のように、関数オブジェクトを登録しておくと、タップイベントが発生したときにその関数が実行されます。今回の例ではクラスのプロパティであるonPressMinusOne
やonPressPlusOne
を実行するようにしてみました。その内部では(2)や(3)のようにsetState
を呼び出しており、ボタン操作によってstate
を更新する流れが作られています。
関数を扱うpropsの注意点
リスト6では、クラス内の関数の定義とJSXの組み合わせが、リスト7のようになっていました。
// 関数定義 onPressMinusOne = () => { this.setState({ count: this.state.count - 1 }); }; // JSX <Button title="-1" onPress={this.onPressMinusOne} />
この関数定義は、ECMAScriptのクラスにおけるメソッド定義の文法ではなく、プロパティにラムダ記法で記述された関数を代入する式です。本来は、リスト8のような書き方をしたほうが、クラスの使い方として直感的です。
// 関数定義 onPressMinusOne() { // (1) this.setState({ count: this.state.count - 1 }); // (3) }; // JSX <Button title="-1" onPress={this.onPressMinusOne} /> // (2)
では、なぜリスト7のような面倒なことをしているのかというと、リスト8の書き方は意図しない動作をする可能性があるからです。
実は、(1)のようにクラス内に定義されたメソッドであっても、その中で使われるthis
の参照先は実行方法によって変わってしまうのです。(2)のように関数オブジェクトをそのまま渡すと、実行時に(3)がthis
として参照するのはButton
コンポーネントになります。そのため、リスト8の形で実装したアプリを起動して、マイナスボタンを押すと、図3のようなエラーが発生します。
図3上部の赤いところを参考にして状況を読み取ってみると、Buttonコンポーネントの中にthis.state
が存在しない、つまりundefinedなので、その中のcountを参照しようとした時点でエラーになる、ということです。これはReactの仕組み上そうなっているわけではなく、ECMAScriptのクラス記法の標準的な仕組みで起こる課題です。
どうしてもメソッド記法を使いたい場合は、リスト9のように記述することができます。
// コンストラクタ constructor(props) { super(props); this.onPressMinusOne = this.onPressMinusOne.bind(this); // (1) } // 関数定義 onPressMinusOne() { this.setState({ count: this.state.count - 1 }); }; // JSX <Button title="-1" onPress={this.onPressMinusOne} />
(1)のように事前にthis
をバインドしておくことで、this
をApp
にひも付けておくことができます。しかし面倒なので、ひとまずリスト7の方法を覚えておくとよいでしょう。