React & TypeScript初学者のすゝめ 第二章

## 簡単な売り上げ記録サービスを作る
フォームの活用やクラス間での値の受け渡しなどReactの基礎知識を使って実際にサービスを作ってみる。
流れとしては
1. セレクトボックスで人数選択
1. 同期処理で合計数と売上を表示
1. 日付を選択し登録ボタンを押すことで売上記録をつける
1. リストから特定の売上記録を削除する機能も実装

完成イメージ
![スクリーンショット 2024-04-23 12.11.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659630/090154fa-a914-50d0-83f2-945754f1b46f.png)

```App.tsx
class App extends React.Component {
  render(){
    return (
    <div>
      <AdmissionFeeCalculator />
    </div>
    )
  }
}
export default App;
```
今回コードの可変性を加味してAppクラスでメイン処理を行わず、AdmissionFeeCalculatorに任せる。
AdmissionFeeCalculatorの大まかな内部構造としてはDetailという売上項目(大人1000円のような)を3つ、下にその合計であるSummaryを、その下に登録機能であるRegisterを、最後に売上記録を表示するSalesListを置く。
```App.tsx
class AdmissionFeeCalculator extends React.Component {
  render() {
    return (
      <div>
        <Detail />
        <Detail />
        <Detail />
        <Summary />
        <Register />
        <SalesList />
      </div>
    );
  }
}
```

## Summary機能実装
```App.tsx
type SummaryProps = {
  numOfPeople: number;
  totalAmount: number;
}

class Summary extends React.Component<SummaryProps> {
  render() {
    return (
      <div className="flex">
        <div className="party">
          <input type="text" className="party" value={this.props.numOfPeople} />
          <span>名様</span>
        </div>
        <div className="total-amount">
          <span>合計</span>
          <input type="text" className="total-amount" value={this.props.totalAmount} />
          <span>円</span>
        </div>
      </div>
    );
  }
}
```
Detailからカテゴリごとの人数を取ってきた後、AdmissionFeeCalculatorでnumOfPeopleとtotalAmountを計算し引数として子要素のSummaryクラスに渡す。
ちなみに<div className="flex">に関して、要素を横一列に並べるための要素なのだが、使用するにあたって**App.css**にコードを追加する。
```App.css
.flex {
  display: flex;
}
```

## Detail機能実装
```App.tsx
type Classification = {
  name: string;
  description: string;
  unitPrice: number;
  numOfPeople: number;
  totalPrice: number;
}
// 親要素を作り型の継承をすることでコーディングをシンプルにできる
type DetailProps = {
  classification: Classification;
  // アロー関数の型宣言の方法(num)の部分は引数
  onNumOfPeopleChange: (num: number) => void;
}

class Detail extends React.Component<DetailProps> {
  // event引数の型宣言はユニーク(onClickのReact.MouseEvent<HTMLInputElement>やonSubmitのReact.FormEvent<HTMLFormElement>などもある)
  onNumOfPeopleChange(e: React.ChangeEvent<HTMLSelectElement>) {
    const num: number = Number(e.target.value);
    this.props.onNumOfPeopleChange(num);
  }
  // onChangeは非常に便利なイベント処理であるため要チェック
  render() {
    return (
      <div className="flex">
        <div className="classification-name">{this.props.classification.name}</div>
        <div className="description">{this.props.classification.description}</div>
        <div className="unit-price">{this.props.classification.unitPrice}円</div>
        <div className="num-people">
          <select value={this.props.classification.numOfPeople} onChange={e => this.onNumOfPeopleChange(e)}>
            <option value="0">0</option>
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
          </select>
          <span>名</span>
        </div>
      </div>
    );
  }
}
```
Detailクラスは3つ配置するため、最初から部品を作ることを前提にコーディングする。
表示する値は基本的に親クラスであるAdmissionFeeCalculatorから引数を使って渡されるが、セレクトボックスに関しては値の変更時にイベントが発生するようにしておく。
```
this.props.onNumOfPeopleChange(num)
```
に関してはAdmissionFeeCalculatorクラスのところで後述するが、Detailクラスに対してonNumOfPeopleChange関数を**引数**として渡しているためこのような実行の形になる。

