ASP.NET Core の React テンプレート使いにくい問題

2021/04/05に公開1

概要

C#って良いですよね。ASP.NETって良いですよね。
と常々思っているので、バックエンドにはASP.NETを使いたいのです。

現状、ASP.NET + Reactを使う場合、以下の選択肢があります。

  • バックエンドはASP.NET Web APIで作成、フロントエンドはCreate React Appなどで別に作成。
  • ASP.NET CoreのReactテンプレートを使用する。

前者を選んだ場合、バックエンドとフロントエンドで開発サーバーが別々に立つので、
CORSの設定など、ちょっとメンドイです。

後者を選んだ場合、バックエンドもフロントエンドも同一の開発サーバーで動くし、
Startup.csに必要なコードが書いてあるので良い感じです。

ただし、React側のコードや設定が若干古い、というかアレな感じなので厳しいのです。
今回は、このReact側の若干アレな部分をなんとかしよう、という話です。

はじめよう

今回はVisual Studio 2019を使ってプロジェクト作成していきます。

  1. Visual Studio 2019を起動して、画面右側の新しいプロジェクトの作成を選択します。
  2. 検索窓にReactと入れると、2個ヒットするので、React.js での ASP.NET Coreを選択します。下の方ですね。
  3. プロジェクト名とかは適当に入れて、作成しましょう。
    ターゲットフレームワークは.NET 5にしてみます。

package.jsonを見てみよう

プロジェクトができたので、ClientApp\package.jsonを見てみます。

