React開発入門④ 〜時刻表示＆ヘッダー実装(UseEffect)〜

## 目的
・リアルタイムな時刻表示を実装
・ヘッダーを作成＆CSS適用していい感じにする

## 成果物のイメージ
![スクリーンショット 2024-11-10 150758.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/f8d96bc1-f723-4330-6d7a-81483f215a5e.png)

こんな感じの奴を作ります

## 実装
### ヘッダーコンポーネント作成
別ファイルにヘッダーコンポーネントを作成します。
・ログインユーザー名(後々の実装になるため仮置き)
・ページネーション
の2つをとりあえずヘッダーに配置します。
(CurrentTimeは次に作成するコンポーネントです。）

```PageHeader.jsx
import React from "react";
import { Link } from "react-router-dom";
import { CurrentTime } from "./CurrentTime";

export const Page_Header = () => {

  return (
    <div>
      <header>
        <h1>勤怠管理システム</h1>
        <p>ようこそ、Guestさん</p>
        <CurrentTime />
        <nav>
          <ul>
            <li><Link to="/">トップページ</Link></li>
            <li><Link to="/punchtimeclock">タイムレコーダー</Link></li>
          </ul>
        </nav>
      </header>
    </div>
  );
};
```

### 時刻表示用コンポーネント
現在時刻を表示させるコンポーネントは別で作成し、ヘッダーコンポーネント内で呼び出すようにします。
時刻表示は1秒ごとに表示が変わる=頻繁に再レンダリングが生じるコンポーネントです。
そのため、ヘッダー部全体を再レンダリングするのは非常に無駄な処理です。
時刻表示の部分のみを子コンポーネントとして切り出すことで再レンダリングの範囲を小さくすることができます。
```CurrentTime.jsx
import React, { useEffect,useState } from 'react';

// 現在時刻を取得して返す関数
const fetchCurrentTime = () => {
        const date = new Date;
        const H = date.getHours(),
                M = date.getMinutes(),
                S = date.getSeconds();
        return `${H}:${M}:${S}`
    };


// 現在時刻をレンダリングして返す関数
export const CurrentTime = () => {

    // 現在時刻管理のuseStateを定義
    const [currentTime, setCurrentTime] = useState(fetchCurrentTime);
    const updateCurrentTIme = () =>{
        setCurrentTime(fetchCurrentTime);
    };

    // 初回レンダリング時にのみ、実行
    useEffect(() => {
        // 0.5秒間隔でuseState更新
        const intervalId = setInterval(updateCurrentTIme, 500);
        // クリーンナップ関数（コンポーネント削除時に実行)
        return () => { clearInterval(intervalId); };
    },[]);
    
    return (
        <p>現在時刻:{ currentTime }</p>
    );
};

};
```

#### useState
コンポーネント内の情報(ただのconst)などは再レンダリングが起こると消えてしまいます。そのため、変数に変更が加わるたびに再レンダリングを起こしたとしても状態の表現は実現できません。
再レンダリングが起きても保持される変数の定義とその変数が変わるたびに再レンダリングを起こすにはuseStateを使います。


#### setInterval関数
setIntervalは関数を指定した秒数間隔で実行できます。
明示的に止めないと無限ループするため、**clearInterval()で停止させるのを忘れないこと**
0.5秒毎変にState更新関数を呼び出してCurrentTimeの値を最新の時刻に変化させています。

:::note
明示的にループさせた関数やファイルなどは必ず明示的に閉じるようにしましょう。
メモリリークなどの原因になり、パフォーマンスの低下を招きます。（今回はフロントなので大きな問題にはなりませんが、サーバーサイドのメモリリークはCPU使用率上昇などで死にます。）
:::


## useEffect
こいつはムズイです。（自分も曖昧な理解...)
簡単にいうと外部APIや特定の状態変数を監視して何かアクションや変更があった時に指定の関数などを実行させられる機能です。

useEffectは2つの引数を取ります。
第一引数：関数（アロー関数）
ここは対象が変化したときに何を実行するかを関数で定義します。
今回はsetInterval関数を使用して0.5秒ごとにState更新を行っています。

