React入門(簡単計算アプリを作ってみよう)

React

こんにちは。ECF Techブログ
担当 Michiharu.Tです。

久々の更新となってしまいました。。。。。反省。
ということで、今回はReactの勉強で簡単な計算アプリを作る試みです。Reactの超初心者向けということで興味ありましたらご覧ください。後半すこし複雑になりますが、ハンズオン資料として使えると思います。

対象読者

  • HTMLやJavaScriptなどでのプログラム経験がある方
  • Reactは初めてという方
  • node.jsなどの雰囲気は知っている方
    ということで、よろしくお願いします。

本記事はWindows10での動作した内容を記録しています。
まずは新規アプリケーションの構築です。nodeおよびnpmはインストール済みとします。

>node -v
v10.19.0

>npm -v
6.13.4

任意のフォルダで次のコマンドを実行します。

>npx create-react-app my-app

しばらく待ちます。終わったら、フォルダへ移動します。

>cd my-app

srcフォルダ内には色々とファイルが作成されています。すべて削除してしまいましょう。

HTMLファイルの作成

srcフォルダ内にindex.htmlを作成し、次の内容を記述しておきます。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>はじめてのReact</title>
</head>
<body>
    <div id="root"></div>    
</body>
</html>

rootというIDを持った空divが作成されています。

JavaScriptファイルの作成

次にsrcフォルダ内にindex.jsというファイルを新規に作成します。次の3行を追加しておきます。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

CSSファイルの作成

同じくsrcフォルダ内にindex.cssのファイルを作成しておきます。内容は空で構いません。

最初のプログラム

では、ReactでHello Worldをしてみましょう。index.jsに次のコードを追加します。

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);

追加したら、コマンドプロンプトで下記のコマンドを実行します。

npm run start

次の画面がブラウザで表示されたら成功です。

JSファイルにHTMLタグが直接記述されていることに驚かされます。これはJSXと呼ばれるJavaScriptの拡張表記法です。

足し算プログラムの画面

では、つぎに簡単に画面を作ることにします。index.jsを次のように書き換えます。

const element = (
  <div>
    <input type="text" /> + <input type="text" /> = <span>0</span>
  </div>
);
ReactDOM.render(element, document.getElementById('root'));

再度ブラウザを確認すると次のように表示されています。

とてもシンプルですが、今回はこの計算機能が動く仕組みをつくっていきたいと思います。

コンポーネントにする

とりあえず画面はできましたが、この状態ではまだ普通のHTMLの状態です。これらをReactのコンポーネントにしていきたいと思います。

コンポーネントとはReactを構成する部品のようなものです。コンポーネントを作ることで、要素同士が相互作用し、リアルタイムで動くアプリを作りやすくなります。index.jsを次のように変更してみましょう。

//この上にimport文

//コンポーネント
function ValueInput(props) {
  return <input type="text" value={ props.value } />
}

const element = (
  <div>
    <ValueInput value="5" /> + <ValueInput value="8" /> = <span>0</span>
  </div>
);
ReactDOM.render(element, document.getElementById('root'));

コンポーネントを作成する最も簡単な方法は関数を作成することです。ここではValueInput関数を作成しました。引数のpropsは、タグのプロパティを保持するものです。

コンポーネントを作成すると、10行目のようにタグとして使う事ができます。関数名がタグ名として使えます。同タグ内のプロパティの値が、コンポーネント関数の引数として渡されます。また、JSXの表記内ではJavaScriptの変数は{}で囲んで表現します。

関数側ではprops.valueで利用できますので、テキストボックス内の値として設定しています。

再度ブラウザを確認すると次のようになっています。

タグのvalueプロパティの値が反映されています。

答えの部分もコンポーネントにしてしまいましょう。関数を追加し、element変数の部分を変更しています。

//import文および
//テキストボックスのコンポーネント
//(省略)

//答えのコンポーネント
function ValueAnswer(props) {
  return <span>{props.value}</span>
}

const element = (
  <div>
    <ValueInput value="5" /> + <ValueInput value="8" /> = <ValueAnswer value="0" />
  </div>
);

見た目は先ほどと同じになります。

ここで少しReactについての説明です。Reactのコンポーネントにおいてpropsは、値を引き渡すだけのためのものです。例えば上記でprops.valueの値を直接変更してはいけません。

コンポーネントをクラスで表現する

次は、コンポーネントに変更可能な値を保持できるしくみを作りましょう。そのためには関数で表現したコンポーネントをクラスでの表現に変更する必要があります。

