Reactの関数コンポーネントとクラスコンポーネントの違いとそれぞれのメリット

Reactの関数コンポーネントとクラスコンポーネントの違い

Reactでは、クラスコンポーネントと関数コンポーネントの2つの主要なコンポーネントの種類があります。
最新のReactバージョンでは、React Hooksの導入により、関数コンポーネントがより強力で柔軟になりました。
以下に、これら2つのコンポーネントの主な違いを示します。

1. 構文の違い

クラスコンポーネント:

class MyClassComponent extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            // ステートの初期化
        };
    }

    render() {
        return (
            <div>
                {/* コンポーネントの描画 */}
            </div>
        );
    }
}

関数コンポーネント:

const MyFunctionalComponent = (props) => {
    // ステートの代わりにHooksを使用
    return (
        <div>
            {/* コンポーネントの描画 */}
        </div>
    );
}

2. ステートとライフサイクルの管理

クラスコンポーネント:

クラスコンポーネントでは、this.stateを使用して状態を管理し、ライフサイクルメソッド(componentDidMount、componentDidUpdate、componentWillUnmountなど)を使用してコンポーネントの振る舞いを制御します。

関数コンポーネント:

関数コンポーネントでは、useStateなどのHooksを使用して状態を管理します。
また、useEffectなどのHooksを使用してライフサイクルイベントに対応します。

3. thisの扱い

クラスコンポーネント:

クラスコンポーネントでは、メソッド内でthisを使用する必要があります。
これはJavaScriptのクラスの特性に基づいています。

関数コンポーネント:

関数コンポーネントでは、通常の関数と同様にthisを使用しません。
代わりに、Hooksを使用して状態やライフサイクルにアクセスします。

4. 可読性とシンプルさ

クラスコンポーネント:

クラスコンポーネントは、クラスの概念に基づいており、冗長なコードや状態の管理に関して比較的複雑になることがあります。

関数コンポーネント:

関数コンポーネントは、簡潔でシンプルな構文を持っており、Hooksを使用することで状態やライフサイクルを管理できます。
これにより、可読性が向上し、コンポーネントがよりシンプルになります。

最近のReactでは、関数コンポーネントが推奨され、Hooksを使用することでクラスコンポーネントの多くの機能が代替できるようになりました。
ただし、既存のコードベースや特定の要件によっては、クラスコンポーネントを使用することもあります。

関数コンポーネントのメリット

関数コンポーネントは、Reactの最新の開発スタイルであり、いくつかのメリットがあります。
以下は、関数コンポーネントの主なメリットです。

1. シンプルで短い構文:
関数コンポーネントは、クラスコンポーネントよりも短く、シンプルな構文を持っています。
これにより、コードが読みやすく、保守が容易になります。

// 関数コンポーネント
const MyFunctionalComponent = () => {
   return (
       <div>
           {/* コンポーネントの描画 */}
       </div>
   );
}

2. Hooksによる状態管理:
関数コンポーネントでは、useStateやuseEffectなどのHooksを利用して状態管理や副作用の処理が行えます。
これにより、複雑な状態やライフサイクルの処理を、よりシンプルで直感的な方法で行うことができます。

const MyFunctionalComponent = () => {
   const [count, setCount] = useState(0);

   useEffect(() => {
       // コンポーネントがマウントされた時や状態が変化した時の処理
       console.log('Component did mount or update');
   }, [count]);

   return (
       <div>
           <p>Count: {count}</p>
           <button onClick={() => setCount(count + 1)}>Increment</button>
       </div>
   );
}

3. 再利用性の向上:
関数コンポーネントは、単なる関数であるため、より再利用しやすいです。
他のコンポーネントから呼び出しやすく、コンポーネントの単位で機能を抽象化しやすくなります。

4. Hooksによるカスタムロジック:
Hooksは関数コンポーネントにおいて、状態やライフサイクルに関するカスタムロジックを抽象化するための素晴らしい手段です。
これにより、コンポーネントのロジックを簡潔に保ちつつ、再利用可能な関数として切り出すことができます。

