【React】Vueユーザー向けReactチュートリアル

僕はフレームワークを学習する際、「Vueで言うところのあの機能は、このフレームワークではどうやって実装するのだろう...」という風な学習の進め方をします。

最近Reactを学習しているのですが、その際に整理した代表的な「Vueで言うところの...」を、記事にしてみました。Vueとの比較をしながら、Reactの基礎も紹介しています。Reactの学習を始められた同志のお力になれると幸いです！(^｡^)

[こちらの記事](https://qiita.com/whopper1962/items/158a8b209ae554089cec)ではAngularの「Vueで言うところの...」を紹介しています。是非併せてご覧ください！✨

## Reactとは
![React_logo_wordmark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/570537da-e25d-b2f8-7b2c-498171125826.png)
ReactはFacebook（現Meta）によって開発されたオープンソースのJavaScriptフレームワークです。2013年に初版がリリースされ、2022年3月に、最新版となるバージョン18がリリースされました（2023年5月現在）。
JavaScriptフレームワークという紹介をしましたが、[公式ページ](https://ja.legacy.reactjs.org/)でも「ユーザインターフェース構築のための JavaScript ライブラリ」と書かれているので、正確にはReactはライブラリであるとされています。Reactで開発を行う際はサードパーティ制のライブラリを併せて使用することが一般的で、React単体で使用することは無いことが理由とされています。
[**公式ドキュメントはこちら**](https://ja.legacy.reactjs.org/)
## Reactアプリケーションの起動方法
Reactのアプリケーションを作成していきます。以下コマンドを実行し、Reactのアプリケーションの起動を行なってください。
~~~bash
npx create-react-app react-tutorial
~~~
作成したディレクトリに移動し、開発サーバーを起動します。
~~~bash
cd react-tutorial
npm run start
~~~
上記のコマンドが正常すると以下のようなログが出力されます。`Local`に表示されるURLにアクセスしてReactアプリケーションの画面が表示されれば、完了です。
~~~bash
  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.73.117:8080/
~~~
![Screen Shot 2023-04-24 at 12.47.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2356280/76e9530f-fb87-9201-3022-10d58c9b6267.png)

## コンポーネント基礎
Reactのコンポーネントには、関数コンポーネントとクラスコンポーネントがあります。これらは構文が色々と違います。これらの違いは[こちらの記事](https://www.twilio.com/ja/blog/react-choose-functional-components-jp)で分かりやすく解説されております。
どちらを使用すれば良いか迷うところですが、記述量が少ないなどの理由から、一般的には関数コンポーネントが推奨されています。本記事でも関数コンポーネントで進めますので、ご了承ください。
~~~react:関数コンポーネント
import React from "react";

function Home() {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

export default Home;
~~~
~~~react:クラスコンポーネント
import React from "react";

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

export default Home;
~~~
コンポーネントに独自のスタイルを定義する際は、CSSファイルを別途用意します。CSSファイルはコンポーネントと同名にするのが一般的です。コンポーネントからCSSファイルを`import`することでスタイルを取り込みます。
~~~css:Home.css
.home-wrapper {
  color: rgb(130, 215, 247);
}
~~~
~~~react:Home.jsx
import "./Home.css";

function Home() {
  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

export default Home;
~~~
Reactでは、HTMLタグにクラスを適用する際は、`class`ではなく`className`を使用します。コンポーネントで記述されているHTMLは[JSX](https://high-career.jp/2022/02/28/jsx/)によって表現されているものであり、あくまでJavaScriptです。その為JavaScriptの予約語である`class`は使用できないことが理由となっています。

## Vueで言うところの`data`
Vueで言うところの`data`は、Reactでは**State**と呼ばれます。`useState`を使用することで、Vueの`data`のようなリアクティブな値（State）を定義することができます。`useState`は参照用の値と、値更新用の関数を返すメソッドです。その為、分割代入を利用して値と値更新用関数を定義することが多いです。引数には、初期値を渡します。
~~~react
import React, { useState } from "react";

function Home() {
  let [name, setName] = useState("");

  return (
    <div>
      <h1>Home</h1>
      <input type="text" onChange={(e) => setName(e.target.value)} />
      <p>Data: {name}</p>
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`methods`
通常の関数を定義する形で表現します。
~~~react
import React from "react";

function Home() {
  const sampleFunction = () => {
    console.log('Hello');
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={sampleFunction}>Submit</button>
    </div>
  );
}

export default Home;
~~~
注意点として、クリックイベントなどで定義した関数を指定する際は、`()`を付与しないようにしてください。`()`を付与すると初期レンダリングのみ関数が実行され、その後クリックしても関数が実行されなくなります。
その為、引数を渡す場合は少し工夫が必要です。以下のように、関数に引数を渡して実行する関数をイベントハンドラとして指定するような実装にする必要があります。
~~~react
import React from "react";
import "./Home.css";

function Home() {
  const argFunction = (arg) => {
    alert(arg);
  };

  return (
    <div className="home-wrapper">
      <h1>Home</h1>
      <button onClick={() => argFunction("Hello")}>Argument</button>
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`computed`
`useMemo()`を使用することでVueの`computed`を再現することができます。第一引数に実際に実行するコールバック関数を指定して、第二引数に監視対象を配列形式で指定します。第二引数に指定した監視対象のいずれかが変更された場合にのみ再計算を行うような仕組みになっています。
以下では`fullName`を3回呼び出していますが、「Called!」は1回のみしかコンソールに出力されません。
~~~react
import React, { useMemo } from "react";

function Home() {
  const firstName = "Peter";
  const lastnNme = "Parker";

  const fullName = useMemo(() => {
    console.log('Called!');
    return `${firstName} ${lastnNme}`;
  }, [firstName, lastnNme]);

  return (
    <div>
      <h1>Home</h1>
      <p>{fullName}</p>
      <p>{fullName}</p>
      <p>{fullName}</p>
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`props`
関数コンポーネントの引数として渡された値を用いて親コンポーネントから子コンポーネントに値を渡すことができます。
~~~react:子コンポーネント
import React from "react";

function Home(props) {
  return (
    <div>
      <h1>Home</h1>
      <p>Name: {props.name}</p> {/* Whopper */}
    </div>
  );
}

export default Home;
~~~
~~~react:親コンポーネント
import Home from './components/Home/Home';

function App() {
  return (
    <div>
      <Home name="Whopper"/>
    </div>
  );
}

export default App;
~~~
以下のように分割代入を用いてpropsとして受け取る値を明示でき、`props.`を省略できるのでお勧めです。
~~~react
import React from "react";

function Home({ name }) {
  return (
    <div>
      <h1>Home</h1>
      <p>Name: {name}</p>
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`emit`
子コンポーネントでコンポーネントの引数（Vueで言うところの`props`）からイベントを実行することで親コンポーネントにデータを渡すことができます。
子コンポーネントではVueの`emit`のように個別に定義する必要はなく、親コンポーネントからは`イベント名={実行するイベント}`のように指定します。
以下では、子コンポーネントから引数にコンポーネントのデータを指定して`sendData`を実行、親コンポーネントのイベントで渡された引数をコンポーネントのデータに反映しています。
~~~react:子コンポーネント
import React from "react";

function Child(props) {
  const user = {
    name: 'Peter Parker',
    email: 'peter@gmail.com',
  }

  const onClick = () => {
    props.sendData(user)
  }

  return (
    <div>
      <h1>Child</h1>
      <button onClick={onClick}>Click here to send data</button>
    </div>
  );
}

export default Child;
~~~
~~~react:親コンポーネント
import React, { useState } from "react";
import Child from "../Child/Child";

function Home() {
  const [data, setData] = useState({
    name: "?",
    email: "?",
  });

  const sendData = (data) => {
    setData(data);
  };

  return (
    <div>
      <h1>Home</h1>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
      <Child sendData={sendData} />
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`watch`
`useEffect()`を使用してVueの`watch`を再現することができます。第一引数に監視対象が変更された時に実行するコールバック関数を指定して、第二引数に監視対象を指定します。以下では、`name`の変更を検知してログを出力しています。
~~~react
import React, { useEffect, useState } from "react";

function Home() {
  let [name, setName] = useState("");

  useEffect(() => {
    console.error('name changed!');
  }, [name])

  return (
    <div>
      <h1>Home</h1>
      <input type="text" onChange={(e) => setName(e.target.value)} />
      <p>Data: {name}</p>
    </div>
  );
}

export default Home;
~~~
ですが、この状態ではコンポーネントのマウント時に2回ハンドラーが実行されます。これはReact18から導入された[StrictMode]()という機能によるものと、`useEffect()`の挙動によるものが原因です。この挙動を消すには、以下の修正を行なってください。
まずStrictModeを解除するには、`src/index.js`を以下のように修正してください。StrictModeを有効にする`<React.StrictMode>`タグを削除することで解除できます。
~~~diff_jsx:src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
-  <React.StrictMode>
    <App />
-  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
~~~
2回目の実行を防ぐ方法は、色々ありますが、個人的にはマウントされる前はハンドラーが実行されないように`useRef`を使用して定義したフラグを使用する方法が好みです。`useRef`は
毎回このような実装をするのが面倒な場合は、新たにモジュールとして切り出すことを検討しても良さそうです。
~~~react
import React, { useEffect, useState, useLayoutEffect, useRef } from "react";

function Home() {
  let [name, setName] = useState("");

  const firstUpdate = useRef(true);
  useEffect(() => {
    if (firstUpdate.current) {
      firstUpdate.current = false;
      return;
    }
    console.error('name changed!');
  }, [name]);

  return (
    <div>
      <h1>Home</h1>
      <input type="text" onChange={(e) => setName(e.target.value)} />
    </div>
  );
}

export default Home;
~~~

## Vueで言うところの`components`
外部コンポーネントをインポートするだけで使用することができます。
~~~react
import React from "react";
import Card from "../Card/Card";

function Home() {
  return (
    <div>
      <h1>Home</h1>
      <Card/>
    </div>
  );
}

export default Home;
~~~
## ディレクティブ

### Vueで言うところの`@click`
`onClick`を使用することで`@click`を再現することができます。`onClick="{実行する関数名}"`というような形で定義します。以下ではボタンクリック時に`onClickButton`を実行しています。
~~~react
import React from "react";

function Home() {
  const onClickButton = () => {
    alert('Hello');
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={onClickButton}>Click</button>
    </div>
  );
}

export default Home;
~~~
また、[Vueで言うところの`methods`]()でも述べたように、`()`を付けると意図しない挙動が起こります。また、引数を渡す際にも注意が必要です。

### Vueで言うところの`v-model`
`onChange`と`useState`を使用することで`v-model`を再現することができます。`useState`で定義した値変更用の関数を、`onCange`発火時（フォーム入力時）に入力された値（`event.target.value`）を引数にして随時実行するような実装をします。
以下では、変数`name`を入力フォームにバインディングしています。
~~~react
import React, { useState } from "react";

function Home() {
  let [name, setName] = useState("");

  return (
    <div>
      <h1>Home</h1>
      <input type="text" onChange={(e) => setName(e.target.value)} />
      <p>Data: {name}</p>
    </div>
  );
}

export default Home;
~~~

### Vueで言うところの`v-for`
JSXの中では`{}`の中にJavaScriptを記述することができます。これを利用して、ループしたい箇所に`{}`を挟み、素のJavaScriptでループ処理を記述します。
~~~react
import React from "react";

function Home() {
  const seasons = ["Spring", "Summer", "Autumn", "Winter"];

  return (
    <div>
      <h1>Home</h1>
      <ul>
        {seasons.map((season, index) => (
          <li key={index}>{season}</li>
        ))}
      </ul>
    </div>
  );
}

export default Home;
~~~
以下のように、あらかじめ定義した配列をそのまま表示する方法もあります。
~~~react
import React from "react";

function Home() {
  const seasons = ["Spring", "Summer", "Autumn", "Winter"];
  let seasonsList = [];

  seasons.forEach((season, index) =>
    seasonsList.push(<li key={index}>{season}</li>)
  );

  return (
    <div>
      <h1>Home</h1>
      <ul>{seasonsList}</ul>
    </div>
  );
}

export default Home;
~~~

### Vueで言うところの`v-if`
要素の表示に条件をつける方法はいくつかありますが、参考演算子を使用した出し分けが記述量が少なくお勧めします。
~~~react
import React from "react";

function Home() {
  const role = "admin";

  return (
    <div>
      <h1>Home</h1>
      <p>Role: {role === "admin" ?  "Admin": "Member"}</p>
    </div>
  );
}

export default Home;
~~~
複雑な条件の場合は、三項演算子ではなく、出し分け処理を関数として切り出す方法もあります。
~~~react
import React from "react";
import Admin from "../Admin/Admin";
import Member from "../Member/Member";

function Home() {
  const role = "admin";

  const landingPage = () => {
    if (role === "admin") return <Admin />;
    return <Member />;
  };

  return (
    <div>
      <h1>Home</h1>
      {landingPage()}
    </div>
  );
}

export default Home;
~~~
条件付きレンダリングの方法が[公式ドキュメント](https://ja.legacy.reactjs.org/docs/conditional-rendering.html#gatsby-focus-wrapper)で紹介されていますので併せてご覧ください。
## Vueで言うところの`created`/`mounted`
[Vueで言うところの`watch`]()で紹介した`useEffect()`を利用することで、画面遷移時の初期処理を定義することができます。`watch`を再現する場合は第二引数に監視対象を配列形式で指定しましたが、これを空配列にすることで画面遷移時に一度のみ実行されるようになります。
~~~react
import React from "react";

function Home() {
  useEffect(() => {
    console.log("Init");
  }, []);

  return (
    <div>
      <h1>Home</h1>
    </div>
  );
}

export default Home;
~~~

## ルーティング

### 初期設定
Reactでルーティングを導入するには、ルーティングライブラリ[ReactRouter](https://www.npmjs.com/package/react-router-dom)を使用します。公式ドキュメントは[こちら](https://reactrouter.com/en/main)です。2023年5月現在最新バージョンが6で、バージョン5と記述方法が変更になっているものが多くあります。[こちらの記事](https://dev.classmethod.jp/articles/react-router-5to6/#toc-6)で変更点が解説されていますので、併せてご覧ください。

以下コマンドでインストールを実行してください。
~~~bash
npm i react-router-dom
~~~
ルートは`src/App.js`にて定義します。ReactRouterの`<BrowserRouter>`、`<Routes>`、`<Route>`タグを使用してルートを定義していきます。存在しないパスにアクセスした際に404などページを表示したい場合は`path`を`/*`にすることで定義できます。
~~~react:src/App.js
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Login from "./components/Login/Login";
import Users from "./components/Users/Users";
import NotFoundError from "./components/Error/NotFoundError";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/users" element={<Users />} />
        <Route path="/*" element={<NotFoundError />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
~~~

### 遷移（Vueで言うところのVueRouter）

#### Vueで言うところの`<router-link/>`
ReactRouterの`<Link>`コンポーネントを使用することでリンクを配置ことができます。`to`に指定したパスと一致するルートに遷移します。
~~~react
import React from "react";
import { Link } from "react-router-dom";

function Home() {
  return (
    <div>
      <h1>Home</h1>
      <Link to="/login">Login</Link>
    </div>
  );
}

export default Home;
~~~

#### Vueで言うところの`$router.push()`
ReactRouterの[`useNavigate()`](https://reactrouter.com/en/main/hooks/use-navigate)を使用することで、プログラムから画面遷移を行うことができます。`useNavigate()`の返り値がそのまま画面遷移用のメソッドとなります。引数に指定したパスと一致するルートに遷移します。
~~~react
import React from "react";
import { useNavigate } from "react-router-dom";

function Home() {
  const navigate = useNavigate();

  const toLoginPage = () => {
    navigate("/login");
  };

  return (
    <div>
      <h1>Home</h1>
      <button onClick={toLoginPage}>Login</button>
    </div>
  );
}

export default Home;
~~~