React入門 ~基礎編~

現在業務で使用しているReactについて、より理解を深めていくために勉強したことを記事に書くことにしました。
ということで、まずは基礎編です。

※この記事はQiitaからの転載です。

記事を書くにあたって #

2019年秋頃からQiitaの記事を書いてきましたが、環境構築系ばかりで、いまだ技術の中身についての記事を書いたことがなかったので、そろそろ書いてみたいなーと思っていました。(ブログで多少技術記事は書いていました)

技術記事となると、すでに同じテーマで記事を書かれている方がいてクオリティも高くてと、自分なんかが書いてもなと尻込みをしていたのですが、自分の理解を深めるという目的で思い切って書くことにしました。
記事を書く上で自然とまとめるようになるので、頭の中が整理できるので。

もし自分がその技術から離れてしまって、また戻ってきたときに記事を見返して助けになればとも思います。
まだまだ勉強中の身であるため内容に間違い等ありましたら、コメントいただけると幸いです。

なお、環境構築に関してはここでは書きません。
ただ、過去にDockerで環境構築した記事を書いているので、もしよろしければそちらをご覧ください。
DockerでReact + Swagger 環境を作ろう

Reactとは #

React はユーザーインターフェイスを構築するための、宣言型で効率的で柔軟な JavaScript ライブラリです。複雑な UI を、「コンポーネント」と呼ばれる小さく独立した部品から組み立てることができます。

React - チュートリアル:React の導入 - React とは? より

React (リアクト) は、Facebookとコミュニティによって開発されているユーザインタフェース構築のためのJavaScriptライブラリである。React.jsまたはReactJSの名称でも知られている。

Reactはシングルページアプリケーションやモバイルアプリケーションの開発におけるベースとして使用することができる。
Wikipedia - React より

Reactの基本 #

以下、記述しているコードはReact公式ドキュメントや公式チュートリアルを引用、もしくはベースにしています。

基本文法 #

index.jsで、ReactDOM.renderでレンダリングするコンポーネントと、レンダリングする箇所を定義。
この例では、Appというコンポーネントに定義されたReact要素を、ルートDOMノード(index.htmlのidがrootの要素)にレンダリングするよう定義しています。

public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

src/index.js

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

ReactDOM.render(<App />, document.getElementById('root')); // ルートDOMノードにAppコンポーネントをレンダリングするよう定義

コンポーネント定義の中のreturnで返されるReact要素(JSX)がレンダリングする内容。
(※関数コンポーネントのため省略されていますが、正確にはrender()メソッドが返すReact要素がレンダリングする内容)
エクスポートすることで、他ファイルからこのコンポーネントを呼び出すことができるようになります。

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return ( // レンダリングする内容
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App; // コンポーネントをエクスポート

コンポーネント #

クラスコンポーネント #

React.Componentを継承したクラスで定義されたコンポーネント。
後述する状態(state)やライフサイクルを持つことができます。

import React from 'react';
class App extends React.Component {  // React.componentを継承するクラスの定義
  render() {  // JSXを戻り値とするrenderメソッドを定義
    return (
      <h1>Hello World</h1>
    );
  }
}

export default App;

関数コンポーネント #

クラスコンポーネントと違い、状態(state)やライフサイクルを持たないコンポーネントをよりシンプルに記述したもの。
constで変数定義などはできますが、基本的にrender()メソッドのみであるため、render()の記述が省略できます。

最近の傾向では、なるべく関数コンポーネントを使用する方向であるようですが、状態(state)やライフサイクルを持つことができないため、関数コンポーネントもクラスコンポーネントと同等の機能を有せるようにとrecomposeReact Hooksが生まれたそうです。

import React from 'react';
function App() {
  return (
      <h1>Hello World</h1>
  );
}

export default App;

アロー関数で以下のようにも書くことができます。

import React from 'react';
const App = () => {
  return (
      <h1>Hello World</h1>
  );
}

export default App;

(※2020/2/1追記)
また変数定義などがなく、純粋にrender()メソッドのみの場合は、さらにreturnも省略することができます。

import React from 'react';
const App = () => (
 <h1>Hello World</h1>
)

export default App;

JSX #

基本 #

コンポーネントのrender()メソッド内に記述するHTMLに似た構文で、React要素を生成するもの。
Babelでのコンパイル時にReact.createElement() 形式に変換されます。
そのため、以下のコードは等価のものになります。

const element = (
  <h1 className="title">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'title'},
  'Hello, world!'
);

