投稿日:2021年7月30日

前回記事 の続きになります。
 

前回と同じですが、アプリと仕様を掲載しておきます。

 仕様

経過日数に調べたい日数を入力し、ラジオボタンからそれぞれ選択します。
日数計算ボタンをクリックすると日付欄に反映され、その時代の年号が表示されます。
日付欄に、日付を直接入力。もしくはカレンダーから日付を選択し、日数計算ボタンをクリックすると同じく、日付欄に反映され、その時代の年号が表示されます。
このとき、ラジオボタンが日前以外を選択していると、日前にチェックが入ります。
日数計算ボタンをクリックすると、表示がクリアになり、日数入力欄日付欄はグレーアウトし、触れなくなります。
クリアをクリックするとリセットされます。
日付入力欄に数値入力し、さらに日付を変更して日数計算をクリックすると、入力した数値が優先されます。
 

前回はReactプロジェクトの環境構築 まで行いました。
 

早速ですが、構築したディレクトリから不要なファイルを削除しましょう。
Node.js
▲赤囲みのファイルを削除します。
場合によっては必要なものもあるので都度検証してから削除するようにしましょう。
ファビコン(favicon.ico)は今回はこれを使用します。
 

Node.js
▲整理した結果です。ここからindex.jsやApp.jsを書き換えたり、必要なコンポーネントを追加していきます。
 

前回、書き忘れてましたが、Bootstrapコンポーネントを使用する場合、別途インストールが必要になります。
 

ターミナルでプロジェクトディレクトリに移動します。
npm startでプロジェクトが表示されていた場合は、control + c(Mac)で終了します。
npm install react-bootstrap bootstrapと入力しreturnを押すとインストールが開始されます。
Node.js
 

public内、index.htmlから不要な記述を削除します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <title>Calendar App</title>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>

</html>

▲言語属性をjaにします。
meta descriptionは必要に応じて書き直しましょう。
noscriptタグも今回はこのまま使用します。
 

次にコンポーネント分けです。
コンポーネント
▲このように分けました。
他に、親コンポーネントとしてApp.jsや、共通して使う関数Functions.jsを別途作成しました。
試行錯誤しましたが、今の技量だと上記のような感じになりました。
上級者の方々から見れば、ツッコミどころ満載だと思います。。。
コンポーネント間の関数、変数の受け渡しや高度な記述を身に付け、精進したいと思います。
 

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';

ReactDOM.render(
    <App /> , document.getElementById('root')
);

▲基になるJSです。
ReactDOM.render でApp.jsをindex.htmlのrootに出力させます。
BootstrapのCSSもここで読み込みます。
 

では、各コンポーネントです。
App.js

import './App.css';
import React from "react";
import DaysBtn from "./DaysBtn";
import CalcBtn from './CalcBtn';
import Calendar from './Calendar';
import Nengou from './Nengou';

class App extends React.Component {
  render() {
    return (
    <div id="contentsWrap">
      <DaysBtn />
      <CalcBtn />
      <div className="calendarWrap">
        <Calendar />
        <Nengou />
      </div>
    </div>
    );
  }
}

export default App;

▲親コンポーネントだけclassコンポーネントにしました。
 

Functions.js

// ▼▼▼ ゼロ埋め関数 2桁
exports.zeroPad = (zeroNum) => {
  zeroNum = ('00' + zeroNum).slice(-2);
  return zeroNum;
}

// ▼▼▼ ゼロ埋め関数 4桁
exports.zeroPadYear = (zeroNum) => {
  zeroNum = ('0000' + zeroNum).slice(-4);
  return zeroNum;
}

// ▼▼▼ 今日の日付を取得する関数
exports.getToDay = () => {
  let getToday = new Date(),
    y = getToday.getFullYear(),
    m = getToday.getMonth() + 1,
    d = getToday.getDate();
  let todayDate = `${this.zeroPadYear(y)}-${this.zeroPad(m)}-${this.zeroPad(d)}`;
  return todayDate;
}

