🏀

Reactの基礎【コンポーネント】

2021/07/07に公開

Reactの基礎【コンポーネント】

React第3弾です。
Reactの中でも最も重要な役割を果たすコンポーネントについて書いていこうと思います。

コンポーネントとは...?

コンポーネント:Reactで画面に表示される部品のことで、表示に必要なデータや処理などを1つのオブジェクトにまとめたものです。画面に表示するための部品を作ることによって、いつでも簡単に組み込むことで部品を再利用することができます。

関数コンポーネント

コンポーネントには、いくつかの書き方がありますが、まずは最もシンプルな関数としてコンポーネントを作成してみましょう。関数コンポーネントは以下のように定義します。

function コンポーネント名( 引数 ) {
    return ...JSXによる表示...;
}

コンポーネントとして関数を定義する場合は、「表示するエレメントをreturnで返す」ことが基本となります。
また、コンポーネントを使用する場合は以下のように、呼び出します。

<コンポーネント名 />

コンポーネントの表示

実際にコードに落として、関数コンポーネントを作成してみましょう。
react_app.htmlを以下のように書きます。(body部のみ記載)

react_app.html
<body>
    <h1>React App</h1>
    <div id="root">Wait...</div>
    <script type="text/babel">
        let dom = document.querySelector("#root");

        const msg = {
            fontSize: "20px",
            fontWeight: "bold",
            padding: "10px",
            color: "red",
            backgroundColor: "darkblue"
        };

        function Welcome(props) {
            return <p style={ msg }>Hello React!</p>;
        }

        let el = (
            <div>
                <Welcome />
            </div>
        );
        ReactDOM.render(el, dom);
    </script>
</body>

画面は以下のように表示されます。

  • <Welcome />がWelcomeという関数コンポーネントを埋め込むタグになります。
  • コンポーネント名は必ず先頭の文字を大文字にして下さい。
    • コンポーネント名の先頭文字が小文字だとコンポーネントとして認識されません。

属性の利用

関数コンポーネントには、引数が1つ用意されています。
先程の例で言うと、propsの部分です。

function Welcome(props) {
    return <p style={ msg }>Hello React!</p>;
}

この引数には、タグの属性をオブジェクトにまとめたものが渡されます。つまり、以下のようにコンポーネントのタグに属性=xxxxという形で指定すれば、関数コンポーネントの引数に渡り、その値を利用して画面表示を行うことができます。

<コンポーネント名 a="xxxx" />

属性へのアクセス方法は、引数.aのように書きます。
実際にコードに落とし込んでみましょう。

react_app.html
<body>
    <h1>React App</h1>
    <div id="root">Wait...</div>
    <script type="text/babel">
        let dom = document.querySelector("#root");

        const msg1 = {
            fontSize: "20px",
            fontWeight: "bold",
            padding: "10px",
            color: "red",
            backgroundColor: "darkblue"
        };

        const msg2 = {
            fontSize: "20px",
            fontWeight: "bold",
            padding: "10px",
            color: "darkblue",
            backgroundColor: "red"
        };

        function Welcome(props) {
            return <p style={ props.style }>Hello { props.name }!</p>;
        }

        let el = (
            <div>
                <Welcome name="Taro" style={ msg1 } />
                <Welcome name="Hanako" style={ msg2 } />
            </div>
        );
        ReactDOM.render(el, dom);
    </script>
</body>

画面は以下のように表示されます。

Welcomeコンポーネントにnameとstyleを指定しています。

<Welcome name="Taro" style={ msg1 } />
<Welcome name="Hanako" style={ msg2 } />

関数の引数であるpropsに対して、props.nameprops.styleでアクセスしています。

function Welcome(props) {
    return <p style={ props.style }>Hello { props.name }!</p>;
}

計算を行う関数コンポーネント