5. Reactの未来への適応:
Reactの新しい機能や最適化は、通常関数コンポーネントに焦点を当てて開発される傾向があります。
関数コンポーネントとHooksを使用することで、新しいReactの機能やアップデートに迅速に対応できます。

これらのメリットから、新しいReactプロジェクトでは通常、関数コンポーネントとHooksの組み合わせが推奨されています。
ただし、既存のプロジェクトや特定の要件によっては、クラスコンポーネントを使用する場合もあります。

Reactのフックと導入されたことによる変化

Reactのフックとは何か

Reactのフック(Hook)は、React 16.8から導入された機能であり、関数コンポーネント内で状態管理やライフサイクルメソッドの利用など、クラスコンポーネントで提供されていた機能を、関数コンポーネントでも利用できるようにするための仕組みです。

主なReactのフックには以下のようなものがあります:

1. useState:
状態を関数コンポーネント内で利用できるようにする。
通常の変数とは異なり、状態を変更するとコンポーネントが再レンダリングされる。

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2. useEffect:
レンダリングの後に副作用(APIの呼び出し、購読の設定など)を実行するために使用される。
クラスコンポーネントのcomponentDidMount、componentDidUpdate、componentWillUnmountに相当する。

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // レンダリング後に実行されるコード
    console.log('Component did mount or update');
    return () => {
      // クリーンアップ関数(componentWillUnmountの代わり)
      console.log('Component will unmount');
    };
  }, []); // 第二引数の空の配列は、マウント時にだけ実行されることを示す
}

3. useContext:
Reactのコンテキストを利用するためのフック。

import React, { useContext } from 'react';

const MyContext = React.createContext();

function Example() {
  const contextValue = useContext(MyContext);
  // contextValueを使って何かをする
}

これらはReactのフックの一部であり、関数コンポーネント内で状態やライフサイクルの概念を導入することができるようにします。
これにより、コンポーネントの記述が簡潔になり、再利用性が向上します。

Reactのコンテキストとは

Reactのコンテキスト(Context)は、Reactアプリケーション内でデータを共有するための仕組みです。
通常、コンポーネントツリーを通じてデータを伝達するためにプロップスが使用されますが、深くネストされたコンポーネントにデータを渡すと煩雑になることがあります。
コンテキストは、これをより簡単にし、コンポーネントのネストの深さにかかわらず、特定のデータを取得できるようにします。

具体的には、React.createContext を使用してコンテキストオブジェクトを作成し、 でデータを提供し、useContext フックを使用してそのデータをコンポーネント内で利用できます。

以下は、簡単な例です:

// コンテキストの作成
const MyContext = React.createContext();

