Help us understand the problem. What is going on with this article?

ざっくり React チュートリアル

!!!Caution!!!

(2019/8/15追加)
ここに書いてあるやり方には、今は推奨されていない古い書き方があります(Class Component、propTypesなど)。

近年、推奨されているやり方でまとめたものが見たい場合はこちらを御覧ください。
https://qiita.com/pullphone/items/fdb0f36d8b4e5c0ae893

とりあえず ReactDOM.render()

基本的にルートポイントとなるスクリプト(例:app.js)を用意して、そこからまたルートとなる Component(Container とも呼ばれる)を呼んだり、Redux の Store を作って Provider と合体させたり(というのは次回書く予定)というのがセオリーらしい。

で、ReactDOM.render() を呼ぶことで、呼び出した HTML の決められた箇所に DOM ツリーを出力する、という処理を最後にやれば良さそう。

とりあえず、src/app.js を以下の様な感じで作っておきましょう。

src/app.js
import React from 'react';
import { render } from 'react-dom';

// ReactDOM.render(...) と同じ意味合い
render(
    <div>Hello world!</div>,
    document.getElementById('app')
);

この app.js を browserify なり watchify なりでトランスパイルした結果を、HTML で読み込めば(上記の例では)id="app" と指定されたタグの箇所に render() で指定された JSX の内容が出力されるようになります。

Component を作る

Component とは、React により最終的に出力する JSX を構成する再利用可能な部品のことを指します。用途に応じてすでに用意された Component を利用するか、新たに自分で作ることもできます。

ここでは、実際に簡単な Component を作ってみて、どのように利用することができるかを見てみましょう。

まず、src/components/Root.js というファイルに以下の内容を記述してください。

src/components/Root.js
import React, { Component, PropTypes } from 'react';

export default class Root extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="container">
        <h1>{this.props.title}</h1>
        <div>
          {this.props.children}
        </div>
      </div>
    );
  }
}

// Props のバリデーション
Root.propTypes = {
  // 文字列(必須)
  title: PropTypes.string.isRequired,
  // 呼び出し元の開始タグと終了タグの間にある要素が入る特別な Props
  children: PropTypes.any.isRequired,
};

通常、React Component を作る場合は React.Component を継承したクラスを作ります。そして、render() メソッドで JSX を return します。ここで return される JSX が、他のコンポーネントや ReactDOM.render() から呼び出された際に出力される JSX となります。

作った Component を呼び出すには、その Component を import し、HTML タグのような形で Component のクラス名を指定することで呼び出すことができます。

また、Component は呼び出し元(親コンポーネント)から値を受け取るために Props という仕組みが用意されています。呼び出す Component の attribute に title="xxxx" と記述することにより、Component 側では this.props.title という形で呼び出し元からの値を受け取ることができます。また、この Props は子コンポーネント側からは値を直接変更することができません。

src/app.js を以下のように書き換えてください。

src/app.js
import React from 'react';
import { render } from 'react-dom';

import Root from './components/Root';

render(
    <Root title="react test">
      てすと
    </Root>,
    document.getElementById('app')
);

もちろん、Component から別の Component を呼び出すこともできます。

src/components/Box.js に以下の内容を記述してください。

src/components/Box.js
import React, { Component, PropTypes } from 'react';

export default class Box extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="panel panel-default">
        <div className="panel-body">
          {this.props.children}
        </div>
      </div>
    );
  }
}

Box.propTypes = {
  children: PropTypes.any.isRequired,
};

そして、src/components/Root.js を以下のように書き換えてください。

src/components/Root.js
import React, { Component, PropTypes } from 'react';

import Box from './Box';

export default class Root extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div className="container">
        <h1>{this.props.title}</h1>
        <Box>
          Sample Box
        </Box>
        <div>
          {this.props.children}
        </div>
      </div>
    );
  }
}

Root.propTypes = {
  title: PropTypes.string.isRequired,
  children: PropTypes.any.isRequired,
};