上記のWelcomeコンポーネントは単純にJSXを表示するものでした。今回の例のように計算を行う関数コンポーネントも定義可能です。returnでエレメントを返しさえすれば良いです。

以下のように実装します。(body部のみ記載)

react_app.html
<body>
    <h1>React App</h1>
    <div id="root">Wait...</div>
    <script type="text/babel">
        let dom = document.querySelector("#root");

        const msg = {
            fontSize: "20px",
            fontWeight: "bold",
            padding: "10px",
            color: "red",
            backgroundColor: "darkblue"
        };

        function Calc(props) {
            let sum = 0;
            for (let i=1; i<=props.num; i++) {
                sum += i;
            }
            return <p style={ msg }>1から{ props.num }までの合計は「{ sum }」です。</p>;
        }

        let el = (
            <div>
                <Calc num="10" />
                <Calc num="50" />
                <Calc num="100" />
            </div>
        );
        ReactDOM.render(el, dom);
    </script>
</body>

画面は以下のように表示されます。

クラスコンポーネント

関数コンポーネントの次はクラスコンポーネントを作成していきましょう。
クラスコンポーネントは、名前の通りですが、クラスを利用したコンポーネントのことで、以下のように定義します。

class クラス名 {
    constructor(props) {
        super(props);
	// 初期化処理;
    }
    プロパティ;
    メソッド;
}
  • クラスは「class クラス名 {...}」という形で定義します。
  • メソッドは、functionの記述なしでいきなり定義することができます。
    • メソッド名 (引数) {...}のような形式です。
  • constructorクラスからオブジェクトが作成された時に最初に呼ばれるメソッドです。
    • super(props);は継承元のコンストラクタを呼び出すためのもで、記載するのを忘れないようにして下さい。

クラスの継承

クラスの継承はextendsを用いて以下のように書きます。
クラスを継承することで、既存のクラスの機能を継承したクラスでも再利用することが可能になります。

class クラス名 extends 継承するクラス {
    プロパティ;
    メソッド;
}

Reactにはコンポーネントの機能を一通り備わっているReact.Compenentというクラスが用意されています。自分で何かしらコンポーネントを定義するときは、このクラスを継承して作る必要があります。
コンポーネントのクラスを定義する際に注意点があり、エレメントを返すrender関数を必ず用意して下さい。

class MyClass extends React.Compenent {
    render() {
        return ...JSX...;
    }
}

クラスコンポーネントの作成

実際に、コンポーネントを使用して画面表示をしてみましょう。
以下のように実装します。(body部のみ記載)

react_app.html
<body>
    <h1>React App</h1>
    <div id="root">Wait...</div>
    <script type="text/babel">
        let dom = document.querySelector("#root");

        const msg = {
            fontSize: "20px",
            fontWeight: "bold",
            padding: "10px",
            backgroundColor: "yellow"
        };

        class Hello extends React.Component {
            constructor(props) {
                super(props);
            }

            render() {
                return <p style={ msg }>Sample Class Component!</p>;
            }
        }

        let el = (
            <div>
                <Hello />
            </div>
        );
        ReactDOM.render(el, dom);
    </script>
</body>

画面は以下のように表示されます。

属性を利用したクラスコンポーネント

クラスコンポーネントの基礎がわかってきたところで、属性を利用したクラスコンポーネントを作成してみましょう。関数コンポーネントでやったように、属性は引数propsに渡されていました。クラスコンポーネントの場合も同様です。
以下のように実装します。(body部のみ記載)