// データを提供する親コンポーネント
function ParentComponent() {
  const sharedData = "This is shared data";

  return (
    <MyContext.Provider value={sharedData}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

// データを利用する子コンポーネント
function ChildComponent() {
  // useContext フックを使用してコンテキストからデータを取得
  const contextData = useContext(MyContext);

  return <p>{contextData}</p>;
}

この例では、ParentComponent が MyContext.Provider を使用してデータを提供し、ChildComponent が useContext(MyContext) を使用してそのデータを取得しています。
このようにすることで、ChildComponent は ParentComponent に直接プロップスを渡さずにデータにアクセスできます。

コンテキストは、テーマの設定、認証情報、言語の設定など、アプリケーション全体で共有する必要があるデータや機能に特に有用です。

Reactにフックが導入されて何が変わったか

Reactにフックが導入される前は、主にクラスコンポーネントが利用され、コンポーネントの状態やライフサイクルメソッドの管理はクラス内で行われていました。
しかし、React 16.8でフックが導入されると、これにより関数コンポーネントでも状態やライフサイクルといった概念を利用できるようになりました。
これによって、いくつかの重要な変化が生じました。

1. コードの簡潔化:
フックを使用することで、コンポーネントのロジックを関数に分割できます。
これにより、同じ機能を持つコードがクラスコンポーネントよりも短く、理解しやすくなります。

2. 再利用性の向上:
フックはロジックを再利用しやすくするため、カスタムフックを作成して関数コンポーネント間で簡単に共有できます。
これにより、コンポーネントのコードをより効果的に再利用できます。

3. クラスコンポーネントの必要性の削減:
フックの導入により、関数コンポーネントがより強力で柔軟になりました。
これにより、クラスコンポーネントを使用する必要性が減り、新しいコンポーネントを作成する際にも関数コンポーネントとフックの組み合わせが主流となりました。

4. 副作用の管理:
useEffect フックを使用することで、クラスコンポーネントのライフサイクルメソッドに相当する処理を関数コンポーネント内で管理できるようになりました。
これにより、API呼び出しや購読の設定などの副作用を処理できます。

5. 関数コンポーネントの機能向上:
フックの導入により、関数コンポーネントが状態を持ち、ライフサイクルメソッドを持つなど、より多くの機能を持つようになりました。
これにより、コンポーネントの設計が柔軟になり、クラスコンポーネントと同等の機能を提供できるようになりました。

総じて、Reactのフックの導入により、コードの簡潔性、再利用性、機能向上が実現され、開発者がより効率的かつ柔軟にReactアプリケーションを構築できるようになりました。

Reactの関数コンポーネントとは

Reactの関数コンポーネントは、Reactコンポーネントの一種で、通常、JavaScriptの関数で構成されます。
関数コンポーネントは、React 16.8以前では状態やライフサイクルメソッドの利用が制限されていましたが、React 16.8で導入されたフック(Hooks)により、これらの機能が関数コンポーネントでも利用可能になりました。

以下は、基本的な関数コンポーネントの例です:

import React from 'react';

// 単純な関数コンポーネント
function MyComponent() {
  return (
    <div>
      <h1>Hello, React Function Component!</h1>
    </div>
  );
}

export default MyComponent;

Reactの関数コンポーネントは、単純なレンダリングを行うだけでなく、フックを使用して状態やライフサイクルといった概念を取り入れることができます。
例えば、useState フックを使用して状態を持つ場合は次のようになります:

import React, { useState } from 'react';

// 状態を持つ関数コンポーネント
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

このように、関数コンポーネントはシンプルで簡潔な記述が可能であり、React 16.8以降ではフックを利用することで、クラスコンポーネントと同等の機能を提供できるようになりました。
関数コンポーネントは、特にコンポーネントがUIを描画することに焦点を当てた場合や、コンポーネントの再利用性を高めたい場合に便利です。

ReactのuseEffectの処理内容と使う場面

useEffectとは何か

useEffectは、Reactフックの1つであり、コンポーネントのレンダリングサイクルの中で副作用を処理するために使用されます。
これは、API呼び出し、イベントの登録、タイマーのセットなどの非同期な操作を行う際に特に便利です。
useEffectは、コンポーネントがマウントされたとき、特定のプロパティが変更されたとき、またはコンポーネントがアンマウントされたときに実行されるコードを指定することができます。

useEffectの基本的な構文は以下の通りです。

import React, { useEffect } from 'react';

function MyComponent() {
  // useEffectの第1引数には副作用のコードが入ります
  useEffect(() => {
    // ここに副作用のコードを書く
    console.log('useEffectが実行されました');

    // クリーンアップ関数を返すことができます
    return () => {
      console.log('クリーンアップ関数が実行されました');
    };
  }, []); // 第2引数には監視対象の変数を指定します
  // 空の配列の場合、コンポーネントがマウントされたときに1回だけ実行されます

  // コンポーネントの描画など他のコードもここに含まれます

  return (
    <div>
      {/* コンポーネントの中身 */}
    </div>
  );
}

useEffectの第1引数には実行される副作用のコードが入ります。
第2引数は依存する変数のリストで、このリスト内の変数が変更されたときにuseEffectが再実行されます。
空の配列を指定すると、コンポーネントがマウントされたときに1回だけ実行され、アンマウント時にクリーンアップ関数が実行されます。

これにより、非同期な操作を行う際にリソースのリークを防ぎ、適切なタイミングで副作用を制御することができます。

useEffectを使う場面

useEffectはReactで非同期な操作や副作用を行う場面で非常に有用です。
以下は、useEffectを使用する一般的な場面です。

1. データの取得やAPI呼び出し
ページがロードされたときや特定の状態が変化したときに、サーバーからデータを取得するためにuseEffectを使用できます。
これにより、非同期操作を実行し、取得したデータをコンポーネントの状態に設定できます。

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      setData(data);
    } catch (error) {
      console.error('データのフェッチ処理でエラーが発生:', error);
    }
  };

  fetchData();
}, []); // 空の配列はマウント時に1回だけ実行

