とほほのReact入門
- React とは
- CDN
- インストールとアプリケーション作成と実行
- ReactDOM.render()
- コンポーネントを定義する
- プロパティを渡す
- JSX の書き方
- リストを表示する
- ステータスを変更する
- ルーティングでページを切り替える
- ユーザ管理画面サンプル
- リンク
React とは
- SPA(Single-Page Application) を実現する JavaScript フレームワークの一つです。
- Angular, Vue.js とよく比較されます。
- Facebook 社によって開発され、Facebook の Web サイトでも利用されています。
- 2020年4月現在の最新バージョンは 16.13.1 です。
- MITライセンスで公開されており、商用利用可能です。
- JavaScript の中に直接 HTML/XML を記述する JSX という技術を利用しています。
- JavaScript は ES6 の文法である import やアロー関数を取り入れています。
- JSX や ES6 文法を、Babel というトランスパイラで ES5 の JavaScript に変換しています。
- Chrome, Firefox などで動作します。IE8 で一部機能、IE9 で制限付き、IE10 でフル機能がサポートされます。
CDN
CDN を用いた例は下記の様になります。
<!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>
上記では開発者用のライブラリを使用していますが、プロダクトモードの場合は下記を使用してください。
<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 を用います。
$ npm install -g create-react-app
create-react-app コマンドでアプリケーションを作成します。
$ create-react-app my-app $ cd my-app
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 という独特な文法で記述することができます。
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 を次のように書き換えてみましょう。
import React from 'react';
import './App.css';
function App() {
return <h1>Hello, world!</h1>;
}
export default App;
npm start を実行すると、開発用の簡易サーバが起動します。ブラウザから http://サーバアドレス:3000/ にアクセスして "Hello, world!" が表示されれば成功です。
$ npm start
ReactDOM.render()
ReactDOM.render() では、第2引数で指定した DOM 要素に対して、第1要素で指定したコンポーネントを割り当てます。React.StrintMode は開発者用に検査・警告を有効にするコンポーネントです。内部で App コンポーネントを呼び出しています。
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
コンポーネントを定義する
コンポーネントの定義は様々な方法があります。React 15.5 より古いバージョンでは、React.createClass() を用いて定義していましたが、React 15.5 で廃止されました。
var App = React.createClass({ // 廃止
render() {
return <h1>Hello!</h1>;
}
});
createReactClass() を利用する方法もありました。npm で create-react-class をインストールしておく必要があります。しかし、この書き方も今ではあまり用いられていません。
var createReactClass = require('create-react-class');
var App = createReactClass({
render: function() {
return <h1>Hello!</h1>;
}
});
React 15.0 からは、ES6 のクラスを用いて定義する方法が主流となりました。
class App extends React.Component {
render() {
return <h1>Hello!</h1>;
}
}
React 16.8 以降では、関数を用いて定義する方法が主流となるようです。関数では元々ステータスを保持することができませんでしたが、React 16.8 でサポートされた Hooks を用いることで、関数でもステータスを制御することが可能となりました。Hooks の説明は別途...
function App() {
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>;
}
}
JSX の書き方
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;
}