## メイン機能実装
```App.tsx
type Sales = {
  date: string;
  numOfPeople: number;
  totalAmount: number;
}
type AdmissionFeeCalculatorState = {
  classifications: Classification[];
  tempDate: string;
  salesList: Sales[];
}

// 子から親に値の受け渡しは難しいため、このクラスのStateを土台に値受け渡し及び計算を行う
class AdmissionFeeCalculator extends React.Component<{}, AdmissionFeeCalculatorState> {
  // 型宣言が定かでない場合の?以外の対処法
  constructor(props: {}) {
    super(props);
    // 本日の日付をYYYY-MM-DDの形で表示できるようにしている
    const date = new Date();
    const today = date.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit",}).split("/").join("-");

    const adults: Classification = {
      name: "大人",
      description: "",
      unitPrice: 1000,
      numOfPeople: 0,
      totalPrice: 0,
    };
    const university: Classification = {
      name: "学生",
      description: "大学",
      unitPrice: 800,
      numOfPeople: 0,
      totalPrice: 0,
    };
    const students: Classification = {
      name: "学生",
      description: "高校",
      unitPrice: 700,
      numOfPeople: 0,
      totalPrice: 0,
    };
    // 一番外側の囲いが{}と[]では出来ることが違う。[]はただ値を入れるだけだが、{}は値に名前をつけれる(今回はclassifications)
    this.state = { 
      classifications: [adults, university, students],
      tempDate: today,
      salesList: [],
    };
  }

  // Detailのセレクトボックスから送られてきた値から新しい値の入ったclassification配列を作る
  handleNumOfPeopleChange(_key: number, num: number) {
    const currentClass = this.state.classifications[_key];
    const newTotalPrice = currentClass.unitPrice * num;
    // Object.assign関数は第一引数がベースとなり第二引数の項目を上書きするというもの
    const newClass: Classification = Object.assign(currentClass, { numOfPeople: num, totalPrice: newTotalPrice });
    // 変更したClassificationをリストにぶち込む
    this.state.classifications[_key] = newClass;
    this.setState({ classifications: this.state.classifications });
  }

  render() {
    // _listと_keyの順番が逆ならエラーが出る
    const detailInfo = this.state.classifications.map((_list: Classification, _key: number) => {
      // onNumOfPeopleChangeというアロー関数を引数としてDetailクラスに渡す(Detailに継承するアロー関数の引数nに注目)
      return(
        <Detail classification={_list} key={_key} onNumOfPeopleChange={n => this.handleNumOfPeopleChange(_key, n)}/>
      );
    });
    // map(list => list.listChild).reduce((previous, current) => previous + current)は親要素から指定の子要素のみ取り出した配列を作り、その値を合計する典型文
    const numOfPeople = this.state.classifications.map(cla => cla.numOfPeople).reduce((p, c) => p + c);
    const totalAmount = this.state.classifications.map(cla => cla.totalPrice).reduce((p, c) => p + c);

    return (
    <div>
      {detailInfo}
      <Summary numOfPeople={numOfPeople} totalAmount={totalAmount} />
    </div>
    );
  }
}
```
constructorの部分で初期値を作り、Stateに保存する。
Stateに保存することでその値はAdmissionFeeCalculatorクラス内で自由に使えるのは勿論、引数として渡すことで子クラスでも簡単に扱える。
次にDetailクラスの配置に関して、3つ羅列するのは見栄えも良くないため、
```App.tsx
const detailInfo = this.state.classifications.map((_list: Classification, _key: number) => {
    return(
    <Detail classification={_list} key={_key} onNumOfPeopleChange={n => this.handleNumOfPeopleChange(_key, n)}/>
    );
});
```
という形でmap関数を使いリストをバラして順にDetailクラスに渡していく。

## 登録削除機能実装
```App.tsx
class AdmissionFeeCalculator extends React.Component<{}, AdmissionFeeCalculatorState> {

  /* 上記内容と重複する部分を省略 */

  handleDateChange(e: React.ChangeEvent<HTMLInputElement>) {
    this.setState({ tempDate: e.target.value });
  }
  
  register(numOfPeople: number, totalAmount: number) {
    const currentSalesList = this.state.salesList;
    currentSalesList.push(
    { date: this.state.tempDate,
      numOfPeople: numOfPeople,
      totalAmount: totalAmount
    }
    );
    this.setState({ salesList: currentSalesList });
  }

  removeList(index: number) {
    const currentSalesList = this.state.salesList;
    currentSalesList.splice(index, 1);
    this.setState({ salesList: currentSalesList });
  }

  render() {

  /* 上記内容と重複する部分を省略 */

    return (
    <div>
      {detailInfo}
      <Summary numOfPeople={numOfPeople} totalAmount={totalAmount} />
      <div className="calendar">
       <input type="date" name="calendar" onChange={e => this.handleDateChange(e)} />
      </div>
      <div className="register">
        <button onClick={()=>this.register(numOfPeople, totalAmount)}>登録</button>
      </div>
      <div className="showList">
       <ul>
        {this.state.salesList.map((sales, index) => <li key={index}>
          {sales.date}
          {sales.numOfPeople}人
          {sales.totalAmount}円
          <button onClick={() => { this.removeList(index) }}>削除</button>
        </li>)}
       </ul>
      </div>
    </div>
    );
  }
}
```
Dateフォームの変更時にイベントを発生させStateに指定日付を保存する。
登録ボタンを押した時にイベント発生でregister関数を実行する。そこにおいて新しく作った売上記録をリストに挿入する。

#### 以上がReactの基礎知識を網羅的に使った簡易サービスの開発である。実際に作ってみるのがプログラミング言語習得の最短経路なので是非やってみてほしい。

次章
[React & TypeScript初学者のすゝめ 第三章](https://qiita.com/pigretyasushi/items/c8f08db84934a021f7bf)