また、JSXでは一つのReact要素を返す必要があります。
複数の要素から構成される場合は、divで囲うなどして、一つのReact要素を返すようにしましょう。
(※divで囲うなどで支障が出る場合は、React.Fragmentの欄を参照)

NGな例

render() {
  return (
    <h1>h1です</h1>
    <h2>h2です</h2>
    <p>pです</p>
  )
}

OKな例

render() {
  return (
    <div>
      <h1>h1です</h1>
      <h2>h2です</h2>
      <p>pです</p>
    </div>
  );
}

JSXはHTMLと似ていますが閉じタグに関しては注意が必要。
imgタグのようにHTMLでは閉じタグが必要なかったものについては、末尾に/とつけて閉じる必要があります。

<img src='画像URL' />

React.Fragment #

場合によっては、複数要素をまとめるためにdivで囲うことで支障が出ることもあります。
そういったときはReact.Fragmentというものを使うことができ、DOMに余分なノードを追加することなく子要素をまとめられます。
また、React.Fragmentでは後述するkeyプロパティを持つことができます。

詳しくはこちら

render() {
  return (
    <React.Fragment>
      <h1>h1です</h1>
      <h2>h2です</h2>
      <p>pです</p>
    </React.Fragment>
  );
}

短縮記法で以下のようにも書くことができますが、こちらではkeyプロパティを持つことができないので注意が必要です。

render() {
  return (
    <>
      <h1>h1です</h1>
      <h2>h2です</h2>
      <p>pです</p>
    </>
  );
}

変数の埋め込み #

JSX内に変数を埋め込む際は{}で囲って記述します。

const name = 'Yamada Taro';
const element = <h1>Hello, {name}</h1>;

なお、デフォルトでは、React DOMはJSXに埋め込まれた値をレンダリングされる前にエスケープするため、以下のようにユーザーの入力を受け付ける場合でも、XSSなどインジェクション攻撃を防ぐことができます。

const title = response.potentiallyMaliciousInput;
const element = <h1>{title}</h1>;

命名規則 #

要素のプロパティに関してはキャメルケース。
例として、class はclassNameとなり、tabindex はtabIndexとなります。

条件付きレンダー(※2020/2/1追記) #

論理値 or 論理式と&&の組み合わせで、条件によってレンダリングする内容の切り替えができます。

render() {
    const flg = true;
    return (
      <div>
        {/* flg変数がtrueの時のみレンダリング */}
        {flg && <p>フラグ:true</p>}
        {/* flg変数がfalseの時のみレンダリング */}
        {!flg && <p>フラグ:false</p>}
      </div>
    )
  }

三項演算子を使用した例

render() {
    const flg = true;
    return (
      <div>
        {flg ? <p>フラグ:true</p> : <p>フラグ:false</p>}
      </div>
    )
  }

props #

基本 #

コンポーネントに渡せるプロパティのこと。
propsを使うことで親コンポーネントが子コンポーネントに情報を渡すことができ、同じコンポーネントでも、渡すpropsによって変化をつけることができます。

呼び出し側

<Message name="Taro" />

コンポーネント側(関数コンポーネントの例)

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

コンポーネント側(クラスコンポーネントの例)

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

children #

子要素を意味するprops。
タグやコンポーネントの中身をchildrenで指定する書き方もできます。
そのため、以下のコードは等価になります。

<h1>test</h1>

<Message>test</Message>
<h1 children="test" />

<Message children="test" />

propsとして渡されるので、コンポーネント側で使用することもできます。

function Message(props) {
  return <h1>{props.children}</h1>
}

state #

基本 #

クラスコンポーネントではstate(状態)を持つことができます。

設定するには、まずクラスコンポーネントにコンストラクタを追加してstateを初期化。
(クラスコンポーネントのコンストラクタは常にpropsを引数として、親クラスのコンストラクタを呼び出す必要があります)

class Square extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: null,
        };
    }

    ※以下省略
  }

stateの更新 #

直接stateに代入するのでなく、setStateを使用して更新するようにします。
なお、一度のsetStateで複数のstateの値を更新することもできます。

stateが更新されると、そのコンポーネントは再度レンダリングされます。
setStateを使用せずにstateを直接変更した場合は再レンダリングされないのでやらないこと)

class Square extends React.Component {
    ※一部省略

    render() {
      return (
        // ボタンクリックでstateに値をセットする
        <button className="square" onClick={() => this.setState({value: 'X'})}>
          {this.state.value}
        </button>
      );
    }
  }

また、this.propsとthis.stateは非同期に更新されるため、setState時にそれらの値に依存してはいけません。

ダメな例

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

子コンポーネントから親コンポーネントのstateを変更したい場合は、this.stateはそのコンポーネント内でプライベートになるため、直接変更することはできません。
この場合は、親コンポーネントから子コンポーネントにstateを更新する関数を渡すようにして、この関数を子コンポーネントで呼び出すことで対処できます。

class Square extends React.Component {
    ※一部省略

    render() {
      return (
        <button className="square" onClick={() => this.props.onClick()}>
          {this.props.value}
        </button>
      );
    }
  }

class Board extends React.Component {
    ※一部省略

    handleClick(i) {
        // 配列のコピーを作成
        const squares = this.state.squares.slice();
        squares[i] = 'X';
        this.setState({squares: squares});
    }

    renderSquare(i) {
      return <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)}/>;
    }

イベント #

基本 #

基本的にはタグ内にイベント名={() => {処理}}で記述することができます。

<button onClick={() => {console.log('Hello World')}}>こんにちは</button>

イベントの例

  • onClick クリックされた時、buttonタグなど
  • onSubmit 送信された時、formタグなど
  • onChange 入力や削除が行われた時、inputタグなど
  • onMouseOver マウスが上に置かれた時
  • onMouseOut マウスが外れた時

onChangeの例
eventは合成 (synthetic) イベントです。event.target.valueで入力された値を取得できます。

<input onChange={(event) => {console.log(event.target.value)}} />

preventDefault #

aタグのリンクやチェックボックスなど、元々のイベントをキャンセルするものです。

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

key #

要素のリストをレンダリングする際、リストの項目それぞれについて、Reactは情報を保持します。
リストに変更、追加、削除などがあった時に、Reactがどのアイテムが変更になったのかを知るために、keyプロパティを与えるようにしましょう。

リストが再レンダリングされる際、Reactはそれぞれのリスト項目のkeyについて、前回のリスト項目内に同じkeyを持つものがないか探し、その結果によってリスト項目を追加したり、削除したりします。
keyプロパティの値は兄弟要素の中で一意であれば問題ないようです。

なお、リスト項目にkeyプロパティを設定していないと、コンソールで警告が表示されます。
また、keyプロパティはpropsの一部のようにも見えるが、this.props.keyで参照することはできません。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li key={number.toString()}>
    {number}
  </li>
);

リスト項目をコンポーネント化した際は、呼び出し時にkeyプロパティを設定するようにしましょう。

function ListItem(props) {
  // ここではkeyプロパティを指定しない
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // ここでkeyプロパティを指定
    <ListItem
      key={number.toString()}
      value={number}
    />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

ライフサイクル #

ライフサイクルの流れ図
Reactコンポーネントのライフサイクル図

React Lifecycle Methods diagram より(GitHub - react-lifecycle-methods-diagram

マウント #

コンポーネントのインスタンスが作成されてDOMに挿入される時に、以下のメソッドが次の順序で呼び出されます。

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新 #

propsやstateの変更によって発生します。
コンポーネントが再レンダリングされるときに、以下のメソッドが次の順序で呼び出されます。

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

アンマウント #

コンポーネントがDOMから削除されるときに呼び出されます。

  • componentWillUnmount()

エラーハンドリング #

任意の子コンポーネントのレンダリング中、ライフサイクルメソッド内、またはコンストラクタ内でエラーが発生したときに呼び出されます。

  • static getDerivedStateFromError()
  • componentDidCatch()

それぞれのメソッドの解説 #

長くなりそうなので割愛します。

こちらで詳しく解説されていますのでご参照ください。

なお、自分はrender()後に一度だけ呼ばれるcomponentDidMount()を使用することが多いです。(といいつつ、最近まで呼ばれるタイミングを少し勘違いしていたのですが…)


Reactの基礎としてどこまで書くか迷いましたが、公式チュートリアルで扱われたものを中心に今回書きました。
続編として、フォームやrouter、recomposeやRedux、React Hooksなどについて書いていけたらと思います。
React Hooksについては、現状全く使ったことがない未知の領域ですが、モダンな書き方を追求するためにも今後ぜひ勉強したいです。

参考リンクまとめ #

シリーズ記事 #