とほほのReact入門

目次

React とは

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

下記環境でのインストール方法を説明します。以前は create-react-app を用いた構築が主流でしたが、現在 create-react-app は非推奨となり、代わりに Vite などを用いた方法が推奨されています。

OS : AlmaLinux 10.0
Node.js: v22.16.0
npm : 10.9.2
Vite : 7.1.6
React : 19.1.1

Node.js をインストールします。

Shell
# dnf -y install nodejs
# node --version
v22.16.0
# npm --version
10.9.2

Vite をインストールし、テンプレートとして React アプリケーション環境を構築します。

Shell
$ npm create vite@7.1 my-app -- --template react

アプリケーション環境に移動し、必要なパッケージをインストールします。

Shell
$ cd my-app
$ npm install

簡易サーバーを起動します。表示される http:// アドレスにブラウザでアクセスして Vite + React の画面が表示されれば成功です。

Shell
$ npm run dev
  VITE v7.1.6  ready in 301 ms
  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ホスト名やポート番号を変更したい場合は vite.config.js ファイルに下記の様に追加してください。

vite.config.js
export default defineConfig({
  plugins: [react()],
  server: {
    host: "0.0.0.0",
    port: 3000,
    strictPort: true,
  },
})

src/App.jsx を次のように書き換えてみましょう。npm run dev を別コンソールで起動したままにしておけば、プログラムを更新した時点で自動的にブラウザが再ロードします。

src/App.jsx
export default function App() {
  return <h1>Hello, world!</h1>;
}

ブラウザからアクセスして "Hello, world!" が表示されれば成功です。

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

コンポーネントの定義は様々な方法があります。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 を用いることで、関数でもステータスを制御することが可能となりました。現在推奨されているのはこの方法です。

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

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

App コンポーネントから Hello サブコンポーネントを呼び出してみましょう。

src/Hello.jsx
export default function Hello() {
  return <h1>Hello!</h1>;
}
src/App.jsx
import Hello from './Hello';

export default function App() {
  return <Hello></Hello>;
}

ブラウザからアクセスして Hello! が表示されれば成功です。

プロパティを渡す

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

src/App.jsx
import Hello from './Hello';

export default function App() {
  return <Hello name="Tanaka"></Hello>;
}

コンポーネント側はこれを引数 props 引数で受け取ります。

src/Hello.jsx
export default function Hello(props) {
  return <h1>Hello {props.name}!</h1>;
}

ブラウザからアクセスして Hello Tanaka! が表示されれば成功です。

JSX の書き方

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

JSX
return <h1>Hello!</h1>;

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

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

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

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

下記の様に無名要素 <>...</> を用いてひとつの要素にすることもできます。

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

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

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

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

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

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

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

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

JSX
function add(x, y) { return x + y; }
export default function Hello(props) {
  return <h1>3 + 5 = { add(3, 5) }</h1>;
}

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

JSX
return <h1>3 + 5 = {a = 3; b = 5; a + b}</h1>; // Error!

コメントは {/* ... */} で囲みます。

JSX
{/* コメント... */}
return <h1>Hello!</h1>;

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

JSX
return <h1 className="my-class">Hello!</h1>;

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

JSX
return <h1 style={{color:'red', fontSize:'64pt'}}>Hello!</h1>;

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

JSX
return <h1 onClick={(e) => {console.log(e)}}>Hello!</h1>;

CSSファイルを読み込む

CSS ファイルも import で読み込むことができます。

src/Hello.css
h1 { color: red; }
src/Hello.jsx
import './Hello.css';

export default function Hello() {
  return <h1>Hello!</h1>;
}

リストを表示する

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

src/Hello.jsx
export default function Hello() {
  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}({user.age})</li>
  );
  return <ul>{userList}</ul>;
}

ステータスを変更する

useState()でステートを作成・変更する

画面上の変数をダイナミックに変更するには、useState() を用います。useState() は変数の値とその変数を動的に変更するための関数を返します。

src/Hello.jsx
import { useState } from 'react';

export default function Hello() {
  const [message, setMessage] = useState("Hello!");
  return (
    <>
      <h1>{message}</h1>
      <button onClick={()=>setMessage("Bye!")}>Click!</button>
    </>
  );
}

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

src/Hello.jsx
import { useState } from 'react';

export default function Hello() {
  const [users, setUsers] = useState([
    { id: 1, name: "Tanaka" },
    { id: 2, name: "Suzuki" },
    { id: 3, name: "Yamada" },
  ]);
  return (
    <>
      <ul>
      {users.map(user => (<li key={user.id}>{user.name}</li>))}
      </ul>
      <button onClick={()=>{ setUsers([...users, { id: 4, name: "Kimura" }]); }}>Add</button>
      <button onClick={()=>{ setUsers(users.filter(user => user.id !== 4)); }}>Delete</button>
    </>
  );
}

useState() と setState()

React 16 の頃のクラスコンポーネントでは下記の様に this.statethis.setState() を用いてステータスを管理していましたが、現在では React 16.8 でサポートされた useState() を使用することが推奨されています。