クラスにする基本的な手順は次のとおりです。

  • React.Componentを継承し、関数名と同じ名前のクラスを作成する。
  • render()というメソッドを定義する
  • renderメソッドの中に、関数のreturn値に使っていたHTML表現をそのまま持ってくる。
  • propsは、this.propsに変更する

上の手順で、ValueInputとAnswerInputをそれぞれクラスに変更すると次のようになります。

//テキストボックスコンポーネント
class ValueInput extends React.Component{
  render() {
    return <input type="text" value={this.props.value} />
  }  
}

//答えのコンポーネント
class ValueAnswer extends React.Component{
  render() {
    return <span>{this.props.value}</span>
  }
}

再度ブラウザ上で確認して、見た目が変わっていなければOKです。

コンポーネントに状態を持たせる

今回のアプリの目標は、テキストボックスに入っっている値が変わったら、即座に答えが変わる動きをさせることです。これを実現させるために必要になるのが、ステート(state)です。コンポーネントを状態を保持するものです。

では、stateを追加していきましょう。テキストボックスの値を読み取って、計算結果として常に値が変化するのは、ValueAnswerの値です。こちらだけを修正します。修正後のプログラムです。

//答えのコンポーネント
class ValueAnswer extends React.Component{
  constructor(props) {
    super(props)
    this.state = {
      value: 0
    }
  }
  render() {
    return <span>{this.state.value}</span>
  }
}

修正点は下のとおりです。

  • this.props.valueからthis.state.valueに変更しました。
  • コンストラクタを追加しました。コンストラクタ内でstateプロパティに、valueという値を持ったオブジェクトが設定されています。valueは初期値として0を設定しています。

クラスとなるコンポーネントのコンストラクタはpropsを引数とし、superを使ってスーパークラスのコンストラクタに引き渡すことになっていますので、そのとおりに書きます。

ブラウザで再確認しても変更はありませんが、「0」はthis.state.valueに代入した値が反映されています。

stateを変更する

ここでstateが変わる仕組みを実現してみましょう。ValueAnswerクラスを次のように修正します。

//答えのコンポーネント
class ValueAnswer extends React.Component{
  constructor(props) {
    super(props)
    this.state = {
      value: 0
    }
  }
  render() {
    return <span onClick={() => this.countUp()}>{this.state.value}</span>
  }
  countUp() {
    let next = this.state.value + 1
    this.setState({value:next})
  }
}

修正点は次です。

  • countUpメソッドを追加
  • spanタグにonClickとその呼び出しを追加

countUpメソッドは、stateのもつvalue値に1加算し、stateに再設定します。ただし、Reactコンポーネントのstateは、setStateメソッドを使って更新する必要があります。したがって、1加算したnextを新しいvalueプロパティの値として渡します。

spanタグ内ではonClickプロパティにcountUpメソッド呼び出しを設定しています。ブラウザで再表示し、0の部分をクリックすると値が1ずつ変化していきます。

stateの値がクリックの度に変更、反映されていることがわかります。コンポーネント内の原理としては、setStateメソッドを呼び出し、stateの値が変更されると、そのコンポーネントのrenderメソッドが再実行されます。それにより画面の数値が変化しているというわけです。

stateを親コンポーネントへ移行する

最後は、この部分がテキストボックスの2つの値の変更と共に変化すればOKです。つまり、2つのValueInputコンポーネントとValueAnswerコンポーネントが連動しなければいけません。そのためには次のように、テキストボックスと答えの欄を管理するためのコンポーネントを作り、それぞれの値を状態(state)として持たせるしくみを整えます。

このように、子コンポーネントのstate値を親コンポーネントのstateに移行する考え方を、Reactではリフトアップと言っています。これを実現していきます。

ここから少々複雑になりますので、まずはソースコード全体を掲示します。

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

//この上にimport文
//テキストボックスコンポーネント
class ValueInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this)
  }

  handleChange(e) {
    this.props.valueChange(e.target.value)
  }

  render() {
    const newValue = this.props.value
    return <input type="text" value={newValue} onChange={this.handleChange} />
  }
}

//答えのコンポーネント
class ValueAnswer extends React.Component {
  render() {
    return <span>{this.props.value}</span>
  }
}

//計算機コンポーネント
class Calculator extends React.Component {
  constructor(props) {
    super(props)
    this.changeValue1 = this.changeValue1.bind(this)
    this.changeValue2 = this.changeValue2.bind(this)
    this.state = {
      value1: 0,
      value2: 0,
      answer: 0
    }
  }

