CDN を用いた例は下記の様になります。
<!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>
上記では開発者用のライブラリを使用していますが、プロダクトモードの場合は下記を使用してください。
<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/ でアクセスできます。簡易サーバ起動中は、ソースファイルが更新されると自動的にリビルドとブラウザの再描画が行われます。
$ npm install -g create-react-app $ create-react-app my-app $ cd my-app $ npm start
<!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>
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
ReactDOM.render(
<h1>Hello world!</h1>,
document.getElementById('root')
);
ReactDOM.render() では、第一引数に JSX 文法で記述した要素を、第二引数に対象とする DOM コンポーネントを指定します。
ReactDOM.render(
<h1>Hello world!</h1>,
document.getElementById('root')
);
上記のサンプルの内、<h1>Hello!</h1> を出力する機能を、Hello という名前の別コンポーネントとして定義することができます。Hello コンポーネントを呼び出す側は、下記の様に定義します。
ReactDOM.render(
<Hello />,
document.getElementById('root')
);
コンポーネントの定義は様々な方法があります。React 15.5 より古いバージョンでは、React.createClass() を用いて定義していましたが、React 15.5 で廃止されました。
var Hello = React.createClass({ // 廃止
render() {
return <h1>Hello!</h1>;
}
});
React 15.5 では、createReactClass() によりコンポーネントを定義します。npm で create-react-class をインストールしておく必要があります。しかし、この書き方も今ではあまり用いられていません。
var createReactClass = require('create-react-class');
var Hello = createReactClass({
render: function() {
return <h1>Hello!</h1>;
}
});
ES6 のクラスを用いて定義する方法もあります。最近ではこちらが主流です。
class Hello extends React.Component {
render() {
return <h1>Hello!</h1>;
}
}
また、関数を用いて記述することもできます。動的にステータスの変わるコンポーネントを作成することはできませんが、一番高速です。
function Hello() {
return <h1>Hello!</h1>;
}
下記の様にして、コンポーネントにプロパティを渡すことができます。
ReactDOM.render(
<Hello name="Tanaka" />,
document.getElementById('root')
);
静的関数の場合は props 引数で受け取ります。
function Hello(props) {
return <h1>Hello {props.name}!</h1>;
}
クラスの場合は this.props.プロパティ名 を参照します。
class Hello extends React.Component {
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
React では、JavaScript の文法中に XML ライクなタグを記述可能な、JavaScript の拡張言語 JSX を採用しています。
return <h1>Hello</h1>;
( ... ) で囲むことにより、複数行のタグを記述することができます。
return (
<div>
<h1>Hello</h1>
<p>This is ...</p>
</div>
);
JSX 構文では、要素は単一の要素として記述する必要があります。下記の例は、2つの要素を返却しているため、Syntax error となります。
return (
<h1>Hello</h1>
<p>This is ...</p> // Syntax error
);
{ 変数名 } で変数の値を参照することができます。
let name = 'Tanaka';
return <h1>Hello {name}!</h1>
{ 変数名 } は属性値として使用することもできます。
let name = 'Tanaka';
return <input type="text" value={name} />;
{ ... } の中では JavaScript の式を記述することができます。
return <div>3 + 5 = { 3 + 5 }</div>;
属性値の中で一部変数を使用したい場合は下記の様に記述します。
let name = 'Tanaka';
return <input type="text" defaultValue={"Hello " + name} />
{ ... } の中から関数を呼び出すこともできます。
add(x, y) { return x + y; }
render() {
return <div>3 + 5 = {this.add(3, 5)}</div>
}
{ ... } の中で複文を記述することはできません。下記は Syntax error となります。
return <div>3 + 5 = {a = 3; b = 5; a + b}</div> // Syntax error
class を指定する際は、class の代わりに className を指定します。
return <div className="main">...</div>;
style を指定する際は、{ ... } の中に JSON で記述します。デリミタはセミコロン(;)ではなくカンマ(,)、値は文字列として指定、font-size などのスネークケースではなく、fontSize などのキャメルケースで指定します。
return <div style={{color:'red', fontSize:'20pt'}}>...</div>;
イベントハンドラを指定するには、下記の様に記述します。
return <button onClick={(e) => {
console.log(e, this);
}}>OK</button>;
下記の例では、users 配列に対して map() を適用し、リストを表示しています。React が配列要素の変更を検出しやすくするために、配列要素には一意キーを持つ key 属性を指定します。
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() 関数で変更することで、変更された値が再度レンダリングされます。
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() で値を置き換えます。
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 をインストールします。
$ npm install react-router-dom --save
プログラムを下記の様に修正してください。<BrowserRouter> の子要素は単一である必要があります。<Link> と <Router> は同じ <BrowserRouter> の子孫である必要があります。<Link> をメニュー、<Router> をページと考えれば、メニューによってページを切り替える SPA を実現することが可能となります。
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 属性を指定します。
<Route exact path="/" component={Home} />
<Route path="/hello-a" component={HelloA} />
<Route path="/hello-b" component={HelloB} />
いずれの URL にもマッチしない場合にデフォルトとして Home コンポーネントを表示させるには下記の様にします。<Switch> では子要素の内、一番最初にマッチしたコンポーネントのみを表示します。
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 引数を渡すには下記の様にします。
<Route path="/hello-a" render={() => <Dashboard msg="This is..." />} />
ボタンを押した際に特定のページにジャンプするには this.props.history.push() を用います。
onCancel = () => { this.props.history.push('/hello-a'); }
:
<button onClick={this.onCancel}>Cancel</button>
最後に、Angular入門 のチュートリアルでも紹介したような、簡単なユーザ管理画面のサンプルを掲載します。
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')
);
* {
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;
}