src/Hello.jsx
import React from 'react';

export default class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: 'Hello!' };  // 古い書き方
  }
  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
        <button onClick={() => this.setState({message: 'Bye!'})}>Click!</button>  // 古い書き方
      </div>
    );
  }
}

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

React Router をインストールする

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

Shell
$ npm install react-router-dom

Link と Router でルーティングを振り分ける

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

src/Hello.jsx
import { BrowserRouter, Link, Routes, Route } from 'react-router-dom';

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

export default function Hello() {
  return (
    <BrowserRouter>
      <ul>
      <li><Link to="/hello-a">HelloA</Link></li>
      <li><Link to="/hello-b">HelloB</Link></li>
      </ul>
      <Routes>
        <Route path="/hello-a" element={<HelloA />} />
        <Route path="/hello-b" element={<HelloB />} />
      </Routes>
    </BrowserRouter>
  );
}

React Router v5 と v6 の差異

React Router v5 から v6 で下記の仕様が変わりました。

ユーザ管理画面サンプル

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

src/Header.jsx
export default function Header() {
  return <div className="header">React Sample Console</div>;
}
src/Menu.jsx
import { Link } from 'react-router-dom';

export default function Menu() {
  return (
    <ul className="menu">
      <li><Link to="/dashboard">Dashboard</Link></li>
      <li><Link to="/users">Users</Link></li>
    </ul>
  );
}
src/Dashboard.jsx
export default function Dashboard() {
  return <h1>Dashboard</h1>;
}
src/Users.jsx
import React, { useState } from 'react';

export default function Users() {
  const [users, setUsers] = useState([
    { id: 1, name: "Yamada", age: 26 },
    { id: 2, name: "Tanaka", age: 32 },
    { id: 3, name: "Suzuki", age: 43 },
  ]);
  const [form, setForm] = useState({ name: '', age: '' });
  const [editingIndex, setEditingIndex] = useState(null);

  const onChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };

  const onSubmit = (e) => {
    e.preventDefault();
    if (editingIndex !== null) {
      const updated = [...users];
      updated[editingIndex] = form;
      setUsers(updated);
      setEditingIndex(null);
    } else {
      const maxId = Math.max(...users.map(user => user.id)) + 1;
      const updated = [...users, { id: maxId, name: form.name, age: form.age}]
      setUsers(updated);
    }
    setForm({ name: '', age: '' });
  };

  const onEdit = (index) => {
    setForm(users[index]);
    setEditingIndex(index);
  };

  const onDelete = (index) => {
    setUsers(users.filter((_, i) => i !== index));
  };

  return (
    <div>
      <h1>Users</h1>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          name="name"
          value={form.name}
          onChange={onChange}
          placeholder="Name"
          required
        />
        <input
          type="text"
          name="age"
          value={form.age}
          onChange={onChange}
          placeholder="Age"
          required
        />
        <button type="submit">{editingIndex !== null ? 'Update' : 'Add'}</button>
      </form>
      <table>
        <thead>
          <tr><th>ID</th><th>Name</th><th>Age</th><th>Edit</th><th>Delete</th></tr>
        </thead>
        <tbody>
          {users.map((user, index) => (
            <tr key={index}>
              <td>{user.id}</td>
              <td>{user.name}</td>
              <td>{user.age}</td>
              <td><button onClick={() => onEdit(index)}>Edit</button></td>
              <td><button onClick={() => onDelete(index)}>Delete</button></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
src/App.jsx
import './App.css';
import Header from './Header';
import Menu from './Menu';
import Dashboard from './Dashboard';
import Users from './Users';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

export default function App() {
  return (
    <BrowserRouter>
      <Header />
      <Menu />
      <div className="main">
        <Routes>
          <Route path="/dashboard" element={ <Dashboard /> } />
          <Route path="/users" element={ <Users /> } />
          <Route path="/*" element={ <Dashboard /> } />
        </Routes>
      </div>
    </BrowserRouter>
  );
}
src/App.css
* {
  margin: 0;
  padding: 0;
  color: #333;
  box-sizing: border-box;
}
.header {
  padding: 8px;
  background-color: #000;
  color: #fff;
  font-weight: bold;
}
.menu {
  padding: 4px;
  background-color: #ddd;
  li {
    display: inline-block;
    margin: 0 8px;
  }
}
.main {
  padding: 8px;
}
input[type="text"] {
  width: 190px;
  height: 30px;
  line-height: 30px;
  margin: 4px 8px 8px 0;
  padding: 0 4px;
  border: 1px solid #ccc;
  border-radius: 4px;
  outline: none;
  &:focus {
    border: 2px solid #888;
  }
}
table {
  border-collapse: collapse;
  th, td {
    padding: 4px 8px;
    border: 1px solid #ccc;
  }
  th {
    background-color: #eee;
  }
}
button {
  height: 30px;
  min-width: 100px;
  border: 1px solid #ccc;
  border-radius: 4px;
}
src/index.css
/* 中身を空にする */