ブラウザ上でボックスコンポーネントっぽいものが表示されましたか?

State

State とは、Component 内部の状態を管理する際に使う仕組みです。

Props が親コンポーネントから値を渡され、かつ不変(Immutable)な値であったのに対し、State は Component 内部だけで値を保持し、変更可能(Mutable)な点(ただし後述する制約あり)に大きな違いがあります。

Root Component を以下のように変更してください。

src/components/Root.js
import React, { Component, PropTypes } from 'react';

import Box from './Box';

export default class Root extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showBox: false,
    };
  }

  handleClick() {
    this.setState({showBox: !this.state.showBox});
  }

  render() {
    const btnName = this.state.showBox ? 'Box非表示' : 'Box表示';
    const boxComponent = this.state.showBox ? (
      <Box>Sample Box</Box>
    ) : null;

    return (
      <div className="container">
        <h1>{this.props.title}</h1>
        {boxComponent}
        {/* onClick イベントが呼び出された時に handleClick() を呼び出す */}
        <button className="btn btn-primary" onClick={() => this.handleClick()}>{btnName}</button>
        <div>
          {this.props.children}
        </div>
      </div>
    );
  }
}

Root.propTypes = {
  title: PropTypes.string.isRequired,
  children: PropTypes.any.isRequired,
};

ブラウザで開くと、ボタンクリックにより Box の表示/非表示が切り替えられるようになったはずです。

なお、State は値を直接変更することができません(つまり this.state 自体は Immutable なものとして扱う必要がある)。そのため、State を変更する際には上記のように setState() を使う必要があります。

Component の Lifecycle

もし Component が他の Component に呼び出されたときに何か処理をしたい、となったときにはどうすればよいのでしょうか?

React では、そういったタイミングで決められた特定のメソッドが呼ばれるようになっています。

なお、ここではよく使われる(であろうと独断で判断した)Lifecycle を紹介するにとどめますが、詳しく知りたいときは公式を参考にすると良いと思われます。

componentWillMount()

componentWillMount() はその Component が呼び出し元の Component により render() の中で出力された際に呼ばれるメソッドです。

ここでは、おもに State の初期化処理などの setState() を伴う処理を行うと良いでしょう。

Box Component を以下のように変更してください。

src/components/Box.js
import React, { Component, PropTypes } from 'react';

export default class Box extends Component {
  constructor(props) {
    super(props);
    this.state = {
      boxTitle: '',
    };
  }

  componentWillMount() {
    this.setState({boxTitle: Number.parseInt(Date.now(), 10)});
  }

  render() {
    return (
      <div className="panel panel-default">
        <div className="panel-heading">{this.state.boxTitle}</div>
        <div className="panel-body">
          {this.props.children}
        </div>
      </div>
    );
  }
}

Box.propTypes = {
  children: PropTypes.any.isRequired,
};

これにより、親コンポーネントで Box Component が呼ばれた時点での Unixtime が入ったタイトルヘッダーが追加されました。

componentDidMount()

componentDidMount() は Component が実際に DOM ツリーとして追加された時点で呼ばれるメソッドです。

ここでは、DOM に関する処理や Ajax リクエスト、setInterval など各種イベントのハンドリングを行うと良いでしょう。

src/components/PostalCodeAjax.js というファイルに以下の内容を記述してください。

src/components/PostalCodeAjax.js
import React, { Component } from 'react';
import Axios from 'axios';

import Box from './Box';

export default class PostalCodeAjax extends Component {
  constructor(props) {
    super(props);
    this.state = {
      postalCode: '106-6113',
      data: {},
    };
  }

  componentDidMount() {
    this.requestAjax();
  }

  requestAjax() {
    Axios.get(`http://api.zipaddress.net/?zipcode=${this.state.postalCode}`).then(
      response => this.setState({data: response.data})
    );
  }