react_app.html
<body>
    <h1>React App</h1>
    <div id="root">Wait...</div>
    <script type="text/babel">
        let dom = document.querySelector("#root");

        class Rect extends React.Component {
            x = 0;
            y = 0;
            width = 0;
            height = 0;
            color = "white";
            style = {};

            constructor(props) {
                super(props);
                this.x = props.x;
                this.y = props.y;
                this.width = props.width;
                this.height = props.height;
                this.color = props.color;
                this.style = {
                    backgroundColor: this.color,
                    position: "absolute",
                    left: this.x + "px",
                    top: this.y + "px",
                    width: this.width + "px",
                    height: this.height + "px"
                }
            }

            render() {
                return <div style={ this.style }></div>;
            }
        }

        let el = (
            <div>
                <Rect x="100" y="100" width="100" height="100" color="cyan" />
                <Rect x="150" y="150" width="100" height="100" color="magenta" />
            </div>
        );
        ReactDOM.render(el, dom);
    </script>
</body>

画面は以下のように表示されます。

クラスコンポーネントの呼び出しの際に指定した属性がpropsに渡され、Rectクラスの各プロパティにconstructorで代入されています。

<Rect x="100" y="100" width="100" height="100" color="cyan" />
<Rect x="150" y="150" width="100" height="100" color="magenta" />

render関数で、Rectクラスのstyleプロパティを返すことで画面に指定したスタイルで表示がされます。

render() {
    return <div style={ this.style }></div>;
}

ステートの使用

コンポーネントの基本的な使い方がわかってきましたね。次はコンポーネントの機能の1つであるステートを使用していきます。ステートとは、コンポーネントで使用する値の保管庫のようなものです。前にプロパティやpropsが出てきまいたが、その違いを以下にまとめます。

  • プロパティ
    • クラスに値を持たせるための変数のこと。コンポーネントに限らず、クラス全体で使用できる。
  • props
    • コンポーネントの属性をまとめて保管するためのもの。基本的に「read only」です。
  • ステート
  • コンポーネントの状態を表す値を保管するもの。ステートの値が変わることで、コンポーネントの状態を操作することができます。また、ステートの値を更新するだけで、自動的に画面表示を更新してくれるメリットがあります。

ステートのサンプル

ステートの値を画面に表示してみましょう。ここからは「srcフォルダ」にあるApp.jsを修正していきます。
以下のように記載します。

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

class App extends Component {
    msgStyle = {
        fontSize: "24px",
        color: "red",
        margin: "20px 0px",
        padding: "5px",
        borderBottom: "2px solid red"
    }

    constructor(props) {
        super(props);
        this.state = {
            msg: "Hello State!"
        };
    }

    render() {
        return <div>
            <h1>React</h1>
            <p style={this.msgStyle}>{ this.state.msg }</p>
            <p style={this.msgStyle}>{ this.props.msg }</p>
        </div>;
    }
}

export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
    <App msg="Hello App!" />, document.getElementById("root")
);

画面は以下のように表示されます。

以下の部分でステートに値を格納しています。

this.state = {
    msg: "Hello State!" 
}

ステートの更新

ステートというくらいですので、値が変わっていきます。ステートを更新させるには2通りのやり方があります。

  • this.setState({...値を格納...});
    • 値を用意して設定するだけの時に使用する。
  • this.setState((state) => ({...値を格納...}));
    • 現在のステートの値を利用して新たな値に更新する場合に使用する。

実際にステートが更新されるコードを記載してみましょう。

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

class App extends Component {
    msgStyle = {
        fontSize: "24px",
        color: "red",
        margin: "20px 0px",
        padding: "5px",
        borderBottom: "2px solid red"
    }

    constructor(props) {
        super(props);
        this.state = {
            msg: "Hello State!"
        };

        setInterval(() => {
            this.setState((state) => ({
                msg: state.msg + "!"
            }));
        }, 500);
    }

