『レンダリングとマウント』をJavaScript、React、Nextjsで比較
下記の記事はClaudeに質問して出力してもらった内容を、自分で理解しながら追記/修正したものになります。
(Claudeによる出力)
↓↓↓↓ここから↓↓↓↓
はじめに
Reactを学び始めると、「レンダリング」や「マウント」といった用語に出会います。これらは似ているようで異なる概念であり、しっかり理解することでより効率的なReactアプリケーションが作れるようになります。
この記事では、JavaScriptの基本から始めて、React、そしてNext.jsでの「レンダリング」と「マウント」の違いを初心者にもわかりやすく解説します。
目次
- 基本概念:レンダリングとマウントとは?
- JavaScriptでの要素表示の仕組み
- Reactにおけるレンダリングとマウント
- Next.jsの特徴とレンダリング戦略
- 実践:コーディング時に意識すべきポイント
- よくある問題とその解決法
- まとめ
基本概念:レンダリングとマウントとは?
レンダリングとは
レンダリングとは、簡単に言えば「画面に表示するための準備をする過程」です。
- JavaScriptでは: HTML要素を作成してDOMに追加する作業
- Reactでは: コンポーネントの状態から仮想DOMを計算する作業
- Next.jsでは: サーバーまたはビルド時にHTMLを生成する作業
マウントとは
マウントとは、「準備したものを実際に画面に表示する過程」です。
- JavaScriptでは: 明確な「マウント」という概念はなく、DOM要素の追加と同義
- Reactでは: 仮想DOMを実際のDOMに反映させる作業
- Next.jsでは: サーバーレンダリングされたHTMLをクライアント側で「ハイドレーション」する作業
(追記)用語解説
■仮想DOM・・・JavaScript上で実際のDOM(Document Object Model)を抽象化した軽量なコピーを指します。React や Vue.js などのフレームワークで使われ、効率的なUI更新を実現するための仕組み。
<仮想DOMを使うメリット>
- パフォーマンスの向上
直接DOMを操作すると遅くなるが、仮想DOMを使うことで最小限の更新で済む。 - コードのシンプル化
DOM操作を直接書く必要がなくなり、状態管理が簡単になる。 - バグを減らせる
直接DOMを触るより安全で、意図しない更新を防げる。
■ハイドレーション・・・サーバーサイドレンダリング(SSR)で生成されたHTMLに、クライアント側でReactのイベントや状態を復元し、インタラクティブにするプロセス のこと。(仕組みについて、後述があります)
<ハイドレーションのメリット>
✅ 高速な初回表示
→ SSRによって最初のHTMLがすぐに描画される。
✅ SEOに強い
→ HTMLが事前に生成されるため、Googleのクローラーが認識しやすい。
✅ クライアント側の負荷を軽減
→ 必要な部分のみReactで管理することで、クライアントの処理負担が減る。
JavaScriptでの要素表示の仕組み
JavaScriptでは、DOM API を使って要素を作成し、表示します。
// 新しい要素を作成
const newElement = document.createElement('div');
newElement.textContent = 'こんにちは!';
// 作成した要素をDOMに追加(これで画面に表示される)
document.getElementById('container').appendChild(newElement);
注意点:
- DOM操作は比較的重い処理
- 大量のDOM操作はパフォーマンスに影響する
初心者向けTips:
- 大量の要素を追加する場合は、
DocumentFragment
を使うとパフォーマンスが向上します - JavaScriptファイルの読み込みはレンダリングをブロックするので、
defer
やasync
属性の使用を検討しましょう
Reactにおけるレンダリングとマウント
Reactでは、「レンダリング」と「マウント」が明確に分かれています。
Reactのレンダリングプロセス
- コンポーネントが評価される(関数コンポーネントが実行される)
- JSXが仮想DOM(Virtual DOM)に変換される
- 前回の仮想DOMと比較される(差分検出)
function Greeting({ name }) {
return <h1>こんにちは、{name}さん!</h1>;
}
// このコンポーネントが評価されるとレンダリングが行われる
Reactのマウントプロセス
- 差分が検出されたら、実際のDOMを更新する
- 更新されたDOMが画面に反映される
// 初回マウント
ReactDOM.render(<App />, document.getElementById('root'));
// React 18以降
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
重要なライフサイクル
コンポーネントには、以下のような「ライフサイクル」があります:
- マウント時:コンポーネントが初めてDOMに追加される
- 更新時:propsや状態が変わってコンポーネントが再レンダリングされる
- アンマウント時:コンポーネントがDOMから削除される
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
console.log('コンポーネントがマウントされました');
return () => {
console.log('コンポーネントがアンマウントされます');
};
}, []); // 空の依存配列でマウント時のみ実行
return <div>Hello React</div>;
}
初心者向けTips:
-
useEffect
の依存配列が空の場合、マウント時のみ実行されます - DOM操作や外部APIとの通信は
useEffect
内で行いましょう - レンダリング中に副作用(APIコールなど)を起こさないようにしましょう
Next.jsの特徴とレンダリング戦略
Next.jsは、Reactをベースに、サーバーサイドレンダリング(SSR)や静的サイト生成(SSG)を簡単に実装できるフレームワークです。
Next.jsのレンダリング方式
-
サーバーサイドレンダリング (SSR)
- サーバーでHTMLを生成し、クライアントに送信
- ユーザーごとに異なるコンテンツに最適
// pages/ssr-page.js export async function getServerSideProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } function SSRPage({ data }) { return <div>{data.title}</div>; }
-
静的サイト生成 (SSG)
- ビルド時にHTMLを生成
- ブログ記事など、頻繁に変更されないコンテンツに最適
// pages/ssg-page.js export async function getStaticProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } function SSGPage({ data }) { return <div>{data.title}</div>; }
-
クライアントサイドレンダリング (CSR)
- 通常のReactと同じように、クライアント側でレンダリング
// pages/csr-page.js import { useEffect, useState } from 'react'; function CSRPage() { const [data, setData] = useState(null); useEffect(() => { async function fetchData() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); setData(data); } fetchData(); }, []); if (!data) return <div>Loading...</div>; return <div>{data.title}</div>; }
ハイドレーション
Next.jsでは、サーバーからHTMLを受け取った後、Reactがそのマークアップを「ハイドレーション」します。これは、静的なHTMLにイベントリスナーなどの機能を付与するプロセスです。
ハイドレーションのステップ:
- サーバーから生成されたHTMLを受け取る
- JavaScriptが読み込まれる
- Reactがサーバーで生成されたDOMと一致するように仮想DOMを構築
- イベントリスナーを追加し、インタラクティブにする
初心者向けTips:
- ハイドレーションエラーを避けるため、サーバーとクライアントで生成されるHTMLが一致するようにする
-
window
やdocument
などのブラウザAPI使用時には注意が必要(サーバーでは使えない)
実践:コーディング時に意識すべきポイント
レンダリングの最適化
-
不要な再レンダリングを防ぐ
// コンポーネントのメモ化 const MemoizedComponent = React.memo(MyComponent); // 計算値のメモ化 const calculatedValue = useMemo(() => expensiveCalculation(a, b), [a, b]); // コールバック関数のメモ化 const handleClick = useCallback(() => { console.log('クリックされました'); }, []);
-
状態の更新を最小限に
// 悪い例 const [user, setUser] = useState({ name: 'Taro', age: 25 }); // 一部だけ更新したい場合でも全体を更新してしまう setUser({ name: 'Jiro', age: 25 }); // 良い例 setUser(prevUser => ({ ...prevUser, name: 'Jiro' }));
マウント時の処理
-
DOMの参照を取得する
import { useRef, useEffect } from 'react'; function MyComponent() { const myElementRef = useRef(null); useEffect(() => { // マウント後にDOMノードにアクセス可能 if (myElementRef.current) { myElementRef.current.focus(); } }, []); return <input ref={myElementRef} />; }
-
クリーンアップ関数の重要性
useEffect(() => { const subscription = someAPI.subscribe(); // クリーンアップ関数(アンマウント時に実行される) return () => { subscription.unsubscribe(); }; }, []);
Next.jsでのデータ取得タイミング
// SSRでのデータ取得
export async function getServerSideProps() {
// リクエストごとに実行される
return { props: { /* データ */ } };
}
// SSGでのデータ取得
export async function getStaticProps() {
// ビルド時のみ実行される
return { props: { /* データ */ } };
}
// クライアントサイドでのデータ取得
function ClientComponent() {
const { data, error } = useSWR('/api/data', fetcher);
// ...
}
よくある問題とその解決法
1. 無限ループに陥るuseEffect
// 問題のあるコード
function BadComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // 状態が更新されるとuseEffectが再実行される
}); // 依存配列がない!
return <div>{count}</div>;
}
// 解決策
function GoodComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// 初回のみ実行
setCount(prevCount => prevCount + 1);
}, []); // 空の依存配列
return <div>{count}</div>;
}
2. ハイドレーションエラー
// 問題のあるコード
function DateComponent() {
// サーバーとクライアントで異なる値になる
return <div>{new Date().toLocaleString()}</div>;
}
// 解決策
function SafeDateComponent() {
const [date, setDate] = useState(null);
useEffect(() => {
setDate(new Date().toLocaleString());
}, []);
return <div>{date || 'Loading...'}</div>;
}
3. パフォーマンス問題
// 問題のあるコード
function ExpensiveComponent({ data }) {
// データが変わるたびに高コストな計算
const processedData = expensiveOperation(data);
return <div>{processedData}</div>;
}
// 解決策
function OptimizedComponent({ data }) {
// データが変わった場合のみ計算
const processedData = useMemo(() => {
return expensiveOperation(data);
}, [data]);
return <div>{processedData}</div>;
}
まとめ
Reactと Next.js でアプリケーションを開発する際、レンダリングとマウントの違いを理解することは非常に重要です。
覚えておくべきポイント
- レンダリング はUIを計算する過程、マウント は計算したUIを実際のDOMに反映する過程
- useEffect の依存配列を正しく設定し、無限ループを避ける
- サーバーサイドとクライアントサイドの違いを理解し、ハイドレーションエラーを防ぐ
- メモ化(React.memo, useMemo, useCallback)を活用して最適化する
- 適切なレンダリング戦略(SSR, SSG, CSR)を選択する
Reactでの開発経験を積みながら、これらの概念をより深く理解していきましょう。最初は難しく感じるかもしれませんが、少しずつ実践していくことで、効率的で高パフォーマンスなアプリケーションを構築できるようになります。
参考資料
(Claudeによる出力)
↑↑↑↑ここまで↑↑↑↑
さいごに
以上がClaudeに解説してもらった内容になります。
ブログサイトを作成する際の技術スタックとしてNextjsを選択したのですが、あまりCSR/SSGとSSRの使い分けができておらず無駄な部分は結構ありそう・・・。かといって直すにはまだ知識不足な気がします。継続して学習していきたいと思います。
Discussion