  //左テキストボックス用変更メソッド
  changeValue1(newValue) {
    let ans = parseInt(newValue) + parseInt(this.state.value2)
    this.setState({
      value1: newValue,
      answer: ans
    })
  }

  //右テキストボックス用変更メソッド
  changeValue2(newValue) {
    let ans = parseInt(newValue) + parseInt(this.state.value1)
    this.setState({
      value2: newValue,
      answer: ans
    })
  }

  render() {
    const newValue1 = this.state.value1
    const newValue2 = this.state.value2
    return (
      <div>
        <ValueInput value={newValue1} valueChange={this.changeValue1} /> +
        <ValueInput value={newValue2} valueChange={this.changeValue2} /> =
        <ValueAnswer value={this.state.answer} />
      </div>
    )
  }
}

ReactDOM.render(<Calculator />, document.getElementById('root'));

左側のテキストボックス内の数値が変更されたことを想定して、動作を追って説明します。

テキストボックスの値が変更されると、19行目にonChangeプロパティとして登録している関数が実行されます(下記)。

return <input type="text" value={newValue} onChange={this.handleChange}

this.handleChangeとなっています。this.となっているので、同クラス内の13行目から定義されている下記が実行されます。

handleChange(e) {
    this.props.valueChange(e.target.value)
}

上記のthis.props.valueChangeとは何でしょうか。this.propsは本ValueInputにプロパティとして渡されたものですね。つまり、66行目(下記)のvalueChangeプロパティに指定された関数です。

<ValueInput value={newValue1} valueChange={this.changeValue1} />

valueChangeには、this.changeValue1が設定されていますので、13行目(下記)のvalueChange呼び出しは、

handleChange(e) {
    this.props.valueChange(e.target.value)
}

44行目からのchangeValue1関数(下記)を呼び出していることになります。なお、上記のe.target.valueはテキストボックスに新たに入力された値です。

changeValue1(newValue) {
  let ans = parseInt(newValue) + parseInt(this.state.value2)
  this.setState({
    value1: newValue,
    answer: ans
  })
}

この関数では新たに渡された値と現在の右側テキストボックスの値(this.state.value2)を加算して答えをもとめ、
setStateメソッドを使って、左側テキストボックスの値(value1)と右端の答欄(answer)を更新しています。setStateによりコンポーネントのstateの更新が入ると、再度61行目以降のrender関数(下記)が実行されるため、

render() {
  const newValue1 = this.state.value1
  const newValue2 = this.state.value2
  return (
    <div>
      <ValueInput value={newValue1} valueChange={this.changeValue1} /> +
      <ValueInput value={newValue2} valueChange={this.changeValue2} /> =
      <ValueAnswer value={this.state.answer} />
    </div>
  )
}

表示部分全体が更新されることになり、テキストボックスを変更すると加算結果の部分も同時に変化するというわけです。

右側のテキストボックス更新の際も同様の動作になります。呼び出す関数がchangeValue2になっているだけです。

また、各関数がコンストラクタ内でbind呼び出し付きで呼び出されて再代入されているのは、同関数内のthisがいつでも自身のインスタンスを指すようにするためです。

かなり複雑ですが、これがスタンダードな方法になります。初心者向けと言いつつ若干難しくなってしまいました。

なぜ、このような方法となるのかもう少し説明してみたいと思います。ポイントとなるのが次の3つです。

  • コンポーネントのpropsは参照専用(変更不可)
  • コンポーネントのstateはsetStateでしか変更できない
  • 親コンポーネントのstateが子コンポーネントのプロパティの値となっている。

このことから、テキストボックスの値1つ変えるにも、次のような手順をたどります。

  • テキストボックスの新しい値を親コンポーネントに伝える
  • 親コンポーネントがsetStateを使ってstateの値を変える。
  • 新しいstateの値で、子コンポーネントに再表示を依頼する。

というわけで、これで目的の簡単計算アプリが完成しました。

おわりに

本日は以上となります。最後までご覧くださりありがとうございます。初心者向けと言いながら後半若干難しくなってしまいましたが、この辺りはReactの基本となりそうです。コンポーネントによる上手な部品化ができるように頑張っていきたいと思います。引き続きTypeScriptなどを使ったReactネタも作成出来たらと思います。ぜひまたご覧ください。


合同会社イー・シー・エフでは、子ども向けプログラミングなどの教育講座を実施しています。プログラミング教室の案内や教育教材の情報、また関連するご相談・問い合わせにつきましては下記よりご確認ください。

ECFエデュケーション
タイトルとURLをコピーしました