僕はフレームワークを学習する際、「Vueで言うところのあの機能は、このフレームワークではどうやって実装するのだろう...」という風な学習の進め方をします。
最近Reactを学習しているのですが、その際に整理した代表的な「Vueで言うところの...」を、記事にしてみました。Vueとの比較をしながら、Reactの基礎も紹介しています。Reactの学習を始められた同志のお力になれると幸いです!(^。^)
こちらの記事ではAngularの「Vueで言うところの...」を紹介しています。是非併せてご覧ください!✨
Reactとは
ReactはFacebook(現Meta)によって開発されたオープンソースのJavaScriptフレームワークです。2013年に初版がリリースされ、2022年3月に、最新版となるバージョン18がリリースされました(2023年5月現在)。
JavaScriptフレームワークという紹介をしましたが、公式ページでも「ユーザインターフェース構築のための JavaScript ライブラリ」と書かれているので、正確にはReactはライブラリであるとされています。Reactで開発を行う際はサードパーティ制のライブラリを併せて使用することが一般的で、React単体で使用することは無いことが理由とされています。
公式ドキュメントはこちら
Reactアプリケーションの起動方法
Reactのアプリケーションを作成していきます。以下コマンドを実行し、Reactのアプリケーションの起動を行なってください。
npx create-react-app react-tutorial
作成したディレクトリに移動し、開発サーバーを起動します。
cd react-tutorial
npm run start
上記のコマンドが正常すると以下のようなログが出力されます。Local
に表示されるURLにアクセスしてReactアプリケーションの画面が表示されれば、完了です。
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.73.117:8080/
コンポーネント基礎
Reactのコンポーネントには、関数コンポーネントとクラスコンポーネントがあります。これらは構文が色々と違います。これらの違いはこちらの記事で分かりやすく解説されております。
どちらを使用すれば良いか迷うところですが、記述量が少ないなどの理由から、一般的には関数コンポーネントが推奨されています。本記事でも関数コンポーネントで進めますので、ご了承ください。
import React from "react";
function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}
export default Home;
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<h1>Home</h1>
</div>
);
}
}
export default Home;
コンポーネントに独自のスタイルを定義する際は、CSSファイルを別途用意します。CSSファイルはコンポーネントと同名にするのが一般的です。コンポーネントからCSSファイルをimport
することでスタイルを取り込みます。
.home-wrapper {
color: rgb(130, 215, 247);
}
import "./Home.css";
function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}
export default Home;
Reactでは、HTMLタグにクラスを適用する際は、class
ではなくclassName
を使用します。コンポーネントで記述されているHTMLはJSXによって表現されているものであり、あくまでJavaScriptです。その為JavaScriptの予約語であるclass
は使用できないことが理由となっています。
Vueで言うところのdata
Vueで言うところのdata
は、ReactではStateと呼ばれます。useState
を使用することで、Vueのdata
のようなリアクティブな値(State)を定義することができます。useState
は参照用の値と、値更新用の関数を返すメソッドです。その為、分割代入を利用して値と値更新用関数を定義することが多いです。引数には、初期値を渡します。
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
通常の関数を定義する形で表現します。
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;
注意点として、クリックイベントなどで定義した関数を指定する際は、()
を付与しないようにしてください。()
を付与すると初期レンダリングのみ関数が実行され、その後クリックしても関数が実行されなくなります。
その為、引数を渡す場合は少し工夫が必要です。以下のように、関数に引数を渡して実行する関数をイベントハンドラとして指定するような実装にする必要があります。
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回のみしかコンソールに出力されません。
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
関数コンポーネントの引数として渡された値を用いて親コンポーネントから子コンポーネントに値を渡すことができます。
import React from "react";
function Home(props) {
return (
<div>
<h1>Home</h1>
<p>Name: {props.name}</p> {/* Whopper */}
</div>
);
}
export default Home;
import Home from './components/Home/Home';
function App() {
return (
<div>
<Home name="Whopper"/>
</div>
);
}
export default App;
以下のように分割代入を用いてpropsとして受け取る値を明示でき、props.
を省略できるのでお勧めです。
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
を実行、親コンポーネントのイベントで渡された引数をコンポーネントのデータに反映しています。
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;
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
の変更を検知してログを出力しています。
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>
タグを削除することで解除できます。
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
は
毎回このような実装をするのが面倒な場合は、新たにモジュールとして切り出すことを検討しても良さそうです。
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
外部コンポーネントをインポートするだけで使用することができます。
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
を実行しています。
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
を入力フォームにバインディングしています。
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でループ処理を記述します。
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;
以下のように、あらかじめ定義した配列をそのまま表示する方法もあります。
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
要素の表示に条件をつける方法はいくつかありますが、参考演算子を使用した出し分けが記述量が少なくお勧めします。
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;
複雑な条件の場合は、三項演算子ではなく、出し分け処理を関数として切り出す方法もあります。
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;
条件付きレンダリングの方法が公式ドキュメントで紹介されていますので併せてご覧ください。
Vueで言うところのcreated
/mounted
Vueで言うところのwatch
で紹介したuseEffect()
を利用することで、画面遷移時の初期処理を定義することができます。watch
を再現する場合は第二引数に監視対象を配列形式で指定しましたが、これを空配列にすることで画面遷移時に一度のみ実行されるようになります。
import React from "react";
function Home() {
useEffect(() => {
console.log("Init");
}, []);
return (
<div>
<h1>Home</h1>
</div>
);
}
export default Home;
ルーティング
初期設定
Reactでルーティングを導入するには、ルーティングライブラリReactRouterを使用します。公式ドキュメントはこちらです。2023年5月現在最新バージョンが6で、バージョン5と記述方法が変更になっているものが多くあります。こちらの記事で変更点が解説されていますので、併せてご覧ください。
以下コマンドでインストールを実行してください。
npm i react-router-dom
ルートはsrc/App.js
にて定義します。ReactRouterの<BrowserRouter>
、<Routes>
、<Route>
タグを使用してルートを定義していきます。存在しないパスにアクセスした際に404などページを表示したい場合はpath
を/*
にすることで定義できます。
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
に指定したパスと一致するルートに遷移します。
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()
を使用することで、プログラムから画面遷移を行うことができます。useNavigate()
の返り値がそのまま画面遷移用のメソッドとなります。引数に指定したパスと一致するルートに遷移します。
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;