package.json
package.json
{
  "name": "dotnet_core_react_typescript_template",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "bootstrap": "^4.1.3",
    "jquery": "^3.5.1",
    "merge": "^1.2.1",
    "oidc-client": "^1.9.0",
    "react": "^16.0.0",
    "react-dom": "^16.0.0",
    "react-router-bootstrap": "^0.25.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "^3.4.4",
    "reactstrap": "^8.4.1",
    "rimraf": "^2.6.2"
  },
  "devDependencies": {
    "ajv": "^6.9.1",
    "cross-env": "^5.2.0",
    "typescript": "^3.7.5",
    "eslint": "^6.8.0",
    "eslint-config-react-app": "^5.2.0",
    "eslint-plugin-flowtype": "^4.6.0",
    "eslint-plugin-import": "^2.20.1",
    "eslint-plugin-jsx-a11y": "^6.2.3",
    "eslint-plugin-react": "^7.18.3",
    "nan": "^2.14.1"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "scripts": {
    "start": "rimraf ./build && react-scripts start",
    "build": "react-scripts build",
    "test": "cross-env CI=true react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "lint": "eslint ./src/"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
  • 全体的にバージョンが古い。
  • Reactなのにjqueryが入っている(bootstrapの関係でしょうが…)
  • TypeScript入ってるのに(古いし、ファイルは全て*.jsだが)、eslint-plugin-flowtype

みたいな感じで、ちょっとアレな感じがします。

目標

ということで、今回の目標をこんな感じで立てました。

  1. npmモジュールの最新化、整理
  2. functionコンポーネント化(React 16.0.0はClassコンポーネントのみ対応)
  3. TypeScript化

やっていきましょう。

npmモジュールの最新化

npm update 一括とかでググれば良いんですが、以下の記事を参考にしました。ありがたや。
https://qiita.com/msykd/items/7f35a914f95e227caf8e

npm-check-updatesをインストールして、

npm install -g npm-check-updates 

一括アップデートします。

ncu -u
npm update
package.json
{
  "name": "dotnet_core_react_typescript_template",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "bootstrap": "^4.6.0",
    "jquery": "^3.6.0",
    "merge": "^2.1.1",
    "oidc-client": "^1.11.5",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-bootstrap": "^0.25.0",
    "react-router-dom": "^5.2.0",
    "react-scripts": "^4.0.3",
    "reactstrap": "^8.9.0",
    "rimraf": "^3.0.2"
  },
  "devDependencies": {
    "ajv": "^8.0.5",
    "cross-env": "^7.0.3",
    "typescript": "^4.2.3",
    "eslint": "^7.23.0",
    "eslint-config-react-app": "^6.0.0",
    "eslint-plugin-flowtype": "^5.4.0",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react": "^7.23.1",
    "nan": "^2.14.2"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "scripts": {
    "start": "rimraf ./build && react-scripts start",
    "build": "react-scripts build",
    "test": "cross-env CI=true react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "lint": "eslint ./src/"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }

最新化されましたね。
あとでパッケージの追加もあるので、一旦ここまでにして次に進みます。

functionコンポーネント化

こんな感じで愚直にやりましょう。

Counter.js(Before)
import React, { Component } from 'react';

export class Counter extends Component {
  static displayName = Counter.name;

  constructor(props) {
    super(props);
    this.state = { currentCount: 0 };
    this.incrementCounter = this.incrementCounter.bind(this);
  }

  incrementCounter() {
    this.setState({
      currentCount: this.state.currentCount + 1
    });
  }

  render() {
    return (
      <div>
        <h1>Counter</h1>

        <p>This is a simple example of a React component.</p>

        <p aria-live="polite">Current count: <strong>{this.state.currentCount}</strong></p>

        <button className="btn btn-primary" onClick={this.incrementCounter}>Increment</button>
      </div>
    );
  }
}
Counter.js(After)
import React, { useState } from 'react';

export const Counter = () => {
  const [currentCount, setCurrentCount] = useState(0);

  const incrementCounter = () => {
    setCurrentCount(currentCount + 1);
  };

  return (
    <div>
      <h1>Counter</h1>
      <p>This is a simple example of a React component.</p>
      <p aria-live="polite">
        Current count: <strong>{currentCount}</strong>
      </p>
      <button className="btn btn-primary" onClick={incrementCounter}>
        Increment
      </button>
    </div>
  );
};

TypeScript化

npmの追加

devDependenciesにTypeScriptがあるのに、@types系がないので、追加していきます。

npm i --save-dev @types/react @types/react-dom @types/react-router-dom

コンポーネントのTypeScript化

これも愚直にやりましょう。

Counter.js(Before)
import React, { useState } from 'react';

export const Counter = () => {
  const [currentCount, setCurrentCount] = useState(0);

  const incrementCounter = () => {
    setCurrentCount(currentCount + 1);
  };

  return (
    <div>
      <h1>Counter</h1>
      <p>This is a simple example of a React component.</p>
      <p aria-live="polite">
        Current count: <strong>{currentCount}</strong>
      </p>
      <button className="btn btn-primary" onClick={incrementCounter}>
        Increment
      </button>
    </div>
  );
};
Counter.tsx(After)
import React, { FC, useState } from 'react';

export const Counter: FC = () => {
  const [currentCount, setCurrentCount] = useState(0);

  const incrementCounter = () => {
    setCurrentCount(currentCount + 1);
  };

  return (
    <div>
      <h1>Counter</h1>
      <p>This is a simple example of a React component.</p>
      <p aria-live="polite">
        Current count: <strong>{currentCount}</strong>
      </p>
      <button className="btn btn-primary" onClick={incrementCounter}>
        Increment
      </button>
    </div>
  );
};

package.jsonの整理

不要そうなパッケージを消して、最終的にはこんな感じにしました。
jqueryは消しても大丈夫そう?

package.json
{
  "name": "dotnet_core_react_typescript_template",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "bootstrap": "^4.6.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-router-dom": "^5.2.0",
    "react-scripts": "^4.0.3",
    "reactstrap": "^8.9.0",
    "rimraf": "^3.0.2"
  },
  "devDependencies": {
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "@types/react-router-dom": "^5.1.7",
    "cross-env": "^7.0.3",
    "eslint": "^7.23.0",
    "eslint-config-react-app": "^6.0.0",
    "eslint-plugin-react": "^7.23.1",
    "typescript": "^4.2.3"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "scripts": {
    "start": "rimraf ./build && react-scripts start",
    "build": "react-scripts build",
    "test": "cross-env CI=true react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "lint": "eslint ./src/"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

感想

まぁまぁ大変でしたが、出来て良かったです。
フロントのコード編集は、Visual Studio 2019よりVisual Studio Codeの方が
やりやすいですねー。

今回作ったテンプレートは、以下にあります。
cloneしてもらえれば、そのまま実行できるはずです。

https://github.com/obuchi3/dotnet-core-react-typescript-template

Discussion

junerjuner

bootstrap 5 以上なら jquery 消しても大丈夫ですね。