73
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React を、サンプルを実装しながら理解する

Last updated at Posted at 2018-01-04

ReactFormApp.gif

画像のようなサンプルアプリを実装してみます。

公式のチュートリアル でも同じように学習できますが、この記事は

  • より単純で分量が少ない
  • 日本語で書かれている
  • 同じアプリを Angular でも実装している
  • Redux 導入の前段階にできる(Redux の記事は後日作成予定)

これらの点が異なっています。1

サンプル実装の手順

まずは Hello world

Create React App を使い、まずは環境をセットアップします。
Create React App を npm (Yarn や npx など任意のツールで構いません)でインストールし、新規プロジェクトを作成、開発サーバーを起動します:

yarn global add create-react-app
create-react-app qiita
cd qiita
yarn start

App.js の中身を以下のように、必要最低限に書き換えます:

src/App.js
import React, { Component } from 'react';

class App extends Component {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}

export default App;

ブラウザーがリロードされ、画面に Hello world が表示されたら成功です。

CodePen で環境設定をスキップする

公式チュートリアルの CodePen を流用し、ブラウザー上で試すこともできます。
その際は、以下のような違いに気をつけます:

  • import/export 文なしにする
  • Component クラスは React.Component として参照する
  • ReactDOM.render() のおまじないを書く
  • ファイル分けはできないので、一か所に全クラスを書く

コードはこのようになります:

class App extends React.Component {
  render() {
    return (
      <h1>Hello world</h1>
    );
  }
}

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

フォームの見た目だけ作成する

<h1>Hello world</h1> の部分をインプット要素に置き換えます:

src/App.js(抜粋)
class App extends Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>SEND</button>
      </div>
    );
  }
}

テキストボックスとボタンが表示されるのを確認したら、これをコンポーネント化し、別のファイルに切り出します2

src/App.js
import React, { Component } from 'react';
import { FormApp } from './FormApp';

class App extends Component {
  render() {
    return (
      <FormApp />
    );
  }
}

export default App;
src/FormApp.js
import React, { Component } from 'react';

export class FormApp extends Component {
  render() {
    return (
      <div>
        <input type="text" />
        <button>SEND</button>
      </div>
    );
  }
}

コンポーネントは、Component クラスを拡張したクラスです。
<FormApp /> のように呼び出すと、render() メソッドが実行され、その戻り値が描画されます。

ブラウザーがリロードされ、同じ見た目となれば成功です。

フォームにイベント検知機能を追加する

まずは、ボタンを押されたことを検知し、コンソールに文字を出力してみます:

src/FormApp.js(抜粋)
export class FormApp extends Component {
  send() {
    console.log('send called!');
  }

  render() {
    return (
      <div>
        <input type="text" />
        <button onClick={this.send.bind(this)}>SEND</button>
      </div>
    );
  }
}

send() メソッドを button 要素の onClick に割り当てています。
{} の中身は JavaScript の式として扱われるので、Angular のように onClick={this.send()} としてしまうと、onClick には send() の戻り値 undefined が割り当てられます。
それでは意味がないので、そうしないよう気をつけます。
また onClick={this.send} としてしまうと、今は問題ありませんが、今後 send() 内で this を使ったときにエラーになる(this が undefined となる)ため、bind() メソッドで this を固定しています。

同様に、input 要素にも onChange ハンドラーを割り当てます:

src/FormApp.js(抜粋)
export class FormApp extends Component {
  handleInput() {
    console.log('handleInput called!');
  }

  send() {
    console.log('send called!');
  }

  render() {
    return (
      <div>
        <input type="text" onChange={this.handleInput.bind(this)} />
        <button onClick={this.send.bind(this)}>SEND</button>
      </div>
    );
  }
}

状態 (state) を持たせ、状態を更新する

constructor を追加し、その中で特殊なプロパティ state を設定します:

src/FormApp.js(抜粋)
export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };
  }

constructor の引数 propssuper(props) の呼び出しは、今はおまじないだと思ってください。
重要なのは this.state = {...} のほうで、ここで valuemessage という状態を初期化しています。

value はテキストボックスの中身、message はボタンを押すと表示されるメッセージを表すために使います:

