Skip to content

DNET_ReactFirstStep2

nishi_74322014 edited this page Sep 11, 2026 · 1 revision

Reactのファースト・ステップ2

概要

2026年、リブート(Vite + React + TypeScript + Tailwind)。

npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev

※ 生成AIのサポートを受けつつ実施

※ TypeScript については JavaScript の「AltJS」の該当節を参照。

詳細

新規

  • 最初の一行だけで実行まで行ける。
  • 「press h + enter to show help」も実行
PS > npm create vite@latest my-app -- --template react-ts
Need to install the following packages:
create-vite@9.0.3
Ok to proceed? (y) y


> npx
> create-vite my-app --template react-ts

│
◇  Install with npm and start now?
│  Yes
│
◇  Scaffolding project in D:\temp\my-app...
│
◇  Installing dependencies with npm...

added 172 packages, and audited 173 packages in 25s

49 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
│
◇  Starting dev server...

> my-app@0.0.0 dev
> vite


  VITE v8.0.2  ready in 940 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
h

  Shortcuts
  press r + enter to restart the server
  press u + enter to show server url
  press o + enter to open in browser
  press c + enter to clear console
  press q + enter to quit

以降

編集を開始

  • コマンドで

    cd my-app
    
  • VSCで
    my-appフォルダを開き編集できる

パッケージをインストール

  • 新規のインストール

    npm install xxxx
    
  • package.jsonから。

    npm install
    

サーバーを起動

npm run dev

もしくは...

外部からのアクセスを可能にする。

npm run dev -- --host

デバッグ

VSCでデバッグ設定を行ってRuntimeに接続する。

  • VSCodeで「デバッグと実行」(Ctrl+Shift+D / Cmd+Shift+D) を開く

  • 上部のドロップダウンで Debug Vite App (Chrome) を選択

  • .vscode/launch.json が生成されるので、URL(ポート番号)を修正する。

    {
        // IntelliSense を使用して利用可能な属性を学べます。
        // 既存の属性の説明をホバーして表示します。
        // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "type": "chrome",
                "request": "launch",
                "name": "localhost に対して Chrome を起動する",
                "url": "http://localhost:5173/",
                "webRoot": "${workspaceFolder}"
            }
        ]
    }
  • VSCode上の .tsx / .ts ファイルにブレークポイントを設定

  • 再度「デバッグと実行」の「▶」再生ボタン または F5 でChromeを起動し、デバッグ接続

ビルド&デプロイ

  • ビルド

    npm run build
    
  • デプロイ

    xcopy /E /Y "dist" "配置先"
    

Viteとは?

フロントエンドの開発を面倒な設定なしで TypeScript、JSX、CSS などをすぐに使い始められるツール
(TypeScript / JSX については JavaScript の「AltJS」の該当節を参照)

プラグインシステム

ほとんどの追加機能(フレームワーク対応、カスタム変換、開発サーバーの拡張など)をプラグインで実現

  • webpack時代に比べてプラグインの作成・共有が簡単で、開発者体験(DX)が非常に良い。
  • フレームワーク(VueReact、Svelte、Solidなど)もViteプラグインとして統合される。
  • プロジェクト作成時にスキャフォールディングで vite.config.js が生成・編集される。
  • 以降は、ほとんどの場合、自分で vite.config.js を直接編集する。

開発サーバー

  • esbuildを活用した高速トランスパイルで起動が速い

  • 本番ビルドはRollup(現在はRolldownに移行傾向)で最適化

    • Rollup:2015年頃にリリースされたバンドラ
    • Rolldown:Rust製の次世代バンドラ
  • 「開発は速く、本番も高品質」というバランスが取れる。

HMR

  • HMR (Hot Module Replacement)
  • ファイルを編集した時に効率よくブラウザに反映

最適化

Rollup/Rolldownに、バンドル、コード分割、minifyなどが同梱される(一部プラグイン)。

Tailwindとは?

  • モダンなWeb開発で非常に人気のある Utility-First なCSSフレームワーク
  • あらかじめ用意された小さなUtility-Classを組み合わせてデザインを作る。
  • Utility-First:CSSにおいて小さな Utility-Class をあらかじめ大量に定義し、それらを組み合わせることでデザインを構築する設計手法。

