とほほのReact入門

目次

React とは

CDN

CDN を用いた例は下記の様になります。

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>React Test</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
  <h1>Hello world!</h1>,
  document.getElementById('root')
);
</script>
</body>
</html>

上記では開発者用のライブラリを使用していますが、プロダクトモードの場合は下記を使用してください。

HTML
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

インストールとアプリケーション作成と実行

Node.js 環境で React をインストールするには、npm を用います。

Shell
$ npm install -g create-react-app

create-react-app コマンドでアプリケーションを作成します。

Shell
$ create-react-app my-app
$ cd my-app

public/index.html は次のような内容になっています。

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="%PUBLIC_URL%/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 は次のような内容になっています。JavaScript と HTML が合体したような、JSX という独特な文法で記述することができます。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

serviceWorker.unregister();

src/App.js を次のように書き換えてみましょう。

src/App.js
import React from 'react';
import './App.css';

function App() {
  return <h1>Hello, world!</h1>;
}

npm start を実行すると、開発用の簡易サーバが起動します。ブラウザから http://サーバアドレス:3000/ にアクセスして "Hello, world!" が表示されれば成功です。

src/App.js
$ npm start

ReactDOM.render()

ReactDOM.render() では、第2引数で指定した DOM 要素に対して、第1要素で指定したコンポーネントを割り当てます。React.StrintMode は開発者用に検査・警告を有効にするコンポーネントです。内部で App コンポーネントを呼び出しています。

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

コンポーネントを定義する

コンポーネントの定義は様々な方法があります。React 15.5 より古いバージョンでは、React.createClass() を用いて定義していましたが、React 15.5 で廃止されました。

React
var App = React.createClass({    // 廃止
  render() {
    return <h1>Hello!</h1>;
  }
});

createReactClass() を利用する方法もありました。npm で create-react-class をインストールしておく必要があります。しかし、この書き方も今ではあまり用いられていません。

React
var createReactClass = require('create-react-class');
var App = createReactClass({
  render: function() {
    return <h1>Hello!</h1>;
  }
});

React 15.0 からは、ES6 のクラスを用いて定義する方法が主流となりました。

React
class App extends React.Component {
  render() {
    return <h1>Hello!</h1>;
  }
}

React 16.8 以降では、関数を用いて定義する方法が主流となるようです。関数では元々ステータスを保持することができませんでしたが、React 16.8 でサポートされた Hooks を用いることで、関数でもステータスを制御することが可能となりました。Hooks の説明は別途...

React
function App() {
  return <h1>Hello!</h1>;
}

プロパティを渡す

下記の様にして、コンポーネントにプロパティを渡すことができます。

React
ReactDOM.render(
  <Hello name="Tanaka" />,
  document.getElementById('root')
);

静的関数の場合は props 引数で受け取ります。

React
function Hello(props) {
  return <h1>Hello {props.name}!</h1>;
}

クラスの場合は this.props.プロパティ名 を参照します。

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

JSX の書き方

React では、JavaScript の文法中に XML ライクなタグを記述可能な、JavaScript の拡張言語 JSX を採用しています。

JSX
return <h1>Hello</h1>;

( ... ) で囲むことにより、複数行のタグを記述することができます。

JSX
return (
  <div>
    <h1>Hello</h1>
    <p>This is ...</p>
  </div>
);

JSX 構文では、要素は単一の要素として記述する必要があります。下記の例は、2つの要素を返却しているため、Syntax error となります。

JSX
return (
  <h1>Hello</h1>
  <p>This is ...</p>  // Syntax error
);

{ 変数名 } で変数の値を参照することができます。

JSX
let name = 'Tanaka';
return <h1>Hello {name}!</h1>

{ 変数名 } は属性値として使用することもできます。

JSX
let name = 'Tanaka';
return <input type="text" value={name} />;

{ ... } の中では JavaScript の式を記述することができます。

JSX
return <div>3 + 5 = { 3 + 5 }</div>;

属性値の中で一部変数を使用したい場合は下記の様に記述します。

JSX
let name = 'Tanaka';
return <input type="text" defaultValue={"Hello " + name} />

{ ... } の中から関数を呼び出すこともできます。

JSX
add(x, y) { return x + y; }
render() {
  return <div>3 + 5 = {this.add(3, 5)}</div>
}

{ ... } の中で複文を記述することはできません。下記は Syntax error となります。

JSX
return <div>3 + 5 = {a = 3; b = 5; a + b}</div> // Syntax error

