UI5 Web Components for React で参戦ライブを振り返る（TypeScript 編）

## はじめに

この記事は [SAP Advent Calendar 2021](https://adventar.org/calendars/6252) の12月21日分の記事として執筆しています。

コロナ禍で多くのイベントがオンライン開催となっていますが、SAP のテクニカルカンファレンスである「SAP TechEd」もそのひとつです。参加無料で多くの最新情報に触れる絶好の機会ですので、ご興味ある方は、以下にアクセスしてみてください。

- [SAP TechEd 2021 Online](https://www.sap.com/about/events/teched.html)
- [SAP TechEd Japan 2021 Online](https://saptechedjapan.com/)

さて、タイトルに TypeScript 編とありますが、この記事は「[UI5 Web Components for React で参戦ライブを振り返る](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720)」の続編となります。そもそも 「Web Components とは？」など基本情報をまとめていますので、Web Coponents そのものについて知りたい方は、こちらも合わせてご覧ください。

普段、SAP と関わりのない方々は、「SAP って ERP の会社でしょ？」、「SAP なのに Web Components？ React？」と思うかも知れません。しかし、SAP 単なる ERP 企業ではなく、自身がクラウドシフト（ERP自体のクラウド版へのシフトや、拡張開発のためのクラウドラットフォームの提供など）を進める上で、オープンソースコミュニティをうまく巻き込み、コミュニティへの貢献を強力にコミットしているクラウド企業の側面も持ちます。

この記事でご紹介する UI5 Web Components for React も Apache License 2.0 ライセンスによる SAP 謹製オープンソースのひとつです（[SAP の公式 GitHub](https://github.com/SAP) では、200 を超えるリポジトリ上で、様々なプログラミング言語による開発が進められています）。この記事が皆さんにとって SAP オープンソースの入り口となれば幸いです。

## React & TypeScript プロジェクトを作成する

まず、はじめに React & TypeScript プロジェクトを作成し、起動確認までしてみましょう。

### テンプレートを活用した初期プロジェクトの作成

古い人間なので、型に厳しい言語の方が好ましいこともあり、TypeScript で React プロジェクト（プロジェクト名：``lives-in-my-life-ts``）を作成します。[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) では、npx を利用していましたが、今回は yarn にしてみました。

```shell
yarn create react-app lives-in-my-life-ts --template typescript
```

早速、起動確認してみましょう。

```shell
cd lives-in-my-life-ts
yarn start
```

極めてシンプルなテンプレートアプリが起動されました。
![screenshot-localhost_3001-2021.12.13-17_07_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/7e4aa6d4-ce4c-39e3-bad8-38d5f3247cca.png)

### UI5 Web Components for React モジュールの追加

[公式サイト](https://sap.github.io/ui5-webcomponents-react/?path=/story/getting-started--page)に記載されている 3 つのモジュールを追加してみましょう。

- @ui5/webcomponents
- @ui5/webcomponents-react
- @ui5/webcomponents-fiori

```shell
yarn add @ui5/webcomponents @ui5/webcomponents-react @ui5/webcomponents-fiori
```

package.json に指定した 3 モジュールが追加されています。``@ui5/webcomponents-react`` は、まだ ``0.20.3`` ですね、、、。一方で、``@ui5/webcomponents`` と ``@ui5/webcomponents-fiori`` は、``1.0.2`` として正式リリース済みでした。[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) の時点では、まだ ``1.0.0-rc`` リリース候補（Release Candidate）でしたが、2021年11月10日に正式リリースされたようです！

```json:package.json
{
  "name": "lives-in-my-life-ts",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "@types/jest": "^26.0.15",
    "@types/node": "^12.0.0",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@ui5/webcomponents": "^1.0.2",
    "@ui5/webcomponents-fiori": "^1.0.2",
    "@ui5/webcomponents-react": "^0.20.3",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-scripts": "4.0.3",
    "typescript": "^4.1.2",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```

## エラー修正前に関数コンポーネント化しておく

[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) の JavaScript ソース ``App.js`` の内容を ``App.tsx`` に丸ごと貼り付けただけでは、大量の TypeScript エラーが発生します。このエラーをひとつひとつ潰していきたいのですが、[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) 以降、React 自体が進化し、よりモダンかつスマートな実装が可能となっています。エラー解消よりも先に、コードをモダナイズしておきます。

### App クラスコンポーネントの関数コンポーネント化

ここでは手始めに、メインの ``App`` クラスコンポーネントを ``App`` 関数コンポーネントに書き換えておきましょう。以下の変更を加えていきます。コンポーネントについて詳しく知りたい方は、[React 公式リファレンス](https://ja.reactjs.org/docs/components-and-props.html) をご参照ください。

- ``class`` をシンプルなアロー関数へ変更
- ``state`` を ``useState`` フックでスッキリと
- ``filterText`` が ``string`` 型であることを明示
- ``handleFilterTextChange`` 内は ``filterText`` のセッターでスッキリと
- 自身が関数となったため、``render()`` メソッドを削除してスッキリと
- 自身が関数となったため、``this`` 参照や ``this.state`` 参照を削除してスッキリと

```jsx:App&nbsp;クラスコンポーネント（変更前）
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      filterText: "",
    };
  }

  handleFilterTextChange = (filterText) => {
    this.setState({
      filterText: filterText,
    });
  };

  render() {
    return (
      <ThemeProvider>
        <FlexBox
          style={{ width: "100%", height: "100%" }}
          direction={FlexBoxDirection.Column}
          justifyContent={FlexBoxJustifyContent.Center}
          alignItems={FlexBoxAlignItems.Center}
        >
          <LiveToolbar
            filterText={this.state.filterText}
            onFilterTextChange={this.handleFilterTextChange}
          />
          <LiveTimeline filterText={this.state.filterText} />
        </FlexBox>
      </ThemeProvider>
    );
  }
}
```

```jsx:App&nbsp;関数コンポーネント（変更後）
const App = () => {
  const [filterText, setFilterText] = useState<string>("");
  const handleFilterTextChange = (filterText: string) => {
    setFilterText(filterText);
  };

  return (
    <ThemeProvider>
      <FlexBox
        style={{ width: "100%", height: "100%" }}
        direction={FlexBoxDirection.Column}
        justifyContent={FlexBoxJustifyContent.Center}
        alignItems={FlexBoxAlignItems.Center}
      >
        <LiveToolbar
          filterText={filterText}
          onFilterTextChange={handleFilterTextChange}
        />
        <LiveTimeline filterText={filterText} />
      </FlexBox>
    </ThemeProvider>
  );
};
```

だいぶスッキリとしましたね！

### LiveTimeline クラスコンポーネントの関数コンポーネント化

続けて、``LiveTimeline`` コンポーネントです。以下の変更を加えていきます。

- ``class`` を ``React.FC`` 型へ変更（FC は Function Component の略）
- パラメータ ``props`` が ``LiveTimelineProps`` 型であることを明示
- ``LiveTimelineProps`` 型を宣言しておく
- 自身が関数となったため、``this`` 参照を削除してスッキリと

```jsx:LiveTimeline&nbsp;コンポーネント（変更前）
const LiveTimeline extends React.Component {
  render() {
    const filteredLives = LIVES.filter((element) => {
      return (
        (element.date + element.title + element.venue)
          .toLocaleLowerCase()
          .indexOf(this.props.filterText.toLowerCase()) > -1
      );
    });

    if (filteredLives.length > 0) {
      return (
        <Timeline className="Lives-timeline">
          {filteredLives.map((element, index) => {
            return (
              <TimelineItem
                key={index}
                icon="calendar"
                itemName={element.title}
                subtitleText={element.date}
              >
                <div>{element.venue}</div>
              </TimelineItem>
            );
          })}
        </Timeline>
      );
    } else {
      return (
        <MessageStrip className="Lives-timeline">No data found.</MessageStrip>
      );
    }
  }
}
```

```jsx:LiveTimeline&nbsp;コンポーネント（変更後）
type LiveTimelineProps = {
  filterText: string;
};
// 途中省略
const LiveTimeline: React.FC<LiveTimelineProps> = (props) => {
  const filteredLives = LIVES.filter((element) => {
    return (
      (element.date + element.title + element.venue)
        .toLocaleLowerCase()
        .indexOf(props.filterText.toLowerCase()) > -1
    );
  });

  if (filteredLives.length > 0) {
    return (
      <Timeline className="Lives-timeline">
        {filteredLives.map((element, index) => {
          return (
            <TimelineItem
              key={index}
              icon="calendar"
              itemName={element.title}
              subtitleText={element.date}
            >
              <div>{element.venue}</div>
            </TimelineItem>
          );
        })}
      </Timeline>
    );
  } else {
    return (
      <MessageStrip className="Lives-timeline">No data found.</MessageStrip>
    );
  }
};
```

### LiveToolbar クラスコンポーネントの関数コンポーネント化

最後は、``LiveToolbar`` コンポーネントです。以下の変更を加えていきます。

- ``class`` を ``React.FC`` 型へ変更（FC は Function Component の略）
- パラメータ ``props`` が ``LiveToolbarProps`` 型であることを明示
- ``LiveToolbarProps`` 型を宣言しておく
- ``handleFilterTextChange`` 関数のパラメータが ``Ui5CustomEvent<HTMLInputElement>`` 型であることを明示
- ``Ui5CustomEvent`` をインポートしておく
- 自身が関数となったため、``this`` 参照を削除してスッキリと

```jsx:LiveToolbar&nbsp;コンポーネント（変更前）
class LiveToolbar extends React.Component {
  handleFilterTextChange = (e) => {
    this.props.onFilterTextChange(e.target.value);
  };

  render() {
    return (
      <ShellBar
        primaryTitle="Lives in My Life"
        logo={
          <img
            alt="SAPUI5 Logo"
            src="https://sap.github.io/ui5-webcomponents/assets/images/ui5.png"
          />
        }
        profile={
          <Avatar image="https://avatars0.githubusercontent.com/u/25473342?s=400&u=b399ebf80c62121616c0435bed3f3c39b4fc9c9b&v=4" />
        }
        searchField={
          <Input
            value={this.props.filterText}
            placeholder="Please input ..."
            onInput={this.handleFilterTextChange}
          />
        }
      />
    );
  }
}
```
```jsx:LiveToolbar&nbsp;コンポーネント（変更後）
import { Ui5CustomEvent } from '@ui5/webcomponents-react/interfaces/Ui5CustomEvent';
// 途中省略
type LiveToolbarProps = {
  filterText: string;
  onFilterTextChange: (filterText: string) => void;
};
// 途中省略
const LiveToolbar: React.FC<LiveToolbarProps> = (props) => {
  const handleFilterTextChange = (e: Ui5CustomEvent<HTMLInputElement>) => {
    props.onFilterTextChange(e.target.value);
  };

  return (
    <ShellBar
      primaryTitle="Lives in My Life"
      logo={
        <img
          alt="SAPUI5 Logo"
          src="https://sap.github.io/ui5-webcomponents/assets/images/ui5.png"
        />
      }
      profile={
        <Avatar image="https://avatars0.githubusercontent.com/u/25473342?s=400&u=b399ebf80c62121616c0435bed3f3c39b4fc9c9b&v=4" />
      }
      searchField={
        <Input
          value={props.filterText}
          placeholder="Please input ..."
          onInput={handleFilterTextChange}
        />
      }
    />
  );
};
```

以上で、``App``、``LiveToolbar``、``LiveTimeline``、合わせて 3 つのクラスコンポーネントの関数コンポーネント化が完了しました。関数コンポーネント化しただけの状態では、幾つかのエラーが残っていますので、それらを潰していきましょう。

## 残っているエラーをコツコツと修正する

以下のエラーを順番に解消していきます。

- Theme の型定義ファイルが見つからない
- TimelineItem に itemName プロパティを指定することはできない
- Avatar に image プロパティを指定することはできない

### エラーその1：Theme の型定義ファイルが見つからない

まずは、型に厳密な TypeScript らしいエラーから対応していきます。TypeScript 対応あるあるですが、[https://github.com/SAP/ui5-webcomponents-react/discussions/1136](https://github.com/SAP/ui5-webcomponents-react/discussions/1136) で回答されているように、一部のコンポーネントについては、TypeScript 型定義ファイルがまだ用意されていないようです。ここで推奨されているように、自分で型定義ファイルを用意しましょう。

```jsx:エラー発生箇所
import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme.js";
```

```text:エラーメッセージ
Could not find a declaration file for module '@ui5/webcomponents-base/dist/config/Theme.js'. '/Users/shunichirock/Repositories/lives-in-my-life-ts/node_modules/@ui5/webcomponents-base/dist/config/Theme.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/ui5__webcomponents-base` if it exists or add a new declaration (.d.ts) file containing `declare module '@ui5/webcomponents-base/dist/config/Theme.js';`ts(7016)
```

``src/@types`` フォルダに ``Theme.d.ts`` ファイルを追加し、モジュールを明示的に宣言することで、エラーが解消されます。``tsconfig.json`` で ``"strict": false`` を指定することでもエラーは解消されますが、せっかく TypeScript を利用するのですから、厳密にまいりましょう。

```javascript:@src/types/Theme.d.ts
declare module "@ui5/webcomponents-base/dist/config/Theme";
```

ついでに ``import`` 時の拡張子（``.js``）も削除しておきます。

```diff_jsx:ソース修正箇所
- import "@ui5/webcomponents/dist/Assets.js";
- import "@ui5/webcomponents-react/dist/Assets.js";
- import "@ui5/webcomponents-fiori/dist/Assets.js"; // Only if using the @ui5/webcomponents-fiori package
- import "@ui5/webcomponents-icons/dist/Assets.js"; // Only if using the @ui5/webcomponents-icons package
- import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme.js";
+ import "@ui5/webcomponents/dist/Assets";
+ import "@ui5/webcomponents-react/dist/Assets";
+ import "@ui5/webcomponents-fiori/dist/Assets"; // Only if using the @ui5/webcomponents-fiori package
+ import "@ui5/webcomponents-icons/dist/Assets"; // Only if using the @ui5/webcomponents-icons package
+ import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme";
```

### エラーその2：TimelineItem に itemName プロパティを指定することはできない

こちらは、UI5 Web Components for React に限らず、ライブラリ全般あるあるですが、バージョンアップにより仕様が変わったようです。[最新のリファレンス](https://sap.github.io/ui5-webcomponents-react/) を確認してみましょう。

```text:エラーメッセージ
Type '{ children: Element; key: number; icon: string; itemName: string; subtitleText: string; }' is not assignable to type 'IntrinsicAttributes & Pick<TimelineItemPropTypes, "children" | "id" | "color" | "translate" | "hidden" | "dir" | "slot" | "style" | ... 252 more ... | "nameClickable"> & RefAttributes<...>'.
  Property 'itemName' does not exist on type 'IntrinsicAttributes & Pick<TimelineItemPropTypes, "children" | "id" | "color" | "translate" | "hidden" | "dir" | "slot" | "style" | ... 252 more ... | "nameClickable"> & RefAttributes<...>'.ts(2322)
```

[最新のリファレンス（TimelineItem）](https://sap.github.io/ui5-webcomponents-react/?path=/docs/data-display-timeline--default-story) によると、``itemName`` プロパティが廃止され、代わりに ``titleText`` プロパティが追加されたようですので、プロパティ名を変更します。これでエラーが解消されました。

```diff_jsx:エラー修正箇所
-               itemName={element.title}
+               titleText={element.title}
```


### エラーその3：Avatar に image プロパティを指定することはできない

こちらも、エラーその 2 と同様のエラーですね。[最新のリファレンス](https://sap.github.io/ui5-webcomponents-react/) を確認してみましょう。

```text:エラーメッセージ
Type '{ image: string; }' is not assignable to type 'IntrinsicAttributes & Pick<AvatarPropTypes, "children" | "id" | "color" | "translate" | "hidden" | "dir" | "slot" | "style" | "title" | ... 252 more ... | "initials"> & RefAttributes<...>'.
  Property 'image' does not exist on type 'IntrinsicAttributes & Pick<AvatarPropTypes, "children" | "id" | "color" | "translate" | "hidden" | "dir" | "slot" | "style" | "title" | ... 252 more ... | "initials"> & RefAttributes<...>'.ts(2322)
```

[最新のリファレンス（Avatar）](https://sap.github.io/ui5-webcomponents-react/?path=/docs/data-display-avatar--default-story) によると、``image`` プロパティは廃止され、子要素として指定するように変更されたようですので、``img``タグに変更します。これでエラーが解消されました。

```diff_jsx:エラー修正箇所
-         <Avatar image="https://avatars0.githubusercontent.com/u/25473342?s=400&u=b399ebf80c62121616c0435bed3f3c39b4fc9c9b&v=4" />
+         <Avatar>
+           <img
+             alt="Profile"
+             src="https://avatars0.githubusercontent.com/u/25473342?s=400&u=b399ebf80c62121616c0435bed3f3c39b4fc9c9b&v=4"
+           />
+         </Avatar>
```

## 動作確認してみる

これで全てのエラーが解消されましたので、早速、実行してみましょう。

```shell
yarn start
```

React アプリは正常に起動はしましたが、以下 2 点の最終調整が必要なようです。

- SAPUI5 ロゴのリンクが切れている
- カレンダーアイコンが表示されない

![screenshot-localhost_3000-2021.12.18-13_49_54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/9306d562-668c-f79c-3b75-af121cf197ce.png)

### 最終調整その1：SAPUI5 ロゴのリンクが切れている

こちらは、SAPUI5 公式のロゴへの URL リンクだったのですが、ファイル名が変更されたようです。最新パスを確認して修正します。細かいポイントですが、リソース名へのこだわりが垣間見えて好ましいですね。

```diff_jsx:エラー修正箇所
-         src="https://sap.github.io/ui5-webcomponents/assets/images/ui5.png"
+         src="https://sap.github.io/ui5-webcomponents/assets/images/ui5-logo.png"
```

### 最終調整その2：カレンダーアイコンが表示されない

[公式のリファレンス](https://sap.github.io/ui5-webcomponents/playground/getting-started/using-icons/) によると、``Assets`` ではなく ``AllIcons`` を指定するか、利用するアイコンを個別指定（今回は ``calendar``）するようです。バージョンアップの過程で、より分かりやすい名称に変更されたようです。こちらも好ましいですね。

```diff_jsx:エラー修正箇所
- import "@ui5/webcomponents-icons/dist/Assets"; // Only if using the @ui5/webcomponents-icons package
+ import "@ui5/webcomponents-icons/dist/AllIcons"; // Only if using the @ui5/webcomponents-icons package
```

### 最終調整結果はこちら

これにて SAPUI5 ロゴもカレンダーアイコンも正常に表示されました。これにて、参戦ライブ記録アプリ ``lives-in-my-life-ts`` の TypeScript 化は完了となります。

![screenshot-localhost_3000-2021.12.18-14_30_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319241/6d36d680-131d-a971-f25a-8a41d564e777.png)

ちなみに、変更後のソースコードは以下の通りです。（[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) と同様ですが）JSON 直書きだったり、日付データが yyyy/MM/dd 形式だったり、i18n 対応していなかったり、コード分割を検討していなかったり、色々と考えるべきことはありますが、お手軽な初期バージョンとして晒します。

```jsx:App.tsx
import {
  Avatar,
  FlexBox,
  FlexBoxAlignItems,
  FlexBoxDirection,
  FlexBoxJustifyContent,
  Input,
  MessageStrip,
  ShellBar,
  ThemeProvider,
  Timeline,
  TimelineItem,
} from "@ui5/webcomponents-react";
import React, { useState } from "react";
import { Ui5CustomEvent } from "@ui5/webcomponents-react/interfaces/Ui5CustomEvent";
import "./App.css";
import "@ui5/webcomponents/dist/Assets";
import "@ui5/webcomponents-react/dist/Assets";
import "@ui5/webcomponents-fiori/dist/Assets"; // Only if using the @ui5/webcomponents-fiori package
import "@ui5/webcomponents-icons/dist/calendar"; // Only if using the @ui5/webcomponents-icons package
import { setTheme } from "@ui5/webcomponents-base/dist/config/Theme";
setTheme("sap_fiori_3_dark");

type LiveTimelineProps = {
  filterText: string;
};

type LiveToolbarProps = {
  filterText: string;
  onFilterTextChange: (filterText: string) => void;
};

const LIVES = [
  {
    date: "1991/12/31",
    title: "METALLICA / EUROPE / TESLA / THUNDER - FINAL COUNTDOWN 1991",
    venue: "東京ドーム",
  },
  /* （以下、省略） */
];

const LiveTimeline: React.FC<LiveTimelineProps> = (props) => {
  const filteredLives = LIVES.filter((element) => {
    return (
      (element.date + element.title + element.venue)
        .toLocaleLowerCase()
        .indexOf(props.filterText.toLowerCase()) > -1
    );
  });

  if (filteredLives.length > 0) {
    return (
      <Timeline className="Lives-timeline">
        {filteredLives.map((element, index) => {
          return (
            <TimelineItem
              key={index}
              icon="calendar"
              titleText={element.title}
              subtitleText={element.date}
            >
              <div>{element.venue}</div>
            </TimelineItem>
          );
        })}
      </Timeline>
    );
  } else {
    return (
      <MessageStrip className="Lives-timeline">No data found.</MessageStrip>
    );
  }
};

const LiveToolbar: React.FC<LiveToolbarProps> = (props) => {
  const handleFilterTextChange = (e: Ui5CustomEvent<HTMLInputElement>) => {
    props.onFilterTextChange(e.target.value);
  };

  return (
    <ShellBar
      primaryTitle="Lives in My Life"
      logo={
        <img
          alt="SAPUI5 Logo"
          src="https://sap.github.io/ui5-webcomponents/assets/images/ui5-logo.png"
        />
      }
      profile={
        <Avatar>
          <img
            alt="Profile"
            src="https://avatars0.githubusercontent.com/u/25473342?s=400&u=b399ebf80c62121616c0435bed3f3c39b4fc9c9b&v=4"
          />
        </Avatar>
      }
      searchField={
        <Input
          value={props.filterText}
          placeholder="Please input ..."
          onInput={handleFilterTextChange}
        />
      }
    />
  );
};

const App = () => {
  const [filterText, setFilterText] = useState<string>("");
  const handleFilterTextChange = (filterText: string) => {
    setFilterText(filterText);
  };

  return (
    <ThemeProvider>
      <FlexBox
        style={{ width: "100%", height: "100%" }}
        direction={FlexBoxDirection.Column}
        justifyContent={FlexBoxJustifyContent.Center}
        alignItems={FlexBoxAlignItems.Center}
      >
        <LiveToolbar
          filterText={filterText}
          onFilterTextChange={handleFilterTextChange}
        />
        <LiveTimeline filterText={filterText} />
      </FlexBox>
    </ThemeProvider>
  );
};

export default App;
```

## さいごに

以前の投稿「[UI5 Web Components for React で参戦ライブを振り返る](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720)」から、早いもので約一年が経ちました。残念ながら一度もライブに参戦できておらず、``App.js`` の JSON 配列へのエントリ追加は実現しませんでした。その間に UI5 Web Components for React のバージョンは`` 0.12.3`` から ``0.20.3`` へ、for React のベースである UI5 Web Components のバージョンは、``1.0.0-rc.10`` から、``1.0.2`` に粛々とバージョンアップしました。歩みを止めないソフトウェア開発/コミュニティ貢献に、頭が下がる思いです。

以下は、[以前の投稿](https://qiita.com/shunichirock/items/1cca353d3d455fe3c720) からの引用ですが、UI5 Web Components が正式リリースされたことで、エンタープライズアプリケーションの世界でも採用しやすくなったのではないでしょうか？ 個人的には「for React」の方も一日も早く正式リリースされることを心待ちにしています。なにかと MUI（Material UI）を採用しがちですが、エンタープライズ向けには UI5 Web Components の優位性が際立ちます。自社のアセット開発だけでなく、クライアント向けにも積極採用していきたいと思います。

> エンタープライズアプリケーションの世界では、どのような UI テーマ（デザイン）を採用すべきか悩みどころですが、少なくとも SAP は Fiori という答えを用意してくれています。2013 年にリリースされた Fiori は、単なるモダン Web フレームワークではなく、SAP の統一的・横断的な UX として進化を遂げてきました。「Fiori にしておけば間違いない」というのは、（エンタープライズアプリケーションの世界では）大きな魅力です。機会があれば、積極的に採用していきたいと思います。

### 参考リンク

- https://sap.github.io/ui5-webcomponents-react/
- https://sap.github.io/ui5-webcomponents/