///////////////////////////////////////////////
// ▼▼▼ 年号取得関数
exports.getNengou = (todayDate) => {
  switch (true) {
    /////////////////////////////////////////////
    // ▼▼▼ 大化以前
    case todayDate <= `0001-01-01`:
      return `カレンダーの最小は、0001年1月1日です。`;
      break;
    case todayDate < `0645-07-17`:
      return `年号がありません。`;
      break;

      /////////////////////////////////////////////
      // ▼▼▼ 飛鳥時代
    case `0645-07-17` <= todayDate && todayDate < `0650-03-22`:
      return `大化(たいか)`;
      break;

    ///////////// 省略 /////////////
   
    case `1865-05-01` < todayDate && todayDate < `1868-10-23`:
      return `慶応(けいおう)`;
      break;

      /////////////////////////////////////////////
      // ▼▼▼ 明治以降
    case `1868-10-23` === todayDate:
      return `慶応(けいおう)→ 明治(めいじ)`;
      break;
    case `1868-10-23` < todayDate && todayDate < `1912-07-30`:
      return `明治(めいじ)`;
      break;
    case `1912-07-30` === todayDate:
      return `明治(めいじ)→ 大正(たいしょう)`;
      break;
    case `1912-07-30` < todayDate && todayDate < `1926-12-25`:
      return `大正(たいしょう)`;
      break;
    case `1926-12-25` === todayDate:
      return `大正(たいしょう)→ 昭和(しょうわ)`;
      break;
    case `1926-12-25` < todayDate && todayDate < `1989-01-07`:
      return `昭和(しょうわ)`;
      break;
    case `1989-01-07` === todayDate:
      return `昭和(しょうわ)`;
      break;
    case `1989-01-08` <= todayDate && todayDate < `2019-04-30`:
      return `平成(へいせい)`;
      break;
    case `2019-04-30` === todayDate:
      return `平成(へいせい)`;
      break;
    case `2019-05-01` <= todayDate && todayDate < `2200-12-31`:
      return `令和(れいわ)`;
      break;
    case todayDate >= `99999-12-31`:
      return `カレンダーの最大は99999年12月31日です。`;
      break;
  }
}

▲共通して使用する関数をまとめてあります。
exports.関数名 のように書くとエクスポートでき、インポート先では、Functions.関数名 のように書いて呼び出します。
 

DaysBtn.js

import React, { useState } from 'react'; //フック
import Form from 'react-bootstrap/Form'; //Bootstrapコンポーネント

const DaysBtn = () => {
  const [val, setVal] = useState("日前");
  const handleChenge = e => setVal(e.target.value);
      return (
        <div className="inputNumWrap">
          <p>経過日数</p>
          <input type="text" id="inputNum" />
          <Form id="radioWrap">
            <div className="mb-1">
              <Form.Check inline label="日前" value="日前" name="radio" type="radio" id="radio01" checked={val === "日前"} onChange={handleChenge} />
              <Form.Check inline label="年前" value="年前" name="radio" type="radio" id="radio02" checked={val === "年前"} onChange={handleChenge} />
              <Form.Check inline label="日後" value="日後" name="radio" type="radio" id="radio03" checked={val === "日後"} onChange={handleChenge} />
              <Form.Check inline label="年後" value="年後" name="radio" type="radio" id="radio04" checked={val === "年後"} onChange={handleChenge} />
            </div>
          </Form>
        </div>
      )
  return val;
}

export default DaysBtn;

▲ラジオボタンの状態管理が必要なのでuseStateフックを読み込み、各ラジオボタンに設定しています。
最初、map関数を使って表示させてましたが、デフォルトでチェックを入れられないようなので、上記のように書きました。
コメントにもありますがFormに関するBootstrapコンポーネントも読み込んでいます。
 

CalcBtn.js

import React, { useState } from 'react'; //フック
import Functions from './Functions'; //Functionsコンポーネント
import Button from 'react-bootstrap/Button'; //Bootstrapコンポーネント