インストール(Tailwind)

npm install tailwindcss
npm install @tailwindcss/forms

設定(Tailwind)

index.css や globals.css などに追記

@import "tailwindcss";
@plugin "@tailwindcss/forms";

導入

AI活用(https://claude.ai/chat )で、殆どWebを参照せず、作業を進めることが出来た(Sonnet4.6)。

ツール類の導入手順

ココまでの導入手順もAIに生成させている。

レイアウト変更

以下はプロンプト

以下で生成したコードを修正して3分割のヘッダ、左メニュー、メインエリアの画面を作成したい。
なお、左メニューのリンクを押下するとメインエリアが画面遷移するものとする。

npm create vite@latest my-app -- --template react-ts

修正内容を教えてください。

以下は、出力から修正ポイントを抜粋したもの。

npm install

npm install react-router-dom

App.tsx、App.css

src/App.tsx、App.css

  • App.tsx

    import { BrowserRouter, Routes, Route } from 'react-router-dom'
    import Header from './components/Header'
    import Sidebar from './components/Sidebar'
    import Home from './pages/Home'
    import About from './pages/About'
    import Settings from './pages/Settings'
    import './App.css'
    
    function App() {
      return (
        <BrowserRouter>
          <div className="app-container">
            <Header />
            <div className="body-container">
              <Sidebar />
              <main className="main-area">
                <Routes>
                  <Route path="/" element={<Home />} />
                  <Route path="/about" element={<About />} />
                  <Route path="/settings" element={<Settings />} />
                </Routes>
              </main>
            </div>
          </div>
        </BrowserRouter>
      )
    }
    
    export default App
  • App.css

    /* src/App.css */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    .app-container {
      display: flex;
      flex-direction: column;
      height: 100vh;
    }
    
    /* ヘッダー */
    header {
      height: 60px;
      background-color: #1e293b;
      color: white;
      display: flex;
      align-items: center;
      padding: 0 24px;
      font-size: 1.2rem;
      font-weight: bold;
      flex-shrink: 0;
    }
    
    /* ヘッダー下の横並びエリア */
    .body-container {
      display: flex;
      flex: 1;
      overflow: hidden;
    }
    
    /* 左メニュー */
    nav.sidebar {
      width: 220px;
      background-color: #f1f5f9;
      border-right: 1px solid #e2e8f0;
      padding: 16px 0;
      flex-shrink: 0;
      overflow-y: auto;
    }
    
    nav.sidebar a {
      display: block;
      padding: 12px 24px;
      text-decoration: none;
      color: #334155;
      font-size: 0.95rem;
      transition: background 0.2s;
    }
    
    nav.sidebar a:hover,
    nav.sidebar a.active {
      background-color: #e0e7ef;
      color: #1e40af;
      font-weight: 600;
    }
    
    /* メインエリア */
    .main-area {
      flex: 1;
      padding: 32px;
      overflow-y: auto;
      background-color: #ffffff;
    }

Header、Sidebar.tsx

src/components/Header、Sidebar.tsx

  • Header.tsx

    export default function Header() {
      return <header>My App</header>
    }
  • Sidebar.tsx

    import { NavLink } from 'react-router-dom'
    
    export default function Sidebar() {
      return (
        <nav className="sidebar">
          <NavLink to="/" end className={({ isActive }) => isActive ? 'active' : ''}>
            Home
          </NavLink>
          <NavLink to="/about" className={({ isActive }) => isActive ? 'active' : ''}>
            About
          </NavLink>
          <NavLink to="/settings" className={({ isActive }) => isActive ? 'active' : ''}>
            Settings
          </NavLink>
        </nav>
      )
    }

Home、About、Settings.tsx

src/pages/Home、About、Settings.tsx

  • Home.tsx

    export default function Home() {
      return <div><h1>ホーム</h1><p>ホームページです。</p></div>
    }
  • About.tsx

    export default function About() {
      return <div><h1>アバウト</h1><p>アバウトページです。</p></div>
    }
  • Settings.tsx

    export default function Settings() {
      return <div><h1>設定</h1><p>設定ページです。</p></div>
    }

既存の載替

ライブラリ系JS

Counter画面

  • Counter.jsコンポーネントをTSXに変換
  • App.tsx、Sidebar.tsxに組み込む。

FetchData画面

  • FetchData.jsコンポーネントをTSXに変換

  • App.tsx、Sidebar.tsxに組み込む。

  • WebAPI呼び出しを行って実行をテスト。

  • 修正

    • WebAPI(SampleDataController.cs)をRST上に復元
    • FetchDataの処理をページング対応させた。

CrudSample画面

  • CrudSample.jsコンポーネントをTSXに変換
  • App.tsx、Sidebar.tsxに組み込む。
  • WebAPI呼び出しを行って実行をテスト。

認証コード実装

  • SignIn、RedirectOfAuth.jsコンポーネントをTSXに変換
    • Header.tsxにSignInを組み込む。
    • App.tsxにRedirectOfAuthを組み込む。
  • 認証テストを実行し、デバッガでデバッグ、修正して完了。

CrudSample2画面

  • CrudSample画面をコンポーネント分割した版。
  • Input、DDL、Button、Output.tsxをコンポーネント化

リファクタリング

fetch

fetchとはWebAPI呼び出し処理のこと。

  • プロンプト

    AIに共通関数を提案させ、
    以下の2つの関数から共通部を切り出せますか?
    切り出した関数は、ライブラリ化して2つの関数内から呼び出します。
    
      selectAll_DT() {
        this.setState({ message: '' });
    
        const method = 'POST';
        const headers = oauth_oidc.createHttpRequestHeader(false);
        const body =
          'ddlDap=' + this.state.ddl.ddlDap
          + '&ddlMode1=' + this.state.ddl.ddlMode1
          + '&ddlMode2=' + this.state.ddl.ddlMode2
          + '&ddlExRollback=' + this.state.ddl.ddlExRollback;
    
        fetch(constants.CrudSampleRootUrl + 'SelectAll_DT', { method, headers, body })
          .then(oauth_oidc.fetchStatusHandler)
          .then(response => response.json())
          .then(data => {
            if (data.result) {
              this.setState({ message: '', shippers: data.result, loading: false });
            } else if (data.errorMSG) {
              this.setState({ message: JSON.stringify(data.errorMSG) });
            } else if (data.exceptionMSG) {
              this.setState({ message: JSON.stringify(data.exceptionMSG) });
            }
          })
          .catch((error: Error) => {
            this.setState({ message: JSON.stringify(error.stack) });
          });
      }
    
      delete() {
        this.setState({ message: '' });
    
        const method = 'POST';
        const headers = oauth_oidc.createHttpRequestHeader(true);
        const body = JSON.stringify({
          ddlDap: this.state.ddl.ddlDap,
          ddlMode1: this.state.ddl.ddlMode1,
          ddlMode2: this.state.ddl.ddlMode2,
          ddlExRollback: this.state.ddl.ddlExRollback,
          shipper: {
            shipperID: this.state.shipper.shipperID,
            companyName: '',
            phone: '',
          },
        });
    
        fetch(constants.CrudSampleRootUrl + 'Delete', { method, headers, body })
          .then(oauth_oidc.fetchStatusHandler)
          .then(response => response.json())
          .then(data => {
            if (data.message) {
              this.setState({ message: data.message });
            } else if (data.errorMSG) {
              this.setState({ message: JSON.stringify(data.errorMSG) });
            } else if (data.exceptionMSG) {
              this.setState({ message: JSON.stringify(data.exceptionMSG) });
            }
          })
          .catch((error: Error) => {
            this.setState({ message: JSON.stringify(error.stack) });
          });
      }
    
  • 出力
    ちなみに getFetch も準備した。

    type SuccessHandler = (data: Record<string, unknown>) => void;
    
    /**
     * 共通のfetchラッパー
     * @param url         リクエスト先URL
     * @param headers     HTTPヘッダー
     * @param body        リクエストボディ
     * @param onSuccess   成功時のハンドラ(data.errorMSG / data.exceptionMSG 以外の処理)
     * @param setMessage  メッセージをセットする関数
     */
    export function postFetch(
      url: string,
      headers: HeadersInit,
      body: BodyInit,
      onSuccess: SuccessHandler,
      setMessage: (msg: string) => void,
    ): void {
      setMessage('');
    
      fetch(url, { method: 'POST', headers, body })
        .then(oauth_oidc.fetchStatusHandler)
        .then(response => response.json())
        .then((data: Record<string, unknown>) => {
          if (data.errorMSG) {
            setMessage(JSON.stringify(data.errorMSG));
          } else if (data.exceptionMSG) {
            setMessage(JSON.stringify(data.exceptionMSG));
          } else {
            onSuccess(data);
          }
        })
        .catch((error: Error) => {
          setMessage(JSON.stringify(error.stack));
        });
    }
  • ソレを使用するように書き直した。

    • 拡張はコールバックにて行う。
    • 幾つかの問題は生成AIで修正
    // 元のクラス内
    
    selectAll_DT() {
      const headers = oauth_oidc.createHttpRequestHeader(false);
      const body =
        'ddlDap=' + this.state.ddl.ddlDap
        + '&ddlMode1=' + this.state.ddl.ddlMode1
        + '&ddlMode2=' + this.state.ddl.ddlMode2
        + '&ddlExRollback=' + this.state.ddl.ddlExRollback;
    
      postFetch(
        constants.CrudSampleRootUrl + 'SelectAll_DT',
        headers,
        body,
        (data) => {
          if (data.result) {
            this.setState({ message: '', shippers: data.result, loading: false });
          }
        },
        (msg) => this.setState({ message: msg }),
      );
    }
    
    delete() {
      const headers = oauth_oidc.createHttpRequestHeader(true);
      const body = JSON.stringify({
        ddlDap: this.state.ddl.ddlDap,
        ddlMode1: this.state.ddl.ddlMode1,
        ddlMode2: this.state.ddl.ddlMode2,
        ddlExRollback: this.state.ddl.ddlExRollback,
        shipper: {
          shipperID: this.state.shipper.shipperID,
          companyName: '',
          phone: '',
        },
      });
    
      postFetch(
        constants.CrudSampleRootUrl + 'Delete',
        headers,
        body,
        (data) => {
          if (data.message) {
            this.setState({ message: data.message });
          }
        },
        (msg) => this.setState({ message: msg }),
      );
    }

CSS

デザインをTailwind 拡張にするように修正。

  • 全体的に編集中のコードの提示では上手く行かないことが多かった。
  • ChromeのF12(DevTools)で「Elements」パネルや *.css の情報をプロンプトに入れると良い。

参考

移行メモ

  • 「全体的に編集中のコードの提示では上手く行かなことが多かった。」→ 「上手く行かないことが多かった。」に修正した。
  • 「ChromeのF12で「Application」の「Element」」は、Chrome DevTools では 「Elements」が「Application」の下ではなく独立したパネルであるため、 「ChromeのF12(DevTools)で「Elements」パネル」に修正した。
  • 生成AIの出力をそのまま貼り付けたことによりコードの先頭に コード・ブロックの言語名が混入していた箇所(css@import "tailwindcss";)は、 言語名を除去してコード・フェンスの言語指定に移した。
  • 元 Wiki の [[Webpack]](先頭大文字)は元 Wiki 側にページが存在しない リンク切れだったため、実在する webpack へのリンクとした。
  • 同名の見出し(「インストール」「設定」)が複数あり GitHub Wiki でアンカが衝突するため、括弧で文脈を補って一意にした。
  • PukiWiki のページ内アンカ(#xxxxxxxx)は GitHub Wiki では再現できないため、 同一ページ内のアンカは見出しから生成されるアンカに張り替え、 他ページのアンカを指すリンクは「〜(ページ名 の該当節を参照)」の形に置き換えた。

Tags: 移行, React, Vite, TypeScript, Tailwind, react-router-dom, 生成AI, リブート

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally