「[[.NET 開発基盤部会 Wiki>http://dotnetdevelopmentinfrastructure.osscons.jp]]」は、「[[Open棟梁Project>https://github.com/OpenTouryoProject/]]」,「[[OSSコンソーシアム .NET開発基盤部会>https://www.osscons.jp/dotNetDevelopmentInfrastructure/]]」によって運営されています。

-戻る([[React]] -> [[Redux]])
--...[[Reactの5thステップ]]
--Reactのファースト・ステップ2
--[[Reactのセカンド・ステップ2]]
--[[Reactのサード・ステップ2]]

*目次 [#xb7db3bd]
#contents

*概要 [#z850fc47]
2026年、リブート([[Vite>#y04513b4]] + [[React]] + [[TypeScript>JavaScript#kf4fe370]] + [[Tailwind>#f3514589]])。
 npm create vite@latest my-app -- --template react-ts
 cd my-app
 npm install
 npm run dev

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

*詳細 [#ad3d14d0]

**新規 [#j2880d76]
-最初の一行だけで実行まで行ける。
-「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

**以降 [#m4a63503]

***編集を開始 [#x7c18d45]
-コマンドで
 cd my-app

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

***パッケージをインストール [#tbd7edce]
-新規のインストール
  npm install xxxx

-package.jsonから。
 npm install

***サーバーを起動 [#a6c057bc]
 npm run dev

***もしくは... [#a41bbecb]
外部からのアクセスを可能にする。
 npm run dev -- --host

***デバッグ [#i39a4219]
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を起動し、デバッグ接続

***ビルド&デプロイ [#rd5cd6fd]
-ビルド
 npm run build

-デプロイ
 xcopy /E /Y "dist" "配置先"

**Viteとは? [#y04513b4]
フロントエンドの開発を面倒な設定なしで[[TypeScript>JavaScript#kf4fe370]], [[JSX>JavaScript#gf642367]], [[CSS]]などをすぐに使い始められるツール

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

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

***開発サーバー [#qd512ea6]
-esbuildを活用した高速トランスパイルで起動が速い

-本番ビルドはRollup(現在はRolldownに移行傾向)で最適化
--Rollup:2015年頃にリリースされたバンドラ
--Rolldown:Rust製の次世代バンドラ

-「開発は速く、本番も高品質」というバランスが取れる。

***HMR [#y5eaca72]
-HMR (Hot Module Replacement)
-ファイルを編集した時に効率よくブラウザに反映

***最適化 [#dba5c548]
Rollup/Rolldownに、バンドル、コード分割、minifyなどが同梱される(一部プラグイン)。

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

***インストール [#qd808e7d]
 npm install tailwindcss
 npm install @tailwindcss/forms

***設定 [#j65764d8]
index.css や globals.css などに追記
 css@import "tailwindcss";
 @plugin "@tailwindcss/forms";

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

**ツール類の導入手順 [#g076258f]
[[ココまでの導入手順>#ad3d14d0]]もAIに生成させている。

**レイアウト変更 [#p14be706]
以下はプロンプト

 以下で生成したコードを修正して3分割のヘッダ、左メニュー、メインエリアの画面を作成したい。
 なお、左メニューのリンクを押下するとメインエリアが画面遷移するものとする。
 
 npm create vite@latest my-app -- --template react-ts
 
 修正内容を教えてください。

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

***npm install [#i836d582]
 npm install react-router-dom

***App.tsx、App.css [#x211e60b]
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 [#gb83e5a2]
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 [#m999ff6e]
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>
 }

**既存の載替 [#k8e3cf6c]

***ライブラリ系JS [#zbf64ee9]
-移行元(react_template)
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/old/react_template/src/common.js
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/old/react_template/src/const.js
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/old/react_template/src/touryo/common.js
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/old/react_template/src/touryo/oauth_oidc.js

-移行先(vite-react-ts)
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/vite-react-ts/src/common.ts
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/vite-react-ts/src/const.ts
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/vite-react-ts/src/touryo/common.ts
--https://github.com/OpenTouryoProject/FrontendTemplates/blob/develop/UI/SPA/React/vite-react-ts/src/touryo/oauth_oidc.ts

***Counter画面 [#o43dad56]
-Counter.jsコンポーネントをTSXに変換
-App.tsx、Sidebar.tsxに組み込む。

***FetchData画面 [#md85089b]
-FetchData.jsコンポーネントをTSXに変換
-App.tsx、Sidebar.tsxに組み込む。
-WebAPI呼び出しを行って実行をテスト。

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

***CrudSample画面 [#t3053035]
-CrudSample.jsコンポーネントをTSXに変換
-App.tsx、Sidebar.tsxに組み込む。
-WebAPI呼び出しを行って実行をテスト。

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

***CrudSample2画面 [#z3083b84]
-CrudSample画面をコンポーネント分割した版。
-Input、DDL、Button、Output.tsxをコンポーネント化

**リファクタリング [#qee70242]

***fetch [#ac5e83f5]
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 [#je0cdf22]
デザインをTailwind 拡張にするように修正。
-全体的に編集中のコードの提示では上手く行かなことが多かった。
-ChromeのF12で「Application」の「Element」や「*.css」の情報をプロンプトに入れると良い。

*参考 [#v9ce761a]
-Vite ってよく聞くけど何なんですか? あれは~
https://zenn.dev/comm_vue_nuxt/articles/what-is-vite

-FrontendTemplates/UI/SPA/React at develop · OpenTouryoProject/FrontendTemplates~
https://github.com/OpenTouryoProject/FrontendTemplates/tree/develop/UI/SPA/React

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS