はじめに
前回はReact超基本的な機能や使い方について学びました。今回は、より発展的なHookの使い方やSPAについて深堀りしていきます。
また、最後にTailwind CSSを紹介・導入し、簡単なポートフォリオサイトを作成してみましょう。
ReactにおけるSPAについて
突然ですが、質問です。
前回の講義で、ページ遷移(/about
にアクセスなど)を学びましたが、これまでの知識で複数のページをもつサイトを作るにはみなさんならどうしますか?
考えられる回答としては、
index.html
を作って、about.html
を作って、profile.html
を作って...<a>
タグでリンクを張る...
my-website/
├── index.html
└── pages/
├── about.html
└── profile.html
というようにページ毎にHTMLファイルを作成する方法が思いつくのではないでしょうか?
そうすると、
- ユーザーがリンクをクリック
- ブラウザがサーバーに対して、リンク先のHTMLファイルをリクエスト
- サーバーがリンク先のHTMLファイルを返す
- ブラウザがレンダリングして画面を表示
という一連の流れが行われます。
ページごとに独立したHTMLファイルがあるためリソースを再取得するために、ページ遷移が遅くなるという問題があります。
では、Reactではどのようにページ遷移を行うのでしょうか?
前回の講義で使用したfirst-react-app
プロジェクトを開き以下のコマンドを実行してみましょう。
npm run build
このコマンドは、私たちが編集しているいわゆるデバッグモードのコードを、本番環境で使えるように最適化したコードに変換するコマンドです。
dist
ディレクトリが作成され、その中にindex.html
、assets
ディレクトリが作成されていることが確認できると思います。サイトを公開する際はこのdist
ディレクトリの中身しか使いません。
そのなかのindex.html
を開いてみましょう。
前回作ったWebサイトはこのようにたった1つのHTMLファイルのみで構成されており、ページ遷移も実装されています。
では、どのようにして動的なWebページを実現しているのでしょうか?
dist/assets
ディレクトリの中に、.js
ファイルがあるので見てみましょう。
このように、1つの.js
ファイルに全てのコードがまとめられています。このファイルがReactの機能を全うしており、たった1つのHTMLファイル、1つの.js
ファイルでWebサイトを構築しているのです。
このように、1つのHTMLファイルのみで構成されたWebサイトを**SPA(Single Page Application)**と呼びます。
SPAは、クライアント側の操作によって新たに必要となった差分のデータのみをHTTPリクエストで取得し、ページ全体を再読み込みすることなく、差分のみを更新することができるため、ページ遷移が高速であるという特徴があります。ただし、JSのコードが大きくなるので、初回読み込み時のページ表示は遅くなります。
他にも、SPAではブラウザで行われていたJSの実行とHTML生成をサーバー側で行うSSR(Server Side Rendering)という手法もあり、Next.jsなどのフレームワークで提供されています。
Hooksの紹介
フック(hook)は前回も説明したように、関数コンポーネントでも簡単に「状態」を持たせたり、「動作」を追加できる仕組みです。これを使えば、複雑なクラスを書かなくても、Reactの便利な機能を利用できるようになります。
Reactには、他にも様々なフックが用意されていますが、復習も兼ねて以下のフックについて紹介します。
useState
:状態を持たせるためのフックuseEffect
:コンポーネントや値がマウントされたり更新されたりしたときに実行する処理を追加するためのフックuseMemo
:値をメモリ上に保存するためのフック
useState
の使い方(おさらい)
useState
は、状態を持たせるためのフックです。(前回のおさらいなので説明は省略)
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
}
return (
<div className="App">
<h1>useState</h1>
<p>{count}</p>
<button onClick={handleClick}>+</button>
</div>
);
};
export default App;
useEffect
の使い方
useEffect
は、第1引数にコールバック関数、第2引数に依存する値の配列を受け取ります。値が更新されたりコンポーネントがマウント(初回レンダリング時)、アンマウント(レンダリングの対象外となったとき)されたときに実行する処理を追加するためのフックです。
簡単に言うと、処理の実行を自由に制御できるフックです。
import { useEffect } from "react";
useEffect
をインポートしたうえで以下のコードを見てみましょう。
useEffect(() => {
console.log('Hello, useEffect');
}, []);
第2引数[]
の部分が空になっている時は、ページ全体がリロードされたときに実行されます。
実際に試してみよう
App.jsx
に以下のコードを追加して、コンソールログで確認してみましょう。
import { useEffect } from 'react';
function App() {
useEffect(() => {
console.log('Hello, useEffect');
},[]);
return (
<div className="App">
<h1>useEffect</h1>
<p>コンソールログで確認してみましょう</p>
</div>
);
};
export default App;
それでは、useState
とuseEffect
を使ってcount
の値が変更されたときにログを出力する処理を書いてみましょう。
import { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
}
useEffect(() => {
console.log('ボタンが押されました');
},[count]);
return (
<div className="App">
<h1>useState, useEffect</h1>
<p>{count}</p>
<button onClick={handleClick}>+</button>
</div>
);
};
export default App;
count
の値が変わったときにuseEffect
が実行されるようになりました。(ボタンを押したときに処理が実行されているという訳ではないので注意)
このように、何かの値や状態が変わったときに処理が実行されるので、useEffect
は副作用と呼ばれることがあります。
useMemo
の使い方
useMemo
は値をブラウザのメモリ上に保存するためのフックです。(関数自体をメモリ上に保存するuseCallback
もあります)
まずは、以下のコードを見てみましょう。
import { useState } from "react";
function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const square = () => {
return count2 * count2;
}
return (
<div className="App">
<h1>useMemo</h1>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<p>square:{square()}</p>
<button onClick={() => setCount1(count1 + 1)}>Increment Count 1</button>
<button onClick={() => setCount2(count2 + 1)}>Increment Count 2</button>
</div>
);
};
export default App;
count1
とcount2
の2つのカウンターと、count2
の値を2乗する処理が書かれています。実際に動かしてみましょう。
ここで、square
関数内に重い処理を追加してみましょう。
const square = () => {
let i = 0;
while (i < 1000000000) {
i++;
}
return count2 * count2;
}
実際に動かしてみると、count2
だけでなく、count1
の値が増えた時も処理が重たくなってしまいます。
これは、仮想DOMにおいてcount1
の値が更新されたときにも、その差分を計算するためにsquare
関数を注目してしまうためです。
そこで、useMemo
を使ってsquare
関数の処理をメモ化し、再度呼ばれたときは保存した(メモ化した)値を返すようにしましょう。
import { useMemo } from "react";
useMemo
をインポートしたうえで、メモ化したい処理をuseMemo
で囲み、第2引数に依存する値を指定します。
import { useMemo, useState } from "react";
function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const square = useMemo(() => {
let i = 0;
while (i < 1000000000) {
i++;
}
return count2 * count2;
}, [count2]);
return (
<div className="App">
<h1>useMemo</h1>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<p>square:{square}</p>
<button onClick={() => setCount1(count1 + 1)}>Increment Count 1</button>
<button onClick={() => setCount2(count2 + 1)}>Increment Count 2</button>
</div>
);
};
export default App;
これで、count1
の値が更新されたときには、square
関数は再計算されず、メモ化された値が返されるようになりました。
useMemo
は、アプリケーションのパフォーマンスを向上させるために使われることが多く、大規模なアプリケーションを作る際に不要な処理を省く際に使われます。個人開発などの小規模なアプリケーションではあまり使う機会はないかもしれません。
では、useMemo
をたくさん使えばいいのかというとそうではありません。useMemo
を使いすぎるとブラウザ上のメモリを圧迫してしまい、かえってパフォーマンスが悪くなってしまうので注意しましょう。
Tailwind CSSの導入
Tailwind CSSは、CSSを書かずにデザインを整えることができるCSSフレームワークです。
まずは、first-react-app
プロジェクトにTailwind CSSを導入してみましょう。
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
ファイルが作成されるので、以下のように設定を追加します。
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
index.css
を以下のように書き換えます。
@tailwind base;
@tailwind components;
@tailwind utilities;
これで、Tailwind CSSを使う準備が整いました。
Tailwind CSSの使い方
Tailwind CSSは、HTML要素にクラスを追加するだけでデザインを整えることができます。例えば、以下のようなコードを書くことで、ボタンを作成することができます。
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
bg-blue-500
:背景色を青色にするhover:bg-blue-700
:マウスを乗せたときに背景色を濃い青色にするtext-white
:文字色を白色にするfont-bold
:文字を太字にするpy-2
:上下のpaddingを0.5rem(8px)にするpx-4
:左右のpaddingを1rem(16px)にするrounded
:角を丸くする
ドキュメントに使い方が詳しく書かれているので、参考にしてみてください。
基本的に、コンポーネント要素のデザインを指定する際は、className
属性の部分を書き換えるだけで大丈夫です。
ポートフォリオサイトを作ってみよう
それでは、ReactとTailwind CSSを使って簡単なポートフォリオサイトを作成してみましょう。
まず、src
ディレクトリ内にComponents
ディレクトリを作成し、その中にHeader.jsx
、Footer.jsx
、Projects.jsx
、Contact.jsx
を作成します。
ヘッダーの作成
export default function Header() {
return (
<header className="bg-gray-800 text-white text-center py-10">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold">My Portfolio</h1>
<p className="mt-4 text-lg">
Welcome to my personal portfolio website built with React and Tailwind
CSS.
</p>
</div>
</header>
);
}
フッターの作成
export default function Footer() {
return (
<footer className="bg-gray-800 text-white text-center py-4">
<div className="max-w-4xl mx-auto">
<p className="mb-0">
© 2024 My Portfolio. All Rights Reserved.
</p>
</div>
</footer>
);
}
コンテンツ部分の作成
import { Link } from "react-router-dom";
export default function Projects() {
return (
<section id="projects" className="py-10 bg-gray-100">
<div className="max-w-6xl mx-auto">
<h2 className="text-3xl font-bold text-center mb-6">My Projects</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<img
src="https://via.placeholder.com/150"
alt="Project 1"
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h5 className="text-xl font-bold">Project 1</h5>
<p className="mt-2 text-gray-600">
A brief description of the project.
</p>
<Link
href="#"
className="mt-4 inline-block text-indigo-500 hover:underline"
>
View Project
</Link>
</div>
</div>
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<img
src="https://via.placeholder.com/150"
alt="Project 2"
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h5 className="text-xl font-bold">Project 2</h5>
<p className="mt-2 text-gray-600">
A brief description of the project.
</p>
<Link
href="#"
className="mt-4 inline-block text-indigo-500 hover:underline"
>
View Project
</Link>
</div>
</div>
<div className="bg-white shadow-md rounded-lg overflow-hidden">
<img
src="https://via.placeholder.com/150"
alt="Project 3"
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h5 className="text-xl font-bold">Project 3</h5>
<p className="mt-2 text-gray-600">
A brief description of the project.
</p>
<Link
href="#"
className="mt-4 inline-block text-indigo-500 hover:underline"
>
View Project
</Link>
</div>
</div>
</div>
</div>
</section>
);
}
問い合わせフォームの作成
export default function Contact() {
return (
<section id="contact" className="py-10">
<div className="max-w-4xl mx-auto">
<h2 className="text-3xl font-bold text-center mb-6">Contact Me</h2>
<form className="space-y-6">
<div>
<label htmlFor="name" className="block text-gray-700 font-medium">
Name
</label>
<input
type="text"
id="name"
className="w-full mt-2 p-3 border border-gray-300 rounded-lg"
placeholder="Your name"
/>
</div>
<div>
<label htmlFor="email" className="block text-gray-700 font-medium">
Email
</label>
<input
type="email"
id="email"
className="w-full mt-2 p-3 border border-gray-300 rounded-lg"
placeholder="Your email"
/>
</div>
<div>
<label
htmlFor="message"
className="block text-gray-700 font-medium"
>
Message
</label>
<textarea
id="message"
rows="5"
className="w-full mt-2 p-3 border border-gray-300 rounded-lg"
placeholder="Your message"
></textarea>
</div>
<button
type="submit"
className="w-full bg-indigo-500 text-white py-3 rounded-lg hover:bg-indigo-600"
>
Send
</button>
</form>
</div>
</section>
);
}
トップページの仕上げ
分割したコンポーネントをApp.jsx
に組み込みます。
import Header from "./Component/Header";
このように記述することで、別のファイルに分割されたコンポーネントを読み込むことができます。
ヘッダー、フッター、コンテンツ部分、問い合わせフォームを読み込んで、トップページを完成させましょう。
import Header from "./Component/Header";
import Footer from "./Component/Footer";
import Contact from "./Component/Contact";
import Projects from "./Component/Projects";
function App() {
return (
<div>
<Header />
<section id="about" className="py-10">
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl font-bold mb-6">About Me</h2>
<p className="text-lg">
Hello! I am a web developer with a passion for creating beautiful
and functional websites.
</p>
</div>
</section>
<Projects />
<Contact />
<Footer />
</div>
);
}
export default App;
ブラウザ上でサイトが表示できることを確認したら、適宜機能を追加したり、デザインを変えてみましょう。
ドキュメントを参考にしてデザインを変えてみましょう。
paddingやmarginなどの余白の指定方法:
widthやheightなどのサイズ指定方法:
まとめ
今回は、ReactのHookやSPA,SSRについて学び、Tailwind CSSを導入して簡単なポートフォリオサイトを作成しました。
次回のWeb研の講義は、冬休み明けになります... それでは、良いお年を!