    render() {
        return <div>
            <h1>React</h1>
            <p style={this.msgStyle}>{ this.state.msg }</p>
            <p style={this.msgStyle}>{ this.props.msg }</p>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

  • setInterval(()=>{実行処理}, 時間間隔):
    • 指定した時間の間隔ごとに処理を実行させます。
    • 今回の例で言うと、0.5sごとに"!"を連結させています。

イベントのバインドとステートによる表示切り替え

次にイベントのバインドとステートの更新で画面表示を切り替えてみましょう。
今回のイベントはクリックイベントとします。
コンポーネントでのイベント設定は2つの作業があります。

  • onClickへの関数設定
    • <button onClick={this.XXXX}>
  • メソッドのバインド
    • this.XXXX = this.XXXX.bind(this);
      • onClickに指定したメソッドにthisをバインドさせ、イベントに割り当てるメソッドから「bind」メソッドを呼び出しすことで、イベントからメソッドが実行できるようになります。
App.js
import React, { Component } from "react";
import './App.css';

class App extends Component {
    msgStyle1 = {
        fontSize: "24px",
        color: "red",
        margin: "20px 0px",
        padding: "5px",
        borderBottom: "2px solid red"
    }

    msgStyle2 = {
        fontSize: "24px",
        color: "blue",
        backgroundColor: "yellow",
        margin: "20px 0px",
        padding: "5px",
        borderBottom: "2px solid red"
    }

    constructor(props) {
        super(props);
        this.state = {
            counter: 0,
            msg: "count start!",
            flg: true
        };

        this.doAction = this.doAction.bind(this);
    }

    doAction() {
        this.setState((state) => ({
            counter: state.counter + 1,
            msg: state.counter,
            flg: !state.flg
        }));
    }

    render() {
        return <div>
            <h1>React</h1>
            {this.state.flg ?
                <p style={ this.msgStyle1 }>count: { this.state.msg }</p>
                :
                <p style={ this.msgStyle2 }>{ this.state.msg }です。</p>
            }
            <button onClick={ this.doAction }>Click</button>
        </div>;
    }
}

export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

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

画面は以下のように表示されます。

以下のメソッドでクリックボタンが押されるたびにカウンターを1増やし、フラグを切り替えています。

doAction() {
    this.setState((state) => ({
        counter: state.counter + 1,
        msg: state.counter,
        flg: !state.flg
    }));
}

三項演算子を用いて、フラグによって、表示スタイルを切り替えています。

{this.state.flg ?
    <p style={ this.msgStyle1 }>count: { this.state.msg }</p>
    :
    <p style={ this.msgStyle2 }>{ this.state.msg }です。</p>
}

プロパティとステートの連携

上記の例のようにステートを利用することで、画面の更新ができました。ステートは非常に便利に感じますが、表示で使用するものを全てステートで保管しておくかというと、そうではありません。プロパティに生データを保管し、それをステートに渡してやるやり方があります。

クリックすると四角形がどんどん増えていくコンポーネントを例に実装してみます。

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

class App extends Component {
    data = []

    msgStyle = {
        fontSize: "24px",
        color: "red",
        margin: "20px 0px",
        padding: "5px",
    }

    area = {
        width: "500px",
        height: "500px",
        border: "1px solid green"
    }

    constructor(props) {
        super(props);
        this.state = {
            list: this.data
        };

        this.doAction = this.doAction.bind(this);
    }

    doAction(e) {
        let x = e.pageX;
        let y = e.pageY;
        this.data.push({ x: x, y: y });
        this.setState({
            list: this.data
        });
    }

    draw(d) {
        let s = {
            position: "absolute",
            left: (d.x - 25) + "px",
            top: (d.y - 25) + "px",
            width: "50px",
            height: "50px",
            backgroundColor: "blue"
        };
        return <div style={ s }></div>
    }

    render() {
        return <div>
            <h1>React</h1>
            <h2 style={ this.msgStyle }>show rect</h2>
            <div style={ this.area } onClick={ this.doAction }>
                { this.data.map((value) => this.draw(value)) }
            </div>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

画面がクリックされたら、辞書型のオブジェクトをthis.dataの配列に追加しています。

doAction(e) {
    let x = e.pageX;
    let y = e.pageY;
    this.data.push({ x: x, y: y });
    this.setState({
        list: this.data
    });
}

this.dataの要素をthis.drawの戻り値である<div style={ s }></div>にmapで変換しています。

<div style={ this.area } onClick={ this.doAction }>
    { this.data.map((value) => this.draw(value)) }
</div>

コンポーネントの様々な機能

最後にコンポーネントの様々な機能を紹介します。

子エレメントの活用

これまでのコンポーネントは基本的に<XXXX />と言う形式で呼び出していました。しかし、以下のように内部に項目を持つことが可能です。

<XXXX>
    ...子エレメント...
</XXXX>

この子エレメントをまとめて取得する方法がthis.props.childrenです。
実際にコードに落としてみましょう。

App.js
import { Component } from "react";
import './App.css';

class App extends Component {
    input = "";

    msgStyle = {
        fontSize: "20px",
        margin: "20px 0px",
        padding: "5px"
    }

    constructor(props) {
        super(props);
        this.state = {
            message: "type your name:"
        };
    }

    render() {
        return <div>
            <h1>React</h1>
            <Message title="Children!">
                これはコンポーネント内のコンテンツです。
                マルでテキストを分割し、リストにして表示します。
                改行は不要です。
            </Message>
        </div>;
    }
}

class Message extends Component {
    li = {
        fontSize: "18px",
        margin: "0px",
        padding: "0px"
    }

    render() {
        let content = this.props.children;
        let arr = content.split("。");
        let arr2 = [];

        for (let i = 0; i < arr.length; i++) {
            if (arr[i].trim() != "") {
                arr2.push(arr[i]);
            }
        }

        let list = arr2.map((value, key) => (
            <li style={ this.li } key={ key }>{ value }</li>
        ));

        return <div>
            <h2>{ this.props.title }</h2>
            <ol>{ list }</ol>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

let content = this.props.children;<Message>の子要素であるこれはコンポーネント内のコンテンツです。マルでテキストを分割し、リストにして表示します。改行は不要です。を取得しています。

<Message title="Children!">
    これはコンポーネント内のコンテンツです。
    マルでテキストを分割し、リストにして表示します。
    改行は不要です。
</Message>

フォームの利用

ユーザと情報のやりとりをする場合は<form>タグを使います。フォームから送信するデータをReactで扱うようにするには、以下のように書きます。

App.js
import { Component } from "react";
import './App.css';

class App extends Component {
    input = "";
    constructor(props) {
        super(props);
        this.state = {
            message: "type your name:"
        };
        this.doChange = this.doChange.bind(this);
        this.doSubmit = this.doSubmit.bind(this);
    }

    doChange(event) {
        this.input = event.target.value;
    }

    doSubmit(event) {
        this.setState({
            message: "Hello, " + this.input + "!"
        });
        event.preventDefault();
    }

    render() {
        return <div>
            <h1>React</h1>
            <h2>{ this.state.message }</h2>
            <form onSubmit={ this.doSubmit }>
                <label>
                    Message:
                    <input type="text" onChange={ this.doChange }/>
                </label>
                <input type="submit" value="click"/>
            </form>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

  • onSubmit<form>で囲まれた内容を送信する時に使用します。
  • preventDefault()は発生したイベントをなくす関数です。

コンテキストの使用

コンテキストとは、全体で共通して使用できる値のことです。
コンテキストは以下のようにして作成します。

const 変数 = React.createContext( 値 );

注意点として、全体で共通して使用するため、クラスの外で定義して下さい。

コンテキストを使用する側では以下のようにアクセスします。
まず、コンポーネントにコンテキストを設定します。

static contextType = 変数;

その後、this.context.XXXXでアクセスできます。

では、実際にコードを書いてコンテキストを使用してみましょう。

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

let data = {
    title: "Title",
    message: "this is sample."
}

const SampleContext = React.createContext(data);

class App extends Component {
    render() {
        return <div>
            <h1>Context</h1>
            <Title />
            <Message />
        </div>;
    }
}

class Title extends Component {
    static contextType = SampleContext;

    render() {
        return <div>
            <h2>{ this.context.title }</h2>
        </div>;
    }
}

class Message extends Component {
    static contextType = SampleContext;

    render() {
        return <div>
            <h2>{ this.context.message }</h2>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

以下で、辞書型のオブジェクトをコンテキストとして設定しています。

let data = {
    title: "Title",
    message: "this is sample."
}

const SampleContext = React.createContext(data);

あとは各クラスコンポーネントにコンテキストを設定した後、this.context.XXXX値を取得しています。

コンテキストの値を変更する

コンテキストは全体で使えるような値のため、コンテキストの値を変更するには少し特種なものを使用します。それはProviderというものです。これは一時的にコンテキストの値を変更します。もっと詳しくいうと、プロバイダーはcreateContextで作成したコンテキストのProviderプロパティとして用意されるコンポーネントです。外側のコンポーネントには影響はなく、内部のコンポーネントのみ値を変更できます。

<コンテキスト.Provider value=値 >
    ...コンポーネント...
</ コンテキスト.Provider>

実際にProviderを使用してみましょう。

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

let data = {
    title: "Title",
    message: "this is sample."
}

const SampleContext = React.createContext(data);

class App extends Component {
    newData = {
        title: "New Title",
        message: " New Message."
    };

    render() {
        return <div>
            <h1>Context</h1>
            <Title />
            <Message />
            <SampleContext.Provider value={ this.newData } >
                <Title />
                <Message />
            </SampleContext.Provider>
            <Title />
            <Message />
        </div>;
    }
}

class Title extends Component {
    static contextType = SampleContext;

    render() {
        return <div>
            <h2>{ this.context.title }</h2>
        </div>;
    }
}

class Message extends Component {
    static contextType = SampleContext;

    render() {
        return <div>
            <h2>{ this.context.message }</h2>
        </div>;
    }
}

export default App;

画面は以下のように表示されます。

Providerを使用した真ん中のコンポーネントのみ指定したnewDataの値が使用されています。

<SampleContext.Provider value={ this.newData } >
    <Title />
    <Message />
</SampleContext.Provider>

コンテキストを利用する簡単な例を示しましたが、実際にはテーマを作る時によくコンテキストが使用されます。例えば、スタイル属性をコンテキストで用意しておき、簡単な操作でテーマを切り替えるようにする場合です。

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

let theme = {
    light: {
        backgroundColor: "#eef",
        color: "#006",
        padding: "10px"
    },
    dark: {
        backgroundColor: "#006",
        color: "#eef",
        padding: "10px"
    },
};

const ThemeContext = React.createContext(theme.light);

class App extends Component {
    static contextType = ThemeContext;

    render() {
        return (
            <div style={this.context}>
                <Title value={"Context Page"} />
                <Message value={"This is Context Theme."} />
                <Message value={"これはテーマのサンプルです。"} />
            </div>
        );
    }
}

class Title extends Component {
    static contextType = ThemeContext;

    render() {
        return <h2 style={ this.context }>{ this.props.value }</h2>;
    }
}

class Message extends Component {
    static contextType = ThemeContext;

    render() {
        return <h2 style={ this.context }>{ this.props.value }</h2>;
    }
}

export default App;

画面は以下のように表示されます。

  • theme.lightの場合
  • theme.darkの場合

themeのオブジェクトを用意して、ThemeContextとして格納しておきます。以下のようにthemeを切り替えると、指定したthemeで画面表示がされます。

  • React.createContext(theme.light);
  • React.createContext(theme.dark);

まとめ

コンポーネントの基礎から応用までまとめたので、かなり長くなっていましました。コンポーネントはReactでもっとも重要な役割を果たしますのでしっかり使えるようになりましょう。プロパティやステート、コンテキストがコンポーネントの主な機能となります。

Discussion