Edited at

ReactでテールスピンRPGを作るっ!Day#03 ストーリーを表示してみる


これまでのあらすじ

神の啓示を受けて「テールスピンRPG」を作ることを決めたノアnoah_40w.pngが神の御使いロジカlogica_40w.pngの助けを借りて開発に着手!Material-UIを使って楽してデザインを適用したサイトに新たな要素を追加することに・・・

実際のアプリはこちらからプレイ可能! >>> テールスピンRPG『TOLDOT〜Another Noah's Chronicle〜』


改訂履歴


  • 2019.1.12 Material-UI v3系アップデートに即して内容を変更


Cardコンポーネントを使ってストーリーを表示する

「ちょっとページが寂しいので何かしらコンテンツを投入したいなぁ。『テールスピンRPG』の最大の特徴はストーリーを書いたり共有したりできることなので、1件のストーリーを表示してみることはできないかな。ダミーのデータで構わないので。」

「やってみるケロ!

普通にHTML要素をrenderメソッド内に書けば何かしらは表示されるケロが、せっかくMaterial-UIを導入したケロから、使えそうなコンポーネントがあるか探してみるケロ。例えば・・・これなんかどうケロか?」

「『Card』コンポーネント?ヘッダや画像、タイトル、本文からなるカード型のコンポーネントか。1件の記事を表示するにはちょうどいいかもしれないね。

じゃ、ちょっと入れてみるよ。

まずimport文を追加して・・・。」

import {Card, CardHeader, CardContent} from '@material-ui/core';

「renderメソッドを書き変える・・・。」

  render() {

return(
<MuiThemeProvider theme={theme}>
<div>
<AppBar position="static" >
<Toolbar>TOLDOT</Toolbar>
</AppBar>
<Card>
<CardHeader title="ノアの記録" subheader="2017.7.22"/>
<CardContent>
Cardコンポーネントを使って1件のストーリーを表示してみました。
</CardContent>
</Card>
</div>
</MuiThemeProvider>
);
}

「保存・・・と。」

スクリーンショット 2019-01-12 14.05.32.png

「おぉ、なるほど。こうなるのね。マージンやフォントサイズも調整されていい感じ!

タイトルはもうちょっと小さくてもいいかな。」

「細かな調整はおいおいとして、ここでちょっと『状態』について説明するケロ。」

「『状態』って?」

「Reactプログラミングではとっても重要な仕組みケロ!」


this.stateで状態を管理する

「まず、このままだと永遠に同じ情報しか表示されないケロね。状況に応じて違うストーリーを表示させるにはどうすればいいケロか?」

「えーっと、固定の文言が入っている部分を変数に置き換える?」

「その通りケロ。Reactのコンポーネントにおいて『状態によって変化する情報』は『state』というインスタンス変数で管理することになっているケロ。インスタンス変数なのでstateにコンポーネントのメソッド内からアクセスする際はthisをつけて『this.state』となるケロ。」

「・・・えと、『インスタンス変数』って何だっけ?」

「オブジェクト自身が持つ変数で、普通はオブジェクトの性質などを表すデータになるケロ。

一般的には『プロパティ』という呼び方をされるケロが、ここで使うとDOM要素のプロパティと混同してややこしくなるので、ここではインスタンス変数と呼ぶことにするケロ。」

「そ、そうなんだね・・・。で、具体的にはどうやって使うの?」

「まずクラス定義の最初にインスタンス変数としてstateオブジェクトを宣言するケロ。

初期値もセットできるけろ。」

「確か初期化って、コンストラクタを使うんじゃなかったけ?」

「本来はそうケロが、インスンタンス変数の宣言と初期化は、今の場合コンストラクタの外でできるので、そっちの方が楽ケロ。」

「そ、そうなんだね。」

class App extends React.Component {

state = {
storyTitle: 'ストーリーを表示する',
storyDate: '2017.7.22',
storyBody: '状態によって内容が書き換わるように変更してみる。'
};
...
}

「そしたら、renderメソッドの記述を書き換えて、stateとして定義された情報を表示するようにするケロ。」

「えと、こうかな?」

render() {

return(
<MuiThemeProvider theme={theme}>
<div>
<AppBar position="static" >
<Toolbar>TOLDOT</Toolbar>
</AppBar>
<Card>
<CardHeader title={this.state.storyTitle} subheader={this.state.storyDate}/>
<CardContent>
{this.state.storyBody}
</CardContent>
</Card>
</div>
</MuiThemeProvider>
);
}

スクリーンショット 2019-01-12 14.09.09.png

