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>; }
npm start を実行すると、開発用の簡易サーバが起動します。ブラウザから http://サーバアドレス:3000/ にアクセスして "Hello, world!" が表示されれば成功です。
$ npm start
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>; } }
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 => { 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 => 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 => 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() を用います。
=> { this.props.history.push('/hello-a'); } : <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)) } } => { 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 }); } => { e.preventDefault(); userList.setUser(this.state.user); this.props.history.push('/users'); } => { this.props.history.push('/users'); } render() { let user = this.state.user; return ( <form <table> <tbody> <tr> <th>Id</th> <td>{user.id}</td> </tr> <tr> <th>Name</th> <td><input type="text" name="name" defaultValue={user.name} /></td> </tr> <tr> <th>E-mail</th> <td><input type="text" name="email" defaultValue={user.email} /></td> </tr> </tbody> </table> <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; }