class を指定する際は、class の代わりに className を指定します。

JSX
return <div className="main">...</div>;

style を指定する際は、{ ... } の中に JSON で記述します。デリミタはセミコロン(;)ではなくカンマ(,)、値は文字列として指定、font-size などのスネークケースではなく、fontSize などのキャメルケースで指定します。

JSX
return <div style={{color:'red', fontSize:'20pt'}}>...</div>;

イベントハンドラを指定するには、下記の様に記述します。

JSX
return <button onClick={(e) => {
  console.log(e, this);
}}>OK</button>;

リストを表示する

下記の例では、users 配列に対して map() を適用し、リストを表示しています。React が配列要素の変更を検出しやすくするために、配列要素には一意キーを持つ key 属性を指定します。

React
class Hello extends React.Component {
  render() {
    const users = [
      { name: "Tanaka", age: 26 },
      { name: "Suzuki", age: 32 },
      { name: "Yamada", age: 43 }
    ];
    const userList = users.map((user, index) =>
      <li key={index}>{user.name} (Age: {user.age})</li>
    );
    return (
      <ul>{userList}</ul>
    );
  }
}

ステータスを変更する

画面上の表示をダイナミックに変更するには、コンポーネントの state 変数に初期値を設定しておき、これを setState() 関数で変更します。setState() 関数で変更することで、変更された値が再度レンダリングされます。

React
class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = { msg: 'Hello!' };
  }

  render() {
    return (
      <div>
        <h1>{this.state.msg}</h1>
        <button onClick={() => this.setState({msg: 'Bye!'})}>Click</button>
      </div>
    );
  }
}

リストを変更するには、state のプロパティに対して直接 push() するのではなく、React にプロパティの変更を認識させるために、一度リストのコピーを作成し、setState() で値を置き換えます。

React
class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      users: [
        { name: "Tanaka", age: 26 },
        { name: "Suzuki", age: 32 }
      ]
    };
  }

  changeState() {
    let users = this.state.users;
    users.push({ name: "Yamada", age: 43 });
    this.setState({ users: users });
  }

  render() {
    let userList = this.state.users.map((user, index) =>
      <li key={index}>{user.name} (Age: {user.age})</li>
    );
    return (
      <div>
        <ul>{userList}</ul>
        <button onClick={() => this.changeState()}>Click</button>
      </div>
    );
  }
}

ルーティングでページを切り替える

URL に応じて、表示するコンポーネントを切り替えるにはルーティングを使用します。まず、react-router-dom をインストールします。

Shell
$ npm install react-router-dom --save

プログラムを下記の様に修正してください。<BrowserRouter> の子要素は単一である必要があります。<Link> と <Router> は同じ <BrowserRouter> の子孫である必要があります。<Link> をメニュー、<Router> をページと考えれば、メニューによってページを切り替える SPA を実現することが可能となります。

React
import { BrowserRouter, Link, Route } from 'react-router-dom';

function HelloA() {
  return <h1>HelloA</h1>;
}

function HelloB() {
  return <h1>HelloB</h1>;
}

class Hello extends React.Component {
  render() {
    return (
      <BrowserRouter>
        <div>
          <ul>
          <li><Link to="/hello-a">HelloA</Link></li>
          <li><Link to="/hello-b">HelloB</Link></li>
          </ul>
          <Route path="/hello-a" component={HelloA} />
          <Route path="/hello-b" component={HelloB} />
        </div>
      </BrowserRouter>
    );
  }
}

path は前方一致でマッチングします。完全一致にしたい場合は exact 属性を指定します。

React
          <Route exact path="/" component={Home} />
          <Route path="/hello-a" component={HelloA} />
          <Route path="/hello-b" component={HelloB} />

いずれの URL にもマッチしない場合にデフォルトとして Home コンポーネントを表示させるには下記の様にします。<Switch> では子要素の内、一番最初にマッチしたコンポーネントのみを表示します。

React
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
  :