「いいケロね。表示が変わっているのが確認できるケロか?」

「ホントだ。ストーリーの本文が変わったね。」


ボタンで状態を変えてみる

「ボタンを1個追加して、ボタンを押したら状態が変わり、それに伴って表示が更新されることを確認してみるケロ!」

「わかった。ボタンもMaterial-UIのコンポーネントを使えばいいね?」

「わかってきたケロね。ズバリ『Button』コンポーネントを使うケロ。」

「OK。

import Button from '@material-ui/core/Button';

を追加して、renderメソッドにRaisedButtonタグを追加・・・。『variant="contained"』は必要みたいなので入れておく、と・・・」

  render() {

return(
<MuiThemeProvider theme={theme}>
<div>
<AppBar position="static" >
<Toolbar>TOLDOT</Toolbar>
</AppBar>
<Card>
<CardHeader title={this.state.storyTitle} subheader={this.state.storyDate}/>
<CardContent>
{this.state.storyBody}
</CardContent>
</Card>
</div>
<Button variant="contained">状態を変更する</Button>
</MuiThemeProvider>
);
}

スクリーンショット 2019-01-12 14.22.16.png

「出たけど、ちょっと分かりにくいなぁ・・・。」

「ちょっと色と位置を調整してみるケロ。とりあえずButtonコンポーネントのプロパティに『color="secondary"』を追加して、こないだ設定したセカンダリカラーに背景色をセットしてみるケロ。

位置は、グローバル変数として『styles』オブジェクトを用意してスタイルを定義、それをButtonコンポーネントに適用するケロ。」

・・・

const styles = {
stateChangeButton: {
display: 'block',
margin: '10px auto'
},
};