src/FormApp.js(抜粋)
  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput.bind(this)} />
        <button onClick={this.send.bind(this)}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }

input 要素には value={this.state.value} を追加し、message<div>{this.state.message}</div> という形でボタンの次に追加しました。
初期値を変えると、その値が画面に表示されることが確認できます。

この段階では、テキストボックスに入力することができませんが、それは正常な動きです。
次のように handleInput() を修正することで、テキストボックスへの入力が有効になります:

src/FormApp.js(抜粋)
  handleInput({ target: { value } }) {
    this.setState({
      value
    });
  }

ES6 の文法は見慣れないかもしれませんが、以下のように書いたのと同じです:

src/FormApp.js(抜粋)
  handleInput(event) {
    let value = event.target.value;
    this.setState({
      value: value
    });
  }

React には厳格なルールがあり、その一つがこれです。
setState() メソッド以外に state を変える方法がないという点です。
たとえユーザーの入力であっても、input 要素の value が value={this.state.value} となっている限り、テキストボックスの中身は this.state.value のままで不変です。
Angular は value の変化を自動で this.state.value に反映し、state の中身を変えてくれますが (two-way binding)、React は onChange イベントを呼び出すだけです。
わざわざ setState() を呼び出すのは無駄に見えますが、明示的に書かせることで、コードを読んでデータの流れがすぐわかるという利点を得られるのです。

この FormApp 程度ではその恩恵はありませんが、そういうものだと思って進んでください。
テキストボックスに入力が可能で、ボタンを押すとコンソールにログが出る状態のアプリができていれば、成功です。

ボタンの機能を追加する

最後の機能を追加して、アプリを完成させます。
send() を以下のように修正します:

src/FormApp.js(抜粋)
  send() {
    const { value } = this.state;
    this.setState({
      value: '',
      message: value
    });
  }

value、すなわちテキストボックスを空にし、div の中身に値を移しています。
ボタンを押すとテキストボックスが空になり、そのすぐ下に中身が移るアプリができていれば、成功です。

Refactoring: チュートリアルどおりのコードスタイルに近づける

constructor()render() の中身を修正します:

src/FormApp.js(抜粋)
export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };

    this.handleInput = this.handleInput.bind(this);
    this.send = this.send.bind(this);
  }

  // ...

  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput} />
        <button onClick={this.send}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }
}

render() が呼ばれるたびに bind() メソッドを呼ぶのではなく、constructor 内で呼ぶようにして計算コストを下げました。
この規模のアプリでわざわざそうしたのは、性能を気にしたからではなく、constructor の中身に定型句を揃えたかったからです。
super(props)、state の初期化そしてメソッドの bind という 3 つのおまじないです。

最終的なコード

src/App.js
import React, { Component } from 'react';
import { FormApp } from './FormApp';

class App extends Component {
  render() {
    return (
      <FormApp />
    );
  }
}

export default App;
src/FormApp.js
import React, { Component } from 'react';

export class FormApp extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
      message: ''
    };

    this.handleInput = this.handleInput.bind(this);
    this.send = this.send.bind(this);
  }

  handleInput({ target: { value } }) {
    this.setState({
      value
    });
  }

  send() {
    const { value } = this.state;
    this.setState({
      value: '',
      message: value
    });
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.value} onChange={this.handleInput} />
        <button onClick={this.send}>SEND</button>
        <div>{this.state.message}</div>
      </div>
    );
  }
}

以上でおしまいです。
おつかれさまでした。

Angular で実装した場合

参考として、Angular による実装を載せておきます:

src/app/form-app/form-app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-form-app',
  template: `
    <div>
      <input type="text" [(ngModel)]="value">
      <button (click)="send()">SEND</button>
      <div>{{message}}</div>
    </div>
  `
})
export class FormAppComponent {

  value = '';
  message = '';

  send() {
    const value = this.value;

    this.value = '';
    this.message = value;
  }
}

参考文献

  1. 私も React 初学者なので、当然品質も異なります。みなさまのフィードバックをお待ちしています。

  2. 同じファイルに書いても動作しますが、コンポーネントを再利用することを考え、別ファイルにしています。

73
74
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
73
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?