Reactアプリケーションを開発する際、ユーザー体験を向上させるためにはパフォーマンスの最適化が重要です。特に、アプリの初期読み込み時間を短縮し、必要なリソースだけを効率的に提供することが求められます。この課題に対処するための有効な手法として、「アセットの遅延ロード」と「コード分割」が挙げられます。本記事では、これらの技術を組み合わせることで得られる効果とその実装方法について、初心者にも分かりやすく解説します。実例や演習を通じて、Reactアプリのパフォーマンスを飛躍的に向上させるための知識を習得しましょう。
アセット遅延ロードとは
アセット遅延ロードとは、アプリケーションで使用するリソース(フォント、アイコン、画像など)を、初期読み込み時ではなく必要になったタイミングで読み込む技術です。この手法により、初期表示に必要なデータ量を削減し、ユーザーに素早くコンテンツを表示できます。
遅延ロードの仕組み
アセット遅延ロードは、ブラウザがアセットをリクエストするタイミングを制御することで実現します。例えば、画像を例にすると、ユーザーがスクロールして画像が表示範囲に入ったときに初めてリクエストが送られるようにします。
Reactでのアセット遅延ロードの必要性
- 高速な初期ロード:初期画面で使用しないフォントやアイコンを後回しにすることで、初期表示が高速化します。
- 効率的なリソース配信:必要なアセットだけを読み込むため、データ量を最適化できます。
- ユーザー体験の向上:不要な読み込みを防ぐことで、スムーズな操作感を提供します。
アセット遅延ロードの例
例えば、Google Fontsを利用してWebフォントを読み込む場合、初期ロードを遅延させることで、重要なコンテンツの表示を優先させることが可能です。また、アイコンフォントやSVGアイコンの遅延読み込みも同様の効果をもたらします。次章では、Reactでの具体的な実装方法を詳しく解説します。
コード分割の概要
コード分割(Code Splitting)とは、アプリケーションのJavaScriptコードを複数の小さなチャンク(塊)に分割し、必要に応じて動的に読み込む技術です。この手法を使うことで、初期ロード時に必要なコード量を減らし、ユーザー体験を大幅に向上させることができます。
コード分割のメリット
- 初期ロード時間の短縮:アプリケーション全体のコードを一度にロードする必要がなくなるため、ユーザーは重要な機能に素早くアクセスできます。
- メモリ使用量の削減:不要なコードの読み込みを防ぐことで、ブラウザのメモリ負荷を軽減します。
- 効率的なリソース配信:ユーザーが利用する機能に応じて、必要なコードだけを配信できます。
Reactでのコード分割
Reactでは、React.lazy
とSuspense
を使用して簡単にコード分割を実現できます。これらを活用することで、コンポーネントごとに必要なコードを遅延読み込みする仕組みを構築できます。
コード分割の実際の動作
コード分割は、アプリケーションが特定の機能や画面を必要としたときに、そのコードを読み込む仕組みです。たとえば、ルーティングによって異なるページが読み込まれるシングルページアプリケーション(SPA)では、ルートごとにコードを分割し、ユーザーがアクセスするまで読み込まないように設定できます。
コード分割の限界
- 初期表示に影響しない部分のみ適用可能:分割したコードが頻繁に必要になる場合は、分割の効果が薄れることがあります。
- 実装の複雑化:コード分割は適切なタイミングでロードを管理するために、多少の設計上の工夫が必要です。
次章では、アセット遅延ロードとコード分割を組み合わせた際の利点について詳しく見ていきます。
アセット遅延ロードとコード分割の併用の利点
アセット遅延ロードとコード分割はそれぞれ単独でもパフォーマンス改善に効果的ですが、両者を組み合わせることでさらなる最適化が可能です。このセクションでは、それぞれの特性が補完し合うことで得られる利点について解説します。
併用のメリット
1. 初期ロードのさらなる短縮
コード分割でアプリ全体のJavaScript量を減らし、アセット遅延ロードで画像やフォントなどのリソースを後回しにすることで、初期ロードを大幅に高速化します。これにより、ユーザーがアプリをすぐに利用できるようになります。
2. ネットワーク負荷の軽減
初期ロード時のネットワーク負荷を最小限に抑え、必要なコードやアセットだけを逐次ロードするため、帯域幅の節約にもつながります。
3. パフォーマンスの向上
コード分割で不要な機能を読み込まないようにし、遅延ロードで現在表示中の画面に必要なアセットのみを取得することで、ページ全体のレンダリング速度が向上します。
4. スケーラブルな設計
アセット遅延ロードとコード分割を組み合わせることで、機能が増えてもパフォーマンスが低下しにくいスケーラブルな設計が可能です。たとえば、大規模アプリケーションでも使用頻度の低い機能を後回しにできるため、快適な操作性を保てます。
具体的な適用シナリオ
- アイコンセットの遅延ロード:UIで使用するすべてのアイコンを最初に読み込むのではなく、必要なアイコンを動的にロードする。
- ダイアログやモーダルのコード分割:特定のユーザー操作でのみ表示されるダイアログのコードやスタイルを分割し、必要になったときにだけ読み込む。
- 特定のページのアセットロード:ルーティングで非表示のページに関連する画像やフォントを後からロードする。
注意点
- 過剰な分割や遅延ロードは、逆にユーザー体験を損なうことがあります。たとえば、必要なアセットやコードが遅れて読み込まれると、画面の空白や操作性の低下につながります。
- これらの技術を適用する際には、読み込みタイミングや条件を適切に設定することが重要です。
次章では、Reactでアセット遅延ロードを具体的に実装する方法について詳しく説明します。
実装例:Reactでのアセット遅延ロード
Reactを使用してアセットの遅延ロードを実現する方法を具体的に解説します。アセットとして画像、フォント、アイコンなどが対象になります。このセクションでは、画像の遅延ロードを中心に進めますが、他のアセットにも応用可能です。
画像の遅延ロード
遅延ロードの一般的な実装では、ユーザーが画像を表示する必要があるタイミングで画像を読み込むようにします。これを簡単に行うために、以下のような実装が考えられます。
手順 1: Intersection Observerを使用
IntersectionObserver
は、要素がビューポートに入ったときに検知できるブラウザのAPIです。これを使うことで、特定のタイミングで画像を読み込むことが可能です。
import React, { useState, useEffect } from "react";
const LazyImage = ({ src, alt, placeholder }) => {
const [isVisible, setIsVisible] = useState(false);
const imgRef = React.useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect(); // 一度読み込んだら監視解除
}
},
{ threshold: 0.1 } // 要素が10%見えたらトリガー
);
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={isVisible ? src : placeholder}
alt={alt}
loading="lazy"
/>
);
};
export default LazyImage;
手順 2: 使用例
上記のLazyImage
コンポーネントを使用して、遅延ロード可能な画像を作成します。初期状態ではプレースホルダー画像を表示し、スクロールして画像が表示範囲に入ると、本来の画像が読み込まれます。
const App = () => {
return (
<div>
<h1>React Lazy Loading Example</h1>
<LazyImage
src="https://example.com/high-res-image.jpg"
placeholder="https://example.com/low-res-placeholder.jpg"
alt="Example Image"
/>
</div>
);
};
export default App;
フォントやアイコンの遅延ロード
- Webフォントの遅延ロード
Webフォントを後回しにするには、rel="preload"
とrel="stylesheet"
の組み合わせを使う方法があります。
<link rel="preload" href="https://example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
<link rel="stylesheet" href="https://example.com/fonts.css" />
- SVGアイコンの動的インポート
React.lazy
とSuspense
を活用して必要なタイミングでアイコンを読み込むことができます。
次章では、Reactでのコード分割について実装例を詳しく解説します。
実装例:Reactでのコード分割
Reactでは、コード分割を実現するために公式サポートされているReact.lazy
とSuspense
を使用することが一般的です。このセクションでは、Reactでコード分割を行う具体的な方法について解説します。
コード分割の基本的な仕組み
コード分割は、通常一つの大きなJavaScriptファイルにまとめられるコードを複数の小さなファイルに分割することで実現します。この分割されたコードは、必要なタイミングで動的に読み込まれます。
React.lazyとSuspenseを使用したコード分割
手順 1: React.lazyの活用
React.lazy
を使用することで、動的にインポートされるコンポーネントを簡単に定義できます。
import React, { Suspense } from "react";
const LazyComponent = React.lazy(() => import("./LazyLoadedComponent"));
const App = () => {
return (
<div>
<h1>React Code Splitting Example</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
上記の例では、LazyLoadedComponent
はApp
コンポーネントがレンダリングされる際に初めて読み込まれます。Suspense
のfallback
プロパティは、ロード中に表示する代替のUIを指定します。
手順 2: ファイル構成の分割
コード分割を効果的に活用するには、機能ごとにコードを分割する設計が重要です。以下は推奨されるディレクトリ構成の一例です。
src/
components/
LazyLoadedComponent.js
pages/
Home.js
About.js
このように、コンポーネントやページ単位でコードを分割することで、必要な部分のみを読み込むことができます。
ルーティングとコード分割
React Routerと組み合わせて、ページごとにコードを分割する方法を見てみましょう。
import React, { Suspense } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
const Home = React.lazy(() => import("./pages/Home"));
const About = React.lazy(() => import("./pages/About"));
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
この例では、Home
とAbout
の各ページコンポーネントが、それぞれ初めてアクセスされたときに動的にロードされます。
注意点
- エラーハンドリング: 動的ロードに失敗した場合に備えて、エラーハンドリングを実装することが推奨されます。
- 過剰な分割を避ける: 過剰に細かく分割すると、ユーザー体験を損なう可能性があります。適切な粒度で分割しましょう。
次章では、遅延ロードとコード分割を組み合わせた際の実用的な構成について解説します。
遅延ロードとコード分割の組み合わせ
アセット遅延ロードとコード分割を組み合わせることで、Reactアプリケーションのパフォーマンスを最大限に引き出すことが可能です。このセクションでは、これらを併用した効果的な構成方法と実装例について解説します。
組み合わせの基本原則
アセット遅延ロードは、画像やフォントなどの静的リソースを対象にする一方、コード分割はJavaScriptコードの読み込みを制御します。この二つを併用する際の基本的な考え方は以下の通りです:
- 優先度の高いリソースを考慮: 初期ロード時に必要なリソースだけを最小限にする。
- タイミングを調整: ユーザーが特定のアクションを実行するまで不要なリソースはロードしない。
- UIのスムーズな更新: 遅延ロードやコード分割で発生する読み込みの遅れをカバーするフォールバックUIを適切に実装する。
実装例:コード分割と遅延ロードの統合
以下は、画像の遅延ロードとコード分割を組み合わせたReactの実装例です。
import React, { Suspense, useState, useEffect } from "react";
// 動的に読み込むコンポーネント
const LazyComponent = React.lazy(() => import("./LazyComponent"));
// 遅延ロード用のカスタムフック
const useLazyImage = (src) => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => setIsLoaded(true);
}, [src]);
return isLoaded;
};
// 遅延ロードされた画像コンポーネント
const LazyImage = ({ src, placeholder, alt }) => {
const isLoaded = useLazyImage(src);
return <img src={isLoaded ? src : placeholder} alt={alt} loading="lazy" />;
};
const App = () => {
return (
<div>
<h1>Lazy Loading and Code Splitting</h1>
<LazyImage
src="https://example.com/high-res-image.jpg"
placeholder="https://example.com/low-res-placeholder.jpg"
alt="Example Image"
/>
<Suspense fallback={<div>Loading Component...</div>}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
実装ポイント
- 画像の遅延ロード:
LazyImage
コンポーネントを使用して、画像をビューポート内に入ったタイミングで読み込みます。 - 動的コンポーネントのロード:
LazyComponent
はReact.lazy
とSuspense
を利用して初めて使用されるタイミングでロードされます。 - フォールバックUI:
Suspense
のfallback
プロパティやプレースホルダー画像でスムーズなユーザー体験を提供します。
最適化の注意点
- ローディング体験の改善: フォールバックのUIが過度に目立たないよう、簡潔で自然なデザインを採用します。
- 重要なアセットの優先度設定: 遅延ロードやコード分割の対象にするリソースの優先度を明確にします。
- バンドルサイズの検討: コード分割の粒度を適切に設定し、過剰な分割によるオーバーヘッドを回避します。
次章では、React.lazyとSuspenseをさらに活用した効率的な実装方法について詳しく解説します。
Reactのライブラリ活用:React.lazyとSuspense
Reactの公式機能であるReact.lazy
とSuspense
は、コード分割を効率的に実現するための強力なツールです。このセクションでは、それぞれの機能の詳細な使い方と応用例を解説します。
React.lazyの詳細
React.lazy
は、コンポーネントを動的にインポートし、必要なタイミングで読み込むことを可能にします。これにより、アプリケーションの初期ロード時に不要なコードを省略できます。
基本的な使用例
import React, { Suspense } from "react";
// 遅延ロードするコンポーネント
const LazyComponent = React.lazy(() => import("./LazyComponent"));
const App = () => {
return (
<div>
<h1>React.lazy Example</h1>
<Suspense fallback={<div>Loading Component...</div>}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
ポイント
- 遅延ロード:
React.lazy
は、指定されたタイミングでのみコンポーネントを読み込みます。 - 簡潔な構文: ES6の
import()
関数を使用して、非同期インポートを実現します。
Suspenseの活用
Suspense
は、動的に読み込まれるコンポーネントのロード中に代替UIを表示する機能を提供します。
基本構文
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
応用例: ネストされた構成
複数の遅延ロードコンポーネントを使用する場合、ネストされた構成でSuspense
を活用できます。
const LazyHeader = React.lazy(() => import("./LazyHeader"));
const LazyFooter = React.lazy(() => import("./LazyFooter"));
const App = () => {
return (
<div>
<Suspense fallback={<div>Loading Header...</div>}>
<LazyHeader />
</Suspense>
<main>
<h1>Main Content</h1>
</main>
<Suspense fallback={<div>Loading Footer...</div>}>
<LazyFooter />
</Suspense>
</div>
);
};
export default App;
ポイント
- 各コンポーネントごとにフォールバックUIをカスタマイズすることで、ロード中の体験を最適化します。
React.lazyとSuspenseの併用による実用例
ページルーティングと組み合わせたコード分割を実現します。
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
// 遅延ロードする各ページ
const Home = React.lazy(() => import("./pages/Home"));
const About = React.lazy(() => import("./pages/About"));
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
エラーハンドリングの追加
React.lazy
の読み込みエラーをハンドリングするために、ErrorBoundary
を組み合わせると効果的です。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.error(error, info);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong!</div>;
}
return this.props.children;
}
}
const App = () => (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</ErrorBoundary>
);
注意点
- 初期ロードの戦略的な設計: 必要最低限のリソースを最初に読み込むように設計しましょう。
- エラーハンドリング: エラー時のユーザー体験を損なわないよう適切なハンドリングを実装します。
次章では、ライブラリやWebフォントの最適化など、さらに高度なテクニックを紹介します。
より高度な実装:ライブラリやWebフォントの最適化
Reactアプリケーションのパフォーマンスをさらに向上させるためには、使用するライブラリやWebフォントの最適化が重要です。このセクションでは、具体的なテクニックと実装例を紹介します。
ライブラリの最適化
不要なライブラリの削除
アプリケーションのコードベースを見直し、使用していないライブラリやモジュールを削除することでバンドルサイズを削減できます。
npm uninstall <package-name>
必要な部分だけをインポート
モジュールの一部だけを利用する場合、ツリ―シェイキング(Tree Shaking)に対応した構文を使うことで無駄なコードを省けます。
// 不要なコードも含まれる全体インポート
import _ from "lodash";
// 必要な関数だけをインポート
import { debounce } from "lodash";
CDNの活用
一般的なライブラリはCDN経由で提供されているため、これを活用してブラウザキャッシュを利用し、初期ロードを高速化します。
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
Webフォントの最適化
フォントのサブセット化
使用する文字セットを限定することで、フォントファイルのサイズを大幅に削減できます。Google Fontsでは、以下のように文字セットを指定できます。
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap&text=HelloWorld" rel="stylesheet">
フォントの遅延ロード
rel="preload"
でフォントをプリロードし、後から適用する方法を使用します。
<link rel="preload" href="https://example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="stylesheet" href="https://example.com/fonts.css">
Webフォントのフォールバック設定
フォントロード中に適用されるフォールバックフォントを適切に指定し、表示の遅延を防ぎます。
body {
font-family: Arial, sans-serif; /* フォールバック */
}
画像の最適化
次世代画像フォーマットの採用
WebPやAVIFなどの次世代フォーマットを使用することで、ファイルサイズを削減できます。
<picture>
<source srcSet="image.avif" type="image/avif">
<source srcSet="image.webp" type="image/webp">
<img src="image.jpg" alt="Optimized Image">
</picture>
画像の遅延ロード
先述のLazyImage
コンポーネントを活用し、必要なタイミングで画像をロードします。
バンドル最適化のツール
WebpackのSplitChunksPlugin
WebpackのSplitChunksPlugin
を利用して、共有モジュールを分割し、効率的にキャッシュできるようにします。
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
バンドルアナライザー
ツールを使って、バンドルサイズを可視化し、最適化ポイントを特定します。
npm install --save-dev webpack-bundle-analyzer
具体的な応用例
フォントとライブラリの組み合わせ
以下は、フォント最適化とライブラリの最適化を同時に実施したコード例です。
import React from "react";
import { debounce } from "lodash";
const App = () => {
const handleResize = debounce(() => {
console.log("Window resized");
}, 300);
React.useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<div style={{ fontFamily: "Roboto, sans-serif" }}>
<h1>Optimized React App</h1>
</div>
);
};
export default App;
注意点
- ツール選定の慎重な検討: 使用する最適化ツールやフォーマットをプロジェクトに適したものにする。
- テスト環境の確認: 最適化後の動作をローカルやステージング環境で十分に確認します。
次章では、アセット遅延ロードとコード分割を実際に体験できる演習問題を紹介します。
演習問題:実際に試してみる
以下の演習問題を通じて、アセット遅延ロードとコード分割の実装方法を体験し、理解を深めましょう。この演習では、Reactアプリケーションを構築しながら、実際に学んだ内容を実装します。
演習概要
Reactを使って以下の機能を実装します:
- 遅延ロードされる画像ギャラリー。
- コード分割されたページ構成。
- フォントとライブラリの最適化。
ステップ 1: 環境セットアップ
以下のコマンドでReactプロジェクトを作成します。
npx create-react-app lazy-loading-example
cd lazy-loading-example
npm install react-router-dom
ステップ 2: 画像の遅延ロードを実装
LazyImage
コンポーネントを作成し、画像の遅延ロード機能を実装します。
LazyImage.js:
import React, { useState, useEffect, useRef } from "react";
const LazyImage = ({ src, alt, placeholder }) => {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
});
if (imgRef.current) observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={isLoaded ? src : placeholder}
alt={alt}
loading="lazy"
/>
);
};
export default LazyImage;
App.js:
import React from "react";
import LazyImage from "./LazyImage";
const App = () => {
return (
<div>
<h1>Image Gallery</h1>
<LazyImage
src="https://example.com/high-res-image1.jpg"
placeholder="https://example.com/low-res-placeholder1.jpg"
alt="Image 1"
/>
<LazyImage
src="https://example.com/high-res-image2.jpg"
placeholder="https://example.com/low-res-placeholder2.jpg"
alt="Image 2"
/>
</div>
);
};
export default App;
ステップ 3: ページのコード分割
React Routerを導入し、ページごとにコードを分割します。
routes.js:
import React, { lazy } from "react";
export const Home = lazy(() => import("./pages/Home"));
export const About = lazy(() => import("./pages/About"));
App.js:
import React, { Suspense } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Home, About } from "./routes";
const App = () => {
return (
<Router>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
};
export default App;
ステップ 4: フォントとライブラリの最適化
index.htmlでフォントをサブセット化して読み込みます。
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap&text=HelloWorld" rel="stylesheet">
App.jsに必要なライブラリの機能だけをインポートします。
import React from "react";
import { debounce } from "lodash";
const App = () => {
const handleResize = debounce(() => {
console.log("Window resized");
}, 300);
React.useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<div style={{ fontFamily: "Roboto, sans-serif" }}>
<h1>Optimized React App</h1>
</div>
);
};
export default App;
演習の確認
- ページを開き、初期ロードの軽さを確認します。
- 画像ギャラリーで遅延ロードが適切に動作していることを確認します。
- ページ遷移時にコード分割が動作しているか、ブラウザの開発者ツールで確認します。
応用問題
- サードパーティライブラリを追加し、その一部だけを効率的に使用する方法を考えてください。
- 遅延ロードとコード分割の組み合わせを利用して、ダッシュボードページを作成してください。
次章では、この演習を通じて学んだ内容をまとめます。
まとめ
本記事では、Reactアプリケーションにおけるアセット遅延ロードとコード分割の重要性とその実装方法について詳しく解説しました。以下のポイントが主な内容です:
- アセット遅延ロード
- フォントや画像などのアセットを必要なタイミングで読み込むことで、初期ロードを高速化しました。
LazyImage
コンポーネントを活用した実装例を紹介しました。
- コード分割
- React.lazyとSuspenseを用いて、必要なコンポーネントやページのみを動的に読み込む方法を学びました。
- ルーティングとの組み合わせによる実装例を通じて、ページ単位でのコード分割を実現しました。
- ライブラリとWebフォントの最適化
- 不要なライブラリの削除や必要な機能だけをインポートする方法を紹介しました。
- Webフォントのサブセット化や遅延ロードのテクニックを学びました。
- 演習問題
- 実践的なアプリを構築し、遅延ロードやコード分割の効果を体感しました。
これらの技術を効果的に組み合わせることで、Reactアプリのパフォーマンスを最大限に向上させることができます。引き続き実践を重ね、より洗練されたアプリケーションを開発してください。
コメント