  render() {
    const data = this.state.data;
    const addressData = data.data != undefined ? data.data : {};
    return (
      <Box>
        <input
          type="text"
          value={this.state.postalCode}
          onChange={elm => this.setState({postalCode: elm.target.value})} />
        <button className="btn btn-primary" onClick={() => this.requestAjax()}>検索</button>
        <div>{addressData.pref}</div>
        <div>{addressData.address}</div>
        <div>{addressData.city}</div>
        <div>{addressData.town}</div>
      </Box>
    );
  }
}

上記 Component を Root Component から呼ばれるようにすると、ページ読み込み時に初期 State で Ajax リクエストが行われ、結果が表示されるようになります。

componentWillReceiveProps(nextProps)

componentWillReceiveProps() は、Props が変更される際に呼ばれるメソッドです。

引数として、変更されたあとの Props が渡される(this.props には変更前の Props が入っている)ため、変更前と比較して、特定の条件の時に State を変更する際などに使います。

Box Components を以下のように変更してください。

src/components/Box.js
import React, { Component, PropTypes } from 'react';

export default class Box extends Component {
  constructor(props) {
    super(props);
    this.state = {
      boxTitle: '',
    };
  }

  componentWillMount() {
    this.setState({boxTitle: Number.parseInt(Date.now(), 10)});
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.boxTitleP != nextProps.boxTitleP) {
      if (nextProps.boxTitleP == '') {
        this.setState({boxTitle: Number.parseInt(Date.now(), 10)});
      } else if (nextProps.boxTitleP != this.state.boxTitle) {
        this.setState({boxTitle: nextProps.boxTitleP});
      }
    }
  }

  render() {
    return (
      <div className="panel panel-default">
        <div className="panel-heading">{this.state.boxTitle}</div>
        <div className="panel-body">
          {this.props.children}
        </div>
      </div>
    );
  }
}

Box.propTypes = {
  children: PropTypes.any.isRequired,
  boxTitleP: PropTypes.string,
};

そして、Root Components を以下のように変更してください。

srrc/components/Root.js
import React, { Component, PropTypes } from 'react';

import Box from './Box';
import PostalCodeAjax from './PostalCodeAjax';

export default class Root extends Component {
  constructor(props) {
    super(props);
    this.state = {
      showBox: true,
    };
  }

  handleClick() {
    this.setState({showBox: !this.state.showBox});
  }

  render() {
    const btnName = this.state.showBox ? 'Box非表示' : 'Box表示';
    const boxTitle = this.state.boxTitle;
    const boxComponent = this.state.showBox ? (
      <Box boxTitleP={boxTitle}>Sample Box</Box>
    ) : null;

    return (
      <div className="container">
        <h1>{this.props.title}</h1>
        boxTitle: <input type="text" onChange={elm => this.setState({boxTitle: elm.target.value})} />
        {boxComponent}
        <button className="btn btn-primary" onClick={() => this.handleClick()}>{btnName}</button>
        <div>
          {this.props.children}
        </div>
        <PostalCodeAjax />
      </div>
    );
  }
}

Root.propTypes = {
  title: PropTypes.string.isRequired,
  children: PropTypes.any.isRequired,
};

ブラウザで開くと、テキストボックスの入力内容に応じてボックスのタイトルが変わるようになったでしょうか?

まとめ

  • 新たに React アプリケーションを作る際にはルートポイントとなるスクリプトを用意し、そこで ReactDOM.render() する
    • それをトランスパイルしたものを HTML で読みこめば良い
  • React でアプリケーションを作る際は、部品となる Component を作って、それを組み合わせて…の繰り返し
    • 親コンポーネントから子コンポーネントに値を渡すときは Props を利用する
    • Component 内部で状態を持つときには State を利用する
    • React 側で Component の状態に応じて決められたメソッドを呼んでくれるので、それを適宜利用する

次回予告

Redux と React を使った実装のチュートリアルを書く(予定)。

pullphone
Server-side Engineer at Toydium Inc
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした