概要
本記事では、Reactプロジェクトにおける実用的な高度機能を3つの軸で解説します。Ant Designを活用したプロダクション対応UI構築、axiosを用いた柔軟かつ信頼性の高いAPI連携、そしてreact-transition-groupによる自然なユーザーインタラクションの実現方法について、実装例を交えて詳細に紹介します。
Ant Designを活用したモダンUI開発
動的クラス名管理:classnamesライブラリの導入
Reactでは条件付きクラス名の適用が頻繁に必要になりますが、文字列連結による実装は可読性・保守性に課題があります。以下のようにclassnamesを活用することで、安全かつ宣言的にスタイルを制御できます。
import React, { useState } from 'react';
import cn from 'classnames';
export default function DynamicClassDemo() {
const [isActive, setToggle] = useState(true);
const [isHighlighted, setIsHighlighted] = useState(false);
return (
<div>
{/* 従来の手動結合(推奨しない) */}
<h2 className={`header ${isActive ? 'active' : ''} ${isHighlighted ? 'highlight' : ''}`}>
手動結合例
</h2>
{/* classnamesによる安全な結合 */}
<h2 className={cn('header', { active: isActive, highlight: isHighlighted })}>
classnames活用例
</h2>
{/* 配列ベースの組み合わせ */}
<h2 className={cn(['header', 'base-style'], { 'is-active': isActive })}>
配列+オブジェクト混合
</h2>
</div>
);
}
Ant Designの統合と国際化設定
Ant Designは、日本語対応やテーマカスタマイズが容易な企業向けUIライブラリです。アプリケーション全体で一貫したローカライズとテーマを適用するには、ConfigProviderをルートコンポーネントでラップします。
// src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<ConfigProvider locale={zhCN} theme={{ token: { colorPrimary: '#25B864' } }}>
<App />
</ConfigProvider>
);
CRACOによるビルド設定の拡張
Create React App(CRA)の制約を超えるために、CRACO(Create React App Configuration Override)を導入します。これにより、Webpackエイリアスやテーマ変数の注入など、カスタムビルドフローをシンプルに実現可能です。
// craco.config.js
const path = require('path');
module.exports = {
webpack: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@utils': path.resolve(__dirname, 'src/utils')
}
}
};
コメント投稿機能の実装例
Ant DesignのComment、Input、Avatarを組み合わせたリアルタイムコメント機能を構築します。状態管理は関数コンポーネント+Hooksで簡潔に記述します。
// src/components/CommentList.jsx
import { Comment, Avatar, Button, Input } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { useState, useCallback } from 'react';
export default function CommentList() {
const [comments, setComments] = useState([]);
const [inputValue, setInputValue] = useState('');
const handleSubmit = useCallback(() => {
if (!inputValue.trim()) return;
const newComment = {
id: Date.now(),
author: '開発者',
avatar: 'https://via.placeholder.com/40',
content: inputValue,
timeAgo: '今から1分前'
};
setComments(prev => [newComment, ...prev]);
setInputValue('');
}, [inputValue]);
const handleDelete = useCallback((id) => {
setComments(prev => prev.filter(c => c.id !== id));
}, []);
return (
<div>
<Input.TextArea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
rows={3}
placeholder="コメントを入力してください..."
/>
<Button type="primary" onClick={handleSubmit} style={{ marginTop: '8px' }}>
投稿
</Button>
<div style={{ marginTop: '24px' }}>
{comments.map(comment => (
<Comment
key={comment.id}
author={<a href="#">{comment.author}</a>}
avatar={<Avatar src={comment.avatar} alt={comment.author} />}
content={<p>{comment.content}</p>}
datetime={<span>{comment.timeAgo}</span>}
actions={[
<span key="delete" onClick={() => handleDelete(comment.id)}>
<DeleteOutlined /> 削除
</span>
]}
/>
))}
</div>
</div>
);
}
axiosによる堅牢なAPI通信設計
基本的なHTTPリクエスト操作
axiosはPromiseベースのHTTPクライアントであり、RESTfulなAPIとのやり取りを直感的に行えます。以下の例では、並列リクエストとエラーハンドリングを含む実践的な使い方を示します。
import axios from 'axios';
// カスタムインスタンスの作成(環境ごとの設定)
const apiClient = axios.create({
baseURL: import.meta.env.DEV ? 'https://httpbin.org' : '/api',
timeout: 10000,
headers: { 'X-Client': 'react-app' }
});
// リクエスト送信と結果処理
async function fetchUserData() {
try {
const response = await apiClient.get('/get', {
params: { userId: 123 }
});
console.log('ユーザー情報:', response.data);
} catch (error) {
if (error.response?.status === 404) {
console.warn('ユーザーが見つかりません');
} else if (error.code === 'ECONNABORTED') {
console.error('タイムアウトしました');
}
}
}
リクエスト/レスポンスインターセプターの活用
共通の前処理・後処理を一元管理するために、インターセプターを定義します。認証トークンの自動付与、ローディング状態の制御、エラー共通処理などが可能です。
// src/lib/api.js
import axios from 'axios';
const client = axios.create();
// リクエストインターセプター
client.interceptors.request.use(
config => {
const token = localStorage.getItem('auth_token');
if (token) config.headers.Authorization = `Bearer ${token}`;
// 共通ローディング表示ロジック(例:Reduxアクション発火)
return config;
},
error => Promise.reject(error)
);
// レスポンスインターセプター
client.interceptors.response.use(
response => response.data, // dataフィールドのみ返却
error => {
const status = error.response?.status;
switch (status) {
case 401:
localStorage.removeItem('auth_token');
window.location.href = '/login';
break;
case 500:
alert('サーバーエラーが発生しました');
break;
default:
console.error('APIエラー:', error);
}
return Promise.reject(error);
}
);
export default client;
状態遷移アニメーションの実装
CSSTransitionによる単一要素のトランジション
要素の表示・非表示時にCSSアニメーションを適用する場合、CSSTransitionが最適です。appear、enter、exitの各フェーズに対応したクラス名を指定することで、細かい制御が可能です。
import { CSSTransition } from 'react-transition-group';
function AnimatedCard({ isVisible }) {
return (
<CSSTransition
in={isVisible}
timeout={300}
classNames="fade"
unmountOnExit
appear
>
<div className="card">アニメーション付きカード</div>
</CSSTransition>
);
}
// CSS(fade-enter → fade-enter-active → fade-enter-done)
/* .fade-enter { opacity: 0; }
.fade-enter-active { opacity: 1; transition: opacity 300ms ease-in; }
.fade-enter-done { opacity: 1; }
.fade-exit { opacity: 1; }
.fade-exit-active { opacity: 0; transition: opacity 300ms ease-out; }
.fade-exit-done { opacity: 0; } */
SwitchTransitionによるコンポーネント切り替えアニメーション
ボタンのON/OFFやタブ切り替えなど、「ある要素が消えて、別の要素が現れる」ようなシナリオにはSwitchTransitionが有効です。mode属性で「新→旧」または「旧→新」の順序を指定できます。
import { SwitchTransition, CSSTransition } from 'react-transition-group';
function ToggleButton({ isOn, toggle }) {
return (
<SwitchTransition mode="out-in">
<CSSTransition
key={isOn ? 'on' : 'off'}
timeout={250}
classNames="slide"
>
<button onClick={toggle} className="toggle-btn">
{isOn ? 'ON' : 'OFF'}
</button>
</CSSTransition>
</SwitchTransition>
);
}
// CSS(slide-enter → slide-enter-active → slide-enter-done)
/* .slide-enter { transform: translateX(100%); opacity: 0; }
.slide-enter-active { transform: translateX(0); opacity: 1; transition: all 250ms ease; }
.slide-exit { transform: translateX(0); opacity: 1; }
.slide-exit-active { transform: translateX(-100%); opacity: 0; transition: all 250ms ease; } */
TransitionGroupによるリストアイテムのアニメーション
配列データの追加・削除に対して個別にアニメーションを適用するには、TransitionGroupとCSSTransitionを組み合わせます。各要素にユニークなkeyを指定することが必須です。
import { TransitionGroup, CSSTransition } from 'react-transition-group';
function AnimatedList({ items }) {
return (
<TransitionGroup component="ul" className="item-list">
{items.map(item => (
<CSSTransition
key={item.id}
timeout={300}
classNames="list-item"
>
<li className="list-item-content">
{item.name}
<button onClick={() => removeItem(item.id)}>×</button>
</li>
</CSSTransition>
))}
</TransitionGroup>
);
}