class Hello extends React.Component {
  render() {
    return (
            :
          <Switch>
            <Route path="/hello-a" component={HelloA} />
            <Route path="/hello-b" component={HelloB} />
            <Route path="*" component={Home} />
          </Switch>
            :

ルーティング先の子コンポーネントに props 引数を渡すには下記の様にします。

React
  <Route path="/hello-a" render={() => <Dashboard msg="This is..." />} />

ボタンを押した際に特定のページにジャンプするには this.props.history.push() を用います。

React
onCancel = () => { this.props.history.push('/hello-a'); }
 :
<button onClick={this.onCancel}>Cancel</button>

ユーザ管理画面サンプル

最後に、Angular入門 のチュートリアルでも紹介したような、簡単なユーザ管理画面のサンプルを掲載します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';

class Users {
  constructor() {
    this.users = [
      { id: 1, name: "Tanaka", email: "tanaka@example.com" },
      { id: 2, name: "Suzuki", email: "suzuki@example.com" },
      { id: 3, name: "Yamada", email: "yamada@example.com" }
    ];
  }

  getUsers() {
    return this.users;
  }

  getUser(id) {
    for (let i = 0; i < this.users.length; i++) {
      if (this.users[i].id === id) {
        return this.users[i];
      }
    }
    return undefined;
  }

  setUser(user) {
    for (let i = 0; i < this.users.length; i++) {
      if (this.users[i].id === user.id) {
        this.users[i].name = user.name;
        this.users[i].email = user.email;
      }
    }
  }
}

const userList = new Users();

function Header() {
  return <div className="header">React Sample Console</div>;
}

function Menu() {
  return (
    <ul className="menu">
      <li><Link to="/dashboard">Dashboard</Link></li>
      <li><Link to="/users">Users</Link></li>
    </ul>
  );
}

function Dashboard() {
  return <h1>Dashboard</h1>;
}

class UserList extends React.Component {
  constructor(props) {
    super(props);
    this.state = { users: userList.getUsers() }
  }

  render() {
    const userRows = userList.getUsers().map((user, index) =>
      <tr key={index}>
        <td>{user.id}</td>
        <td><Link to={"/users/" + user.id + "/edit"}>{user.name}</Link></td>
        <td>{user.email}</td>
      </tr>
    );
    return (
      <div>
        <h1>Users</h1>
        <table>
          <thead><tr><th>Id</th><th>Name</th><th>E-mail</th></tr></thead>
          <tbody>{userRows}</tbody>
        </table>
      </div>
    )
  }
}

class UserEdit extends React.Component {
  constructor(props) {
    super(props);
    this.state = { user: userList.getUser(Number(this.props.match.params.id)) }
  }

  onCange = (e) => {
    let user = this.state.user;
    switch (e.target.name) {
    case 'name':
      user.name = e.target.value;
      break;
    case 'email':
      user.email = e.target.value;
      break;
    default:
      break;
    }
    this.setState({ user: user });
  }

  onSubmit = (e) => {
    e.preventDefault();
    userList.setUser(this.state.user);
    this.props.history.push('/users');
  }

  onCancel = () => {
    this.props.history.push('/users');
  }

  render() {
    let user = this.state.user;
    return (
      <form onSubmit={this.onSubmit}>
        <table>
          <tbody>
            <tr>
              <th>Id</th>
              <td>{user.id}</td>
            </tr>
            <tr>
              <th>Name</th>
              <td><input type="text" name="name" defaultValue={user.name} onChange={this.onCange} /></td>
            </tr>
            <tr>
              <th>E-mail</th>
              <td><input type="text" name="email" defaultValue={user.email} onChange={this.onCange} /></td>
            </tr>
          </tbody>
        </table>
        <button onClick={this.onCancel}>Cancel</button>
        <button type="submit">OK</button>
      </form>
    );
  }
}

class Root extends React.Component {
  render() {
    return (
      <BrowserRouter>
        <div>
          <Header />
          <Menu />
          <div className="main">
            <Switch>
              <Route path="/dashboard" component={Dashboard} />
              <Route path="/users/:id/edit" component={UserEdit} />
              <Route path="/users" component={UserList} />
              <Route path="*" component={Dashboard} />
            </Switch>
          </div>
        </div>
      </BrowserRouter>
    );
  }
}

ReactDOM.render(
  <Root />,
  document.getElementById('root')
);
index.css
* {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}
.header {
  background-color: #000;
  color: #fff;
}
.menu {
  background-color: #ddd;
}
.menu li {
  display: inline-block;
  margin: 0 4px;
}
.main {
  padding: 4px;
}
table {
  border-collapse: collapse;
  margin: 4px 0;
}
table th,
table td {
  padding: 4px;
  border: 1px solid #888;
}
table th {
  background-color: #ddd;
}
input[type="text"] {
  width: 320px;
}
button {
  min-width: 120px;
  margin-right: 4px;
}
a:link,
a:visited {
  color: #000;
}