ユーザー操作のイベントを使って状態を更新する
それでは、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の方法を覚えておくとよいでしょう。
