3KB の軽量 React 代替 - Preact で始める高速 Web アプリ開発

Webアプリケーション開発において、パフォーマンスと開発効率のバランスは常に重要な課題です。 Reactは素晴らしいライブラリですが、ファイルサイズやパフォーマンスが気になる場面もあるでしょう。
そんな悩みを解決してくれるのがPreactです。 わずか3KBの軽量サイズでありながら、Reactとほぼ同じ機能と使い心地を提供してくれます。
Preactとは何か
PreactはJason Miller氏によって開発された、React互換のJavaScriptライブラリです。 2015年にリリースされて以来、軽量でありながら高機能なライブラリとして多くの開発者に愛用されています。
Preactは「最小限のReact」をコンセプトとして設計されており、Reactの主要機能をサポートしながらも大幅にサイズを削減することに成功しました。 その結果、モバイル環境や低帯域幅な環境でも快適に動作するWebアプリケーションを作成できます。
Preactが解決する問題
現代のWeb開発では、以下のような課題が存在します。
課題 | 説明 | 影響 |
---|---|---|
1 | バンドルサイズの肥大化 | ページ読み込み速度の低下 |
2 | 初期表示の遅延 | ユーザー体験の悪化 |
3 | モバイル環境での動作重量 | パフォーマンス問題 |
4 | 学習コストの高さ | 開発効率の低下 |
Preactはこれらの課題を、軽量性と互換性の両立によって解決します。
ReactとPreactの違い
ReactとPreactには重要な違いがいくつか存在しますが、基本的な開発体験は非常に似ています。 主な違いを理解することで、プロジェクトに最適な選択ができるでしょう。
サイズの比較
最も顕著な違いはファイルサイズです。
ライブラリ | gzip圧縮後のサイズ | 備考 |
---|---|---|
1 | React + ReactDOM | 約42KB |
2 | Preact | 約3KB |
3 | Preact/compat | 約4KB |
この差は約14倍にも及び、特にモバイルアプリケーションやプログレッシブWebアプリ(PWA)において大きなアドバンテージとなります。
APIの違い
PreactはReactのコアAPIをほぼそのままサポートしていますが、いくつかの違いがあります。
typescript// React
import React from 'react';
import ReactDOM from 'react-dom';
// Preact
import { render } from 'preact';
主な違いは以下の通りです。
機能 | React | Preact | 互換性 |
---|---|---|---|
1 | className | class | preact/compat で互換 |
2 | React.Fragment | Fragment | 同等 |
3 | useEffect | useEffect | 同等 |
4 | useState | useState | 同等 |
対応ブラウザ
PreactはIE11を含む幅広いブラウザをサポートしており、レガシー環境でも安心して使用できます。
Preactのメリット
Preactを採用することで得られるメリットは多岐にわたります。 特に以下の3つの観点から大きなメリットを享受できるでしょう。
ファイルサイズの軽量性
最大のメリットは圧倒的な軽量性です。
わずか3KBという軽量さは、ネットワーク転送時間を大幅に短縮し、初期表示速度を向上させます。 特にモバイル環境や帯域制限のある環境では、この差が顕著に現れるでしょう。
javascript// バンドルサイズの比較例
// React: vendor.js (200KB) + app.js (50KB) = 250KB
// Preact: vendor.js (50KB) + app.js (50KB) = 100KB
// 約60%のサイズ削減を実現
このサイズ削減により、以下の効果が期待できます。
- ページ読み込み時間の短縮(約2-3倍高速化)
- モバイルデータ通信量の削減
- キャッシュ効率の向上
パフォーマンスの向上
軽量性は単にファイルサイズの問題だけではありません。 実行時のパフォーマンスも大幅に向上します。
PreactはReactよりも高速な仮想DOM実装を持っており、レンダリング処理が効率的に行われます。
javascript// パフォーマンス測定の例
const startTime = performance.now();
// Preact コンポーネントのレンダリング
render(<App />, document.body);
const endTime = performance.now();
console.log(`レンダリング時間: ${endTime - startTime}ms`);
// Reactと比較して20-30%高速
特に以下のような場面でパフォーマンスの違いが顕著に現れます。
シナリオ | パフォーマンス向上 | 理由 |
---|---|---|
1 | 初期表示 | 30-40%高速 |
2 | 再レンダリング | 20-30%高速 |
3 | メモリ使用量 | 40-50%削減 |
React互換性
PreactはReact互換性を重視して設計されており、既存のReactコードの多くをそのまま使用できます。
javascript// preact/compatを使用することで
// Reactライブラリとの互換性を確保
import { render } from 'preact/compat';
import { useState, useEffect } from 'preact/hooks';
// Reactと同じように記述可能
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `カウント: ${count}`;
}, [count]);
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>
増加
</button>
</div>
);
}
この互換性により、以下のメリットを享受できます。
- 既存のReactスキルをそのまま活用
- 豊富なReactエコシステムの利用
- 段階的な移行が可能
開発環境のセットアップ
Preactの開発環境をセットアップしましょう。 いくつかの方法がありますが、最も効率的な方法をご紹介します。
Preact CLIを使用したセットアップ
Preact CLIは公式が提供する開発ツールで、プロジェクトの初期化から本番ビルドまでを一貫してサポートします。
bash# Preact CLIのインストール
yarn global add preact-cli
# 新しいプロジェクトの作成
preact create my-preact-app
cd my-preact-app
この方法では、以下の設定が自動で完了します。
設定項目 | 内容 | 備考 |
---|---|---|
1 | Webpack設定 | 最適化済み |
2 | TypeScript対応 | 標準サポート |
3 | PWA対応 | Service Worker |
4 | Hot Reload | 開発効率化 |
手動でのセットアップ
既存プロジェクトにPreactを追加する場合は、手動でセットアップを行います。
bash# Preactのインストール
yarn add preact
# 開発用パッケージのインストール
yarn add -D @babel/core @babel/preset-env
yarn add -D webpack webpack-cli webpack-dev-server
yarn add -D babel-loader
Webpack設定ファイルを作成します。
javascript// webpack.config.js
module.exports = {
entry: './src/index.js',
mode: 'development',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-transform-react-jsx', { pragma: 'h' }]
]
}
}
}
]
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'react': 'preact/compat',
'react-dom': 'preact/compat'
}
},
devServer: {
contentBase: './dist',
port: 3000,
hot: true
}
};
プロジェクト構造
推奨されるプロジェクト構造は以下の通りです。
csharpmy-preact-app/
├── src/
│ ├── components/ # 再利用可能なコンポーネント
│ ├── routes/ # ページコンポーネント
│ ├── assets/ # 静的ファイル
│ ├── style/ # スタイルシート
│ └── index.js # エントリーポイント
├── public/
│ └── index.html # HTMLテンプレート
└── package.json
基本的な使い方
Preactの基本的な使い方をマスターしましょう。 ReactとほぼD同じ感覚で開発できるため、学習コストは最小限に抑えられます。
コンポーネント作成
最初に、シンプルなコンポーネントから始めましょう。
javascript// src/components/Welcome.js
import { h } from 'preact';
// 関数コンポーネントの作成
function Welcome(props) {
return (
<div>
<h1>こんにちは、{props.name}さん!</h1>
<p>Preactへようこそ</p>
</div>
);
}
export default Welcome;
クラスコンポーネントも同様に作成できます。
javascript// src/components/Counter.js
import { Component } from 'preact';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<p>カウント: {this.state.count}</p>
<button onClick={this.increment}>
クリック
</button>
</div>
);
}
}
export default Counter;
JSX記法
PreactではJSX記法をフルサポートしており、Reactと同じ感覚で記述できます。
javascript// JSXの基本的な使い方
import { h, render } from 'preact';
import { useState } from 'preact/hooks';
function TodoItem({ todo, onToggle, onDelete }) {
return (
<div className={`todo-item ${todo.completed ? 'completed' : ''}`}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span className="todo-text">{todo.text}</span>
<button
className="delete-btn"
onClick={() => onDelete(todo.id)}
>
削除
</button>
</div>
);
}
イベントハンドリングも直感的に記述できます。
javascriptfunction InputForm({ onSubmit }) {
const [inputValue, setInputValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (inputValue.trim()) {
onSubmit(inputValue.trim());
setInputValue('');
}
};
return (
<form onSubmit={handleSubmit} className="input-form">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="新しいタスクを入力"
className="task-input"
/>
<button type="submit" className="submit-btn">
追加
</button>
</form>
);
}
propsとstate
propsとstateの概念はReactと全く同じです。
javascript// propsを受け取るコンポーネント
function UserProfile({ user, onEditClick }) {
return (
<div className="user-profile">
<img
src={user.avatar}
alt={`${user.name}のアバター`}
className="avatar"
/>
<div className="user-info">
<h2>{user.name}</h2>
<p>{user.email}</p>
<p>登録日: {user.registeredAt}</p>
<button onClick={onEditClick} className="edit-btn">
編集
</button>
</div>
</div>
);
}
Hooksを使用した状態管理も簡単です。
javascriptimport { useState, useEffect } from 'preact/hooks';
function UserSettings() {
const [settings, setSettings] = useState({
theme: 'light',
notifications: true,
language: 'ja'
});
const [isLoading, setIsLoading] = useState(true);
// 設定の読み込み
useEffect(() => {
const loadSettings = async () => {
try {
const response = await fetch('/api/user/settings');
const data = await response.json();
setSettings(data);
} catch (error) {
console.error('設定の読み込みに失敗:', error);
} finally {
setIsLoading(false);
}
};
loadSettings();
}, []);
const updateSetting = (key, value) => {
setSettings(prev => ({
...prev,
[key]: value
}));
};
if (isLoading) {
return <div className="loading">設定を読み込み中...</div>;
}
return (
<div className="user-settings">
<h2>ユーザー設定</h2>
{/* 設定フォームの実装 */}
</div>
);
}
実践的な例:ToDoアプリの作成
実際にToDoアプリを作成して、Preactの機能を体験してみましょう。 この例では、状態管理、イベント処理、コンポーネント間のデータのやり取りを学びます。
アプリケーションの設計
まず、ToDoアプリに必要な機能を整理します。
機能 | 説明 | 実装方法 |
---|---|---|
1 | タスク追加 | フォーム送信 |
2 | タスク削除 | ボタンクリック |
3 | 完了切り替え | チェックボックス |
4 | 一覧表示 | リスト表示 |
メインコンポーネント
アプリケーション全体を管理するメインコンポーネントを作成します。
javascript// src/components/TodoApp.js
import { useState, useEffect } from 'preact/hooks';
import TodoList from './TodoList';
import TodoInput from './TodoInput';
import TodoFilter from './TodoFilter';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all'); // all, active, completed
const [nextId, setNextId] = useState(1);
// ローカルストレージからデータを読み込み
useEffect(() => {
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
try {
const parsedTodos = JSON.parse(savedTodos);
setTodos(parsedTodos);
// 次のIDを計算
const maxId = Math.max(...parsedTodos.map(todo => todo.id), 0);
setNextId(maxId + 1);
} catch (error) {
console.error('データの読み込みに失敗:', error);
}
}
}, []);
// todosが変更されたらローカルストレージに保存
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);
return (
<div className="todo-app">
<header className="app-header">
<h1>My Todo App</h1>
<p>Preactで作るシンプルなToDoアプリです</p>
</header>
<main className="app-main">
<TodoInput onAddTodo={addTodo} />
<TodoFilter
currentFilter={filter}
onFilterChange={setFilter}
/>
<TodoList
todos={filteredTodos}
onToggleTodo={toggleTodo}
onDeleteTodo={deleteTodo}
/>
</main>
<footer className="app-footer">
<p>総タスク数: {todos.length} | 完了: {completedCount}</p>
</footer>
</div>
);
}
タスク追加機能
新しいタスクを追加するためのコンポーネントを作成します。
javascript// src/components/TodoInput.js
import { useState } from 'preact/hooks';
function TodoInput({ onAddTodo }) {
const [inputValue, setInputValue] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
// バリデーション
if (!inputValue.trim()) {
return;
}
if (inputValue.length > 100) {
alert('タスクは100文字以内で入力してください');
return;
}
setIsSubmitting(true);
try {
await onAddTodo(inputValue.trim());
setInputValue('');
} catch (error) {
console.error('タスクの追加に失敗:', error);
alert('タスクの追加に失敗しました');
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="todo-input-form">
<div className="input-group">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="新しいタスクを入力してください"
className="task-input"
disabled={isSubmitting}
maxLength={100}
/>
<button
type="submit"
className="add-btn"
disabled={isSubmitting || !inputValue.trim()}
>
{isSubmitting ? '追加中...' : '追加'}
</button>
</div>
<div className="input-info">
<span className="char-count">
{inputValue.length}/100文字
</span>
</div>
</form>
);
}
export default TodoInput;
タスク一覧表示
タスクの一覧を表示するコンポーネントを作成します。
javascript// src/components/TodoList.js
import TodoItem from './TodoItem';
function TodoList({ todos, onToggleTodo, onDeleteTodo }) {
if (todos.length === 0) {
return (
<div className="empty-state">
<p className="empty-message">
まだタスクがありません
</p>
<p className="empty-hint">
上のフォームから新しいタスクを追加してみましょう
</p>
</div>
);
}
return (
<div className="todo-list">
<div className="list-header">
<h2>タスク一覧</h2>
<span className="task-count">{todos.length}件</span>
</div>
<ul className="todo-items">
{todos.map(todo => (
<li key={todo.id} className="todo-item-wrapper">
<TodoItem
todo={todo}
onToggle={onToggleTodo}
onDelete={onDeleteTodo}
/>
</li>
))}
</ul>
</div>
);
}
export default TodoList;
個別タスクコンポーネント
各タスクを表示する詳細コンポーネントを作成します。
javascript// src/components/TodoItem.js
import { useState } from 'preact/hooks';
function TodoItem({ todo, onToggle, onDelete }) {
const [isDeleting, setIsDeleting] = useState(false);
const handleDelete = async () => {
if (!confirm('このタスクを削除しますか?')) {
return;
}
setIsDeleting(true);
try {
await onDelete(todo.id);
} catch (error) {
console.error('タスクの削除に失敗:', error);
alert('タスクの削除に失敗しました');
setIsDeleting(false);
}
};
const formatDate = (timestamp) => {
return new Date(timestamp).toLocaleString('ja-JP');
};
return (
<div className={`todo-item ${todo.completed ? 'completed' : ''} ${isDeleting ? 'deleting' : ''}`}>
<div className="todo-content">
<label className="checkbox-wrapper">
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
className="todo-checkbox"
/>
<span className="checkmark"></span>
</label>
<div className="todo-text-content">
<span className="todo-text">
{todo.text}
</span>
<span className="todo-meta">
作成日: {formatDate(todo.createdAt)}
{todo.completedAt && (
<span> | 完了日: {formatDate(todo.completedAt)}</span>
)}
</span>
</div>
</div>
<div className="todo-actions">
<button
onClick={handleDelete}
className="delete-btn"
disabled={isDeleting}
>
{isDeleting ? '削除中...' : '削除'}
</button>
</div>
</div>
);
}
export default TodoItem;
状態管理の実装
ToDoアプリの状態管理ロジックを完成させます。
javascript// TodoApp.js の続き
const addTodo = (text) => {
const newTodo = {
id: nextId,
text,
completed: false,
createdAt: Date.now(),
completedAt: null
};
setTodos(prevTodos => [...prevTodos, newTodo]);
setNextId(prev => prev + 1);
};
const toggleTodo = (id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id
? {
...todo,
completed: !todo.completed,
completedAt: !todo.completed ? Date.now() : null
}
: todo
)
);
};
const deleteTodo = (id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
};
// フィルタリング機能
const filteredTodos = todos.filter(todo => {
switch (filter) {
case 'active':
return !todo.completed;
case 'completed':
return todo.completed;
default:
return true;
}
});
// 統計情報
const completedCount = todos.filter(todo => todo.completed).length;
ビルドと最適化
アプリケーションが完成したら、本番環境向けにビルドと最適化を行いましょう。 Preactは優れた最適化機能を提供しており、さらなるパフォーマンス向上が期待できます。
本番ビルドの実行
Preact CLIを使用している場合は、以下のコマンドでビルドを実行します。
bash# 本番ビルドの実行
yarn build
# ビルド結果の確認
yarn serve
手動設定の場合は、Webpack設定を本番モード用に調整します。
javascript// webpack.prod.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets: '> 0.25%, not dead' }]
],
plugins: [
['@babel/plugin-transform-react-jsx', { pragma: 'h' }]
]
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
パフォーマンス分析
ビルド後のバンドルサイズを分析し、最適化の余地を確認します。
bash# webpack-bundle-analyzerのインストール
yarn add -D webpack-bundle-analyzer
# バンドル分析の実行
yarn webpack-bundle-analyzer dist/main.*.js
分析結果から以下の項目を確認できます。
項目 | 確認ポイント | 最適化方法 |
---|---|---|
1 | バンドルサイズ | 全体サイズ |
2 | 重複モジュール | 同じライブラリの複数読み込み |
3 | 未使用コード | インポートされているが未使用 |
4 | 大きなライブラリ | サイズの大きなライブラリ |
最適化テクニック
さらなる最適化のためのテクニックをご紹介します。
javascript// 動的インポートによるコード分割
import { lazy, Suspense } from 'preact/compat';
// 重いコンポーネントを遅延読み込み
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>読み込み中...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}
メモ化によるレンダリング最適化も重要です。
javascriptimport { memo } from 'preact/compat';
import { useMemo, useCallback } from 'preact/hooks';
// メモ化されたコンポーネント
const TodoItem = memo(({ todo, onToggle, onDelete }) => {
// 高価な計算をメモ化
const formattedDate = useMemo(() => {
return new Date(todo.createdAt).toLocaleDateString('ja-JP');
}, [todo.createdAt]);
// コールバック関数をメモ化
const handleToggle = useCallback(() => {
onToggle(todo.id);
}, [todo.id, onToggle]);
return (
<div className="todo-item">
{/* コンポーネントの実装 */}
</div>
);
});
まとめ
Preactは軽量でありながら、Reactと同等の機能性を提供する優れたライブラリです。 わずか3KBという驚異的な軽量さにより、パフォーマンスを重視するWebアプリケーション開発において大きなアドバンテージを得られるでしょう。
今回学んだ内容をまとめると以下の通りです。
Preactの主要なメリット
- 圧倒的な軽量性(3KB)によるパフォーマンス向上
- Reactとの高い互換性による学習コストの低減
- 優秀な開発者エクスペリエンスとツールサポート
実践で活用できる知識
- 基本的なコンポーネント作成とJSX記法
- Hooks を活用した現代的な状態管理
- 実用的なアプリケーション開発手法
最適化とパフォーマンス
- 効率的なビルド設定とバンドル最適化
- コード分割とメモ化による高速化
- 本番環境での運用ノウハウ
Preactを採用することで、高速で軽量なWebアプリケーションを効率的に開発できるようになります。 特にモバイルファーストな現代のWeb開発において、その価値は非常に高いと言えるでしょう。
ぜひ今回学んだ内容を活用して、素晴らしいWebアプリケーションを作成してみてください。 Preactの軽量性と柔軟性が、あなたの開発体験をより良いものにしてくれるはずです。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来