React.js

【React.js】チュートリアルのJSXからStateまで理解をまとめる

こんにちは、ともです。

現在Reactチュートリアルを見ながら勉強中です。

ReactチュートリアルのMAIN CONCEPTの章をやっているんですが、大変分かりやすいですね。

そこで学んだことを(自分が忘れてしまいそうなこと)をメモしておきます。

コンセプト

Reactのチュートリアルには次のように記載されています。

マークアップとロジックを別々のファイルに書いて人為的に技術を分離するのではなく、React はマークアップとロジックを両方含む疎結合の「コンポーネント」という単位を用いて関心を分離します。

Reactのコンポーネントを書いた時に思ったのは

JavaScriptの中にHTMLがある!

って感想です。

次のチュートリアルで作成するClockコンポーネントを見てください。

renderメソッドでマークアップ(実際はJavaScriptだが)を記述し、それ以外にロジックを記述しており、コンポーネント内に両方(マークアップとロジック)を含んでいますが、疎結合となっています。

class Clock extends React.Component {
            constructor(props) {
                super(props)
                this.state = {date: new Date()}
            }

            // JSXで書かれたマークアップぽいもの
            render() {
                return (
                    <div>
                        <h1>Hello World</h1>
                        <h2>It is {this.state.date.toLocaleString()}.</h2>
                    </div>
                );
            }

            componentDidMount () {
                this.timerID = setInterval(() => this.tick(), 1000);
            }

            componentWillUnmount () {
                clearInterval(this.timerID)
            }

            tick () {
                this.setState({
                    date: new Date()
                })
            }
        }

JSX(JavaScript eXtensionとは

JSX(JavaScript eXtension)とはFacebookが開発したJavaScriptの拡張記法です。

JSXのサンプルを以下に示します。

const element = <h1>Hello, world!</h1>;

これはマークアップを書いているように思えますが、JavaScriptを書いています。

<h1>Hello, world!</h1>の部分がJSXというJavaScriptを拡張した記法でして、これを変換するとオブジェクトに変換されます。

ビルドが必要

ブラウザはJSXを理解できないのでプリコンパイルが必要ですが、JSXTransfomerを利用することで開発者はプリコンパイルを意識する必要は無くなります。

ただ規模が大きくなると事前にビルドをしておく方が望ましいです。

JSX→React.createElement->Object

JSXで記述されたマークアップのようなものはReact.createElement()に変換されます。

つまり、

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

というJSXはビルドにより

const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

に変換される訳です。

createElement()は次のようなオブジェクトを返します。

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

つまりJSXは最終的にオブジェクトに変換され、ブラウザで解釈可能となるのです。

コンポーネントの作り方

Reactでコンポーネントを作成する場合、関数コンポーネントとクラスコンポーネントの2種類が存在します。

関数コンポーネント

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

JavaScriptはプロトタイプベースのオブジェクト指向で、ES6からclass記法が利用できるようになりました。

ES5までのクラス(実際はObject)の記述方法でコンポーネントを作成した場合は上記のようになります。

コンストラクタ内でJSXをreturnするように記述します。

クラスコンポーネント

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

こちらはES6からのクラスの記法を利用し、React.Componentを継承して作成したコンポーネントです。

上記のようにrender()でJSXをreturnさせます。

関数コンポーネントとクラスコンポーネントの違い

クラスコンポーネント = 関数コンポーネント + 追加機能

クラスコンポーネントには関数コンポーネントにはない追加機能があります。

  1. state
  2. ライフサイクル

上記の追加機能が付与されます。React.Componentを継承しているためです。

React要素とコンポーネント

React要素というものが存在します。それはJSXやcreateElementで作成するオブジェクトで表現されたDOMの構成情報です。

下記はReact要素です。

const element = <h1>Hello, world!</h1>;

コンポーネントは次です。

class HelloWorldComponent extends React.Component {
            render () {
                return (
                    <h1>Hello, world!</h1>
                )
            }
 }

つまりReact要素はコンポーネントを構成する要素です。

PropsとState

PropsとStateと呼ばれるデータが存在します。

Props

親コンポーネントから子コンポーネントにデータを渡す際にPropsを利用します。

Propsは『プロパティ』を意味してます。

React がユーザ定義のコンポーネントを見つけた場合、JSX の属性を単一のオブジェクトとしてこのコンポーネントに渡します。このオブジェクトのことを “props” と呼びます。

つまり

class SelfIntroduction extends React.Component {
            render () {
                return (
                    <h1>I am {this.props.name}</h1>// PropsにはJSXの属性を単一オブジェクトとして渡されている
                )
            }
        }

        ReactDOM.render(
            <SelfIntroduction name="Tomo"/>,//ユーザ定義のコンポーネントにnameというJSXの属性を発見
            document.getElementById('root')
        )

<SelfIntroduction name="Tomo"/>のようなユーザ定義のコンポーネントにnameという属性を発見した場合に、単一のオブジェクト({name : "Tomo"})としてPropsに格納するということです。

ですので、this.props.nameTomoにアクセスできています。

Propsは読み取り専用

Propsは読み取り専用であり、変更することが許されません。

全ての React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。

propsに対して純関数のように振舞わなければならない、ということは変更してはいけないことを意味しています。

しかし、ユーザからの入力によりコンポーネント内の状態を何かの変数に格納しておきたいですよね。

コンポーネント内の状態の管理にはstateが利用できます。

State

コンポーネントはstateと呼ばれる特別なプロパティが存在しています。

このstateというプロパティは、コンポーネントで完全にプライベートで管理されています。

コンポーネント内の閉じた状態を管理するのに利用できます。

Stateの初期化

constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

Stateの初期状態を設定するためにconstructor内で直接格納します。

しかしthis.state = {date: new Date()};のように直接代入して良い場所はconstuctorのみです。

Stateの更新(setState)

// Wrong
this.state.comment = 'Hello';

このように直接stateを変更しても再レンダーされません。

// Correct
this.setState({comment: 'Hello'});

setStateを利用しstateを変更することで再レンダーされます。

stateの更新はマージされる

React はパフォーマンスのために、複数の setState() 呼び出しを 1 度の更新にまとめて処理することがあります。

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

このようなstateの変更がstateの状態に依存する場合はオブジェクト形式での変更は利用すべきではありません。

次のような関数として格納する方法を利用することで最新のstate・propsを利用できます。

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

アロー関数ではなく、普通の関数リテラルを利用してもOKです。

まとめ

ReactチュートリアルのJSXからSateまでの理解をまとめました。

断片的な記事を見るよりも少しずつチュートリアルを進める方が理解が捗りますね。

Reactのチュートリアルは丁寧に書かれていますので、一読してみては如何でしょうか?