React入門

トップ > 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 crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></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 を用います。create-react-app コマンドでアプリケーションを作成し、npm start で簡易サーバを起動します。簡易サーバには http://サーバアドレス:3000/ でアクセスできます。簡易サーバ起動中は、ソースファイルが更新されると自動的にリビルドとブラウザの再描画が行われます。

Shell
$ npm install -g create-react-app
$ create-react-app my-app
$ cd my-app
$ npm start
public/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

ReactDOM.render(
  <h1>Hello world!</h1>,
  document.getElementById('root')
);

ReactDOM.render()

ReactDOM.render() では、第一引数に JSX 文法で記述した要素を、第二引数に対象とする DOM コンポーネントを指定します。

React
ReactDOM.render(
  <h1>Hello world!</h1>,
  document.getElementById('root')
);

コンポーネントを追加する

上記のサンプルの内、<h1>Hello!</h1> を出力する機能を、Hello という名前の別コンポーネントとして定義することができます。Hello コンポーネントを呼び出す側は、下記の様に定義します。

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

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

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

React 15.5 では、createReactClass() によりコンポーネントを定義します。npm で create-react-class をインストールしておく必要があります。しかし、この書き方も今ではあまり用いられていません。

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

ES6 のクラスを用いて定義する方法もあります。最近ではこちらが主流です。

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

また、関数を用いて記述することもできます。動的にステータスの変わるコンポーネントを作成することはできませんが、一番高速です。

React
function Hello() {
  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;
}

Copyright (C) 2018 杜甫々
初版:2018年2月18日 最終更新:2018年2月18日
http://www.tohoho-web.com/ex/react.html