IT

【TypeScript】React でページの時限公開を実装したい!

フロントだけで公開日時の指定ってどうやってやるんだろう?と思ったのでやってみました。

やること

  • React + Typescript でページの公開開始/終了を実装する
  • 日時情報は環境変数に JST で格納して管理する
  • サイトで利用する日付情報は、環境編集から取得し描画させる
  • CSS は TailWindCSS を利用する
  • favicon 等の要素はいじらない。

プロジェクトのセットアップ

前提

Node.js がインストールされている。

プロジェクト作成

React のプロジェクトを TS で作成

npx create-react-app sample-app --template typescript && cd sample-app

Tailwind CSS のインストール

npm install -D tailwindcss && npx tailwindcss init

Path の設定:src/tailwind.config.js の書き換え

/** @type {import('tailwindcss').Config} \*/
module.exports = {
content: [
"./src/**/\*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

src/index.css に以下を追加

@tailwind base;
@tailwind components;
@tailwind utilities;

これで作成したプロジェクト配下でTSファイルでTailWindが利用できるようになります。

利用するファイル等

  • 新規作成する環境変数用ファイル:src/config/env.ts
// 値は可変です。
export const env = {
  releaseStartDate: new Date(
    process.env.REACT_APP_START_DATE || "2024-05-01T00:00:00+09:00"
  ),
  releaseEndDate: new Date(
    process.env.REACT_APP_END_DATE || "2024-05-231T23:59:00+09:00"
  ),
};
  • メインファイル:src/App.tsx
import React, { useState, useEffect } from "react";
import { env } from "./config/env";

// 時限公開の日時を指定
const releaseStartDate = env.releaseStartDate;
const releaseEndDate = env.releaseEndDate;

// 待機画面のコンポーネント
const ComingSoon: React.FC = () => {
  return (
    <main className="flex items-center justify-center flex-grow bg-gray-100">
      <div className="text-center">
        <h2 className="text-3xl font-bold sm:text-4xl text-gray-800">
          公開までお待ちください。
        </h2>
        <p className="mt-4 text-lg sm:text-xl text-gray-600">
          公開日時: {releaseStartDate.toLocaleString()}
        </p>
      </div>
    </main>
  );
};

// 公開終了画面のコンポーネント
const ReleaseEnded: React.FC = () => {
  return (
    <main className="flex items-center justify-center flex-grow bg-gray-100">
      <div className="text-center">
        <h2 className="text-3xl font-bold sm:text-4xl text-gray-800">
          公開は終了しました。
        </h2>
      </div>
    </main>
  );
};

// メインのコンポーネント
const MainContent: React.FC = () => {
  return (
    <main className="flex-grow container mx-auto py-2">
      <div>
        <h2 className="text-xl font-bold text-gray-800">いらっしゃいませ。</h2>
        <p className="text-gray-700 mb-2">
          このサイトではITエンジニア向けのコンテンツを配信しています。
        </p>
      </div>
      <div>
        {/* ダミーのオブジェクト */}
        <div className="bg-gray-300 h-96 flex items-center justify-center">
          <p className="text-gray-600 text-2xl">コンテンツ領域</p>
        </div>
      </div>
      <div>
        {/* 注記事項 */}
        <p className="m-4 text-lg text-gray-600">
          このコンテンツは {releaseEndDate.toLocaleString()}に公開終了します。
        </p>
      </div>

      <div className="bg-blue-100 p-6 rounded">
        <div className="font-bold mb-4 text-gray-800">お役立ちリンク</div>
        <ul className="list-disc pl-6 text-gray-700">
          <li>エンジニアの1日</li>
          <li>アプリエンジニアって?</li>
          <li>インフラエンジニアって?</li>
          <li>情シスって?</li>
          <li>未経験からエンジニアになるには</li>
        </ul>
      </div>
    </main>
  );
};

const App: React.FC = () => {
  const [isReleased, setIsReleased] = useState(false);
  const [isEnded, setIsEnded] = useState(false);

  useEffect(() => {
    const checkReleaseDate = () => {
      const currentDate = new Date();
      setIsReleased(currentDate >= releaseStartDate);
      setIsEnded(currentDate >= releaseEndDate);
    };

    checkReleaseDate();
    const timer = setInterval(checkReleaseDate, 1000);

    return () => {
      clearInterval(timer);
    };
  }, []);

  return (
    <div className="flex flex-col min-h-screen bg-white">
      <header className="bg-gray-800 text-white py-4">
        <div className="container mx-auto">
          <h1 className="text-lg font-bold sm:text-xl">期間限定公開サイト</h1>
        </div>
      </header>

      {isEnded ? (
        <ReleaseEnded />
      ) : isReleased ? (
        <MainContent />
      ) : (
        <ComingSoon />
      )}

      <footer className="bg-gray-800 text-white py-4">
        <div className="container mx-auto text-center text-sm sm:text-base">
          &copy; 2024 期間限定公開サイト. All rights reserved.
        </div>
      </footer>
    </div>
  );
};

export default App;

動作サンプル

サンプルとして以下のように指定しています。

"2024-05-25T19:04:00+09:00" - "2024-05-25T19:04:10+09:00"

処理の説明

メインの処理

  • useState フックを使用して isReleasedisEnded の 2 つの状態変数を定義しています。
  • isReleased は、現在の日時が公開開始日時(releaseStartDate)以降かどうかを判定し、isEnded は、現在の日時が公開終了日時(releaseEndDate)以降かどうかを判定します。
  • これらの状態変数の値に応じて、↓のコンポーネントがレンダリングされます。
{
  isEnded ? <ReleaseEnded /> : isReleased ? <MainContent /> : <ComingSoon />;
}

状態管理の処理

  • isReleasedisEnded の初期値はどちらも false に設定されています。これにより、初期状態ではメインコンテンツが表示されるようになっています。
  • checkReleaseDate 関数を定義し、現在の日時と公開開始日時・公開終了日時を比較して、isReleasedisEnded の状態を更新します。
  • setInterval を使用して、1 秒ごとに checkReleaseDate 関数を呼び出し、定期的に公開状態をチェックします。
  • コンポーネントのアンマウント時には、clearInterval を使用してタイマーをクリアし、メモリリークを防ぎます。

条件付きレンダリング

  • isEnded が true の場合、ReleaseEnded コンポーネントがレンダリングされ、公開終了画面が表示されます。
  • isEndedfalse で、isReleasedtrue の場合、MainContent コンポーネントがレンダリングされ、メインコンテンツが表示されます。
  • isEndedisReleased がどちらも false の場合、ComingSoon コンポーネントがレンダリングされ、待機画面が表示されます。

TypeScript の型指定

  • TypeScript を使用しているため、env ファイルで定義された環境変数の型を明示的に指定する必要があります。
  • env ファイルで releaseStartDatereleaseEndDate Date 型で指定しています。

JavaScriptと違い、TypeScriptでは型の指定が必要になるため、ちょっと面倒ですね。

最後に

TSを触ったことがなかったので、TSで実施してみましたが特段恩恵は受けられなかったですね。。。

ですが、Amplify Gen2等の新しいプロダクトだとTSがデフォになってきているので、継続して触っていきたいと思います。

  • 参考ドキュメント

Create React App で Tailwind CSS をインストールする

以上、どなたかのお役に立てば幸いです。

  • この記事を書いた人

緑川縁

ニートからシステムエンジニアになった人
クラウド案件をメインにやっています。
保持資格:CCNA,AWS SAA&SAP,秘書検定2級
趣味でボカロ曲作り始めました。

-IT
-, ,