第二引数：依存配列を指定する
依存配列とは「これが変わったら第一引数の関数を実行させてね～」を定義します。
**ただし、引数がない場合は初回のレンダリング時に1回のみ実行させます**
今回は初回レンダリング後に無限ループさせればいいので依存配列の引数はありません。

return内：クリーンナップ関数と呼ばれ、このコンポーネントが削除されるときに実行される関数です。
setIntervalの無限ループをここで明示的に終了させています。
**

最後にこのヘッダーコンポーネントをそれぞれのコンポーネント内に呼び出して表示させてください。

![スクリーンショット 2024-11-10 154106.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/aeaf52e6-9913-1722-24dd-7562abb58d97.png)
こんな感じになったと思います。
正直、殺風景というかとてもヘッダーって感じではないですね

htmlはあくまでwebページの構成要素を示すファイルであり、見た目を整えるためには『CSS』というスタイルシートを使用します。

### ヘッダーにCSSを適用してみる
CSSを用いてヘッダーの見た目をいい感じに。
自分はデザイナーではないのでセンスがないのは気にしないでください。
まず、ヘッダーに適用させるCSSが下記になります。

:::note
CSS適用には『CSS modules』を使用しています。
:::

```headerstyle.module.css
.header header {
    border: 1px solid silver;
    width: auto; /* 横幅はウィンドウのサイズに自動調整 */
    display: flex; /* フレックスコンテナ(子要素を横並び) */
    flex-wrap: wrap; /* 要素を折り返す(navを折り返している) */
    align-items: center; /* 縦方向の中央揃え */
    background-color: skyblue; /* 背景色 */
}
.header h1 {
    padding-left: 1%;
    margin: 0pt ; /* 上下方向の余白削除 */
    margin-right: auto; /* 右方向の余白を自動調整 */
}
.header p {
    margin: 0pt 10pt; /* 上下方向の余白削除＆左右10pt確保 */
}

.header nav {
    background-color: aliceblue;
    width: 100%; /* navを全幅にする（折り返される） */
    padding-top: 2pt;
}
.header nav ul {
    display: flex; /* ul内要素をフレックスコンテナ（リストを横並び) */
    padding: 0pt 5pt;
    margin: 0;
    list-style: none; /* リストスタイルを削除 */
}
.header nav li {
    padding-left: 1%;
    padding-right: 1%;
    width: auto;
}
.header nav li:not(:last-child){
    border-right:2px solid silver;
}
.header nav li a {
    text-decoration: none; /* リンクの下にある線を消す */
}
.header nav a:hover{
    color: violet;
}

/* =====疑似要素===== */

/* 疑似要素でリンクがホバーされると線を横から表示させる */
.header nav li a:after {
    position: relative;
    display: block;
    content: "";
    width: 0%;
    height: 0.5px;
    bottom: 3pt;
    background: violet;
    transition: all 0.2s cubic-bezier(0.455, 0.03, 0.515, 0.955);
  }
.header nav li a:hover:after {
    width: 100%;
}
```

また、このCSSをヘッダーコンポーネントに読み込ませないといけません。
CSSを読み込ませるためには
CSSファイルをimportする（importするときの名前は自由に決められます。)
CSSとhtmlをclassName={}でリンクさせます。
(CSS moduleの書き方です)

```PageHeader.jsx
import React from "react";
import { Link } from "react-router-dom";
import { CurrentTime } from "./CurrentTime";
import headercss from "../css/headerstyle.module.css"

export const Page_Header = () => {

  return (
    <div  className={headercss.header}>
      <header>
        <h1>勤怠管理システム</h1>
        <p>ようこそ、Guestさん</p>
        <CurrentTime />
        <nav>
          <ul>
            <li><Link to="/">トップページ</Link></li>
            <li><Link to="/punchtimeclock">タイムレコーダー</Link></li>
          </ul>
        </nav>
      </header>
    </div>
  );
};
```

![スクリーンショット 2024-11-10 155200.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3801383/038ed137-9a1f-d544-0d2e-b5e8f31b8454.png)
見栄えがそこそこよくなりました。


## NEXT
ログインフォームをやりたいです。