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

React-Redux超入門(1)

More than 1 year has passed since last update.

概要

React/Reduxを少しかじったので,自分のメモがてら導入からreact-reduxの導入など,React/Reduxを使ったアプリケーションの雛形の作り方を書いていく.

準備

node.jsのインストール.ここでは以下のバージョンを前提に話を勧めていく

$ node -v
v9.11.1

npmでcreate-react-appのインストール

Reactを使うには.色々なライブラリ(e.g., babel)をインストールしなければならない.これはとても面倒なので,必要なライブラリのインストールや設定,アプリケーションの雛形を作ってくれる"create-react-app"をnpmでインストールする.

>npm install -g create-react-app

なお,-gオプションはグローバルにインストールする.と意味になる.

雛形の作成と実行

create-react-appによって簡単に雛形を作ることができる

>create-react-app hello
>cd hello
>npm start

この3行で."hello"というディレクトリにアプリケーションの雛形を作成し,実行する.npm startを実行すると.勝手にブラウザがhttp://localhost:3000/をアクセスし,Reactの雛形のアプリケーションが見えるようになる.

雛形の解説

helloディレクトリを見ると

>ls
>README.md  node_modules/  package.json  public/  src/  yarn.lock

このようなディレクトリ構成なっており,npm startとしたときに開かれるのはpublic/index.htmlである.
index.htmlを見ると,

<body>
  <noscript>
    You need to enable JavaScript to run this app.
  </noscript>
  <div id="root"></div>
</body>

となっている.

続いて,src/index.jsをみると

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

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

となっていて,document.getElementById('root')という記述が確認できる.
これはつまり,index.html<div id='root'><App/>を描画する,ということを意味している.

そして,描画処理,およびJSX(後述)を利用するため,import ReactDOM from 'react-dom'import React from 'react'でインポートする必要がある.

Reactコンポーネント

先のAppはReactコンポーネントと呼ばれる.Appはsrc/App.jsに定義されている.
もう少し詳しく言うと,AppはReactのクラスコンポーネントである(この他にFunctionコンポーネントがある).

クラスコンポーネント

クラスコンポーネントの例としてAppを取り挙げる.

App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <Hello/>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

クラスコンポーネントは以下の制約を守る必要がある.

  • Componentを継承する
  • render()メソッドを定義する
  • renderでは,Reactコンポーネントを返す.

Appはこの制約に従っている.なお,この例ではrenderは<div>を返しているが,この<div>はHTMLではなくReactが予め用意してあるReactコンポーネントである.この例のように,Reactでは多くのHTMLタグに対応するコンポーネントが予め定義されている.

関数コンポーネント

先ほどのApp.jsに自作コンポーネントとして関数コンポーネントを追加する.

App.js
//(省略)
import './App.css';

const Hello = () => {      // ここから関数コンポーネントの定義
    return <p>Hello</p>;
};                         // ここまで