2. イベントの登録や解除
useEffectを使用してコンポーネントがマウントされたときにイベントを登録し、アンマウント時にイベントの購読を解除することができます。

useEffect(() => {
  const handleResize = () => {
    // ウィンドウのリサイズ時の処理
  };

  // イベントの登録
  window.addEventListener('resize', handleResize);

  // クリーンアップ関数でイベントの解除
  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []); // 空の配列はマウント時に1回だけ実行

3. タイマーの設定やクリア
useEffectを使用してタイマーを設定し、コンポーネントがアンマウントされたときにタイマーをクリアできます。

useEffect(() => {
  const timerId = setInterval(() => {
    // 定期的な処理
  }, 1000);

  // クリーンアップ関数でタイマーのクリア
  return () => {
    clearInterval(timerId);
  };
}, []); // 空の配列はマウント時に1回だけ実行

4. 外部ライブラリの初期化
外部ライブラリやリソースの初期化や解放をuseEffect内で行うことができます。

useEffect(() => {
  // 外部ライブラリの初期化

  // クリーンアップ関数で解放
  return () => {
    // 外部ライブラリの解放
  };
}, []); // 空の配列はマウント時に1回だけ実行

これらの例は、useEffectが非同期な操作や副作用をコンポーネントのライフサイクルに合わせて処理する方法になります。

ReactのuseMemoの処理内容と使用する場面

useMemoの処理内容

useMemoはReactフックの一つであり、計算コストの高い計算や再計算を最適化するために使用されます。
このフックは、指定された関数と依存配列に基づいてメモ化された値を返します。
メモ化された値は、依存配列が変更されない限り、同じ値を保持し続けます。
要は関数の結果をキャッシュ化したい場合に使用するフックです。

useMemoの基本的な構文は以下の通りです。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

ここで、第1引数(a, b)には計算を行う関数、第2引数[a, b]にはその関数の依存配列が渡されます。
依存配列の中の任意の要素が変更された場合、useMemoは新しい値を計算し直します。
依存配列が変更されない場合、以前のメモ化された値が返されます。

例えば、以下はuseMemoの基本的な実装例です。
(a * bは特にコストがかかる計算ではないのであくまで例です。)

import React, { useMemo } from 'react';

const MyComponent = ({ a, b }) => {
  // 「return a * b」の処理結果をメモ化
  const memoizedValue = useMemo(() => {
    // 高コストな計算を行う関数
    return a * b;
  }, [a, b]);

  return (
    <div>
      <p>a: {a}</p>
      <p>b: {b}</p>
      <p>計算結果: {memoizedValue}</p>
    </div>
  );
};

export default MyComponent;

この例では、aとbが変更されない限り、高コストな計算(a * b)は一度しか実行されません。
これにより、パフォーマンスが向上し、余分な計算が回避されます。

useMemoを使う場面

上記だけ見ればなんでもかんでもメモ化したくなりますが、場面によっては効果が出ないことが(むしろパフォーマンスが低下することも)あります。
useMemoは、計算コストが高い関数や値をメモ化するために使います。
特に、以下のような状況でuseMemoが有用です。

1. 計算コストが高い場合
もしコンポーネントのレンダリング時に高コストな計算がある場合、それをuseMemoを使って最適化することができます。
例えば、大きなデータセットを処理し、表示が遅い場合などです。

const memoizedData = useMemo(() => expensiveCalculation(data), [data]);

2. 不要な再計算を避けたい場合
コンポーネントが再レンダリングされると、そのコンポーネント内のすべてのコードが再実行されます。
useMemoを使うことで、依存するデータが変更されない限り、不要な再計算を避けることができます。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

3. 遅延評価が必要な場合
計算が非同期で行われる場合や、非同期の処理が必要な場合にもuseMemoを使用できます。
例えば、非同期処理を経て計算される結果をメモ化することがあります。

const memoizedAsyncValue = useMemo(async () => {
  const result = await fetchData();
  return result;
}, []);

4. propsやstateが変更されない場合:
コンポーネントが再レンダリングされるが、特定のpropsやstateが変更されない限り、計算結果が同じである場合にuseMemoを使用します。
これにより、無駄な計算を防ぎます。

const memoizedResult = useMemo(() => performComplexCalculation(propA, propB, propC), [propA, propB, propC]);

注意点として、useMemoはあくまでパフォーマンス最適化の手段であり、常に使用するべきではありません。
必要な場面でのみ使用し、適切なタイミングで最適化を行うよう心がけましょう。

useMemoのデメリット

useMemoはパフォーマンスの向上に寄与する一方で、不適切な使用や過度な使用は逆効果になる可能性があります。
以下はuseMemoのデメリットや注意点です。

1. 過度な最適化
useMemoはコストの高い計算を最適化するための手段であり、必要のない場面での使用は避けるべきです。
不要なメモ化はむしろ余計な複雑さを追加し、コードを理解しづらくする可能性があります。

2. メモリ使用量の増加
useMemoによりメモ化された値はメモリ内に保持されるため、不必要な大量のメモ化はメモリ使用量を増加させる可能性があります。
これは大規模なアプリケーションで注意が必要です。

3. 冗長な依存配列
不必要な依存配列の指定は、正確な再計算のトリガーを設定することができず、逆にパフォーマンスの低下を招く可能性があります。
適切な依存配列を設定することが重要です。

4. 冗長なコードの複雑化
すべての計算をuseMemoでメモ化すると、コードが冗長になり、理解しにくくなる可能性があります。
メモ化が必要な計算のみに適用するように心がけましょう。

5. 無駄な再計算の回避が困難な場合
一部の場合、useMemoを使っても無駄な再計算を回避するのが難しい場合があります。
特に非同期処理が関与する場合や、複雑なデータ依存性がある場合には慎重に検討する必要があります。

総じて、useMemoは適切な使用場面で使うことでパフォーマンスの向上が期待できますが、必要のない場面での過度な使用は避け、コードの可読性と保守性を損なわないように心がけた方がよいです。
使い所に迷う場合は、とりあえずはuseMemoを使用せずに実装し、パフォーマンスに懸念が出た時に使用を検討するのが良いのではないでしょうか。

AMPページの特徴とこれから

AMPページの特徴

AMP(Accelerated Mobile Pages)は、ウェブページを高速に読み込み、モバイルデバイスでのユーザーエクスペリエンスを向上させるために設計されたオープンソースのイニシアチブです。
以下は、AMPページの主な特徴です。

1. 高速な読み込み速度:
AMPページは、高速な読み込み速度を提供することが最も重要な特徴です。
これは、ウェブページが迅速に表示され、ユーザーがコンテンツに素早くアクセスできるようにするために設計されています。

2. 軽量な構造:
AMPはHTML、CSS、JavaScriptを最適化しています。
冗長なコードを削減し、ページの軽量な構造を維持することで、高速な読み込みが可能です。

3. プリローディング:
AMPは、ユーザーが次のページに移動する前に、次のコンテンツを予め読み込むプリローディングをサポートしています。
これにより、ユーザーエクスペリエンスが向上し、ページ間の移動が滑らかになります。

4. キャッシュの最適化:
Google AMP Cacheと呼ばれるキャッシュサーバーを使用することで、AMPページはグローバルに配信され、高速なコンテンツ提供が可能です。

5. レスポンシブデザイン:
AMPは、デバイスの種類やサイズに応じて適切に表示されるレスポンシブデザインをサポートしています。
これにより、モバイルデバイスやタブレットなど、さまざまな端末で一貫したユーザーエクスペリエンスが得られます。

6. 外部コンテンツの最適化:
AMPは、外部からのコンテンツの統合を容易にし、動画やソーシャルメディアなどの外部リソースを最適化して組み込むことができます。

7. SEOの向上:
GoogleはAMPページを検索結果で優先的に表示する傾向があり、AMP対応ページは検索エンジン最適化(SEO)に寄与する可能性があります。

これらの特徴により、AMPは特にモバイルデバイスでのウェブページの読み込み速度を向上させ、ユーザーエクスペリエンスを最適化することを目的としています。

AMPの流行性

AMP(Accelerated Mobile Pages)は、導入当初から一定の注目を集め、特にウェブページのモバイル表示において高速かつ効果的なソリューションとして受け入れられています。
以下は、AMPの流行性に関するいくつかの観点です:

1. Googleのサポート:
GoogleはAMPページを検索結果で優遇し、AMP導入サイトに対して特別なカルーセル表示や「AMP」アイコンを表示するなどの特典を提供しています。
これにより、AMPを採用することがSEO上の利点となり、多くのウェブサイトがAMPを導入する動機となりました。

2. 主要なメディアとプラットフォームの導入:
多くの主要なメディア企業やプラットフォームがAMPに対応しており、これによってAMPは広く普及しました。
特にニュースや出版関連のウェブサイトがAMPを利用し、高速でモバイルフレンドリーなコンテンツを提供しています。

3. eコマースの採用:
一部のeコマースサイトもAMPを導入しており、ユーザーが商品ページなどを迅速に閲覧できるようにしています。
高速な読み込み速度が顧客の利便性に寄与し、コンバージョン率の向上に寄与しています。

4. オープンソースコミュニティのサポート:
AMPはオープンソースのプロジェクトであり、多くの開発者やコミュニティが参加しています。
これにより、新しい機能の追加やバグの修正が積極的に行われ、プロジェクトの進化が促進されています。

一方で、AMPには一部の批判もあります。
例えば、Googleによる制御の強さや、一部の開発者からは柔軟性の制限が指摘されています。
しかし、多くのウェブサイトがモバイルユーザーエクスペリエンスの向上を求めてAMPを採用していることは確かであり、その流行性は継続しています。

AMPの実装例

AMPを実装するには、HTML、CSS、JavaScriptのコードを特定の形式に変更し、AMPのガイドラインに従う必要があります。
以下は、AMPを実装した簡単な例です。
なお、これは基本的な例であり、実際のプロジェクトにはさらなるカスタマイズが必要です。

1. HTMLの基本構造:
AMPページは通常、通常のHTMLとは異なるDOCTYPE宣言とAMPのJavaScriptライブラリのインクルードを含む基本的なHTML構造を持っています。

<!DOCTYPE html>
<html amp lang="en">
<head>
  <meta charset="utf-8">
  <script async src="https://cdn.ampproject.org/v0.js"></script>
  <title>Your AMP Page Title</title>
  <link rel="canonical" href="https://your-regular-page-url.com/">
  <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
  <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes...
</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
  <style amp-custom>
    /* Custom styles go here */
  </style>
</head>
<body>
  <!-- Content goes here -->
</body>
</html>

2. AMPコンポーネントの使用:
AMPは特有のコンポーネントを使用して、高速かつ最適化されたコンテンツを提供します。
例えば、は画像の遅延読み込みを可能にし、は画像やコンテンツのスライダーを実装できます。

<!-- Example of using amp-img -->
<amp-img src="your-image.jpg" width="600" height="400" layout="responsive" alt="Your Image"></amp-img>

<!-- Example of using amp-carousel -->
<amp-carousel width="400" height="300" layout="responsive" type="slides">
  <amp-img src="image1.jpg" width="400" height="300" layout="responsive" alt="Image 1"></amp-img>
  <amp-img src="image2.jpg" width="400" height="300" layout="responsive" alt="Image 2"></amp-img>
  <!-- Add more slides as needed -->
</amp-carousel>

3. CSSの最適化:
AMPは一部のCSSプロパティやセレクタを制限しています。
また、カスタムCSSは