////////////////////////
// ▼▼▼ 関数コンポーネント
const CalcBtn = () => {
  const [flg, setFlg] = useState(true);
  const toggle = () => {
    const inputNum = document.getElementById('inputNum'); //経過日数入力欄
    const inputNumVal = inputNum.value; // テキスト入力欄の値
    const calendar = document.getElementById('calendar'); //カレンダー
    const radioWrap = document.getElementById('radioWrap'); //ラジオボタンを取得
    const nengou = document.getElementById('nangouInputField'); //年号を取得

     // ▼▼▼ 選択しているラジオボタンからのロジック
    for (let i = 0; i < radioWrap.radio.length; i++) {
      if (radioWrap.radio[i].checked) {
        switch (radioWrap.radio[i].value) {
          case "日前": // 日前を選択した場合
            var passedYears = "";
            var passedDays = inputNumVal * -1;
            break;
          case "年前": // 年前を選択した場合
            var passedYears = inputNumVal * -1;
            var passedDays = "";
            break;
          case "日後": // 日後を選択した場合
            var passedYears = "";
            var passedDays = inputNumVal * 1;
            break;
          case "年後": // 年後を選択した場合
            var passedYears = inputNumVal * 1;
            var passedDays = "";
            break;
        }
      }
    }
   
    // ▼▼▼ 入力された数値から年月日を算出する関数
    const setToDay = (days, years, cal) => {
      let getToday = new Date();
      getToday.setDate(getToday.getDate() + days); // 変更日を取得
      getToday.setFullYear(getToday.getFullYear() + years); // 変更年を取得
      let y = getToday.getFullYear(),
          m = getToday.getMonth() + 1,
          d = getToday.getDate();
      cal.value = `${Functions.zeroPadYear(y)}-${Functions.zeroPad(m)}-${Functions.zeroPad(d)}`; // 変更後の日付をカレンダーに表示
      document.getElementById('nangouInputField').innerText = Functions.getNengou(cal.value); //年号を表示
    }
    
    // ▼▼▼ カレンダー変更の関数
    const changeCalendar = () => {
      const changeCalVal = calendar.value; //変更したカレンダーの値を取得
      let getToday = new Date();
      let y = getToday.getFullYear(),
          m = getToday.getMonth() + 1,
          d = getToday.getDate();
      const todayCalDate = new Date(`${Functions.zeroPadYear(y)}-${Functions.zeroPad(m)}-${Functions.zeroPad(d)}`); // 今日の日付を'yyyy-mm-dd'形式で取得
      const changeCalDate = new Date(changeCalVal); // 入力した日付を'yyyy-mm-dd'形式で取得
      let differenceDays = ((todayCalDate - changeCalDate) / 86400000); // 86400000は、1日をミリ秒に換算
      if (differenceDays < 0) { // 変更日がマイナスの場合、後日の場合
        differenceDays = differenceDays * -1;
        document.getElementById('radio01').checked = false; // 日前のチェックを外す
        document.getElementById('radio03').checked = true; // 日後のチェックを入れる
      } else {
        document.getElementById('radio01').checked = true; // ラジオボタンを日数にする
      }
      document.getElementById('nangouInputField').innerText =Functions.getNengou(changeCalVal); //年号を表示
      document.getElementById('inputNum').value = differenceDays;
      document.getElementById('inputNum').disabled = true;
      document.getElementById('calendar').disabled = true;
    }
    
    setFlg(!flg);
    flg ?
      (v => { //数計算ボタン
        inputNum.disabled = true;
        calendar.disabled = true;
        if (inputNum.value != "") { // 入力欄に何か入力されたら
          setToDay(passedDays, passedYears, calendar);
        } else {
          changeCalendar();
        }
        
      })() :
      (v => { //クリアボタン
        inputNum.disabled = false;
        calendar.disabled = false;
        document.getElementById('radio01').checked = true; //日数をチェック
        inputNum.value = ""; //入力欄を空欄にする
        calendar.value = Functions.getToDay(); //カレンダーを今日に戻す
        nengou.innerText = Functions.getNengou(Functions.getToDay()); //年号を令和に戻す
      })();
  }

  return (
    <div className="card-body">
      <Button variant="primary" id={flg ? 'getNengouBtn' : 'clearBtn'} onClick={toggle}>{flg ? '日数計算' : 'クリア'}</Button>
    </div>
  );
}

export default CalcBtn;

日数計算ボタンの処理です。
useStateフックでflgを立て、trueとfalseの切り替えを設定しています。
日付入力欄の処理と、カレンダーが変更されたロジックも組み込み、flgの切り替えでid値とボタンの表示を切り替えています。
DaysBtn.jsでuseStateフックで切り替えを設定しましたが、document.getElementById('radioWrap');で取得したラジオボタンにチェック状態の分岐を書いています。
この辺はコンポーネントを統合してflgの切り替え処理の中に記述しても良さそうですね。。。
 

Calendar.js

import Functions from './Functions'; //Functionsコンポーネント

const Calendar = () => {
  return (
    <>
      <p id="displayDay">日付</p>
      <input type="date" id="calendar" defaultValue={Functions.getToDay()} />
    </>
  )
}

export default Calendar;

▲カレンダーのロジックです。
カレンダーのvalueをFunctions.jsgetToday関数から取得しています。
 

Nengou.js

import Functions from './Functions';

const Nengou = () => {
  return (
    <>
      <p> id="displayNengou">年号</p>
      <p> id="nangouInputField">{Functions.getNengou(Functions.getToDay())}</p>
    </>
  );
}

export default Nengou;

▲年号を表示するコンポーネントです。
Functions.jsからgetNengou関数を実行し、引数をFunctions.jsgetToDay関数としています。
 

最後にbiuld静的htmlを書き出します。
デフォルトの状態でbiuldすると画面が真っ白になるのでpackage.jsonに下記の1行を追記します。

{
  "name": "calendar-app",
  "version": "0.1.0",
  "homepage": "./", //←これを追記
  "private": true,

 

biludします。
npm startでプロジェクトが表示されていた場合は、control + c(Mac)で終了します。
npm run buildと入力しreturnを押します。
すると同じディレクトリにbuildフォルダが書き出されます。
これをサーバーにデプロイします。
build
▲書き出されたbuildディレクトリ
 

まとめ

前回のJavaScriptアプリ のReactプロジェクト化は非常に苦労しました。
その甲斐あってか、なんとか動かすことができ、充実感、達成感を感じています。
コンポーネントの分割、分割後の各コンポーネント間の関数、変数の受け渡しなど、色々な書き方があるのだなぁ。。。と、とても勉強になりました。
 

今後の課題として、さらに理解度を上げることと、このアプリをReact-native化し年内にはスマホアプリとしてリリースできればと思います。
 

Facebook Twitter などでご意見、ご指摘お待ちしております。
 

最後まで読んでくださりありがとうございました。
 

関連記事




 

Pocket