class App extends React.Component {
・・・
render() {
return (
・・・
<Button variant="contained" color="secondary"
       style={styles.stateChangeButton}>状態を変更する</Button>

「どうケロか?」

スクリーンショット 2019-01-12 14.30.02.png

「うん、随分わかりやすくなったよ!押してみたくなるね。

って押してみるね!・・・あれ?何も起こらないけど・・・」

「それはそうケロ。まだイベントハンドラを作ってないケロ。

『ボタンが押された』というイベントに呼応して実行される処理を定義するのがイベントハンドラケロ。こういう感じで定義するケロ。」

class App extends React.Component {

・・・
handleButtonTap = (e) => {
// ボタンが押されたときの処理を記述
};

「・・・これって何?どういう構文なんだろうか・・・。」

「無名関数の定義の省略記法ケロ。こうやって定義された関数は『アロー関数』と呼ばれているケロ。」

「『無名関数』って?」

「まとめて説明するケロ。」

class X {

// 通常関数(メソッド)の定義
method1(a, b) {
return a + b;
}

// 無名関数(メソッド)の定義
method2 = function(a, b) {
return a + b;
};

// アロー関数(メソッド)の定義
method3 = (a, b) => {
return a + b;
}
}

「関数やメソッドの定義の方法はいろいろあるということだね・・・。違いはあるの?」

「基本的には書き方の違いで、動きは基本的に同じケロ。

ただ処理中に『this』を書く時、それが何を指すかがアロー関数とそれ以外とでは変わってくるケロ。

イベントハンドラとして使う場合、アロー関数記法で定義しないと都合が悪い場合が多いケロよ。」

「そうなんだね。とりあえず『イベントハンドラの定義はアロー関数で書く』と覚えておくよ。」

「じゃ、イベントハンドラが用意できたので呼び出す処理を書くケロ。ボタンコンポーネントの『onClick』プロパティの値にイベントハンドラを指定するケロ!」

<Button

variant="contained" color="secondary"
style={styles.stateChangeButton} onClick={this.handleButtonTap}
>状態を変更する</Button>

「これでボタンと処理がつながったケロ。じゃノア『ボタンがタップされたらストーリーの内容を変更する。』処理をイベントハンドラに書いてみるケロ。」

「OK!インスタンス変数のstateの中の、『storyBody』を変更するので、こうなるかな。」

handleButtonTap = (e) => {

this.state.storyBody = '状態が変更されたよ!';
};

「じゃあボタンをクリックしてみるケロ。」

「うん。あれっ?何も変わらないな。ロジカ、イベントハンドラが呼ばれていないんじゃないの?」

「確認用のコードを入れてみるケロ。」

  handleButtonTap = (e) => {

console.log("ボタンが押されました");
this.state.storyBody = '状態が変更されたよ!';
};

「ディベロッパーツールの『Console』タブに『ボタンが押されました』と表示されれば、イベントハンドラは呼ばれていることがわかるケロ。」

「やってみるね。あ、表示されてるね。イベントハンドラは確かに呼ばれているか・・・。」

「ConsoleタブにWarningが表示されているケロ。なんて書かれているケロか?」

「え〜と、『stateの内容を直接変えちゃダメダメ。setState()を使うべし。』かな。」

「その通りケロ。

this.stateに直接値を代入してはいけないんだケロ。エラーにはなんないケロが、そのやり方だと何も起こらないケロ。

this.setState()を使って値を更新することにより、変更された値でコンポーネントのrederメソッドがが呼ばれ、再描画されるケロ。

こう書くケロ。」



handleButtonTap = (e) => {

this.setState({storyBody: '状態が変更されたよ!'});

};



スクリーンショット 2019-01-12 14.55.40.png

「ホントだ、変わった!なるほどこう書くのね。」

「stateを変更するときは『setState()』を使う、これがReactの基本のキの一つケロ。」

「うっかり直接代入しちゃいそうだね。覚えておくよ。」

「じゃついでにsetStateの落とし穴、もう1つ伝えておくケロ。」

「まだあるんだね。」

「まず、ストーリー関連のデータをオブジェクトを使って1つにまとめるケロ。分かりやすくなって冗長さも減るケロ。」

「えと、どうすればいい?」

「こんな感じケロ。」

class App extends React.Component {

state = {
story: {
title: 'ストーリーを表示する',
date: '2017.7.22',
body: '状態によって内容が書き換わるように変更してみる。'
}
};
・・・
render() {
const story = this.state.story;
return(
<MuiThemeProvider theme={theme}>
<div>
<AppBar position="static" >
<Toolbar>TOLDOT</Toolbar>
</AppBar>
<Card>
<CardHeader title={story.title} subheader={story.date}/>
<CardContent>
{story.body}
</CardContent>
</Card>

「ほんとだ。スッキリしたね。」

「ケロ。じゃイベントハンドラの処理を書き変えるケロ。」

「えと、こうかな?」

handleButtonTap = (e) => {

this.setState({story.body: '状態が変更されたよ!'});
};

「文法エラーか・・・こういう書き方はできないんだね・・・。

じゃぁ一旦stateからstoryオブジェクトを持ってきて・・・こうすればいいのかな。」

handleButtonTap = (e) => {

const story = this.state.story;
story.body = '状態が変更されたよ!';
this.setState({story: story});
};

「あ、変わった。成功!」

「おめでとうケロ。」

「あれ?自己ツッコミだけど、『const story』って宣言しているのに、『story.body=』で後から値変更していますが、これはエラーにはならないの?」

「オブジェクトの場合、要素の値が変更されてもオブジェクト自身が入れ替わっているわけではないのでエラーにはならないケロ。確かにそれはそれでいいのかという気もするケロが・・・。

その辺は気にしても仕方がないところなので流すケロ。

今回のまとめケロ!」


src/App.js

import React from 'react';

import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import {Card, CardHeader, CardContent} from '@material-ui/core';
import Button from '@material-ui/core/Button';

const styles = {
stateChangeButton: {
display: 'block',
margin: '10px auto'
},
};

const theme = createMuiTheme({
palette: {
primary: {
main: '#656733', // 深いモスグリーン,
},
secondary: {
main: '#B71C1C', // 暗い赤
},
},
});

class App extends React.Component {
state = {
story: {
title: 'ストーリーを表示する',
date: '2017.7.22',
body: '状態によって内容が書き換わるように変更してみる。'
}
};
handleButtonTap = (e) => {
console.log("ボタンが押されました")
const story = this.state.story;
story.body = '状態が変更されたよ!';
this.setState({story: story});
};
render() {
const story = this.state.story;
return(
<MuiThemeProvider theme={theme}>
<div>
<AppBar position="static" >
<Toolbar>TOLDOT</Toolbar>
</AppBar>
<Card>
<CardHeader title={story.title} subheader={story.date}/>
<CardContent>
{story.body}
</CardContent>
</Card>
</div>
<Button
variant="contained" color="secondary"
style={styles.stateChangeButton} onClick={this.handleButtonTap}
>状態を変更する</Button>
</MuiThemeProvider>
);
}
}

export default App;



次回予告

次回はストーリーデータをデータベースに登録してみるケロ。お楽しみに!


連載記事リンク

Day#01 ReactでテールスピンRPGを作るっ!

Day#02 Material-UIでデザインを適用する

Day#03 ストーリーを表示してみる

Day#04 はじめてのNoSQL

Day#05 REST APIでデータをゲット!