React Study(3) ～ 世界時計を作ってみよう

# 世界時計を作る
　ここまでサンプルプログラムを動かすようなことだけやってきたので、今ある知識で作れそうなものをとりあえず一つ作ってみましょう。
　ドロップダウンがあってそこでタイムゾーンを選ぶと、そのタイムゾーンの時計が表示され、秒を刻むシンプルな世界時計を作ってみます。

## プロジェクト作成
　まずはcreate-react-appでベースを作ります。コマンドぽちでしばし待機。

```console:create-react-app
c:\projects>npx create-react-app react_watch
（省略）
We suggest that you begin by typing:

  cd react_watch
  npm start

Happy hacking!

c:\projects>
```

　ここまではほぼテンプレ。ここからsrc/App.jsをいじります。本当はちゃんとコンポーネント化したほうがいいよ、ってのをよく見るのですが、とりあえずApp.jsに全部書いちゃいます。

## 画面の構成
　完成系はこんな感じです。スタイルも何もなく非常にそっけない画面ですが気にしません。
![react-day3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/833719/6b4dd480-129b-437e-b9a7-b6d468512e6d.jpeg)

 - 現在時刻
 - タイムゾーン
 - タイムゾーンのセレクタ

　パーツはこれだけになります。動きとしてはタイムゾーンのセレクタを動かすとそのタイムゾーンの時間を表示してくれる。これだけです。

## 画面パーツ
　最初に画面パーツ用の部品を用意します。と、言ってもセレクタ用のoptionの配列と、いくら何でもマージンのない画面も。。。ということで申し訳程度のstyleです。

```javascript:App.js
const WatchComponent = () => {

  // 時間
  const date = new Date();

  // 最低限のスタイル…
  const style = { margin:"10pt" };

  // 選択できるタイムゾーン
  const timeZones = [
    {id:1, zone:"Asia/Tokyo"},
    {id:2, zone:"Europe/London"},
    {id:3, zone:"America/New_York"}
  ];

  // タイムゾーンをoptionタグに変換しておく
  const SelectItems = timeZones.map((item)=>{
    return (
      <option value={item.zone} key={item.id}>{item.zone}</option>
    )
  });
```

　後から好きにタイムゾーンを増やせるようにoptionタグをべた書きせずに配列に入れて、map関数でばらしてoptionタグに変換しています。

## 状態の保存
　状態は選ばれているタイムゾーン、今の時間。この２つです。

```javascript:App.js
  // 初期は日本時間
  const [selectedTimeZone, setTimeZone] = useState(timeZones[0].zone);

  // 初期表示は日本時間の今
  const [dispTime, setTime] = useState(date.toLocaleString('en-US',{ timeZone: selectedTimeZone }));

  // selectが選択された時の挙動
  const handleChange = (e) => {
    // タイムゾーンを選択されたものにする
    setTimeZone(e.target.value);
  };
```
　useStateを使って２つの状態を宣言します。それぞれselectedTimeZone、dispTimeです。
　セレクタが選択された時にタイムゾーンを設定しなおすhandlerも宣言しておきます。

## １秒ごとに画面を更新
　さて、時計アプリなので毎秒画面を更新したいので、useEffectからsetIntervalでタイマーを作ります。クリーンアップ関数でそのタイマーを削除する処理も忘れずに。

```javascript:App.js
  const callbackFunction = () =>{
    // 1000ミリ秒ごとにsetTimeしなおす
    const timer = setInterval(
      () => {
        setTime(new Date().toLocaleString('en-US',{ timeZone: selectedTimeZone }));
      }
      ,1000);
    return () => {
      clearInterval(timer);
    };
  };

  // 時間・タイムゾーンに変更があったら呼び出し
  useEffect(callbackFunction,[selectedTimeZone,dispTime]);
```

　タイムゾーンが変更されたり、表示時間が変わったたらまた１秒待って再描画…を繰り返しています。毎秒callbackFunctionを作り直してる動きになるのかな？ここのパフォーマンスをよくするにはuseCallbackを使うようですが、それはおいおい…

## 時計の表示
　これでパーツはそろったのであとは表示だけ。

```javascript:App.js
  return (
    <>
      <div style={style}>
        <div>{dispTime}</div>
        <div>{selectedTimeZone}</div>
        <select value={selectedTimeZone} onChange={handleChange}>
          {SelectItems}
        </select>
      </div>
    </>
  );
```

これで無事、時計が表示されました。

### ソース全体
　ソース全体を貼り付けます。正直React云々じゃなくてJavascriptとして修正すべき点が多い気がしますが、学習数日でこの少ないコード量でインタラクティブなページが簡単にできるのはReactの力かなぁと感じました。

```javascript:App.js
import React, {useState,useEffect} from "react";

const WatchComponent = () => {

  // 時間
  const date = new Date();

  // 最低限のスタイル…
  const style = { margin:"10pt" };

  // 選択できるタイムゾーン
  const timeZones = [
    {id:1, zone:"Asia/Tokyo"},
    {id:2, zone:"Europe/London"},
    {id:3, zone:"America/New_York"}
  ];

  // タイムゾーンをoptionタグに変換しておく
  const SelectItems = timeZones.map((item)=>{
    return (
      <option value={item.zone} key={item.id}>{item.zone}</option>
    )
  });

  // 初期は日本時間
  const [selectedTimeZone, setTimeZone] = useState(timeZones[0].zone);

  // 初期表示は日本時間の今
  const [dispTime, setTime] = useState(date.toLocaleString('en-US',{ timeZone: selectedTimeZone }));

  // selectが選択された時の挙動
  const handleChange = (e) => {
    // タイムゾーンを選択されたものにする
    setTimeZone(e.target.value);
  };

  const callbackFunction = () =>{
    // 1000ミリ秒ごとにsetTimeしなおす
    const timer = setInterval(
      () => {
        setTime(new Date().toLocaleString('en-US',{ timeZone: selectedTimeZone }));
      }
      
      ,1000);
    return () => {
      clearInterval(timer);
    };
  };

  // 時間・タイムゾーンに変更があったら呼び出し
  useEffect(callbackFunction,[selectedTimeZone,dispTime]);

  return (
    <>
      <div style={style}>
        <div>{dispTime}</div>
        <div>{selectedTimeZone}</div>
        <select value={selectedTimeZone} onChange={handleChange}>
          {SelectItems}
        </select>
      </div>
    </>
  );
};

export default function App() {
  return <WatchComponent />;
}
```