class App extends Component {
  render() {
    return (
      <div className="App">
       //(省略)
    <Hello/>             // ここでコンポーネントを使う
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

クラスコンポーネントに対して関数コンポーネントはシンプルで,Reactコンポーネントを返すという制約を守れば良い.先のHelloは,<p>というReactコンポーネントを返している.

関数コンポーネントとクラスコンポーネントの使い分けは,stateを持つかどうかによって決めれば良い.

JSX

JSXは,簡単に言うとJavaScriptの中にHTMLのタグ(Reactコンポーネント)を記述できるようにする仕組み.

例えば,

App.js
 return (
      <div className="App">
      ...
      );

はすでにJSXで,returnの返り値にdivタグ(に相当するReactコンポーネント)を返している.
さらに,Reactコンポーネントの中にもJavaScriptを記述することができる.

()
const f = () => {       // 関数の定義
    return "yahoo";
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <h1>{f()}</h1>   // f()の実行と返り値の表示
        <h/>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

この例では,fという関数を定義し,これをJSXの中で呼び出している.
このように,プログラムをJSXの中で実行するためには,{}で囲む.かつ,中に書けるのは式だけである(文はかけない).文を書くにはクロージャ作って実行,のようにしないといけない.

Props

Reactコンポーネントは,外部からデータを受け取る仕組みとしてPropsという仕組みを持っている.

class MyComp extends Component {
    render() {
        return "Hello " + this.props.name ;
    }
}

const MyfComp = (props) => {
    return "Hello " + props.name;
}

この例のように,クラスコンポーネントの場合はthis.props.xxxでアクセスし,関数コンポーネントの場合は引数にpropsを指定してprops.xxxのようにアクセスすればよい.

これを使うには,

<MyComp name="hoge1"/>
<MyfComp name="hoge2"/>

こんな風に書けば良い.

Propsのチェック

ReactにはPropsの型チェックをする機能がある.これを使うことで,実行前に”渡されるべきプロパティが設定されない”,とか,"渡すプロパティの型違い"を検出して実行時エラーを減らすことができる.
そのためには,prop-typesパッケージをインストールする

>npm install --save prop-types

こうした上で,例えば上記のMyCompコンポーネントのnameプロパティをstring型としてチェックしたい場合,以下のように記述する.

()
import PropTypes from 'prop-types';
()
MyComp.propTypes = {
    name: PropTypes.string,
};

こうした上で,例えば

<MyComp name={12}/>

のように書いてみると,JavaScriptのConsoleで以下のようなwarningが出る.

index.js:2178 Warning: Failed prop type: Invalid prop `name` of type `number` supplied to `MyComp`, expected `string`.
    in MyComp (at App.js:34)
    in App (at index.js:7)

なお,name=12と書くことはできない.なぜなら,HTMLの原則に従わなければならないので,属性は””で囲まなくてはならない.そうすると,それらは文字列として扱われるので,例えばnumber型などを使う場合にはJSXの中で{}を使ってJSのコードとして記述する必要がある.

また,isRequiredを指定することで,必須プロパティとすることも可能

MyComp.propTypes = {
    name: PropTypes.string,
    age: PropTypes.number.isRequired,
};

この場合,ageを指定しないとJavaScriptのコンソールにWarningが出る.

ここまでのエラーのないApp.jsのコードは次のようになる.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import PropTypes from 'prop-types';

const Hello = () => {
    return <p>Hello</p>;
};

const f = () => {
    return "yahoo";
}


class MyComp extends Component {
    render() {
        return "Hello " + this.props.name ;
     }
}

const MyfComp = (props) => {
    return "Hello " + props.name;
}

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <h1>{f()}</h1>
        <MyComp name="hoge1"/>
        <MyfComp name="hoge2"/>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

MyComp.propTypes = {
    name: PropTypes.string,
    /*age: PropTypes.number.isRequired,*/
};

export default App;

Propsのデフォルト値

Propsはデフォルト値を与えることができる.

MyComp.defaultProps = {
    name:'abc'
};

これで,指定しない場合はprops.nameは"abc"となる.

イベントハンドラ

Reactコンポーネントはイベントハンドラを設定することができる.Appコンポーネントにbuttonコンポーネントを追加し,onClickイベントを受け取ってみる.

App.js
(省略)
class App extends Component {

  handleClick() {  // イベントハンドラ
    console.log("hello");
  }

  render() {
    return (
      <div className="App">
    (省略)
    <button onClick={this.handleClick}>Register</button> // buttonの定義とハンドラの設定
      </div>
    );
  }
}

まずイベントハンドラhandleClick関数を定義し,buttonコンポーネントのonClickイベントに関連付ける.これでボタンが押されるたびに,コンソールに"hello"と表示されるようになる.

イベントと値の変更

イベントハンドラでイベントを受け取ったら,それに応じて処理をし,画面を更新したいということがある.そこで,Appコンポーネントを次のように変更する

App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import PropTypes from 'prop-types';

const Hello = () => {
    return <p>Hello</p>;
};

const f = () => {
    return "yahoo";
}


class MyComp extends Component {
    render() {
        return "Hello " + this.props.name ;
     }
}

const MyfComp = (props) => {
    return "Hello " + props.name;
}


class App extends Component {
  constructor() {
      super();
      this.ary = [1,2,3,4,5];
      this.handleClick = this.handleClick.bind(this);  // (1)イベントハンドラのコンテキスト設定
  }

  handleClick() {
      this.ary.push(6);                               // (2)イベント発生時にaryに追加
      console.log(this.ary);
      console.log("hello");
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <h1>{f()}</h1>
        {
            (() => {                         // (3)aryの個数に応じて"abc"を画面に表示
                let list = [];
                for (var i = 0; i < this.ary.length; ++i) {
                    list.push(<div key={i}>abc</div>);
                }
                console.log(list);
                return <div>{list}</div>
            })()
        }
        <MyComp name="hoge1"/>
        <MyfComp name="hoge2"/>
        <button onClick={this.handleClick}>Register</button>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

MyComp.propTypes = {
    name: PropTypes.string,
    /*age: PropTypes.number.isRequired,*/
};

export default App;

変更後のコードでは,Appコンポーネントに配列(ary)を追加し,その個数分だけ"abc"という文字列を画面に表示するようにしている.主に重要なのは(1)~(3)になる.
(1)は,イベントハンドラの実行コンテキストを設定している.JavaScriptはfirst class functionなので,関数がオブジェクトになる.そのため,関数を引数で渡したり,返り値として返したりできる.この場合,クラスのメソッドを関数オブジェクトとした時,その関数の内部でthisを使うとき,thisが指すオブジェクトが何なのかを決める必要がある.そのため,Reactではbindメソッドによって実行時のthisを設定する手段を提供している.(1)の場合.handleClickが実行されるときのthis(コンテキスト)をAppコンポーネントに固定している.そのため,handleClickの中で参照するthis.aryは,Appのconstructorで設定するaryになる.

(2)では,handleClickが実行された時,Appのconstructorで設定したaryに要素を追加している.

(3)は,this.aryの個数分だけ<div>abc</div>コンポーネントを画面に表示するため,<div>abc</div>コンポーネントを個数分だけlistに追加し,最後に<div>{list}</div>を返している.JSXの中で記述するJavaScriptは式でなければいけないので,文を書くことはできない.そのため,ここではクロージャを作成し,クロージャを実行してその返り値として<div>{list}</div>を受け取り,画面に表示している.

さて,このコードを実行すると,"abc"という文字が5回表示される.そして,ボタンをクリックするとaryに要素が追加されるので,"abc"は6回表示されることを期待する.しかしそうはならない.なぜならば,aryに6が追加されても,そのことを通知し,クロージャを再実行する必要があるからで,ここではそんな処理をしていないからである(console.logでのthis.aryの出力を見ると6が確かにに追加されていることは確認できる).アプリケーションが複雑になるとこのイベント通知はカオスになる.これに対する解決策を与えるのがReactなのだと個人的には考えている.Reactは後述するステートを利用することで,この更新処理を開発者から隠蔽する.

ステート

Reactのクラスコンポーネントはステートを持ち,ステートが変更されたときに必要なコンポーネントだけが再描画するような仕組みになっている(どうやっているのか今はブラックボックス).先のコードをステートを使うように変更する.

App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import PropTypes from 'prop-types';

const Hello = () => {
    return <p>Hello</p>;
};

const f = () => {
    return "yahoo";
}


class MyComp extends Component {
    render() {
        return "Hello " + this.props.name ;
     }
}

const MyfComp = (props) => {
    return "Hello " + props.name;
}


class App extends Component {
  constructor() {
      super();
      //this.ary = [1,2,3,4,5];    // 削除
      this.state = {               // (1)ステートの生成
          ary: [1,2,3,4,5]
      };
      this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
//    this.ary.push(6);
      const { ary } = this.state;   // (2)ステートからaryの取り出し
      ary.push(6);                  // 値の挿入
      this.setState({ary});         // (2)ステートの更新
      console.log("hello");
  }

  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <h1>{f()}</h1>
        {
            (() => {
                let list = [];
                const {ary} = this.state;               // (3)ステートからaryの取り出し
                for (var i = 0; i < ary.length; ++i) {  // (3) 取り出したaryを使ってオブジェクトを生成
                    list.push(<div key={i}>abc</div>);
                }
                console.log(list);
                return <div>{list}</div>
            })()
        }
        <MyComp name="hoge1"/>
        <MyfComp name="hoge2"/>
        <button onClick={this.handleClick}>Register</button>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

MyComp.propTypes = {
    name: PropTypes.string,
    /*age: PropTypes.number.isRequired,*/
};

export default App;

主な変更点は3つ.
(1)まず,ステートはJavaScriptのオブジェクトで,1つのクラスコンポーネントに1つ存在する.そして,ステートの作成はコンストラクタで行う.ここでは,

this.state = {               
    ary: [1,2,3,4,5]
};

このように,オブジェクトのメンバとしてary,その値に[1,2,3,4,5]を設定してthis.stateにオブジェクトを設定する.これがステートの初期化となる.

(2)ステートから必要なメンバだけを取り出し,メンバに値を追加した後,ステートに再設定している.多くのReactの解説にあるように,ステートの中身に直接値を代入すると,正しく再描画されないことがある.具体的には,以下のように書いてはならない

this.state.ary.push(6);

こう書くと,Reactコンポーネントがステートが変化したかどうか判定できない可能性があるためである.変更を正しく伝えるには,this.setStateメソッドを利用して新しいステートオブジェクトを再設定する.ここでは,取り出したaryを使って,新しいステートオブジェクトを生成し,それを新たなステートとして再設定している.このsetStateメソッドはComponentクラスに定義されている(のだろう).

(3)ステートから取り出したaryを使って<div>abc</div>オブジェクトを必要な数だけ生成している.

以上の変更により,(2)のthis.setStateでステートの変更がReactに通知され,ステートを参照しているオブジェクトに変更が通知されることにより再描画されるようになる.

とりあえず,Reactを使った最低限のアプリケーションができた.が,これはまだアプリケーションとは呼べない代物だし,ソースコードも汚い.よって,ファイルを分割するなどしてもうちょっと意味のあるアプリケーションにしていく.次回に続く..

hiro4669
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